Skip to content

site: two new designs (Signal, Bulletin), retire Manual, serve from sscsb.dev - #58

Merged
p4gs merged 19 commits into
mainfrom
integrate/site-redesign
Sep 13, 2026
Merged

p4gs merged 19 commits into
mainfrom
integrate/site-redesign

Conversation

@p4gs

@p4gs p4gs commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Replaces the four-up design trial with a five-up one and re-roots the site on its own domain.

What changes

Two new designs. signal — the reference-inspired register (paperclip.ing / cocoindex.io / xirp.spotify.com / openai.com-the-defense-factory): warm paper, one blue accent, Inter Tight / Inter / JetBrains Mono, a pill nav that condenses at scrollY > 24 and never leaves the screen, numbered chapter rail, hairline zebra tables. bulletin — a Swiss/brutalist poster grid: ink on warm paper, one hot accent spent only on state, hard 2px rules, oversized Anton numerals, broadsheet tables.

manual is retired — directory deleted, registry entry and DesignId member gone. Ledger stays the default; registry order is ledger, console, chain, signal, bulletin.

The site serves at the root of sscsb.dev. BASE_PATH /sscsb/ → /, SITE_ORIGIN → https://sscsb.dev, root CNAME updated, the umbrella "Sensible Security Tools" landing retired. Share/copy URLs now derive from config via a new share-urls.ts instead of being hand-built per design. The scan-intake relay's CORS allow-list gains https://sscsb.dev (the old origin stays for the transition, with a comment saying when to drop it) — note the relay has no Git integration and needs a manual vercel deploy --prod from relay/.

Two build defects this exposed

  • build.ts wrote every page under a hardcoded "sscsb" literal, so BASE_PATH and the output tree could disagree silently. Now derived.
  • The domain-root landing was written after the design loop, so at BASE_PATH="/" it would have overwritten the home page.

Verification

  • 654 tests pass, 0 fail (baseline on main was 450). tsc --noEmit clean. 30 pages build.
  • Visual validation in real Chrome at 1440 and a true 390px layout viewport, four rounds: measure → three independent judges per design → fix → rebuild → re-judge. Final panel scores both designs 4+ on aesthetics, delight and mobile seamlessness, with two 5s on mobile. ~200 defects found and fixed across the rounds.
  • Regression: ledger, console and chain render byte-identical to main across six pages each, once the intended path/domain/switcher changes are normalized out.
  • Cross-vendor-style audit of the integrated diff caught a defect the green suite missed: both new designs had hand-built share URLs on the old host, and the one test written to catch that iterated only the three old designs. Fixed, and the test widened to the whole registry (red-first).

Old tools.sensiblesecurity.xyz URLs are deliberately not redirected — that host was never shared. Its CNAME comes out of the zone after cutover so no unverified hostname is left pointing at GitHub Pages.

🤖 Generated with Claude Code

https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm

p4gs and others added 19 commits September 12, 2026 17:58
BASE_PATH becomes "/" and SITE_ORIGIN becomes https://sscsb.dev. The site
stops being one tool path-routed under a shared tools domain and becomes the
whole of its own.

The umbrella "Sensible Security Tools" landing retires with the move. It was
not optional tidying: build() wrote it to dist/index.html AFTER the design
loop, so at BASE_PATH "/" it would have overwritten the SSCSB home with a
registry page listing one tool. designs/ledger/landing.ts is deleted; nothing
else imported it.

build() also wrote every page under a literal "sscsb" directory, so BASE_PATH
and the output tree could disagree in silence — exactly what moving to the
domain root would have broken. The output directory is derived from BASE_PATH
now, and the test that asserts the tree derives it the same way.

Each design had four fully-hardcoded absolute URLs (origin, base path and all)
in the prefilled issue bodies it puts on other people's trackers, and NOTHING
tested them: every one would have kept shipping the old host after this
migration with the suite green. They are built from SITE_ORIGIN + BASE_PATH in
one new module, designs/share-urls.ts, and 16 tests assert about the ORIGIN
the text carries rather than a spelling, so a design that re-hardcodes any
host fails whatever it hardcodes. Proven red-first at all three call sites.

Two things that test taught, both kept as comments: the nudge body is
URL-encoded inside an HTML-escaped attribute, and decoding only & reads
' as a parameter separator and truncates every body at its first
apostrophe; and the two nudges are different call sites — the local-scan one
renders for a provisional listing, the authenticated-scan one only for an
EXTERNAL-lane record — so rendering one fixture covers one of them and a
regression in the other survives green.

The scan-intake relay is a functional dependency, not a doc edit: sscsb.dev
joins the CORS allowlist and the issue footer, and tools.sensiblesecurity.xyz
stays DELIBERATELY until the old host stops serving, since pages still being
served from it call this endpoint. Removing it is a later, separate step, and
a test now pins both origins so that step has to be deliberate.

designs/manual/** is untouched on purpose — it is being retired in a separate
change and still carries its original literals.

Verified: tsc clean; 466 tests pass (450 before, 0 fail); build succeeds;
dist/index.html is the SSCSB home with canonical https://sscsb.dev/;
dist/CNAME reads sscsb.dev; alternate designs at dist/_d/<id>/ with no
/sscsb/ prefix; a dist walk resolved 517 internal href/src refs with 0 broken
(checker proven able to fail by planting one). The 22 residual
"sensiblesecurity" strings in dist are 12 in the shipping trees, all of them
repo.description text inside SIGNED scan records, plus 10 under _d/manual/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
A poster grid in ink on warm off-white with exactly one hot accent, spent
only on state that matters: a failing grade, a fail segment, a named
contradiction, the active nav item, the one call to action. Hard 2px rules
instead of cards, no shadows, no radii. The numbers that matter — grade,
percent passed, percent answered, control counts — are set at poster scale
in Anton; body is Source Sans 3, data is IBM Plex Mono, each with a real
fallback stack. A strict 12-column grid that collapses to a single column.

It ships its own complete stylesheet (`css`), so it does not ride
public/style.css, and maps its palette onto the shared components' bridge
tokens: `pass` is INK, because a passing check is the page's ordinary
state, and `fail` is the accent, which is the whole reason the accent
exists. Registered LAST in DESIGNS, so Ledger stays the default.

Every honesty rule is rendered, and none of them is this design's to
reword: lane marks resolve through trust.ts (the local stamp is dashed and
unfilled — attributable, and weaker than the action lane); a listing under
the coverage floor names the unanswered checks and the one-line fix from
coverage.ts; a contradiction between evidence sources is named on the row
AND on the detail page from shared-facts.ts; unverified is hatched
everywhere and counted nowhere; A+ is exactly 100%; provisional tags stay.
The shared section renderers supply the facts; only the presentation is new.

MOBILE IS A CORRECTNESS TARGET. Below 760px the directory and the control
table restack into labelled blocks — `td::before` prints the `data-label`
each cell now carries — so the page never scrolls sideways. That works
without touching the shared filter: filter.js only ever sets `display:none`
or `""` on a row, so a shown row falls back to whatever the stylesheet
says. Every id, data-attribute and selector filter.js pins is unchanged.
Tables that cannot restack (the Scorecard comparison, the every-check
index, whose markup is shared) keep a scroller with a VISIBLE affordance:
an always-drawn scrollbar plus an edge shadow that retracts at each end.

Verified in real Chrome with lifecycle live, at 1440 and 390, on all four
pages. Four defects were found that way and fixed:

  * "no evidence" is a nowrap label in what was a fixed 5.5ch track. At
    390px it pushed 29px past that track, walked up through .prules →
    .poster-slab → main, and made the document 403px wide on a 390px
    screen — while every element's own rect measured clean, so only
    documentElement.scrollWidth showed it. The track sizes to content now.
  * A phase with nothing in scope drew "n/a" INSIDE a 10px bar box that
    clips: the box that exists to show a length was slicing its own label
    in half. It is a dashed baseline now, with no box.
  * The phone stylesheet hides the banner's <br>, and with no whitespace
    behind it the two clauses rendered as "not runis not a pass".
  * The skip link measured 40px against the 44px floor.

Two design-count canaries are bumped from four to five (layout.test.ts,
home.test.ts). They are canaries by design — the literal is what forces
someone to look at the suite when the registry grows — so the count moves
and the assertion does not soften to DESIGNS.length. 450 tests before,
489 after, 0 failing; tsc clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
The four-up trial is now three: Manual loses, Ledger stays default. Its
directory, the registry entry and the DesignId member go together; the
design-count canaries in the tests move when the two new designs land.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
…site-redesign

# Conflicts:
#	site/src/designs/registry.ts
#	site/src/designs/types.ts
A fifth entry in the design trial, appended last so the Ledger stays the
default. Signal is the "record of evidence" direction: warm paper (#FBFAF7)
and warm ink (#1A1714) with ONE brand accent, blue #2B5FD9.

The accent is blue for a structural reason, not a taste one. This site
encodes control state in colour — pass, fail, gap, unverified — so green,
amber and red are spoken for. A brand accent inside that range would teach
the eye, in the first three seconds, that colour here is decoration, and
the directory then asks that same eye to read colour as meaning. Every
status therefore renders as colour AND shape AND text: a filled disc, a
half-filled disc, a struck ring, a dashed ring, each with its word beside
it. That solves colourblind access without a separate mode.

Three mechanisms borrowed from the reference set, each measured before it
was copied rather than remembered:

  - the cocoindex header morph. Single threshold at scrollY > 24, no
    hysteresis, class toggled inside requestAnimationFrame on a passive
    listener, everything else a CSS transition over 0.42s
    cubic-bezier(.2,.8,.2,1). At rest a full-bleed bar owns the glass and
    the hairline; past the threshold the bar goes transparent and the inner
    element becomes a pill that takes the blur, border and long-throw
    shadow with it. Measured at 1440: max-width 100% -> min(1100px, 100% -
    24px), radius 0 -> 999px, translateY(0) -> translateY(10px), padding
    48px -> 18px. Identical behaviour at 390, where the pill lands at
    366px. The header never dissolves and never becomes a hamburger — on a
    page with a 200-row table, chrome that hides is chrome that hides
    search exactly when it is needed.
  - Paperclip's fill-less hairline grid, hardened into a real table: zebra
    striping (at fifty rows hairlines alone stop the eye tracking),
    tabular numerals, 52px rows, hover a tint and nothing else.
  - OpenAI's numbered sticky chapter pills, scrolling sideways on a phone
    with a mask at the edge so the cut reads as "more this way".

Deliberately NOT copied: decorative colour, a 16k-pixel scrollytelling
hero, and marketing chrome. The hero has no "Get started" — the directory
search box and a live stat row are the whole of it.

Mobile is a first-class target, and the 44px floor is enforced rather than
inherited: three of the four reference sites fail it.

Verified in Chrome at true 390px layout-viewport reflow
(Emulation.setDeviceMetricsOverride), all four page kinds, both widths: no
horizontal page overflow, no clipped text, all three faces loaded, the
switcher visible with its clearance measured, the filter driven for real
(3 rows -> 1 -> 0 -> 3).

Six defects that only the browser found, and their fixes:

  1. overflow:hidden on the table wrapper silently killed the sticky
     header. The wrapper became the sticky containing block, so the header
     stuck to a box that never scrolls — at scrollY 900 its top read -382.
     overflow:clip trims the corners without creating a scroll container.
  2. the chapter rail's scroll-spy bound nothing: it ran inline, above the
     sections it points at, so every getElementById returned null. Wired
     up on DOMContentLoaded instead.
  3. the same spy ranked sections by intersectionRatio, which is
     intersected-area over ELEMENT-area — a tall section filling the screen
     scores lower than a short one peeking in. Replaced with a reading line.
  4. that line sat 6px above where a clicked anchor lands (sections carry
     scroll-margin 136px), so the rail marked the previous chapter.
  5. the wordmark and the chapter pills were under the 44px tap floor
     (72x27 and 20x20 for the wordmark; 40px pills). The shared block's
     nav-a rule cannot reach the wordmark — it is a sibling of the nav.
  6. the hero's last-scan date wrapped mid-token ("2026-09-" / "03"), which
     reads as a rendering fault. The stat row now spans the hero.

The two test literals that pin the design roster go 4 -> 5. They are
literals on purpose: comparing them to DESIGNS.length would be
tautological, since both lists are derived from the registry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
…te-redesign

# Conflicts:
#	site/src/designs/registry.ts
#	site/src/designs/types.ts
#	site/test/home.test.ts
#	site/test/layout.test.ts
The two new designs were written before share-urls.ts existed and hand-built
their off-site URLs, so the domain move to sscsb.dev left them shipping the old
host into other people's issue trackers and footering it on every page. Several
smaller correctness defects rode along with them, and three of the fixes turned
out to belong in one shared source rather than in the new designs alone.

Host honesty
- signal/ and bulletin/ directory nudges now build their URLs from
  ../share-urls (listingShareUrl, METHODOLOGY_SHARE_URL, shareUrl,
  LOCAL_METHODOLOGY_SHARE_URL), exactly as ledger does. Decoding the rendered
  nudge bodies for all five designs now yields https://sscsb.dev as the only
  site origin.
- share-urls.test.ts drives the nudge assertions off DESIGNS instead of a
  hand-kept list of three; the stale "widen once manual is gone" comment is
  gone. Widened against the unfixed sources first, which failed 4 assertions
  across signal and bulletin.
- config.ts gains SITE_HOST_LABEL, derived from SITE_ORIGIN + BASE_PATH, and
  all five footers use it — the three that already said sscsb.dev included, so
  the next move is one line. A new test asserts no rendered page of any
  registered design names the old host in the site's own voice, scoped by
  stripping record descriptions (untrusted repo prose that legitimately names
  it — the live records do), with a control proving the scoping is load-bearing.

One source instead of five copies
- trust.ts gains LANE_TITLE. Signal and chain described the local lane as "a
  shorter chain than the authenticated lane"; the contract word, in trust.ts's
  own header and the methodology, is WEAKER. Every design's lane tooltip now
  reads from it; visible labels stay each design's voice.
- scoring.ts gains PHASE_NAMES beside PHASES. Ledger, console and chain had
  five-entry maps and rendered a bare "Phase 6"; signal called it "Other
  checks" where checks.ts and reclassify.ts both say distribution & publishing.
- reclassify.ts gains CONTROL_COUNT. Four home pages hardcoded 54, and signal's
  "one verdict each" denied the third state; they now derive the count and say
  "each answered or left unanswered".

Fragments
- bulletin/methodology.ts had no id="trust" while bulletin's directory links
  methodology/#trust through the shared directoryTermsNote. A new test walks
  every internal #fragment on every page of every registered design and checks
  it lands on a real id — it found the same dead anchor in chain and console,
  both fixed here.

Smaller correctness
- signal listing rows use gradeBadge(r.score), so the provisional tag rides
  with the grade as it does on the detail page.
- signal's partly-fixable coverage note said "The rest fail elsewhere", turning
  UNVERIFIED into fail; it now names the rest as unanswered controls a
  repository scan can see.
- signal commits to one light theme, so it now declares color-scheme: light —
  without it a dark UA paints the directory's native search and select controls
  dark on a page painted light throughout.
- signal's --ink-3 was 4.18:1 on --paper-2 and 3.89:1 on --paper-3, under AA
  for the 11px lane chips and for all 38 places the token sets text; darkened
  to #6A635A, which clears 4.5:1 on every surface while staying a clear step
  below --ink-2 so external and unsigned still read weaker than verified.
- data-api / data-fallback are escapeHtml'd in all four designs that were
  interpolating them raw.
- bulletin's masthead said "Edition 0.3.0" with no source behind it; it now
  shows the methodology and schema versions from config.
- bulletin's lead slab showed a provisional grade at poster size with no
  incomplete note; exemplars.ts now exports incompleteNoteFor as the one
  source for that sentence and the slab uses it.
- relay corsHeadersFor returns Vary: Origin unconditionally. Sending it only on
  the allowed branch is the wrong way round: the cacheable-and-replayable case
  is a REFUSED response being served back to an allowed origin.
- public/style.css drops the dead .landing-*, .registry-card and .reg-* block
  left by the deleted domain-root landing — verified unused against all 549
  class tokens in the built site.
- filter.test.ts's "the shipped value by default" comment was wrong (the
  shipped prefix is /directory/); reworded to say why the stub keeps the old
  shape.

site: 553 pass / 1 skip / 0 fail (was 504), tsc clean, build green.
relay: 60 pass / 0 fail.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
Round 1 scored 2-3 against a bar of >=4 on every judge for aesthetics,
delight and seamlessness at 1440 AND 390. Every blocking and major item is
fixed or refused with the measurement that refuses it; the minors that were
cheap and did not fight the register are fixed too. Nothing in "Strengths
noted" was touched: the pill morph, the numbered rail, the four-shape status
vocabulary, the one-accent palette and the zebra table are all as they were.

THE BLOCKING ONE. The directory printed every merge finding in full inside
the Repository cell, so three listings sharing one ~130-word self-report
paragraph made 318/338/337px rows at 1440 and ~700px cards at 390 — the
product's hero surface had stopped being a table. The findings still ride the
row; all but a contradiction ride it folded, counted by their own summary
("2 notes on this record"). A CONTRADICTION is never folded, because types.ts
requires the disagreement to be named on the listing and the detail page, and
a new test pins that split rather than trusting this paragraph. The
description and the disclosure share one line, and the summary buys its 44px
hit area with padding it returns as margin. Measured: 83/96/96 at 1440,
292/331/330 at 390.

WHAT ELSE MOVED, each measured in real Chrome at a true 390px layout viewport
through device-metrics override:
- The 44px floor had no inline half (--tap was applied via min-block-size
  only), so the GitHub icon was 41x44 at 1440 and 35x44 at 390 on all four
  pages. Now 44x44 at rest and inside the floating pill. Row links, the Order
  label, the nine exposure-group links and 18 incident links join it: the only
  sub-44 control left on any page is the 20px checkbox the shared block sets,
  whose label supplies a 166x44 target.
- Red stopped meaning "caveat". Every merge note wore the fail hue on rows
  carrying data-contradictions="0", teaching the eye red before it met a real
  FAIL glyph. Contradictions keep it; notes are hairline; the merge-findings
  card is accent, not fail.
- 42 "EVIDENCE" labels per repo page had no affordance — `summary
  {display:inline-flex}` suppresses the native marker. They carry a drawn
  caret that rotates on open and their own line count ("evidence · 3").
- The control grid kept its desktop `auto` chip column at 390, so eleven words
  of reason wrapped in a 190px column with a void beside it. The chip takes
  its own line; the reason went 190px -> 272px.
- Three methodology tables clipped at 390 with no scroll affordance, hiding
  the 4-column comparison's entire payload. They stack as labelled cards the
  way .directory already does; scrollOver is 0 on all five.
- The design switcher was a 370x102 two-row slab at 390 and covered the A2
  row's status text and the table's SOURCE/SCANNED columns at 1440. It
  collapses to 85x56 on a fine pointer and expands on hover or focus; on a
  coarse pointer every link stays reachable on ONE row. Covers nothing at 1440
  on any page.
- Command blocks wrap at <=560 instead of looking truncated; the two empty
  home sections became two compact cards with every word intact; the honesty
  chapter stops being the one carded chapter among six; code chips survive the
  zebra fill; cards are capped at 920px instead of spanning 1264 with prose
  stopping at 700; nine identical disclaimers became one; the glossary
  footnote moved up under the marks it defines; a family with no controls in
  scope renders no card; search returned to the persistent pill.
- Found while measuring, not on the list: at 320px the header pill pushed the
  document sideways (scrollWidth 325). Fixed.

REFUSED, with the reason: inline-in-paragraph links stay at their text height
(WCAG 2.5.8 exempts them, and 44px inline targets would overlap each other and
wreck the prose); "Action" stays out of the phone pill because .sg-nav
measures scrollWidth - clientWidth = 16 at 390 once the GitHub link is at its
44px floor.

tsc clean; 559 pass / 1 skip / 0 fail (baseline 553, +6 new); build green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
Every blocking/major item from the round-1 audit, plus the cheap minors.
Measured at 1440 and at a TRUE 390px layout viewport (device-metrics
override), not at a scaled desktop render.

The switcher (majors 1, 2, 10, 35; minor 29). It wrapped to 370x98 at 390px
and stood over the grade slab and the first listing's score line at every
scroll position, and at 1440 its 407x54 bar overlapped the figure band's
fourth numeral at rest. Now: one full-bleed 44px row docked to the bottom
edge on a phone (five 78px links, label dropped, safe-area inset), and at
desk width a 174px "DESIGN | BULLETIN" chip that opens on hover or keyboard
focus. The four hidden links collapse to zero AREA rather than to display:none,
so they keep their seat in the tab order — measured: focusing the current link
expands the strip to 445px with all five links at 44px. Zero covered text at
scrollY 0 at 1440.

The directory stopped decorating (majors 4, 5, 6). The ~95-word "Two scores
are on this page" explainer was printed verbatim under all three listings
alongside the commit-mismatch note, and the hot rule fired on every row of a
page with no failures. A contradiction still prints in full on the hot rule —
that is a published rule, not a layout choice — and the other three merge
findings fold behind one mono line carrying the facts that actually differ
(local 3cb1290 != scan c8a2349 · self-reported A+ 100% vs DIRECTORY A+ 100%).
The sentences inside the fold are shared-facts.ts verbatim, not a paraphrase.
Rows 340px -> 169-241px at 1440; cards 812-884px -> 565-637px at 390.

The control sheet is a results table again (majors 19, 20, 21, 22; minors 45,
50, 51). The Phase column spent ~60px on one digit in all 54 rows; it is now a
ruled band per phase that doubles as an anchor. Rows share a line with their
verdict, the DETAIL label is gone, evidence is an inline "EVIDENCE (n)" with a
real disclosure marker (the shared layer's summary{display:inline-flex} had
been eating it), and a sticky JUMP TO PHASE strip carries a CSS-only "hide the
13 out-of-scope checks" fold — verified live: 13 rows hidden on check, 13 back
on uncheck. The UNVERIFIED chip paints the hatch as a 16px swatch instead of
as the background behind its own letters, and the repo sheet now carries the
directory's key. Page 20,264px -> 11,817px at 390.

The methodology's tables restack (majors 13, 14, 16; minor 36). Four tables
560-747px wide in a 354px box were hiding the sscsb-mapping column — the
reason that table exists — and cutting questions mid-word. All five are 354px
now, 0 clipped cells. Their markup is shared, so the labels come from column
position and the two shared tables are told apart by data-coverage. The
section index moved out of the page head so it can stick: one scrolling 48px
row with the edge shadow, on a 32,000px page that had no navigation at all.

The accent went back to meaning something (majors 15, 8; minors 31, 34, 37,
40). 155 control ids on the methodology were hot red — 0 now. The figure
band's A+ is ink, matching the design's own grade scale. --rust was #8A4418,
hue 24 beside the accent's 8, which read as a second colour; it is #8F200C.
The poster's one sentence lost its hard <br> for a 13ch measure plus
text-wrap:balance: "A CHECK THAT / COULD NOT RUN / IS NOT A PASS." at both
widths, no orphan.

Also: the two waiting panels sit side by side at >=1000px instead of running
1,400px of blank paper down the middle (major 9); commands wrap instead of
clipping at every width (majors 24, 41, 49, 53); every prose link carries a
44px hit rectangle via inline padding-block, which moves no type (majors 3, 7,
11, 12, 17, 18; minors 26, 30, 39) — 0 sub-44 targets on all four pages at
both widths, against 22 and 15 in round 1; Anton's 400 face is asked for
explicitly so the repo title stops being synthetically emboldened and the
case-sensitive slug stops being uppercased (minors 42, 48); a record scored
under an older methodology says so on the row (minor 52); +local n is defined
in the key (minor 27); the out-of-scope chip no longer breaks over two lines
(minor 46); the phase-label column fits all six names on one line (minor 44);
anchors clear the sticky strips; and the colophon has a way back to the top.

filter.js is untouched and still works against this markup: search filters
3 -> 1, clearing restores 3, sort by name reorders, the coverage checkbox
hides all three. 553 tests pass, tsc clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
Round 2 scored 4/4/3, 4/4/4 and 4/3/4 across three judges. Every change here
answers one of the 45 defects they and the measurement pass recorded, verified
in real Chrome at 1440 and at a TRUE 390px layout viewport (device-metrics
override plus touch emulation, so the coarse-pointer branch is the one that
actually runs), not in the source.

The nine majors:

- The sticky chapter rail was transparent to body text on both long pages at
  both widths. Root cause measured, not guessed: `mask-image` on `nav.sg-rail`
  (the 28px edge fade) makes that element a BACKDROP ROOT, so the plate's
  `backdrop-filter` sampled an empty backdrop and painted nothing — leaving 8%
  transparency as the only thing between 16px prose and the rail. The fade is
  now a `::after` on a non-scrolling wrapper (it cannot live on the scroller or
  it scrolls away with the track), the track is its own element, and the plate
  is opaque `--surface`, which is what the brief specified for it. Measured
  after: 0 of 157 sample points inside the rail reach the page behind it.
- The floating header pill carried the same 92% plate. It is opaque now; the
  resting bar keeps its glass, so the morph still reads glass-bar to solid-pill.
  This trades a noted round-1 strength for legibility, deliberately.
- The pill dropped Search AND Action on a phone: five items at 1440, three at
  390, and no route to the directory's search from 14,000px down a repo page.
  Search is persistent at every width now, icon-only below 560px at its 44px
  floor with its aria-label intact. Action stays hidden below 560 — it is the
  one item the brief does not call persistent and the row does not fit five.
  Below 380px the nav becomes its own scroller rather than dropping an item or
  shrinking a target: measured, the head had overrun its pill by 32px and
  pushed the document to 339px.
- The design switcher kept all five links expanded on a coarse pointer, and at
  390 that 314px bar sat over body text at scrollY 0 on all four pages — 93% of
  the home hero's caption. It collapses on a phone too now (84x56, coverage
  17.5%), and a tap opens it: CSS alone cannot, because the only focusable
  thing in the collapsed state is the current design's own link, so layout.ts
  intercepts that first tap. Every link stays reachable and in the tab order.
- `.cmp-none` was written unscoped, so it also hit five BARE `<span>—</span>`
  markers with no border-width reset: initial values gave them 3px dashed
  `currentColor` with no radius — a tofu box that also borrowed this design's
  dashed "nobody could answer" shape for a cell carrying no status. Scoped to
  the pill, and the dash itself replaced with the words "No sscsb equivalent",
  because this site's rule is colour AND shape AND text.
- Every heading tier below the hero inherited the body's 1.6 leading and h2
  never scaled down, so at 390 the ladder read 40/32/28/22 — a 1.14 ratio
  between a page title and its section headings against 1.71 at 1440. Now
  1.06/1.12/1.20 with a phone-width mid tier: 40/32/22/17.
- The search box promised "type any owner/repo to search the record" and
  answered a non-match with a void plus an orphan 43px header row. It says so
  now, echoes the query, and hides the table shell. `public/filter.js` is
  shared, so this reads back the row visibility that filter.js sets rather than
  re-deriving any matching rule; the proper fix is filed as a proposal.

And the minors worth taking: the nine incident disclosures open with a count
(the section's intro promises those primary sources and no reader met them);
one 200-character reason stated once per family instead of six times across a
42-row grid; the glossary as a real definition list built from `glossary.ts`;
zero-count verdict glyphs dropped to muted ink so an all-passing directory
stops spending red and amber on nothing; numeric columns right-aligned on
tabular numerals; card meters bound to their own figures; the listing card
tappable with a chevron; stacked method tables as hairline rows instead of 54
bordered cards (the check table went 11,768px to 6,053px at 390); code blocks
sized to their contents with a scroll shadow that only paints when there is
something to scroll to; and inline identifiers that no longer break mid-token
("...github" / ".com" was a mis-keyable principal in a command a compliance
reader is meant to copy).

Refused, with reasons: every remaining sub-44px target is an inline-in-prose
link (WCAG 2.5.8's inline exception) — 0 non-inline controls are under 44px on
any page at either width; "Action" at 390 stays hidden; and the directory's
52px row is not reachable while the coverage floor's one-line fix rides on the
row, which types.ts requires.

tsc clean, 576 pass / 1 skip / 0 fail (559 before, 17 new), build green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
Every major, plus the minors that were not pure taste. Measured at 1440 and
at a TRUE 390px layout viewport (device-metrics override on a lifecycle-live
instance), not at a scaled desktop render.

THE SWITCHER IS NOT FIXED ANY MORE (majors 1, 2, 7; minors 24, 26). Two rounds
of shrinking a fixed overlay did not stop it painting over body text, because
nothing can: round 1 was two rows standing on the grade slab, round 2 a 174x48
chip that still cut nine text runs out of the Scorecard comparison between
scrollY 8100 and 9900, clipped the last glyph of ALL ANSWERED CHECKS PASSED on
the repo sheet, and at 390 covered "100% passed · coverage 90.9%" outright at
rest. --switcher-clearance reserves room at the END of a document and can do
nothing for the MIDDLE of one. It is set in the colophon now, in the flow,
where a broadsheet prints its apparatus — the label comes back, the box lines
up with the colophon rule above it, and body{padding-block-end} goes to 0
because there is no overlay to clear. Measured after: a 300px-step scroll sweep
over all four pages at both widths reports ZERO fixed overlays and zero
occluded text runs, against 10 events over 9 texts on the methodology page
alone in round 2. The cost is honest: switching design on a 31,000px page now
means reaching the colophon, and the navigation for that (sticky section index,
jump-to-phase strip, the colophon's own back-to-top) is navigation built for
the job rather than review chrome parked over the copy.

THE JUMP STRIP WRAPS INSTEAD OF SCROLLING (majors 13, 25, 34; minor 8). It held
558px in a 358px box, so the one control that changes what the table shows —
worth 13 rows and 1,646px of page — sat 200px off-screen reading "Hide th",
riding the top of an 11,817px page behind a 14px fade. Two rows, 92px of sticky,
label fully visible: measured scrollWidth 358 == clientWidth 358, ctl-hide
right edge 374 inside a 390 viewport. The phase anchors are re-offset to clear
two rows (#phase-3 lands at 96 against a bar bottom of 92).

THE CONTROL SHEET STOPPED REPEATING ITSELF (majors 15, 36; minor 27). Four
rationales covered 25 of 44 rows, two of them three lines at desk width and
five on a phone. Each prints IN FULL, once, as a numbered note under the KEY;
every row it covers keeps its own leading clause, a link, and the whole sentence
in `title`. The rule is structural, not a match on wording — repeated, over 60
characters, and carrying a clause break — so a contradiction, which names the
commits that disagree and is therefore unique to its row, can never be folded
by it. 14 rows now reference 3 notes.

THE ACCENT WENT BACK TO MEANING SOMETHING (major 14, 35). All 27 raw-verdict
chips wore the accent family against 44 rows, including 11 rows already greyed
out as out-of-scope and 6 where raw and final both say pass — louder than the
ink-filled PASS chip beside them. 9 now, only where a verdict actually changed
inside the scored scope. And fail stopped being hue alone: it takes a
counter-diagonal hatch, so a phase bar reads pass = solid, fail = "/",
unverified = "\" in greyscale, in print, and to a protanope — the third state
earned a pattern for exactly this reason and the second one had not.

Also: the nine attack boxes go 2-up at desk width instead of ten ruled panels
each filling half its own measure (major 16); the closing sentence repeated
verbatim under all nine attack groups shows once, as the footnote it is
(minors 3, 9, 17, 28, 37 — the sentence lives in threats-shared.ts, so this is
CSS here and a proposal there); the italic axis is actually requested, so
italics are a loaded face rather than a slanted upright — measured, the two had
an identical 739.516px advance (minor 4); the glossary sets in broadsheet
columns and fills the measure (minor 44); the repo meta rule draws its
separators on the TAIL of each item, so a wrap can no longer open a line with an
orphaned middot (minors 6, 22); phase 6 is advertised AND present — the band,
the jump strip and the hero now agree, and a phase with nothing in scope says
"no checks in scope" rather than "no evidence", which are different facts
(minor 38); the home board prints a key, in plain words, under the bars it
explains (minor 39); the Scorecard table's four columns are allocated rather
than left to auto (minor 20); the formula slab takes 72ch instead of the full
measure (minor 21); the phase block fills its own grid (minor 19); the evidence
disclosure has a fixed home, so all 42 start on the same edge instead of
trailing prose at a different x on every row (minor 30); the section index
says less so more of it fits — 1,394px of links in a 358px strip became 970
(minor 29); the re-verify command sets at 11px on a phone so the 38-character
signing principal fits whole rather than breaking mid-character (minor 43);
the already-listed chips fill their row at both widths with the eyebrow on its
own line (minor 23); Anton is asked for at the one weight it loads (minor 10).

Measured after, all four pages at both widths: 0 fixed overlays, 0 occluded
text runs over a full scroll sweep, documentElement.scrollWidth == viewport
everywhere, 0 sub-44 tap targets (the bare 20x20 coverage checkbox inside its
250x44 label excepted, as in round 1). Repo sheet 8,549 -> 8,106px at 1440.
559 tests pass, tsc clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
Nine majors and fourteen minors, every one re-measured at 1440 and at a true
390px device-metrics viewport rather than read back out of the source.

THE TWO COLOURS THE REGISTER FORBIDS, BOTH PAINTED BY THE BROWSER. The search
field kept appearance:auto, so Chrome drew its own cancel glyph at #40649F —
a system blue — inside the home hero a hundred pixels above the one hot CTA,
on the two most-visited pages. The directory's coverage checkbox likewise drew
the native control: the only rounded corner, the only pure-white fill and the
only 1px border on a page whose stated rule is hard 2px rules, and system blue
once checked, while the repo sheet's identical control drew a hard ink square.
Both are now this design's own ink; measured, no element on any of the four
pages at either width reports a border-radius or a box-shadow.

A CONTRACT A READER COULD NOT PARSE. Round 1 stopped the local-lane contract
block clipping by wrapping it, which traded a visible defect for a silent one:
at 354px every continuation landed flush at the KEY column, so `record-fields
schema_version methodology_version repo scanned_at scanner` wrapped to a line
reading `request_issue controls score` — a key and a value, two lines under the
real key `methodology-version 2`. A hanging indent cannot fix it (text-indent
applies to a block's first line; a forced break in a <pre> does not restart it,
and each-line is unsupported), so the twelve lines are parsed out of
CONTRACT_TEXT — the same bytes both trees pin a digest over — and set as rows:
an ink slab at desk width, key above value below 760px. The test pins the
substitution from both ends, because a reformat upstream would silently put the
old block back.

NAVIGATION AT THE WIDTH THAT NEVER GOT IT. The methodology's section index was
sticky on a phone and static at every desk width — on an 18,900px document the
eight pills appeared once and were then eighteen thousand pixels away, at the
width where a reader is most likely to be scanning rather than reading. It is
one sticky row at every width now, short labels painted and long titles kept as
the accessible name, and it carries a scroll-spy: the current section takes an
INK FILL (not the accent — it survives greyscale and does not spend the one hot
colour on a position) and the strip scrolls itself to keep it in view. Measured
across six sections at both widths, the active link tracks and stays visible,
and every anchor jump lands clear of the bar (68px against a 48px bar).

The repo sheet's jump strip hid its label below 760px, leaving a bare row
reading P1 P2 P3 P4 P5 P6 above a table — six tokens a thumb reads as column
headings, explained only by a title attribute touch never fires — and painted
its underline transparent until a :hover a phone cannot send. The label is back
in its short form, the pills carry a resting hairline and a real accessible
name, and both long pages got a way back in the strip that was already sticky
rather than a second bar of permanent chrome.

FILTERING TO NOTHING NOW SAYS SO. Ticking the coverage box took the listings
from three to zero and left the page blank with a 13px count as its only
feedback. A dashed strip and a reset now appear at zero rows, driven entirely
off the contract public/filter.js documents — it observes the rows and fires
the events that file already handles, never reaching into it.

THE DEAD COLUMN, AND THE ACCENT. Section 07 ran ~6,000px of prose with nothing
beside it and subheads set in the body face. The subheads take the page's own
anatomy (Anton 400 over a hard rule) and each prose section carries a sticky
running head — ordinal and title, aria-hidden apparatus — so the band spans the
page and the margin is composed rather than empty; the wide-table section
deliberately has none, because there the column IS the table. The honesty panel
gives up its hot left rule for ink: that token means a contradiction on the repo
sheet, and one token may not mean two things one page apart.

Also: the last synthetic-bold Anton heading pinned to 400 (3,639 pixels differ
from the real face; forcing 900 is byte-identical to the shipped 700); nine
attack-group boxes in three columns so the ninth stops orphaning; the evidence
class column widened in rem, not ch — a ch on an 11px mono th made the first
attempt a measured no-op; EVIDENCE(n) brought to the row's first baseline
(-2px, was ~15px); phase bands and phase-bar labels on one line at 390; the
glossary from four columns to two with its terms ruled; the chips single-column
and the two waiting states welded into one dashed box under one heading (530px
to 348px); P1-P6 given a legend on the only surface that showed them without
one; and two screen-reader nits in the KEY.

Measured after: 0 sub-44px interactive targets on all four pages at both widths
(94 interactive elements on the repo sheet alone), documentElement.scrollWidth
equals the viewport at 1440, 390 and 360 on every page, and the only horizontal
scroller left is the section index, which says so with an edge fade and now
scrolls itself.

tsc clean; 597 pass / 1 skip / 0 fail (baseline 576); build green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
Round 3 scored 4/4/4 across three judges and left 36 findings. Everything
here answers one of them, measured in real Chrome at 1440 and at a TRUE
390px layout viewport (device-metrics override, lifecycle asserted live).

The changes that move the score:

- THE STICKY CHROME IS ONE OBJECT. Floating, the header bar goes
  transparent and only its pill paints, so a 13px slit opened between the
  pill and the chapter rail and live body text scrolled through it at every
  scroll position on the longest page. A fixed, full-bleed paper ground now
  sits behind the whole stack, painted only while the rail is stuck.
  Measured: 11 text hits in that band, now 0, at both widths.

- ONE CHROME AXIS. The rail took whatever the page's content column gave it
  (904px on methodology, 1264px on the repo page) against a 1100px pill —
  three left edges for one chrome system. Both pills share the 1100 axis;
  measured pill x=169.7 w=1100.7 against rail x=170 w=1100.

- THE PROSE FLOOR ON A PHONE. Two of three prose classes got SMALLER at 390
  than at 1440 because the shared clamps bottom out below their own desktop
  value. The per-control honesty copy is 15px, exposure prose 16px, detail
  lines 14px; the 13px JetBrains Mono evidence lines are deliberately
  untouched, because that is the register for machine truth.

- THE CONTROL ROW SPENDS ITS WIDTH. The identifier and its verdict share one
  line at every width: at 1440 the reason moves into the ~950px that sat
  empty between them (44 rows, 4890px -> 3762px), and at 390 the chip comes
  off its own row and drops under the identifier only when it does not fit.

- THE FAMILY CHIP CARRIES ITS DENOMINATOR. It read a bare "100%" for a
  family that had left a check unanswered — the one element a skimmer reads,
  collapsing the two numbers this site spends paragraphs keeping apart. It
  now reads "8/8 passed" with the unanswered count inside the same pill.

- THE KEY NAMES EVERY STATE. The legend named four states while the control
  list rendered a fifth (info, 26 marks on one listing). Five entries now,
  and the info glyph is a solid ring with a dot against unverified's dashed
  ring, so ring STYLE carries the distinction rather than fine ray detail.

- ONE NAV SET AT EVERY WIDTH. "Action" was hidden below 560px, which three
  judges read as two products. It leaves the pill at every width and moves
  to the footer, so the four the brief calls persistent are the four that
  render, everywhere.

- TWO EMPTY STATES ARE A LEDGER, AND THEY COME AFTER THE DATA. Ordering by
  emptiness is the honest layout, and it reverts by itself the day a panel
  has data because the selector asks whether it is waiting.

Also: both meters on one 72px track (a 100% bar was drawing SHORTER than a
90.9% one, because the track length came from the column header's character
count); the methodology's five tables break out of the reading column and
gain 196px; the local-lane contract block no longer clips its last token;
a two-sided, scroll-aware rail fade that never washes the active pill; the
nine identical attack-group disclaimers removed from the MARKUP and stated
once above the list; an unanswered row links to the remedy section this
listing actually renders; the repo meta is a fact list on a phone.

FOUND WHILE MEASURING, on no judge's list because every round is measured at
1440 and 390: between the 768px card breakpoint and ~1000px the directory
table was CLIPPING its last two columns unreachably (185px on the round-3
build). In that band only, the wrap scrolls with the same right-edge shadow
the method tables use and the thead stands down from sticky.

Verified in a real browser, not in the source: 0 document overflow at 320,
360, 390, 414, 768, 1024, 1200 and 1440 on all four pages; 0 non-inline
sub-44px targets on all four pages at both widths; the whole listing card is
the tap target at 390 (hit-tested at three points).

tsc clean, 593 tests / 0 fail, build green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
All six re-measured in real Chrome at 1440 and at a true 390px layout
viewport (VerifyViewport device-metrics override), on all four pages plus
all three repo listings. Evidence in MEMORY/WORK/sscsb-site-redesign/
signal/final/.

D1 The trial switcher covered body text at 4-7 of 11 evenly-spaced scroll
   positions per page at 390 (worst: 100% of the word PASS in a status chip
   on the repo page at scrollY 3817) and 1 of 11 on the directory at 1440.
   A fixed control cannot be proven clear of text at every scroll position,
   so Signal stops floating it: it is in normal flow under the footer, where
   the page reserves real space for it. Now 0 of 11 on every page at both
   widths. --switcher-clearance is zeroed because an in-flow strip occupies
   the space that padding was approximating; the 44px tap floor is untouched.
   layout.ts's tap-to-open affordance stands itself down by asking the live
   element for its own position.

D2 span.tx-reported rendered as a 637px amber bar holding 62px of text with
   prose flowing under it: .tx-incident is a grid, a grid item blockifies, so
   the shared inline-block computed to block. It also spent --degraded, the
   "looked for, not found" hue, on an editorial provenance tag. Now hugs its
   word (82.4px box / 62.4px text), paper fill + hairline + muted italic, and
   sits on the incident date's line.

D3 "resolved by a signed local scan" was rendered 7x on p4gs/sscsb-action in
   two competing treatments: hoisted once, 4 compact chips, and 2 verbatim
   223-character paragraphs. Repeats were counted per FAMILY, so one sentence
   could clear the threshold in one phase and miss it in another. The scope is
   the page now: one note at the top of the grid, every row a chip. Measured:
   the full sentence appears exactly once in the document (was 3), renderings
   are {family-note: 1, ctl-src: 6}. The truncated chip names its own note
   rather than reading "see the note above", which named neither of the two.

D4 The status chip sat at three different y-offsets across 44 control rows at
   390 (level, 28px below, 52px below) because it was the last item of a
   wrapping flex line. It is a pinned grid cell at <=767px. Measured: one
   distinct dy (0) and one distinct x over all 44 rows, on all three listings.

D5 .meter-score .meter-fill painted the PASSED bar in #2B5FD9 - the one brand
   accent, two columns from the green pass glyph, beside a neutral coverage
   meter. The accent is blue precisely so it can never read as a verdict. The
   score bar is var(--pass) now and coverage stays neutral ink. Every other
   accent use is audited in a comment beside the rule: all are interactive,
   brand/identifier, or provenance/scope - none encodes control state.

D6 overflow-wrap: anywhere sheared 22 identifier chips mid-token on the
   methodology page at 1440 with the chip background split across lines.
   :not(pre) > code goes nowrap at >=768px (the :not(pre) scope is
   load-bearing: white-space is inherited and pre.code's text lives in its own
   <code>). Measured 0 broken at 1440 on every page. The comma half: chip runs
   were joined with a literal ", " that landed outside the chip's own edge -
   148 on methodology, 14 on the repo page. Signal's own renderers use a
   separator-free chip list; runs in threats-shared markup are rewritten on
   the way out. Now 0 on the repo page and 6 on methodology, all six confirmed
   to be commas doing grammatical work in prose.

Only site/src/designs/signal/ and its own tests are touched. Gates: tsc
clean, 626 pass / 1 skip / 0 fail (baseline 613 + 13 new), build green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
Three of these are about whether the page tells the truth well; four are
craft. Every one was re-measured in a real browser at 1440 and at a true
390px layout viewport, and two of the seven turned out to have a different
cause than the defect report named.

1. THE HONESTY PANEL NOW SETS TO A MEASURE. `.honesty-body` filled 834px of
   a 7fr track and ran 107 characters per line — the first thing on the
   methodology page, and the statement the whole site rests on. Every other
   prose block here is capped at 62-72ch; this one had been missed. Capped
   at 62ch: 107 -> 69 cpl at 1440, the 7fr track untouched.

2. THE GHOST NUMERAL IS NO LONGER STRUCK THROUGH — for two reasons, and the
   reported one was the smaller. `.sec-rail-in` is a <span> carrying block
   children and nothing blockified it (`position: sticky` does not), so it
   was an inline box where a block-start border and vertical padding
   contribute nothing to layout: its 12px of padding had never existed and
   its 2px rule painted at the numeral's own box top. Raising the padding,
   as suggested, changed nothing — measured. `display: block` makes the
   padding real. Underneath that sat the actual strike: `.sec-head` spans
   the full page by design, and the rail was inset from the section's
   CONTENT top, which is the head's own top, so the band ran through the
   glyph at 59% of its height. The rail now starts below the band. Verified
   by scanning every border that spans each numeral horizontally: 5 rails x
   5 widths (1100-2560), 0 crossings, rail-rule clearance +9.6px.

3. A PHONE GETS ONE WAITING PANEL, AND IT IS A REAL ONE. The round-4 fix
   clip-pathed the second panel's eyebrow and heading away and welded the
   dashed boxes, which made the two widths disagree about what the page
   contained: a sighted phone reader saw one heading followed by two
   unrelated paragraphs, the second under a heading where it is a
   non-sequitur, while a screen reader heard the correct two-section
   structure. `home.ts` now renders a single honest panel when BOTH
   exemplar panels are waiting — one eyebrow, one heading, both sentences
   verbatim from exemplars.ts, one way out — and the pair is swapped out
   with `display`, not clipped. At 390 the heading list is now NOT ENOUGH
   LISTINGS YET / STILL UNCHECKED for eye and screen reader alike.

4. THE CONTROL IDENTIFIER OUTRANKS THE GROUP CODE. `code` is 0.9em, which
   is right in running prose and wrong inside a box already at the smallest
   step: the ids in the restacked question cards rendered at 9.9px, 54 of
   them, under group codes at 11px. `code { font-size: max(0.9em, 0.75rem) }`
   states the floor once as an effect rather than as a list of selectors
   that goes stale, and the id is set a step above the groups in ink.
   Measured at 390: id 13px #141210, groups 12px #5C564C.

5. THE SMALLEST PHONE TEXT IS ALSO THE BEST-CONTRASTED. 11px was carrying a
   large volume of load-bearing state text on a phone, most of it in the
   muted token at 4.70:1. Two token overrides below 760px do the whole job —
   `--t--3` 11px -> 12px (the ramp stays monotonic) and `--ink-3` #6E675C ->
   #5C564C (4.70:1 -> 6.11:1). Tokens rather than a selector list, so a chip
   added tomorrow inherits the floor. Measured at 390 across all four pages:
   157/86/74/30 elements under 12px -> 0, 0, 0, 0. Desktop is unchanged.

6. THE COVERAGE FILTER SAYS WHICH CONTROL EMPTIED THE TABLE. The reported
   finding — no empty-state node in the DOM — did not reproduce: the strip
   ships and works, and a probe that ticks the box and reads the DOM in the
   same synchronous block sees it still hidden, because a MutationObserver
   callback is a microtask. What was real is that the message was generic.
   The coverage checkbox is the one control that exists for "find the
   listings under the floor", so when it matches nothing the honest answer
   is a finding, not a miss; it now gets its own sentence, chosen from the
   state of the controls, and rendered only on a board where it is true.
   The orphan `<thead>` — 43px of ruled headings on blank paper inside a 2px
   box — leaves with the rows.

7. THE COVERAGE NUMBER CARRIES ITS VERDICT. The repo hero set 87.1% in 84px
   Anton and never said whether it cleared the floor; the page's innerText
   contained no "75", no "floor", no "provisional". A mono verdict rides
   under the numeral in both directions, solid ink when the floor is
   cleared and dashed accent when it is not — never colour alone — and the
   below-floor form links down to the panel that already names the
   unverified controls and the one-line fix. The directory row says it too,
   but ONLY where it was silent: a below-floor row already says PROVISIONAL
   three times, and a fourth accent chip there spends the one hot colour on
   nothing.

The honesty contract is unchanged and re-checked live: unverified is a
visible third state and uncounted, provisional tags stay, lane marks come
from trust.ts with the local badge reading weaker than the action lane,
below-floor listings still name their unverified controls and the fix, and
contradictions are still named on the row and on the sheet.

Gates: tsc clean, 641 pass / 1 skip / 0 fail (baseline 613; 28 added, none
weakened or removed), build 30 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
@p4gs
p4gs merged commit 6b02d9d into main Sep 13, 2026
16 checks passed
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.

1 participant