feat(app): scrollback search bar (⌘F) on the focused panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 12:42:14 +07:00
parent 52a502c38b
commit ac3f0886d5
4 changed files with 153 additions and 4 deletions
+16 -2
View File
@@ -3,6 +3,7 @@ import { LayoutEngine } from "./LayoutEngine";
import { Sidebar } from "./Sidebar";
import { TopBar } from "./TopBar";
import { CenterToolbar } from "./CenterToolbar";
import { SearchBar } from "./SearchBar";
import { Wizard } from "./Wizard";
import { EventCenter } from "./EventCenter";
import { maybeNotify } from "./notify";
@@ -24,6 +25,8 @@ export function App() {
const [health, setHealth] = useState<DaemonHealth | null>(null);
const [connected, setConnected] = useState(false);
const [focusedId, setFocusedId] = useState<string | null>(null);
const [searchOpen, setSearchOpen] = useState(false);
const [searchNonce, setSearchNonce] = useState(0);
const activeRef = useRef<string | null>(null);
const wsRef = useRef<WorkspaceView[]>([]);
activeRef.current = activeId;
@@ -91,6 +94,16 @@ export function App() {
return () => { void unlisten.then((f) => f()); void reconnect.then((f) => f()); };
}, [refresh, seedEvents, loadHealth]);
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "f") {
if (activeRef.current) { e.preventDefault(); setSearchOpen(true); setSearchNonce((n) => n + 1); }
}
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, []);
const unread = useMemo(() => events.filter((e) => !e.read).length, [events]);
const active = workspaces.find((w) => w.id === activeId) ?? null;
const leaves = active ? leafIds(active.layout) : [];
@@ -109,12 +122,13 @@ export function App() {
<Sidebar groups={groups} workspaces={workspaces} activeId={activeId} onSelect={selectWorkspace} onNew={() => setWizard(true)} health={health} connected={connected} />
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
{active && (
<CenterToolbar selected="" onSelect={(p) => { if (active) void applyPreset(active.id, p, []); }} />
<CenterToolbar selected="" onSelect={(p) => { if (active) void applyPreset(active.id, p, []); }} onOpenSearch={() => setSearchOpen(true)} />
)}
<div style={{ flex: 1, minHeight: 0 }}>
<div style={{ flex: 1, minHeight: 0, position: "relative" }}>
{active
? <LayoutEngine workspaceId={active.id} layout={active.layout} running={running} states={states} surfaces={active.surfaces} focusedId={effectiveFocus} onFocus={setFocusedId} zoomed={active.zoomed} />
: <div style={{ color: COLORS.textMuted, padding: 24 }}>No workspace create one to begin.</div>}
{searchOpen && active && <SearchBar surfaceId={effectiveFocus} reopenNonce={searchNonce} onClose={() => setSearchOpen(false)} />}
</div>
</div>
{eventsOpen && (