diff --git a/frontend/src/components/VttEditor/VttDiffView.tsx b/frontend/src/components/VttEditor/VttDiffView.tsx
new file mode 100644
index 0000000..7a1f46a
--- /dev/null
+++ b/frontend/src/components/VttEditor/VttDiffView.tsx
@@ -0,0 +1,104 @@
+import { useState } from 'react';
+import { useQuery } from '@tanstack/react-query';
+import { apiClient } from '../../lib/api';
+import type { VttKind } from '../../types/api';
+
+interface VttDiffViewProps {
+ jobId: string;
+ lang: string;
+ kind: VttKind;
+}
+
+export function VttDiffView({ jobId, lang, kind }: VttDiffViewProps) {
+ const [open, setOpen] = useState(false);
+
+ const { data: versions } = useQuery({
+ queryKey: ['vtt-versions', jobId, lang, kind],
+ queryFn: () => apiClient.listVttVersions(jobId, lang, kind, 0, 200),
+ enabled: open,
+ staleTime: 30_000,
+ });
+
+ const versionList = versions?.versions ?? [];
+ const baseline = versionList.length > 0 ? versionList[versionList.length - 1] : null; // oldest = version 1
+ const latest = versionList.length > 0 ? versionList[0] : null; // newest first
+
+ const canShowDiff = baseline && latest && baseline.version !== latest.version;
+
+ const { data: diffData, isLoading: diffLoading } = useQuery({
+ queryKey: ['vtt-diff', jobId, lang, kind, baseline?.version, latest?.version],
+ queryFn: () => apiClient.diffVttVersions(jobId, lang, kind, baseline!.version, latest!.version),
+ enabled: open && !!canShowDiff,
+ staleTime: 30_000,
+ });
+
+ if (!open) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+ Diff vs AI baseline
+ {diffData && (
+
+ +{diffData.added_count}
+ {' / '}
+ -{diffData.removed_count}
+ {` lines changed from v${baseline?.version} → v${latest?.version}`}
+
+ )}
+
+
+
+
+
+ {diffLoading && (
+
Loading diff…
+ )}
+
+ {!diffLoading && !canShowDiff && versionList.length > 0 && (
+
No changes from baseline — content is identical to AI output.
+ )}
+
+ {!diffLoading && versionList.length === 0 && (
+
No version history yet.
+ )}
+
+ {diffData?.lines.map((line, i) => {
+ if (line.type === 'unchanged') return null;
+ return (
+
+
+ {line.type === 'added' ? '+' : '-'}
+
+ {line.content}
+
+ );
+ })}
+
+ {diffData && diffData.added_count === 0 && diffData.removed_count === 0 && (
+
No changes from AI baseline.
+ )}
+
+
+ );
+}
diff --git a/frontend/src/routes/admin/QCDetail.tsx b/frontend/src/routes/admin/QCDetail.tsx
index b6d62fb..8756b01 100644
--- a/frontend/src/routes/admin/QCDetail.tsx
+++ b/frontend/src/routes/admin/QCDetail.tsx
@@ -11,6 +11,7 @@ import {
} from '../../hooks/useAccessibleVideoEdit';
import { StatusBadge } from '../../components/StatusBadge';
import { VttEditor } from '../../components/VttEditor/VttEditor';
+import { VttDiffView } from '../../components/VttEditor/VttDiffView';
import { VideoWithCaptions } from '../../components/VideoWithCaptions';
import { VoiceSelector } from '../../components/VoiceSelector';
import { TimelinePreview } from '../../components/TimelinePreview';
@@ -1584,6 +1585,9 @@ export function QCDetail() {
glossaryTerms={glossaryTerms}
language={selectedLanguage}
/>
+
+
+
)}
@@ -1611,6 +1615,9 @@ export function QCDetail() {
glossaryTerms={glossaryTerms}
language={selectedLanguage}
/>
+
+
+
)}