agent-ecosystem/src/renderer/components/team/review/DiffErrorBoundary.tsx
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

101 lines
3.3 KiB
TypeScript

import { Component, type ReactNode } from 'react';
import { AlertTriangle } from 'lucide-react';
interface DiffErrorBoundaryProps {
children: ReactNode;
filePath: string;
oldString?: string;
newString?: string;
onRetry?: () => void;
}
interface DiffErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
export class DiffErrorBoundary extends Component<DiffErrorBoundaryProps, DiffErrorBoundaryState> {
constructor(props: DiffErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): DiffErrorBoundaryState {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
console.error(
'[DiffErrorBoundary] Error rendering diff for',
this.props.filePath,
error,
errorInfo
);
}
render(): JSX.Element {
if (!this.state.hasError) {
return <>{this.props.children}</>;
}
const { filePath, oldString, newString, onRetry } = this.props;
const { error } = this.state;
return (
<div className="m-4 rounded-lg border border-red-500/20 bg-red-500/10 p-4">
<div className="mb-3 flex items-center gap-2">
<AlertTriangle className="size-4 text-red-400" />
<span className="text-sm font-medium text-red-300">Failed to render diff view</span>
</div>
<p className="mb-3 text-xs text-red-300/80">
{error?.message ?? 'An unexpected error occurred while rendering the diff.'}
</p>
<div className="flex items-center gap-2">
{onRetry && (
<button
onClick={() => {
this.setState({ hasError: false, error: null });
onRetry();
}}
className="rounded bg-red-500/20 px-3 py-1 text-xs text-red-300 transition-colors hover:bg-red-500/30"
>
Retry
</button>
)}
</div>
{(oldString || newString) && (
<details className="mt-3">
<summary className="cursor-pointer text-xs text-red-300/60 hover:text-red-300/80">
Show raw diff data
</summary>
<div className="mt-2 max-h-60 overflow-auto rounded bg-surface p-2 font-mono text-xs text-text-muted">
<div className="mb-1 text-text-secondary">File: {filePath}</div>
{oldString && (
<div className="mb-2">
<div className="mb-0.5 text-red-400">--- Original</div>
<pre className="whitespace-pre-wrap">{oldString.slice(0, 2000)}</pre>
{oldString.length > 2000 && (
<span className="text-text-muted">... ({oldString.length} chars total)</span>
)}
</div>
)}
{newString && (
<div>
<div className="mb-0.5 text-green-400">+++ Modified</div>
<pre className="whitespace-pre-wrap">{newString.slice(0, 2000)}</pre>
{newString.length > 2000 && (
<span className="text-text-muted">... ({newString.length} chars total)</span>
)}
</div>
)}
</div>
</details>
)}
</div>
);
}
}