From 02b1ee637d297104c9c17bfa6748735c94e06a0b Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sun, 30 Aug 2026 14:28:22 +0300 Subject: [PATCH] feat(ui): move project actions from the titlebar overlay into the header before Open in --- packages/ui/src/components/layout/Header.tsx | 33 +++++++------------ .../src/components/layout/SidebarTopBar.tsx | 4 +-- .../layout/TitlebarLeftControls.tsx | 12 +------ 3 files changed, 15 insertions(+), 34 deletions(-) diff --git a/packages/ui/src/components/layout/Header.tsx b/packages/ui/src/components/layout/Header.tsx index 43126229..3fce3d60 100644 --- a/packages/ui/src/components/layout/Header.tsx +++ b/packages/ui/src/components/layout/Header.tsx @@ -46,6 +46,8 @@ import { import type { SessionContextUsage } from '@/stores/types/sessionTypes'; import { DesktopHostSwitcherDialog } from '@/components/desktop/DesktopHostSwitcher'; import { OpenInAppButton } from '@/components/desktop/OpenInAppButton'; +import { ProjectActionsButton } from '@/components/layout/ProjectActionsButton'; +import { useProjectActionsContext } from '@/hooks/useProjectActionsContext'; import { SessionSwitcherDropdown } from '@/components/session/SessionSwitcherDropdown'; import { SessionTabsStrip, type SessionTabMenuArgs } from './SessionTabsStrip'; import { canUseElectronDesktopIPC, invokeDesktop, isDesktopLocalOriginActive, isDesktopShell, isVSCodeRuntime, startDesktopWindowDrag, type UpdateInfo } from '@/lib/desktop'; @@ -998,27 +1000,9 @@ export const Header: React.FC = () => { return normalize(openDirectory || activeProject?.path || ''); }, [activeProject?.path, openDirectory]); - const activeProjectRef = React.useMemo(() => { - if (!activeProject) { - return null; - } - return { id: activeProject.id, path: activeProject.path }; - }, [activeProject]); - - const lastProjectActionsContextRef = React.useRef<{ - projectRef: { id: string; path: string }; - directory: string; - } | null>(null); - - React.useEffect(() => { - if (!activeProjectRef || !actionDirectory) { - return; - } - lastProjectActionsContextRef.current = { - projectRef: activeProjectRef, - directory: actionDirectory, - }; - }, [actionDirectory, activeProjectRef]); + // Same resolution the titlebar overlay used to own: worktree → session → + // draft → project path, sticky across session switches. + const projectActionsContext = useProjectActionsContext(); const planModeEnabled = useFeatureFlagsStore((state) => state.planModeEnabled); @@ -1295,6 +1279,13 @@ export const Header: React.FC = () => { const desktopSidebarActions = ( <> + {projectActionsContext ? ( + + ) : null} {/* Instances only exist in the desktop app. On web the menu was left holding a single dev-only shutdown action, which is not a reason to diff --git a/packages/ui/src/components/layout/SidebarTopBar.tsx b/packages/ui/src/components/layout/SidebarTopBar.tsx index 564e051a..2e332521 100644 --- a/packages/ui/src/components/layout/SidebarTopBar.tsx +++ b/packages/ui/src/components/layout/SidebarTopBar.tsx @@ -2,8 +2,8 @@ import React from 'react'; /** * Strip at the top of the desktop left sidebar that reserves room for the - * persistent {@link TitlebarLeftControls} overlay (sidebar toggle + project - * actions), so the session list starts below them. Its height tracks the + * persistent {@link TitlebarLeftControls} overlay (sidebar toggle), so the + * session list starts below it. Its height tracks the * header via `--oc-header-height`. * * Split into two regions so the strip stays a window drag area while the diff --git a/packages/ui/src/components/layout/TitlebarLeftControls.tsx b/packages/ui/src/components/layout/TitlebarLeftControls.tsx index c8484eb7..41b92b3f 100644 --- a/packages/ui/src/components/layout/TitlebarLeftControls.tsx +++ b/packages/ui/src/components/layout/TitlebarLeftControls.tsx @@ -4,8 +4,6 @@ import { Icon } from '@/components/icon/Icon'; import { cn } from '@/lib/utils'; import { useUIStore } from '@/stores/useUIStore'; import { useI18n } from '@/lib/i18n'; -import { useProjectActionsContext } from '@/hooks/useProjectActionsContext'; -import { ProjectActionsButton } from '@/components/layout/ProjectActionsButton'; import { WindowsWindowControls } from '@/components/desktop/WindowsWindowControls'; import { formatShortcutForDisplay, getEffectiveShortcutCombo } from '@/lib/shortcuts'; import { invokeDesktop } from '@/lib/desktop'; @@ -15,7 +13,7 @@ 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'; /** - * Persistent top-left titlebar controls (sidebar toggle + project actions). + * Persistent top-left titlebar controls (app menu on frameless chrome + sidebar toggle). * * Rendered exactly once as an absolutely-positioned overlay above both the * sidebar and the header, so the buttons never migrate / re-mount between the @@ -29,7 +27,6 @@ export const TitlebarLeftControls: React.FC = () => { const { t } = useI18n(); const toggleSidebar = useUIStore((state) => state.toggleSidebar); const shortcutOverrides = useUIStore((state) => state.shortcutOverrides); - const projectActionsContext = useProjectActionsContext(); const clusterRef = React.useRef(null); const toggleShortcut = formatShortcutForDisplay(getEffectiveShortcutCombo('toggle_sidebar', shortcutOverrides)); @@ -123,13 +120,6 @@ export const TitlebarLeftControls: React.FC = () => {

{t('header.actions.openSessionsWithShortcut', { shortcut: toggleShortcut })}

- - {projectActionsContext ? ( - - ) : null} );