fix(panel,ui): move the Pull request header's ⋯ into the side panel's, one pill size

Merged
DA
6d ago
+293-130

Summary

Two changes to the Pull request tab's header, as asked: move the header's ⋯ into the side panel's topbar ⋯ (keeping Refresh and Open on GitHub, dropping the copy of the GitHub link), and draw the state pill at one size in every state, a size the rest of the app already uses.

  • Topbar ⋯. The header's ⋯ (PullRequestMenu) is gone, and the merge control now ends the title row, at the same right edge as the tab row's Review. While the Pull request tab is shown, the side panel's topbar ⋯ offers Open on GitHub (GitHub mark) and Refresh (rotate glyph, dimmed while a read is out, as before) first, then the existing Copy link (the tab's Luke link) in its own group. The header's "Copy link", which copied the GitHub address, is dropped; useCopyToast and CopyIcon leave the tab with it.
    • The topbar ⋯ had no way for a tab's body to add items, so this adds the smallest one that fits the strip's pattern: TabDoors.handed and useHandedTabActions(hand, actions) in strip-tabs.tsx. A tab's body hands its actions up while it's drawn, and they're dropped when it unmounts. The side panel keeps them under the tab that handed them, and only that tab's toolbar ⋯ offers them. The right-click on a tab is unchanged.
  • One pill size. Open, Draft and Closed (each has a menu) were a 30px control-size Chip pill. Merged (no menu) was a ~20px Badge. A survey of the app's status chips found the Badge box the most common: 20px, 12px medium caption type, 8px side padding, fully rounded, 12px glyph. Every Badge, PendingBadge, this header's labels and the compact chips use it, and nothing else stood at 30px. So the menu pill is now Chip PILL + CHIP_SIZE.COMPACT, and @luke/ui draws a compact pill in exactly a Badge's box: same height, side padding, weight, glyph, tint with no hairline, and the badge's ink mix for caption contrast. It keeps its chevron, its deeper tint on hover, press and open, and the focus ring. No other caller used a compact pill. The catalog shows the compact tones beside a badge.

Screenshots

State pills. Before: Open, Draft and Closed are 30px menu pills and Merged is a 20px badge. After: all four use one 20px box, and the menu pills keep their chevron.

LightDark
Beforepills before, lightpills before, dark
Afterpills after, lightpills after, dark

Header in the side panel. Before: the ⋯ ends the title row. After: the merge control ends it, level with Review.

LightDark
Beforeheader before, lightheader before, dark
After, openheader after, lightheader after, dark
After, mergedmerged after, lightmerged after, dark

Menus. Before: the header's ⋯ held Open on GitHub, Refresh pull request and Copy link (GitHub). After: the topbar ⋯ holds Open on GitHub and Refresh, then Copy link (Luke).

Before, header ⋯After, topbar ⋯, lightAfter, topbar ⋯, dark
header menu beforetopbar menu after, lighttopbar menu after, dark

Evidence

  • pnpm check:affected: lint, oxlint and discarded-effect pass. Typecheck and test hit the sandbox's memory limit (desktop tsc was killed with 137, and vitest workers timed out), so I ran them per package:
    • tsc --noEmit for @luke/ui, @luke/panel, @luke/catalog, @luke/desktop, @luke/web: pass
    • vitest run --project panel: 253 files, 2638 tests pass
    • vitest run packages/ui/src/components/{chip,badge}: pass
    • pnpm knip: clean
    • ./scripts/repository-checks.sh: pass
    • cd apps/desktop && node scripts/build.mjs: builds within the bundle budget
  • Tests:
    • tab-actions.test.ts: the PR tab's topbar ⋯ offers Open on GitHub, Refresh, Copy link, in that order, and Open on GitHub opens the pull request. The worker, page and Plan tabs offer neither item.
    • agent-pull-request-tab.test.ts: the header holds no ⋯; Refresh is dimmed while a read is out; nothing is offered before the pull request is held. The existing Refresh tests now press it from the handed actions.
    • Test count: +3 (1 in tab-actions, 2 in the PR tab).

Invariants

  • ./scripts/check.sh green. (CI)
  • JSON Schema goldens unchanged (LUKE_UPDATE_FIXTURES not run, or diff explained line by line). (CI)
  • Every behavior change has, in this PR, the test that fails without it; no change-detector test added. (manual; root AGENTS.md "Testing")
  • 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 and the shims tools/anti-slop/effect-edges.json records. (CI via anti-slop/no-run-promise-outside-edges)
  • No new setTimeout/setInterval/new Promise/AbortController/fs.watch outside the files tools/anti-slop/effect-edges.json records. (CI via anti-slop/no-raw-async-primitives)
  • No Effect, Stream, or Layer written as a statement and dropped; a description a caller walks past runs nothing. (CI via pnpm discarded-effect)
  • Test count equals the pre-PR count or the delta is listed. (+3, above)
  • Each hand-rolled file the PR replaces is deleted, or the part that remains is a permanent adaptor named in root AGENTS.md's "The permanent adaptors" with the reason it stays.
  • Every CLAUDE.md/AGENTS.md sentence naming a changed part is edited; root pair stays byte-identical. (None names these parts.)
  • PRIVACY.md unchanged, or the change is a product decision called out in the PR body.
  • Package package.json deps match what the sources import by bare specifier; effect via catalog:. (CI)
  • Barrel/door rule: no Node-reaching Effect module (@effect/platform-node, effect/sql) behind a barrel the renderer or a web function opens. (CI)

Notes

  • Desktop and panel only, so no Preview run is needed.
  • The screenshots come from a harness that mounts the real DesktopTasks and side panel, with fixture reads, over the built styles.css. It doesn't draw the shell's own panel toggle, so the topbar's right end is empty in the pictures. In the app, that toggle stands there, level with the merge control and Review.

Made with Luke