Skip to content

Console: ⌘K search or jump to #770

Closed

Description

@V3RON

Part of #756.

Scope

A search box reaches every page, item and action. After this PR an operator presses ⌘K or Ctrl+K (both work on every system) or clicks "Search or jump to…" at the top of the sidebar. They type a few letters and press Enter to open a page, a worker, a device, a lease or a request, or to run any action the ⋯ menus offer.

In short

A dialog opens with a search box and a list of results in groups: Pages, Workers, Devices, Leases, Requests, Actions.

  • Typing filters the list. Both the typed text and each label are split into words at spaces, · and —, so mac-studio-2 stays one word and a pasted label still matches. A result matches when every typed word is the start of some word of its label, ignoring case. For example, drain mac matches "Drain — mac-studio-2" but not "Undrain — mac-studio-2".
  • Groups show in the order Pages, Workers, Devices, Leases, Requests, Actions. Inside a group, results where every typed word equals a whole label word come first, then the rest, each by label alphabetically. So drain mac-studio-2 puts "Drain — mac-studio-2" before "Drain — mac-studio-20".
  • The first result is highlighted. ↑ and ↓ move the highlight. Enter opens or runs the highlighted result, and Escape closes.
  • The list is built when the dialog opens. A group that was loading fills in when its data arrives (Actions only once workers, leases and requests have all loaded or failed); a group already shown never changes while the dialog is open. The highlight stays on the same result throughout, so a live update never moves it onto another. Reopening builds the list again.
  • ⌘K does nothing while a drawer or a dialog is already open.
  • An action runs exactly as from its ⋯ menu, including its confirm dialog and toast.
  • With nothing typed, the list shows the pages.
  • With no match, it says "No results for <text>".
Typed Shows
(nothing) Pages: Overview, Inbox, Fleet, Leases, Queue, Activity, Usage
fleet Pages: Fleet
mac-studio-2 Workers: mac-studio-2; Devices on it; Actions on it (e.g. Drain — mac-studio-2)
agent-1 Leases: agent-1 · iPhone 17 Pro; Actions: Release lease — agent-1 · iPhone 17 Pro
DRAIN every Drain action, and no Undrain (case ignored)
drain mac-studio-2 with mac-studio-2 and mac-studio-20 Actions: Drain — mac-studio-2 first, then Drain — mac-studio-20
zzz No results for zzz
drain on a single host No results for drain

Labels:

  • Page: its sidebar name.
  • Worker: its name, the label or else the id.
  • Device: <model> · <worker>.
  • Lease: <holder> · <model>.
  • Request: <requester> · <device asked for>.
  • Action: <action> — <item label>, for leases, requests and workers. Inbox items are not listed, since their actions are the same actions on the same worker, device, lease or request.
flowchart LR
  K[⌘K or the sidebar box] --> P[dialog: search box + groups]
  P -->|Enter on a page or item| N[navigate: page or drawer address]
  P -->|Enter on an action| A[same as the ⋯ menu: confirm, toast]
Loading

If this goes wrong, ⌘K shows an action the item's ⋯ menu does not, or runs one without its confirm.

Technical spec

Facts

# Fact Source
F1 The action list per kind and its availability come from model/actions.ts #769 Modules touched; ADR 0037 §3
F2 Page and item addresses #756 Shared values
F3 Base UI 1.9.0 ships Dialog and Autocomplete with groups, and documents a command-palette pattern built from them @base-ui/react@1.9.0 dialog/, autocomplete/ in a scratch install; base-ui.com/react/components/autocomplete
F4 The console reads workers (/v1/workers), leases (/v1/leases, holder names from /v1/tokens, falling back to ids when tokens are refused) and waiting requests (/v1/lease-requests) ui/src/state/workers.ts, ui/src/state/leases.ts:52-56, ui/src/state/waiting.ts
F6 Base UI's Autocomplete highlights nothing unless autoHighlight is set @base-ui/react@1.9.0 autocomplete/root/AutocompleteRoot.d.ts:90
F7 Running an action, its confirm included, is one call: useActions().run(action) opens the action's confirm when it has one, sends it, and shows the toast #769 Modules touched (state/actions.ts)
F5 No keyboard listener exists in the console today git grep -n "keydown|onKeyDown" 97bc8eaf -- ui/src: no hits

Modules touched

Searches: keydown, actionsFor, useActions, Sidebar, NavigationProvider, autoHighlight.

  • ui/src/model/search.ts (new): pure. Builds the result list from pages, workers, devices, leases, requests and the actions Console: release, cancel, drain, undrain and remove from the console #769 offers, and filters it by the typed words.
  • ui/src/state/search.ts (new): gathers the data, builds the list once per opening, opens and closes, runs a result through navigate or useActions().run.
  • ui/src/ds/command-palette.tsx (new): Base UI Dialog plus Autocomplete. ds/sidebar.tsx: the "Search or jump to…" button.
  • ui/src/app/shell.tsx: the ⌘K / Ctrl+K listener and the palette.
  • e2e/console/search.spec.ts (new), views.spec.ts.
  • docs/CONSOLE.md (a ⌘K section), docs/internal/DESIGN.md, ui/AGENTS.md.
flowchart LR
  shell["app/shell (changed)"] --> ss["state/search (new)"]
  shell --> cp["ds/command-palette (new)"]
  ss --> ms["model/search (new)"]
  ms --> ma["model/actions (#769)"]
  ss --> sa["state/actions (#769)"]
Loading

Contract and event changes

None.

Other code on the same state

The palette reads what the pages read and runs actions through #769's useActions().run, which owns the confirm and the toast (F7). Searches: useActions(, actionsFor(. With one runner, a ⌘K action and a menu action cannot differ.

  • Browser shortcuts: ⌘K/Ctrl+K is taken only while the console has focus, and preventDefault stops the browser's own use of it.

Failure modes

Call Outcome What happens Limit, counted from
a result's action any outcome as from the ⋯ menu (#769 Failure modes) as #769
a result for an item gone since the list was built not found the drawer's "gone" message (#765, #766, #768) or the action's error toast —
the data not loaded yet loading Pages, and "Loading…" under each other group —
a read fails or is refused error Pages and the groups that loaded; the failed group says "Could not load ."; holders fall back to ids when /v1/tokens is refused, as Leases does —

Rules in play

  • ADR 0037 §3, "the row's ⋯ menu, … and ⌘K all read that list": the palette reads model/actions.ts.
  • ADR 0037 §1: the dialog is Base UI under ds/, with no <style> element.
  • DESIGN.md focus ring and keyboard rules: every result is reachable by keyboard, and focus returns to where it was on close.

Tests

Seam: the browser lane (new e2e/console/search.spec.ts, modelled on e2e/console/shell.spec.ts). The filter in Node in model/search.test.ts.

  • every row of the In short table gives the results it lists (model test; the single-host row with mode worker)
  • ⌘K and Ctrl+K open the dialog and focus the box; the sidebar button opens it too; Escape closes it and focus returns
  • typing fleet and Enter goes to /fleet; a worker result goes to /fleet/<id>; a device result opens /fleet/<workerId>/<deviceId>; a lease result opens /leases/<id>; a request result opens /queue/<id>
  • opened before the data loads, the groups show "Loading…", then fill in when the data arrives while the highlight stays on its result; with /v1/workers failing, the Workers group says "Could not load workers." (stubbed through page.route)
  • a live update while the dialog is open does not change the list or the highlight
  • ↑ and ↓ move the highlight, and Enter runs the highlighted result
  • drain <worker> and Enter drains it with the same toast and Undo as the menu; release <holder> asks the same confirm first; cancel <requester> asks the cancel confirm; remove <worker> (a stopped worker) asks to type the name, with Remove worker disabled until it matches
  • ⌘K does nothing while a drawer is open
  • no results shows "No results for <text>"
  • axe passes with the palette open, in light and dark; no <style> element or CSP violation (extend views.spec.ts)

Done when

  • pnpm test:console --project=chromium passes, and CI's Console check passes in every browser.

Out of scope

  • Searching event payloads or history. Fuzzy matching beyond "every word appears".

Depends on

Written by an agent.

Activity

  1. added
    task:draftScope written; technical spec or its spec check missing.
    on Oct 20, 2026
  2. added
    task:readySpec checked. An agent may implement it once its Depends on allow.
    and removed
    task:draftScope written; technical spec or its spec check missing.
    on Oct 20, 2026
  3. self-assigned this
    on Oct 21, 2026
  4. 28 remaining items

  5. added a commit that references this issue on Oct 21, 2026
    7b5b4d9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

task:readySpec checked. An agent may implement it once its Depends on allow.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions