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

@@ -30,6 +30,10 @@ import type {
BattleShape,
ContestantShape,
CrossExaminationShape,
AnalyticsSummary,
SessionAnalyticsRow,
ContextWindowStats,
TokenBreakdownAgg,
} from './types';
// v2.6 Phase 1-UX §9b: chat-scoped agent-session rows. Returned by
@@ -159,12 +163,13 @@ export const api = {
request<{ files: string[] }>(`/api/projects/${id}/files`),
git: (id: string) =>
request<GitMeta>(`/api/projects/${id}/git`),
gitDiff: (id: string, mode: GitDiffMode | null) =>
request<GitDiffResult>(
mode !== null
? `/api/projects/${id}/git/diff?mode=${mode}`
: `/api/projects/${id}/git/diff`,
),
gitDiff: (id: string, mode: GitDiffMode | null, hideWhitespace?: boolean) => {
const params: string[] = [];
if (mode !== null) params.push(`mode=${mode}`);
if (hideWhitespace) params.push('whitespace=1');
const qs = params.length > 0 ? `?${params.join('&')}` : '';
return request<GitDiffResult>(`/api/projects/${id}/git/diff${qs}`);
},
gitStage: (id: string, files: string[]) =>
request<{ ok: boolean }>(`/api/projects/${id}/git/stage`, {
method: 'POST',
@@ -185,6 +190,11 @@ export const api = {
method: 'POST',
body: JSON.stringify({ files }),
}),
writeFile: (id: string, filePath: string, content: string) =>
request<{ ok: boolean }>(`/api/projects/${id}/write_file`, {
method: 'POST',
body: JSON.stringify({ path: filePath, content }),
}),
},
sessions: {
@@ -590,6 +600,14 @@ export const api = {
costStats: () => request<{ stats: ToolCostStat[] }>('/api/tools/cost_stats'),
},
// token-analyzer-ui: analytics aggregate endpoints.
analytics: {
summary: () => request<AnalyticsSummary>('/api/coder/analytics/summary'),
sessions: () => request<{ sessions: SessionAnalyticsRow[] }>('/api/coder/analytics/sessions'),
context: () => request<ContextWindowStats>('/api/analytics/context'),
tokenBreakdown: () => request<{ categories: TokenBreakdownAgg[] }>('/api/coder/analytics/token-breakdown'),
},
settings: {
get: () => request<Record<string, unknown>>('/api/settings'),
patch: (body: Record<string, unknown>) =>