Repository navigation
Console: ⌘K search or jump to #770
Description
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—, somac-studio-2stays 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 macmatches "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-2puts "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]
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 throughnavigateoruseActions().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)"]
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
preventDefaultstops 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
fleetand 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/workersfailing, the Workers group says "Could not load workers." (stubbed throughpage.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 (extendviews.spec.ts)
Done when
pnpm test:console --project=chromiumpasses, 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
test: typing `fleet` and Enter goes to /fleet; a worker result goes t…
feat(console): Enter on a page, worker, device, lease or request resu…
test: every row of the In short table gives the results it lists (#770)
feat(console): the search filters by word, ranks whole words first an…
test: drain <worker> and Enter drains it with the menu's toast and Un…
feat(console): Enter on an action runs it through the menus' runner (#…
test: ↑ and ↓ move the highlight, and Enter runs the highlighted resu…
test: no results shows "No results for <text>" (#770)
feat(console): the search says when nothing matches (#770)
test: ⌘K does nothing while a drawer is open (#770)
28 remaining items
refactor(console): the search's statuses and shortcut live in the mod…
fix: a loading search group lists no results; ranking test; docs matc…
fix(console): the drawer slides in, its ⋯ shows, and Overview and Usa…
fix(console): a table outside a panel sits on the same card surface a…
fix(console): a page's list fills the page's height, on a card, even …
fix(console): the drawer slides out when it closes, then the address …
fix(console): a worker's Events tab sits on a card like its other tabs (
fix(console): the feed's end line and an empty list's message sit ins…
fix(console): the Inbox's empty state sits on a card, and switching t…
fix(console): the drawer has no shadow, like the rest of the console (#…
fix(console): Sign out and Collapse are the same secondary button, ic…
refactor(console): split the capacity count and drop an unused type r…
docs(adr): one row for 0037 in the index, in order (#770)
feat(console): search or jump to anything with ⌘K (#834)