Repository navigation
Console redesign: an Inbox of what needs you, actions on every row, built on Base UI #756
Description
Request: none
Problem
The console only shows things. An operator who sees a stuck request, a drained worker or a lost worker has to leave it for the CLI or the HTTP API, even though the HTTP API already offers release, cancel, drain, undrain and remove. Each page is laid out a little differently. Nothing lists in one place what needs a person now, and the layout has no room for the features coming next.
Who it is for
The operator of a Simlock gateway or single host. They watch many agents lease devices across several workers, and need to see in a few seconds whether anything is wrong, then fix it from the same page.
Outcome
After this lands, an operator can:
- open the console and see the whole system at a glance on Overview: five numbers, what needs them (or one line saying nothing does), capacity per platform, the last hour of leases held and requests waiting, and every worker;
- open Inbox for the full list of what needs a person, and undrain a drained worker from its row in one click;
- release a lease, cancel a waiting request, and drain, undrain or remove a worker, from the row's ⋯ menu, a right-click on the row, the item's drawer or the Inbox;
- see each item's details in a drawer over the page, with an address that can be shared;
- find any page, worker, device, lease or request, and run any of those actions, from ⌘K;
- scroll Activity back through the daemon's event history, as far as it is kept, up to 10,000 events in the list;
- read on every page the same parts in the same places: page header, toolbar, table with a pager, drawer, toasts.
How it works for the user
flowchart TD
A[Sign in] --> B[Overview]
B -->|nothing needs you| B1[One line: Nothing needs you]
B -->|something does| C[Needs you: top 4, each opening its drawer]
C --> D[Inbox: every item]
D --> E{Fix button or row ⋯}
E -->|undoable: drain| F[Toast with Undo]
E -->|final: release, cancel| G[Confirm naming the thing] --> H[Toast]
E -->|remove worker| I[Type the worker's name] --> H
H -->|daemon refuses| J[Error toast stays: the daemon's message, Retry]
B --> K[Fleet, Leases, Queue, Activity, Usage]
K --> L[Row opens the drawer: details, Screen, Events]
B -.⌘K.-> M[Search or jump to: pages, items, actions]
The sidebar, top to bottom: Overview, Inbox (with its count), then Operate: Fleet, Leases, Queue, then Observe: Activity, Usage.
On a single host, Drain, Undrain and Remove never appear: a single host has no fleet to take a worker out of.
Examples
What goes in the Inbox:
| Situation | In the Inbox | Group |
|---|---|---|
| a worker is disconnected or incompatible | yes | Broken |
| a device is quarantined | yes | Broken |
| a worker is drained, over its RAM budget, or a device stalled | yes | Needs a look |
| an enrolled phone is absent (locked, untrusted, unplugged) | yes (new) | Needs a look |
| a request has waited 61 s | yes (new) | Needs a look |
| a request has waited 59 s | no | — |
| a lease ends in 4 min | yes (new) | Heads-up |
| a lease ends in 6 min (a default lease is 15 min) | no | — |
| a worker's disk is at 91% | no (shows on its row and page) | — |
Which actions an item offers:
| Item | Gateway | Single host |
|---|---|---|
| held lease | Release lease | Release lease |
| waiting request | Cancel request | Cancel request |
| connected worker, not drained | Drain | none |
| drained worker | Undrain | none |
| disconnected worker | Drain or Undrain, Remove worker | (cannot happen) |
What could go wrong
- An operator releases the wrong lease and an agent's run fails. Likely enough that release always asks first and names the holder, device and worker.
- An action fails without the operator noticing. Unlikely: an error toast stays until closed and says the daemon's reason.
- A table or drawer is harder to use with a keyboard or screen reader than today's lists. Checked by the accessibility checks in every browser.
- Signed in through a sign-in proxy over plain
http://on a network name, every action is refused. Rare; the error toast says so.
Words used
- Inbox: the page listing what needs a person, e.g. "linux-ci-2 is disconnected".
- Needs you: the Overview panel showing the first four Inbox items with problems.
- Heads-up: an Inbox item that is not a problem yet: a lease with under 5 minutes left.
- Drawer: the panel that slides over the page from the right to show one item, e.g. a lease's details and live screen.
- Toast: a short message in the bottom-right corner after an action, e.g. "mac-studio-2 drained · Undo".
- Single host: a daemon running on its own, not a gateway; it has one worker, itself.
Non-goals
- New HTTP routes or actions: retry a wipe, delete a device, extend a lease, run cleanup, snooze an item, create or revoke tokens, edit alert destinations.
- The Manage pages (Components, Physical devices, Alerts, Access, Audit, Settings, Baselines). Each comes with its own feature.
- A record of who did an action (Every action leaves a trace of who did it #703).
- Redirects from the old addresses
/workers,/waiting,/events,/attention.
Completion conditions
- A person signed in with an operator token on a gateway sees Overview at
/: five numbers, "Nothing needs you" when the Inbox is empty, capacity per platform, two charts, all workers. - Every Inbox rule in Examples holds, in the browser.
- From the console alone, a person can release a lease, cancel a waiting request, drain then undo, undrain, and remove a disconnected worker; each ends in a toast, and the page shows the daemon's new state.
- A refused action shows the daemon's message in a toast that stays until closed.
- On a single host, no Drain, Undrain or Remove appears anywhere.
- Every page has the shared header; every page with a list of items shows it in the one table component, with the shared toolbar (chips left, search right), a pager once the rows exceed a page, and details in the drawer. Overview and Usage use the shared header and toolbar. Every page passes the accessibility checks in light and dark.
- No page, drawer, menu, dialog, toast or ⌘K search creates a
<style>element or a CSP violation. - ⌘K finds a page, a worker, a device, a lease and a request by name, and runs an action.
- Activity scrolls back past the last hour, to the oldest event the daemon keeps or until the list holds 10,000 events.
Open questions
Decisions
- ADR 0037 (PR docs(adr): 0037 the console acts through the HTTP routes, on Base UI #762) — The console acts through the HTTP routes, on Base UI: Base UI under
ds/with its<style>switch off; details in a drawer at/<page>/<id>; one action list per kind of item; the console readsdaemon.modeonce; toasts report outcomes.
Shared values
| Value or state | Rule | Written by | Read by | Source |
|---|---|---|---|---|
| Pages and addresses | / Overview, /inbox[/<key>] Inbox, /fleet Fleet, /fleet/<workerId>[?tab=devices|warm-pool|host|events] worker page, /fleet/<workerId>/<deviceId> device drawer, /leases[/<leaseId>], /queue[/<requestId>], /activity[/<eventId>], /usage. An address whose last segment holds a . ends in /. Search text is ?q=. No redirects from old addresses; an unknown address shows Not found. |
#765 (renames, /leases), #766 (/fleet/<workerId>…), #768 (/, /inbox) |
every task, the browser lane | ADR 0037 §2; ui/src/app/routes.ts:19-27; src/http/server.ts:108-111 |
| Sidebar | No group: Overview, Inbox (count = Inbox items with a problem, not heads-ups). Operate: Fleet, Leases, Queue. Observe: Activity, Usage. | #765 (groups, renames), #768 (Overview, Inbox) | #770 | this spec |
| Drawer | Modal over the whole console, sidebar included; opens at /<page>/<id> (or /fleet/<workerId>/<deviceId>), closes to the address it opened from, keeping the query; Escape, ✕ or the dim layer closes; animates on open only. |
#765 | #766, #767, #768 | ADR 0037 §2 |
| Inbox items | Today's six attention kinds, plus an absent enrolled phone (<workerId>/<deviceId>/absent, needs a look), a request waiting over WAITING_TOO_LONG_MS = 60 000 ms (request/<requestId>/waiting, needs a look), a lease whose deadline is under EXPIRING_UNDER_MS = 5 min away by the daemon's clock (lease/<leaseId>/ending, heads-up). Groups in this order: Broken, Needs a look, Heads-up; inside a group, worker order, then longest wait, then least time left. |
#768 | #769, #770 | ui/src/model/attention.ts:35-77, ui/src/model/leases.ts:130-135, src/core/config.ts:52 |
| Inbox fix button | Only a drained worker's item has one: Undrain. Every other item has none; its actions are under ⋯ and in its drawer. | #769 | #768 | this spec |
| Ending soon | EXPIRING_UNDER_MS = 5 min, one home in model/leases.ts, read by the Leases filter, Overview and the Inbox |
#768 | #765 | ui/src/model/leases.ts:130-135 |
| Action list | Per kind (lease, request, worker, Inbox item): label, when offered, confirm kind, danger. As in ADR 0037 §3. | #769 | #770 | ADR 0037 §3 |
| Daemon mode | daemon.mode from GET /v1/status after sign-in and after each reconnect; gateway offers worker actions; anything else, or a failed read, offers none. |
#766 | #766 (worker Events tab), #769, #770 | ADR 0037 §4; src/contract/operations.ts:117 |
| Toast and confirm wording, the action runner | Written out in #769's In short; useActions().run owns the confirm, the write and the toast |
#769 | #770 | this spec |
Tasks
flowchart LR
A["#765 shell, Base UI, drawer, Leases"] --> B["#766 Fleet and worker page"]
B --> C["#767 Queue and Activity"]
G["#764 events history paging"] --> C
C --> D["#768 Overview, Inbox, Usage"]
D --> E["#769 actions"]
E --> F["#770 ⌘K"]
The console tasks run one after another: each edits the route table, the page list in views.spec.ts and the manual's Pages table that the next one edits.
- Events history pages backwards: GET /v1/events with before and limit #764 Events history pages backwards — a client can read the event history page by page, back as far as it is kept. Risk: a page skips or repeats events.
- Console: grouped sidebar, renamed pages, Base UI and the drawer, starting with Leases #765 Grouped sidebar, renamed pages, Base UI and the drawer, starting with Leases — an operator opens a lease in a drawer over the page and shares its address. Risk: a drawer that will not close by keyboard, or a page not found under its new name.
- Console: Fleet table and a page per worker #766 Fleet table and a page per worker — an operator reads every worker in one table and opens its own page. Risk: warm pool or host facts hard to find.
- Console: Queue and Activity in the shared frame, Activity scrolls back through history #767 Queue and Activity in the shared frame — an operator scrolls Activity back through days of history. Risk: events shown twice or skipped, or a jumping list.
- Console: Overview home and Inbox #768 Overview home, Inbox and Usage — an operator sees the whole system and everything that needs them. Risk: "Nothing needs you" while something does.
- Console: release, cancel, drain, undrain and remove from the console #769 Actions — an operator releases, cancels, drains, undrains and removes from the console. Risk: a lease ended by accident, or a failure that looks like success.
- Console: ⌘K search or jump to #770 ⌘K search or jump to — an operator finds any page or item and runs any action by typing. Risk: an action run without its confirm.
Completion conditions → tasks:
- Overview at
/→ Console: Overview home and Inbox #768. - Inbox rules → Console: Overview home and Inbox #768.
- The five actions, toasts, state shown → Console: release, cancel, drain, undrain and remove from the console #769.
- Refused action toast → Console: release, cancel, drain, undrain and remove from the console #769.
- No worker actions on a single host → Console: release, cancel, drain, undrain and remove from the console #769.
- Same parts on every page, accessibility → Console: grouped sidebar, renamed pages, Base UI and the drawer, starting with Leases #765 (shell, Leases), Console: Fleet table and a page per worker #766 (Fleet), Console: Queue and Activity in the shared frame, Activity scrolls back through history #767 (Queue, Activity), Console: Overview home and Inbox #768 (Overview, Inbox, Usage).
- No
<style>, no CSP violation → Console: grouped sidebar, renamed pages, Base UI and the drawer, starting with Leases #765 (pages, drawer), Console: release, cancel, drain, undrain and remove from the console #769 (menus, dialogs, toasts), Console: ⌘K search or jump to #770 (⌘K). - ⌘K → Console: ⌘K search or jump to #770.
- Activity scrolls back → Events history pages backwards: GET /v1/events with before and limit #764, Console: Queue and Activity in the shared frame, Activity scrolls back through history #767.
Open work in the same area:
- Console: time in state for every device #319 (time in state per device,
task:draft): its paths (ui/src/views/…) no longer exist; it touches the worker page Console: Fleet table and a page per worker #766 rebuilds. No Depends on: it is not ready, and its next spec session re-reads the code. - Every device shape says when the device entered its state #318 (
task:blocked) addsstateEnteredAtto devices: nothing here needs it. - A worker joined to a gateway does not serve its own HTTP API or console #705 (
request:new): a joined worker would stop serving the console; it narrows where a single-host console exists, which Console: release, cancel, drain, undrain and remove from the console #769's mode read already handles. - Every action leaves a trace of who did it #703 (
request:new): who did an action. A non-goal here. - flaky: long-lists events feed p95 frame bound on WebKit #730 (flaky long-lists events feed frame bound on WebKit): Console: Queue and Activity in the shared frame, Activity scrolls back through history #767 changes the Activity page that test measures; the flaky-test rule applies.
Written by an agent.
Activity
docs(adr): draft 0037 for #756
V3RON commentedon Oct 20, 2026
Spec updated: all business sections, Decisions (ADR 0037, PR #762), Shared values and Tasks (#764–#770) written from the 2026-10-20 session.
Written by an agent.
docs(adr): accept 0037 for #756
V3RON commentedon Oct 20, 2026
Spec updated: tasks #764–#770 checked and set ready; ADR 0037 accepted (PR #762); decisions this session: Activity infinite scroll back through history, ending soon under 5 min, Undrain is the only Inbox fix button, the mode read moves to #766.
Written by an agent.
V3RON commentedon Oct 20, 2026
Delivery run
Merged: none (the feature merges as one stack at the end)
In the stack, review passed: #773 (#764), #780 (#765), #787 (#766), #789 (#767), over ADR PR #762 (stack #781)
Waiting: #768 (PR #793 part built; handoff on #768), #769 on #768, #770 on #769
Decided: none
Asleep: 144 min
The run stopped at the session's 10-hour budget. Run
deliver 756in a new session to go on from #768.Written by an agent.
docs(adr): draft 0037 for #756
docs(adr): accept 0037 for #756
github-actions commentedon Oct 21, 2026
All 7 sub-issues are closed. If the completion conditions hold on
main, close this feature and flip its ADRs to Accepted.github-actions commentedon Oct 21, 2026
All 7 sub-issues are closed. If the completion conditions hold on
main, close this feature and flip its ADRs to Accepted.github-actions commentedon Oct 21, 2026
All 7 sub-issues are closed. If the completion conditions hold on
main, close this feature and flip its ADRs to Accepted.docs(adr): 0037 the console acts through the HTTP routes, on Base UI (#…
github-actions commentedon Oct 21, 2026
All 7 sub-issues are closed. If the completion conditions hold on
main, close this feature and flip its ADRs to Accepted.