diff --git a/apps/web/src/app/pricing/page.tsx b/apps/web/src/app/pricing/page.tsx index abdab754f..507b41b26 100644 --- a/apps/web/src/app/pricing/page.tsx +++ b/apps/web/src/app/pricing/page.tsx @@ -17,8 +17,8 @@ const CORE_CAPABILITIES = [ 'Evidence-gated YouTube analysis', 'Timed caption transcript', 'Visible source provenance', - 'Durable workflow run history', - 'Review-only action preparation', + 'Workflow run history', + 'Proposed work from a Video Pack', 'Export after quality verification', ]; @@ -32,7 +32,7 @@ const PRO_CAPABILITIES = [ const COMPARISON = [ ['Verified video analysis', true, true, true], ['Timed transcript and provenance', true, true, true], - ['Review-only action plans', true, true, true], + ['Proposed work from a Video Pack', true, true, true], ['External agent dispatch', false, true, 'User-managed'], ['Knowledge-store writes', false, true, 'User-managed'], ['Infrastructure and provider keys', 'Hosted', 'Hosted', 'User-managed'], diff --git a/apps/web/src/components/OneLoopStudio.tsx b/apps/web/src/components/OneLoopStudio.tsx index eebb07129..44ce07a17 100644 --- a/apps/web/src/components/OneLoopStudio.tsx +++ b/apps/web/src/components/OneLoopStudio.tsx @@ -570,6 +570,15 @@ export default function OneLoopStudio({ artifactCount: packFormation.artifacts.length, toolCount: packFormation.tools.length, }); + const eventsEmptyMessage = studioEventsEmptyMessage({ + busy: transcriptWorking, + hasCompletedRun: Boolean(selected && selected.status !== 'processing'), + eventCount, + hasTranscript: Boolean(selected?.transcript?.trim()), + hasArchitecture: Boolean(packFormation.architecture), + artifactCount: packFormation.artifacts.length, + toolCount: packFormation.tools.length, + }); const hasPayload = studioCanExport({ transcript: selected?.transcript, eventCount, @@ -619,6 +628,7 @@ export default function OneLoopStudio({ const canAttemptBuildLive = Boolean( getYouTubeId(url || selected?.url || '') && (selected || url.trim()), ); + const canAct = Boolean(getYouTubeId(url || selected?.url || '')); const act = async () => { const next = (selected?.url || url).trim(); @@ -1044,6 +1054,20 @@ export default function OneLoopStudio({ Ship this + + ) : null} diff --git a/apps/web/src/components/studio/__tests__/StudioIdeOutput.playflow.test.tsx b/apps/web/src/components/studio/__tests__/StudioIdeOutput.playflow.test.tsx index fa67b7c34..e828dcca0 100644 --- a/apps/web/src/components/studio/__tests__/StudioIdeOutput.playflow.test.tsx +++ b/apps/web/src/components/studio/__tests__/StudioIdeOutput.playflow.test.tsx @@ -41,6 +41,20 @@ describe('StudioIdeOutput playflow', () => { expect(row.textContent).not.toMatch(/production is held/i); }); + it('uses the honest Events empty copy and can host a pack workbench', () => { + render( + Pack workbench} + />, + ); + const events = screen.getByTestId('studio-ide-output-events'); + expect(events.textContent).toMatch(/no extracted events/i); + expect(events.textContent).not.toMatch(/INSIGHT/i); + expect(screen.getByTestId('studio-pack-workbench')).toBeTruthy(); + }); + it('renders review_action results as cards with status, title, and detail', () => { render( { reviewActions={[ { tool: 'review_action', status: 'proposed', result: 'Review evidence' }, ]} + onAct={() => undefined} + canAct />, ); fireEvent.click(screen.getByTestId('studio-ide-output-tab-intent')); @@ -55,5 +71,6 @@ describe('StudioIdeOutput playflow', () => { expect(card.textContent).toMatch(/review this result/i); expect(card.textContent).toMatch(/needs review/i); expect(card.textContent).toMatch(/Review evidence/); + expect(screen.getByTestId('studio-review-act-button').textContent).toMatch(/Act on this run/); }); }); diff --git a/apps/web/src/lib/__tests__/studio-pipeline-status.test.ts b/apps/web/src/lib/__tests__/studio-pipeline-status.test.ts index 05167ecba..87cb4d02d 100644 --- a/apps/web/src/lib/__tests__/studio-pipeline-status.test.ts +++ b/apps/web/src/lib/__tests__/studio-pipeline-status.test.ts @@ -546,6 +546,10 @@ describe('studio-pipeline-status', () => { expect(shell).toContain('data-testid="studio-ide-toolbar"'); expect(studio).toContain('StudioIdeShell'); expect(pricing).not.toMatch(/reviewed plan dispatches backend agents/); + expect(pricing).not.toContain('Durable workflow run history'); + expect(pricing).not.toContain('Review-only action preparation'); + expect(pricing).toContain('Workflow run history'); + expect(pricing).toContain('Proposed work from a Video Pack'); expect(apiDocs).not.toMatch(/durable Studio analysis workflow/); }); @@ -683,6 +687,41 @@ describe('studio-pipeline-status', () => { expect(studio).not.toMatch(/mapKeyframes|fakeEvents|invent.*events/i); }); + it('rewires playflow B–G into the IDE (retry, Events≠INSIGHTs, stack skip, review cards)', () => { + const studio = readFileSync(join(process.cwd(), 'src/components/OneLoopStudio.tsx'), 'utf8'); + const output = readFileSync(join(process.cwd(), 'src/components/studio/StudioIdeOutput.tsx'), 'utf8'); + + // B — Retry transcript is a visible control, not only a computed flag. + expect(studio).toContain('data-testid="studio-transcript-retry"'); + expect(studio).toMatch(/Retry transcript/); + expect(studio).toContain('showTranscriptRetry'); + + // API events[] vs UI label: Events tab, never INSIGHTs. + expect(output).toContain("{ id: 'events', label: 'Events' }"); + expect(output).not.toMatch(/label:\s*['"]INSIGHT/i); + expect(output).not.toMatch(/label:\s*['"]Insights['"]/); + expect(output).toContain('eventsEmptyMessage'); + expect(studio).toMatch(/studioEventsEmptyMessage\(/); + expect(studio).toMatch(/ { const studio = readFileSync(join(process.cwd(), 'src/components/OneLoopStudio.tsx'), 'utf8'); // The header/transcript follow a resolved selection, and pack identity is diff --git a/apps/web/src/lib/studio-playflow-PLAN.md b/apps/web/src/lib/studio-playflow-PLAN.md index ab68c22ec..992df26f9 100644 --- a/apps/web/src/lib/studio-playflow-PLAN.md +++ b/apps/web/src/lib/studio-playflow-PLAN.md @@ -1,29 +1,26 @@ -# TASK: Restore Studio Ship this playflow after IDE shell +# TASK: Rewire leftover Studio playflow onto Ship this ## 1. Goal & Scope -* **Objective:** Get UVAI Studio playflow back toward ≥9/10 by restoring a discoverable **Ship this** path and rewiring leftover B/E/G helpers the IDE shell left unused. -* **Context:** Kill A (#1692) merged. B–G helpers exist (`studioTranscriptStage`, `studioActionCard`, `deployHoldReason` skip/unknown, export toast, player overlay). The 2026-10-07 IDE refactor deleted `studio-deploy-button` and stopped rendering Ship this, stack-check honesty, review_action cards, and transcript retry. Dogfood kill F still stands: no Ship this; seadance/cluely-class checks must not hold anonymous ship. +* **Objective:** Keep #2437 **Ship this** and re-render the leftover B/E/G helpers the IDE still left unused: honest Events empty copy + pack workbench when `events[]` is empty. +* **Context:** A/#1692 and B–G helpers landed in September. #2437 restored Ship this, transcript retry, stack-check skip/unknown, and review_action cards. Live pack GET for pBsT6v-ciO8 has transcript + architecture + artifacts + stack.tools and **no `events` field**. UI must label that as **Events**, never INSIGHTs. No G.A.T.E. No fake events. No ClipToAction. * **Scope:** - * `apps/web/src/lib/studio-pipeline-status.ts` — `studioShipEnabled` / `studioShipCta` (exportability gates ship; holdReason never does) - * `apps/web/src/lib/official-templates.ts` — stack-check row view (skip/unknown) - * `apps/web/src/components/OneLoopStudio.tsx` — Ship this CTA, transcript retry, toast position, pass checks/actions - * `apps/web/src/components/studio/StudioIdeOutput.tsx` — stack checks + `studioActionCard` - * *Initial check:* Extend existing helpers. Do not restore `studio-deploy-button`, origin deploy kickoff, fake events, or G.A.T.E. contract changes. + * `apps/web/src/components/OneLoopStudio.tsx` — pass `studioEventsEmptyMessage` + `PackWorkbench` when `studioPromotePackWorkbench` + * `apps/web/src/components/studio/StudioIdeOutput.tsx` — Events empty honesty + pack workbench; keep Ship this / reviewActions / stackCheckViews from #2437 + * `apps/web/src/lib/__tests__/studio-pipeline-status.test.ts` — lock Events≠INSIGHTs + workbench wiring + * *Initial check:* Do not restore `studio-deploy-button`, origin deploy kickoff, fake events, or G.A.T.E. contract changes. ## 2. Execution Plan -- [x] Failing tests for Ship this enablement, skip/unknown checks, review cards, retry, toast position -- [x] Ship this CTA enabled by `studioCanExport`, never by non-runnable stack checks -- [x] Tools tab shows honest skip/unknown; Intent tab renders `review_action` cards -- [x] Transcript retry control when `studioCanRetryTranscript` -- [x] Export toast no longer uses sticky-footer `bottom-20` -- [x] Focused Vitest +- [x] Rebase onto origin/main after #2437 (clears DIRTY + stale required closer) +- [x] Events tab uses `studioEventsEmptyMessage` + pack workbench +- [x] Keep Ship this, transcript retry, skip/unknown stack checks, review_action cards +- [x] Focused Vitest green ## 3. Definition of Done (Success Verification) -* **Expected Outcome:** Anonymous Studio on a seadance/cluely pack can click **Ship this** (ZIP export). Stack checks read skip/unknown and do not disable the button. No live hostname claim. Transcript retry and review cards are visible. +* **Expected Outcome:** Anonymous Studio can click **Ship this**. Events tab says this pack has no extracted events (not INSIGHTs) and can show the pack workbench. Stack checks stay skip/unknown and do not hold ship. Transcript retry and review cards remain visible. * **Verification Method:** `cd apps/web && npx vitest run src/lib/__tests__/studio-pipeline-status.test.ts src/lib/__tests__/official-templates.test.ts src/lib/__tests__/uvai-surface-unification.test.ts src/components/__tests__/OneLoopStudio.three-panel.test.tsx src/components/studio/__tests__/StudioIdeOutput.playflow.test.tsx` -* **Proof Artifact:** `cd apps/web && npm test -- src/lib/__tests__/studio-pipeline-status.test.ts src/lib/__tests__/official-templates.test.ts src/lib/__tests__/uvai-surface-unification.test.ts src/components/__tests__/OneLoopStudio.three-panel.test.tsx src/components/studio/__tests__/StudioIdeOutput.playflow.test.tsx` → **5 files, 49 passed**. Related: OneLoopStudio.build-live + job-strip + linked-sop → **3 files, 16 passed**. `tsc --noEmit` and eslint on touched files passed. +* **Proof Artifact:** `cd apps/web && npx vitest run src/lib/__tests__/studio-pipeline-status.test.ts src/lib/__tests__/official-templates.test.ts src/lib/__tests__/uvai-surface-unification.test.ts src/components/__tests__/OneLoopStudio.three-panel.test.tsx src/components/studio/__tests__/StudioIdeOutput.playflow.test.tsx` → **5 files, 51 passed**. ## 4. Post-Task Reflection -* **What was done:** Restored a discoverable **Ship this** toolbar CTA that downloads the pack ZIP. Anonymous/unknown SeaDance/Cluely stack checks render as skip/unknown and never disable the button. Intent tab renders `review_action` cards. Transcript retry is visible on retryable failure. Export toast sits at `bottom-4` (no sticky footer). Origin deploy button was not restored. -* **Why it was needed:** IDE shell left playflow helpers unwired; dogfood kill F was a disabled Deploy with no Ship this. -* **How it was tested:** Focused Vitest (49 + 16), type-check, eslint. No production live claim. GitHub issue create was 403 so this cut has no new issue number. +* **What was done:** Rebased #2436 onto #2437. Wired honest Events empty copy and PackWorkbench into the IDE without dropping Ship this. +* **Why it was needed:** The required closer check was a stale FAILURE on the old SHA, and main had moved (CONFLICTING/DIRTY). +* **How it was tested:** Focused Vitest after rebase. Required closer then failed because #2435 closed when #2437 merged; remaining E/G lives on open #2440 (Act CTA + pricing copy).