Files
kandev/docs/plans/github-pr-merge-queue/plan.md
T
2026-08-24 11:09:03 +00:00

203 lines
10 KiB
Markdown

---
spec: docs/specs/integrations/requirements/github-pr-merge-queue.md
created: 2026-08-17
status: complete
---
# Implementation Plan: GitHub PR Merge Queue
## Overview
Extend the existing GitHub merge boundary to use GitHub's asynchronous,
queue-aware merge request and return a typed merged-or-queued outcome. Observe
the resulting merge-queue entry through the existing GraphQL status sync,
persist its state, one-based position, and optional estimated duration, and
project it through the existing task and PR status surfaces. Reuse the typed
outcome and the persisted status in the existing PR merge button across the
desktop detail, compact status, and mobile Review surfaces, with targeted unit
and E2E proof.
## Backend
### Provider client contract
- Update `apps/backend/internal/github/client.go` so the merge operation returns
a typed outcome rather than only an error.
- Update `apps/backend/internal/github/gh_client.go` and
`apps/backend/internal/github/pat_client.go` to call
`PUT /repos/:owner/:repo/pulls/:number/merge-async` with
`merge_action=default`, preserve the selected `merge_method`, and normalize
GitHub's accepted, already-queued, and already-merged response shapes.
- Update `apps/backend/internal/github/mock_client.go` and
`apps/backend/internal/github/noop_client.go` for the same contract so local
and E2E behavior remains deterministic.
### Service and HTTP response
- Update `apps/backend/internal/github/service_pr.go` to propagate the typed
outcome while retaining workspace scope checks, personal-write credential
routing, merge-method fallback, and cache invalidation.
- Update `apps/backend/internal/github/controller.go` to return
`{"status":"merged"}` or `{"status":"queued"}` and retain the existing
operational-auth and provider-error mapping.
### Queue observation, persistence, and bounded projection
- Extend the batched GraphQL pull-request selection and normalized `PRStatus`
with `mergeQueueEntry { state position estimatedTimeToMerge }`. A populated
GraphQL result replaces all three queue fields atomically; queue-unaware REST
and `gh pr view` reads preserve the last complete observation, while an
authoritative null or terminal lifecycle state clears it.
- Add the queue state, one-based position, and optional estimate in seconds to
every `TaskPR` schema, migration, write, restore, and update path. Extend
the mock controller so integrated tests can seed the same contract.
- Carry queue membership through live and restart-built task status summaries
as the bounded `queued` state. Terminal states precede queue membership;
queue membership precedes other non-terminal states for one PR, while the
existing aggregate ranking keeps a failing sibling above a queued sibling.
## Frontend
### Queue-aware merge action
- Update `apps/web/lib/api/domains/github-pr-api.ts` and the re-exporting API
module to type the merged-or-queued response.
- Update `apps/web/components/github/pr-task-icon.tsx` with a tested eligibility
predicate that permits a queue-required branch only after explicit successful
checks and satisfied reviews, while continuing to reject drafts, conflicts,
behind branches, changes requested, and incomplete review/check gates.
- Update `apps/web/components/github/pr-merge-button.tsx` to render for direct or
queued eligibility, show the outcome-specific notification, suppress repeat
submission after acceptance, and refresh the PR state.
- Add the new action and outcome copy to all locale catalogs under
`apps/web/src/locales/*/github.json`; generate the Traditional Chinese pair
through the repository script rather than translating them independently.
### Queue status color and surfaces
- Add the normalized queue fields to the web `TaskPR` contract and the shared
queued semantic color `text-[#966600]`. Terminal colors remain first; an
active queue entry is second, so hydrated failure, draft, dirty, or behind
fields cannot overwrite queue presentation. Multi-PR aggregation still ranks
a failing sibling above a queued sibling.
- Extend the task hover summary, compact desktop popover, phone status drawer,
and PR detail panel with translated queue state, one-based position, and an
optional estimate rounded up to localized whole minutes. Missing estimates
omit only the estimate line. Recognized provider states retain their labels;
future non-empty states use generic `Queued` copy.
- Keep queue derivation shared across desktop and mobile. Mobile display uses
the existing drawer and Review destination; the mobile merge-action test
separately verifies the existing 44px action target.
### Mobile design contract
- Desktop outcome: the existing PR detail header and compact status surface
expose one merge action whose result says merged or queued.
- Mobile entry point: the task's existing Review bottom-navigation destination.
- Nearest shipped exemplar: the existing full-height mobile Review surface and
`PRMergeButton`; retain its single internal scroll owner and shared PR state.
- Information hierarchy: PR status and blockers remain above the primary merge
action; the action is the terminal operation for an eligible PR.
- Presentation: retain direct navigation to the full-height Review surface
because PR review is dense primary content, not a temporary picker.
- Geometry: keep the existing dynamic-height/safe-area behavior, avoid document
horizontal overflow, and preserve a touch target at least 44px high on phone.
- Shared logic: eligibility, mutation, result handling, and refresh remain
shared; only the existing responsive composition differs.
## Tests
- **What:** PAT and `gh` clients send `merge-async`, `merge_action=default`, and
the chosen merge method, and normalize direct, queued, and idempotent results.
**Files:** `apps/backend/internal/github/pat_client_writes_test.go`,
`apps/backend/internal/github/gh_client_commands_test.go`. **How:** focused
HTTP/command capture tests with representative GitHub responses.
- **What:** service preserves method resolution and cache invalidation while
returning the provider outcome. **File:**
`apps/backend/internal/github/service_pr_test.go`. **How:** table-driven unit
tests with the stub client.
- **What:** HTTP validation, outcome JSON, auth routing, and provider errors.
**File:** `apps/backend/internal/github/controller_test.go`. **How:** handler
tests for merged, queued, malformed method, and rejection paths.
- **What:** frontend API response typing and queue eligibility boundaries.
**Files:** `apps/web/lib/api/domains/github-api.test.ts`,
`apps/web/components/github/pr-task-icon.test.ts`. **How:** request capture and
table-driven predicate tests.
- **What:** button notifications, accepted-state suppression, retry behavior,
and refresh callback. **File:**
`apps/web/components/github/pr-merge-button.test.tsx`. **How:** component tests
with mocked API outcomes.
- **What:** queue observation, persistence, queue-unaware read preservation,
terminal clearing, and bounded live/restart projection. **Files:**
`apps/backend/internal/github/*merge_queue_status_test.go`,
`apps/backend/internal/github/store_taskpr_schema_drift_test.go`,
`apps/backend/internal/task/statussummary/merge_queue_status_test.go`, and
`apps/backend/internal/backendapp/status_summary_adapter_test.go`. **How:**
focused GraphQL, real-store, and projection tests.
- **What:** queue color precedence, failing-sibling aggregation, translated
queue metadata, estimate boundaries, terminal queue clearing, and generic
fallback copy. **Files:**
`apps/web/components/github/pr-task-icon.test.ts`,
`pr-task-status-summary.test.ts`, `pr-status-chip.test.tsx`,
`pr-merge-queue-status.test.tsx`, `pr-mergeability-row.test.tsx`, and
`pr-detail-panel.test.tsx`. **How:** pure formatter and focused component
tests for terminal, queued, future-state, missing-estimate, sub-minute, and
minute-boundary cases.
## E2E Tests
- **Scenario:** an eligible queue-required merge is accepted. **File:**
`apps/web/e2e/tests/pr/pr-merge-queue.spec.ts`, action test. **What to
verify:** the desktop PR surface exposes `Merge PR`, the merge request
returns `{"status":"queued"}`, the success notification appears, and the
accepted state suppresses the duplicate action.
- **Scenario:** an already queued PR exposes hydrated queue metadata. **File:**
`apps/web/e2e/tests/pr/pr-merge-queue.spec.ts`, display test. **What to
verify:** task icon color, task hover summary, compact popover, and detail
notice show queued state, position, and estimated duration.
- **Scenario:** the same accepted action is touch-usable. **File:**
`apps/web/e2e/tests/pr/mobile-pr-merge-queue.spec.ts`, action test. **What to
verify:** Review navigation, touch activation, queued notification, a
minimum 44px action target, and no document horizontal overflow.
- **Scenario:** an already queued PR is readable on a phone. **File:**
`apps/web/e2e/tests/pr/mobile-pr-merge-queue.spec.ts`, display test. **What to
verify:** the existing PR status drawer and full-height Review surface show
queued state, position, and estimate without hover or horizontal overflow.
## Verification Results
Recorded after the remediation run:
- Backend: `cd apps/backend && go test -tags fts5
./internal/github/... ./internal/task/statussummary/...
./internal/backendapp` passed 2,193 tests across 3 packages, including the
queue precedence regression.
- Frontend: the six-file queue-status command passed 160 tests. `pnpm run
typecheck` and `pnpm run lint` passed. `pnpm run i18n:check` passed with
7,223 referenced keys, 8,779 English entries, 48 orphans, and all five
required catalogs complete.
- Desktop E2E: the fresh-build `cd apps/web && pnpm e2e:run
tests/pr/pr-merge-queue.spec.ts` passed 2 tests in 12.2 seconds. Mobile E2E:
`pnpm e2e:run --no-build --project mobile-chrome
tests/pr/mobile-pr-merge-queue.spec.ts` passed 2 tests in 9.6 seconds.
## Implementation Waves And Parallel Candidates
Wave 1:
- [x] [task-01-backend-queue-aware-merge](task-01-backend-queue-aware-merge.md)
Wave 2:
- [x] [task-02-frontend-merge-outcomes](task-02-frontend-merge-outcomes.md)
Wave 3:
- [x] [task-03-desktop-mobile-e2e](task-03-desktop-mobile-e2e.md)
The detailed queue-observation task package under
`docs/plans/github-pr-merge-queue-status/` is the synchronized execution record
for the persistence, projection, queue color, metadata, and responsive display
work described above. Its E2E task and this parent plan intentionally record
the same two desktop and two mobile scenarios.
Execution is sequential in the primary conversation unless the user explicitly
authorizes subagents.