agent-ecosystem/src/renderer/components/team/review/DiffErrorBoundary.tsx
2026-05-25 00:14:43 +03:00

133 lines
4.2 KiB
TypeScript

import { Component, type JSX, type ReactNode } from 'react';
import { useAppTranslation } from '@features/localization/renderer';
import { AlertTriangle } from 'lucide-react';
interface DiffErrorBoundaryProps {
children: ReactNode;
filePath: string;
oldString?: string;
newString?: string;
onRetry?: () => void;
}
interface DiffErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
type ReviewT = ReturnType<typeof useAppTranslation>['t'];
interface DiffErrorBoundaryInnerProps extends DiffErrorBoundaryProps {
t: ReviewT;
}
class DiffErrorBoundaryInner extends Component<
DiffErrorBoundaryInnerProps,
DiffErrorBoundaryState
> {
constructor(props: DiffErrorBoundaryInnerProps) {
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 as JSX.Element;
}
const { filePath, oldString, newString, onRetry, t } = 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">{t('review.diffError.title')}</span>
</div>
<p className="mb-3 text-xs text-red-300/80">
{error?.message ?? t('review.diffError.unexpected')}
</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"
>
{t('review.diffError.actions.retry')}
</button>
)}
</div>
{(oldString || newString) && (
<details className="mt-3">
<summary className="cursor-pointer text-xs text-red-300/60 hover:text-red-300/80">
{t('review.diffError.raw.show')}
</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">
{t('review.diffError.raw.file', { file: filePath })}
</div>
{oldString && (
<div className="mb-2">
<div className="mb-0.5 text-red-400">{t('review.diffError.raw.original')}</div>
<pre className="whitespace-pre-wrap">{oldString.slice(0, 2000)}</pre>
{oldString.length > 2000 && (
<span className="text-text-muted">
{t('review.diffError.raw.charsTotal', { count: oldString.length })}
</span>
)}
</div>
)}
{newString && (
<div>
<div className="mb-0.5 text-green-400">{t('review.diffError.raw.modified')}</div>
<pre className="whitespace-pre-wrap">{newString.slice(0, 2000)}</pre>
{newString.length > 2000 && (
<span className="text-text-muted">
{t('review.diffError.raw.charsTotal', { count: newString.length })}
</span>
)}
</div>
)}
</div>
</details>
)}
</div>
);
}
}
export const DiffErrorBoundary = (props: DiffErrorBoundaryProps): JSX.Element => {
const { t } = useAppTranslation('team');
const { children, filePath, newString, oldString, onRetry } = props;
return (
<DiffErrorBoundaryInner
filePath={filePath}
newString={newString}
oldString={oldString}
onRetry={onRetry}
t={t}
>
{children}
</DiffErrorBoundaryInner>
);
};