Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 30 additions & 26 deletions frontend/scripts/smoke-clip-batch-export.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -188,33 +188,36 @@ assert.deepEqual(
);

const panelSource = sourceFor('../src/components/AIPanel.tsx');
const controllerSource = sourceFor('../src/features/clips/useClipExportController.ts');
const cardSource = sourceFor('../src/features/clips/ClipDraftCard.tsx');
const controlsSource = sourceFor('../src/features/clips/ClipPrepareExportControls.tsx');
const exportFilesSource = sourceFor('../src/features/clips/clipExportFiles.ts');
const autosaveSource = sourceFor('../src/hooks/useProjectAutosave.ts');
const projectSource = sourceFor('../src/types/project.ts');
const manifestSource = exportFilesSource.slice(exportFilesSource.indexOf('export function buildClipBatchManifest'));

assert.match(panelSource, /getClipBatchExportCandidates\(clipDrafts, words, videoPath\)/);
assert.match(panelSource, /for \(let index = 0; index < exportableDrafts.length; index\+\+\)/);
assert.doesNotMatch(panelSource, /Promise\.all\([^)]*export/);
assert.match(panelSource, /const exportBusy = isBatchExporting \|\| exportingDraftId !== null/);
assert.match(panelSource, /useClipExportController\(\{ setCreatorNotice \}\)/);
assert.doesNotMatch(panelSource, /pollClipExportJob|setClipExportJobs|stopBatchExportRef|\/jobs\/export/, 'AIPanel must not own clip export job orchestration');
assert.match(controllerSource, /getClipBatchExportCandidates\(clipDrafts, words, videoPath\)/);
assert.match(controllerSource, /for \(let index = 0; index < exportableDrafts.length; index\+\+\)/);
assert.doesNotMatch(controllerSource, /Promise\.all\([^)]*export/);
assert.match(controllerSource, /const exportBusy = isBatchExporting \|\| exportingDraftId !== null/);
assert.match(panelSource, /exportBusy=\{exportBusy\}/);
assert.match(controlsSource, /disabled=\{exportBusy \|\| exportableDraftCount === 0\}/);
assert.match(cardSource, /disabled=\{!canExport \|\| exportBusy \|\| isExporting \|\| exportActive\}/);
assert.match(cardSource, /disabled=\{!exportValidation\.ready \|\| exportBusy\}/);
assert.doesNotMatch(controlsSource, /useAIStore|useEditorStore|fetch\(|localStorage/, 'prepare/export controls must stay presentational');
assert.ok((panelSource.match(/if \(exportBusy\) return;/g) || []).length >= 3, 'export handlers have defensive busy guards');
assert.ok((controllerSource.match(/if \(exportBusy\) return;/g) || []).length >= 4, 'export handlers have defensive busy guards');
assert.match(controlsSource, /onClick=\{onStopBatchExport\}/);
assert.match(panelSource, /for \(let index = 0; index < exportableDrafts.length; index\+\+\)[\s\S]*?await handleExportClip/);
assert.doesNotMatch(panelSource, /\/jobs\/export-batch|\/export\/v2/);
const cancelHandlerSource = panelSource.slice(panelSource.indexOf('const cancelDraftExport'), panelSource.indexOf('const retryDraftExport'));
assert.match(controllerSource, /for \(let index = 0; index < exportableDrafts.length; index\+\+\)[\s\S]*?await handleExportClip/);
assert.doesNotMatch(controllerSource, /\/jobs\/export-batch|\/export\/v2/);
const cancelHandlerSource = controllerSource.slice(controllerSource.indexOf('const cancelDraftExport'), controllerSource.indexOf('const retryDraftExport'));
assert.match(cancelHandlerSource, /\/cancel/);
assert.doesNotMatch(cancelHandlerSource, /status: 'failed'/, 'cancel request must not mark the draft terminal before polling confirms cancellation');
assert.doesNotMatch(cancelHandlerSource, /setExportingDraftId/, 'cancel request must keep exportBusy active until the export poll reaches a terminal state');
assert.match(panelSource, /failedCount/);
assert.match(panelSource, /handleExportClip\(draft, draft, true\)/);
assert.match(panelSource, /if \(stopBatchExportRef\.current\) break/);
assert.match(controllerSource, /failedCount/);
assert.match(controllerSource, /handleExportClip\(draft, draft, true\)/);
assert.match(controllerSource, /if \(stopBatchExportRef\.current\) break/);
assert.match(controlsSource, /Stopping after current clip/);
assert.match(cardSource, /Retry export/);
assert.match(cardSource, /const exportRetryable = status === 'failed'/);
Expand All @@ -225,25 +228,26 @@ assert.ok(
(panelSource.match(/if \(isClipTimelineMutationBlocked\(\)\) return;/g) || []).length >= 7,
'compound filler/edit-plan timeline actions must not update decisions while a clip export blocks timeline mutation',
);
assert.match(panelSource, /const currentDraft = useAIStore\.getState\(\)\.clipDrafts\.find/);
assert.match(panelSource, /clearClipExportAttempt\(currentDraft\.id\)/);
assert.doesNotMatch(panelSource, /\/jobs\/\$\{job\.id\}\/retry/, 'clip retry must not replay an old backend export target');
assert.match(panelSource, /handleExportClip\(draft, draft, true\)/);
assert.match(panelSource, /handleExportClip\(currentDraft, currentDraft, true\)/);
const handleExportClipSource = panelSource.slice(panelSource.indexOf('const handleExportClip'), panelSource.indexOf('const cancelDraftExport'));
assert.match(controllerSource, /const currentDraft = useAIStore\.getState\(\)\.clipDrafts\.find/);
assert.match(controllerSource, /clearClipExportAttempt\(currentDraft\.id\)/);
assert.doesNotMatch(controllerSource, /\/jobs\/\$\{job\.id\}\/retry/, 'clip retry must not replay an old backend export target');
assert.match(controllerSource, /handleExportClip\(draft, draft, true\)/);
assert.match(controllerSource, /handleExportClip\(currentDraft, currentDraft, true\)/);
const handleExportClipSource = controllerSource.slice(controllerSource.indexOf('const handleExportClip'), controllerSource.indexOf('const cancelDraftExport'));
assert.ok(
handleExportClipSource.indexOf("status: 'exporting'") < handleExportClipSource.indexOf('await fetch(`${backendUrl}/jobs/export`'),
'durable exporting claim must happen before the first export request await',
);
assert.match(panelSource, /getCurrentClipBatchDraftForExport\([\s\S]*?plannedDraft\.id/);
assert.match(panelSource, /pausedForDraftChange = true/);
assert.match(panelSource, /Batch paused because a planned clip changed/);
assert.match(panelSource, /outputPath/);
assert.match(panelSource, /srtPath: output\.srtPath/);
assert.match(panelSource, /exportWarnings: output\.warnings/);
assert.match(panelSource, /manifest/);
assert.match(panelSource, /manifestWarning/);
assert.match(panelSource, /writeManifest: window\.electronAPI\?\.writeClipManifest/);
assert.match(controllerSource, /getCurrentClipBatchDraftForExport\([\s\S]*?plannedDraft\.id/);
assert.match(controllerSource, /pausedForDraftChange = true/);
assert.match(controllerSource, /Batch paused because a planned clip changed/);
assert.match(controllerSource, /outputPath/);
assert.match(controllerSource, /srtPath: output\.srtPath/);
assert.match(controllerSource, /exportWarnings: output\.warnings/);
assert.match(controllerSource, /manifest/);
assert.match(controllerSource, /manifestWarning/);
assert.match(controllerSource, /writeManifest: window\.electronAPI\?\.writeClipManifest/);
assert.doesNotMatch(controllerSource, /defaultProvider|providers|startAIJob|clip-metadata/, 'clip export controller must not absorb AI/publishing orchestration');
assert.match(manifestSource, /schema: 'scriptcut\.clipBatchManifest\.v1'/);
assert.match(manifestSource, /remaining/);
assert.match(manifestSource, /stopped/);
Expand Down
5 changes: 3 additions & 2 deletions frontend/scripts/smoke-clip-prepare-export.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { dirname, resolve } from 'node:path';

const __dirname = dirname(fileURLToPath(import.meta.url));
const panelSource = readFileSync(resolve(__dirname, '../src/components/AIPanel.tsx'), 'utf8');
const controllerSource = readFileSync(resolve(__dirname, '../src/features/clips/useClipExportController.ts'), 'utf8');
const cardSource = readFileSync(resolve(__dirname, '../src/features/clips/ClipDraftCard.tsx'), 'utf8');

assert.match(cardSource, /const \[advancedExportOpen, setAdvancedExportOpen\] = useState\(false\)/);
Expand Down Expand Up @@ -51,7 +52,7 @@ assert.match(panelSource, /srtPath: undefined/);
assert.match(panelSource, /exportedAt: undefined/);
assert.match(cardSource, /disabled=\{!exportValidation\.ready \|\| exportBusy\}/);

assert.match(panelSource, /const \[clipExportOutputs, setClipExportOutputs\]/);
assert.match(controllerSource, /const \[clipExportOutputs, setClipExportOutputs\]/);
assert.match(cardSource, /Clip ready/);
assert.match(cardSource, /Video output/);
assert.match(cardSource, /Reveal in Finder/);
Expand All @@ -60,4 +61,4 @@ assert.match(cardSource, /SRT sidecar/);
assert.match(cardSource, /Download SRT/);
assert.match(cardSource, /const srtPath = exportResult\?\.srtPath \|\| draft\.srtPath/);
assert.match(cardSource, /const exportWarnings = exportResult\?\.warnings \|\| draft\.exportWarnings \|\| \[\]/);
assert.match(panelSource, /\$\{successCount\} clip\$\{successCount === 1 \? '' : 's'\} ready/);
assert.match(controllerSource, /\$\{successCount\} clip\$\{successCount === 1 \? '' : 's'\} ready/);
8 changes: 5 additions & 3 deletions frontend/scripts/smoke-create-clips-workspace.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const findStageSource = readSource('../src/features/clips/ClipFindStage.tsx');
const prepareExportSource = readSource('../src/features/clips/ClipPrepareExportControls.tsx');
const clipDraftModelSource = readSource('../src/features/clips/clipDraftModel.ts');
const clipExportFilesSource = readSource('../src/features/clips/clipExportFiles.ts');
const clipExportControllerSource = readSource('../src/features/clips/useClipExportController.ts');
const reviewActionsSource = readSource('../src/features/clips/useClipReviewActions.ts');
const exportDialogSource = readSource('../src/components/ExportDialog.tsx');
const reviewSource = readSource('../src/features/clips/ClipReviewWorkspace.tsx');
Expand All @@ -34,6 +35,7 @@ assert.match(panelSource, /import ClipFindStage from '\.\.\/features\/clips\/Cli
assert.match(panelSource, /import ClipPrepareExportControls from '\.\.\/features\/clips\/ClipPrepareExportControls'/);
assert.match(panelSource, /import ClipWorkspaceHeader from '\.\.\/features\/clips\/ClipWorkspaceHeader'/);
assert.match(panelSource, /import \{ useClipReviewActions \} from '\.\.\/features\/clips\/useClipReviewActions'/);
assert.match(panelSource, /import \{ useClipExportController \} from '\.\.\/features\/clips\/useClipExportController'/);
assert.match(panelSource, /<ClipDraftCard/);
assert.match(panelSource, /<ClipFindStage/);
assert.match(panelSource, /<ClipPrepareExportControls/);
Expand Down Expand Up @@ -136,9 +138,9 @@ assert.match(projectSchemaSource, /"clipReviewDecisions":/);
assert.match(autosaveSource, /normalizeClipReviewDecisions\(workspace\.clipReviewDecisions\)/);
assert.match(autosaveSource, /clipReviewDecisions: aiState\.clipReviewDecisions/);
assert.match(panelSource, /validateClipDraftForExport\(draft, words, videoPath\)/);
assert.match(panelSource, /getClipBatchExportCandidates\(clipDrafts, words, videoPath\)/);
assert.match(clipExportControllerSource, /getClipBatchExportCandidates\(clipDrafts, words, videoPath\)/);
assert.match(
panelSource,
clipExportControllerSource,
/const clipWords = buildClipExportCaptionWords\([\s\S]*?body: JSON\.stringify\(\{[\s\S]*?word_timeline: 'export',[\s\S]*?words: captions !== 'none' \? clipWords : undefined,/,
'clip workspace marks pre-projected caption words as export timeline',
);
Expand Down Expand Up @@ -295,7 +297,7 @@ const queueSummary = getClipQueueSummary(
new Set(['failed-retryable']),
);
assert.deepEqual(queueSummary, { suggested: 1, prepare: 1, exporting: 1, retry: 1, exported: 1, failed: 2 });
assert.match(panelSource, /getClipBatchExportCandidates/);
assert.match(clipExportControllerSource, /getClipBatchExportCandidates/);
assert.match(panelSource, /updateClipDraft\(id, \{ status: 'draft', lastError: undefined \}\)/);
assert.match(transcriptSource, /Draft clip/);
assert.match(prepareExportSource, /Export (Ready Clips|all ready clips)/);
Expand Down
Loading