Skip to content

feat(web): raise board sentences to a 15px tier and collapse the label tier - #361

Merged
gcko merged 15 commits into
mainfrom
spacedock-ensign/drc-4587
Sep 17, 2026
Merged

gcko merged 15 commits into
mainfrom
spacedock-ensign/drc-4587

Conversation

@gcko

@gcko gcko commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

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

  • Raise 67 sans prose rules to a new 15px --fs-sentence tier at weight 500.
  • Collapse the label tier onto one 11px --fs-label, absorbing 37 literal sizes.
  • Delete three scale tokens with no callers, and one tracking value replaces four.
  • Move both absence explanations out of their <header>s into sans sentences.
  • Cap prose at a 540px --measure on 49 rules, excluding the 18 a cap would clamp wrongly.

Evidence

  • Dashboard suite 3527/3527 passed, scripts suite 515/515 passed, coverage 86.8%.
  • Nine byte pins regenerated from the assets; test_next_page 25/25, test_next_flag 7/7 and test_focus 96/96 each re-run alone.

Review guidance

styles.css carries 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.md commits are workflow process notes learned during this issue: the acceptance-criteria scan rules, and the captain's standing directives for this milestone.


drc-4587

gcko and others added 8 commits September 17, 2026 14:50
… 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>
@github-actions

github-actions Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Coverage

Name                                                                  Stmts   Miss Branch BrPart  Cover
-------------------------------------------------------------------------------------------------------
cargento/skills/cargento/agy_hook.py                                     79     14     28      7  78.5%
cargento/skills/cargento/cargento_runtime/__init__.py                     0      0      0      0 100.0%
cargento/skills/cargento/cargento_runtime/aggregate.py                  323      3    110      3  98.6%
cargento/skills/cargento/cargento_runtime/annotations.py                373     19    130     21  92.0%
cargento/skills/cargento/cargento_runtime/asks.py                       110      0     28      0 100.0%
cargento/skills/cargento/cargento_runtime/claude_data.py                340     40    158     18  88.4%
cargento/skills/cargento/cargento_runtime/cli.py                        212     20     52      6  89.4%
cargento/skills/cargento/cargento_runtime/collectors/__init__.py          0      0      0      0 100.0%
cargento/skills/cargento/cargento_runtime/collectors/antigravity.py     423     41    174     25  87.3%
cargento/skills/cargento/cargento_runtime/collectors/claude.py          305     17    112     15  91.8%
cargento/skills/cargento/cargento_runtime/collectors/codex.py           258     18     96     15  90.7%
cargento/skills/cargento/cargento_runtime/collectors/copilot.py         153      4     52      2  97.1%
cargento/skills/cargento/cargento_runtime/collectors/cursor.py          287     19    108     16  90.6%
cargento/skills/cargento/cargento_runtime/collectors/droid.py            34      3      6      1  90.0%
cargento/skills/cargento/cargento_runtime/collectors/gemini.py           54      7     16      4  84.3%
cargento/skills/cargento/cargento_runtime/collectors/goose.py            95      6     28      4  91.9%
cargento/skills/cargento/cargento_runtime/collectors/opencode.py        124      4     44      4  95.2%
cargento/skills/cargento/cargento_runtime/collectors/pi.py              328     34    152     20  88.8%
cargento/skills/cargento/cargento_runtime/config.py                     232      1     20      1  99.2%
cargento/skills/cargento/cargento_runtime/deliveries.py                 128     12     30      7  88.0%
cargento/skills/cargento/cargento_runtime/departures.py                 131      6     30      6  92.5%
cargento/skills/cargento/cargento_runtime/diagnostics.py                 88      3     26      3  94.7%
cargento/skills/cargento/cargento_runtime/dismissals.py                 111      3     32      3  95.8%
cargento/skills/cargento/cargento_runtime/ends.py                        89      5     18      0  95.3%
cargento/skills/cargento/cargento_runtime/events.py                     230      1     86      3  98.7%
cargento/skills/cargento/cargento_runtime/focus.py                       72      2     20      2  95.7%
cargento/skills/cargento/cargento_runtime/git_status.py                  59      1     22      2  96.3%
cargento/skills/cargento/cargento_runtime/history.py                    206      9     56      7  93.9%
cargento/skills/cargento/cargento_runtime/http_api.py                   933    107    300     37  88.3%
cargento/skills/cargento/cargento_runtime/interaction_prototype.py      709    221    198     38  65.7%
cargento/skills/cargento/cargento_runtime/io.py                         163      2     38      1  98.5%
cargento/skills/cargento/cargento_runtime/irreversible.py                39      0      6      0 100.0%
cargento/skills/cargento/cargento_runtime/lifecycle.py                  412     47    140     14  87.5%
cargento/skills/cargento/cargento_runtime/notifications.py              264     12     92      7  94.7%
cargento/skills/cargento/cargento_runtime/observation.py                412      6    122      3  98.3%
cargento/skills/cargento/cargento_runtime/observer.py                   388     24    156     15  92.8%
cargento/skills/cargento/cargento_runtime/probe.py                       44      0     18      1  98.4%
cargento/skills/cargento/cargento_runtime/project_context.py           1588    179    686    121  85.3%
cargento/skills/cargento/cargento_runtime/quota.py                      392      7    144      6  97.2%
cargento/skills/cargento/cargento_runtime/reach.py                       78      6     30      4  90.7%
cargento/skills/cargento/cargento_runtime/reading.py                    365      7    122      6  97.3%
cargento/skills/cargento/cargento_runtime/records.py                    265      5    118      9  96.3%
cargento/skills/cargento/cargento_runtime/semantic_history.py           290     14    110     18  92.0%
cargento/skills/cargento/cargento_runtime/sessions.py                   189      4     72      4  96.9%
cargento/skills/cargento/cargento_runtime/snapshot.py                    36      0      4      0 100.0%
cargento/skills/cargento/cargento_runtime/spacedock.py                  510     50    260     26  89.9%
cargento/skills/cargento/cargento_runtime/state.py                       83      0      2      0 100.0%
cargento/skills/cargento/cargento_runtime/stream.py                      57      0      8      0 100.0%
cargento/skills/cargento/cargento_runtime/transcripts.py                529     29    272     25  93.3%
cargento/skills/cargento/cargento_runtime/tripwires.py                  232     14     94     13  91.7%
cargento/skills/cargento/cargento_runtime/turns.py                      209     11    104     12  92.7%
cargento/skills/cargento/cargento_runtime/unasked.py                    119      2     34      1  98.0%
cargento/skills/cargento/cargento_runtime/web/__init__.py                 0      0      0      0 100.0%
cargento/skills/cargento/cargento_runtime/web/page.py                    38      0     10      0 100.0%
cargento/skills/cargento/event_hook.py                                  213     54     92     14  68.5%
cargento/skills/cargento/mcp_server.py                                  377     22    112     14  92.2%
cargento/skills/cargento/notify_hook.py                                  49     15      6      1  67.3%
cargento/skills/cargento/server.py                                        3      0      2      1  80.0%
cargento/skills/cargento/statusline_hook.py                             131     13     46      8  87.0%
scripts/bench_collect.py                                                214     13     54      6  92.9%
scripts/bench_event_latency.py                                           67     21     14      1  67.9%
scripts/bump_version.py                                                  60     12     24      5  77.4%
scripts/capture_focus_raise.py                                          729    131    236     16  79.4%
scripts/capture_hook.py                                                 292     30     90     11  88.7%
scripts/capture_team_registry.py                                        172     33     48      6  80.5%
scripts/capture_terminal_identity.py                                    347    108    104     14  66.3%
scripts/derive_prompt_shapes.py                                         210     16     88     14  89.3%
scripts/lint_embedded.py                                                 97      3     32      3  95.3%
scripts/mark_abstention.py                                              382    142    140     23  58.4%
scripts/score_abstention.py                                             493     52    182     18  88.4%
scripts/serve_operator_cockpit.py                                       448    191    112     20  54.5%
scripts/validate_plugins.py                                             762    202    434     67  70.8%
-------------------------------------------------------------------------------------------------------
TOTAL                                                                 18234   2097   6590    798  86.6%

Threshold: fail_under in pyproject.toml · label coverage-exception to bypass (visible in PR timeline).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Comment thread docs/design-next-ui.md Outdated
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>
gcko added a commit that referenced this pull request Sep 17, 2026
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>
@gcko
gcko merged commit 84d27a5 into main Sep 17, 2026
12 checks passed
@gcko
gcko deleted the spacedock-ensign/drc-4587 branch September 17, 2026 13:05
gcko added a commit that referenced this pull request Sep 17, 2026
…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>
gcko added a commit that referenced this pull request Sep 17, 2026
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>
gcko added a commit that referenced this pull request Sep 18, 2026
…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>
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.

2 participants