Skip to content

Commit 8f8f992

Browse files
authored
feat: add top-bar file search with Cmd+P quick open (#7)
1 parent b7e0928 commit 8f8f992

10 files changed

Lines changed: 544 additions & 32 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Stave is an Electron-based AI coding workspace built with Bun, React, Vite, and
1010
- `$skill-name` composer selector that resolves installed Claude and Codex skills across global, user, and workspace scopes
1111
- file and image attachments in the composer, including screenshot capture and clipboard image paste with inline preview
1212
- Monaco editor with workspace-backed TypeScript IntelliSense, optional Python LSP support, docked terminal, and source-control actions
13+
- always-visible top-bar quick open for searching workspace files, with `Cmd/Ctrl+P` focusing it
1314
- redesigned project, workspace, and task shell with a collapsible project sidebar, workspace task tabs, and a right-side activity rail
1415
- recent-project switching that preserves each project's own workspace list and last active workspace
1516
- automatic import of existing branch-backed git worktrees when a project opens

docs/ui/project-workspace-task-shell.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Replace the current `WorkspaceBar + TaskList` shell with a three-part layout:
1313
- The left project list is full-height and reaches the top edge of the app shell.
1414
- The top bar now applies only to the main work area, not the left project list.
1515
- The top bar shows the selected workspace path for the current workspace.
16+
- The top bar exposes an always-visible quick-open file search input, and `Cmd/Ctrl+P` focuses it from anywhere outside text inputs.
1617
- The center column contains the top bar, the selected workspace's task tab strip, and the main chat/editor surface.
1718
- The right edge contains a vertical rail for editor, explorer, changes, and terminal toggles.
1819

src/components/layout/AppShell.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Suspense, lazy, useEffect, useRef, useState } from "react";
1+
import { Suspense, lazy, useCallback, useEffect, useRef, useState } from "react";
22
import { useShallow } from "zustand/react/shallow";
33
import { TopBar } from "@/components/layout/TopBar";
44
import { ProjectWorkspaceSidebar } from "@/components/layout/ProjectWorkspaceSidebar";
@@ -18,7 +18,6 @@ const EditorPanel = lazy(() =>
1818
default: module.EditorPanel,
1919
}))
2020
);
21-
2221
type ResizableLayoutKey =
2322
| "taskListWidth"
2423
| "editorPanelWidth"
@@ -69,6 +68,11 @@ export function AppShell() {
6968
const [zoomHudPercent, setZoomHudPercent] = useState<number | null>(null);
7069
const [showCloseConfirm, setShowCloseConfirm] = useState(false);
7170
const zoomHudTimerRef = useRef<number | null>(null);
71+
const handleFocusFileSearch = useCallback(() => {
72+
const input = document.querySelector<HTMLInputElement>("[data-file-search-input]");
73+
input?.focus();
74+
input?.select();
75+
}, []);
7276

7377
function flushPendingLayoutPatch() {
7478
if (!pendingLayoutPatchRef.current) {
@@ -158,6 +162,14 @@ export function AppShell() {
158162
const onKeyDown = (event: KeyboardEvent) => {
159163
const store = useAppStore.getState();
160164
const hasMod = event.ctrlKey || event.metaKey;
165+
166+
if (hasMod && !event.shiftKey && event.key.toLowerCase() === "p") {
167+
event.preventDefault();
168+
event.stopPropagation();
169+
handleFocusFileSearch();
170+
return;
171+
}
172+
161173
if (isEditableShortcutTarget(event.target)) {
162174
return;
163175
}
@@ -236,7 +248,7 @@ export function AppShell() {
236248

237249
window.addEventListener("keydown", onKeyDown);
238250
return () => window.removeEventListener("keydown", onKeyDown);
239-
}, []);
251+
}, [handleFocusFileSearch]);
240252

241253
useEffect(() => () => {
242254
if (resizeFrameRef.current !== null) {

src/components/layout/KeyboardShortcutsDrawer.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ export function KeyboardShortcutsDrawer({ open, onOpenChange }: KeyboardShortcut
111111
title: "Actions",
112112
description: "Common task and editor commands.",
113113
shortcuts: [
114+
{
115+
label: "Quick open file",
116+
description: "Search the active workspace files and open a file in the editor.",
117+
sequences: [[modifierLabel, "P"]],
118+
},
114119
{
115120
label: "Save file",
116121
description: "Save the active editor tab.",
@@ -188,7 +193,7 @@ export function KeyboardShortcutsDrawer({ open, onOpenChange }: KeyboardShortcut
188193
</div>
189194
<DrawerFooter className="border-t border-border/70 px-5 py-4 md:flex-row md:items-center md:justify-between md:px-6">
190195
<p className="text-sm text-muted-foreground">
191-
Task quick jump works on the active task list, and the guide shortcut is ignored while typing in inputs.
196+
Task quick jump works on the active task list, and the quick open / guide shortcuts are ignored while typing in inputs.
192197
</p>
193198
<DrawerClose asChild>
194199
<Button variant="outline">Close</Button>

src/components/layout/TopBar.tsx

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useShallow } from "zustand/react/shallow";
44
import { Card, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui";
55
import { useAppStore } from "@/store/app.store";
66
import { TopBarBranchDropdown } from "@/components/layout/TopBarBranchDropdown";
7+
import { TopBarFileSearch } from "@/components/layout/TopBarFileSearch";
78
import { TopBarOpenPR } from "@/components/layout/TopBarOpenPR";
89
import { TopBarUtilityActions } from "@/components/layout/TopBarUtilityActions";
910
import { TopBarWindowControls } from "@/components/layout/TopBarWindowControls";
@@ -128,10 +129,10 @@ export function TopBar() {
128129
<>
129130
<header
130131
data-testid="top-bar"
131-
className="relative z-30 flex h-12 items-center justify-between border-b border-border/70 bg-card px-3.5"
132+
className="relative z-30 flex h-12 items-center justify-between gap-3 border-b border-border/70 bg-card px-3.5"
132133
style={TOP_BAR_DRAG_STYLE}
133134
>
134-
<div className="flex min-w-0 flex-1 items-center gap-2 pr-[18rem]">
135+
<div className="flex min-w-0 flex-1 items-center gap-2">
135136
<TooltipProvider>
136137
<TopBarBranchDropdown noDragStyle={TOP_BAR_NO_DRAG_STYLE} />
137138
{activeWorkspacePath ? (
@@ -152,24 +153,23 @@ export function TopBar() {
152153
</TooltipProvider>
153154
</div>
154155
<div
155-
className="absolute right-0 top-0 z-20 flex h-12 shrink-0 items-center"
156-
style={{
157-
right: "8px",
158-
}}
156+
className="absolute left-1/2 top-1/2 z-10 -translate-x-1/2 -translate-y-1/2"
157+
style={TOP_BAR_NO_DRAG_STYLE}
159158
>
160-
<div className="flex shrink-0 items-center gap-1.5">
161-
<TopBarUtilityActions
162-
isDarkMode={isDarkMode}
163-
noDragStyle={TOP_BAR_NO_DRAG_STYLE}
164-
onRefresh={handleRefreshProjectFiles}
165-
onToggleTheme={handleToggleTheme}
166-
onOpenShortcuts={handleOpenShortcuts}
167-
onOpenSettings={handleOpenSettings}
168-
onPreloadShortcuts={handlePreloadShortcuts}
169-
onPreloadSettings={handlePreloadSettings}
170-
/>
171-
<TopBarWindowControls noDragStyle={TOP_BAR_NO_DRAG_STYLE} />
172-
</div>
159+
<TopBarFileSearch noDragStyle={TOP_BAR_NO_DRAG_STYLE} />
160+
</div>
161+
<div className="flex shrink-0 items-center gap-1.5" style={TOP_BAR_NO_DRAG_STYLE}>
162+
<TopBarUtilityActions
163+
isDarkMode={isDarkMode}
164+
noDragStyle={TOP_BAR_NO_DRAG_STYLE}
165+
onRefresh={handleRefreshProjectFiles}
166+
onToggleTheme={handleToggleTheme}
167+
onOpenShortcuts={handleOpenShortcuts}
168+
onOpenSettings={handleOpenSettings}
169+
onPreloadShortcuts={handlePreloadShortcuts}
170+
onPreloadSettings={handlePreloadSettings}
171+
/>
172+
<TopBarWindowControls noDragStyle={TOP_BAR_NO_DRAG_STYLE} />
173173
</div>
174174
</header>
175175
{shortcutsOpen ? (

0 commit comments

Comments
 (0)