The accent budget — two claims per screen (kpbk7)
The TUI carried 282 ACCENT references and a single hub screen painted twenty-plus orange spans: the header border and its breadcrumb, every key in the footer hint bar, each folder header, the "N live" counters, the rotating spinner verb, the "Task" tag, the "1-12 of 40" window counter, the project slug, the live rows' throughput sparklines, and the selected row. A palette with one accent only works while the accent is scarce; at that density brand orange had stopped meaning "look here" and become the colour of chrome.
The standing rule, declared as ACCENT_CLAIM_BUDGET in src/tui/theme.ts next to the neutral ladder and the spacing scale: a rendered screen may spend ACCENT on at most TWO claims — the FOCUSED element (the selected row, the focused pane's frame, the caret of the input the keyboard is talking to, or on an otherwise empty screen the single call to action) and the BRAND MARK. Everything else demotes onto the neutral ladder: chrome frames and dividers to RULE, secondary labels/keys/tags/counters to MUTED_STRONG, their subordinate text to MUTED, placeholders to MUTED_FAINT. Demotion is not a loss of hierarchy — the ladder still separates a key from its label, it just does it with luminance instead of hue.
What actually moved in the hub: the AppHeader frame to RULE and its current crumb to bolded body text, every KeyHints key to MUTED_STRONG, the folder-header "N live" counters, the spinner verb, the kind tag, the project slug and the "1-12 of 40" window counters to MUTED_STRONG, the RunPreview field label to MUTED_STRONG and its resting divider to RULE, and the compose/session bar labels in app.tsx to MUTED_STRONG (their carets stay accented — that is the focus). A live row's sparkline used to ramp up to the accent, which put an eight-cell orange bar on every running row; it now tops out at the run's own state colour, the same colour as the status badge already on that row.
Run recipe:
npm run selftest:accent-budget # offline; renders the hub screens through Ink, no networkThe guard RENDERS each hub screen through Ink with colour forced on and reads the ANSI back, rather than counting ACCENT tokens in the source — a source count cannot tell a branch that fires from one that does not (selected ? ACCENT : undefined is one reference and paints at most one row), and it cannot see a colour that arrives through a helper, which is how the sparkline's ramp reached the accent without ever naming it. A claim is measured as a BAND: one maximal group of adjacent frame rows carrying at least one accent-painted cell, with whitespace excluded because an accent colour on a bare space paints nothing. Each screen fixture DECLARES the claims it is allowed, and the smoke asserts three things independently: the band count is within budget, every accent-painted row carries a declared claim (which closes the loophole where two accented elements on adjacent rows merge into one band), and every declared claim is actually painted (so a fixture that stopped exercising the selection cannot pass by rendering nothing).
Extend recipe. A new hub screen gets an entry in the screens array of scripts/kpbk7-accent-budget-smoke.tsx: a name, a node that renders the composed screen, and its claims — each one tagged focus or brand plus a substring of the row that carries it. A fixture declaring more than ACCENT_CLAIM_BUDGET claims fails on its own; the budget is not a knob a screen widens by declaring its way past it. Treat a failing budget as "this element is not a claim, demote it" rather than "the budget is too tight" — raising the number is how the 282-reference sprawl happened the first time.
Known Limitations and Deferred Work. The guard covers the cyberine agents hub screens only: the runs list in all three view modes, the unified agents view, the chat-sessions list, and the empty first-run hub. The chat REPL surfaces (ChatApp, WelcomeBanner, InputBox, Markdown, the run-detail transcript and the diff viewer) are NOT under it — they need a different render harness — so their accent usage is governed by the rule but not yet measured, and WelcomeBanner in particular still spends the accent on its version line and its adapter tiers alongside the brand mark (its leading divider no longer does — see k6shr below). The measurement is also foreground-only and truecolor-only: an accent used as a BACKGROUND is not counted, and in the ansi variant the accent is a terminal-defined colour name with no rgb sequence to match, so the smoke measures the default dark palette.
The sparkline colours by value (kefc7)
The fleet's throughput sparkline maps each point to one of eight block levels. That level — how hot the point is — was computed and then thrown away, and the whole line rendered in a single...
Divider and border weight (k6shr)
Two related fixes to how a Divider (src/tui/components/Divider.tsx) sits next to the chrome around it, documented in full beside the accent budget and the spacing scale in src/tui/theme.ts: