feat(v2): TASK-122 Phase A — the ruled shell restyle (tint step, radius ladder, Inter, ink primary, halo focus, stable rows) - #1518
Conversation
…us ladder, Inter, ink primary, halo focus, stable rows) Sam ruled the shell parity look on 2026-09-03 (canvas Board 3); TASK-122 is the row, TASK-123 the Phase A build. This lands the shared tokens plus the Activity page, its compose box and its reply box, so the live page matches the ruling. Tokens (v2.css :root and design-system/tokens.css move together): --v2-shell-bg #f1f1f4, --v2-content-radius 14px, radius ladder 6/10/14 (--v2-radius-sm 8→6, --v2-radius-lg 12→14), --v2-ink #111827 / --v2-ink-hover #1f2937 / --v2-on-ink #fff, --v2-shadow-pending 0 1px 2px rgba(15,23,42,.06), --v2-font "Inter Variable" first (self-hosted via @fontsource-variable/inter, imported in V2App.tsx above v2.css; SF Pro is the fallback), body 14/20, meta 12/16, labels 11/14. Shell: .v2-shell and the rail/pods/inspector panes take the shell tint; the content pane becomes the inset white card (8px gutter, 1px ring, 14px radius, flush below 760px; banner variant keeps its height). Activity: compose box ring + halo focus (the two outline:2px overrides are deleted; the global halo applies, the pod <select> carries its own); Send and row actions are ink; queue rows carry a permanent transparent border and change only their fill on hover, no dividers; a pending decision/approval row sits one step up on the one chrome-card shadow and a ruled decision settles back down (--settled modifier from V2ActivityPage). Tests: three pins flip from accent to ink; six new invariants pin the tint step, the ladder, stable hover, halo focus, ink primary and the Inter import. jest maps @fontsource-variable/inter to the file mock. Frontend 518/518, tsc clean. README updated so this is the written standard; before/after in docs/design/task-122-phase-a-activity-before-after.jpg. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
lilyshen0722
left a comment
There was a problem hiding this comment.
SR-GATE: APPROVED @ 22bf9a81 — all checks green (only the ADR-number job skipped).
I could not run the frontend suites — this checkout has no frontend/node_modules at all, so your 518/518 is on report. (Correcting my own standing phrasing: I have been saying "no typescript"; the directory is simply absent here.) So instead of taking the presence tests at their word I measured the stylesheet in a real browser, which is the check v2-layout-invariants.test.ts's own header says it cannot make.
Real-browser measurements
Both sheets served over http and parsed by Chromium:
| sheet | cssRules |
last rule |
|---|---|---|
v2.css base |
1224 | @media (max-width: 640px) { .v2-activity__reply … } |
v2.css head |
1234 | same |
tokens.css base / head |
16 / 16 | ::selection |
No silent collapse — this is the check that matters for a 221-line CSS diff, because a stray brace drops every rule after it while every presence test still passes (the v2.css incident: 1094 → 495 selectors with 11/11 green).
Rendering the inset card in a real DOM confirms the spec: computed height: 784px on an 800px shell, border-radius: 14px, 1px solid, rgb(255,255,255), overflow: hidden.
I checked the zero right-margin before reporting it and it is intentional. .v2-shell:not(--no-inspector):not(--feature):not(--feature-wide) .v2-pane--main { margin-right: 0 } is (0,5,0) and beats .v2-pane--main's margin: 8px 8px 8px 0, so the card is flush right in the default three-pane shell. That rule is new in this PR and its comment says why ("the card meets the inspector gutter"). Not a finding.
Non-blocking: the banner-height comment names the wrong mechanism, and the real one is unpinned
v2.css:524 says the banner variant beats .v2-authenticated-shell__content .v2-pane { height: 100% } "on specificity." It does not. Both selectors are two classes — (0,2,0) each. It wins on source order: :534 comes after :410.
Measured rather than argued. Same file, same two rules, only their order swapped:
as shipped → computed height 784px (banner variant wins — correct)
banner moved up → computed height 800px (height:100% wins — both 8px gutters gone)
So the inset card's vertical gutters depend on a property no test asserts. The new invariant pins the string height: calc(100% - 16px) inside that rule, which stays true through a reorder or a later competing .v2-authenticated-shell__content .v2-pane rule. Cheapest fix is to make the comment say "source order" and add a class to the selector (.v2-authenticated-shell .v2-pane.v2-pane--main, (0,3,0)) so it wins on specificity as the comment already claims.
The rest of the suite is better than presence tests usually are
Worth saying explicitly, because I have complained about this file's weaknesses before:
- The hover test slices the rule body and asserts every declaration matches
/^background/, rather than grepping for one string. - The focus test enumerates all
.v2-activity__*:focus-visibleblocks and guards the loop withexpect(activityFocusRules.length).toBeGreaterThan(0)— an explicit positive control, so it cannot pass vacuously on an empty set. - The three flipped pins (
--v2-accent→--v2-inkon the recommended option, the queue-actions button, and the compose Send) are the ruled change, each paired with a negative asserting blue is gone.
One residual: ruleBody() returns '' for a selector it cannot find, so any .not.toContain(...) on its result passes vacuously if that selector is ever renamed. Most are protected by a positive assertion on the same variable; expect(ruleBody(v2, '.v2-root .v2-activity__queue-actions button')).not.toContain('var(--v2-accent)') in the ink test is not — its positive control lives in a different test in the file. Fine today, worth pairing.
Also verified
package.json and package-lock.json agree on @fontsource-variable/inter@^5.3.0 (lock has the resolved 5.3.0 entry), and the jest moduleNameMapper sends the font import to fileMock.js so it cannot break a suite. The V2ActivityPage.tsx change is the one-line settled-row class the invariant asserts.
lilyshen0722
left a comment
There was a problem hiding this comment.
SR-GATE: APPROVED @ 7ad411cc — carry-forward. Rebase-only over 22bf9a81.
Base moved 05a9184e → 77b8857b (main now carries #1517); 8 files both sides, +/- signatures identical, so the 22bf9a81 gate carries — including the real-browser parse (1224 → 1234 cssRules) and the inset-card measurements.
#1517 rewrote parts of V2ActivityPage.tsx, which this PR also touches, so I checked the interaction rather than trusting the clean rebase: the settled-row class is intact at :423 with its ruledDecisions guard, and both halves of the invariant at :934-935 still have their targets.
Still open and unchanged: the banner-height rule wins on source order, not the specificity its comment at v2.css:524 claims.
lilyshen0722
left a comment
There was a problem hiding this comment.
UX-GATE: APPROVED @ 95173c0 — Phase A measured live on commonly.me after Deploy Dev 1b11c66 (17:54Z), every §6 line holds at 1440 and 390.
Measured (real browser, smoke account, three seeded needs-you rows: mention, pending decision, ruled decision):
- shell
rgb(241,241,244); main pane white,1px solid rgb(229,231,235), radius 14px, margin 8px 8px 8px 0, overflow hidden; feature ground white - 390: pane margin 0, radius 0, border 0;
scrollWidth390 .v2-rootcomputed"Inter Variable"14px/20px;document.fontshas Inter Variable loaded; title 700 / -0.03em- Send:
rgb(17,24,39)on white, radius 10, 44px, weight 600 - compose textarea focus:
outline: none, halorgba(47,111,235,.18) 0 0 0 3px, border accent, radius 6, Inter 14/20; compose box border steps to strong - rows: mention
1px transparent, radius 10, padding 12px 14px, no shadow; pending decision white +1px rgb(229,231,235)+0 1px 2px rgba(15,23,42,.06); ruled (--settled) back to transparent/no shadow; norow + rowdivider rule in any sheet - hover on the mention row: bbox identical before/after (194,447.25 1120×90), background only (transparent →
rgb(241,242,245)) - copy 14/20, kind 12/16
Captures: .dev/t123/live-1440.png, live-1440-focus.png, live-1440-hover.png, live-390.png (pod file to follow). TASK-123 closes on this; Phase B (chat, board, Your team, agent profile) is filed as a child of TASK-122.
…ens (#1522) * fix(activity): make the page match the ruled canvas, not just its tokens Phase A (#1518) painted the new tokens over the old layout, so the live page kept the big composer heading, a bordered textarea with a 44px ink block beside it, 19px section headings with eyebrows, circle marks, and a reply box squeezed into the right column. Sam: not close to the preview. This is the canvas's after-side as structure: - composer is one radius-lg card: uppercase 11px label + pod chip on top, borderless textarea, a foot row with the hint and a 30px ink Send; focus is the halo on the card - section headings are 15/20 with an ink count badge and the description as muted meta on the same line; eyebrows gone - rows: name + meta on the top line, body in ink, the reply line UNDER the message (input 32px, ink Send, quiet Acknowledge / Open thread); square marks; pending cards radius-lg with the strong ring - the producer now stores actorName (resolved from the User row when a PG message carries only user_id), so a mention row is headed by who said it instead of 'Someone mentioned you' Frontend: 86 tests green (Activity, invariants, i18n keys), tsc clean. Backend: attentionItemService 9/9 incl. the author-lookup case. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHfcrzjN6MpeuCCAap5Qnb * fix(activity): phone layout — heading badge stays inline, pod chip fits the card At 390 the mobile block stacked the section heading as a column with align-items: stretch, which turned the count badge into a full-width bar, and the pod picker overflowed the composer card. Row + wrap + baseline for the heading; the chip label stays on one line and the select shrinks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHfcrzjN6MpeuCCAap5Qnb --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
What
Phase A of the shell parity restyle Sam ruled on 2026-09-03 (canvas Board 3): shared tokens plus the Activity page, compose box and reply box. Rows: TASK-122 (spec, ux-lead) → TASK-123 (this build). Spec of record in Sprint HQ: pod file 1788424115139-23785206.md; ruled before/after 1788424134427-616531996.png. Composer stays on top (Sam, 62744).
Before / after (real browser, live API, 1440 + 390)
Left: main at 5edce2e. Right: this branch's
v2.cssand the bundled Inter woff2 applied over the live page, after deleting the five live rules this branch removes (queue box, row dividers, the two outline overrides, the old row rule). Three seeded rows on both sides because the smoke account has none. Measured after: shell rgb(241,241,244);.v2-pane--mainwhite, 1px #e5e7eb, radius 14 (flush at 390); Inter Variable 14px/20px; Send #111827 at 44px; pending decision row shadow0 1px 2px rgba(15,23,42,.06), settled row none; mention-row hover changesbackground-coloronly with an identical bounding box; compose focusoutline: none+ halo0 0 0 3px rgba(47,111,235,.18)with an accent edge;scrollWidth1440 / 390.Tokens (both files move together)
--v2-shell-bg #f1f1f4·--v2-content-radius 14px· radius ladder 6 / 10 / 14 (--v2-radius-sm8→6,--v2-radius-lg12→14) ·--v2-ink #111827,--v2-ink-hover #1f2937,--v2-on-ink #fff·--v2-shadow-pending·--v2-font"Inter Variable" first, SF Pro fallback, self-hosted via@fontsource-variable/inter(OFL) imported inV2App.tsxabovev2.css· body 14/20, meta 12/16, labels 11/14.Tests
row + rowrule, hover body is background-only, pending shadow, settled drop,--settledin the TSX); halo focus (nooutline: 2pxin any Activity focus-visible rule, global halo intact, select carries its own); ink primary (Send and queue actions carry no accent); Inter import precedesv2.cssand the package is a dependency.@fontsource-variable/intermapped to the file mock (the bare specifier is not matched by the\.css$mapper). Frontend 518/518;tsc --noEmitclean.Deferred to Phase B on purpose
Chat, board, Your team and the agent profile (same tokens, own rows after Sam has seen A live);
body.v2-canvas/ App.css / index.html canvas literal stays#f8f8fb(visible only in the load flash). Build artifacts were fable-lead's on TASK-123; applied here verbatim by ux-lead because that seat could not write files.Gate
sprint-review gates the code. UX-GATE stays a live measurement after deploy (spec §6): shell tint, pane ring/radius, Inter 14/20, halo not outline, hover bounding box identical, pending shadow vs settled none, 390 flush.
🤖 Generated with Claude Code