feat(panel): fold and stick the PR tab's Conversation sections, filters in the heading
Merged
DA
9d ago+476+476+012345678901234567890123456789-189-189-012345678901234567890123456789
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, keyluke.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: stickyat the scroller's top, 32px tall (--luke-scroll-band), on the panel's--luke-color-wellground. 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'sDetailPagecolumn wasn't aScrollArea, and under 720px its body, not the column, was the scroller. So the view is now aScrollAreaholding a.pull-request-pageat the same measure and inset theDetailPagegave it. Checks keeps itsDetailPage. The header-fold scroller selector names the new scroller. - Filters in the heading. Hide resolved and Hide bots are now compact filter chips (
ChipFILTER+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.
centreCardnow 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 theshowAll/SHOW_LESSstrings are deleted.
Screenshots
Every shot is the side panel with fixture data, at about 900px and 600px wide, in light and dark.
| Light, 900 | Dark, 900 | Light, 600 | Dark, 600 | |
|---|---|---|---|---|
| Conversation at the top | ![]() | ![]() | ![]() | ![]() |
| Scrolled: the Conversation heading stuck, content fading under it | ![]() | ![]() | ![]() | ![]() |
| Description folded | ![]() | ![]() | ![]() | ![]() |
| The Conversation heading with its filters, Hide resolved pressed | ![]() | ![]() | ![]() | ![]() |
| Checks listing every check | ![]() | ![]() | ![]() | ![]() |
Evidence
pnpm check:affected:@luke/desktop#typecheckran out of memory in the sandbox (exit 137), as on #4101, so I ran the parts per package:tsc --noEmitfor@luke/panelpasses;@luke/desktoptypecheck 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 checkon 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
ScrollAreawithdata-fade="start"anddata-fade-inset="band". Each section's heading is thesummary.pull-request-section-headband with itsh3and 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.shgreen. (CI) - JSON Schema goldens unchanged (
LUKE_UPDATE_FIXTURESnot 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
astype assertion outside the allowlisted files. (CI via anti-slop) - No
Effect.runPromise/runSync/runForkoutside 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.mdsentence naming a changed part is edited. (None names these parts.) -
PRIVACY.mdunchanged. - Package
package.jsondeps 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



















