Skip to content

Console: watch any device's screen, and layout fixes from the 2026-10-23 review #906

Closed
4 of 4 issues completed

Description

@V3RON

Request: none

Problem

In the console, a device's live screen is shown on the lease, not on the device. A device with no lease, such as an idle or warm-pool simulator, cannot be watched at all. An operator looking at a device has to find its lease first. The stream's numbers (frame rate, bandwidth, dropped frames) sit under the picture all the time, though they only matter when something looks wrong.

The same review of the console found layout faults. The page shifts sideways when a scrollbar appears. The worker page has a "Fleet" link the sidebar already gives. The warm-pool tab runs its summary into its table with no line between them. The worker's Events tab shows only the last hour, does not fill the page and cannot load older events. Several boxes are cramped or overlap: the row ⋯ button in the fleet table, the Inbox row buttons, the toast's close button, and a ⋯ list that repeats the button beside it.

Who it is for

An operator using the console, on a single worker or on a gateway, who wants to see what a device is showing, whether or not an agent holds it.

Outcome

  • The operator opens any device in the console and watches its screen, leased or not.
  • The picture keeps running when an agent leases the device.
  • The stream's numbers are one click away, not always on screen.
  • The worker page's Events tab shows that worker's whole history, scrolling back as far as it is kept.
  • The layout faults listed above are gone.

How it works for the user

flowchart TD
  A[Fleet → worker → device row] --> B[Device drawer opens on Details]
  B --> C{Screen tab}
  C -->|device ready or leased| D[Picture starts by itself]
  C -->|device not running| E["Sentence: why there is no picture"]
  E -->|device becomes ready| D
  C -->|streaming off on the worker| F["Streaming is off on <worker>."]
  D --> G[ⓘ opens the stream numbers]
  D -->|leave the tab or close the drawer| H[Picture stops]
  D -->|device reset after a lease, or shut down| I[Picture ends, the tab says why]
Loading

Before: Leases → a lease → Screen tab → Show screen. A device with no lease has no screen anywhere.

After: Fleet → a worker → a device → Screen tab. The lease drawer has no Screen tab.

Examples

Who may watch, and what they get, through GET /v1/stream/workers/{workerId}/devices/{deviceId}:

Caller and device Today (lease route) After
Operator, device ready (no lease) cannot be asked: no lease id 200, the picture
Operator, device leased 200 through the lease id 200
Operator, device shutdown cannot be asked 409 DEVICE_NOT_RUNNING
Operator, device id unknown on that worker — 404 UNKNOWN_DEVICE
Operator, worker id unknown — 404 UNKNOWN_WORKER
Agent token, its own leased device 200 through its lease id 404 UNKNOWN_DEVICE
Anyone, GET /v1/stream/leases/{id} 200 or 404 UNKNOWN_LEASE 404: no such stream path

GET /v1/events?worker=<id>:

Daemon, worker Today After
Gateway, worker=w1 parameter ignored, every event only events whose payload says workerId: "w1"
Worker whose id is w1, worker=w1 ignored every event
Worker whose id is w1, worker=w2 ignored no events
Any, worker= (empty) or over 128 characters ignored 400

What could go wrong

  • The picture freezes or ends when an agent leases the device being watched. Unlikely; a test covers it on a real simulator.
  • A device is erased while still streaming to the console after its lease ends. Unlikely; the reset stops the stream first.
  • A picture freezes, still marked live, after its idle device shut down. Unlikely; the worker stops the stream when the device stops running.
  • The Events tab on a busy gateway loads slowly. Possible on a large history; it reads 100 events at a time.
  • A layout fix moves something else on the page. Possible; the browser tests measure each box.

Words used

  • Running device: a device in state ready (booted and idle, warm pool included) or leased. Only a running device streams.
  • Fleet of one: a single worker answers fleet routes as if it were a gateway with one worker, itself. Its worker id is its instance id, as GET /v1/workers shows it.
  • Device drawer: the panel that opens over the worker page when you select a device row.
  • Stream numbers: Picture, Frames, Rate, Bandwidth and Dropped, today shown under the picture.

Non-goals

  • Sharing one capture between several viewers (One capture per lease, shared by every viewer of its screen #602). Each viewer is still its own capture.
  • Letting an agent token watch a screen. Streams are for operators.
  • A --worker flag on simlock events, or a worker filter on the MCP tools.
  • Booting a shut-down device to watch it.
  • Any change to how a physical iPhone captures (ADR 0031).

Completion conditions

  • On a gateway and on a single worker, an operator opens a ready device with no lease, picks Screen, and sees its picture.
  • While the picture runs, an agent leases that device, and the picture keeps running.
  • After that lease ends, the picture stops before the device is reset.
  • An agent token gets 404 UNKNOWN_DEVICE from the stream route, for its own leased device too.
  • GET /v1/stream/leases/{id} answers 404 and the lease drawer has no Screen tab.
  • The ⓘ button shows the stream numbers; they are not under the picture.
  • The worker page's Events tab fills the page and scrolls back to the oldest kept event of that worker, or to the console's event cap.
  • The layout checks in the layout task pass in the browser lane.

Open questions

None.

Decisions

  • ADR 0045 — A device's screen streams to operators, not a lease's (PR docs(adr): 0045 a device's screen streams to operators, not a lease's (#906) #908): the one route shape, operators only, running devices only, the refusal codes, and when a stream ends (viewer, capture, a lease end before reset, or the device stopping running). Numbered 0045 because PR docs(adr): 0044 the console reads what its page shows, and events mark it stale (#905) #907 holds 0044.
  • ADR 0012 — A worker answers the fleet operations as a fleet of one: why a single worker answers the same /v1/stream/workers/{workerId}/… path, for its own id.
  • Interview, 2026-10-23: the lease route is removed outright (nothing published uses it); the device drawer gets Details and Screen tabs, Details first; the picture starts when the Screen tab opens and stops when it closes, with no button; a not-running device keeps the tab and says why, and starts by itself when it becomes ready; the lease drawer loses its Screen tab with no link in its place; the worker filter is on GET /v1/events only, fleet of one on a worker; a ⋯ list never repeats the button beside it, anywhere; toasts are two columns with a borderless close button; the slow lane moves the stream tests to the device route and adds one "survives a lease" case on an iOS simulator.

Shared values

Value or state Rule Written by Read by Source
Stream route GET /v1/stream/workers/{workerId}/devices/{deviceId} on every daemon; {workerId} and {deviceId} are path segments, URL-encoded #909 #910 ADR 0045 §1
Streamable states ready, leased #909 #910 (which sentence the Screen tab shows) ADR 0045 §3; src/core/domain.ts:202-204
Refusal: unknown device 404, code UNKNOWN_DEVICE, message Unknown device: <deviceId> #909 #910 src/core/registry.ts:147; src/contract/errors.ts:324
Refusal: unknown worker 404, code UNKNOWN_WORKER, message Unknown worker: <workerId> #909 #910 src/gateway/worker-registry.ts:617; src/contract/errors.ts:441
Refusal: not running 409, code DEVICE_NOT_RUNNING, message The device is <state>; only a ready or leased device streams #909 #910 ADR 0045 §3
The lease stream route exists until #910 removes it with its last caller; #909 moves every slow-lane caller (rg -l stream/leases e2e/slow-*.test.ts: 5 files) to the device route, so #910's removal leaves only the console #909 keeps it, #910 removes it — src/stream/definition.ts:27; e2e/slow-android-stream.test.ts:93,157,212; slow-ios-physical.test.ts:494,602; slow-ios-physical-manual.test.ts:174; slow-android-physical.test.ts:190; slow-ios-stream.test.ts:78
docs/internal/DESIGN.md #910 edits the drawer bullet (ends at line 167); #912 edits the Menu bullet (starts at 168) and Toast. Adjacent lines: whichever lands second rebases the other's line, no wording shared #910, #912 — docs/internal/DESIGN.md:157-184
website/.../worker/console.mdx #910 edits the Screen and device-drawer text; #911 the worker Events tab; #912 nothing #910, #911 — console.mdx:126,159

Behaviours

Behaviour (invoker, action, what it sees) Seam Cases Task
An operator asks a single worker for a device's stream createWorkerStream mount (src/stream/create.test.ts) ready → 200 frames; leased → 200; shutdown, provisioning, reclaiming, quarantined, physical absent → 409 DEVICE_NOT_RUNNING; unknown device → 404 UNKNOWN_DEVICE; another worker id → 404 UNKNOWN_WORKER; driver cannot capture → 415 #909
An agent token asks for a device's stream worker mount and gateway (create.test.ts, stream-gateway.e2e.test.ts) worker, device it holds → 404 UNKNOWN_DEVICE; gateway, device it holds → 404 UNKNOWN_DEVICE; nothing is opened #909
An operator asks a gateway for a device's stream src/daemon/stream-gateway.e2e.test.ts relayed → 200 frames; unknown worker → 404 UNKNOWN_WORKER; disconnected worker → 503 WORKER_UNREACHABLE; worker's 409 and 404 kept with their codes #909
A watched device changes createWorkerStream (create.test.ts) ready device leased → stream keeps running; lease ends → capture stopped before the reclaim hook returns; reclaimed before the first frame → 409 DEVICE_NOT_RUNNING; device.shutdown, device.absence-detected, device.quarantined, device.deleted for the device → capture stopped, stream ends; the same events for another device → nothing; capture ends → stream ends; viewer aborts → capture closed #909
A real unleased simulator streams, and keeps streaming when leased slow lane e2e/slow-ios-stream.test.ts unleased ready → frames; leased while streaming → frames continue; every other slow test that streams (slow-android-stream, slow-ios-physical, slow-ios-physical-manual, slow-android-physical) uses the device route #909
An operator opens a device drawer and its Screen tab e2e/console/screen.spec.ts opens on Details, no stream request; Screen → request to the device route and a picture, ?drawer=screen; closing drops drawer, so the next device opens on Details; leave tab → request aborted; close drawer → aborted; worker and gateway mode use the same URL shape; the device gets leased with the tab open → the same request stays open (one capture opened, none closed); the device is shut down while watched → the stream ends and the tab shows its sentence #910
The Screen tab of a device that is not running e2e/console/screen.spec.ts shutdown, provisioning, reclaiming, quarantined, absent → its sentence, no request; device turns ready while open → picture starts; streaming off on the worker → "Streaming is off on ."; unavailable → "Streaming is unavailable on . ", both as today #910
An operator opens the stream numbers e2e/console/screen.spec.ts ⓘ → popover with State, Picture, Frames, Rate, Bandwidth, Dropped; Escape and a second click close it; no numbers under the picture #910
An operator opens a lease drawer e2e/console/leases.spec.ts tabs Details and Events only #910
A caller asks for a stream path that names no stream service.test.ts the old GET /v1/stream/leases/lse_1 and other unmatched paths → 404 UNKNOWN_DEVICE "This path names no stream" #910
A caller pages GET /v1/events?worker= app.test.ts (refusals), e2e/gateway-event-relay.test.ts (pages), history.test.ts (memory) gateway w1 → only payload.workerId: "w1", worker.rejected excluded, paged with before and limit; with since; worker own id → all; worker other id → none; empty or 129 characters → 400 #911
An operator opens a worker's Events tab e2e/console/workers.spec.ts:783 fills the page height; scrolls back to older events of that worker until the start or the console's event cap; other workers' events never appear; a new event of the worker arrives at the top #911
The page reserves room for a scrollbar layout spec html's computed scrollbar-gutter is stable on Overview, Fleet and a worker page #912
An operator opens a worker page layout spec no breadcrumb; warm pool tab has a 1px line between its facts and its table; going back to the Fleet with its page kept is the browser's Back (the tests that clicked the breadcrumb use it) #912
An operator looks at fleet and Inbox rows layout spec fleet row ⋯ inside its cell, 24px from the table's right edge; Inbox row buttons 8px from the row's top and bottom lines #912
An operator sees a toast layout spec close button inside the toast; text never under it; one-line toast's text and X share a centre line within 4px #912
An operator opens a ⋯ beside a button ui/src/ds or model/actions Vitest, and layout spec drained worker header: Undrain button, ⋯ without Undrain; Inbox drained row: same; lease drawer, whose only action is dangerous: no button, ⋯ keeps Release lease; a ⋯ left with no entry is not drawn #912

Tasks

flowchart LR
  T09["#909 device stream route"] --> T10["#910 console Screen tab on the device"]
  T11["#911 worker events history"]
  T12["#912 layout fixes"]
Loading

Completion conditions → tasks: 1–3 and 4 → #909 (server) and #910 (console); 5–6 → #910; 7 → #911; 8 → #912.

Open work in the same area:

Written by an agent.

Activity

  1. added
    feature:specSpec in progress, business and technical together.
    on Oct 23, 2026
  2. V3RON commented on Oct 23, 2026

    @V3RON
    ContributorAuthor

    Spec updated: Problem, business sections, Decisions (ADR 0044, PR #908), Shared values, Behaviours and Tasks added.

    Written by an agent.

  3. V3RON commented on Oct 23, 2026

    @V3RON
    ContributorAuthor

    Spec updated: ADR renumbered to 0045 (PR #907 holds 0044); a device stream also ends when its device stops running; slow-lane callers move in #909; Behaviours and Tasks now name #909–#912.

    Written by an agent.

  4. added a commit that references this issue on Oct 23, 2026
  5. added
    feature:plannedSplit into tasks. Never picked up itself.
    and removed
    feature:specSpec in progress, business and technical together.
    on Oct 23, 2026
  6. V3RON commented on Oct 23, 2026

    @V3RON
    ContributorAuthor

    Spec approved: ADR 0045 accepted (PR #908, stays open at the bottom of the stack); #909–#912 ready; feature planned.

    Written by an agent.

  7. V3RON commented on Oct 23, 2026

    @V3RON
    ContributorAuthor

    Delivery run

    Merged: none (the stack merges as a whole once every task has joined)
    Parked: #909 (PR #923): the slow lane could not run, because the disk had 14.3 GiB free and the lane needs 15 (needs-hardware)
    Waiting: #912 (PR #924), reviewed, in the stack; #910 (PR #928), reviewed, top of the stack; #911, on #914 and #915; ADR #908, reviewed, bottom of the stack
    Decided: none

    Written by an agent.

  8. github-actions commented on Oct 23, 2026

    @github-actions

    All 4 sub-issues are closed. If the completion conditions hold on main, close this feature and flip its ADRs to Accepted.

  9. github-actions commented on Oct 23, 2026

    @github-actions

    All 4 sub-issues are closed. If the completion conditions hold on main, close this feature and flip its ADRs to Accepted.

  10. github-actions commented on Oct 23, 2026

    @github-actions

    All 4 sub-issues are closed. If the completion conditions hold on main, close this feature and flip its ADRs to Accepted.

  11. github-actions commented on Oct 23, 2026

    @github-actions

    All 4 sub-issues are closed. If the completion conditions hold on main, close this feature and flip its ADRs to Accepted.

  12. V3RON commented on Oct 23, 2026

    @V3RON
    ContributorAuthor

    Delivery run

    Merged: #908 (ADR 0045), #923 (#909), #924 (#912), #928 (#910), #943 (#911) — merged by the maintainer after the gate's third CI attempt
    Parked: none
    Waiting: none
    Decided: none in this run (#912's dependency for #915 was dropped by the maintainer during #905)

    Fixed during the run: the stack sync with #905 left useLiveResource in #910 (now useRouteQuery) and the ADR index rows out of order; #911's Events tab filter now follows the daemon mode when it arrives late.

    Written by an agent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature:plannedSplit into tasks. Never picked up itself.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions