feat(web): raise board sentences to a 15px tier and collapse the label tier - #361
Conversation
… milestone Group the milestone's PRs by dependency tier rather than one per issue, since every issue touches the single-in-flight web/ surface, and record the standing conn for its triage and merge gates. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team>
The milestone's twelve issues triage in PR groups, and the default of two serialised a stage that holds no worktree and takes no lock. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team>
Two triages drew the heading from the Linear issue's own wording, which left the gate's structured acceptance read erroring, and the error surfaces only when the first officer runs the read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team>
A correct heading over numbered items scans as an empty list rather than an error, so the quieter half of the failure reads as having no criteria at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team>
The bullet shape pinned in the previous commit was not the cause; an isolated probe of four criterion shapes shows the scanner keys on the hyphen in AC-N and ignores everything else about the line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team>
A bold label that wraps before its closing marker is skipped, so a hard-wrapped file drops long criteria and returns a partial scan that looks complete. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team>
…l tier Board prose rendered at 12.5px, where the audit put even --ink, the brightest colour in the palette, below the body-text requirement. There was no colour left to spend, so size was the only lever. - Add --fs-sentence:15px and --measure:540px; repoint 65 sans prose rules to weight 500 / line-height 1.55, 47 of them capped at --measure. The cap skips rules carrying overflow-wrap:anywhere and the two prose containers, where 540px would clamp the cards rather than the sentences. - Collapse the label tier onto --fs-label:11px: delete --fs-column, --fs-meta, --fs-breadcrumb (all three had zero callers) and --fs-meta-detail, remap 27 literal font-size:10px sites, 4 font: shorthands and 6 sub-10px literals, and normalise 24 tracking declarations onto .09em. Three declarations in the .1em and .08em groups kept their own value because their content is not uppercase. - Move .next-cockpit-held-revision and .next-cockpit-landed-axes out of their <header>s. Both are absence explanations, which design-next-ui.md already ruled are sentences; neither could reach the sentence tier inside an <h2>'s header without dragging the label with it. - Delete the "see ruling R9" comment with the 9px token it defended. No R9 exists under docs/. - Regenerate all nine frontend byte pins from the assets in one pass. docs/design-next-ui.md records which selectors are sentences, so a reviewer argues with a list rather than re-deriving one from the cascade. It also records the gap this change does not close: 17 further sans rules pass the same test at 13px to 14.5px and were left, because two of them are not sentences and the set needs reading one selector at a time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team>
…ectives A recommendation the first officer then waits on adds a decision rather than removing one, and a deferred gap is worked inside its own milestone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team>
CoverageThreshold: |
There was a problem hiding this comment.
🟡 Changes recommended
docs/design-next-ui.md’s tracking-value inventory is now inconsistent with the actual CSS (additional uppercase label rules still use non-.09em tracking).
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR updates the Next UI typography system to improve board readability by introducing a dedicated 15px sentence tier and consolidating sub-12px typography into a single 11px label tier, with corresponding documentation and byte-oracle test updates to lock the new bundled assets.
Changes:
- Introduces
--fs-sentence(15px, weight 500) and collapses prior sub-12px tokens into--fs-label(11px), applying these tiers broadly across the dashboard CSS. - Moves two cockpit “absence explanation” strings out of
<header>blocks in the rendered markup so they can be styled as sentences, and adds a regression test for that behavior. - Regenerates the pinned bundle byte-length and SHA-256 oracles in the Next UI test suite.
File summaries
| File | Description |
|---|---|
| docs/roadmap-burndown/README.md | Adds workflow-process notes (triage concurrency and AC-scan formatting guidance). |
| docs/design-next-ui.md | Updates canonical UI design contract to reflect the new sentence/label tiers and auditing rationale. |
| CONTRIBUTING.md | Aligns contributor guidance with the updated sentence/label tier rules. |
| cargento/skills/cargento/tests/test_next_page.py | Updates pinned asset digests/lengths for styles.css, assembled page, and next-cockpit.js. |
| cargento/skills/cargento/tests/test_next_flag.py | Updates pinned assembled-page length and digest. |
| cargento/skills/cargento/tests/test_next_cockpit.py | Adds a regression test ensuring absence explanations render as sentence-tier content outside <header>. |
| cargento/skills/cargento/tests/test_focus.py | Updates the pinned assembled-page digest used by focus-page tests. |
| cargento/skills/cargento/cargento_runtime/web/styles.css | Implements the new --fs-sentence / --fs-label tiers, adds --measure, and remaps many rules to the new tiers. |
| cargento/skills/cargento/cargento_runtime/web/next-cockpit.js | Restructures cockpit markup to move two explanatory spans out of <header> blocks. |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The paragraph said the uppercase label tier carries one tracking value and that three rules kept their own. Both halves were false. Nine rules sit off .09em, and six of the nine print capitals: .next-capacity-head span, .next-capacity-models small and .next-capacity-row small at .06em, the course and direction header badges and .next-scope-cue at .07em, and the recovery memo field labels at .04em. Verified one selector at a time against the emitters in next-capacity.js and next-cockpit.js. The cause is worth keeping: the issue inventoried four tracking values and the stylesheet had seven, so sweeping the four named groups never reached the other six declarations. The paragraph now states the smaller true claim, that the uppercase tier is not uniform, and names every rule a reader can check. Docs only. No CSS changed, so no byte pin moves. Signed-off-by: Jared Scott <jared.scott@variable.team>
Correction round 1. Raising a container to the sentence tier without raising the value rules inside it left the absence larger than the fact it stands in for. Measured live on this branch: "assignment unavailable" computed 15px/500 sans in --ink3 while the value that was captured computed 12.5px mono in --ink. This change created that inversion, and it is the one the milestone exists to remove: you can tell a label from its answer, a figure from a gap. Swept for the shape rather than patching the reported cells. Seven value rules now join their absence or caption on the sentence tier: - .next-cockpit-recovery strong, split from the `small` storage cue, which is not a value and stays down - .next-cockpit-recovery .next-project-goal-text, keeping mono - .next-cockpit-now-state strong and the memo field value - .pc-terminal-identity code and strong, each drawn against a 15px absence emitted by the same ternary - .next-attention-open strong Nine more candidates the sweep surfaced are deliberately unchanged: fleet figures are already 27px, section and field captions are meant to sit below their values, and two pairs turned out to be separate components rather than a value and its caption. docs/design-next-ui.md carries the list. Lowering the absences back to 12.5px was the other way out and is refused: it reinstates the sub-floor sentence the header moves exist to remove. Documentation corrections in the same round, all four measured against the tree: the deferred gap is 19 rules rather than 17, because two compose their size, family and line-height across three rules each and no single-rule census sees them; the zero-caller tokens are named rather than counted, since the count turns on whether the 12px step is inside the band; the sub-11px :root steps are stated as four with 9px the smallest; and the APCA justification now carries the audit-only caveat the milestone already carries, beside the recomputed 16.36:1 that shows this was never a contrast defect. Seven byte pins regenerated from the assets. next-cockpit.js is untouched this round, so its size and digest were verified unchanged rather than rewritten. Signed-off-by: Jared Scott <jared.scott@variable.team>
The sentence does not exist on main; #361 authors it, so correcting it is not a promoted finding. A third composed case shows the defect already propagated into the disclosure meant to cover it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Correction round 2. The defect is slot-shaped, not selector-shaped: a value and the absence that replaces it are chosen by a ternary, so they never co-exist in one render. No sweep of co-existing selectors sees the pair, and neither does reading a populated board, because only one branch is ever on screen. Round 1 swept selectors, passed, and missed all six of these. Measured in a browser before and after, both branches built separately from each emitter's own markup: - recovery published value, 11.5 -> 15px, against a 15px absence - rail wait duration, 11 -> 15px - rail token rate, 10.5 -> 15px - rail pace and resets, 11 -> 15px - event time, 11 -> 15px - published value, 12.5 -> 15px Two of round 1's nine exemptions were wrong on exactly this point, and both are in that list: `.next-cockpit-recovery span` was read as a caption when it also sizes every nextProjectValue span, and `.pc-entry-details time` as a timestamp when projectEventTime swaps it for a reason. A rule is a caption only when nothing chooses it as the answer, and only the emitter can say so. Also fixes the caption regression round 1 introduced: `.next-cockpit-recovery strong` is a descendant selector and caught the briefing's own `<strong>`, so PROJECT RECOVERY BRIEFING rendered at 15px, level with every value it labels. Scoped to the value slot, it computes 11.5px again. The invariant test is now slot-shaped. It lists the ternaries and resolves each branch through tests/css_cascade.py, a restricted cascade resolver whose every figure was checked against getComputedStyle in a browser before it was trusted. Both halves survive a mutation check. The old test pinned seven co-existing pairs and would have stayed green through all six of these. Prose corrected against the tree: the deferred gap is twenty-one rules at 12.5px to 14.5px, not nineteen at 13px, because the earlier census used a `font:` shorthand pattern that matched the weight instead of the size and silently dropped two rules; the sub-12px band holds seven tokens once `--fs-2xs` is counted, and the paragraph now states the inclusive reading it uses rather than mixing two. Seven byte pins regenerated. No JS changed this round, so next-cockpit.js's size and digest were verified unchanged rather than rewritten. Signed-off-by: Jared Scott <jared.scott@variable.team>
The membership test this branch authored defined the tier over a single rule's declarations, then named two composed cases beneath it as members. Those two fail its letter while passing its intent, so an audit written against the sentence returns clean while elements still render at 12.5px on screen. The definition and the disclosure under it contradicted each other, and the disclosure was right. Membership is now a property of the element's resolved style: sans with a prose line-height, arriving through the cascade, possibly inherited, possibly from different rules. The warning that a per-rule census understates the set stops being an exception bolted onto a rule that denies it and becomes what the rule predicts. The size of the remaining set is no longer stated here. Every figure this branch produced for it came from a per-rule census, so each is a floor rather than a count, and DRC-4602 owns the audit that can produce a real one. Also adds the rule that an element count carries the date and commit it was taken at. The same shape read 163 and then 183 forty minutes later on an unchanged stylesheet, because the board renders whatever sessions exist. A bare number reads as a property of the code when it is a property of an afternoon. The example now carries its own provenance, since a rule whose illustration breaks it teaches the opposite. Docs only: no CSS, so no byte pin moves. Signed-off-by: Jared Scott <jared.scott@variable.team>
Correction round 3, structural. Three rounds fixed what each was handed and the population kept growing, because the set was never bounded. This closes the class rather than the instances. An absence that replaces a machine string now takes that string's tier: .next-cockpit-work-absent, .next-cockpit-reading-clause-absent and .pc-graph-time. Raising the value instead removes the inversion by breaking the register, and a rate, a timestamp and a duration are compact on purpose. Prose absences in the same panels keep the floor, .next-cockpit-work-limit and .next-cockpit-work-dropped among them, so that rule had to be split out of the declaration it shared. The discriminator is not a new taxonomy. It is the sans/mono register this document already owns: three of the four inversions pair a sans absence with a mono value. And every one of these slots already emits a class separating its machine-paired use from its prose-paired one, so this is CSS only, with no emitter change and no new vocabulary. Fixes the guard's fixture, which built a DOM the app never renders. next-project.js:396 wraps the cockpit in .next-cockpit-content, and without that ancestor an inverted row read clean. The recovery-goal row modelled a <p> where nextProjectGoal emits a nextProjectValue span, so it never exercised the rule added for it, and the graph-row row carried one of the two classes its emitter sets. Re-running the mutation checks afterwards found a survivor the old fixture had masked: reverting the recovery published value dropped value and absence together, leaving the comparison equal while the value left the tier the document records it on. A promotion test now binds that. Makes the resolver fail loudly. An unsupported selector raised None, which the caller read as "does not match", so a rule left the cascade in silence; it raises now. An always-true @media(min-width:1px) left the guard green, so a min-width block that sets a font size raises rather than being dropped. Records the promotion in docs/design-next-ui.md. The tier sentence claimed timestamps and rates sit at 11px while six machine slots render at 15px; it now names them and says why, and names the three that moved the other way instead. Seven byte pins regenerated. No JS changed, so next-cockpit.js is unchanged. Signed-off-by: Jared Scott <jared.scott@variable.team>
"Comparable in size to the one this branch already moved" is a count. The set it compares against is stated three paragraphs earlier as sixty-seven, so the sentence asserts a magnitude two to three times the highest figure any census here produced, and no census produced it at all. Retracting the numbers and keeping this was the same error pointing the confident way: a floor cannot be reported as a count in either direction. The paragraph now says the shape and refuses the size in any form, comparison included. The textarea-and-terminal clause stays, because it is an example rather than a tally. Also narrows the provenance clause on the 163-then-183 readings. It claimed an unchanged stylesheet; 4fb5ee6 changed styles.css by thirteen lines. None of them matches operation-fact, so the rules resolving the counted shape were the same across both readings, which is the true and narrower statement. A paragraph whose whole job is to stop a number being read as a property of the code must not overstate by a step itself. Docs only: no CSS, so no byte pin moves. Signed-off-by: Jared Scott <jared.scott@variable.team>
Correction round 4. Four review cycles each found more of one class, and the class contains instances that predate this branch. A defect class older than the pull request is not the pull request's to close, so this scopes back to the part that cannot be wrong. The rule: no absence rule is raised unless the value it is paired with already resolves at or above the sentence tier. That cannot create an inversion by construction, which is the property none of the last three rounds had, and it needs no bound, no emitter census and no reading of 31 call sites. Twenty absence rules and the value raises that existed only to answer the inversions they created return to their value on main. Four rules this branch invented for that purpose are removed outright: .pc-graph-time, .pc-terminal-identity strong, .pc-semantic-timeline .pc-source, and .next-cockpit-recovery .next-project-value. Two selector groups had to be split because they mixed absences with prose that stays raised, and two that this branch had split are recombined as they were. Two absences stay on the sentence tier, both named by string in the issue's own criterion, and each is safe for a reason that does not depend on finding every pair. "two axes, read separately" is drawn beside values already on that tier. "No revision saved yet" shares one class with the revision line it alternates with, so both branches resolve identically whatever the tier is; it is a polymorphic slot rather than an absence in a ternary, which is a stronger guarantee than a comparison. The guard shrinks to those two and asserts nothing wider. The test it replaces checked an absence's size without its value and so asserted an inversion as correct, which is the third guard on this branch to pass over the defect it was named for: one compared a value against its absence and could not see both move together, one built a DOM the application never renders, and this one read only one side. An absence is now only ever read beside the value it replaces. The documentation follows the smaller claim. The six promotions, the three demotions and the absence-register prose are gone rather than reworded, because a sentence describing work that moved to DRC-4602 is the false-record failure this branch has already had to repair twice. Runtime is negative: styles.css loses 14 lines net. Seven byte pins regenerated; no JS changed, so next-cockpit.js is unchanged. Signed-off-by: Jared Scott <jared.scott@variable.team>
…e syntax The PR Workflow bullet said to always use explicit Closes #NNNN lines. Work here is tracked in Linear as DRC-NNNN, which is not a GitHub issue number, so Closes #4589 points at nothing today and will point at an unrelated issue once this repository's own numbering reaches it -- it currently has #221 and #25 and nothing else. The bullet predates the Linear move. Found by the integrator for PR #364, which correctly used the Implements [DRC-NNNN](url) form that #361 and #362 both use, and flagged that doing so contradicted this file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team>
Five findings from the implementers, each measured by them and re-measured here against the consolidated tree rather than taken on report. DRC-4589 showed its own `AnAbsentVariantBorrowsItsSizeFromTheValueItReplacesTest` does not subsume the delegation tuple I dropped: that class constrains the STAMPED variant and says nothing about the base rule it sits on, so raising `.next-delegation-withheld strong` to 40px against a 32px figure left it green. Its replacement is here and reds at 40px. DRC-4592 showed the same for the rail card: with its title/meta tuple dropped, redefining `--fs-2xs` above `--fs-sm` in `:root` survives every other check in the file, because the surviving assertions pin which token each half uses and cannot see the token move underneath them. Its four-line replacement reds on that mutation. DRC-4595 found a hole in its own sentence-tier count. A length passes a swap where one rule leaves the tier and another joins at 15px, and THIS INTEGRATION IS THAT SWAP: DRC-4589 moved `.next-cockpit-authority>span` off the tier and `>small` on. The oracle would have been green on the change that defeats it. It is a set now as well as a count, because two selectors are declared twice and a set alone loses that. DRC-4591 found a regression its own commit created: `.pc-substrate-steps` sat at 15.0 beside a 12.5 sentence in the same disclosure, because #361's review lowered the neighbour after the branch forked. Lowered to match. Nothing tested DRC-4589's trap 1, that a colour tidied into the authority override block would retire the captain-needed ink step by winning on source order. It is the hunk I resolved, so it is guarded here, and the guard was confirmed to fail when the colour is added. The design doc carries three changes. DRC-4589's correction, that a register makes a repoint safe and a removal dangerous, with its example rebased onto the merged tree: the competitor it names was deleted by DRC-4592, and the live instance is the rail card's own title rule winning on source order. The sentence-tier paragraph now reports the set rather than a capped/uncapped split that no test held. And main's refusal to state the sub-floor size is restored, rescoped to the true total, because the set that IS asserted counts what a per-rule census can see rather than what the page has. Byte pins and both DRC-4596 registries regenerated after the stylesheet edit. Every mutation check excluded the byte-pin oracle, which fires on any stylesheet edit at all and would have reported all five as killed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team>
…gration (#364) * feat(web): give label, value, absence and caption four ink registers Four registers in the one existing :root block, and the label, value, absence and caption rules repointed at them. Twenty-one cockpit and session label rules each spelled var(--ink3) in their own declaration block, so moving the label tier meant editing twenty-one rules by hand and hoping none of them was a value. Nothing changes colour: --ink-label and --ink-absence both resolve to --ink3 under the captain's ruling of 2026-09-17, --ink-value to --ink. An absence separates from its value on shape, never on tone. COUNTS and DELEGATION keep their own ink and gain a family swap plus a leading em dash from [data-next-absent]::before, stamped at emission because no selector can tell a null from a real 0. The three kinds of absence a reading paragraph can state carry data-absence and are told apart by a left rule: dim solid, bright solid, dotted, all three legible in greyscale. The briefing's emphasis is inverted back. FO INSPECTING returns to a mono chip at the label tier with a one-line gloss beside the heading, and the line saying whether the captain is needed takes the sentence tier at full ink. The evidence caption is suppressed when no task was observed, which is what leaves the label, value and caption in three distinct registers, and "captured" leaves the briefing on a two-verb rule that introduces no new absence phrase. All nine byte pins recomputed from the assets across three files. The absence side of every ternary is compared against its own value rather than asserted alone, which is the defect class that cost this milestone four review cycles. Implements DRC-4589 Implements DRC-4593 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * feat(web): name each tab, re-expose the timeline filter, rebuild the rail card Three issues on one branch, because exactly one in-flight PR may touch cargento_runtime/web/ and all three do. DRC-4592 - make each tab name its own panel and carry a derived state cue. A lede per tab at the sentence tier, naming its own tab word, because three of five open onto a heading that does not repeat the label. A derived cue on the label in four states: a figure, a collection read and found empty, one nothing has published, and one whose context has not arrived. Course counts context.project.changes, Decisions the decision facts off the same semantic nextCockpitDecisionSummary selects, and Held to session.departures under the lane rule at nextCockpitDepartureLaneCount, which is now lifted out of nextCockpitDepartures so the section and the cue cannot disagree. Now and Console render no cue, and neither does Held to with the store off: those panels render no collection, which is not an absence to assert. Every read is guarded on the collection being an array first, because a length taken off a list declared on every row reports the schema (DRC-4559, the first Measured Invariant). Departure and revision are defined inline at first use; the reading definition renders only on the arms where NEXT_READING_OFFER does not. DRC-4598 - re-expose the timeline's own filter. nextCockpitTimeline stops passing mode:"decisions", controls:false and passes defaultMode instead, so an untouched tab renders what it rendered before and a reader who presses a button reaches the filtered activity view and the all-events view. One resolver, projectResolveGraphMode, is shared by the renderer and the cockpit heading, so the heading cannot say RECORDED DECISIONS over an all-events list. The mode mirrors to one cargento.next.graph.mode key and is read back on load. The dead nextCockpitProjectScope is deleted; nextCockpitProjectScopeKind survives. DRC-4597 - rebuild the scope rail card around the session title. Two lines: the title alone on line 1 at --fs-sm in --ink, clipped with the whole string in title=; harness, state, age and, on a twin row, the sid on line 2 at --fs-2xs. The builder takes a value and a meta rather than a label and a subtitle, because the project row and the session rows hold their value in opposite slots. The word SESSION moves into the accessible name and the round marker and connector rule carry the kind without it. Moving the sid off the title repairs the withheld-title marker, which the exact-match test missed on every twin row. The harness hoists into the heading when every row shares one. Both branches of every value-and-absence ternary resolve at one size. The cue's three state rules restate their size so the pair check can see them, and the rail's withheld rule supplies font-family only, leaving the one board-wide rule to own the colour. Implements DRC-4592, DRC-4598 and DRC-4597. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * feat(web): put Console's operations first and make the asset test enforce size DRC-4595. The Console panel spent its first 341px of 968 on setup prose for two capabilities a default run has switched off, and the one place on the page a person can write anything sat 86% of the way down, inside a section captioned "local only, nothing enforces these". Nothing leaves the page: the panel now emits the scope header, the project-scope prompt, the operations rail, then one disclosure holding the terminal surface, the observer-model controls and the status line. The summary reads the capability flags rather than whether those bodies came back non-empty, because both return "" on routes where the capability is on, and a capability that is on renders expanded and outside the disclosure. The steer composer is built once, in the project chrome, through the existing renderer rather than retyped markup, so it keeps data-next-steer-form, data-next-draft, data-next-controls-project, data-next-focus and the 500-character cap. The dead nextProjectControls is deleted: it had no caller in web/ and built a second composer, which made "renders once" unprovable. The correction no longer waits for the press, and the field was raised with the caveat so the warning is not drawn larger than the words it warns about. DRC-4596. The asset test measured the palette, which already passes on every pair, and measured size nowhere. It now enforces the label floor, the exact registry of sub-11px literals, and the sentence floor as a literal 15.0 with an exact inventory of what still sits below it, each demonstrated failing by a mutant applied to an in-process copy of the stylesheet. Its blind spot is stated rather than implied away: it reads one rule at a time, so a sentence whose size, family and line-height are composed across three rules is outside it, and two such sentences render today. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * feat(web): tier the board's caveats and put Held to in reading order Two issues on one branch, because exactly one in-flight PR may touch `cargento_runtime/web/`. DRC-4591 — a three-tier caveat rule. Every caveat rendered as one paragraph in the reading flow, and after DRC-4587 the caveat and the finding-label beside it carried the identical font shorthand, so size no longer separated them at all and the same sixty-nine words cost more vertical space than when the issue was filed. `nextCockpitWhy` puts the claim inline and the rest behind a summary, reusing `nextCockpitDisclosureAttr` so the existing restore lane reopens it after a redraw with no registration; Console reuses `projectDisclosure` for the same reason. Nothing is deleted: the one removal is the `two axes, read separately` aside, which said what the footer under the same cards already said, and its reason joins that footer. The rule is recorded as NUI-19, including why tier 3 ships no `docs/` href and no `DEC-N` token — an installed plugin has no `docs/` beside the page, and `RuntimeDecisionCitationsTest` reads the whole runtime tree rather than its comments. DRC-4594 — purpose and inputs before the caveats. A lede says what typing buys, worded to the default board because the unasked lane is off unless the reader started with `--unasked-readings`. The observed record moves from first to last, which overturns half of the placement constraint recorded at `nextCockpitHeldTo` deliberately: the sentence that was anchored to the record's old position is reworded in the same change rather than left claiming a position it no longer has. The re-entry paragraph becomes a primary action over two labelled rows with the platform explanation behind a disclosure, the count labels drop the quantity noun their new group heading supplies, and the `your words` sub-label goes — DRC-4587 set it at the same size as the label it restated, so it was a second label saying less. `PairedBranchRegisterTest` is new and was not in either estimate. A value and the absence that replaces it are chosen by a ternary and never render together, so no selector sweep and no walk of a populated board can see a mismatch; it resolves both branches through the cascade and compares them. It found one open instance on this base, filed as DRC-4607 and pinned in `KNOWN_OPEN` rather than fixed here, so a new violation fails the test and so does fixing that one without moving it off the list. All nine frontend byte figures recomputed from the assets across `test_next_page.py`, `test_next_flag.py` and `test_focus.py`. Implements DRC-4591, DRC-4594. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * docs: reconcile the Held to composition and the caveat rule to the code The shipped skill body and the runtime module map both enumerated the tab in its old order, with the observed record beside the two fields rather than after the landing axes, and neither mentioned the lede. Both now read in render order, and the skill body says what tiering does for a reader: the claim stays on the page, the rest opens, and an opened one survives a redraw. NUI-19 is retoned to the repository's voice standard. The rule itself is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * test(web): build the paired-branch guards on the merged cascade resolver This branch carried its own resolver, written when `css_cascade.py` was not yet on `main`. It is deleted rather than kept beside the merged one, which is strictly more capable: real specificity ahead of source order, an element-path walk that can resolve a branch declaring no size of its own, an error raised on a selector form it cannot express instead of a rule silently dropped, and a lookbehind that stops `font:10.5px/1.5` resolving as 0.5px. Two resolvers drift, and the weaker one produces plausible numbers, which is the failure this guard exists to stop. Two checks survive because the census above cannot make them. A tier-2 summary read against the body it hides is not a value/absence pair, and that test is scoped to DRC-4587's claim and says so; the summary is still the same trap one layer out, because it carries the only words a reader has for deciding whether to open the body. And the COUNTS row emitter is asserted at the source, in the shape `test_the_revision_slot_cannot_invert_because_one_class_carries_both` already uses: by the time a second class reaches the stylesheet, a CSS-only check is comparing two rules rather than noticing there is now a pair. `RAISED_ABSENCES` held one entry, `two axes, read separately`, and this branch deletes that span as a duplicate of the footer under the same cards. Removing it silently would have left the tuple empty and its loop green over nothing, so it is replaced by the landing card's own value and absence. It also did not fail when the class went: `resolve` walks up the path and inherits, so the absence still resolved to 15.0 off the section and compared equal to its value. That is the class's own second docstring failure, "one built a DOM the application never renders", reproduced in the test named for it. A path is only evidence while the emitter still builds it, and no resolver can tell you that it does, so `test_the_retired_axes_span_stays_retired` holds that half against both the emitter and the stylesheet. One inversion neither guard can see is recorded on the census docstring and tracked as DRC-4607: a value inheriting its size, drawn against an absence that declares a larger one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * fix(web): hold a withheld scope title on the absence register Integration fix across DRC-4589, DRC-4592 and DRC-4597, none of which could see the others' trees. DRC-4597 moved `data-next-withheld` off a `<small>` and onto `span.next-cockpit-scope-title`. That class declares `color:var(--ink)` at (0,1,0) -- the same specificity as the bare `[data-next-withheld]` rule and later in the sheet -- so with DRC-4589's AC-3 having stripped the colour from the scope-rail override, a withheld title resolved to FULL ink and rendered byte-identical to a published one. That is the absence-outranks-its-value inversion this milestone exists to remove, and it was reachable only by resolving the pair: two separate rule-counting tests passed over it. The override is (0,2,0) and is now the only rule that can hold the title on the absence register, so it carries the colour again. AC-3's "exactly one rule colours a withheld value" was a count standing in for a property; the property is that every such rule resolves through the register, and that is what is asserted now. `WithheldTitleKeepsTheAbsenceInkTest` resolves both branches through `css_cascade` and was confirmed to fail when the declaration is removed. Two label rules added after DRC-4589 introduced the registers, by branches that never saw them, still spelled `var(--ink3)` beside `var(--fs-label)` in their own blocks. Repointed to `--ink-label`; no colour moves. The DRC-4596 registries and the sentence-tier count are recomputed from the merged sheet. The 70 the branch carried was read against the pre-squash DRC-4587 tree, which raised thirteen rules the narrower #361 that merged did not: main resolves 55 and the four branches here add seven, for 62. The sub-floor inventory is 33, unchanged from main, so nothing here pushed a rule below the floor. Each of the four below-floor absence rules was measured against the value it replaces and all four pairs resolve 12.5/12.5. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * fix(test): reconcile four cockpit oracles the branches broke in each other Each of these passed on its own branch and fails only when the four are in one tree, which is the class of defect a per-branch merge cannot surface. DRC-4591's two COUNTS readers match `class="next-cockpit-count-value">`, with no room for an attribute. DRC-4589 then stamped `data-next-absent` on exactly the absent spans, so both readers silently dropped the two "not published" rows and asserted against three values where the render has five. Every other call site in this file already allows attributes; these two now match it. DRC-4597's own withheld-title test asserts the count of one that this issue's change refuted -- the attribute now sits on `span.next-cockpit-scope-title`, which declares `color:var(--ink)` at the same specificity and later in the sheet, so the rail override is load-bearing. It asserts the register on every colouring rule instead, and expects the override to carry it. DRC-4593's briefing-register test reads the size off a standalone `.next-cockpit-recovery strong` rule. That rule carries no size on this lineage: the narrower #361 that merged puts it on the head shared with `small`. Repointed, and the three registers still resolve distinct. The Now briefing's word cap read 101 against 100. Neither branch overran it alone: DRC-4593's authority gloss is thirteen words and DRC-4595's steer composer is twenty-six, and only the sum crosses. The composer is not briefing prose, though -- it is a sibling `<section>` DRC-4595 moved into the project chrome, and the mirror regex was anchored on the last `</section>` before the tabs nav, so it swallowed a control and charged its words to the briefing. The lookahead now stops at the briefing's own close. The cap is left at 100 deliberately: it states what the panel is for, and raising it to absorb a measurement error would retire the constraint without anyone deciding to. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * docs(design): count the sentence tier against the sheet that merged Every hand-counted figure in this section was taken against the pre-squash DRC-4587 tree, which raised thirteen rules the narrower change that merged did not. `main` said sixty-seven, forty-nine and eighteen; the branch that added the assertion said seventy, forty-seven and twenty-three. Measured on the merged sheet: sixty-two, forty-four and eighteen. Two independent counts, both confidently specific, both wrong, neither caught by review, which is why the paragraph now says the figures are written down only because `NextPageAssetContractTest` asserts them. The sub-floor inventory had the same defect in the other direction. Its four sub-group figures were right -- seven on `--fs-sm`, three on `--fs-body`, two on `--fs-summary`, seven literals -- but it omitted the `--fs-xs` group entirely, because on the tree it was written against those fourteen rules had already been raised. Thirty-three, not nineteen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * test(web): carry the three absence pairs the rewritten table dropped DRC-4592 and DRC-4597 each appended pairs to `AnAbsenceNeverOutranksTheValueItReplacesTest` when it was a flat table of CSS selector pairs. It was rewritten on the way in to resolve DOM paths through the cascade and narrowed to the absences raised to the sentence tier, which these three are not, so they no longer fit it and a textual merge would have dropped them silently. The property is true on the merged sheet -- the tab cue and both of its marks declare 11.0, and the scope title is 14.0 against an 11.5 caption -- so the guard is carried rather than discarded on the technicality that its old home changed shape. Confirmed failing: raising `.next-cockpit-tab-cue--pending` to `--fs-body` reds it at 13.0 against 11.0. An earlier mutant that inserted `font-size:14px` ahead of the existing `font:` shorthand did NOT red it, and correctly so -- the shorthand resets the size, so that mutant changed nothing a reader would see. A guard is only worth committing once something has been seen to break it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * docs(skill): describe the four surfaces this branch changed sync-docs pass for the nine UI/UX entities. Each claim was read off the code rather than off the issue, and the four that were user-visible and undocumented are now in the shipped body. Console leads with the operations rail and keeps the startup capabilities in one disclosure, with a capability that is on rendered expanded and outside it. The tab strip names its own panel and carries a derived cue on the three tabs that have one, with the loading and unpublished marks called out as distinct from a zero. The scope rail leads with the session title over a harness, state and age caption, says so when a title was never published, and carries the session key only on rows that would otherwise render identically. The timeline filter is back, with its three modes remembered per session in the browser. The steer composer's move out of Tripwires is recorded where a reader would look for it, since the control did not change but its location did. COMPATIBILITY.md's docs-synced-through marker is deliberately untouched: this is a feature branch, and parallel branches cannot vouch for one another. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * fix(web): close the oracle holes the four branches left in each other Five findings from the implementers, each measured by them and re-measured here against the consolidated tree rather than taken on report. DRC-4589 showed its own `AnAbsentVariantBorrowsItsSizeFromTheValueItReplacesTest` does not subsume the delegation tuple I dropped: that class constrains the STAMPED variant and says nothing about the base rule it sits on, so raising `.next-delegation-withheld strong` to 40px against a 32px figure left it green. Its replacement is here and reds at 40px. DRC-4592 showed the same for the rail card: with its title/meta tuple dropped, redefining `--fs-2xs` above `--fs-sm` in `:root` survives every other check in the file, because the surviving assertions pin which token each half uses and cannot see the token move underneath them. Its four-line replacement reds on that mutation. DRC-4595 found a hole in its own sentence-tier count. A length passes a swap where one rule leaves the tier and another joins at 15px, and THIS INTEGRATION IS THAT SWAP: DRC-4589 moved `.next-cockpit-authority>span` off the tier and `>small` on. The oracle would have been green on the change that defeats it. It is a set now as well as a count, because two selectors are declared twice and a set alone loses that. DRC-4591 found a regression its own commit created: `.pc-substrate-steps` sat at 15.0 beside a 12.5 sentence in the same disclosure, because #361's review lowered the neighbour after the branch forked. Lowered to match. Nothing tested DRC-4589's trap 1, that a colour tidied into the authority override block would retire the captain-needed ink step by winning on source order. It is the hunk I resolved, so it is guarded here, and the guard was confirmed to fail when the colour is added. The design doc carries three changes. DRC-4589's correction, that a register makes a repoint safe and a removal dangerous, with its example rebased onto the merged tree: the competitor it names was deleted by DRC-4592, and the live instance is the rail card's own title rule winning on source order. The sentence-tier paragraph now reports the set rather than a capped/uncapped split that no test held. And main's refusal to state the sub-floor size is restored, rescoped to the true total, because the set that IS asserted counts what a per-rule census can see rather than what the page has. Byte pins and both DRC-4596 registries regenerated after the stylesheet edit. Every mutation check excluded the byte-pin oracle, which fires on any stylesheet edit at all and would have reported all five as killed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * docs(design): say what each absence guard does, not what one of them does DRC-4589 caught the paragraph claiming more than the code. It led with "resolve both through the cascade and compare them" and then listed two classes under it, but only one resolves: `AnAbsenceNeverRendersLargerThanItsValueTest` compares the size each rule declares for itself, deliberately, and its own docstring says so in as many words. A paragraph warning that a rule count is not a rendered colour should not itself describe a test by what the test beside it does. The two are now described separately, by what each answers. The cascade one holds the absences raised to the sentence tier. The declared-size one holds the pairs that are not raised, and fails loudly when a pair stops declaring a size rather than letting it inherit quietly. The delegation pair stays in the stamp class rather than moving to the declared-size class the taxonomy would put it in. DRC-4589 argued against its own placement and then gave the better reason to keep it: the test exists because the stamp assertions do not cover that pair, and a reader who opens that class and finds only stamp assertions is one step from concluding they suffice, which is what both of us concluded before measuring. Filing it correctly would hide the gap it was written to expose. Prose only. No byte pin moves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * test(web): point the taxonomic home at the pair that is not in it `AnAbsenceNeverRendersLargerThanItsValueTest` is where DELEGATION's figure and the withheld string that replaces it belong by type: separate selectors, neither raised, both declaring literal sizes. It is not there, and the reason is only recorded in the class that does hold it, so a reader arriving by type finds an absence with no explanation. The pointer names the pair, says where it lives, and gives the reason: it sits beside the stamp assertions precisely to show they are not sufficient, because the stamp constrains only the absent variant and says nothing about the base rule it sits on. Measured at 40px against the 32px figure, where every stamp assertion stays green. Both readers are served now. The one who goes looking by type finds the pointer; the one who opens the stamp class and might conclude those assertions are enough finds the counter-example in front of them. That conclusion is not hypothetical: DRC-4589 and I both reached it before either of us measured. Docstring only. No assertion changes and no byte pin moves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * fix(web): say what is not read yet, and put each caveat on the side it claims Six review findings triaged by execution rather than by reading. Two were refuted on their premise and four were real; the sweep that replaces the weakest criterion then found a fifth instance nobody had looked for. Refuted, no change made: - projectAction's graph-mode ternary drops `decisions`, but the function has no caller and the `data-calm` attribute it serves has no dispatcher in the bundle. The live Decisions button is rewritten to `data-next-cockpit-action="graph-mode"` and passes its argument through the validating projectSetGraphMode. Executed against the rendered cockpit: decisions -> all -> decisions, heading RECORDED DECISIONS. - The unknown HOW IT LANDED card renders a stated absence, not a blank slot. nextObservedPair falls back to the reason, so endText and claimText are never empty, and the pair separates on the `--absent` selector. The `data-next-absent` stamp is the figure-slot form of that ruling, which a sentence card is not. Fixed: - The re-entry disclosure said "Why a raise is unavailable" one line under "Its terminal can be raised". The label now travels with its branch. - The Decisions tab cue reported a context entry that had not arrived as "decisions not published" while the panel beside it said "Loading semantic context". `pending` existed and was reached only at session scope. - The Console setup summary told a reader whose server was started with both capabilities on that the terminal bridge and the observer model were off, because an unread capability collapsed into false. Both are three-state now. Measured on a fixture serving an enabled model and a registered terminal: unread read false/false where settled read true/true, and the terminal section sat inside the setup disclosure on that render. - Two sentences claimed the observed record is above them. DRC-4594 moved it last, and both are false by construction rather than on one fixture. Oracles, replacing four criteria that were verified over lists: - HeldToPositionalSentencesTest sweeps every positional sentence the bundle emits, reads the direction each one states, and compares rendered indices on the correct side. It found a third instance on its first run: the two settle cues say "the question above", and nextCockpitConflict draws them inside its own header, above the question. Both are corrected. A positional word is kept and required to be true rather than deleted, because a sweep with nothing to find passes forever. - The Held to lede is now measured against every absence element the tab renders, derived by the same convention, rather than four listed strings passed through a filter that dropped its own misses. - "two axes, read separately" is checked over every .py, .js, .css and .html under cargento_runtime rather than two files. - The caveat-survival set is derived from the merge base's own reading-why paragraphs, split into sentences because the tiering splits a paragraph. The revision and the extraction are recorded, and a test re-derives the list wherever the base blob is reachable. The absence register guard matched 8 of the 14 rules that carry an absence marker: four spell it with a single hyphen and all four spelled their ink var(--ink3) directly, so repointing --ink-absence would have left them behind. The sweep derives its subject now, the four rules are repointed, and two grouped selectors were split because each shared a rule with something that is not an absence. Byte pins regenerated from the assets. Full suite green: 3636 dashboard tests and 515 script tests, with coverage at 86.8% against a floor of 73. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * fix(web): keep a refreshing terminal's answer instead of unlearning it The three-state capability read in 8b9b57a keyed off the `loading` flag rather than the state beside it, and that is wrong for the arm that matters. `projectTerminalLookup` marks a REGISTERED terminal `{state:"registered", loading:true}` while it re-checks, and it re-checks whenever the payload revision advances -- which is every poll. A flag-based read therefore reported `null` on every poll of a working console, flipped the summary to "terminal bridge not read yet" and moved the live terminal section into the setup disclosure it had just left. Measured before and after: settled `{terminal:true}`, one revision later `{terminal:null}` with the terminal inside the disclosure; now `{terminal:true}` across the refresh with the section still outside it. `state` is the tri-state on its own -- `loading` only before any answer, then `registered` or `unavailable` -- so the flag plays no part in the decision. Found by re-reading the diff rather than by a suite, which is why it now has one. `ConsoleSetupNeverCallsAnUnreadCapabilityOffTest` pins all three states, asserts the refresh really is in flight before comparing, and was confirmed to fail on three mutants: the `loading` read this replaces, the original `Boolean()` pair, and a version that reports an unavailable terminal as unknown rather than off. Byte pins regenerated. Full suite green: 3639 dashboard tests, 515 script tests, coverage 86.8%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * fix(web): let the Decisions cue and its panel resolve one read F1. The cue keyed off `entry.data`; the panel keyed off `entry.data` AND `entry.error`, and the poll's `.catch()` is the only writer of `error`. So a first fetch that FAILED left the panel saying "Semantic context unavailable." beside a cue reporting `pending`, glossed "decisions not loaded yet" -- a completed, failed read described as still in flight. The cue's own comment asserted the opposite. `nextCockpitTabCue` does not exist on origin/main; a54451d introduced it in this pull request, so this is a regression created here rather than one found in passing, and it is fixed rather than filed. Reproduced at BOTH scopes, which is one more than was reported: the focused arm had the same blind spot, and it differed a second way as well -- the panel also requires the PROJECT entry when a session is focused and the cue did not. Two conditions that must agree is the shape that produced both, so there is now one `nextCockpitContextRead` and neither caller re-derives the answer. The cue also takes its facts from that read at both scopes instead of from the `observation` argument. The two were the same object, measured `===`, so no board changes; what goes is the second read that let state and facts drift. A failed read gets its own cue state rather than borrowing one. It is not `pending`, which claims a read is still coming, and not `unobserved`, which claims nothing was published. The mark is the em dash this sheet already uses for a slot with no figure in it. The variant needed a rule of its own, and the obvious test does not prove it has one: pinning the emitted class name passes whether or not the sheet styles it, and measured, deleting the rule left the whole cockpit module green. Only the byte pins went red, and those fire on any stylesheet edit. `EveryTabCueVariantIsColouredByItsOwnRuleTest` derives the variants from the producer and resolves each through the cascade against the unstyled cue, so a state added with no rule reds instead of rendering an absence in the ink a real figure gets. Seven mutants run, all seven fail the suite: the cue ignoring `error`, the mark collapsing into `unobserved`, the panel dropping its failed arm, each of the two variant rules deleted, and a newly emitted variant with no rule. Two earlier mutants that appeared to survive were re-run after confirming the substitution had applied; both then failed. Byte pins regenerated. Full suite green: 3640 dashboard tests, 515 script tests, coverage 86.8%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * fix(web): key the timeline filter per project, and repair six blind oracles The blocker and every finding from the four verdicts, in one commit. M1. `projectSetGraphMode` keyed on the session, and `projectQuerySession` is "" at project scope for EVERY project, so one entry held every project's choice -- and this branch newly mirrors that map to `localStorage`, which turned a per-tab quirk into a persisted one. The scope is now the session when one is focused and the project otherwise, composed by ONE function the write and the read both call, because namespacing only the write passes half the criterion. `projectDisclosure` already composed that pair; this copies it rather than inventing a shape. The mirror is kept: it is a shipped capability and the criterion asks for the key by name. The fixture is the oracle here. Both of that issue's tests passed on a one-project board and one SEEDED the collided key as its expected value, so no stronger assertion at any width could have found this -- only a second project. The new test presses on one, opens the other untouched, presses there, and returns, because a fix that resolves the fallback from the wrong scope passes the first half alone. The context map. `nextCockpitContexts` has five reachable shapes, three writers, and only one writes `error`. The tab cue this branch adds keyed off `.data` alone and called a finished, failed read "decisions not loaded yet" beside a panel already saying "Semantic context unavailable." Both surfaces now resolve one classification, so they cannot describe a state differently. The fifth shape is the one neither independent enumeration had and the comparison between them found: the `.catch()` MERGES, carrying previous data into the new object, so a failure over loaded data is invisible to `.data`. It keeps its rows and claims nothing about staleness; whether the board should SAY the rows are stale is DRC-4613, not a sentence invented inside a fix commit. The comment that claimed the two "read one object under two names" -- true of the object, false of the field sets -- is replaced by what the function does. `projectTerminalLookup` preserved `registered` across a re-check and reset `unavailable` to `loading`, so a default bridge-off console flipped to "not read yet" on every poll. That is the third instance of this shape and it was inside the repair for the first. The pinning test drives it the way production does and samples on the render that STARTS the re-check: a sample taken four microtasks later passes on the broken code, which is why the first version of this test did. `.next-guardrail-copy small` rendered at 12.5px inside the compliant set, being declared at 15px in one grouped rule and 12.5px by the next at equal specificity. Taken structurally: the compliant set is resolved on ELEMENTS, and the sheet no longer declares a size it immediately overrides. The straddle census is pinned because straddling is the precondition even where it is benign, and the shipped defect was a member of it. `docs/design-next-ui.md` said a per-rule census "will understate the set". It can also overstate, by a different mechanism -- two rules at equal specificity rather than three composing -- and both are now written down, with the limit of the new guard stated rather than implied. Oracles, each with its own falsifier run: DRC-4597 AC-2's count is exercised across group sizes, since every fixture held exactly two sessions and a literal satisfied the derivation; DRC-4593 AC-4 asserts registers as well as pairs, and deliberately not three hexes, because label and caption share `--ink3` by a stated ruling; DRC-4595 AC-1 holds every part of the setup block behind the rail, not just the disclosure; DRC-4591 AC-7 runs on every tiered tab including the `pc-` surface that issue added; DRC-4591 AC-3 requires the disclosure to hide something; DRC-4594 AC-5 exercises all six branch combinations. Not closed, and not claimed: the element-resolved guard does NOT close DRC-4595's AC-5 hole. A rule reaching an element through ancestors the tier selector never names is still invisible, because the path is built from the selector rather than from the page. Widening it by hypothesising every DOM a rule could match was measured and abandoned at 8,873 false positives. All thirteen pins re-derived from the assets after the last edit. Suites green: 3652 dashboard, 515 scripts, coverage 86.8% against a floor of 73. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * fix(web): carry both halves in the mode key, and derive the citation sweep Four corrections to the previous commit, three of which make a repair stronger rather than wider. The mode key now carries the project AND the session, joined on a separator no label can contain. Keying by the session alone was the collision; keying by the project alone silently destroys the per-session distinction the resolver reads at session scope, and that mutant passed every test the previous commit added. It has one of its own now: three scopes in one project, each pressed to a different mode, each read back after visiting the others. `projectLoadGraphModes` validated values and not key shapes. A browser that has already run this branch holds every project's choice under one empty key, so a scheme where that key parsed as a real project would hand the old collision straight to the new one. Unparseable shapes are dropped once at load rather than defended against at each read site, and the separator is what makes the shape decidable. AC-4's storage test is rewritten against the two-project fixture rather than patched. It seeded the collided key and asserted the first render took it -- the defect written down as the expected result -- and on a one-project board that key reads as "project scope" rather than as "every project", so changing the seed alone would have left a one-project fixture asserting a per-project property. AC-7 is derived over every `web/*.js` instead of rendered on a list of tabs. Adding the Console surface to a fixture was the weaker repair: a second fixture still cannot fail on the surface nobody thought to add, which is the argument `js_literals` makes about itself and the reason AC-4's retired-span check walks the runtime rather than naming files. The sweep asserts its file count, and its one blind spot -- a template literal split at `${...}`, so an href composed through a hole is out of reach -- is in the docstring rather than implied. Four mutants, each with the substitution proved applied: the session-only key, the project-only key, a read that resolves from the other scope, and a load without the shape check. A `docs/` href planted in three different bundle files reds the citation sweep from each. What is NOT claimed. The cue and the panel agree across all five context states and neither calls a finished read pending; the stale-plus-error state renders its last known rows by the ruling in DRC-4613 and is separately classified so that issue can act on it. They agree through one classifier, so a future defect there is one condition wrong rather than two readers drifting -- but agreement is not coverage, and DRC-4613 is open. Still open and not closed by this commit: DRC-4595's AC-5 hole, since the element-resolved guard builds its path from the selector rather than the page; and DRC-4597's AC-6 live measurement, which needs a viewport this desk would not give. Pins re-derived from the assets after the last edit. Suites green: 3654 dashboard, 515 scripts, coverage 86.8%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> * test(web): stop a caveat rendering empty, and make the six branches be six Five items, all test and doc; no runtime file changes and no pin moves. AC-3 had two halves and checked one. That the count rows stay OUT of the disclosure was asserted; that the disclosure has anything IN it was not, so the criterion's own empty-body falsifier passed. Removing `nextCockpitWhy`'s empty-body return leaves 3652 behavioural tests green and draws exactly one `<p class="next-cockpit-reading-why"></p>` in the re-entry block on the default board -- a reachable arm emitting an empty sentence, seen by nothing. Both shapes are now rejected across the whole tab. AC-5's matrix had collapsed a second time, and in my own repair. The capability stub returned `{content:"tmux"}`; `nextFocusCapability` requires `typeof meta.getAttribute === "function"`, so it resolved to "" and all six cases drew the capability-off branch -- one raise sentence, no disclosure, one of three branches. Every per-case guard passed, because the anchor and both rows render on every branch, which is why guarding each case cannot see six copies of one. The stub is now `CockpitHeldReEntryTest.FOCUS_ON`'s, which answers `getAttribute` as the page reads it, and the six are asserted to DIFFER: three distinct raise claims, two distinct re-entry sentences, six distinct pairs, and the disclosure present on exactly the two branches that have a limit to explain. `--ink-caption` doubles onto `--ink3` and `design-next-ui.md` recorded only `--ink-label` and `--ink-absence`. The ruling cited for declining a three-distinct-hexes assertion was therefore one role short of covering it. Three of the four register names land on one hex; only `--ink-value` has an ink to itself, and that is now written where a reader can re-read it rather than rediscover it from a failing test. `_below_floor` swallowed `UnsupportedSelectorError`. It now returns the skip count and the test asserts it is zero -- 77 swept, 77 resolved, 0 skipped. Recorded honestly as a TRIPWIRE and not a live guard: putting a sibling combinator on a tier selector does not produce a skip, because `resolve` calls `matches` over every rule and lets the error out, so the sweep raises before the branch is reached. `_steps` is shared, so anything `path_for` refuses, `matches` refuses too. The branch opens only if the resolver is later changed to skip rules it cannot express, which is precisely the change that would open the hole. Mutants: removing the empty-body return reds AC-3; restoring the broken capability stub reds AC-5 with `6 != 2`; moving the anchor last reds all six branches. The skip counter has no falsifier and the docstring says why rather than implying one. Suites green: 3654 dashboard, 515 scripts, coverage 86.8%. Byte pins re-derived and unchanged, since nothing in this commit touches an asset. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@variable.team> --------- Signed-off-by: Jared Scott <jared.scott@variable.team> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Implements DRC-4587 — Raise board sentences to a 15px tier and collapse six sub-12px tokens into one label tier
Readers report the board is dim, and contrast has nowhere left to go — every ink already passes AA — so size is the only lever.
What changed
--fs-sentencetier at weight 500.--fs-label, absorbing 37 literal sizes.<header>s into sans sentences.--measureon 49 rules, excluding the 18 a cap would clamp wrongly.Evidence
test_next_page25/25,test_next_flag7/7 andtest_focus96/96 each re-run alone.Review guidance
styles.csscarries 128 of the 132 changed runtime lines. Two declared overages, both deliberate: runtime is 0.75 lines past its ±25% ceiling because six sub-10px literals were remapped rather than left behind a deleted token, and docs is 7 lines past because the gap note below is recorded rather than implied.AC-1's universal clause is not met — 17 sans rules remain at 13–14.5px. Accepted on its verifier, the recorded inventory, and carried by DRC-4602 inside this milestone. Two of those 17 are not sentences, so a sweep would be wrong.
The seven
docs/roadmap-burndown/README.mdcommits are workflow process notes learned during this issue: the acceptance-criteria scan rules, and the captain's standing directives for this milestone.drc-4587