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
19 changes: 17 additions & 2 deletions frontend/src/lib/components/workstation/FolderStudioView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@
<span>Projected output</span>
<strong>{formatBytes(predictedFolderSizeBytes(studioFolder))}</strong>
</div>
<div>
<div class="folder-header__metric">
<span>Metric</span>
<strong>{resolvedMetricCopy(studioFolder)}</strong>
</div>
Expand Down Expand Up @@ -389,7 +389,7 @@
onclick={focusBenchComposer}
data-mf-action={workflow.primaryAction}>{workflow.primary}</button
>
{:else if workflow.primaryAction === 'open-ops'}
{:else if workflow.primaryAction === 'open-ops' || workflow.primaryAction.startsWith('monitor-')}
<a
class="control control--primary"
href={resolve('/ops')}
Expand Down Expand Up @@ -458,6 +458,10 @@
<a class="control" href={resolve('/ops')} data-mf-action={workflow.secondaryAction}
>{workflow.secondary}</a
>
{:else if workflow.secondaryAction.startsWith('monitor-')}
<a class="control" href={resolve('/ops')} data-mf-action={workflow.secondaryAction}
>{workflow.secondary}</a
>
{:else if workflow.secondaryAction === 'download-review-pack' && !workflowActionState(workflow.secondaryAction).disabled}
<form
class="action-form"
Expand Down Expand Up @@ -914,6 +918,17 @@
min-width: 88px;
}

.folder-header__metric {
color: var(--mf-fg-tertiary);
font-size: var(--mf-text-xs);
justify-content: end;
}

.folder-header__metric strong {
color: var(--mf-fg-secondary);
font-size: var(--mf-text-xs);
}

.folder-header__facts span,
.folder-header__path span,
.decision-fact span,
Expand Down
84 changes: 79 additions & 5 deletions frontend/src/lib/components/workstation/folder-studio-view.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import type { FolderPayload, FolderStatusPayload } from '$lib/api/types';
import type { EncodeQueueJob, FolderPayload, FolderStatusPayload } from '$lib/api/types';
import type { FolderCalibrationJob } from '$lib/folders/studio';
import {
buildBenchHostOptions,
Expand Down Expand Up @@ -484,7 +484,7 @@ describe('Folder Studio review request mapping', () => {
)
).toMatchObject({
label: 'Review pending',
primary: 'Open Ops',
primary: 'Wait for review media',
secondary: 'Revise smaller'
});
expect(
Expand Down Expand Up @@ -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(
Expand All @@ -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', {
Expand Down Expand Up @@ -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,
Expand Down
45 changes: 26 additions & 19 deletions frontend/src/lib/components/workstation/folder-studio-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -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 (
Expand All @@ -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'
};
Expand Down Expand Up @@ -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'
};
Expand Down Expand Up @@ -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 [
{
Expand Down