feat(panel): fold and stick the PR tab's Conversation sections, filters in the heading

Merged
DA
9d ago
+476-189

Summary

Asked for: make Description and Conversation collapsible and sticky, move Hide resolved and Hide bots into the Conversation header, use the fade effect, and drop "Show all N" / "Show less" from Checks.

Conversation pane (agent-pull-request-tab.tsx, tasks.css)

  • Collapsible. The Description and Conversation headings are now presses across the whole row, with a leading chevron and aria-expanded. They're built on the app's own disclosure (Fold, FoldSummary, DisclosureChevron, FoldBody), so they get its motion, its reduced motion, and find-in-tab support. Both start open.
  • Open state is kept in the same renderer store as the filters (conversation-filters.ts, key luke.conversation-filters). There's one choice per section, the same for every pull request, and a change in one tab shows at once in the others. Choices stored before this change still read correctly, and both sections default to open.
  • Sticky. Each heading (and the comment box's) is position: sticky at the scroller's top, 32px tall (--luke-scroll-band), on the panel's --luke-color-well ground. A sticky box stands only inside its own section, so the next section's heading pushes the previous one off.
  • The fade. This is ScrollArea's band fade, the one the Code and Call logs use under their checkpoints (fade="start", fadeInset="band"): content dissolves just beneath the stuck heading instead of being cut at its edge. The Conversation view's DetailPage column wasn't a ScrollArea, and under 720px its body, not the column, was the scroller. So the view is now a ScrollArea holding a .pull-request-page at the same measure and inset the DetailPage gave it. Checks keeps its DetailPage. The header-fold scroller selector names the new scroller.
  • Filters in the heading. Hide resolved and Hide bots are now compact filter chips (Chip FILTER + COMPACT, aria-pressed, the same look as the reactions) with their counts, at the right end of the Conversation heading. Their tooltips, descriptions and stored choice are unchanged. Pressing a chip doesn't fold the section. The separate filter row and its CSS are gone.
  • Jumps. A thread or entry landing (#3884/#3887) opens a folded Conversation so the card can be drawn. centreCard now centres the card in the space under the scroller's scroll padding (the stuck heading plus the fade). A card taller than that space lands with its top just under the heading.

Checks pane

  • No fold: every check is listed in the service's order (failed, then running, then the rest). CHECKS_UNFOLDED, unfoldedCountOf, and the showAll / SHOW_LESS strings are deleted.

Screenshots

Every shot is the side panel with fixture data, at about 900px and 600px wide, in light and dark.

Light, 900Dark, 900Light, 600Dark, 600
Conversation at the topconversation-top light 900conversation-top dark 900conversation-top light 600conversation-top dark 600
Scrolled: the Conversation heading stuck, content fading under itconversation-stuck light 900conversation-stuck dark 900conversation-stuck light 600conversation-stuck dark 600
Description foldeddescription-collapsed light 900description-collapsed dark 900description-collapsed light 600description-collapsed dark 600
The Conversation heading with its filters, Hide resolved pressedfilters-header light 900filters-header dark 900filters-header light 600filters-header dark 600
Checks listing every checkchecks light 900checks dark 900checks light 600checks dark 600

Evidence

  • pnpm check:affected: @luke/desktop#typecheck ran out of memory in the sandbox (exit 137), as on #4101, so I ran the parts per package:
    • tsc --noEmit for @luke/panel passes; @luke/desktop typecheck passes with --max-old-space-size=4096.
    • pnpm exec vitest run --project panel: 264 files, 2718 tests pass.
    • ./scripts/oxlint.sh: 0 warnings, 0 errors.
    • pnpm knip: clean.
    • biome check on the touched files: clean.
    • ./scripts/repository-checks.sh: pass.
    • cd apps/desktop && node scripts/build.mjs: builds within the bundle budget.
  • Tests (agent-pull-request-tab.test.ts):
    • New: Description and Conversation each fold under their heading, both start open, and each stays as left, on its own, for the next pull request drawn.
    • New: the Conversation scroller is a ScrollArea with data-fade="start" and data-fade-inset="band". Each section's heading is the summary.pull-request-section-head band with its h3 and chevron, and the filters stand in the Conversation heading's end, with no filter row.
    • New: a link to a thread opens a folded Conversation and lands on it, centred under the 58px band. A card taller than the space lands with its top at the band.
    • Replaced: the two Show all / Show less tests are now one test that checks every check is listed in order and nothing folds them.
    • Updated: the Hide bots / Hide resolved test presses the chips in the heading (aria-pressed) and checks that a press folds nothing. The phone header-fold tests scroll the new scroller.
    • Test count: 94 → 95 in this file (+3 new, −2 fold tests).

Invariants

  • ./scripts/check.sh green. (CI)
  • JSON Schema goldens unchanged (LUKE_UPDATE_FIXTURES not run). (CI)
  • Every behavior change has, in this PR, the test that fails without it; no change-detector test added.
  • Gateway envelope goldens unchanged. (CI)
  • No new as type assertion outside the allowlisted files. (CI via anti-slop)
  • No Effect.runPromise/runSync/runFork outside the runtime edges. (CI)
  • No new setTimeout/setInterval/new Promise/AbortController/fs.watch. (CI)
  • No Effect, Stream, or Layer written as a statement and dropped. (CI)
  • Test count equals the pre-PR count or the delta is listed. (+1 in the file, above)
  • Each hand-rolled file the PR replaces is deleted. (None replaced.)
  • Every CLAUDE.md/AGENTS.md sentence naming a changed part is edited. (None names these parts.)
  • PRIVACY.md unchanged.
  • Package package.json deps match what the sources import. (No new imports outside @luke/ui.)
  • Barrel/door rule unchanged.

Notes

  • Desktop/panel only, so no Preview run is needed.

Made with Luke