feat(panel): one annotation card for review threads, in Files changed and the Conversation
Merged
DA
10d ago+933+933+012345678901234567890123456789-216-216-012345678901234567890123456789
Summary
This is PR 1 of 3 for the approved "Code annotations: one card" design (v3). It moves review threads onto one shared card. PR 2 moves Luke's File-tab notes onto the same card, and PR 3 continues the change band behind it.
AnnotationCard(packages/panel/src/tasks/annotation-card.tsx) is the review thread's quiet card: surface ground, quiet border, 10px corners, width capped atmin(48rem, 100%).- Header: one line that never wraps. It holds the chevron in the file header's 24px slot, the first writer's 20px face, their name (which truncates first), the age, state chips (Resolved, Outdated, Pending), the count, and the ⋯ at the end. The count is a message glyph and a number, shown only while the card is folded. The whole row folds the card, with the disclosure rows' hover fill.
- No lines in the header, and no preview when folded.
- One face column and one words column: each reply's face sits under the header's face, and every comment's words sit under the header's name. The positions come from custom properties on
.annotation-cardthat the header and the comments both read, so nothing uses a negative-margin offset. - Narrow (container under 440px):
- the age hides
- a state becomes a dot, with its word still in the tooltip and the accessible name
- the count drops its word
- the card moves to the pane's 8px inset
- the footer's presses draw closer
- Conversation cards have a path strip above the header, shown folded or open (the orchestrator's call). It shows the file path cut in the middle so the file name stays, then
L12–13, and pressing it goes to the thread in Files changed. The excerpt and comments follow in the body. - Files changed cards have no strip, because their place already says which lines. The one exception is a thread above the diff (outdated, or on a line no hunk shows): it has no line to stand on, so its strip carries the lines alone.
- Kept as before: fold state, deep-link landing (#3884), Reply / Resolve as the card's footer, pending Edit/Delete (#4015), reactions, ⌘F through folded cards, and the minimized chip (#3789).
- Hover: lighting a thread's lines on hover now also works for keyboard focus inside the thread. Like a tooltip, it only does this when the keyboard moved the focus there, so a card that a link lands on and focuses doesn't light lines by itself.
Evidence
Screenshots come from a harness that runs the real AgentPullRequestTab on fixture reads, with the real diff bundle (Pierre plus the highlight worker) and the built styles.css. They show before and after at 900px and 360px, in dark and light, plus hover and folded.
pnpm check:affected: panel typecheck ran out of memory (exit 137), so I ran the fallback checks below.tsc --noEmitforpackages/panel,apps/desktopandapps/web: clean../scripts/oxlint.sh: 0 warnings, 0 errors.biome check: clean on the changed files.pnpm knip: clean../scripts/repository-checks.sh: passed.vitest(panel): 280 files. 3 failures in the first run came from swapping inmain's sources mid-run for the before shots; the rerun of those files plus the changed ones passed, 349/349.cd apps/desktop && node scripts/build.mjs: bundle budget passes.
New tests in annotation-card.test.ts:
- Alignment: with the real stylesheet laid, every custom property and sum is resolved, and the test asserts that the header face's x equals the reply faces' x, and the header name's x equals the words' x.
- No preview when folded: a folded card is its header alone and shows the count; once open, the count goes.
- Skinny rules: at 360px the age hides, the state is a dot whose word is still in the DOM, and the count drops its word; at 900px they show.
In agent-pull-request-tab.test.ts:
- Hover lighting: the hover test now also checks that keyboard focus lights a thread's lines and that focus leaving lets them go.
- Updated selectors: the existing thread tests use the new head, strip and comments selectors, and the strip asserts
src/invite.tsL12–13andsrc/old.tsL9(folded).
Test count: +3 tests in a new file.
Screenshots
| Before | After | |
|---|---|---|
| Files changed, 900px, dark | ![]() | ![]() |
| Files changed, 900px, light | ![]() | ![]() |
| Files changed, 360px, dark | ![]() | ![]() |
| Files changed, 360px, light | ![]() | ![]() |
| Conversation, 900px, dark | ![]() | ![]() |
| Conversation, 900px, light | ![]() | ![]() |
| Conversation, 360px, dark | ![]() | ![]() |
| Conversation, 360px, light | ![]() | ![]() |
| Hover (lit lines) | Folded |
|---|---|
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
Invariants
-
./scripts/check.shgreen. (CI) - JSON Schema goldens unchanged.
- Every behavior change has, in this PR, the test that fails without it; no change-detector test added.
- Gateway envelope goldens unchanged.
- No new
astype assertion outside the allowlisted files. - No
Effect.runPromise/runSync/runForkoutside the runtime edges. - No new raw async primitives.
- No dropped Effect statements.
- Test count: +3 (new
annotation-card.test.ts). - No hand-rolled file replaced.
- Doc comments naming the changed parts are edited (
agent-pull-request-tab.tsx's header,tasks.css). -
PRIVACY.mdunchanged. - Package deps unchanged.
- Barrel/door rule untouched.
Notes
- This is desktop/panel only, so no Preview run is needed.
- Next: PR 2 (Luke's File-tab notes on this card) and PR 3 (the band continued behind the card).
Made with Luke























