agent-ecosystem/src/renderer/hooks/useViewedFiles.ts
iliya 190cafdb8e feat: implement diff view with 4 phases — review, accept/reject, task scoping, enhanced UX
Phase 1: Core diff extraction and display
- ChangeExtractorService: JSONL streaming parser with snippet extraction
- FileContentResolver: 3-level content resolution (file-history → snippets → disk)
- ReviewApplierService: hunk-level accept/reject with conflict detection
- CodeMirrorDiffView: unified merge view with syntax highlighting
- ReviewFileTree: file browser with status indicators
- changeReviewSlice: Zustand state for review workflow

Phase 2: Interactive review with accept/reject
- Per-hunk and per-file accept/reject decisions
- Conflict checking before apply
- ReviewToolbar with bulk actions
- DiffErrorBoundary for graceful degradation

Phase 3: Per-task change scoping
- TaskBoundaryParser: detects task boundaries in JSONL (Tier 1-4 confidence)
- TaskChangeSetV2 with scope + warnings
- ConfidenceBadge and ScopeWarningBanner components

Phase 4: Enhanced features
- Keyboard navigation (j/k/n/p/a/x shortcuts via useDiffNavigation)
- Viewed file tracking (localStorage + useViewedFiles hook)
- File edit timeline (chronological events per file)
- Git fallback (GitDiffFallback service for incomplete JSONL data)
- Auto-viewed detection (IntersectionObserver sentinel)
2026-02-24 23:39:41 +02:00

74 lines
2 KiB
TypeScript

import { useCallback, useMemo, useState } from 'react';
import * as storage from '@renderer/utils/diffViewedStorage';
interface UseViewedFilesResult {
viewedSet: Set<string>;
isViewed: (filePath: string) => boolean;
markViewed: (filePath: string) => void;
unmarkViewed: (filePath: string) => void;
markAllViewed: (filePaths: string[]) => void;
clearAll: () => void;
viewedCount: number;
totalCount: number;
/** Progress 0-100 */
progress: number;
}
export function useViewedFiles(
teamName: string,
scopeKey: string,
totalFiles: string[]
): UseViewedFilesResult {
// version bump pattern for re-reading localStorage
const [version, setVersion] = useState(0);
const viewedSet = useMemo(() => {
// version is used to trigger re-read
if (version < 0) return new Set<string>();
return storage.getViewedFiles(teamName, scopeKey);
}, [teamName, scopeKey, version]);
const markViewed = useCallback(
(filePath: string) => {
storage.markFileViewed(teamName, scopeKey, filePath);
setVersion((v) => v + 1);
},
[teamName, scopeKey]
);
const unmarkViewed = useCallback(
(filePath: string) => {
storage.unmarkFileViewed(teamName, scopeKey, filePath);
setVersion((v) => v + 1);
},
[teamName, scopeKey]
);
const markAllViewedFn = useCallback(
(filePaths: string[]) => {
storage.markAllViewed(teamName, scopeKey, filePaths);
setVersion((v) => v + 1);
},
[teamName, scopeKey]
);
const clearAll = useCallback(() => {
storage.clearViewed(teamName, scopeKey);
setVersion((v) => v + 1);
}, [teamName, scopeKey]);
const viewedCount = totalFiles.filter((f) => viewedSet.has(f)).length;
return {
viewedSet,
isViewed: (fp: string) => viewedSet.has(fp),
markViewed,
unmarkViewed,
markAllViewed: markAllViewedFn,
clearAll,
viewedCount,
totalCount: totalFiles.length,
progress: totalFiles.length > 0 ? Math.round((viewedCount / totalFiles.length) * 100) : 0,
};
}