feat(ui): Ctrl-K command palette #101
No reviewers
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lz/agent-nexus!101
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/command-palette"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Issue #96, design D7. Fourth and last PR in the chain — stacked on #100, merge after it.
A filter over the
workspacesstore, an overlay, and a keydown listener.Index = every workspace, every session, and each of the five
DockViews for each of them, plusNew workspace….Enteropens the highlighted target's terminal;Tabnarrows to that target's views;Escapebacks out of the narrowing before it closes the palette, so leaving costs the same keystrokes as entering.An empty query is not "everything": it shows the two groups worth showing unprompted — Needs you (workspaces stuck in
pulling/bootingor errored, and previews awaiting approval) and Recent. Once the operator types, the list is flat and ranked — grouping there put a pending-port row above the workspace you just named, andEnterthen opened the wrong thing.Two deliberate deviations from the spec
GET /api/previews/pending, behind the same operator gate as everything else. Without it the group can only report half of what it promises.Tabstep into "the D3 menu, rendered inline". Half of that menu (new-session,Container logs) is wired to component-local state a palette cannot reach, so a faithful version would ship rows that do nothing — and each newonPickswitch is a new place for exactly that.Tabnarrows to the target's views instead: the drill-down a palette is for, with nothing dead in it.Also here, because the palette is what makes them reachable
Ctrl-Kchip — the whole affordance on a phone. Hidden off the main page, where the palette is not mounted and it would do nothing.NewWorkerModalmoves fromWorkersup toMainSplit: three surfaces raise it now and only one is the list. Focus returns to whichever raised it.The binding
isPaletteChordis a pure predicate with its own test, because the interesting part is what it must not match and that is only checkable in a test. Measured in a real browser:Ctrl-K/Ctrl-PpreventDefaultCtrl-Shift-KCtrl-Kwith caps lock'K'withshiftKey: falseCmd-KVerification
Gates: 4875 files / 0 errors 0 warnings, 113 files / 1075 tests, lint clean.
Browser (seeded instance,
playwright-cli): open by chord / chip / rail footer; the Needs-you group against seeded stuck workspaces and pending previews; ranking;Tabdrill andShift-Tab;Enteropening a panel and recording a recent; the modal from all three entry points with focus return; and the mobile layout at 390px.New unit tests:
palette-index.test.ts(index construction, the Needs-you predicate, shell-preview routing to the workspace target, token filtering and ranking),palette-chord.test.ts,palette-recents.test.ts.Rebased after the spec audit
Rebased onto the updated #100. One conflict resolution worth naming: both branches added a
VIEW_LABELrecord topanel-target.ts, which is a compile error together. The palette's copy stayed — it documents why the ellipsis onSettings…belongs at the call site rather than in the name.Verified after the rebase that the palette and the new menu work together:
Ctrl-Kstill opens, the rail's pinned footer (Search / +) coexists with the rail's new context menu, and the fleet-wideworkspaces.portCount()feeds the sharedmenuContextForrather than the per-card fetch it replaced.Rebased again after #100's review pass. Gates at this tip: 4889 files / 0 errors 0 warnings, 117 files / 1133 tests, lint clean.
Closes a latent bug the review found NEXT DOOR to this PR: `viewItems` in target-menu.ts was a hand-written four-element array — the one per-view list with no compile-time guard. Dock's panel map and SessionTab's icon map are both `Record<DockView, …>`, so a sixth view fails to compile there; added to viewItems it compiled clean and was simply absent from every ⋯ menu, on every surface, with nothing to catch it. Now derived from VIEWS. `needsYouReason` stops enumerating phases. `provision_phase` is nulled on the first successful tmux probe, so non-null already means "still coming up, or failed" — which also covers queued/creating/starting, dropped by the old list while its own comment argued for including them. Nothing left to drift. `PaletteRow` carried each row's target three times: as `action.scope`, as a `scope` field always identical to it, and as a `colourKey` that was `targetKey` of the same thing. One `rowKey(row)` accessor replaces both fields. Drilling is keyed on that too, which deletes the one-caller `sameScope`, a non-null assertion and a guard. `GET /api/previews/pending` returned `{previews}` — the only list GET in the codebase that wraps; all eight others return a bare array. Unwrapped, and the one-off generic in the client goes with it. It also had no test, the only new server surface in this PR without one; now covered for both the fleet-wide pending set and the empty case. The header no longer tests `pathname === '/'` to decide whether to show its Ctrl-K chip. AGENTS.md calls out this exact file for deriving visibility rather than hardcoding a route — a literal there once hid Settings from the operator who needed it. The palette now reports its own mount, so the chip follows what exists rather than where it is mounted. Falls out of that: the Ctrl-K chord is claimed from the browser ONLY on a page that has a palette, verified. Smaller: `groupRows` already returns the shape the identity `.map()` rebuilt; `query.trim()` was recomputed five times beside a `searching` that was used once; the reset effect no longer lists `drill`, which drillInto/drillOut already handle synchronously (an effect would paint one frame on the old row first); GROUP_ORDER is module-private; a dead class attribute and a stray blank line. Reviewed and left alone, with reasons: the fleet-wide read and its absent try/catch (single operator by design, and its GET siblings do the same); PaletteAction's two-member union (a third member is a compile error at the destructure); building the index per open rather than caching.f6d4619931fc2872ba4efc2872ba4ed164870f48