ui/js/tree.js draws subagents.md §12's data: the main agent at the root, each
subagent under its parent in the order added (name, id, position, state in
team.read.states' words, model, mode with request options, context % and a
meter, its progress line, its room messages and the last one), open slots, and
the ended ones folded apart, dimmed, with how each ended; open its session,
its findings, stop a live one. Not wired into the activity panel yet: the
server's /api/v2 does not carry the tree, the transcript or the findings yet.
tests/ui: the spec's own example tree, drawn and acted on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ui/js/progress.js draws harness spec 3.4.0's `progress` (progress.md) with the
spec's texts as the page's sentences: the thirteen phases, their variants and
field words, the line with its clock in whole seconds (5s, 1m 05s, 1h 02m),
none for stopped and failed, retrying counted down. A live reply takes
`progress` from any stream frame that carries it and ticks once a second; with
no progress (an older server, or null) it draws the status text as before.
The sentences are the server's to translate: gen-i18n.py takes them from its
catalogues once they are there (SERVER_PENDING), English until then, without
failing.
tests/ui: every case of the spec's conformance/progress.json (copied, 3.4.0),
and a live stream's progress, clock, fallback and stopped line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Terminal panel opens a shell on the device an agent chat works on, in its
folder: for a new agent chat as soon as the device and folder are chosen (the
server's /api/v2/devices/{device}/terminal/ws?directory=, which the chat's first
message takes over -- the same socket carries on), and for an open agent chat
from the header (/api/v2/chats/{chat}/terminal/ws, the web page's shell).
Offered where GET /devices says the device opens a terminal.
The socket is Rust's (src-tauri/src/terminal.rs, tungstenite on the rustls
ureq already brings): the bearer token goes in the handshake and never reaches
the page, which gets the frames as `terminal` events and sends keys, sizes and
close through commands. 4401 signs the app out as a 401 does.
xterm and its fit addon are the server's vendored build, pinned through
fetch-vendor.sh like CodeMirror and loaded on the first open. Send to chat puts
the selection, the last marked command and its output, or the screen into the
composer, never sent. The terminal and the activity panel take turns in the
panel column. Continue in a terminal stays in the chat's menu.
tests/ui: seven B3 cases with a mocked socket, at 1280 and 390 px.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Subagents chip on a new chat follows `/models` -> model.subagents.offered, the same
team_run.offered the web page renders from. The app read /models once, at sign-in, and kept
it while it stayed open in the tray: subagents switched on afterwards (or a model added)
never reached a new chat until a restart. startNew() now re-reads it (at most every 5 s,
keeping the last list if the read fails) and redraws the draft's chips (modelsChanged()).
An open chat already re-reads its own `subagents` with GET /chats/{id}.
tests/ui: A5 cases -- parallel model offered: chip, slider to the server's cap, no note;
one-request model offered named_only: the `own` note; offered false everywhere: no chip;
switched on while open: the next new chat has it; an open chat follows its `subagents`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An agent chat's bash approval left "Waiting for you to allow bash…" with no card: cardOf()
called append(actions, a, b, c) while append() took one array, so `children.flat` threw for
every approval and plan card (questions used el.append and worked). renderCards() had already
unhidden #cards, which stayed empty. append() now takes children as rest arguments too.
- The editable command is `detail` (services/device_link.py puts the fixed "Run a command" in
the title); it is sent back as the item's text only when changed. Non-editable approvals
show the title and the device's input. `purpose` ("It says: …"), `reason` and the asking
subagent (`job`) are shown, as on the web page.
- renderCards() keeps a card already on the screen (by id) instead of drawing every card again
on each `ask` frame, which the server re-sends whenever the countdown changes; a card that
cannot be drawn shows the error instead of an empty box.
- tests/ui/: headless harness with a mocked Tauri bridge; five A1 cases (device bash card
shows and is visible, Allow/edit/Deny post to /chats/{id}/interactions/{card}, redraw keeps
edits, reopen gets the card from the stream's first pass, 390 px).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>