feat(ui): read substrate statuses as words, and chart the actor mix - #2706
feat(ui): read substrate statuses as words, and chart the actor mix#2706Charlesthebird wants to merge 9 commits into
Conversation
The controller names the actor states it knows and falls back to the protobuf constant for the rest, so `ACTOR_STATE_CRASHED` reached the page unread. The substrate page now turns any constant-shaped status into a word and drops the enum-name prefix that only repeats the column header. Crashed and failed get their own tone rather than sharing amber with the transitions, and the "Actors by status" tags are chips now, so the summary is coloured the same way the table is. Fixes #2702 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
The fixtures held three of the nine states a controller can report, so the idle tone was drawn by nothing and `ACTOR_STATE_CRASHED` was the only wire constant under test — enough for a humaniser that special-cased that one word. Deletion no longer shares the transition colour. It is the one in-flight state that does not come back, and an actor reading the same shade as one taking a snapshot is an actor nobody looks at twice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
The tiles say how many actors are running; nothing said what the rest were doing, and with the table paged there was nowhere to find out — a reader on page one of 410,110 could not learn that most of them had crashed. Each card now carries a bar above its table, a segment per actor or pod, coloured by status and grouped with everything parked at the end. The legend lists every state a controller can report, so `Crashed` is a thing a reader knows can happen before one does. The tab icon is the kagent mark, and `branding.faviconUrl` lets a distribution replace it alongside the name and logo it could already replace. Status borders were 1.28-2.83:1 against their own fill, which is a decorative edge rather than a boundary. Every tone now measures at least 3:1 in both themes, and the idle chip takes the strong border token rather than the app's hairline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
`ateapi.Worker` carries no actor reference — the binding is recorded on the actor, in `ActorStatus.worker_assignment` — so every worker came back with an empty actor and the workers table reported an idle pod for each one, whatever was running on it. Introduced in #2697, which dropped the fields when v0.0.25 removed the singular assignment from `WorkerStatus` and put nothing in their place. Joined in the service, where both whole lists are in hand: the reads that reach a browser are pages, and a join across two pages matches only where they overlap. A worker holds several actors under v0.0.25 and `SubstrateWorker` has room for one, so the lowest actor id on the pod wins. Lowest rather than first seen because ate-api returns actors unordered — by arrival the cell would name a different actor on each poll. Carrying the whole set needs a repeated field in system.proto. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
The workers bar coloured its segments from the loaded actor page, so searching, sorting or paging the *actors* table recoloured the *workers* one — a pod whose actor fell off the page read as `Not loaded`, a status no cluster reports. The controller has both whole lists when it builds the response, so it carries the status on the worker entry and the page joins nothing. Also from review: - Every actor is offered to the join, not only those the atespace scope kept. A worker running an actor whose template lives elsewhere reported itself idle. - A live actor on a shared pod wins over a parked one; the lowest id breaks ties. - A failed read no longer renders as "No actors in this scope", and the legend stays when a scope empties so the table beneath it does not jump. - The per-actor ceiling is 80, which is the widest row that fits the card. - An extension's PNG favicon is no longer served under an SVG type, and the created link carries the marker so a second call retargets it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
`ActorStatusLabel` fell back to the wire constant for a state it had not been taught, and callers sort on what it sends while showing what they sorted — so `ACTOR_STATE_DELETING` filed itself under A and read as "Deleting" beneath a heading that says sorted by status. Every value is named now, including ones added after this build. The test walks `ActorState_name` rather than a list, so a new state fails here instead of reaching a reader as a protobuf symbol. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
There was a problem hiding this comment.
This automated review is a first pass. A manual review will follow once the blockers are addressed.
Reviewed the branch. Nothing blocks merge. The review below was written against a larger branch that also carried the controller fix; that work came out in 62182d90 and moved to #2709, so only the findings against the UI still stand.
Found and fixed — four, already pushed
- A failed read rendered as "No actors in this scope", showing a broken backend as a healthy empty cluster (
ffe57a7c). - The legend vanished when a scope emptied, moving the table under whoever was reading it — the thing the comment above it argues against (
ffe57a7c). - The bar asked for 1077px at its 120-segment ceiling and spilled outside a ~990px card; the ceiling is 80, which is 717px (
ffe57a7c). - An extension's PNG favicon was served under
type="image/svg+xml", and the fallback link lacked the marker so a second call appended a second icon (ffe57a7c).
What I checked
✅ Every status border measures at least 3:1 against its own fill — worst case 3.01:1 light, 3.02:1 dark, up from 1.28:1 and 1.96:1.
✅ Green and red stay apart under deuteranopia, protanopia and tritanopia (text ΔE 47–87), and no status is carried by colour alone: every chip and legend key is labelled.
✅ The actors bar needs no controller change — it reads actorStatusCounts from the summary, and humanizeEnum covers the wire constants the shipped controller still sends for CRASHED and DELETING.
✅ The fixtures match the shipped controller: raw constants for those two states, words for the rest.
✅ Nothing under go/ or proto/ — git diff against the merged main is empty for both.
Left for #2709
The Workers card has no bar. workerFromProto sets no actor fields, so actor_id is empty on every cluster and a bar there would draw an idle fleet while pods are running. The issue carries the diagnosis, the data-model question and this dependency.
🤖 written by Claude
The worker→actor mapping is a controller bug with its own cause (#2697) and its own issue (#2709), and fixing it raises a data-model question — a v0.0.25 worker holds several actors while `SubstrateWorker` has room for one — that belongs to whoever owns substrate rather than to a UI change. The workers bar goes with it. `workerFromProto` sets no actor fields at all, so `actorId` is empty on every cluster: the bar would read as an idle fleet while pods are running, which is worse than not drawing it. What stays needs no controller change: the actors bar reads the summary's own counts, and a wire constant is still read as a word by the page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Nicholas Bucher <behappy54321@gmail.com>
|
A bar above the Actors table, one segment per actor, coloured by status with everything parked sorted to the end. The legend lists every state a controller can report, so The Workers card has no bar: its Actor column is empty on every cluster (#2709), so one there would draw an idle fleet while pods are running.
🤖 written by Claude |


🤖 written by Claude (start)
Changelog
Substrate actor states read as words rather than wire constants, and a bar above the Actors table shows the mix — a segment per actor, with a legend of every state a controller can report.
Closes #2702
Testing
cd ui && VITE_API_MODE=mock yarn dev, open/substrate.CrashedandDeleting, notACTOR_STATE_*.7f21— the bar and its caption narrow with the table.Additional Notes
Status borders measured 1.28–2.83:1 against their own fill, so the tokens moved to at least 3:1 in both themes. That touches every antd component reading
colorSuccessBorderand friends, not only this page.The Workers table is untouched: its Actor column is empty on every cluster (#2709), so a bar there would read as an idle fleet while pods are running.
🤖 written by Claude (end)