feat(panel): compact pull request header with the merge control in the title row

Merged
DA
6d ago
+937-510

Summary

The Pull request tab's fixed header took too much vertical space: a top bar (pill, Review, GitHub, ⋯), then the title, branches, people, and a full-width merge banner, then the tabs. This is option A from the "PR header: mockups" page, with Review moved into the tabs' row as the developer asked.

  • Row 1, the title row: the state pill (its menu kept: Ready/Draft, Close/Reopen) · the title and #number (inline edit and the pencil kept) · at the row's end, the merge control and ⋯.

  • Row 2: branches and stack, then opened by, time, reviewers and their picker, the label picker, and labels, all on one line that wraps.

  • Row 3: Conversation · Checks · Files changed, with Review (its popover, pending count, and Discard all kept) at the row's end, shown only while the PR is open or a draft.

  • PullRequestBar is removed. Open on GitHub is now the first item in ⋯. ⌘-click on the title opens GitHub too, and a tooltip on the number says so. A plain click still selects text, and the pencil still edits.

  • The merge banner is now one merge control (merge-box.tsx's MergeControl). It's a button labelled with what can happen now and coloured by state:

    • Merge (primary)
    • Auto-merge (outline)
    • Merge when ready (primary)
    • Queued #N and Auto-merge on (working tint)
    • Conflicts (danger tint)
    • Review required, Checks failing, Changes requested, Draft, and the rarer Checks running, Out of date, Checking (outline)

    It opens an anchored popover, the same pattern as Review's panel. The popover holds:

    • GitHub's sentence as its title (the blockedWords and mergeability words), with the mergeability under it where it differs (for example "#3 in the merge queue" over "Ready to merge")
    • the method choice as radios, still kept per repository
    • the one press: Merge with its confirm, Enable/Disable auto-merge, Merge when ready, or Remove from queue

    This reuses mergeActionOf and the existing writes; there's no new behaviour. A press closes the popover. While the write is out the control shows busy, and a refusal or sign-in-required appears in PullRequestWriteNote above the header, as before.

  • Narrow panels: the title wraps beside the controls. Below a 520px header, the merge control shows its glyph and a short label (#3, Auto, When ready, Review, …), and its accessible name stays the full label. Below a 650px side panel, Review shows its glyph and count, with the word in its tooltip and name, so the tabs keep their names a little longer. Below about 600px the tabs still fold to glyphs on their own (ChoiceBar's existing rule), so the 600px shots show that.

  • Edges follow --column-inset / --reading-room as the header already did. The tabs' band now carries that padding, so Review lines up with ⋯ above it.

Header height in the harness at 900px: about 118px, down from about 236px.

Evidence

  • pnpm check:affected ran out of memory in this sandbox: @luke/panel#typecheck was Killed, and one unrelated test timed out under load. So I ran the per-package checks instead:
    • tsc -p packages/panel: clean
    • pnpm exec vitest run packages/panel/src/tasks/agent-pull-request-tab.test.ts: 80 passed
    • the full panel vitest suite inside check:affected: 2408 passed, 1 timed out (move-task-dialog.test.ts, which passes alone, 7/7)
    • ./scripts/oxlint.sh packages/panel/src/tasks/: 0 warnings, 0 errors
    • biome check packages/panel/src: no errors
    • pnpm knip: clean
    • ./scripts/repository-checks.sh: passed
    • cd apps/desktop && node scripts/build.mjs: bundle budget passed
  • Screenshots follow in a comment.

Invariants

  • ./scripts/check.sh green. (CI)
  • JSON Schema goldens unchanged. (CI)
  • Every behavior change has, in this PR, the test that fails without it; no change-detector test added. New tests:
    • each merge state's label, short label, look, and panel contents
    • the panel closing on Escape and on a press outside, with focus returned
    • the busy control and a refusal shown in GitHub's words
    • Review in the tabs' row only while open or a draft
    • Open on GitHub as ⋯'s first item
    • ⌘-click on the title
  • 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 raw async primitives. (CI)
  • No dropped Effect statements. (CI)
  • Test count: 74 → 79 test(...) cases in agent-pull-request-tab.test.ts (+5 above; the merge and Review tests were rewritten for the new header).
  • No hand-rolled file replaced.
  • No CLAUDE.md/AGENTS.md sentence names the changed parts.
  • PRIVACY.md unchanged.
  • Package deps unchanged.
  • Barrel/door rule untouched.

Notes

  • Desktop/panel only, so no Preview run is needed.
  • Blockers or follow-up verification: none. A stacked follow-up (the Files changed tab's "lines left to review") will build on this.

Made with Luke