From aa980f5284051012769e868433904e6f5dade42a Mon Sep 17 00:00:00 2001 From: Chris Busillo Date: Wed, 27 May 2026 14:56:09 -0400 Subject: [PATCH] Clarify Folder Studio review flow actions --- .../workstation/FolderStudioView.svelte | 19 ++++- .../workstation/folder-studio-view.test.ts | 84 +++++++++++++++++-- .../workstation/folder-studio-view.ts | 45 +++++----- 3 files changed, 122 insertions(+), 26 deletions(-) diff --git a/frontend/src/lib/components/workstation/FolderStudioView.svelte b/frontend/src/lib/components/workstation/FolderStudioView.svelte index e754ba3..81990d6 100644 --- a/frontend/src/lib/components/workstation/FolderStudioView.svelte +++ b/frontend/src/lib/components/workstation/FolderStudioView.svelte @@ -344,7 +344,7 @@ Projected output {formatBytes(predictedFolderSizeBytes(studioFolder))} -
+
Metric {resolvedMetricCopy(studioFolder)}
@@ -389,7 +389,7 @@ onclick={focusBenchComposer} data-mf-action={workflow.primaryAction}>{workflow.primary} - {:else if workflow.primaryAction === 'open-ops'} + {:else if workflow.primaryAction === 'open-ops' || workflow.primaryAction.startsWith('monitor-')} {workflow.secondary} + {:else if workflow.secondaryAction.startsWith('monitor-')} + {workflow.secondary} {:else if workflow.secondaryAction === 'download-review-pack' && !workflowActionState(workflow.secondaryAction).disabled}
{ ) ).toMatchObject({ label: 'Review pending', - primary: 'Open Ops', + primary: 'Wait for review media', secondary: 'Revise smaller' }); expect( @@ -531,8 +531,8 @@ describe('Folder Studio review request mapping', () => { resolveWorkflow(folder, folderStatusPayload(), calibration, null, null, null, null, true) ).toMatchObject({ label: 'Review pending', - primary: 'Open Ops', - primaryAction: 'open-ops', + primary: 'Wait for review media', + primaryAction: 'monitor-review', secondary: 'Revise smaller' }); expect( @@ -549,7 +549,7 @@ describe('Folder Studio review request mapping', () => { ) ).toMatchObject({ label: 'Review pending', - primaryAction: 'open-ops' + primaryAction: 'monitor-review' }); expect( resolveWorkflowActionState('approve-size-tradeoff', { @@ -590,6 +590,80 @@ describe('Folder Studio review request mapping', () => { expect(resolveQueueSubmissionMode('open-ops', null)).toBeNull(); }); + it('makes approved folders queue-first and keeps review media secondary', () => { + const workflow = resolveWorkflow( + folderPayload(), + folderStatusPayload(), + null, + null, + { status: 'accepted', message: 'Sample accepted.' }, + null, + null, + true, + false + ); + + expect(workflow).toMatchObject({ + label: 'Approved', + title: 'Queue the approved folder', + primary: 'Queue encode', + primaryAction: 'queue-encode', + secondary: 'Download pack', + secondaryAction: 'download-review-pack' + }); + }); + + it('makes processing folders monitor-first and keeps review media secondary', () => { + const workflow = resolveWorkflow( + folderPayload({ encode_queue_summary: '1 folder is running.' }), + folderStatusPayload(), + null, + null, + null, + null, + { + job_id: 'encode-1', + prefix: 'tv/Example/Season 1', + status: 'running', + telemetry_summary: '2 workers active.' + } as EncodeQueueJob, + true + ); + + expect(workflow).toMatchObject({ + label: 'Processing', + title: 'Approved folder is processing', + primary: 'Monitor processing', + primaryAction: 'monitor-processing', + secondary: 'Download pack', + secondaryAction: 'download-review-pack' + }); + }); + + it('makes active sampling a sample-state action with an explicit review-media prerequisite', () => { + const workflow = resolveWorkflow( + folderPayload(), + folderStatusPayload({ calibration_status: 'running' }), + null, + null, + null, + { status: 'running' } as FolderCalibrationJob, + null + ); + + expect(workflow).toMatchObject({ + label: 'Sampling', + primary: 'Monitor sample', + primaryAction: 'monitor-sample', + secondary: 'Stop sample' + }); + expect(workflow.copy).toMatch(/Review media is the missing prerequisite/); + expect(buildWorkflowSteps(workflow).find((step) => step.label === 'Sample')).toMatchObject({ + current: true, + detail: 'Representative sample is running' + }); + }); + it('surfaces a draft warning before over-budget review evidence', () => { const calibration = { browser_review_ready: true, diff --git a/frontend/src/lib/components/workstation/folder-studio-view.ts b/frontend/src/lib/components/workstation/folder-studio-view.ts index b988270..bf5bff9 100644 --- a/frontend/src/lib/components/workstation/folder-studio-view.ts +++ b/frontend/src/lib/components/workstation/folder-studio-view.ts @@ -65,6 +65,9 @@ export type WorkflowAction = | 'approve-size-tradeoff' | 'download-review-pack' | 'focus-bench' + | 'monitor-processing' + | 'monitor-review' + | 'monitor-sample' | 'open-ops' | 'queue-encode' | 'retry-encode' @@ -298,6 +301,9 @@ export function resolveWorkflowActionState( : { disabled: true, title: 'Review media is not ready yet.' }; } if (action === 'revise-proposal') return { disabled: false, title: '' }; + if (['monitor-processing', 'monitor-review', 'monitor-sample'].includes(action)) { + return { disabled: false, title: '' }; + } if (action === 'open-ops') return { disabled: false, title: '' }; if (action === 'retry-encode') return { disabled: false, title: '' }; if (action === 'stop-sample') { @@ -1037,24 +1043,24 @@ export function resolveWorkflow( encodeJob?.telemetry_summary ?? folder.encode_queue_summary ?? 'Folder settings are approved. Monitor progress here or open Ops for deeper worker state.', - primary: 'Download review pack', - primaryAction: 'download-review-pack', - secondary: 'Open Ops', - secondaryAction: 'open-ops' + primary: 'Monitor processing', + primaryAction: 'monitor-processing', + secondary: 'Download pack', + secondaryAction: 'download-review-pack' }; } if (reviewGate?.status === 'accepted') { return { tone: 'ready', label: 'Approved', - title: 'Folder proposal is approved', + title: 'Queue the approved folder', copy: reviewGate.message ?? - 'The proposal has been accepted. Queue or monitor full-folder work from this context.', - primary: 'Download review pack', - primaryAction: 'download-review-pack', - secondary: 'Queue encode', - secondaryAction: 'queue-encode' + 'The sample was accepted. Queue full-folder processing when you are ready.', + primary: 'Queue encode', + primaryAction: 'queue-encode', + secondary: 'Download pack', + secondaryAction: 'download-review-pack' }; } if ( @@ -1081,9 +1087,9 @@ export function resolveWorkflow( tone: 'active', label: 'Sampling', title: 'Representative sample is running', - copy: 'The operator is waiting for review evidence. Keep worker, elapsed time, and queue state visible until the pack is ready.', - primary: 'Open Ops', - primaryAction: 'open-ops', + copy: 'The sample job is running. Review media is the missing prerequisite; approval appears once the comparison preview is ready.', + primary: 'Monitor sample', + primaryAction: 'monitor-sample', secondary: 'Stop sample', secondaryAction: 'stop-sample' }; @@ -1171,8 +1177,8 @@ export function resolveWorkflow( label: 'Review pending', title: 'Target missed, waiting for review media', copy: `${verdict.predictedPerItem} per episode against ${verdict.target}. Wait for the comparison preview before approving this larger result.`, - primary: 'Open Ops', - primaryAction: 'open-ops', + primary: 'Wait for review media', + primaryAction: 'monitor-review', secondary: 'Revise smaller', secondaryAction: 'focus-bench' }; @@ -1233,15 +1239,16 @@ export function buildWorkflowSteps(workflow: WorkflowState): WorkflowStep[] { const activeAction = workflow.primaryAction; const activeLabel = workflow.label.toLowerCase(); const sampleCurrent = - ['focus-bench', 'start-sample', 'retry-sample', 'stop-sample'].includes(activeAction) || - ['not sampled', 'sampling', 'retryable', 'draft ready'].includes(activeLabel); + ['focus-bench', 'monitor-sample', 'start-sample', 'retry-sample', 'stop-sample'].includes( + activeAction + ) || ['not sampled', 'sampling', 'retryable', 'draft ready'].includes(activeLabel); const reviewCurrent = - ['download-review-pack', 'revise-proposal'].includes(activeAction) || + ['download-review-pack', 'monitor-review', 'revise-proposal'].includes(activeAction) || ['review ready', 'check draft'].includes(activeLabel); const approveCurrent = ['queue-encode', 'approve-size-tradeoff'].includes(activeAction) || activeLabel === 'approved'; const encodeCurrent = - ['open-ops', 'retry-encode'].includes(activeAction) || + ['monitor-processing', 'open-ops', 'retry-encode'].includes(activeAction) || ['processing', 'processing failed', 'processing stopped'].includes(activeLabel); return [ {