Moatbrain PR #56 Visual review T3 · review only head 233e3d7

The Screener lane is right on desktop and breaks below 1200

Reviewed live on the deployed build, not from source: staging.ownermind.co reports sourceSha 233e3d7 / dpl_DQ44MnMxJjNe2wYwfsrBDztu5nhW, which is this PR's head. Signed in on a fresh isolated QA account, started an assessment, and measured Screener, Risk and Memo at 1440 / 1280 / 1199 / 390.

Ref drift, worth knowing before anything else. The task named 2757985 as the exact head. The head is 233e3d7, eight minutes later, and it touches app.screener.tsx and sana-pages.css — both surfaces I was asked to review. I reviewed 233e3d7, because that is the branch head and the deployed build; reviewing the named commit would have produced findings against code that had already moved.

PASSWhat is right

CheckedResultEvidence
No white/light content panels
Screener · Risk · Memo, 1440
confirmed Swept every element ≥24×12px inside .sana-source for a background luminance above 0.35. The only hits are .btn.solid at rgb(114,205,176) — that is var(--accent), the intended filled button. Zero light content panels.
Risk / Memo dark surfacesconfirmed Panels resolve to rgb(17,19,24) = --surface, fields to rgb(5,6,8) = --sunk, .p-body padding 18px 20px. The whole new block is var()-based — no literals added.
Review Lane, desktop
1440 and 1280
works Default open 246px 794px 400px; collapsed adds drawer-closed and gives 246px 1194px 0px with the document pane expanding 794→1194; reopen restores exactly. Both mechanisms wired, which was the thing most likely to be half-done.
Horizontal overflow0px All four surfaces at 1440 / 1280 / 1199 / 390.
Auth routes drop the public headerconfirmed /sign-in and /sign-up render no header. See D5 for the rest.

D1Collapsing the lane below 1200 crushes the page into a 246px column

New behaviour — it exists only because the Screener can now be collapsed

sana-source.css:333 declares .sana-source.drawer-closed{grid-template-columns:246px minmax(0,1354px) 0} with no media-query guard, at specificity (0,2,0). The mobile rule inside @media(max-width:1200px) is .sana-source{grid-template-columns:1fr} at (0,1,0) — media queries add no specificity, so the desktop rule wins at every width. Meanwhile .funnel is display:none below 1200, so .work falls into the first 246px track.

WidthStateGridDocument pane
1199collapsed246px 953px 0px246px wide, 953px of dead space
390collapsed246px 144px 0px246px wide in a 390px viewport
1440collapsed246px 1194px 0px1194px — correct
Screener at 1199px with the Review Lane collapsed: all content squeezed into a narrow left column with a large empty area to its right and the reopen handle rotated sideways at the right edge.
1199px, lane collapsed. The search field and Save button are clipped inside a 246px column, roughly 950px sits empty, and the reopen handle renders rotated against the right edge. The screener table is 1297px wide inside that column.

D2On a phone the lane is open by default and covers the whole app

reviewLaneOpen defaults to true and drives both drawerClosed and the review-mobile-open class. Below 1200 the grid is a single column, so an open lane is a full-bleed overlay: at 390 the drawer measures 390px — the entire viewport. Open-by-default is right on desktop and wrong on mobile.

This is the seam I flagged in the contract as two mechanisms rather than one. They are both correctly wired — but to a single boolean, and the correct default differs by breakpoint. Desktop wants open; mobile wants closed with the trigger visible.

Screener at 390px on load: the coach panel fills the entire screen, with no screener content visible.
390px, first paint of /app/screener. No screener content is reachable without dismissing the panel.

D3The new 16px field rhythm produces no separation at all

Two compounding causes, both measured on the deployed build:

Fixing the :last-child level alone will not land the rhythm — the field row has to stop being an inline label first. A block or grid row, with the spacing on the row's container, solves both at once.

D4Textarea rows read as broken because of the same inline label

With .f inline, a short label baseline-aligns against a tall field. On Memo the Business case label sits at y=545 while its textarea spans y=427–559 — so the label renders at the bottom edge of the field rather than above it. Short inputs and selects look correct, which is exactly why this survives a quick look.

Memo page: three form rows where the label sits at the bottom-left of a floating textarea rather than above it.
Memo at 1440. The surface itself is right — dark, tokenised, consistent panel padding and a restrained header pattern. The three field rows are the defect.

D5The provenance nav was hidden on two routes, not removed

__root.tsx:83 and :85 still render Home and Build provenance → /health-ui. The change adds /sign-in and /sign-up to hidePublicHeader; every other signed-out route keeps the nav. Confirmed live — /demo-access and /health-ui both render Moatbrain · Home · Build provenance · Sign in, and /health-ui publishes sourceSha and deploymentId to anyone past the staging password.

The new test cannot catch this. It asserts the two links have count 0 on the sign-in page — which passes because the entire header is hidden there. It would pass unchanged with Build provenance still linked on every other public route, which is the current state. If the intent really was "hide on auth routes", the test is fine and the requirement changed; if the intent was "remove", the test is measuring the wrong page.

D6Spacing is not yet coherent

Rendered padding values still in use per surface: 21 on Screener, 22 on Risk1 2 3 4 6 8 9 10 11 12 13 14 15 16 17 18 20 22 24 30 42 80. The --space-* tokens are still absent, and the new .p-body{padding:18px 20px} introduces 18px, which is not a step on the canonical 4/8/12/16/20/24/32/40/56 scale. Not a blocker for this PR; still open from the contract.

D7The new tests pin the palette the contract flagged as wrong

Asserting a computed colour is the right technique and I would keep it. Note only that rgb(17,19,24) and rgb(5,6,8) are the cold-steel values from sana-accessibility.css, which currently wins the token collision. Resolving that collision in favour of the warm Desk will break these two assertions — better known now than discovered then.

!What this review did not exercise

/app/progress is not covered by my "no light panels" result. The sweep returned zero there — but the QA account has no data: 0 calibration cells, 0 spine entries, 3 empty states. app.css is untouched by this PR, so the five light cards and eight AA failures from the contract are still in the stylesheet and will paint the moment an account has records. Zero findings there means nothing rendered, not fixed.

Live review against staging.ownermind.co at sourceSha 233e3d7 (builtAt 2026-08-03T03:55:00Z) — the same deployment as the PR preview. PR #56 open, base main, mergeable, all four checks passing. No code, no merge, no deploy.