Improve Files and Changes workflow - #52
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The file explorer’s immediate onActiveDiffEntriesChange emission can publish diff entries in an inconsistent order, causing incorrect default/primary entry selection for embedded per-file changes.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR enhances the workspace “Files” and “Changes” inspector experience in web/src by sharing and reusing Git diff summary state, improving per-file change visibility, and adding richer navigation/accessibility for file trees and diffs.
Changes:
- Introduces a shared, keyed Git diff summary store with deduping, queuing, and resource retirement, plus unit tests.
- Unifies git status badge presentation via normalized U/A/M/D/C codes, and preserves multiple entries for partially staged files.
- Embeds per-file diffs into the file preview, adds diff hunk navigation, and improves keyboard navigation + ARIA roles for the file tree and menus.
File summaries
| File | Description |
|---|---|
| web/src/styles.css | Adds styling for focused rows, git status code badges, embedded diff view, preview tabs, and hunk navigation UI. |
| web/src/gitDiffSummaryStore.ts | New shared external-store cache for diff summaries with bounded retention and race handling. |
| web/src/gitDiffSummaryStore.test.ts | Tests deduping, queued refresh, retirement behavior for the shared diff summary store. |
| web/src/gitDiffStatus.ts | Adds normalization for git diff entry kinds/statuses into U/A/M/D/C plus human labels. |
| web/src/gitDiffStatus.test.ts | Tests code normalization for mixed explorer/diff-viewer states. |
| web/src/components/WorkspaceInspectorHost.tsx | Wires file explorer to provide active diff entries; adds embedded per-file changes view and keyboard focus handoff. |
| web/src/components/FilePreviewContent.tsx | Adds accessible File/Changes tabs and supports rendering embedded changes content. |
| web/src/components/FileExplorerDialog.tsx | Integrates shared diff summary, adds keyboard tree navigation + context menu roles, improves preview cache invalidation. |
| web/src/components/FileExplorerDialog.test.ts | Adds tests for partial-stage entry preservation and preview invalidation behavior. |
| web/src/components/DiffViewerPanel.tsx | Switches to shared diff summary store, adds working-entry selection helpers, aligns status badges to U/A/M/D/C. |
| web/src/components/DiffViewerPanel.test.ts | Updates/extends directory expansion behavior tests for refreshed trees. |
| web/src/components/DiffContentView.tsx | Adds embedded mode, hunk navigation with virtualized targeting, and inline “Load diff/Retry” actions. |
| web/src/components/DiffContentView.test.ts | Tests hunk target parsing and hunk index navigation behavior. |
| CHANGELOG.md | Documents the user-visible workflow improvements under Unreleased. |
Review details
- Files reviewed: 14/14 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🔵 Needs a closer look
The change set spans multiple stateful UI surfaces (shared caching, keyboard navigation, embedded diff rendering) where subtle regressions are hard to rule out via diff-only review.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
web/src/components/DiffViewerPanel.tsx:1273
statusCodesis derived from a Set, so its iteration order depends onchild.entriesordering. Sorting the resulting array makes badge order stable across refreshes and avoids unnecessary DOM churn.
const statusCodes = Array.from(
new Set(child.entries.map((entry) => gitDiffCode(entry))),
);
web/src/components/FileExplorerDialog.tsx:546
- Directory
codesare built from a Set and converted withArray.from(...), which keeps insertion order and can vary based on server entry ordering. Sorting the result avoids non-deterministic badge ordering.
codes: Array.from(directoryCodes.get(path) ?? []),
- Files reviewed: 14/14 changed files
- Comments generated: 1
- Review effort level: Lite
There was a problem hiding this comment.
🔵 Needs a closer look
DiffViewerPanel can leave summaryRequestLoading stuck true when the diff context changes during an in-flight refresh, disabling the UI until reload.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
web/src/components/DiffViewerPanel.tsx:817
loadSummaryusessummaryRequestLoadingto disable/animate refresh controls, but it can get stucktrueif the diff scope/workspace context changes while a refresh is in flight (thefinallyguard prevents clearing it). Track the latest summary refresh with a request sequence/token so only the newest request clears the loading flag, regardless of scope changes.
This issue also appears on line 945 of the same file.
const pendingWorkingEntriesRef = useRef<GitDiffEntry[]>([]);
const preferredSummarySelectionRef = useRef<GitDiffEntry | null>(null);
const diffScopeRef = useRef(diffScope);
diffScopeRef.current = diffScope;
const onSelectionChangeRef = useRef(onSelectionChange);
web/src/components/DiffViewerPanel.tsx:949
- The
finallyblock only clearssummaryRequestLoadingwhenisCurrentContext(workspaceId, scope)is still true. If the user switches scope/workspace during an in-flight refresh, this leavessummaryRequestLoadingstucktrue(and the UI disabled). Use a request sequence/token to ensure only the most recent refresh clears the flag even when context changes.
const scope = diffScope;
preferredSummarySelectionRef.current = previousSelected;
advanceDiffCacheRevision(
diffCacheKey(connectionClient, workspaceId, scope, cacheResourceKey),
);
- Files reviewed: 14/14 changed files
- Comments generated: 0 new
- Review effort level: Lite
There was a problem hiding this comment.
🔵 Needs a closer look
The changes span multiple interdependent UI state/caching paths (shared summary store, embedded diffs, and keyboard navigation) where subtle race/focus regressions are hard to fully validate from diff review alone.
Review details
- Files reviewed: 14/14 changed files
- Comments generated: 0 new
- Review effort level: Lite
Summary
Verification
bun test— 699 passed, 1 skippedbun run format:checkbun run lintcd web && bun run typecheckcd web && bun run build