10 KiB
spec, created, status
| spec | created | status |
|---|---|---|
| docs/specs/pr-only-commit-details/spec.md | 2026-08-04 | implemented |
Implementation Plan: Repair PR-only commit details
Overview
Make commit provenance a first-class part of the Changes panel. Preserve the local session path for SHAs known to the worktree, but route PR-only SHAs to a new workspace-authorized GitHub individual-commit action. Hide unmeasured row statistics and local mutation controls for remote commits, then cover the force-pushed/stale-worktree case on desktop and mobile.
Confirmed root cause
For task 8531031a-9a98-439f-ab0a-c25b480158dc, the stored session worktree
was created at local head 5c2873d, while linked PR #2253 was later
force-pushed to six different commits ending at df3c05e.
mergeCommitsinapps/web/components/task/changes-panel-helpers.tsappends PR-only SHAs to the local commit feed without preserving their source.- GitHub's pull request commit-list response does not include commit stats.
convertRawPRCommitsleaves Go integer fields at zero, so the frontend presents unknown values as+0 -0. - Every row click is reduced to a SHA and repository name, then sent through
session.commit_diff. The backend runsgit showin the task worktree even for PR-only SHAs. - The shared row also exposes local reset/revert/amend callbacks for PR-only
commits, while
CommitDetailPanelresolves metadata only from local session commits.
The repair invariant is: row statistics, detail data, metadata, and available actions must all derive from the same explicit commit source.
Backend
Workspace-scoped GitHub commit detail
- Add a
github.pr_commit.getWebSocket action inapps/backend/pkg/websocket/actions.goand register it inapps/backend/internal/github/handlers.go. - Add
StatsAvailable boolwith JSON fieldstats_availabletoPRCommitInfo. DefinePRCommitDetailinapps/backend/internal/github/models.gowith SHA, message, author login/name/date, exact additions/deletions, files changed, and the merged[]PRFilereturned by the individual GitHub commit endpoint. - Extend the GitHub client interface and the
gh, PAT, noop, and mock clients with an exact-SHA commit-detail method. The CLI client should callgh api repos/{owner}/{repo}/commits/{sha}; the PAT client should call the equivalent REST path. Parse both through one helper so their response semantics stay aligned. - Request the maximum supported page size and merge every returned file page
for both clients (
gh api --paginate --slurpfor the CLI representation and Link-header pagination for PAT). Preserve the first page's metadata/stats, append file records in provider order without duplication, and test a multi-page response. - Add a service method that reuses
ensureRepositoryInWorkspaceScopeand the existing personal read-client resolution. Validate that the SHA is a safe exact object ID before interpolating it into either client path. - Do not fetch, persist, or import the commit into the task worktree.
Unknown list statistics
- Use
PRCommitInfo.stats_availableto explicitly represent whether additions, deletions, and files-changed values are available. The normal GitHub list parser must set it to false rather than relying on zero values. - Keep exact integers mandatory on
PRCommitDetail, where the individual endpoint provides them. This lets a genuinely empty commit remain distinguishable from an unmeasured row.
Frontend
Source-aware commit targets
- Introduce a discriminated commit-detail target: local targets contain the existing SHA/repository routing data; GitHub targets additionally contain workspace, owner, and repository identity.
- Make
mergeCommitsreturn that target plus a statistics-availability flag. A SHA found in both feeds remains one local row and retains its pushed state. A PR-only SHA becomes a GitHub target and does not acquire zero statistics. - Thread the full target through the Changes panel callbacks, desktop dockview panel params/actions, and mobile diff target. Include source and repository identity in dockview panel identity so equal SHAs from different repositories cannot collide.
Unified detail loading and rendering
- Add a source-aware commit-detail hook/request layer. Local targets continue
to use
session.commit_diff; GitHub targets callgithub.pr_commit.getand mapPRFilerecords into the current diff-view model. - Have
CommitDetailPanelobtain metadata and files from that unified result, fixing the missing PR-only header as well as the patch source. - For GitHub targets, disable worktree-dependent file actions and local context expansion. Preserve the current rendering for available, binary, empty, or truncated GitHub patches; never retry through the local git path.
- Gate reset/revert/amend row actions on local provenance. Render additions and deletions only when the row marks them as measured.
Desktop and mobile behavior
The visual composition does not change. Desktop continues to open the existing
dockview commit panel. Mobile continues to use
MobileChangesPanel -> MobileDiffSheet, the current full-height drawer and
closest native-mobile exemplar. Both surfaces consume the same discriminated
target and detail hook. The mobile drawer keeps its existing single scroll
owner, close affordance, and touch behavior; only its data source and local
action eligibility change.
Tests
- Backend parser tests prove list statistics are unavailable and individual commit responses preserve exact metadata, stats, file status, and patch.
ghand PAT client tests prove the exact repository/SHA path and shared response parsing.- Handler/service tests prove workspace/repository authorization, exact-SHA validation, response routing, and error propagation.
- Frontend merge tests prove PR-only provenance, hidden unknown stats, multi-repository identity, and local precedence for a SHA found in both feeds.
- Request/hook tests prove mutually exclusive local and GitHub actions and no local fallback after a GitHub error.
- Panel/row tests prove remote metadata is visible and reset/revert/amend, worktree-file actions, and local expansion are absent for GitHub targets.
- Dockview and mobile target tests prove source/repository identity survives serialization and selection.
All production changes follow Red-Green-Refactor: add each focused regression, record the expected failure against current behavior, add the smallest source change, then rerun the focused and package-level checks.
E2E tests
Extend apps/web/e2e/tests/git/git-changes-panel.spec.ts with a linked PR whose
mocked current commits are absent from the stale local worktree. Assert that the
row omits false +0 -0, its local mutation menu is absent, and opening it shows
the GitHub-specific commit header and patch marker. The scenario must fail if
the UI invokes session.commit_diff for that SHA.
Extend apps/web/e2e/tests/task/mobile-changes-panel.spec.ts with the same
PR-only source. Assert that tapping the row opens the existing full-height
commit sheet, renders the remote marker patch, exposes no local action, closes
normally, and introduces no horizontal overflow. Add deterministic mock GitHub
individual-commit seeding to the existing mock controller/API helper rather
than depending on live GitHub.
Implementation waves and parallel candidates
Wave 1 (sequential):
- Task 01: GitHub commit detail contract — add the authenticated individual-commit backend contract and distinguish unknown list statistics.
Wave 2 (depends on Wave 1):
- Task 02: Source-aware commit UI — route local and GitHub targets correctly across desktop and mobile.
Wave 3 (depends on Waves 1-2):
- Task 03: Commit-source E2E coverage — exercise stale local history against deterministic remote commit details.
The tasks are intentionally sequential. Task 02 consumes the backend wire contract from Task 01, and Task 03 validates the integrated behavior and adds mock support for that settled contract.
Verification
Run the focused commands recorded in each task, then finish with:
cd apps/backend && go test ./internal/github
cd apps/web && pnpm run typecheck
cd apps && pnpm --filter @kandev/web lint
cd apps/web && pnpm run i18n:check && pnpm run i18n:ratchet
cd apps/web && pnpm e2e:run tests/git/git-changes-panel.spec.ts -- --grep "PR-only commit"
cd apps/web && pnpm e2e:run --project mobile-chrome tests/task/mobile-changes-panel.spec.ts -- --grep "PR-only commit"
git diff --check
Verification results
- Backend:
go test ./internal/githubpassed (1081 tests). - Frontend: focused source/target suite passed (7 files, 72 tests), followed by
pnpm run typecheck, web lint,i18n:check, andi18n:ratchet. - E2E: the desktop PR-only scenario passed once; the mobile scenario passed once after narrowing an assertion to the opened sheet. Both scenarios cover stale local history, hidden unknown statistics/actions, remote metadata, and remote patch rendering.
git diff --checkpassed.- Review remediation: complete PR identity now gates visible commit-list results; unavailable or malformed detail responses show a persistent retry state; and remote detail loading is independent of local agent readiness. The broader focused frontend suite passed (8 files, 69 tests), plus the remediation request/hook/panel tests (4 files, 15 tests), typecheck, lint, i18n checks, and ratchet.
- Review follow-up after rebasing onto
main: root repository identity is now matched exactly, invalid/null commit payloads are rejected at both backend and frontend boundaries, target switches cannot expose prior detail state, and serialized GitHub targets require complete identity. Backend GitHub tests passed (1,086); focused frontend tests passed (8 files, 73 tests), with typecheck, lint, i18n checks, ratchet, and diff checks green.
Risks and out of scope
- Individual GitHub commit requests add a lazy network round trip and can be rate-limited. The existing loading/error treatment must remain responsive, and no eager N+1 list loading is allowed.
- GitHub can omit patches for binary or oversized changes. That state must be presented honestly and must not trigger a local-data fallback.
- The source target becomes part of desktop panel identity and mobile selection state. Tests must prevent stale serialized targets or cross-repository SHA collisions.
- No database migration, public documentation change, worktree synchronization, or non-GitHub provider is part of this repair.