From 313f04e916cfa9030823e57aa2cb0037d63fd9e7 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Mon, 15 Jun 2026 14:52:24 +0300 Subject: [PATCH] fix: keep Windows header menu aligned Moved the Windows app menu into the fixed titlebar controls Kept sidebar controls stable when opening and closing the sidebar Documented longer validation timeouts for workspace checks --- AGENTS.md | 2 +- packages/ui/src/components/layout/Header.tsx | 19 ---------- .../layout/TitlebarLeftControls.tsx | 35 +++++++++++++++++++ 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 24ded422..186cc4f7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -451,7 +451,7 @@ A single store with N properties means every subscriber re-evaluates on every st ## Validation expectations -- Run `bun run type-check` and `bun run lint` before finalizing source-code changes that can affect TypeScript, runtime behavior, builds, lint rules, package resolution, or generated assets. For docs-only or isolated config-only changes, run the narrowest relevant validation instead (for example JSON/schema validation) and do not run the full checks unless the change can affect code execution. +- Run `bun run type-check` and `bun run lint` before finalizing source-code changes that can affect TypeScript, runtime behavior, builds, lint rules, package resolution, or generated assets. Use a sufficiently long tool timeout for these workspace-wide checks (for example 240000ms) so successful package-level results are not lost to a tool timeout. For docs-only or isolated config-only changes, run the narrowest relevant validation instead (for example JSON/schema validation) and do not run the full checks unless the change can affect code execution. - For hot-path changes, verify behavior under streaming or repeated events, not just static render. - For sync or startup changes, verify fresh load, retry/failure, and restart behavior. - For session changes, verify create, stream, abort, permission, archive/delete, and revisit flows when relevant. diff --git a/packages/ui/src/components/layout/Header.tsx b/packages/ui/src/components/layout/Header.tsx index 1ae2aa9d..9a76dff8 100644 --- a/packages/ui/src/components/layout/Header.tsx +++ b/packages/ui/src/components/layout/Header.tsx @@ -1417,16 +1417,6 @@ export const Header: React.FC = ({ toggleSidebar(); }, [blurActiveElement, isMobile, isSessionSwitcherOpen, setSessionSwitcherOpen, toggleSidebar]); - const handleOpenWindowsAppMenu = React.useCallback((event: React.MouseEvent) => { - const rect = event.currentTarget.getBoundingClientRect(); - void invokeDesktop('desktop_show_app_menu', { - x: rect.left, - y: rect.bottom, - }).catch((error) => { - console.warn('[header] failed to open app menu', error); - }); - }, []); - const handleOpenDraftMiniChat = React.useCallback(() => { void invokeDesktop('desktop_open_draft_mini_chat_window', { directory: normalize(openDirectory || activeProject?.path || ''), @@ -2154,15 +2144,6 @@ export const Header: React.FC = ({ className="app-region-no-drag shrink-0 self-stretch transition-[width] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none" style={{ width: headerControlsSpacerWidth }} /> - {isWindowsElectronDesktop ? ( - - ) : null} {/* Sidebar toggle + project actions live in the persistent TitlebarLeftControls overlay; the header reserves matching left space via padding (see headerStyle) when the sidebar is collapsed. */} diff --git a/packages/ui/src/components/layout/TitlebarLeftControls.tsx b/packages/ui/src/components/layout/TitlebarLeftControls.tsx index 33636d56..9f29795d 100644 --- a/packages/ui/src/components/layout/TitlebarLeftControls.tsx +++ b/packages/ui/src/components/layout/TitlebarLeftControls.tsx @@ -7,6 +7,7 @@ import { useI18n } from '@/lib/i18n'; import { useProjectActionsContext } from '@/hooks/useProjectActionsContext'; import { ProjectActionsButton } from '@/components/layout/ProjectActionsButton'; import { formatShortcutForDisplay, getEffectiveShortcutCombo } from '@/lib/shortcuts'; +import { invokeDesktop } from '@/lib/desktop'; const ICON_BUTTON_CLASS = 'app-region-no-drag inline-flex h-8 w-8 items-center justify-center gap-2 rounded-md typography-ui-label font-medium text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary hover:bg-interactive-hover transition-colors'; @@ -31,6 +32,22 @@ export const TitlebarLeftControls: React.FC = () => { const clusterRef = React.useRef(null); const toggleShortcut = formatShortcutForDisplay(getEffectiveShortcutCombo('toggle_sidebar', shortcutOverrides)); + const isWindowsElectronDesktop = React.useMemo(() => { + if (typeof window === 'undefined') { + return false; + } + return Boolean(window.__OPENCHAMBER_ELECTRON__) && window.__OPENCHAMBER_PLATFORM__ === 'win32'; + }, []); + + const handleOpenWindowsAppMenu = React.useCallback((event: React.MouseEvent) => { + const rect = event.currentTarget.getBoundingClientRect(); + void invokeDesktop('desktop_show_app_menu', { + x: rect.left, + y: rect.bottom, + }).catch((error) => { + console.warn('[titlebar] failed to open app menu', error); + }); + }, []); React.useEffect(() => { if (typeof document === 'undefined') { @@ -71,6 +88,24 @@ export const TitlebarLeftControls: React.FC = () => { }} >
+ {isWindowsElectronDesktop ? ( + + + + + +

{t('header.actions.openAppMenu')}

+
+
+ ) : null} +