Skip to content

Update header Git dropdown review flow#156

Open
friuns2 wants to merge 29 commits into
mainfrom
codex/header-git-commit-dropdown
Open

Update header Git dropdown review flow#156
friuns2 wants to merge 29 commits into
mainfrom
codex/header-git-commit-dropdown

Conversation

@friuns2
Copy link
Copy Markdown
Owner

@friuns2 friuns2 commented May 11, 2026

Updates the header Git dropdown review flow after PR #121, including commit file review mode, worktree change counts, and Review pane layering.

@friuns2 friuns2 marked this pull request as ready for review May 11, 2026 03:14
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 11, 2026

Review Summary by Qodo

(Agentic_describe updated until commit 33764be)

Update header Git dropdown with commit file review mode and workspace summary

✨ Enhancement 🧪 Tests 📝 Documentation

Grey Divider

Walkthroughs

Description
• **Redesigned header Git dropdown** with three-column layout: branches, commits, and commit file
  details browser
• **Added commit-scoped review mode** enabling file review within specific commits with commitSha
  parameter support
• **Implemented commit file listing** via new /codex-api/git/commit-files endpoint with line
  counts and status labels
• **Added workspace summary endpoint** (/codex-api/review/summary) to retrieve change statistics
• **Simplified ReviewPane component** to focus on changes view, removing tab-based UI and findings
  display
• **Enhanced untracked file handling** using git ls-files with null-byte delimiters and
  preservation during git operations
• **Fixed z-index layering** for proper modal behavior with adjusted stacking contexts across layout
  components
• **Extended API types** to support 'commit' review scope and commit-specific review snapshots
• **Updated Chinese translations** for commit review UI elements
• **Expanded test coverage** from 14 to 30 steps with comprehensive validation for commit file
  review, worktree counts, and dropdown layering
• **Updated agent guidelines** for dev server persistence during active UI work
Diagram
flowchart LR
  A["Git Commit Data"] -->|"buildCommitDiff"| B["Commit File Changes"]
  B -->|"/codex-api/git/commit-files"| C["HeaderGitBranchDropdown"]
  C -->|"onOpenContentHeaderCommitFile"| D["ReviewPane"]
  D -->|"commitSha prop"| E["Commit-Scoped Review"]
  A -->|"buildReviewSummary"| F["Workspace Summary"]
  F -->|"/codex-api/review/summary"| G["App State"]
  G -->|"threadWorktreeChangeSummary"| C
  H["DesktopLayout Z-Index"] -->|"isolate + z-index"| D
Loading

Grey Divider

File Changes

1. src/server/reviewGit.ts ✨ Enhancement +211/-17

Add commit review mode and workspace summary endpoints

• Added support for commit-scoped reviews by extending ReviewScope type to include 'commit' and
 adding commitSha field to ReviewSnapshot
• Implemented new functions runCommandCaptureRaw, buildCommitDiff, buildWorkspaceDiffSummary,
 buildReviewSummary, and helper utilities for parsing git output
• Refactored untracked file handling to use git ls-files with null-byte delimiters instead of `git
 status` parsing
• Added new /codex-api/review/summary endpoint to retrieve workspace change statistics

src/server/reviewGit.ts


2. src/server/codexAppServerBridge.ts ✨ Enhancement +147/-13

Add commit file listing and untracked file preservation

• Refactored runCommandCapture to delegate to new runCommandCaptureRaw function, separating
 trimming logic
• Added preserveUntrackedFilesForGitTarget function to backup conflicting untracked files before
 git operations
• Implemented new /codex-api/git/commit-files endpoint to retrieve file changes for a specific
 commit with line counts
• Enhanced branch checkout and reset operations to preserve untracked files that would conflict with
 target refs
• Added includeResetHistory query parameter to /codex-api/git/branch-commits endpoint

src/server/codexAppServerBridge.ts


3. src/api/codexGateway.ts ✨ Enhancement +74/-3

Add commit files API and review summary endpoints

• Added GitCommitFileChange type to represent individual file changes in a commit
• Implemented getGitCommitFiles function to fetch file changes for a specific commit SHA
• Added normalizeReviewSummary function and getReviewSummary export for retrieving workspace
 change statistics
• Updated getGitBranchCommits to accept includeResetHistory option parameter
• Extended getReviewSnapshot to accept optional commitSha parameter for commit-scoped reviews

src/api/codexGateway.ts


View more (10)
4. src/composables/useUiLanguage.ts 📝 Documentation +2/-6

Update Chinese translations for commit review

• Added Chinese translation for 'Commit' label
• Added Chinese translation for 'No file changes in this commit.' message
• Removed obsolete Chinese translations for review UI elements ('Reviewing…', 'Run review',
 'Findings', etc.)

src/composables/useUiLanguage.ts


5. src/types/codex.ts ✨ Enhancement +4/-2

Add commit scope and summary types

• Extended UiReviewScope type to include 'commit' scope option
• Added commitSha field to UiReviewSnapshot type
• Added UiReviewSummary type alias for review change statistics
• Removed UiReviewTab type (no longer used for tab switching)

src/types/codex.ts


6. src/components/content/ReviewPane.vue ✨ Enhancement +113/-300

Simplify review pane to commit-focused changes view

• Removed tab-based UI for switching between changes and findings views; now displays only changes
 view
• Added support for commit review mode with commitSha prop and commit-specific UI labels
• Refactored toolbar controls to hide workspace/branch-specific options when reviewing commits
• Simplified file tree rendering and removed hunk ref tracking for scroll-to-hunk functionality
• Changed review pane positioning from relative to fixed with z-index layering for proper modal
 behavior
• Removed review execution UI (runReview button, status tracking, findings display)

src/components/content/ReviewPane.vue


7. src/components/content/HeaderGitBranchDropdown.vue ✨ Enhancement +480/-99

Redesign dropdown with commit file browser

• Restructured dropdown menu into three-column layout: branches, commits, and commit file details
• Added commit file browser showing changed files with line counts and status labels
• Implemented commit selection and file opening functionality with copy-to-clipboard for commit SHAs
• Added reset-history refs toggle checkbox to control historical commit visibility
• Enhanced branch selection with visual indicators for current and selected branches
• Replaced branch expand/collapse with dedicated commit panel and file detail panel

src/components/content/HeaderGitBranchDropdown.vue


8. src/App.vue ✨ Enhancement +110/-11

Wire up commit file review and worktree summary

• Added threadWorktreeChangeSummary state and loadThreadWorktreeChangeSummary function to track
 workspace changes
• Implemented loadThreadCommitFiles function to fetch files for selected commits
• Added onOpenContentHeaderCommitFile handler to open review pane for specific commit files
• Added onToggleContentHeaderReview handler to reset review context when toggling review pane
• Passed new props to ReviewPane component: initialFilePath and commitSha for commit-focused
 reviews
• Updated HeaderGitBranchDropdown props and event handlers for new commit file browser
 functionality

src/App.vue


9. src/components/layout/DesktopLayout.vue ✨ Enhancement +3/-3

Adjust z-index stacking for modal layering

• Added isolate class to .desktop-layout for stacking context isolation
• Added z-0 to .desktop-sidebar for explicit z-index layering
• Updated .desktop-main z-index from implicit to explicit z-[100] for proper modal layering

src/components/layout/DesktopLayout.vue


10. src/components/content/ContentHeader.vue ✨ Enhancement +1/-1

Increase content header z-index

• Updated .content-header z-index from z-10 to z-[250] for proper layering above modals

src/components/content/ContentHeader.vue


11. src/style.css ✨ Enhancement +61/-13

Add dark mode styles for commit browser

• Added dark mode styles for new commit file browser UI elements (.header-git-file,
 .header-git-ref, .header-git-reset-commit, etc.)
• Added dark mode styles for commit detail panel and file list components
• Updated dark mode colors for file status badges, line count indicators, and interactive elements
• Removed obsolete dark mode styles for findings-related components
• Enhanced dark mode styling for review pane content area and file tree

src/style.css


12. AGENTS.md 📝 Documentation +2/-0

Update agent guidelines for dev server persistence

• Added note to leave dev server running after Playwright verification unless explicitly requested
 to stop
• Added guidance to treat port 4173 verification server as reusable test infrastructure during
 active UI work

AGENTS.md


13. tests.md 🧪 Tests +90/-22

Comprehensive test expansion for commit file review and dropdown layering

• Expanded test coverage for header Git dropdown from 14 steps to 30 steps, adding comprehensive
 validation for commit file review mode, worktree change counts, and dropdown layering
• Added new test scenarios for commit selection, file details panel, reset-history toggle behavior,
 mobile viewport responsiveness, and whitespace-path handling
• Introduced "Preserved Prior Coverage" section documenting backward compatibility with existing
 branch search, commit loading, and reset functionality
• Enhanced expected results with detailed specifications for dropdown layering, Review pane
 integration, commit file display, and mobile UI behavior
• Expanded rollback/cleanup procedures and added performance audit evidence section documenting
 profiling reports and Git command optimization

tests.md


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 11, 2026

Code Review by Qodo

🐞 Bugs (9) 📘 Rule violations (3)

Grey Divider


Action required

1. Untracked filenames get trimmed 🐞 Bug ≡ Correctness
Description
The new /codex-api/review/summary path can return wrong counts (or 500) because untracked paths are
parsed from git status --porcelain=v1 with .trim(), which changes filenames and can’t represent
Git’s quoted/escaped porcelain paths. That trimmed path is then fed into stat()/readFile(), so
repos with untracked filenames that include leading/trailing whitespace (or require quoting) can
break the header worktree +/- summary.
Code

src/server/reviewGit.ts[R371-374]

+  const statusOutput = await runCommandCapture('git', ['status', '--porcelain=v1', '--untracked-files=all'], { cwd: repoRoot })
+  for (const path of parseUntrackedPaths(statusOutput)) {
+    summary = addReviewSummary(summary, await summarizeUntrackedFile(repoRoot, path))
+  }
Evidence
The summary endpoint calls buildWorkspaceDiffSummary(), which parses untracked paths (currently
trimming them) and then stat()/readFile() those paths. Trimming and porcelain quoting mean the
path string may not match an actual filesystem entry, so the summary can fail or miscount.

src/server/reviewGit.ts[281-289]
src/server/reviewGit.ts[335-346]
src/server/reviewGit.ts[348-375]
src/server/reviewGit.ts[902-924]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The review summary endpoint computes untracked file counts using `git status --porcelain=v1` and `parseUntrackedPaths()` which trims path text. This can corrupt filenames (leading/trailing whitespace) and fails for porcelain-quoted paths, leading to incorrect summary counts or thrown fs errors.
## Issue Context
- `/codex-api/review/summary` is now used to power the header worktree +/- counts.
- `parseUntrackedPaths()` currently does `line.slice(3).trim()`.
- `summarizeUntrackedFile()` calls `stat()`/`readFile()` using that derived path.
## Fix Focus Areas
- src/server/reviewGit.ts[281-289]
- src/server/reviewGit.ts[335-346]
- src/server/reviewGit.ts[348-375]
- src/server/reviewGit.ts[902-924]
## Implementation notes
- Prefer a NUL-delimited source of untracked paths (e.g. `git ls-files --others --exclude-standard -z`) to avoid porcelain quoting/escaping entirely.
- Do not apply `.trim()` to filenames.
- Make `summarizeUntrackedFile()` robust: catch ENOENT / non-file cases and return a zero summary instead of throwing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Commit file paths altered 🐞 Bug ≡ Correctness
Description
The new /codex-api/git/commit-files endpoint (and the client normalizer) trims and tab-splits paths
coming from git diff-tree, which can corrupt legitimate filenames containing leading/trailing
whitespace and can desync name-status vs numstat path matching. This can cause the commit file list
to show wrong/missing entries and prevent opening the intended file in commit review mode.
Code

src/server/codexAppServerBridge.ts[R7037-7055]

+          for (const line of numstatOutput.split('\n')) {
+            const parts = line.split('\t')
+            const addedRaw = parts[0]?.trim() ?? ''
+            const removedRaw = parts[1]?.trim() ?? ''
+            const path = (parts.length >= 4 ? parts[3] : parts[2])?.trim() ?? ''
+            if (!path) continue
+            const addedLineCount = /^\d+$/.test(addedRaw) ? Number(addedRaw) : null
+            const removedLineCount = /^\d+$/.test(removedRaw) ? Number(removedRaw) : null
+            lineCountsByPath.set(path, { addedLineCount, removedLineCount })
+          }
+          const files = output.split('\n').flatMap((line) => {
+            const parts = line.split('\t').map((part) => part.trim()).filter(Boolean)
+            const status = parts[0] ?? ''
+            if (!status) return []
+            const statusKind = status.charAt(0)
+            const isRenameOrCopy = (statusKind === 'R' || statusKind === 'C') && parts.length >= 3
+            const path = isRenameOrCopy ? parts[2] : parts[1]
+            const previousPath = isRenameOrCopy ? parts[1] : null
+            if (!path) return []
Evidence
The server commit-files handler trims path strings extracted from diff-tree output and the client
normalizer trims them again before using them for UI display/navigation. This directly mutates the
filename content and can prevent correct file selection/opening.

src/server/codexAppServerBridge.ts[7011-7069]
src/api/codexGateway.ts[2725-2751]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The commit-files API parses `git diff-tree` output using newline/tab splitting and applies `.trim()` to parsed path fields. This can change the actual filename (e.g. leading/trailing spaces) and makes parsing ambiguous for filenames containing special characters. The client then trims again, further ensuring the UI cannot round-trip exact paths.
## Issue Context
- Server: `/codex-api/git/commit-files` builds `files` via `diff-tree --name-status` and separately parses `diff-tree --numstat`, trimming the `path` field in both.
- Client: `getGitCommitFiles()` trims `record.path` and `record.previousPath`.
## Fix Focus Areas
- src/server/codexAppServerBridge.ts[7011-7071]
- src/api/codexGateway.ts[2725-2751]
## Implementation notes
- Switch both `--name-status` and `--numstat` invocations to `-z` and parse NUL-delimited records.
- Do not `.trim()` path fields; treat them as opaque.
- Ensure rename/copy parsing keeps both previousPath and path exactly as emitted.
- Update client normalizer to avoid trimming `path`/`previousPath` (it may still validate type/emptiness without modifying content).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. review-pane lacks dark styles 📘 Rule violation ≡ Correctness
Description
The updated Review pane overlay uses light-surface styling (e.g., bg-white) but no dark-theme
override is added in src/style.css, so it will render as a light surface in dark mode. This
violates the requirement to centralize decisive dark overrides for shared route surfaces in the
global stylesheet.
Code

src/components/content/ReviewPane.vue[R933-937]

+  @apply fixed inset-3 z-[1200] flex min-h-0 min-w-0 flex-col overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-2xl;
}
.review-pane.is-mobile {
-  @apply fixed inset-0 z-40 rounded-none border-0;
+  @apply inset-0 rounded-none border-0;
Evidence
Rule 2 requires shared/large UI dark-theme overrides to be centralized in src/style.css. The
Review pane overlay styling is updated and still applies a light surface (bg-white) with no dark
override present in the global stylesheet area where other dark overrides are being added.

AGENTS.md
src/components/content/ReviewPane.vue[933-937]
src/style.css[891-992]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Review pane is a shared, full-surface overlay and now explicitly uses light-surface styles (`bg-white`, light borders). There are no corresponding global `:root.dark ...` overrides in `src/style.css`, risking a light UI surface in dark mode.
## Issue Context
Compliance requires decisive dark-theme overrides for shared route surfaces/large feature UIs to live in `src/style.css`.
## Fix Focus Areas
- src/components/content/ReviewPane.vue[933-937]
- src/style.css[891-992]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (4)
4. tests.md missing cleanup notes 📘 Rule violation ⚙ Maintainability
Description
The updated manual test section introduces steps that change Git state and create untracked/tracked
files, but it does not include any rollback/cleanup guidance. This makes the instructions
non-reproducible and can leave the repo/worktree (and .codex/untracked-backups/) in a modified
state after testing.
Code

tests.md[R785-857]

#### Steps
1. In light theme, open the Git dropdown in the thread header.
2. Confirm the trigger shows the current branch, or the detached commit subject if the repository is already detached.
-3. Click `Review` and confirm the review pane opens; click it again and confirm the pane toggles.
-4. Type part of a branch name in search and confirm the branch list filters.
-5. Select a different branch with a clean worktree and confirm the header updates to that branch.
-6. Expand a branch row and confirm recent commits load with short SHA, subject, and date.
-7. Expand a remote branch row and confirm its commit rows are disabled with a tooltip explaining remote branches cannot be reset.
-8. Select an older commit on the disposable local branch and confirm the header stays on that branch instead of entering detached HEAD.
-9. Confirm `git -C <thread-cwd> rev-parse --abbrev-ref HEAD` still prints the branch name and `git -C <thread-cwd> rev-parse --short HEAD` matches the selected commit.
-10. Reopen/expand the same branch and confirm commits that were ahead of the reset target still appear, with the selected branch HEAD marked `current`.
-11. Repeat reset on the same branch several times and confirm the dropdown still opens quickly and shows recent reset-history commits.
-12. Create a tracked uncommitted change, try to switch branch or reset to a commit, and confirm the dropdown shows a dirty-worktree error instead of switching or resetting.
-13. Create only an untracked file, try to reset to a commit, and confirm the reset proceeds unless Git reports the untracked file would be overwritten.
-14. Switch to dark theme and repeat steps 1, 2, 4, 6, 7, 10, 12, and 13.
-
-#### Expected Results
-- The header dropdown exposes Review, current checkout state, searchable branches, and inline commits.
-- Branch switching and branch reset-to-commit are blocked by tracked uncommitted changes, but untracked-only changes are allowed unless Git would overwrite them.
-- Commit selection resets the local branch to that commit instead of detaching HEAD.
-- Remote branch commit rows are inspectable but cannot trigger local branch reset.
+3. Confirm the menu initially shows only commits on the left and branches on the right, with no commit-files panel before a commit is selected.
+4. Confirm the left column defaults to the current branch and shows no more than 50 recent commits with short SHA, subject, and date.
+5. Confirm the open dropdown visually layers above the sidebar and above the Review pane if the pane is already open.
+6. Confirm the top action is styled as a button, reads `Review Worktree Changes`, and shows `+`/`-` line counts; click it and confirm the dropdown stays open while the review pane opens above it directly to changes without showing a `Findings` tab or `Run review` button; click the Review pane `X` and confirm only the pane closes while the dropdown stays open; click `Review Worktree Changes` again and confirm the pane toggles open.
+7. Type part of a commit subject or short SHA in the left commit search and confirm the commit list filters.
+8. Turn off `Reset-history refs` and confirm the commit list reloads without saved reset-history refs.
+9. Turn `Reset-history refs` back on and confirm saved reset-history commits reappear when available.
+10. Toggle `Reset-history refs` while the commit list is still loading and confirm the list reloads for the new toggle state instead of staying on the previous result.
+11. Type part of a branch name in search and confirm the branch list filters.
+12. Click a different branch row and confirm the left commit list changes to that branch without immediately switching checkout.
+13. Use the branch row `Checkout` action with a clean worktree and confirm the header updates to that branch.
+14. Confirm local branches appear first and remote branches appear at the end of the branch list.
+15. Select an older commit on the disposable local branch and confirm the dropdown widens and shows a left-side file panel with that commit subject, file changes, per-file `+`/`-` line counts, and a `Reset` button without changing HEAD.
+16. Click a commit ref badge and confirm the full commit SHA is copied to the clipboard without changing the selected commit.
+17. Click a file in the selected commit details and confirm the dropdown closes and the Review pane opens in commit mode, with the selected commit diff loaded and that file selected without auto-centering the first hunk.
+18. Click `Reset` for the selected commit and confirm the header stays on that branch instead of entering detached HEAD.
+19. Confirm `git -C <thread-cwd> rev-parse --abbrev-ref HEAD` still prints the branch name and `git -C <thread-cwd> rev-parse --short HEAD` matches the selected commit.
+20. Reopen/select the same branch and confirm commits that were ahead of the reset target still appear, with the selected branch HEAD marked `current`.
+21. Repeat reset on the same branch several times and confirm the dropdown still opens quickly and shows recent reset-history commits.
+22. Create a tracked uncommitted change, try to switch branch or reset to a commit, and confirm the dropdown shows a dirty-worktree error instead of switching or resetting.
+23. Create only an untracked file whose path does not exist in the target commit, try to reset to a commit, and confirm the reset proceeds while the untracked file remains in place.
+24. Create only an untracked file whose path exists in the target commit, try to reset to that target, and confirm the reset proceeds and the untracked file is moved under `.codex/untracked-backups/` instead of being overwritten.
+25. At a mobile viewport around 375px wide, select a commit and confirm the dropdown fits inside the viewport with branches first, commits second, and selected-commit files last, stacked vertically instead of squeezed into columns.
+26. Narrow the Review pane file list and confirm changed-file rows do not inherit folder-depth indentation, long names truncate on one line instead of wrapping vertically, and the `+`/`-` counts remain visible.
+27. At a mobile viewport around 375px wide, open the Review pane, scroll the diff content vertically, and confirm the `X` close button remains visible and tappable in the top-right corner.
+28. Switch to dark theme and repeat steps 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 16, 17, 20, 22, 23, 24, 25, 26, and 27.
+
+#### Preserved Prior Coverage
+1. Click `Review Worktree Changes` and confirm the review pane opens; click it again and confirm the pane toggles.
+2. Type part of a branch name in search and confirm the branch list filters.
+3. Select a different branch with a clean worktree using the checkout action and confirm the header updates to that branch.
+4. Select a branch row and confirm recent commits load with short SHA, subject, and date.
+5. Confirm remote branch rows are inspectable, appear after local branches, and do not trigger local reset without a supported local branch action.
+6. Select an older commit on the disposable local branch and confirm the header stays on that branch instead of entering detached HEAD.
+7. Confirm `git -C <thread-cwd> rev-parse --abbrev-ref HEAD` still prints the branch name and `git -C <thread-cwd> rev-parse --short HEAD` matches the selected commit after reset.
+8. Reopen/select the same branch and confirm commits that were ahead of the reset target still appear, with the selected branch HEAD marked `current`.
+9. Repeat reset on the same branch several times and confirm the dropdown still opens quickly and shows recent reset-history commits.
+10. Create a tracked uncommitted change, try to switch branch or reset to a commit, and confirm the dropdown shows a dirty-worktree error instead of switching or resetting.
+11. Create only an untracked file, try to reset to a commit, and confirm the reset proceeds while preserving the untracked file in place or moving it to `.codex/untracked-backups/` if the target would overwrite it.
+12. Switch to dark theme and repeat the branch filtering, commit loading, reset-history, dirty-worktree, and untracked-file checks.
+
+#### Expected Results
+- The header dropdown exposes Review, current checkout state, a left-side commit list, and a right-side searchable branch list before a commit is selected.
+- The selected-commit file panel is hidden until commit selection, then appears on the left and expands the dropdown width.
+- Each selected-commit file row shows added and removed line counts, using `-` for binary or unavailable counts.
+- The dropdown layer is viewport-positioned and appears above the sidebar when open.
+- The Review pane renders above the open dropdown and app chrome.
+- Clicking the dropdown `Review Worktree Changes` button keeps the dropdown open while toggling the Review pane.
+- Clicking the Review pane `X` while the dropdown is open closes only the Review pane and leaves the dropdown open.
+- The `Review Worktree Changes` button shows current worktree added and removed line counts.
+- The Review pane toolbar keeps `Refresh` but does not show a `Findings` tab or `Run review` button.
+- The current branch commit list loads by default and is capped at 50 commits.
+- The commit list can be searched by SHA, subject, or date without changing the selected branch.
+- Reset-history refs can be shown or hidden from the commit list without changing the selected branch.
+- Reset-history toggles are keyed by both branch and reset-history state, so an in-flight load for one state does not suppress loading the other state.
+- Branch switching and branch reset-to-commit are blocked by tracked uncommitted changes, but untracked-only changes are preserved and allowed.
+- Commit selection opens file details without resetting or detaching HEAD.
+- Commit ref badges copy the full SHA to the clipboard without triggering commit selection.
+- The selected commit `Reset` button resets the local branch to that commit instead of detaching HEAD.
+- Clicking a selected commit file opens the Review pane against that commit diff and selects that path without auto-centering the selected hunk.
+- Changed-file rows in the Review pane file list do not inherit folder-depth indentation, so long names have enough room to truncate horizontally instead of wrapping one character per line when the list is narrow.
+- Remote branches appear after local branches in the branch list.
+- Prior branch-search, inline-commit, remote-branch inspection, reset-to-commit, dirty-worktree, and untracked-file manual coverage remains represented in the section.
- The branch commit list still shows commits that were ahead of the reset target by reading saved internal reset-history refs.
- Reset-history refs are bounded so repeated resets do not grow commit-list inputs without limit.
-- The selected branch HEAD commit is marked `current` in expanded commit lists.
+- Untracked files that would collide with target tracked files are moved to `.codex/untracked-backups/` before checkout/reset.
+- The selected branch HEAD commit is marked `current` in the commit list.
+- The mobile Review pane keeps its close button visible above the app chrome in both light theme and dark theme.
+- The mobile Review pane diff area scrolls vertically without moving or hiding the pane header.
+- On mobile, branches, commits, and selected-commit file details stack vertically in that order and stay inside the viewport in both light theme and dark theme.
Evidence
PR Compliance ID 2 requires rollback/cleanup notes when applicable. In the updated `Header Git
branch dropdown with commit reset` test steps, the instructions include branch checkout/reset and
creating untracked/tracked files (including moving conflicts into .codex/untracked-backups/), but
there is no Rollback/Cleanup (or equivalent) subsection describing how to restore the previous
branch/HEAD state and remove any created/moved files.

AGENTS.md
tests.md[785-857]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The updated `tests.md` section for the header Git dropdown/review flow lacks rollback/cleanup notes, despite instructing testers to reset branches and create untracked/tracked files.
## Issue Context
Per the manual test documentation requirements, sections should include rollback/cleanup notes when applicable. This feature’s test steps include operations that can alter checkout state and move files into `.codex/untracked-backups/`, so cleanup guidance is applicable.
## Fix Focus Areas
- tests.md[785-857]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Commit diff not recursive 🐞 Bug ≡ Correctness
Description
buildCommitDiff() runs git diff-tree without -r, so commit review mode can return an
empty/incomplete patch for commits whose changes are only in subdirectories. This breaks commit-file
review mode because the Review pane snapshot may show no files/hunks despite real changes.
Code

src/server/reviewGit.ts[R434-441]

+async function buildCommitDiff(repoRoot: string, commitSha: string): Promise<{ diffText: string; commitSha: string }> {
+  const resolvedSha = await runCommandCapture('git', ['rev-parse', '--verify', `${commitSha}^{commit}`], { cwd: repoRoot })
+  const diffText = await runCommandCapture(
+    'git',
+    ['diff-tree', '--root', '--no-commit-id', '--no-ext-diff', '--find-renames', '--patch', resolvedSha],
+    { cwd: repoRoot },
+  )
+  return { diffText, commitSha: resolvedSha }
Evidence
The commit review snapshot path uses git diff-tree ... --patch without -r, while the repo’s
commit-files endpoint uses diff-tree ... -r, demonstrating that recursion is required to
enumerate/patch file changes reliably.

src/server/reviewGit.ts[434-441]
src/server/codexAppServerBridge.ts[7022-7031]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Commit review snapshots are generated via `git diff-tree` without the `-r` (recursive) flag, which can omit file-level diffs for commits that only modify files inside subdirectories.
### Issue Context
The server already uses `git diff-tree ... -r` elsewhere (commit-files endpoint), indicating recursion is expected for correct commit traversal.
### Fix Focus Areas
- src/server/reviewGit.ts[434-441]
### Implementation notes
- Update the `runCommandCapture('git', ['diff-tree', ...])` argv in `buildCommitDiff` to include `-r` (and keep existing flags like `--root`, `--find-renames`, `--patch`).
- Consider matching the commit-files endpoint behavior/flags for consistency.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Remote checkout detaches HEAD 🐞 Bug ≡ Correctness
Description
HeaderGitBranchDropdown shows a "Checkout" action for remote branches, but the server checkout route
accepts remote ref names and runs git checkout , which can detach HEAD and break the intended
branch-based flow.
Code

src/components/content/HeaderGitBranchDropdown.vue[R169-177]

+                  <button
+                    v-if="branch.value === selectedBranch && branch.value !== currentBranch"
+                    class="header-git-branch-checkout"
+                    type="button"
+                    :disabled="busy"
+                    @click="emit('checkoutBranch', branch.value)"
+                  >
+                    Checkout
+                  </button>
Evidence
The UI renders a checkout action for any selected branch (including those marked remote), and the
server checkout endpoint does not validate that the branch is local before executing checkout,
unlike the reset-to-commit endpoint which does enforce local branches.

src/components/content/HeaderGitBranchDropdown.vue[155-177]
src/server/codexAppServerBridge.ts[6552-6584]
src/api/codexGateway.ts[311-316]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Remote branches (e.g. `origin/main`) can be selected and then checked out via the new `Checkout` button. The server’s `/codex-api/git/checkout` route does not restrict to local branches and calls `git checkout <branch>`, which will commonly result in a detached HEAD for remote refs.
### Issue Context
- UI already surfaces `branch.isRemote` (badge), but the `Checkout` button condition does not account for it.
- Server-side reset-to-commit explicitly enforces local branches via `assertLocalGitBranch`, but checkout does not.
### Fix Focus Areas
- src/components/content/HeaderGitBranchDropdown.vue[155-177]
- src/server/codexAppServerBridge.ts[6552-6584]
### What to change
1. UI: Hide or disable the `Checkout` button for `branch.isRemote === true` (and optionally show a tooltip/message).
2. Server: Add `await assertLocalGitBranch(gitRoot, targetBranch)` in the checkout route (or implement a safe remote checkout behavior like creating a local tracking branch explicitly, if that’s desired).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. tests.md removes prior steps ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The updated Header Git branch dropdown with commit reset section replaces the previous test
steps/expected results instead of appending/updating them, which removes existing manual test cases.
This breaks the requirement to keep prior cases and only add/update what’s needed.
Code

tests.md[L725-742]

-3. Click `Review` and confirm the review pane opens; click it again and confirm the pane toggles.
-4. Type part of a branch name in search and confirm the branch list filters.
-5. Select a different branch with a clean worktree and confirm the header updates to that branch.
-6. Expand a branch row and confirm recent commits load with short SHA, subject, and date.
-7. Expand a remote branch row and confirm its commit rows are disabled with a tooltip explaining remote branches cannot be reset.
-8. Select an older commit on the disposable local branch and confirm the header stays on that branch instead of entering detached HEAD.
-9. Confirm `git -C <thread-cwd> rev-parse --abbrev-ref HEAD` still prints the branch name and `git -C <thread-cwd> rev-parse --short HEAD` matches the selected commit.
-10. Reopen/expand the same branch and confirm commits that were ahead of the reset target still appear, with the selected branch HEAD marked `current`.
-11. Repeat reset on the same branch several times and confirm the dropdown still opens quickly and shows recent reset-history commits.
-12. Create a tracked uncommitted change, try to switch branch or reset to a commit, and confirm the dropdown shows a dirty-worktree error instead of switching or resetting.
-13. Create only an untracked file, try to reset to a commit, and confirm the reset proceeds unless Git reports the untracked file would be overwritten.
-14. Switch to dark theme and repeat steps 1, 2, 4, 6, 7, 10, 12, and 13.
-
-#### Expected Results
-- The header dropdown exposes Review, current checkout state, searchable branches, and inline commits.
-- Branch switching and branch reset-to-commit are blocked by tracked uncommitted changes, but untracked-only changes are allowed unless Git would overwrite them.
-- Commit selection resets the local branch to that commit instead of detaching HEAD.
-- Remote branch commit rows are inspectable but cannot trigger local branch reset.
+3. Confirm the menu initially shows only commits on the left and branches on the right, with no commit-files panel before a commit is selected.
+4. Confirm the left column defaults to the current branch and shows no more than 50 recent commits with short SHA, subject, and date.
+5. Confirm the open dropdown visually layers above the sidebar and above the Review pane if the pane is already open.
+6. Confirm the top action is styled as a button, reads `Review Worktree Changes`, and shows `+`/`-` line counts; click it and confirm the dropdown stays open while the review pane opens above it directly to changes without showing a `Findings` tab or `Run review` button; click the Review pane `X` and confirm only the pane closes while the dropdown stays open; click `Review Worktree Changes` again and confirm the pane toggles open.
+7. Type part of a commit subject or short SHA in the left commit search and confirm the commit list filters.
+8. Turn off `Reset-history refs` and confirm the commit list reloads without saved reset-history refs.
+9. Turn `Reset-history refs` back on and confirm saved reset-history commits reappear when available.
+10. Toggle `Reset-history refs` while the commit list is still loading and confirm the list reloads for the new toggle state instead of staying on the previous result.
+11. Type part of a branch name in search and confirm the branch list filters.
+12. Click a different branch row and confirm the left commit list changes to that branch without immediately switching checkout.
+13. Use the branch row `Checkout` action with a clean worktree and confirm the header updates to that branch.
+14. Confirm local branches appear first and remote branches appear at the end of the branch list.
+15. Select an older commit on the disposable local branch and confirm the dropdown widens and shows a left-side file panel with that commit subject, file changes, per-file `+`/`-` line counts, and a `Reset` button without changing HEAD.
+16. Click a commit ref badge and confirm the full commit SHA is copied to the clipboard without changing the selected commit.
+17. Click a file in the selected commit details and confirm the dropdown closes and the Review pane opens in commit mode, with the selected commit diff loaded and that file selected without auto-centering the first hunk.
+18. Click `Reset` for the selected commit and confirm the header stays on that branch instead of entering detached HEAD.
+19. Confirm `git -C <thread-cwd> rev-parse --abbrev-ref HEAD` still prints the branch name and `git -C <thread-cwd> rev-parse --short HEAD` matches the selected commit.
+20. Reopen/select the same branch and confirm commits that were ahead of the reset target still appear, with the selected branch HEAD marked `current`.
+21. Repeat reset on the same branch several times and confirm the dropdown still opens quickly and shows recent reset-history commits.
+22. Create a tracked uncommitted change, try to switch branch or reset to a commit, and confirm the dropdown shows a dirty-worktree error instead of switching or resetting.
+23. Create only an untracked file whose path does not exist in the target commit, try to reset to a commit, and confirm the reset proceeds while the untracked file remains in place.
+24. Create only an untracked file whose path exists in the target commit, try to reset to that target, and confirm the reset proceeds and the untracked file is moved under `.codex/untracked-backups/` instead of being overwritten.
+25. At a mobile viewport around 375px wide, select a commit and confirm the dropdown fits inside the viewport with branches first, commits second, and selected-commit files last, stacked vertically instead of squeezed into columns.
+26. Narrow the Review pane file list and confirm changed-file rows do not inherit folder-depth indentation, long names truncate on one line instead of wrapping vertically, and the `+`/`-` counts remain visible.
+27. At a mobile viewport around 375px wide, open the Review pane, scroll the diff content vertically, and confirm the `X` close button remains visible and tappable in the top-right corner.
+28. Switch to dark theme and repeat steps 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 16, 17, 20, 22, 23, 24, 25, 26, and 27.
+
+#### Expected Results
+- The header dropdown exposes Review, current checkout state, a left-side commit list, and a right-side searchable branch list before a commit is selected.
+- The selected-commit file panel is hidden until commit selection, then appears on the left and expands the dropdown width.
+- Each selected-commit file row shows added and removed line counts, using `-` for binary or unavailable counts.
+- The dropdown layer is viewport-positioned and appears above the sidebar when open.
+- The Review pane renders above the open dropdown and app chrome.
+- Clicking the dropdown `Review Worktree Changes` button keeps the dropdown open while toggling the Review pane.
+- Clicking the Review pane `X` while the dropdown is open closes only the Review pane and leaves the dropdown open.
+- The `Review Worktree Changes` button shows current worktree added and removed line counts.
+- The Review pane toolbar keeps `Refresh` but does not show a `Findings` tab or `Run review` button.
+- The current branch commit list loads by default and is capped at 50 commits.
+- The commit list can be searched by SHA, subject, or date without changing the selected branch.
+- Reset-history refs can be shown or hidden from the commit list without changing the selected branch.
+- Reset-history toggles are keyed by both branch and reset-history state, so an in-flight load for one state does not suppress loading the other state.
+- Branch switching and branch reset-to-commit are blocked by tracked uncommitted changes, but untracked-only changes are preserved and allowed.
+- Commit selection opens file details without resetting or detaching HEAD.
+- Commit ref badges copy the full SHA to the clipboard without triggering commit selection.
+- The selected commit `Reset` button resets the local branch to that commit instead of detaching HEAD.
+- Clicking a selected commit file opens the Review pane against that commit diff and selects that path without auto-centering the selected hunk.
+- Changed-file rows in the Review pane file list do not inherit folder-depth indentation, so long names have enough room to truncate horizontally instead of wrapping one character per line when the list is narrow.
+- Remote branches appear after local branches in the branch list.
Evidence
PR Compliance ID 1 requires that feature-related updates to the root tests.md must not remove
existing test cases. This PR modifies the section starting at tests.md around the listed
steps/expected results, and the diff shows prior items were deleted/replaced rather than preserved
alongside the new flow.

AGENTS.md
tests.md[710-782]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR updates the `Header Git branch dropdown with commit reset` manual test section by replacing the prior steps/expected results, which removes existing test coverage. Compliance requires that `tests.md` updates do not remove existing test cases; they should append or update while preserving prior cases.
## Issue Context
The current section has extensive new steps and expected results, but the diff shows earlier steps/expected-result bullets were deleted rather than preserved (even if they need wording updates).
## Fix Focus Areas
- tests.md[722-781]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

8. Untracked backup rename aborts 🐞 Bug ☼ Reliability ⭐ New
Description
preserveUntrackedFilesForGitTarget() renames conflicting untracked files without handling
filesystem races (e.g., untracked file deleted between listing and rename), so checkout/reset can
fail even when it could safely proceed. This can block /codex-api/git/checkout-branch and
/codex-api/git/reset-to-commit operations unnecessarily.
Code

src/server/codexAppServerBridge.ts[R2873-2879]

+  const backupRoot = join(repoRoot, HEADER_GIT_UNTRACKED_BACKUP_DIR, new Date().toISOString().replace(/[:.]/g, '-'))
+  for (const filePath of conflictingUntrackedPaths) {
+    const sourcePath = resolveGitRelativePath(repoRoot, filePath)
+    const backupPath = join(backupRoot, ...filePath.split('/'))
+    await mkdir(dirname(backupPath), { recursive: true })
+    await rename(sourcePath, backupPath)
+  }
Evidence
The backup routine renames files without any error handling, and it is called before both `git
checkout and git reset --hard`. Therefore, any transient rename failure will throw and prevent the
git operation from running.

src/server/codexAppServerBridge.ts[2863-2880]
src/server/codexAppServerBridge.ts[2883-2894]
src/server/codexAppServerBridge.ts[7119-7138]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`preserveUntrackedFilesForGitTarget()` moves conflicting untracked files via `rename()` but does not tolerate expected race/FS conditions (ENOENT if the file disappears, EPERM/EACCES on locked files, etc.). Any such error aborts the entire checkout/reset flow.

## Issue Context
This function is invoked as a pre-step for both branch checkout and hard reset. Because untracked files can change between `git ls-files` and the filesystem operations, this code should either (a) skip missing files, or (b) surface a clear, targeted error and continue/rollback as appropriate.

## Fix Focus Areas
- src/server/codexAppServerBridge.ts[2863-2880]
- src/server/codexAppServerBridge.ts[2883-2894]
- src/server/codexAppServerBridge.ts[7119-7138]

## Implementation notes
- Wrap `rename(sourcePath, backupPath)` in try/catch.
- If error code is `ENOENT`/`ENOTDIR`, continue (the file no longer blocks checkout/reset).
- For `EACCES`/`EPERM`, return a user-facing error message that clearly explains the file couldn’t be moved and therefore the operation cannot proceed.
- Consider recording which files were moved so you can include them in logs/response if needed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Commit shows base compare 🐞 Bug ≡ Correctness
Description
In ReviewPane, the meta row renders vs {{ snapshot.baseBranch }} whenever `activeScope ===
'baseBranch'`, even during commit review. Because commit review hides the Compare controls and
activeScope is not reset on commitSha changes, users can get a misleading “vs base branch” label
while viewing a commit diff.
Code

src/components/content/ReviewPane.vue[R102-106]

    <span class="review-pane-summary-pill review-pane-summary-pill-add">+{{ snapshot.summary.addedLineCount }}</span>
    <span class="review-pane-summary-pill review-pane-summary-pill-remove">-{{ snapshot.summary.removedLineCount }}</span>
    <span v-if="snapshot.headBranch">{{ snapshot.headBranch }}</span>
+      <span v-if="isCommitReview && snapshot.commitSha">{{ shortCommitSha(snapshot.commitSha) }}</span>
    <span v-if="activeScope === 'baseBranch' && snapshot.baseBranch">vs {{ snapshot.baseBranch }}</span>
Evidence
The template renders the vs label based on activeScope only, while commit review hides the
Compare controls and the commitSha watcher does not reset activeScope, allowing a
previously-selected baseBranch scope to persist into commit review.

src/components/content/ReviewPane.vue[100-107]
src/components/content/ReviewPane.vue[23-46]
src/components/content/ReviewPane.vue[375-376]
src/components/content/ReviewPane.vue[868-878]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In commit review mode (`props.commitSha` set), the Review pane can still show the base-branch comparison label (`vs <baseBranch>`) if the user previously selected `activeScope = 'baseBranch'`. This is misleading because the snapshot being loaded is a commit diff.
### Issue Context
- `activeScope` is a local ref and is **not** automatically aligned to the snapshot scope.
- Commit review hides Compare controls (`v-if="!isCommitReview"`), so the user cannot change the scope back while in commit mode.
### Fix Focus Areas
- src/components/content/ReviewPane.vue[100-107]
- src/components/content/ReviewPane.vue[23-46]
- src/components/content/ReviewPane.vue[868-878]
### Suggested fix
1. Update the meta-row `vs ...` rendering to also require `!isCommitReview`:
- `v-if="!isCommitReview && activeScope === 'baseBranch' && snapshot.baseBranch"`
2. Optionally (stronger), when `commitSha` becomes truthy, reset `activeScope` to `'workspace'` (and when it becomes falsy you may keep the prior scope or also reset), so internal state can’t drift into an invalid UI combination.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. No performance audit for review-summary 📘 Rule violation ➹ Performance
Description
This PR introduces new behavior that triggers additional Git-backed server work (e.g., the header
worktree +/- summary fetch) but includes no in-repo performance audit (measurements or concrete
code-path analysis). Without an audit, this change risks introducing regressions (extra
requests/CPU) that are hard to detect until users notice UI slowdowns.
Code

src/App.vue[R3131-3149]

+function loadThreadWorktreeChangeSummary(cwd: string): void {
+  const targetCwd = cwd.trim()
+  if (!targetCwd) {
+    threadWorktreeChangeSummary.value = { addedLineCount: 0, removedLineCount: 0 }
+    return
+  }
+  const requestId = ++threadWorktreeSummaryRequestId
+  void getReviewSummary(targetCwd, 'unstaged')
+    .then((summary) => {
+      if (requestId !== threadWorktreeSummaryRequestId || !canLoadBranchStateForCwd(targetCwd)) return
+      threadWorktreeChangeSummary.value = {
+        addedLineCount: summary.addedLineCount,
+        removedLineCount: summary.removedLineCount,
+      }
+    })
+    .catch(() => {
+      if (requestId !== threadWorktreeSummaryRequestId || !canLoadBranchStateForCwd(targetCwd)) return
+      threadWorktreeChangeSummary.value = { addedLineCount: 0, removedLineCount: 0 }
+    })
Evidence
PR Compliance ID 2 requires performance audit evidence for behavior changes. The new
loadThreadWorktreeChangeSummary() issues a new request (getReviewSummary) during branch-state
loads, and the server adds a new /codex-api/review/summary route that performs Git work; no
performance audit documentation is added alongside these changes.

AGENTS.md
src/App.vue[3131-3150]
src/server/reviewGit.ts[812-823]
src/server/reviewGit.ts[941-957]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR adds new feature/behavior that can impact performance (new summary fetch and server-side Git work), but there is no repo-local performance audit evidence (measurements or concrete code-path analysis) documenting expected cost and risks.
## Issue Context
Compliance requires documenting performance impact for behavior changes, either via measurements (timings, traces, request counts) or a concrete analysis of the code paths and expected scaling characteristics.
## Fix Focus Areas
- documentation/APP_SERVER_DOCUMENTATION.md[1-99999]
- src/App.vue[3131-3150]
- src/server/reviewGit.ts[812-823]
- src/server/reviewGit.ts[941-957]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (3)
11. Untracked summary reads full files 🐞 Bug ➹ Performance
Description
The /codex-api/review/summary implementation reads every untracked file fully into memory and scans
every byte to count lines, which can cause large CPU/RAM spikes and stall UI refreshes when
large/binary untracked files exist. This path is called during thread git-state refreshes (App.vue
loadThreadWorktreeChangeSummary), so the impact can be frequent.
Code

src/server/reviewGit.ts[R335-345]

+async function summarizeUntrackedFile(repoRoot: string, path: string): Promise<ReviewSummary> {
+  const absolutePath = join(repoRoot, ...path.split('/'))
+  const info = await stat(absolutePath)
+  if (!info.isFile()) {
+    return { fileCount: 0, addedLineCount: 0, removedLineCount: 0 }
+  }
+  const content = await readFile(absolutePath)
+  const addedLineCount = content.length === 0
+    ? 0
+    : content.reduce((count, byte) => count + (byte === 10 ? 1 : 0), content[content.length - 1] === 10 ? 0 : 1)
+  return { fileCount: 1, addedLineCount, removedLineCount: 0 }
Evidence
The server summary path enumerates untracked files and for each one reads the whole file into memory
to count newlines; the UI calls this endpoint on thread branch-state refresh, making the cost
user-visible and frequent.

src/server/reviewGit.ts[335-346]
src/server/reviewGit.ts[348-376]
src/server/reviewGit.ts[779-789]
src/App.vue[3131-3149]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`/codex-api/review/summary` counts untracked additions by `readFile()`-ing each untracked file and iterating each byte to count `\n`. This can be very expensive (RAM + CPU) for large files and can make header refreshes sluggish or unstable.
## Issue Context
The summary endpoint is used to show worktree +/- counts in the header Git dropdown. It’s invoked automatically during thread git-state refresh.
## Fix Focus Areas
- src/server/reviewGit.ts[335-376]
## Suggested fix
- Replace `readFile()` + `Buffer.reduce()` with a streaming line counter (`createReadStream`) to avoid loading entire files into memory.
- Add a size cap via `stat.size` (e.g., if > N MB, skip counting and treat as 0 or a capped approximation) to prevent pathological cases.
- Consider a lightweight binary heuristic (e.g., NUL byte in first chunk) and skip/cap binary files to avoid meaningless counts.
- Keep the endpoint responsive even when untracked files are huge.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. Untracked summary brittle to fs races 🐞 Bug ☼ Reliability
Description
Untracked-file summarization does not handle stat/read failures (e.g., file deleted between `git
status and stat()/readFile()`), which can make /codex-api/review/summary return 500. The UI then
silently falls back to 0/0 counts, causing incorrect header deltas and masking the underlying error.
Code

src/server/reviewGit.ts[R371-374]

+  const statusOutput = await runCommandCapture('git', ['status', '--porcelain=v1', '--untracked-files=all'], { cwd: repoRoot })
+  for (const path of parseUntrackedPaths(statusOutput)) {
+    summary = addReviewSummary(summary, await summarizeUntrackedFile(repoRoot, path))
+  }
Evidence
The server loop awaits per-file summarization without guarding fs operations, and the route handler
returns a 500 on any thrown error; the App caller swallows failures and resets to 0/0, producing
incorrect UI counts.

src/server/reviewGit.ts[348-376]
src/server/reviewGit.ts[335-345]
src/server/reviewGit.ts[908-923]
src/App.vue[3137-3149]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
While building the review summary, the server iterates untracked paths and immediately `stat()`s/`readFile()`s them. If any untracked file disappears or becomes unreadable during this window, the entire summary request can fail.
## Issue Context
This endpoint powers the header Git dropdown +/- counts. Failures are common in repos with temp files (editors, build tools) and lead to misleading 0/0 deltas.
## Fix Focus Areas
- src/server/reviewGit.ts[335-376]
- src/server/reviewGit.ts[908-923]
## Suggested fix
- Wrap `stat()`/`readFile()` in `summarizeUntrackedFile()` with error handling.
- On `ENOENT` (deleted) return `{fileCount:0, addedLineCount:0, removedLineCount:0}`.
- On `EACCES`/`EPERM` either skip similarly or count as `{fileCount:1, addedLineCount:0, removedLineCount:0}` depending on desired semantics.
- Keep other unexpected errors as hard failures so real bugs still surface.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. Stale errors hide cached data 🐞 Bug ≡ Correctness
Description
In src/App.vue, loadThreadBranchCommits/loadThreadCommitFiles return early on cache hits before
clearing threadBranchCommitsError/threadCommitFilesError. This can leave a previous failure message
active and cause HeaderGitBranchDropdown to render an error state instead of cached commits/files
for the newly selected branch/commit.
Code

src/App.vue[R3256-3266]

+function loadThreadBranchCommits(payload: string | { branch: string; includeResetHistory?: boolean }): void {
+  const targetBranch = (typeof payload === 'string' ? payload : payload.branch).trim()
+  const includeResetHistory = typeof payload === 'string' ? true : payload.includeResetHistory !== false
const cwd = composerCwd.value.trim()
-  if (!targetBranch || !cwd || threadBranchCommitsLoadingFor.value === targetBranch) return
-  if (threadBranchCommitsByBranch.value[targetBranch]) return
+  const cacheKey = toThreadBranchCommitsKey(targetBranch, includeResetHistory)
+  if (!targetBranch || !cwd || threadBranchCommitsLoadingFor.value === cacheKey) return
+  if (threadBranchCommitsByBranch.value[cacheKey]) return
const requestId = ++threadBranchCommitsRequestId
-  threadBranchCommitsLoadingFor.value = targetBranch
+  threadBranchCommitsLoadingFor.value = cacheKey
threadBranchCommitsError.value = ''
-  void getGitBranchCommits(cwd, targetBranch)
+  void getGitBranchCommits(cwd, targetBranch, { includeResetHistory })
Evidence
App.vue shows cache-hit early returns that occur before the corresponding error-reset lines, so the
error strings can persist. HeaderGitBranchDropdown.vue renders commitFilesError/commitsError in
v-else-if branches ahead of rendering the lists, so a persisted error hides cached content.

src/App.vue[3256-3310]
src/components/content/HeaderGitBranchDropdown.vue[38-108]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`loadThreadBranchCommits` and `loadThreadCommitFiles` clear their error state only after the cache-hit early return checks. If an earlier request set an error, then later selecting a branch/commit whose data is already cached can keep the error string non-empty, and the dropdown will render the error UI instead of the cached list.
## Issue Context
The dropdown template prioritizes `commitsError` / `commitFilesError` display (`v-else-if`) before rendering the cached lists, so stale errors effectively block the happy-path UI.
## Fix Focus Areas
- Move `threadBranchCommitsError.value = ''` to occur *before* the `if (threadBranchCommitsByBranch.value[cacheKey]) return` cache-hit return.
- Move `threadCommitFilesError.value = ''` to occur *before* the `if (threadCommitFilesBySha.value[targetSha]) return` cache-hit return.
- (Optional) Consider scoping errors by `cacheKey` / `sha` (e.g., `Record<string,string>`) so different selections can't overwrite each other.
### References
- src/App.vue[3256-3310]
- src/components/content/HeaderGitBranchDropdown.vue[38-108]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

@friuns2 friuns2 marked this pull request as draft May 12, 2026 02:34
@friuns2 friuns2 marked this pull request as ready for review May 12, 2026 02:34
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 12, 2026

Persistent review updated to latest commit c55825a

Comment thread tests.md Outdated
@friuns2 friuns2 marked this pull request as draft May 12, 2026 03:11
@friuns2 friuns2 marked this pull request as ready for review May 12, 2026 03:11
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 12, 2026

Persistent review updated to latest commit 597a12b

Comment on lines +169 to +177
<button
v-if="branch.value === selectedBranch && branch.value !== currentBranch"
class="header-git-branch-checkout"
type="button"
:disabled="busy"
@click="emit('checkoutBranch', branch.value)"
>
Checkout
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Remote checkout detaches head 🐞 Bug ≡ Correctness

HeaderGitBranchDropdown shows a "Checkout" action for remote branches, but the server checkout route
accepts remote ref names and runs git checkout <ref>, which can detach HEAD and break the intended
branch-based flow.
Agent Prompt
### Issue description
Remote branches (e.g. `origin/main`) can be selected and then checked out via the new `Checkout` button. The server’s `/codex-api/git/checkout` route does not restrict to local branches and calls `git checkout <branch>`, which will commonly result in a detached HEAD for remote refs.

### Issue Context
- UI already surfaces `branch.isRemote` (badge), but the `Checkout` button condition does not account for it.
- Server-side reset-to-commit explicitly enforces local branches via `assertLocalGitBranch`, but checkout does not.

### Fix Focus Areas
- src/components/content/HeaderGitBranchDropdown.vue[155-177]
- src/server/codexAppServerBridge.ts[6552-6584]

### What to change
1. UI: Hide or disable the `Checkout` button for `branch.isRemote === true` (and optionally show a tooltip/message).
2. Server: Add `await assertLocalGitBranch(gitRoot, targetBranch)` in the checkout route (or implement a safe remote checkout behavior like creating a local tracking branch explicitly, if that’s desired).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@friuns2 friuns2 marked this pull request as draft May 12, 2026 03:21
@friuns2 friuns2 marked this pull request as ready for review May 12, 2026 03:21
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 12, 2026

Persistent review updated to latest commit 374c8c2

Comment thread tests.md
Comment on lines 785 to +857
#### Steps
1. In light theme, open the Git dropdown in the thread header.
2. Confirm the trigger shows the current branch, or the detached commit subject if the repository is already detached.
3. Click `Review` and confirm the review pane opens; click it again and confirm the pane toggles.
4. Type part of a branch name in search and confirm the branch list filters.
5. Select a different branch with a clean worktree and confirm the header updates to that branch.
6. Expand a branch row and confirm recent commits load with short SHA, subject, and date.
7. Expand a remote branch row and confirm its commit rows are disabled with a tooltip explaining remote branches cannot be reset.
8. Select an older commit on the disposable local branch and confirm the header stays on that branch instead of entering detached HEAD.
9. Confirm `git -C <thread-cwd> rev-parse --abbrev-ref HEAD` still prints the branch name and `git -C <thread-cwd> rev-parse --short HEAD` matches the selected commit.
10. Reopen/expand the same branch and confirm commits that were ahead of the reset target still appear, with the selected branch HEAD marked `current`.
11. Repeat reset on the same branch several times and confirm the dropdown still opens quickly and shows recent reset-history commits.
12. Create a tracked uncommitted change, try to switch branch or reset to a commit, and confirm the dropdown shows a dirty-worktree error instead of switching or resetting.
13. Create only an untracked file, try to reset to a commit, and confirm the reset proceeds unless Git reports the untracked file would be overwritten.
14. Switch to dark theme and repeat steps 1, 2, 4, 6, 7, 10, 12, and 13.

#### Expected Results
- The header dropdown exposes Review, current checkout state, searchable branches, and inline commits.
- Branch switching and branch reset-to-commit are blocked by tracked uncommitted changes, but untracked-only changes are allowed unless Git would overwrite them.
- Commit selection resets the local branch to that commit instead of detaching HEAD.
- Remote branch commit rows are inspectable but cannot trigger local branch reset.
3. Confirm the menu initially shows only commits on the left and branches on the right, with no commit-files panel before a commit is selected.
4. Confirm the left column defaults to the current branch and shows no more than 50 recent commits with short SHA, subject, and date.
5. Confirm the open dropdown visually layers above the sidebar and above the Review pane if the pane is already open.
6. Confirm the top action is styled as a button, reads `Review Worktree Changes`, and shows `+`/`-` line counts; click it and confirm the dropdown stays open while the review pane opens above it directly to changes without showing a `Findings` tab or `Run review` button; click the Review pane `X` and confirm only the pane closes while the dropdown stays open; click `Review Worktree Changes` again and confirm the pane toggles open.
7. Type part of a commit subject or short SHA in the left commit search and confirm the commit list filters.
8. Turn off `Reset-history refs` and confirm the commit list reloads without saved reset-history refs.
9. Turn `Reset-history refs` back on and confirm saved reset-history commits reappear when available.
10. Toggle `Reset-history refs` while the commit list is still loading and confirm the list reloads for the new toggle state instead of staying on the previous result.
11. Type part of a branch name in search and confirm the branch list filters.
12. Click a different branch row and confirm the left commit list changes to that branch without immediately switching checkout.
13. Use the branch row `Checkout` action with a clean worktree and confirm the header updates to that branch.
14. Confirm local branches appear first and remote branches appear at the end of the branch list.
15. Select an older commit on the disposable local branch and confirm the dropdown widens and shows a left-side file panel with that commit subject, file changes, per-file `+`/`-` line counts, and a `Reset` button without changing HEAD.
16. Click a commit ref badge and confirm the full commit SHA is copied to the clipboard without changing the selected commit.
17. Click a file in the selected commit details and confirm the dropdown closes and the Review pane opens in commit mode, with the selected commit diff loaded and that file selected without auto-centering the first hunk.
18. Click `Reset` for the selected commit and confirm the header stays on that branch instead of entering detached HEAD.
19. Confirm `git -C <thread-cwd> rev-parse --abbrev-ref HEAD` still prints the branch name and `git -C <thread-cwd> rev-parse --short HEAD` matches the selected commit.
20. Reopen/select the same branch and confirm commits that were ahead of the reset target still appear, with the selected branch HEAD marked `current`.
21. Repeat reset on the same branch several times and confirm the dropdown still opens quickly and shows recent reset-history commits.
22. Create a tracked uncommitted change, try to switch branch or reset to a commit, and confirm the dropdown shows a dirty-worktree error instead of switching or resetting.
23. Create only an untracked file whose path does not exist in the target commit, try to reset to a commit, and confirm the reset proceeds while the untracked file remains in place.
24. Create only an untracked file whose path exists in the target commit, try to reset to that target, and confirm the reset proceeds and the untracked file is moved under `.codex/untracked-backups/` instead of being overwritten.
25. At a mobile viewport around 375px wide, select a commit and confirm the dropdown fits inside the viewport with branches first, commits second, and selected-commit files last, stacked vertically instead of squeezed into columns.
26. Narrow the Review pane file list and confirm changed-file rows do not inherit folder-depth indentation, long names truncate on one line instead of wrapping vertically, and the `+`/`-` counts remain visible.
27. At a mobile viewport around 375px wide, open the Review pane, scroll the diff content vertically, and confirm the `X` close button remains visible and tappable in the top-right corner.
28. Switch to dark theme and repeat steps 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 16, 17, 20, 22, 23, 24, 25, 26, and 27.

#### Preserved Prior Coverage
1. Click `Review Worktree Changes` and confirm the review pane opens; click it again and confirm the pane toggles.
2. Type part of a branch name in search and confirm the branch list filters.
3. Select a different branch with a clean worktree using the checkout action and confirm the header updates to that branch.
4. Select a branch row and confirm recent commits load with short SHA, subject, and date.
5. Confirm remote branch rows are inspectable, appear after local branches, and do not trigger local reset without a supported local branch action.
6. Select an older commit on the disposable local branch and confirm the header stays on that branch instead of entering detached HEAD.
7. Confirm `git -C <thread-cwd> rev-parse --abbrev-ref HEAD` still prints the branch name and `git -C <thread-cwd> rev-parse --short HEAD` matches the selected commit after reset.
8. Reopen/select the same branch and confirm commits that were ahead of the reset target still appear, with the selected branch HEAD marked `current`.
9. Repeat reset on the same branch several times and confirm the dropdown still opens quickly and shows recent reset-history commits.
10. Create a tracked uncommitted change, try to switch branch or reset to a commit, and confirm the dropdown shows a dirty-worktree error instead of switching or resetting.
11. Create only an untracked file, try to reset to a commit, and confirm the reset proceeds while preserving the untracked file in place or moving it to `.codex/untracked-backups/` if the target would overwrite it.
12. Switch to dark theme and repeat the branch filtering, commit loading, reset-history, dirty-worktree, and untracked-file checks.

#### Expected Results
- The header dropdown exposes Review, current checkout state, a left-side commit list, and a right-side searchable branch list before a commit is selected.
- The selected-commit file panel is hidden until commit selection, then appears on the left and expands the dropdown width.
- Each selected-commit file row shows added and removed line counts, using `-` for binary or unavailable counts.
- The dropdown layer is viewport-positioned and appears above the sidebar when open.
- The Review pane renders above the open dropdown and app chrome.
- Clicking the dropdown `Review Worktree Changes` button keeps the dropdown open while toggling the Review pane.
- Clicking the Review pane `X` while the dropdown is open closes only the Review pane and leaves the dropdown open.
- The `Review Worktree Changes` button shows current worktree added and removed line counts.
- The Review pane toolbar keeps `Refresh` but does not show a `Findings` tab or `Run review` button.
- The current branch commit list loads by default and is capped at 50 commits.
- The commit list can be searched by SHA, subject, or date without changing the selected branch.
- Reset-history refs can be shown or hidden from the commit list without changing the selected branch.
- Reset-history toggles are keyed by both branch and reset-history state, so an in-flight load for one state does not suppress loading the other state.
- Branch switching and branch reset-to-commit are blocked by tracked uncommitted changes, but untracked-only changes are preserved and allowed.
- Commit selection opens file details without resetting or detaching HEAD.
- Commit ref badges copy the full SHA to the clipboard without triggering commit selection.
- The selected commit `Reset` button resets the local branch to that commit instead of detaching HEAD.
- Clicking a selected commit file opens the Review pane against that commit diff and selects that path without auto-centering the selected hunk.
- Changed-file rows in the Review pane file list do not inherit folder-depth indentation, so long names have enough room to truncate horizontally instead of wrapping one character per line when the list is narrow.
- Remote branches appear after local branches in the branch list.
- Prior branch-search, inline-commit, remote-branch inspection, reset-to-commit, dirty-worktree, and untracked-file manual coverage remains represented in the section.
- The branch commit list still shows commits that were ahead of the reset target by reading saved internal reset-history refs.
- Reset-history refs are bounded so repeated resets do not grow commit-list inputs without limit.
- The selected branch HEAD commit is marked `current` in expanded commit lists.
- Untracked files that would collide with target tracked files are moved to `.codex/untracked-backups/` before checkout/reset.
- The selected branch HEAD commit is marked `current` in the commit list.
- The mobile Review pane keeps its close button visible above the app chrome in both light theme and dark theme.
- The mobile Review pane diff area scrolls vertically without moving or hiding the pane header.
- On mobile, branches, commits, and selected-commit file details stack vertically in that order and stay inside the viewport in both light theme and dark theme.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. tests.md missing cleanup notes 📘 Rule violation ⚙ Maintainability

The updated manual test section introduces steps that change Git state and create untracked/tracked
files, but it does not include any rollback/cleanup guidance. This makes the instructions
non-reproducible and can leave the repo/worktree (and .codex/untracked-backups/) in a modified
state after testing.
Agent Prompt
## Issue description
The updated `tests.md` section for the header Git dropdown/review flow lacks rollback/cleanup notes, despite instructing testers to reset branches and create untracked/tracked files.

## Issue Context
Per the manual test documentation requirements, sections should include rollback/cleanup notes when applicable. This feature’s test steps include operations that can alter checkout state and move files into `.codex/untracked-backups/`, so cleanup guidance is applicable.

## Fix Focus Areas
- tests.md[785-857]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/server/reviewGit.ts
Comment on lines +434 to +441
async function buildCommitDiff(repoRoot: string, commitSha: string): Promise<{ diffText: string; commitSha: string }> {
const resolvedSha = await runCommandCapture('git', ['rev-parse', '--verify', `${commitSha}^{commit}`], { cwd: repoRoot })
const diffText = await runCommandCapture(
'git',
['diff-tree', '--root', '--no-commit-id', '--no-ext-diff', '--find-renames', '--patch', resolvedSha],
{ cwd: repoRoot },
)
return { diffText, commitSha: resolvedSha }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Commit diff not recursive 🐞 Bug ≡ Correctness

buildCommitDiff() runs git diff-tree without -r, so commit review mode can return an
empty/incomplete patch for commits whose changes are only in subdirectories. This breaks commit-file
review mode because the Review pane snapshot may show no files/hunks despite real changes.
Agent Prompt
### Issue description
Commit review snapshots are generated via `git diff-tree` without the `-r` (recursive) flag, which can omit file-level diffs for commits that only modify files inside subdirectories.

### Issue Context
The server already uses `git diff-tree ... -r` elsewhere (commit-files endpoint), indicating recursion is expected for correct commit traversal.

### Fix Focus Areas
- src/server/reviewGit.ts[434-441]

### Implementation notes
- Update the `runCommandCapture('git', ['diff-tree', ...])` argv in `buildCommitDiff` to include `-r` (and keep existing flags like `--root`, `--find-renames`, `--patch`).
- Consider matching the commit-files endpoint behavior/flags for consistency.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@friuns2 friuns2 marked this pull request as draft May 12, 2026 03:34
@friuns2 friuns2 marked this pull request as ready for review May 12, 2026 03:34
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 12, 2026

Persistent review updated to latest commit 88653f3

Comment on lines +933 to +937
@apply fixed inset-3 z-[1200] flex min-h-0 min-w-0 flex-col overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-2xl;
}

.review-pane.is-mobile {
@apply fixed inset-0 z-40 rounded-none border-0;
@apply inset-0 rounded-none border-0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. review-pane lacks dark styles 📘 Rule violation ≡ Correctness

The updated Review pane overlay uses light-surface styling (e.g., bg-white) but no dark-theme
override is added in src/style.css, so it will render as a light surface in dark mode. This
violates the requirement to centralize decisive dark overrides for shared route surfaces in the
global stylesheet.
Agent Prompt
## Issue description
The Review pane is a shared, full-surface overlay and now explicitly uses light-surface styles (`bg-white`, light borders). There are no corresponding global `:root.dark ...` overrides in `src/style.css`, risking a light UI surface in dark mode.

## Issue Context
Compliance requires decisive dark-theme overrides for shared route surfaces/large feature UIs to live in `src/style.css`.

## Fix Focus Areas
- src/components/content/ReviewPane.vue[933-937]
- src/style.css[891-992]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@friuns2 friuns2 marked this pull request as draft May 12, 2026 03:41
@friuns2 friuns2 marked this pull request as ready for review May 12, 2026 03:41
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 12, 2026

Persistent review updated to latest commit 9c42465

Comment thread src/server/reviewGit.ts Outdated
Comment on lines +371 to +374
const statusOutput = await runCommandCapture('git', ['status', '--porcelain=v1', '--untracked-files=all'], { cwd: repoRoot })
for (const path of parseUntrackedPaths(statusOutput)) {
summary = addReviewSummary(summary, await summarizeUntrackedFile(repoRoot, path))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Untracked filenames get trimmed 🐞 Bug ≡ Correctness

The new /codex-api/review/summary path can return wrong counts (or 500) because untracked paths are
parsed from git status --porcelain=v1 with .trim(), which changes filenames and can’t represent
Git’s quoted/escaped porcelain paths. That trimmed path is then fed into stat()/readFile(), so
repos with untracked filenames that include leading/trailing whitespace (or require quoting) can
break the header worktree +/- summary.
Agent Prompt
## Issue description
The review summary endpoint computes untracked file counts using `git status --porcelain=v1` and `parseUntrackedPaths()` which trims path text. This can corrupt filenames (leading/trailing whitespace) and fails for porcelain-quoted paths, leading to incorrect summary counts or thrown fs errors.

## Issue Context
- `/codex-api/review/summary` is now used to power the header worktree +/- counts.
- `parseUntrackedPaths()` currently does `line.slice(3).trim()`.
- `summarizeUntrackedFile()` calls `stat()`/`readFile()` using that derived path.

## Fix Focus Areas
- src/server/reviewGit.ts[281-289]
- src/server/reviewGit.ts[335-346]
- src/server/reviewGit.ts[348-375]
- src/server/reviewGit.ts[902-924]

## Implementation notes
- Prefer a NUL-delimited source of untracked paths (e.g. `git ls-files --others --exclude-standard -z`) to avoid porcelain quoting/escaping entirely.
- Do not apply `.trim()` to filenames.
- Make `summarizeUntrackedFile()` robust: catch ENOENT / non-file cases and return a zero summary instead of throwing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/server/codexAppServerBridge.ts Outdated
Comment on lines +7037 to +7055
for (const line of numstatOutput.split('\n')) {
const parts = line.split('\t')
const addedRaw = parts[0]?.trim() ?? ''
const removedRaw = parts[1]?.trim() ?? ''
const path = (parts.length >= 4 ? parts[3] : parts[2])?.trim() ?? ''
if (!path) continue
const addedLineCount = /^\d+$/.test(addedRaw) ? Number(addedRaw) : null
const removedLineCount = /^\d+$/.test(removedRaw) ? Number(removedRaw) : null
lineCountsByPath.set(path, { addedLineCount, removedLineCount })
}
const files = output.split('\n').flatMap((line) => {
const parts = line.split('\t').map((part) => part.trim()).filter(Boolean)
const status = parts[0] ?? ''
if (!status) return []
const statusKind = status.charAt(0)
const isRenameOrCopy = (statusKind === 'R' || statusKind === 'C') && parts.length >= 3
const path = isRenameOrCopy ? parts[2] : parts[1]
const previousPath = isRenameOrCopy ? parts[1] : null
if (!path) return []
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Commit file paths altered 🐞 Bug ≡ Correctness

The new /codex-api/git/commit-files endpoint (and the client normalizer) trims and tab-splits paths
coming from git diff-tree, which can corrupt legitimate filenames containing leading/trailing
whitespace and can desync name-status vs numstat path matching. This can cause the commit file list
to show wrong/missing entries and prevent opening the intended file in commit review mode.
Agent Prompt
## Issue description
The commit-files API parses `git diff-tree` output using newline/tab splitting and applies `.trim()` to parsed path fields. This can change the actual filename (e.g. leading/trailing spaces) and makes parsing ambiguous for filenames containing special characters. The client then trims again, further ensuring the UI cannot round-trip exact paths.

## Issue Context
- Server: `/codex-api/git/commit-files` builds `files` via `diff-tree --name-status` and separately parses `diff-tree --numstat`, trimming the `path` field in both.
- Client: `getGitCommitFiles()` trims `record.path` and `record.previousPath`.

## Fix Focus Areas
- src/server/codexAppServerBridge.ts[7011-7071]
- src/api/codexGateway.ts[2725-2751]

## Implementation notes
- Switch both `--name-status` and `--numstat` invocations to `-z` and parse NUL-delimited records.
- Do not `.trim()` path fields; treat them as opaque.
- Ensure rename/copy parsing keeps both previousPath and path exactly as emitted.
- Update client normalizer to avoid trimming `path`/`previousPath` (it may still validate type/emptiness without modifying content).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@friuns2 friuns2 marked this pull request as draft May 12, 2026 03:53
@friuns2 friuns2 marked this pull request as ready for review May 12, 2026 03:53
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 12, 2026

Persistent review updated to latest commit 7c8313e

@friuns2 friuns2 marked this pull request as draft May 12, 2026 04:02
@friuns2 friuns2 marked this pull request as ready for review May 12, 2026 04:02
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 12, 2026

Persistent review updated to latest commit 9cad38f

@friuns2 friuns2 marked this pull request as draft May 12, 2026 12:52
@friuns2 friuns2 marked this pull request as ready for review May 12, 2026 12:52
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 12, 2026

Persistent review updated to latest commit a9105d6

@friuns2 friuns2 marked this pull request as draft May 12, 2026 13:06
@friuns2 friuns2 marked this pull request as ready for review May 12, 2026 13:06
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 12, 2026

Persistent review updated to latest commit 33764be

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