fix(panel,ui): move the Pull request header's ⋯ into the side panel's, one pill size
Merged
DA
6d ago+293+293+012345678901234567890123456789-130-130-012345678901234567890123456789
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;useCopyToastandCopyIconleave 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.handedanduseHandedTabActions(hand, actions)instrip-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.
- 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:
- One pill size. Open, Draft and Closed (each has a menu) were a 30px control-size
Chippill. Merged (no menu) was a ~20pxBadge. A survey of the app's status chips found theBadgebox the most common: 20px, 12px medium caption type, 8px side padding, fully rounded, 12px glyph. EveryBadge,PendingBadge, this header's labels and the compact chips use it, and nothing else stood at 30px. So the menu pill is nowChipPILL+CHIP_SIZE.COMPACT, and@luke/uidraws 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.
| Light | Dark | |
|---|---|---|
| Before | ![]() | ![]() |
| After | ![]() | ![]() |
Header in the side panel. Before: the ⋯ ends the title row. After: the merge control ends it, level with Review.
| Light | Dark | |
|---|---|---|
| Before | ![]() | ![]() |
| After, open | ![]() | ![]() |
| After, merged | ![]() | ![]() |
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 ⋯, light | After, topbar ⋯, 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 --noEmitfor@luke/ui,@luke/panel,@luke/catalog,@luke/desktop,@luke/web: passvitest run --project panel: 253 files, 2638 tests passvitest run packages/ui/src/components/{chip,badge}: passpnpm knip: clean./scripts/repository-checks.sh: passcd 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.shgreen. (CI) - JSON Schema goldens unchanged (
LUKE_UPDATE_FIXTURESnot 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
astype assertion outside the allowlisted files. (CI via anti-slop) - No
Effect.runPromise/runSync/runForkoutside the runtime edges and the shimstools/anti-slop/effect-edges.jsonrecords. (CI viaanti-slop/no-run-promise-outside-edges) - No new
setTimeout/setInterval/new Promise/AbortController/fs.watchoutside the filestools/anti-slop/effect-edges.jsonrecords. (CI viaanti-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.mdsentence naming a changed part is edited; root pair stays byte-identical. (None names these parts.) -
PRIVACY.mdunchanged, or the change is a product decision called out in the PR body. - Package
package.jsondeps match what the sources import by bare specifier;effectviacatalog:. (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
DesktopTasksand side panel, with fixture reads, over the builtstyles.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












