feat(desktop): stacked merge, merge queue, and restack for pull requests
Summary
- Merge a whole stack in one step. The merge control reads "Merge stack" with the number of pull requests it lands, and the confirmation lists each one.
- Merge queue support. Add a pull request or a stack to the base branch's merge queue, see its position, and remove it again.
- A stack navigator. The stack chip ("2/4") opens a popover with one verdict, the reason when a merge is blocked, a status for each layer, and two actions: create a GitHub stack from a chain of base branches, and restack when the branch still carries a layer that merged below it.
- Stack reads and merges act as the viewer, through the same shared library the other pull request actions use.
Why / Context
A pull request in a stack cannot be reasoned about alone. A merge has to land every open layer below it, a blocker three layers down blocks the top, and after a lower layer merges the branches above it are stale. The app showed none of this, and it had no way to add to a merge queue at all: on a repository that requires one, the merge button failed.
How It Works
Shared library (packages/shared)
pull-request-stackreads a pull request's stack. It uses GitHub's own stack when there is one. Otherwise it infers a chain from base branches. Layers that already merged are found from the pull request's base-change history, and a restack is flagged by comparing commits on GitHub, so nothing here needs a local checkout.pull-request-async-mergewraps GitHub's background merge (PUT .../pulls/{n}/merge-async), which lands a stack as one operation and can join a merge queue. The request returns an id, and the client polls for the result.
Gateways. Five new viewer procedures, on both the desktop main process (viewerGithub) and the API (integration.github): getPullRequestStack, requestPullRequestMerge, getPullRequestMergeResult, createPullRequestStack, enqueuePullRequest. The detail read gains one optional output field, mergeability.queueEnabled.
Renderer. One hook, usePullRequestMergeFlow, holds the merge state for a pull request: alone, a stack, or a merge queue, what blocks it, and the actions. Four surfaces use it: the pull request page, the workspace pull request pane, the changes control, and the workspace activity menu. The decision logic is a pure function, runPullRequestMerge:
- For a stack, read it again from GitHub. If the pull requests that would land differ from what the person confirmed, stop.
- Ask for the background merge, pinned to the head commit that read saw.
- If GitHub has no background merge here: one pull request merges or joins the queue directly, and a stack of several is refused with the reason.
Compatibility
- Every tRPC change is additive: new procedures and one new optional output field. No existing procedure, input, or output changed.
- A desktop build with this change against an API without it degrades instead of failing. The stack read answers "no stack", so no chip shows. The background merge answers "unavailable", so a single pull request merges the way it does today. The merge queue items show only when the API reports a queue.
- The host-service
getStackandmergeStackprocedures removed here existed only on this branch. They are not on main and no released client calls them.
Manual QA Checklist
Done in the dev app, against real GitHub for reads:
- Stack read on a real open chain of three pull requests in this repository: correct order, the merged bottom layer is found, the review-required layer is named as the blocker
- Chip, navigator, merge control, merge menu, and confirmation in each state, with the state set in the query cache: ready, conflicts, review required, checks failing, merge queue, queued, restack, base-branch chain, bottom of a stack, single pull request with a merge queue
- Workspace activity menu and workspace pull request pane show the same stack state and open the same confirmation
- Confirm path, run for real on an already merged pull request with a ready stack set in the cache: the flow re-read the stack, saw it differ, and refused with "The stack changed while this was open." No merge request was sent
- GitHub's background merge and stacks endpoints exist for this repository (read-only probes)
- The exact HTTP requests for every write, recorded from the real code with a real Octokit and not sent: merge request, stack creation, enqueue, dequeue. The 202, 409, 400, and 404 answers are handled
- Enqueue and dequeue mutation inputs match GitHub's GraphQL schema
Not done, because this repository has no GitHub stack and no merge queue, and no real pull request was merged for testing:
- A real stack merge that GitHub completes
- A real merge queue join and leave
- A real "Create stack"
- The Restack handoff to an agent (the dev host had no agent configured; the warning itself was checked)
Testing
tsc --noEmitinpackages/shared,packages/trpc,packages/host-service,apps/desktopbun teston the pull request suites: desktop (199), shared (36), trpc (6), host-service (75)bun run check:i18n(38 new strings, translated in all 17 locales)biome checkon the changed files
Design Decisions
- The merge runs on GitHub, and the client polls. The API is serverless, so one long request that waits for a stack merge would time out.
- The stack is read again right before the merge. The confirmation lists what lands; if that changed, the merge must not run.
- A chain of base branches is not merged layer by layer. A sequence of separate merges can stop halfway. The navigator offers "Create stack" instead, and GitHub then merges it as one operation.
- Restack goes to an agent. A restack is a rebase with possible conflicts, which needs a checkout. The warning shows on every host; the button shows when the host has an agent.
- The bottom pull request of a stack reads as a plain "Merge". It lands one pull request, so the control says so, but it still goes through the background merge.
Known Limitations
- The write path has not run against GitHub. See the unchecked items above.
- After a repository rename, the workspace header and the pull request pane can hold separate cache entries for the same stack, because the host still reports the old repository name. Both read the same data from GitHub.
- A native stack larger than 100 layers is reported as unreadable, not paged.
Follow-ups
- Auto-merge ("merge when ready") for a single pull request.
- A test repository with stacks and a merge queue, to run the unchecked items.
Risks / Rollout
- Risk: the merge control on a pull request with no stack and no merge queue is unchanged. A pull request in a stack or on a queue-protected branch now merges through GitHub's background merge.
- Rollback: revert this PR. There is no schema or data change.
Summary by cubic
Adds full stack and merge queue support to pull requests in the desktop app: a stacked PR shows its position in a navigator, merges the whole stack as one operation, can join the base branch's merge queue, and offers restack when a lower layer merges.
- The shared library reads a PR's stack from GitHub (native or inferred from base branches) and wraps GitHub's background merge API, which lands a stack and can enter a merge queue.
- Five new viewer tRPC procedures (
getPullRequestStack,requestPullRequestMerge,getPullRequestMergeResult,createPullRequestStack,enqueuePullRequest) act as the viewer; the detail read gains one optionalqueueEnabledfield. - The merge flow is shared across the PR page, the workspace PR pane, the changes control, and the workspace activity menu. The stack is re-read before merging, and a changed stack refuses the merge.
- A chain of base branches is offered "Create stack" instead of merging layer by layer. Restack is handed to an agent when the host has one.
- Without background merge, a single PR still merges directly; a stack of several is refused with the reason.
- Picking a layer checks out its branch in this workspace; the host refuses a dirty tree or a branch another worktree holds, and the toast then offers the PR view. The switched-to layer shows in the chip at once while its status loads.
- The activity menu nests every other layer of the stack under the workspace's PR.
Compatibility
- All tRPC changes are additive; an older API degrades gracefully (no stack chip, direct merge for a single PR).
- The merge queue UI shows only when the API reports a queue.
- The removed
getStack/mergeStackhost-service procedures existed only on this feature branch and are not on main; the new hostcheckout-branchprocedure is additive.
Summary by CodeRabbit
- New Features
- View pull-request stacks, their readiness, blockers, queue status, and individual layers from pull-request pages and workspaces.
- Create stacks and merge pull requests or stacks with confirmation, or add and remove pull requests from GitHub’s merge queue.
- Navigate between stack layers, restack after a parent merge, and refresh stack details when they cannot be loaded.
- Workspace stack navigation can switch to a layer’s branch or open its pull request in the app.
- Added localized interface text for stack and merge-queue workflows.
- Bug Fixes
- Failed background merges now surface GitHub’s conflict details.