feat(web): enhanced file panel — side-by-side diff, hide whitespace, inline review

Adds DiffSplitView component for side-by-side diff mode, whitespace-only
change filtering, inline review comments with thread/gutter cell UI, diff
preferences persistence, and write-file API support for in-browser editing.

Backend: hideWhitespace param on git diff endpoint, write_file route.
This commit is contained in:
2026-06-07 22:16:20 +00:00
parent eceae1475c
commit 811e59d7bb
15 changed files with 1247 additions and 47 deletions

View File

@@ -3,7 +3,7 @@ import { api } from '@/api/client';
import type { GitDiffMode, GitDiffResult, GitDiscardFileInfo } from '@/api/types';
import { sessionEvents } from './sessionEvents';
export function useGitDiff(projectId: string | null | undefined) {
export function useGitDiff(projectId: string | null | undefined, hideWhitespace = false) {
const [mode, setMode] = useState<GitDiffMode>('uncommitted');
const [pinned, setPinned] = useState(false);
const [result, setResult] = useState<GitDiffResult | null>(null);
@@ -23,7 +23,7 @@ export function useGitDiff(projectId: string | null | undefined) {
// FIX 1: when not pinned, omit mode param so the server auto-selects based on
// dirty state (dirty → uncommitted, clean → committed).
api.projects
.gitDiff(projectId, pinned ? mode : null)
.gitDiff(projectId, pinned ? mode : null, hideWhitespace)
.then((r) => {
if (!pinned) {
setMode(r.mode);
@@ -43,7 +43,7 @@ export function useGitDiff(projectId: string | null | undefined) {
inFlightRef.current = false;
setLoading(false);
});
}, [projectId, mode, pinned]);
}, [projectId, mode, pinned, hideWhitespace]);
// Re-run refresh when mode changes (user pinned a new mode).
useEffect(() => {
@@ -52,7 +52,7 @@ export function useGitDiff(projectId: string | null | undefined) {
return;
}
refresh();
}, [projectId, mode]); // eslint-disable-line react-hooks/exhaustive-deps
}, [projectId, mode, hideWhitespace]); // eslint-disable-line react-hooks/exhaustive-deps
// Subscribe to git_diff_refresh events (tab open, message_complete, manual).
useEffect(() => {