Skip to content

feat(v2): TASK-122 Phase A — the ruled shell restyle (tint step, radius ladder, Inter, ink primary, halo focus, stable rows) - #1518

Merged
lilyshen0722 merged 2 commits into
mainfrom
feat/task-123-shell-parity-phase-a
Sep 3, 2026
Merged

lilyshen0722 merged 2 commits into
mainfrom
feat/task-123-shell-parity-phase-a

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

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)

before/after

Left: main at 5edce2e. Right: this branch's v2.css and 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--main white, 1px #e5e7eb, radius 14 (flush at 390); Inter Variable 14px/20px; Send #111827 at 44px; pending decision row shadow 0 1px 2px rgba(15,23,42,.06), settled row none; mention-row hover changes background-color only with an identical bounding box; compose focus outline: none + halo 0 0 0 3px rgba(47,111,235,.18) with an accent edge; scrollWidth 1440 / 390.

Tokens (both files 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 · --v2-font "Inter Variable" first, SF Pro fallback, self-hosted via @fontsource-variable/inter (OFL) imported in V2App.tsx above v2.css · body 14/20, meta 12/16, labels 11/14.

Tests

  • Three existing pins flip from accent to ink (they pinned the old blue buttons): recommended option, queue action, 44px option.
  • Six new invariants: tint step in both files + inset card + flush-at-760 + white feature ground; radius ladder in both files; stable hover rows (transparent border, no row + row rule, hover body is background-only, pending shadow, settled drop, --settled in the TSX); halo focus (no outline: 2px in any Activity focus-visible rule, global halo intact, select carries its own); ink primary (Send and queue actions carry no accent); Inter import precedes v2.css and the package is a dependency.
  • jest: @fontsource-variable/inter mapped to the file mock (the bare specifier is not matched by the \.css$ mapper). Frontend 518/518; tsc --noEmit clean.

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

…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 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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-visible blocks and guards the loop with expect(activityFocusRules.length).toBeGreaterThan(0) — an explicit positive control, so it cannot pass vacuously on an empty set.
  • The three flipped pins (--v2-accent--v2-ink on 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 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

SR-GATE: APPROVED @ 7ad411cc — carry-forward. Rebase-only over 22bf9a81.

Base moved 05a9184e77b8857b (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
lilyshen0722 merged commit 95173c0 into main Sep 3, 2026
15 checks passed
@lilyshen0722
lilyshen0722 deleted the feat/task-123-shell-parity-phase-a branch September 3, 2026 17:24

@lilyshen0722 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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; scrollWidth 390
  • .v2-root computed "Inter Variable" 14px/20px; document.fonts has Inter Variable loaded; title 700 / -0.03em
  • Send: rgb(17,24,39) on white, radius 10, 44px, weight 600
  • compose textarea focus: outline: none, halo rgba(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; no row + row divider 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.

lilyshen0722 added a commit that referenced this pull request Sep 3, 2026
…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>
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