feat(desktop): lay the Pull request tab out as a fixed header over Conversation, Checks, and Files changed tabs
Follows #3239 (merged). This is the layout the developer chose for the agent's Pull request tab: "a tabbed approach below the header info of the PR … a fixed section and then tabs beneath, reminiscent of stage-cli." It's modelled on stage-cli's pull-request-layout.tsx and pull-request-header.tsx. The developer approved the screenshots below in their inbox.
Summary
-
Fixed header that stays put while the tab under it scrolls:
- the bar: state pill on the left, GitHub and ⋯ on the right;
- the title and
#number, thenbase ← head; - who opened it and when, and the reviewers' facepile (requested reviewers over standings from reviews, as in #3239);
- while the PR is open, the one-line merge status.
It draws at once from the published summary (
control.pullRequests) and fills in as the pull request read lands, with #3239's skeletons and per-part Retry. -
Tab row under the header, replacing the Overview | Files changed
Segmented(a form control). It's an underlined secondary row in Luke's look, withrole="tablist"/tab/tabpaneland arrow keys moving along it. Each tab is a glyph, a name and a quiet count:- Conversation (MessagesSquare): the description, then the comments, reviews and review threads. Counts entries + earlier entries left on GitHub + threads.
- Checks (CircleCheck): the checks list. From the first frame it wears the published checks summary as its glyph in its colour (✕ red, ● amber, ✓ green, nothing for none), the same rule as the pill's mark, which costs no extra GitHub request. The number joins it once the checks read lands.
- Files changed (FileDiff):
AgentChangesTabmounted whole, its ownFileViewBarand tree, counted+N −Mfrom the published summary. I agreed with the Pierre diffs agent to leave its bar untouched, so its Diff settings button lands there without a move.
-
Reads: the header's reads start when the tab shows: the pull request, and the conversation, because its reviews give each reviewer's standing in the facepile. That way a tab opened on Files changed still shows who approved. Checks reads when its tab is first shown. Refresh (⋯) re-reads the header's reads, plus the checks if their tab has been shown. Each tab stays drawn once shown, so switching back reads nothing again.
-
Failures: only a read that says there's no pull request yet takes the header and tabs away. A failed read says so in its own part with its own Retry; the tab row and Files changed stay and read on their own. This replaces #3239's single centred failure for the case where nothing is held.
-
Kept: the per-agent view memory (
pullRequestViewsinuse-side-panel.ts), nowconversation | checks | files(it's held for the window's life, so nothing stored needs migrating). The PR chip opens Conversation and the Changes chip opens Files changed. -
Narrow panel: below 520px, tabs you're not on keep only their glyph and count. Their name is visually hidden but stays readable to a screen reader.
Screenshots
These come from a throwaway harness that mounts the tab alone. The harness can't run the Pierre diff renderer, so the thread preview and the Files changed diff show "The diff could not be shown." In the app they draw the diff.
| Conversation, 620px, dark | Checks, 620px, light | Files changed, 620px, dark |
|---|---|---|
![]() | ![]() | ![]() |
| Reads still out, 400px, light (Checks already shows the published ✕) | Conversation, 400px, dark |
|---|---|
![]() | ![]() |
Evidence
pnpm check:affected: exit 0 (before the rebase onto main; after it, the touched desktop tests and the desktop typecheck pass again).pnpm exec vitest run apps/desktop/src/renderer/tasks/agent-pull-request-tab.test.ts apps/desktop/src/renderer/desktop/side-panel.test.ts: 16 passed.
New tests, each of which fails without this change:
- "opened on its files, as the Changes chip opens it, the header stands above them with where each reviewer stands, and switching between the tabs draws each as it was".
- "where nothing of the pull request is held and its reads fail, each part says so with its own Retry, and Files changed still reads on its own".
- "each tab counts what Luke holds of it: the conversation its comments, reviews, and threads; the checks how they went as published from the first frame, and how many once read; the files the lines published". This includes the arrow keys moving along the row.
The existing tab tests now choose the Checks tab before asserting on checks. The progressive test checks that the Checks skeleton appears only once that tab is shown.
Invariants
-
./scripts/check.shgreen. (CI) - JSON Schema goldens unchanged.
- Every behavior change has, in this PR, a test that fails without it. No change-detector test.
- Gateway envelope goldens unchanged.
- No new
astype assertion. - No
Effect.run*outside the edges. - No new raw async primitive.
- No discarded Effect.
- Test count: +1 renderer test (the old switch test is replaced by the header-over-files test, the all-failed test is rewritten, and the tab-counts test is new).
- No hand-rolled file replaced.
- No
AGENTS.mdsentence names the changed parts. -
PRIVACY.mdunchanged: the same reads, now started per tab. - Deps unchanged.
- Barrel rule unaffected.
Notes
- Three follow-up chains build on this branch: Conversation filters and reactions, Checks extras, and labels / reviewers / stack nav. So the header, the tab row and how the Conversation and Checks panes mount should stay stable.




