From b0e078641907e4d8b7442ca8488f3df544de8048 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 18 Apr 2026 16:03:01 +0700 Subject: [PATCH] fix(app): make cmd+o work regardless of sidebar visibility The worktree.new keyboard handler was registered inside ProjectHeaderRow, which only mounts when the sidebar is open. Register a single global handler in the app layout keyed off the navigation-active workspace selection. --- packages/app/src/app/_layout.tsx | 3 + .../src/components/sidebar-workspace-list.tsx | 11 ---- .../hooks/use-active-worktree-new-action.ts | 58 +++++++++++++++++++ 3 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 packages/app/src/hooks/use-active-worktree-new-action.ts diff --git a/packages/app/src/app/_layout.tsx b/packages/app/src/app/_layout.tsx index a35cb1eb0..b0624229e 100644 --- a/packages/app/src/app/_layout.tsx +++ b/packages/app/src/app/_layout.tsx @@ -64,6 +64,7 @@ import { ProjectPickerModal } from "@/components/project-picker-modal"; import { KeyboardShortcutsDialog } from "@/components/keyboard-shortcuts-dialog"; import { WorkspaceSetupDialog } from "@/components/workspace-setup-dialog"; import { useKeyboardShortcuts } from "@/hooks/use-keyboard-shortcuts"; +import { useActiveWorktreeNewAction } from "@/hooks/use-active-worktree-new-action"; import { queryClient } from "@/query/query-client"; import { WEB_NOTIFICATION_CLICK_EVENT, @@ -443,6 +444,8 @@ function AppContainer({ cycleTheme, }); + useActiveWorktreeNewAction(); + const containerStyle = useMemo( () => ({ flex: 1 as const, backgroundColor: theme.colors.surface0 }), [theme.colors.surface0], diff --git a/packages/app/src/components/sidebar-workspace-list.tsx b/packages/app/src/components/sidebar-workspace-list.tsx index e07b0d1c0..da2f46af0 100644 --- a/packages/app/src/components/sidebar-workspace-list.tsx +++ b/packages/app/src/components/sidebar-workspace-list.tsx @@ -797,17 +797,6 @@ function ProjectHeaderRow({ const mergeWorkspaces = useSessionStore((state) => state.mergeWorkspaces); const toast = useToast(); - useKeyboardActionHandler({ - handlerId: `worktree-new-${project.projectKey}`, - actions: ["worktree.new"], - enabled: isProjectActive && canCreateWorktree && Boolean(serverId), - priority: 0, - handle: () => { - handleBeginWorkspaceSetup(); - return true; - }, - }); - const interaction = useLongPressDragInteraction({ drag, menuController, diff --git a/packages/app/src/hooks/use-active-worktree-new-action.ts b/packages/app/src/hooks/use-active-worktree-new-action.ts new file mode 100644 index 000000000..434ffe20f --- /dev/null +++ b/packages/app/src/hooks/use-active-worktree-new-action.ts @@ -0,0 +1,58 @@ +import { useCallback } from "react"; +import { router } from "expo-router"; +import { useKeyboardActionHandler } from "@/hooks/use-keyboard-action-handler"; +import type { KeyboardActionId } from "@/keyboard/keyboard-action-dispatcher"; +import { useNavigationActiveWorkspaceSelection } from "@/stores/navigation-active-workspace-store"; +import { useSessionStore } from "@/stores/session-store"; +import { buildHostNewWorkspaceRoute } from "@/utils/host-routes"; +import { projectDisplayNameFromProjectId } from "@/utils/project-display-name"; + +const WORKTREE_NEW_ACTIONS: readonly KeyboardActionId[] = ["worktree.new"]; + +export function useActiveWorktreeNewAction() { + const selection = useNavigationActiveWorkspaceSelection(); + const serverId = selection?.serverId ?? null; + const workspaceId = selection?.workspaceId ?? null; + + const workingDir = useSessionStore((state) => { + if (!serverId || !workspaceId) { + return null; + } + const workspace = state.sessions[serverId]?.workspaces?.get(workspaceId); + if (!workspace || workspace.projectKind !== "git") { + return null; + } + return workspace.projectRootPath; + }); + + const displayName = useSessionStore((state) => { + if (!serverId || !workspaceId) { + return null; + } + const workspace = state.sessions[serverId]?.workspaces?.get(workspaceId); + if (!workspace || workspace.projectKind !== "git") { + return null; + } + return workspace.projectDisplayName || projectDisplayNameFromProjectId(workspace.projectId); + }); + + const handle = useCallback(() => { + if (!serverId || !workingDir) { + return false; + } + router.navigate( + buildHostNewWorkspaceRoute(serverId, workingDir, { + displayName: displayName ?? undefined, + }) as never, + ); + return true; + }, [serverId, workingDir, displayName]); + + useKeyboardActionHandler({ + handlerId: "worktree-new-active", + actions: WORKTREE_NEW_ACTIONS, + enabled: serverId !== null && workingDir !== null, + priority: 0, + handle, + }); +}