diff --git a/apps/desktop/src/app/chat/composer/controls.test.tsx b/apps/desktop/src/app/chat/composer/controls.test.tsx
index bbee021649..d5694b6144 100644
--- a/apps/desktop/src/app/chat/composer/controls.test.tsx
+++ b/apps/desktop/src/app/chat/composer/controls.test.tsx
@@ -72,6 +72,7 @@ describe('HUD mode', () => {
expect(screen.getByLabelText('Voice dictation')).toBeTruthy()
expect(screen.getByLabelText('Read replies aloud')).toBeTruthy()
expect(screen.queryByLabelText('Exit HUD mode')).toBeNull()
+ expect(screen.queryByLabelText('Reset HUD size and position')).toBeNull()
expect(screen.queryByLabelText('Voice')).toBeNull()
})
@@ -80,6 +81,7 @@ describe('HUD mode', () => {
renderControls()
expect(screen.getByLabelText('Voice')).toBeTruthy()
+ expect(screen.getByLabelText('Reset HUD size and position')).toBeTruthy()
expect(screen.getByLabelText('Exit HUD mode')).toBeTruthy()
// Folded away, not duplicated — the whole point is the row's width back.
diff --git a/apps/desktop/src/app/chat/composer/controls.tsx b/apps/desktop/src/app/chat/composer/controls.tsx
index c48e3eb21b..388df3cdc0 100644
--- a/apps/desktop/src/app/chat/composer/controls.tsx
+++ b/apps/desktop/src/app/chat/composer/controls.tsx
@@ -7,7 +7,7 @@ import { useI18n } from '@/i18n'
import { triggerHaptic } from '@/lib/haptics'
import { AudioLines, Ear, EarOff, iconSize, Layers3, Loader2, Square, Volume2, VolumeX } from '@/lib/icons'
import { cn } from '@/lib/utils'
-import { $hudMode, closeHud } from '@/store/hud'
+import { $hudMode, closeHud, resetHudLayout } from '@/store/hud'
import { $wakeWord, toggleWakeWord } from '@/store/wake-word'
import { ACTIVE_ICON_BTN, GHOST_ICON_BTN, PRIMARY_ICON_BTN } from './control-classes'
@@ -173,27 +173,41 @@ export function ComposerControls({
the surface, paid for in every state, for a control that is invisible
until hovered. Here it costs no reserved space and sits with the other
things you can press. */}
- {hudMode ? : null}
+ {hudMode ? : null}
)
}
-function ExitHudButton() {
+function HudWindowButtons() {
const { t } = useI18n()
return (
-
-
-
+ <>
+
+
+
+
+
+
+ >
)
}
diff --git a/apps/desktop/src/i18n/ar.ts b/apps/desktop/src/i18n/ar.ts
index 2bc76807ab..0f18ac2a7b 100644
--- a/apps/desktop/src/i18n/ar.ts
+++ b/apps/desktop/src/i18n/ar.ts
@@ -218,6 +218,7 @@ export const ar = defineLocale({
openStarmap: 'فتح خريطة الذاكرة',
enterHud: 'وضع HUD',
exitHud: 'إنهاء وضع HUD',
+ resetHudLayout: 'إعادة تعيين حجم HUD وموضعه',
layoutEditor: 'محرر التخطيط',
layoutEditorTitle: modifier => `محرر التخطيط — انقر مع ${modifier} لإعادة ضبط التخطيط`
},
diff --git a/apps/desktop/src/i18n/en.ts b/apps/desktop/src/i18n/en.ts
index 65c9078a88..9b55af9d6e 100644
--- a/apps/desktop/src/i18n/en.ts
+++ b/apps/desktop/src/i18n/en.ts
@@ -251,6 +251,7 @@ export const en: Translations = {
openStarmap: 'Open memory graph',
enterHud: 'HUD mode',
exitHud: 'Exit HUD mode',
+ resetHudLayout: 'Reset HUD size and position',
layoutEditor: 'Layout editor',
layoutEditorTitle: mod => `Layout editor — ${mod}-click resets the layout`
},
diff --git a/apps/desktop/src/i18n/ja.ts b/apps/desktop/src/i18n/ja.ts
index 4c339562a8..b4d1047d00 100644
--- a/apps/desktop/src/i18n/ja.ts
+++ b/apps/desktop/src/i18n/ja.ts
@@ -249,7 +249,8 @@ export const ja = defineLocale({
muteHaptics: '触覚フィードバックをオフ',
unmuteHaptics: '触覚フィードバックをオン',
openSettings: '設定を開く',
- openStarmap: 'メモリグラフを開く'
+ openStarmap: 'メモリグラフを開く',
+ resetHudLayout: 'HUD のサイズと位置をリセット'
},
language: {
diff --git a/apps/desktop/src/i18n/types.ts b/apps/desktop/src/i18n/types.ts
index 4479acf298..534732c320 100644
--- a/apps/desktop/src/i18n/types.ts
+++ b/apps/desktop/src/i18n/types.ts
@@ -287,6 +287,7 @@ export interface Translations {
openStarmap: string
enterHud: string
exitHud: string
+ resetHudLayout: string
layoutEditor: string
layoutEditorTitle: (modifier: string) => string
}
diff --git a/apps/desktop/src/i18n/zh-hant.ts b/apps/desktop/src/i18n/zh-hant.ts
index e1988b62b4..c3ab703aea 100644
--- a/apps/desktop/src/i18n/zh-hant.ts
+++ b/apps/desktop/src/i18n/zh-hant.ts
@@ -241,7 +241,8 @@ export const zhHant = defineLocale({
muteHaptics: '靜音觸感回饋',
unmuteHaptics: '開啟觸感回饋',
openSettings: '開啟設定',
- openStarmap: '開啟記憶圖譜'
+ openStarmap: '開啟記憶圖譜',
+ resetHudLayout: '重設 HUD 大小和位置'
},
language: {
diff --git a/apps/desktop/src/i18n/zh.ts b/apps/desktop/src/i18n/zh.ts
index 3041c5d8c5..316a80d145 100644
--- a/apps/desktop/src/i18n/zh.ts
+++ b/apps/desktop/src/i18n/zh.ts
@@ -244,6 +244,7 @@ export const zh: Translations = {
openStarmap: '打开记忆图谱',
enterHud: 'HUD 模式',
exitHud: '退出 HUD 模式',
+ resetHudLayout: '重置 HUD 大小和位置',
layoutEditor: '布局编辑器',
layoutEditorTitle: mod => `布局编辑器 — ${mod} 点击重置布局`
},
diff --git a/apps/desktop/src/store/hud.test.ts b/apps/desktop/src/store/hud.test.ts
index 8cd85c0157..a2ed7a6663 100644
--- a/apps/desktop/src/store/hud.test.ts
+++ b/apps/desktop/src/store/hud.test.ts
@@ -4,16 +4,17 @@ import { $activeGatewayProfile } from '@/store/profile'
import { $sessions } from '@/store/session'
import type { SessionInfo } from '@/types/hermes'
-import { $hudActive, $hudSession, openHud } from './hud'
+import { $hudActive, $hudSession, openHud, resetHudLayout } from './hud'
const desktopWindow = window as unknown as { hermesDesktop?: Window['hermesDesktop'] }
const initialHermesDesktop = desktopWindow.hermesDesktop
const open = vi.fn().mockResolvedValue({ ok: true })
+const resetLayout = vi.fn().mockResolvedValue({ ok: true })
function installBridge() {
desktopWindow.hermesDesktop = {
- hud: { open }
+ hud: { open, resetLayout }
} as unknown as Window['hermesDesktop']
}
@@ -23,6 +24,7 @@ function session(overrides: Partial): SessionInfo {
beforeEach(() => {
open.mockClear()
+ resetLayout.mockClear()
installBridge()
$hudActive.set(false)
$hudSession.set(null)
@@ -38,6 +40,14 @@ afterEach(() => {
}
})
+describe('resetHudLayout', () => {
+ it('uses the native HUD recovery capability', () => {
+ resetHudLayout()
+
+ expect(resetLayout).toHaveBeenCalledOnce()
+ })
+})
+
describe('openHud profile targeting (#82285)', () => {
it('carries the session-stamped profile when the target belongs to another profile', () => {
$sessions.set([session({ id: 'abc', profile: 'work' })])
diff --git a/apps/desktop/src/store/hud.ts b/apps/desktop/src/store/hud.ts
index 2f04dafdb7..9d52b86067 100644
--- a/apps/desktop/src/store/hud.ts
+++ b/apps/desktop/src/store/hud.ts
@@ -87,6 +87,11 @@ export function closeHud(): void {
export const toggleHud = (sessionId?: null | string) => ($hudActive.get() ? closeHud() : openHud(sessionId))
+/** Restore the HUD's persisted geometry to its display-aware default. */
+export function resetHudLayout(): void {
+ void window.hermesDesktop?.hud?.resetLayout?.()
+}
+
/** Tell main which session this HUD is on. Main holds it (the HUD's renderer
* doesn't outlive the window) and hands it back in the close broadcast so the
* app window knows what to re-home onto. */