Files
kandev/docs/plans/pr-only-commit-details/plan.md
T
2026-08-05 20:45:59 +01:00

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.

  • mergeCommits in apps/web/components/task/changes-panel-helpers.ts appends PR-only SHAs to the local commit feed without preserving their source.
  • GitHub's pull request commit-list response does not include commit stats. convertRawPRCommits leaves 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 runs git show in the task worktree even for PR-only SHAs.
  • The shared row also exposes local reset/revert/amend callbacks for PR-only commits, while CommitDetailPanel resolves 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.get WebSocket action in apps/backend/pkg/websocket/actions.go and register it in apps/backend/internal/github/handlers.go.
  • Add StatsAvailable bool with JSON field stats_available to PRCommitInfo. Define PRCommitDetail in apps/backend/internal/github/models.go with SHA, message, author login/name/date, exact additions/deletions, files changed, and the merged []PRFile returned 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 call gh 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 --slurp for 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 ensureRepositoryInWorkspaceScope and 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_available to 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 mergeCommits return 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 call github.pr_commit.get and map PRFile records into the current diff-view model.
  • Have CommitDetailPanel obtain 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.
  • gh and 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):

Wave 2 (depends on Wave 1):

Wave 3 (depends on Waves 1-2):

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/github passed (1081 tests).
  • Frontend: focused source/target suite passed (7 files, 72 tests), followed by pnpm run typecheck, web lint, i18n:check, and i18n: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 --check passed.
  • 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.