Skip to content

fix(activity): make the page match the ruled canvas, not just its tokens - #1522

Merged
lilyshen0722 merged 2 commits into
mainfrom
fix/activity-match-canvas
Sep 3, 2026
Merged

lilyshen0722 merged 2 commits into
mainfrom
fix/activity-match-canvas

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

Why

Phase A (#1518) painted the new tokens over the old layout. Live, the page kept the big composer heading, a bordered textarea with a 44px ink block beside it, 19px section headings with eyebrows, circle marks, and the reply box squeezed into the right column. Sam, on seeing it: not close to the preview, and the dark button does not align with the style. He is right — the canvas after-side is a structure, not a palette.

What

The after-side of the Shell Parity canvas (board 3, the ruling surface) as structure:

  • Composer: one radius-lg card — uppercase 11px label + pod chip on top, borderless textarea, foot row with the hint and a 30px ink Send. Focus is the halo on the card.
  • Section headings: 15/20 weight 600, ink count badge, 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 (32px input, ink Send, quiet Acknowledge / Open thread). Square marks. Pending decision/approval cards radius-lg with the strong ring.
  • Producer: AttentionItem.actorName, resolved from the User row when a PG message carries only user_id — every live mention read Someone mentioned you. The row is now headed by who said it.

Proof

  • Frontend: V2ActivityPage, v2-layout-invariants, translationKeys — 86/86; tsc --noEmit clean.
  • Backend: attentionItemService 9/9 including the new author-lookup case.
  • Real-browser render at 1440 against the production API through a local proxy, compared to the canvas: composer, headings and rows match; Sam has the screenshot.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UHfcrzjN6MpeuCCAap5Qnb

lilyshen0722 and others added 2 commits September 3, 2026 11:18
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
…ts 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

@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 @ 2b91fb08 — with one sequencing note that matters before the backfill runs.

The backend half of this PR is not scope creep, it is a real defect fix, and it is one I missed on #1517. recordMentionedUsers derived the author as message?.username || message?.userId?.username || 'Someone'. On the PostgreSQL path — the default for chat messages — Message.create returns INSERT … RETURNING *, and config/schema.sql:42 shows the messages table has user_id and no username column, so both fallbacks are undefined and every live mention rendered "Someone mentioned you." I reviewed that exact line on #1517 and checked the podId / userId / content field-name fallbacks without checking this one.

Sequencing: the fix reaches new mentions only, unless the backfill runs after it

recordForRecipients writes under $setOnInsert, which is correct and deliberate — it is what stops a retried source write from resurrecting a resolved card. The consequence here is that actorName and the corrected title are never applied to an AttentionItem that already exists. So:

  • every row materialized between #1517 shipping and this PR keeps "Someone mentioned you" permanently;
  • and if the attention-items backfill is applied before this lands, every backfilled row is written with "Someone" too, also permanently.

@sam this bears directly on your 17:37 plan — dry-run the backfill, bring you the counts, apply only on your say-so. Land #1522 first and the backfill writes real names; apply the backfill first and it bakes in "Someone" for the whole 14-day window.

What I verified

The #1518 invariants still hold, which was the live risk in a 227-line CSS restructure that ships no change to v2-layout-invariants.test.ts. Re-derived each assertion against the new sheet rather than waiting on CI:

ok  .v2-activity__queue-row                      <- border: 1px solid transparent
ok  .v2-activity__queue-row                      <- border-radius: var(--v2-radius)
ok  .v2-root .v2-activity__queue-actions button  <- background: var(--v2-ink)
ok  .v2-root .v2-activity__compose button        <- var(--v2-ink)
ok  .v2-activity__queue-row--settled             <- box-shadow: none
ok  .v2-pane--main                               <- border-radius: var(--v2-content-radius)
row+row divider rule present?  False
outline: 2px in any .v2-activity focus-visible rule?  False

The sheet parses. Served over http and read in Chromium: 1234 → 1243 cssRules, same last rule. No silent collapse from the restructure.

Non-blocking

  1. resolveAuthorName's catch returns 'Someone' — the same value the no-author path returns, so a User lookup failure is indistinguishable from a genuinely unknown author, and it is unlogged. Small, but it is the exact shape TASK-099 spent the day removing, re-entering in a new function two hours later.
  2. recordApproval sets actorName: agentName || undefined while its title still falls back to 'Approval requested'. Consistent, but it means an approval with no agentMetadata.agentName has no actor for the UI's new topline to use; worth confirming the fallback renders.

Scope limits

Test & Coverage and E2E Tests are pending. I did not run any frontend suite — no frontend/node_modules in this checkout — so the component restructure, the seven locale strings and the CSS are read plus the two measurements above, not executed. The invariant re-derivation is my own reimplementation of those assertions; CI running the real file is still the authority.

@lilyshen0722
lilyshen0722 merged commit fd1e7d8 into main Sep 3, 2026
15 checks passed
@lilyshen0722
lilyshen0722 deleted the fix/activity-match-canvas branch September 3, 2026 21:32
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