Skip to content

Show per-file details for commits, and stop review from freezing the app - #385

Open
0x92 wants to merge 6 commits into
dcouple:mainfrom
0x92:feature/commit-file-details
Open

Show per-file details for commits, and stop review from freezing the app#385
0x92 wants to merge 6 commits into
dcouple:mainfrom
0x92:feature/commit-file-details

Conversation

@0x92

@0x92 0x92 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Description

Reviewing a session meant reading one combined patch: which files a commit
touched, and how much, was not visible anywhere. Two changes, both in the diff
path.

Per-file detail. Commit history rows expand to a per-file list with status and
line counts, and clicking a file opens that file's diff directly.

The review freeze. Reviewing a large working tree blocked the UI for seconds
at a time. It was real work, not a hang: capturing the working-directory diff
spawned one cat per untracked file and one wc -l per file, all synchronously
on the main thread — 800+ process spawns for a session with many untracked files.
That path is now async and batched, with the file list gathered once instead of
once per consumer.

The parsers for --numstat -z, --name-status -z and porcelain -z are pure
functions with their own tests; the -z format packs XY path into a single
token, which is easy to get wrong and impossible to notice by eye.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Performance improvement

Checklist

  • I have read the CONTRIBUTING.md guidelines
  • My code follows the code style of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • New and existing unit tests pass locally with my changes
  • I have run pnpm typecheck and pnpm lint locally
  • I have tested the Electron app locally with pnpm electron-dev
  • I have added tests that prove my fix is effective or that my feature works

Critical Areas Modified

  • State management/IPC events (sessions:get-commit-files)

Screenshots (if applicable)

image

Additional Notes

Tests: commit file changes (23) and the unified diff parser (11).

frontend/src/utils/parseUnifiedDiff.ts also appears in the commit-graph PR —
same file, same content, needed by both.

These four feature PRs are independent but all add an entry to the same
navigation plumbing (navigationStore's ActiveView, the two sidebar
components, preload.ts, api.ts, electron.d.ts). Whichever lands first,
the others need a small rebase there — no logic overlaps.

Not done: the packaged-build check from CONTRIBUTING. I develop on Windows,
so pnpm build:mac was not run.

Automated QA

Status: passed on head 77c978c3 using an isolated Pane directory, unique Vite port, and Chromium.

  • Expanded a commit to show its modified file with +8/-3 counts.
  • Clicked the file and verified the matching unified diff expanded.
  • Static gates: pnpm typecheck and pnpm lint passed.
Expanded file details Selected file revealed
Expanded commit file details Selected file diff revealed

Remaining human check: exercise the same flow in packaged Electron with a real repository and a very large untracked tree.

0x92 added 2 commits August 23, 2026 12:21
Reviewing a session meant reading one combined patch: which files a commit
touched, and how much, was not visible anywhere. Two changes, both in the
diff path:

- Commit history rows expand to a per-file list with status and line
  counts, and clicking a file opens that file's diff directly
- Reviewing a large working tree no longer blocks the UI

The freeze was real work, not a hang: capturing the working-directory diff
spawned one `cat` per untracked file and one `wc -l` per file, all
synchronously on the main thread — 800+ spawns for a session with many
untracked files, several seconds each pass. The path is now async and
batched, with the file list gathered once instead of per consumer.

The parsers for `--numstat -z`, `--name-status -z` and porcelain `-z` are
pure functions with their own tests; the `-z` format packs `XY path` into a
single token, which is easy to get wrong and impossible to notice by eye.

Tests: commit file changes (23) and the unified diff parser (11).
Two defects, both in the path that inlines untracked files into a
working-tree diff.

The listing used `git ls-files --others --exclude-standard` split on
newlines. Git delimits with newlines there, which a filename may contain,
and quotes anything non-ASCII into a C-style escape — `täst.txt` arrived
as the literal `"t\303\244st.txt"`, a name matching no file on disk, so
the file vanished from the diff and from the stats without a word. The
listing now uses `-z` and is split on NUL, and nothing is trimmed: a
leading or trailing space is part of the name.

Content and line counts were then read with `cat "<worktree>/<file>"` and
`wc -l "<worktree>/<file>"`, built by interpolation. Git allows `$`,
backticks and parentheses in a filename, and inside bash double quotes
those are still syntax: a file named `back`whoami`.txt` in a repository
was enough to run a command, with no interaction beyond opening the
session. Both now go through `fs` — readFile for content, a streamed
newline count for the totals — so a repository-controlled name never
reaches a shell. untrackedFilePath() resolves the worktree-relative name
git reports, going through the UNC mount for a WSL project the same way
gitPlumbingCommands already does.

The performance fix this PR made is kept and improved: capture spawns a
fixed handful of commands whatever the file count, where before it was
one `cat` and one batched `wc` per file. MAX_UNTRACKED_INLINE_FILES and
MAX_UNTRACKED_INLINE_BYTES still bound what is inlined, and a per-file
ceiling stands in for the 1 MB buffer `cat` used to have.

chunkByCommandLength() goes with them — nothing builds a command line out
of paths any more.

@parsakhaz parsakhaz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: Request changes, three correctness/security defects block the stated per-file review flow.
Counts: Must Fix: 3 (security: 1) · Should Fix: 1 · pass 1/3

Must Fix

  • MF-1 (security): validate the commit ref before it reaches CommandRunner at main/src/ipc/git.ts:570 and main/src/services/gitDiffManager.ts:565-575,622. The new renderer-callable endpoint accepts any string and interpolates it into shell commands. A ref such as HEAD; <command> is shell syntax, so a compromised renderer or direct IPC caller can execute a command in the worktree. Restrict this endpoint to index/UNCOMMITTED or a full hexadecimal object ID before any command runs, and test rejection.
  • MF-2: make file-path extraction handle Git quoted paths at frontend/src/utils/parseUnifiedDiff.ts:43-50. Git emits a non-ASCII filename as diff --git "a/t\\303\\244st.txt" "b/t\\303\\244st.txt", while --name-status -z returns the raw täst.txt. The parser returns no file for that header, so clicking the correctly listed file cannot reveal its diff. Add quoted-path decoding and a test using a non-ASCII path.
  • MF-3: update and extend the extracted pending-view tests for the new payload at frontend/src/components/panels/diff/pendingViewCommit.ts:14-18. Current evidence: pnpm --filter frontend test fails both existing pendingViewCommit.test.ts cases because takePendingViewCommit now returns an object rather than a string. Add the file-path preservation assertion and restore the suite.

Should Fix

  • SF-1: the PR currently fails the repository blocking lint command on PR-introduced anti-slop findings in CommitFileList.tsx, parseUnifiedDiff.ts, gitDiffManager.ts, and the new tests. Clear these before the branch is ready.

Checks

  • pnpm typecheck: passed.
  • Focused git diff tests: 30 passed; daemon registry suite was blocked by the local native SQLite ABI mismatch.
  • Frontend suite: 270 passed, 2 failed as described in MF-3.
  • pnpm lint: failed on PR-introduced blocking findings.

@parsakhaz
parsakhaz force-pushed the feature/commit-file-details branch from a272064 to 77c978c Compare August 23, 2026 19:53

@parsakhaz parsakhaz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review on 77c978c: all three Must-Fix findings are resolved with focused regression coverage. Ref validation blocks shell-like input before CommandRunner, Git-quoted paths decode to their raw filenames, and pending file-reveal payload tests pass.

@parsakhaz

Copy link
Copy Markdown
Member

Review, simplify, refactor, and QA complete

Final head: 77c978c3

Pass 1: Review

  • Requested changes for three Must-Fix defects: shell injection through the new commit-ref IPC argument, Git-quoted non-ASCII paths failing file reveal, and the rebased pending-view contract breaking its tests.
  • Fixed all three in e8a6d12b (fix(review): validate refs and preserve file reveal paths).
  • Re-reviewed the fixes and approved the PR.

Pass 2: Simplify

  • Consolidated the working-tree pseudo-ref into the shared Git contract.
  • Replaced three parallel status maps with one typed metadata table.
  • Removed a duplicate failure test, assertion-heavy runner stubs, an unnecessary cache allocation, duplicate/default exports, and lint/dead-code residue.
  • Commit: 11753492 (refactor(simplify): consolidate commit file detail plumbing).

Pass 3: Refactor

  • Made tracked changed-file collection NUL-safe, matching the untracked-path boundary and preserving non-ASCII/hostile filenames.
  • Removed unused rename-similarity data from the main/shared IPC contract.
  • Added a regression for tracked non-ASCII filenames.
  • Commit: d2974f84 (refactor: normalize commit file path boundaries).

Automated QA

  • Added and passed a Playwright journey that expands commit file details, verifies status and +8/-3, clicks the file, and confirms its diff is revealed.
  • Commit: 77c978c3 (test: cover commit file detail navigation).
  • Environment: Chromium, isolated PANE_DIR, unique Vite port, one worker.
Step Evidence
Commit details expanded The commit row lists src/review.ts, status M, and per-file counts.
Expanded commit file details
File diff revealed Clicking the file expands the matching unified diff.
Selected file diff revealed

Checks

  • pnpm typecheck: passed.
  • pnpm lint: passed, including Oxlint, ESLint, advisory anti-slop, boundary conformance, and Knip.
  • Main focused tests: 42 passed (gitDiffManager.commitFiles, gitDiffManager.untracked, daemonRegistryBindings).
  • Renderer focused tests: 9 passed (parseUnifiedDiff, pendingViewCommit).
  • Playwright commit-file navigation: 1 passed.

Follow-ups

  • Convert sessions:get-commit-files and its three Git calls to an async service boundary with cancellation/latency coverage, so expanding a row never performs synchronous Git work in Electron main.
  • Consider one structured diff boundary instead of decoding numstat/name-status in main and unified-patch paths again in the renderer.
  • Human check: repeat the flow in packaged Electron against a real repository with a very large untracked tree.

Left for parsa

  • Review the force-pushed rebased history and CI results.
  • Run the packaged Electron check if desired.
  • Merge manually. No merge was performed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants