Skip to content

feat(ui): the console draws each worker's load as gauges on its detail page - #575

Open
V3RON wants to merge 10 commits into
mainfrom
task/569
Open

V3RON wants to merge 10 commits into
mainfrom
task/569

Conversation

@V3RON

@V3RON V3RON commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

What

Each of the Load panel's CPU, memory and disk figures gains a bar in a line of its own underneath, filled to that figure's own percent. The panel was text alone.

before after
CPU 34% busy, 10 cores the same line, with a 34%-filled bar under it
Memory 9.8 GiB of 32.0 GiB available the same line, with a 69%-filled bar under it, in the pressure's colour
Disk 112.4 GiB of 494.4 GiB free the same line, with a 77%-filled bar under it

Swap gains none: the wire sends swapUsedBytes and no swap total, so there is nothing to divide by.

Spec: #569. Part of #527.

Closes #569

How

Plain CSS, not the charts library — the string render the Load tests already use draws an empty <div> from Recharts, so a library gauge's fill could only have been asserted in the browser lane. This one is asserted at both seams.

  • loadGauges() in ui/src/views/load-model.ts answers each figure's fullness as a whole 0–100 percent, and nothing where the reading has no denominator. Whole, so a CPU bar rounds the way the busy share above it rounds. Clamped, because a memory or disk pair is two independent non-negative numbers on the wire and the console revalidates no view.
  • The level arrives as a --load-bar custom property; every colour comes from styles.css by tone class (load-bar-ok/-warn/-error/-idle, deliberately not .status-* — the lane checks that every .status shows a word, and a bar has none). The CSP refuses an injected <style> element and applies a style attribute, which is how the level is allowed (checked against a live daemon).
  • Memory takes the tone its pressure already prints. CPU and disk stay --status-idle: the search found no CPU or disk threshold anywhere that reaches a view, and the console invents no threshold.
  • A gauge is aria-hidden and takes no focus — the figure above it is the text, so nothing is said twice and no number is visible only as a shape.
  • FigureRow holds the shared "unavailable, or a figure with its gauge under it" shape, so CPU, memory and disk each hand it their own text rather than repeating it.

Motion

The fill slides to a new level over --duration, which prefers-reduced-motion: reduce already zeroes document-wide. Measured before choosing it (Chromium, Chrome's own counters, 600 level changes on three bars at one per frame — 15× the real 15 s cadence): transition: width cost 0.132 ms layout and 0.827 ms task per frame; transform: scaleX() skipped the layouts (7 vs 614) but cost more total task time (0.900 ms). Neither made a long task, so the compositor-only trick buys nothing here and width is the simpler shape.

DESIGN.md's Motion section said nothing slides, so this PR amends it with the one exception and its reason, and adds the gauge to the guide's component list. docs/CONSOLE.md's Load panel paragraph says what the panel now draws.

Done when

  • Every test in the spec is green. 181 unit tests pass, incl. 12 new: percent math, both clamp directions, no-denominator, one bar per figure row and none in Swap/Pressure/Read, no bar in an Unavailable row, a zero-total figure printed with its number and no bar, the memory tone from each pressure level, CPU/disk idle at any level, the number said once, no bars for no-load/disconnected, and the card unchanged.
  • pnpm test:console --project=chromium is green. 4 new lane tests: each fill's width is its figure's percent of its track within a pixel, one fill per figure required; the track caps at 20rem rather than filling its value column and sits under the figure, left-aligned with it; each fill's colour is its tone's status token and never the accent, in both themes — with all four tones painted by the real component in both themes, driven by answering the page's own /v1/workers read with memory.pressure set to each level in turn (a Linux worker never reports one); the transition is width over --duration and 0s under emulated reduced motion. The lane's existing 360px, keyboard and axe AA rules still pass.
  • They fail for the right reason. Mutation-checked: dropping the clamp, the no-denominator guard, the tone map, the transition, the accent rule, the max-width cap, or turning the bar back into an inline sibling each turns its own test red. On the colour side: deleting the three unreachable tone rules, swapping one tone's colour in the dark theme only, and painting another with the accent each fail a named assertion.
  • A bar under CPU, memory and disk; memory matches its pressure — checked on this machine (macOS, so the worker reports pressure): memory normal drew the ok green at 41%, CPU 26% and disk 97% drew neutral. warn and critical are no longer left to that check: the lane drives them through the route the page reads, so the whole chain — pressure → tone class → token → painted pixel — is asserted in both themes without a macOS-only machine.
  • The bar slides, and not under reduced motion — the fill eased 66 48 34 25 20 17 16 16 px over eight frames after a level change (transition: width 0.12s), and reads 0s under emulated reduce. A 120 ms ease is not settle-able by eye, so the lane asserts the computed transition.
  • The docs say what is now true — docs/CONSOLE.md Load panel paragraph; docs/internal/DESIGN.md Motion, plus a Load gauges entry under Components.

Status

Implement: done Review: round 1, passed Hardware: n/a Gate: waiting for the stack

Assumptions

  • Assumption: The gauge is 8px high, capped at 20rem wide, and --field for a track. The spec fixes the shape (a bar under the figure) but not these numbers; taken from the console's own vocabulary — the 8px square a status uses, --field as the recessed surface, and a cap so a wide panel does not turn one line of text into a band.
  • Assumption: The cap is measured as 20rem against the value column (minmax(0, 1fr)), so on a wide panel the bar stops short of the column's end. The spec says "under the figure"; which of the column's widths it fills it does not say, and the narrower one is the quieter.
  • Assumption: A memory gauge with no pressure in the view is idle rather than ok. The spec names pressure as the colour's source and says nothing about its absence; Linux workers never send one, so a green default would claim "healthy" from a field that was never read.
  • Assumption: The reduced-motion half of the transition test is guarded to Chromium (browserName), the way the lane's keyboard tests already branch on it. Playwright documents reducedMotion for all three engines, but only Chromium's was verified on this machine and its Firefox/WebKit binaries are not installed here; a guard that could hide a real failure in the other two is worse than a narrower one.

What the review changed

A fresh-context review of this diff (five axes, spec in hand) found four things worth a commit each; all are in the branch:

  • The gauge clamped with Math.round while the figure above it rounds with the module's roundHalfUp. They differ just under a half — a busy share of 0.49999999999999994 printed 1% and drew a 0 bar. Now one round, and that float is in the test's list so its title is true of every case.
  • The colour test's DOM probe asked the stylesheet what a tone class would paint. It proved the stylesheet's rule but never passed through the component, so the pressure → tone map it leans on was pinned only in the unit seam. It now drives real data through the route the page reads.
  • docs/CONSOLE.md said no CPU or disk "limit" is reported, on a page that prints (limit) beside the CPU figure when the worker limits itself — a container's user saw both at once. The word is threshold; the doc says so, and what a (limit) is.
  • DESIGN.md's new entry opened "A figure of how full something is carries a bar", which is binding for later console work and made the card's Load and Capacity rows wrong as they stand (a card gauge is this task's stated non-goal). It now describes the panel it was written for.

Plus two test-honesty fixes: the fill-width test waited on the reading's stamp, which moves between reads even when the levels do not, so it retried for nothing; and the transition test's title promised a slide its body never sees, so it now says what it checks.

Not in this PR

No gauge on the worker's card. No swap gauge. No CPU or disk threshold — that would be a wire change of its own. No history or graph. No wire or protocol change.

Review

Spec review: 0 blocking, 0 fixed, 1 note. Code review: 1 blocking, 1 fixed, 0 notes. Claims review: 1 blocking, 1 fixed, 1 note.
Rounds: 1. Tokens: n/a.

Fixed unreviewed (text only, delivery rule 14):

  • code: ui/src/views/worker-detail.tsx:420 said style-src does not govern style attributes; probed, the policy refuses the attribute and the level survives because React writes through the CSSOM
  • claims: docs/internal/DESIGN.md:192 pointed at the charts rule "below"; that rule is the bullet above the gauge entry
  • code: ui/src/views/worker-detail.tsx:429 and DESIGN.md cited ADR 0011 for markup attributes; the ADR says only that the style prop creates no <style> element

Nothing was rejected: each finding was reproduced against the code at the reviewed commit and confirmed. The CSP one came with a probe — a page served with style-src 'self' logged a violation and left the custom property empty from markup, and resolved it through the CSSOM.

Written by an agent.

V3RON added 8 commits October 14, 2026 21:06
…l page

Each of the Load panel's CPU, memory and disk figures gains a bar in a line
of its own underneath, filled to that figure's own percent. Memory takes the
colour of the pressure it already prints; CPU and disk stay neutral because
the worker reports no limit for them that the console could judge them
against. Swap gains none: the wire sends swap used and no swap total.

The gauge is plain CSS rather than the charts library, because the string
render the Load tests already use draws an empty div from Recharts, so a
library gauge's fill could only be asserted in the browser lane. Moving the
fill's width costs 0.13ms of layout per change, measured, over a panel that
changes once every 15 seconds; the slide uses the existing --duration token,
which prefers-reduced-motion already zeroes.

Closes #569
The spec check on #569 found three gaps this commit closes in code:

- LoadFacts grew to cyclomatic 11, so the three figure rows' shared
  "unavailable, or a figure with a gauge under it" shape moves to
  FigureRow, which each of CPU, memory and disk hands its own text to.
- Nothing proved the bar is under its figure rather than beside it, or
  that it caps rather than filling the value column; the new lane test
  measures both on a page wide enough for the two to differ.
- The fill's own 120 ms ease could land a measurement between two levels,
  so the percent test now waits for the reading and the fill to settle
  together instead of only for the reading.

Closes #569
CI runs the console lane on Linux, and only macOS reports a memory pressure,
so a live worker can only ever paint the idle tone there. Deleting the ok,
warn and error rules from the stylesheet left the suite green. The colour
test now asks the stylesheet what each of the four tone classes would paint,
in both themes, and still never the accent.

It checks the colour rule only. Which pressure reaches which class is the
unit test's, and no healthy machine can be made to report warn or critical.

Closes #569
The gauge clamped with Math.round while the figure above it rounds with the
module's own roundHalfUp. The two differ just under a half: a busy share of
0.49999999999999994 printed 1% and drew a 0 bar. No reading will ever be that
number, but the spec says the two round identically and a near-copy of the
canonical helper sat beside it.

The test's list now includes that float, so its title is true of every case it
runs and the pair of roundings is told apart by a named assertion.

Found by the code review.

Closes #569
One test was titled for a figure that says Unavailable and also carried a
Memory row that says "12.0 GiB of 0 B available" — a figure shown with its
number and no gauge, which is a different promise and was named by no title.
It now has its own, and asserts the number survives as well as the bar's
absence.

Found by the code review.

Closes #569
Only macOS reports a memory pressure, so a worker in CI — Linux — can only ever
paint the idle gauge. The colour test closed that with a probe: it built a
load-bar in the page and asked the stylesheet what each tone class would paint.
That proved the stylesheet's rule, but never passed through the component, so
the tone map it leans on was proven only in the unit seam.

This answers the page's own read of /v1/workers with memory.pressure set to each
of the four states and every other field left as the daemon sent it, so the real
component paints each tone, in both themes, and no tone rule — or accent
substitute — survives being changed. Verified: a dark-only swap of one tone's
colour, and painting another with the accent, each fail a named assertion.

Two smaller things from the same review. The fill-width test waited for the
reading's own stamp to hold still, which moves between two reads even when the
levels do not, so it retried for nothing; it now compares the levels themselves.
And the transition test's title promised a slide to a new level, which its body
never sees — it reads the stylesheet's transition back, so that is what it says.

Closes #569
CONSOLE.md said CPU and disk stay neutral because no CPU or disk "limit" is
reported, on a page that prints "(limit)" beside the CPU figure whenever the
worker limits itself — a container's user saw both at once. The console invents
no thresholds, so the word is threshold; the doc says so, and says what a
"(limit)" is instead. It also now says the memory bar is neutral where no
pressure is reported, which is every Linux worker.

DESIGN.md's new entry opened "A figure of how full something is carries a bar",
which is binding for later console work and makes the worker card's Load and
Capacity rows wrong as they stand — and a card gauge is this task's stated
non-goal. It now describes the panel it was written for and says so.

Found by the code review.

Closes #569
@V3RON
V3RON marked this pull request as ready for review October 15, 2026 06:55
@V3RON
V3RON added this pull request to stack #590 October 15, 2026 08:04
V3RON added 2 commits October 15, 2026 10:16
…e charts rule is

The review probed the claim rather than reading it: a page served with
`style-src 'self'` drops the whole style attribute, so the custom property
arrives empty, while the same level set through the CSSOM resolves. The gauge
works because React writes styles through the CSSOM — which is what ADR 0011
says — and not because style attributes sit outside `style-src`, which is what
the comment and DESIGN.md both said. The distinction is not academic here: the
unit seam renders the gauge with `renderToStaticMarkup`, and that markup is
exactly what the policy refuses, so only this component's own render paints a
bar. Both now say the reason, and the guide adds the consequence the reviewer
named: a gauge is drawn by the console's code, never by server-rendered output.

The gauge entry also pointed at the charts rule "below" it. That rule is the
bullet ten lines above, so the pointer sent a reader away from the answer.
…e attributes

The fix that replaced one unsupported CSP claim wrapped the replacement in
another: it cited ADR 0011 for markup style attributes being refused and the
bar coming out empty. The ADR says neither. It states one thing — React's
`style` prop is allowed because it creates no `<style>` element — and is silent
on attributes, so citing it for what the policy does to markup puts a claim in
the ADR that its authors never wrote. The attribution now stops where the ADR
does, and the comment says outright not to read the policy's behaviour into it.

What the probe did establish stays: a level written into a style attribute is
refused by `style-src 'self'` and the bar comes out empty; the same level set
through the CSSOM resolves, because the policy does not reach there. That is
observed here, not sourced from the ADR, and the comment now keeps the two
apart. Same correction in the design guide's gauge entry.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The console draws each worker's load as gauges on its detail page

1 participant