Read against origin/main 25f21f6 (verified as the current head of
origin/main; note origin/develop is behind it at
525194a). Covers the Risk and Memo stages, Screener, the auth shell and the token
layer. No code, no merge, no deploy — every item below is a change for Charlene to make.
The headline. Risk and Memo are not the problem. They use the Desk vocabulary
cleanly — .p-body, .btn, .chip,
.eyebrow — with zero inline styles and only two undeclared classes in the whole
set. The light surfaces come from two places: a token collision that silently replaces the
approved palette, and two stylesheet regions written in literal light hexes (the
progress page and the public/auth shell). Fix C1 first; several other items shrink once it
lands.
Root cause · fix before anything else
Both of these declare tokens on the same selector, at the same specificity
(0,1,0), and both are loaded by apps/web-start/src/routes/app.tsx:
| Declared in | Load order in app.tsx | ground | surface | cream | accent |
|---|---|---|---|---|---|
sana-source.css:12the warm Desk |
3rd of 7 | #141210 | #1B1815 | #F2EBDD | #7FBFA8 |
sana-accessibility.css:7cold steel + a light theme |
5th — wins | #08090b | #111318 | #f7f8fa | #72cdb0 |
So all 231 Desk component rules render, painted in a palette nobody picked. The Desk block in
sana-source.css is dead as tokens while its own header comment still describes the
room it no longer produces. tokens.css is a one-line stub claiming
"Token values are owned by the immutable Sana source stylesheet" — which is now false.
Contract. Exactly one file declares the palette. Put it in tokens.css,
which is what its name and its own comment already promise, and delete the block from both
other files. sana-accessibility.css keeps focus rings, reduced-motion and contrast
helpers — it should not own a colour system.
Which palette is canonical is a call I should not make silently, so I am making it
explicitly and you can overrule it: the warm Desk. It is the direction Khoa picked, it is
what every mock in this program has been drawn in, and it is what the progress round was signed
off against two days ago. The steel palette has one thing the Desk block does not — a working
[data-theme="light"] variant that milestone.spec.ts:1177 asserts. That
light theme is a tested feature and must survive; it needs re-deriving from the Desk hues rather
than being deleted.
Do not read "eliminate light cards" as "delete the light theme." They are different
things, and conflating them breaks a passing test. The rule that resolves it:
a light surface is legal only under [data-theme="light"], and only ever from a
token. A literal light hex in a rule that is not theme-scoped is the defect. That single
sentence is the whole of C2 and C3.
apps/web-start/src/styles/app.css lines 24–77 · inside a dark DeskRoot
The build itself is faithful — app.progress.tsx honours the null confidence
bucket, the hollow low-n point, insufficient-data held outside the grid, the never-summed note
and the paused-memory state. The defect is confined to the stylesheet. Two failure
classes, both from light-theme values rendered on the dark ground:
Measured against the ground actually in effect, #08090b — the value
.sana-source resolves to today, not the Desk's #141210. Both are dark,
so the failures hold either way; the exact ratios move slightly once C1 lands.
Contract. Every value in both tables becomes a token. No literal hex survives in the
.progress-*, .calibration-*, .confidence-* or
.base-rate rules. Borders #c9ccc3 #d7dad2 #bdc3b9 #9ba39a #adb3aa #cbd0c6
#989e95 #a9aea5 all collapse to var(--line) or var(--line-2).
The first thing a user sees, and it is not the product
sign-in.tsx, sign-up.tsx, demo-access.tsx and the
landing route all render <main className="tracer-main"> outside
DeskRoot, so no .sana-source applies and they fall to
app.css:1 — :root{background:#f4f5f0;color:#171915}.
| Selector | Location | Now | Contract |
|---|---|---|---|
.tracer-header | app.css:919 | background:#fff | var(--sunk) + var(--line) border |
.wordmark | app.css:934 | color:#171915 | var(--cream) |
.tracer-main h1 | app.css:954 | Georgia serif, clamp(38–68px) | var(--sans), one display size — see C7 |
.tracer-main > .eyebrow | app.css:947 | color:#566258 | var(--cream-3) |
.tracer-main > .lede | app.css:962 | color:#454a43 | var(--cream-2) |
a (bare element selector) | app.css:916 | color:#245342 | var(--accent); scope it, do not leave it global |
.primary-link | app.css:977 | #1f493a bg / #fff text | reuse .btn.solid |
.account-password button | app.css:1029 | background:#f4f2eb | var(--raised) |
Contract. Wrap the public and auth routes in the same scope the app uses, so they
inherit tokens instead of re-declaring colours — either DeskRoot directly or a thin
PublicRoot that applies .sana-source without the
app3 grid. Then delete every literal above.
While you are in there: tracer-* is leftover naming from the pipeline-tracer
lab UI. I flagged the visible "pipeline tracer" pill in the July audit and it went; the class
names stayed. Rename to auth-* / public-* in the same pass — it is
free while you are already touching every one of these rules, and expensive later.
apps/web-start/src/routes/__root.tsx lines 73–86
The block is gated on !isAuthenticatedRoute, where
isAuthenticatedRoute is pathname.startsWith("/app"). Three links:
Home → /, Build provenance → /health-ui,
Sign in → /sign-in.
Contract. Remove Home and Build provenance. Keep the wordmark
(it is the home affordance) and keep Sign in. If the <nav> is
then a single link, drop the element and its aria-label rather than shipping a
one-item navigation.
/health-ui route. The ask is to stop advertising build
provenance to signed-out visitors, not to remove an ops surface. Unlink it; leave it
reachable./launch/* and
/calculators/*, not only the auth pages. Confirm that is intended.These need separate fixes; doing one leaves the other broken
| Breakpoint | Visible by default? | Collapsible? | Why |
|---|---|---|---|
| ≥ 1200px | yes | no | app.screener.tsx:177 passes only pageClass. DeskRoot
accepts drawerClosed (index.tsx:13) and the collapse is fully built
— sana-source.css:252 margin, :333 grid, :264 reveals
the .dopen button — but the Screener never sets the state, so the control never
appears. |
| < 1200px | no — display:none | unreachable | sana-source.css:225 sets .sana-source .drawer{display:none}. The
assessment shell escapes it via review-mobile-open
(sana-extensions.css:64) plus .review-mobile-trigger /
.review-mobile-close (app.assessments.$assessmentId.tsx:255,264).
The Screener has neither. |
Contract. Hold lane state in app.screener.tsx, default open; pass
drawerClosed={!laneOpen} to DeskRoot and render the .dopen
toggle. Separately, adopt the assessment shell's mobile pattern by reusing its exact class
names and buttons rather than writing a Screener-specific variant — a second mechanism for
the same behaviour is how these two got out of step in the first place.
Responsive caveat, and it is the one most likely to be missed. Desktop collapse and
mobile open are two different mechanisms. drawerClosed does nothing below
1200px because the drawer is display:none there; review-mobile-open
does nothing above 1200px because the media query does not apply. Wire one and the review will
look correct at whatever width you happened to test. Check 1400 / 1199 / 900 / 390.
Also on the Screener: .check and .screener-protection are
rendered in app.screener.tsx and declared in no stylesheet — unstyled elements
falling back to browser defaults. Either style them or drop the class.
33 distinct pixel values across padding, margin and gap in app.css, and
29 in sana-pages.css — including every integer from 1 to 20. That is not a
system, and it is worse than what I audited in July, which found 13 and called it two
interleaved scales.
The canonical scale from that audit — --space-1..9 = 4 / 8 / 12 / 16 / 20 / 24 / 32 / 40
/ 56 — was implemented and verified green at 1e5b4ea on 19 July.
It is gone: there is no --space-* token anywhere on main.
Contract. Re-adopt it rather than re-derive it, with the tier rule that came with it: a gap is one tier larger than the gaps inside what it separates. Snap every value to the nearest step, and treat any new literal in a padding, margin or gap as a review failure. This is mechanical and it is the single cheapest way to make the whole app read as one product.
| Rule | Now | Contract |
|---|---|---|
.tracer-main h1 app.css:954 |
Georgia, clamp(38–68px) | One display size per page.
Page titles 27, section headings 20 — the hierarchy verified at
1e5b4ea. No serif: Georgia is the Ledger direction, not the Desk. |
.progress-intro h1 app.css:26 |
Georgia, 42px | |
.progress-section h2 app.css:30 |
Georgia, 27px | |
.calibration-cell strong app.css:38 |
Georgia, 42px | var(--num), 27px — see the note below |
The 42px quadrant count is a compliance point, not a taste point. It is the largest element on a page whose entire design brief is that it must never rate the reader. A number set larger than the page title is the closest thing to a score that surface can produce, and it undoes the restraint the rest of the build got right. It is 27px in the approved design and in monospace, deliberately, so that four counts read as four counts rather than as one headline figure.
Outside the visual scope — raising, not expanding
Both were named as acceptance criteria in the signed-off round, and neither is in the current build. Flagging them here only because I was already in the file:
data-count-of attribute anywhere in app.progress.tsx.
The rule was that every figure names the table it counts, so a figure that cannot is caught
as a derived index.om_flywheel_aggregates is absent, so the refusal currently lives only in the
round record.Neither blocks the visual work. Both belong to Scott's build spec rather than to this contract.
25f21f6. Every
selector, line number and colour is read from the tree; the contrast ratios are computed from
those values. What a browser actually paints can differ — cascade order across seven
stylesheets is exactly where that bites, and C1 is itself a cascade bug I found by reading
load order rather than by looking. Confirm C1 and C5 in a live render before treating them
as closed.apps/web, the legacy Next app. It has its own
sana-desk-* stylesheets and its own copy of the scope class.ownermind-web origin/main 25f21f6 · Merge PR #53
charlene/moatbrain-residual-casing · 2 Aug 2026 17:22 +0700.#08090b, the ground
.sana-source resolves to on this ref. Design review only — no code, no merge,
no deploy.