From 732b8fb58c2d7d6e0618613d9450cab20d5f1b0e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 17 Oct 2026 16:36:29 +0000 Subject: [PATCH 1/2] fix(web): rewire Studio playflow B-G into the IDE Surface retry, honest empty Events, skip/unknown stack checks, and review_action cards after the IDE restyle. Keep Events labeling. Co-authored-by: KK --- apps/web/src/components/OneLoopStudio.tsx | 20 +++++++ .../src/components/studio/StudioIdeOutput.tsx | 58 ++++++++++++------- .../StudioIdeOutput.playflow.test.tsx | 14 +++++ .../__tests__/studio-pipeline-status.test.ts | 31 ++++++++++ apps/web/src/lib/studio-playflow-PLAN.md | 35 +++++------ 5 files changed, 117 insertions(+), 41 deletions(-) diff --git a/apps/web/src/components/OneLoopStudio.tsx b/apps/web/src/components/OneLoopStudio.tsx index eebb07129..3c0f29af0 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, @@ -1145,6 +1154,7 @@ export default function OneLoopStudio({ outputPane={ { const card = studioActionCard(action); @@ -1154,6 +1164,16 @@ export default function OneLoopStudio({ result: action.result ?? card.detail, }; })} + packWorkbench={ + promotePack ? ( + void exportPkg()} + canExport={hasPayload} + /> + ) : undefined + } specReview={ selected?.videoPack?.pack ? ( = [ export default function StudioIdeOutput({ video, specReview, + eventsEmptyMessage, stackChecks = [], reviewActions = [], + packWorkbench, }: { video: Video | undefined; - specReview?: React.ReactNode; + specReview?: ReactNode; + eventsEmptyMessage?: string; stackChecks?: StudioStackCheckView[]; reviewActions?: StudioReviewActionInput[]; + packWorkbench?: ReactNode; }) { const [active, setActive] = useState('events'); @@ -56,8 +60,10 @@ export default function StudioIdeOutput({ video?.videoPack || video?.events?.length || video?.insights || + eventsEmptyMessage || stackChecks.length > 0 || - reviewActions.length > 0, + reviewActions.length > 0 || + packWorkbench, ); const pack = video?.videoPack?.pack; @@ -110,29 +116,37 @@ export default function StudioIdeOutput({ ) : ( <> {active === 'events' && ( -
    +
    {events.length === 0 ? ( - + ) : ( - events.map((e) => ( -
  • -
    - - - {e.type} - - {e.timestamp ? ( - {e.timestamp} +
      + {events.map((e) => ( +
    • +
      + + + {e.type} + + {e.timestamp ? ( + {e.timestamp} + ) : null} +
      +

      {e.title}

      + {e.description ? ( +

      {e.description}

      ) : null} -
    -

    {e.title}

    - {e.description ? ( -

    {e.description}

    - ) : null} -
  • - )) + + ))} +
)} - + {packWorkbench} + )} {active === 'lingo' && ( 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..da8fccff0 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( { 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..3b2f6b946 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. From c7a09a175762cfd6981c65c812b22e24c38b7519 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 17 Oct 2026 16:48:10 +0000 Subject: [PATCH 2/2] fix(web): surface Act CTA and soften pricing copy Keep honest empty Events after the #2437 rebase, and finish leftover #2440 so the required closer can target an open issue. Co-authored-by: KK --- apps/web/src/app/pricing/page.tsx | 6 +++--- apps/web/src/components/OneLoopStudio.tsx | 18 ++++++++++++++++++ .../OneLoopStudio.three-panel.test.tsx | 2 ++ .../src/components/studio/StudioIdeOutput.tsx | 17 +++++++++++++++++ .../StudioIdeOutput.playflow.test.tsx | 3 +++ .../__tests__/studio-pipeline-status.test.ts | 10 +++++++++- apps/web/src/lib/studio-playflow-PLAN.md | 2 +- 7 files changed, 53 insertions(+), 5 deletions(-) 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 3c0f29af0..44ce07a17 100644 --- a/apps/web/src/components/OneLoopStudio.tsx +++ b/apps/web/src/components/OneLoopStudio.tsx @@ -628,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(); @@ -1053,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 da8fccff0..e828dcca0 100644 --- a/apps/web/src/components/studio/__tests__/StudioIdeOutput.playflow.test.tsx +++ b/apps/web/src/components/studio/__tests__/StudioIdeOutput.playflow.test.tsx @@ -62,6 +62,8 @@ describe('StudioIdeOutput playflow', () => { reviewActions={[ { tool: 'review_action', status: 'proposed', result: 'Review evidence' }, ]} + onAct={() => undefined} + canAct />, ); fireEvent.click(screen.getByTestId('studio-ide-output-tab-intent')); @@ -69,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 0a8b6ae28..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/); }); @@ -708,10 +712,14 @@ describe('studio-pipeline-status', () => { expect(studio).not.toMatch(/!canAttemptBuildLive\s*\|\|\s*.*holdReason/); expect(studio).not.toContain('studio-deploy-button'); - // E — review_action cards render in the Intent tab. + // E — review_action cards render in the Intent tab, with Act CTA un-gated. expect(output).toContain('reviewActions'); expect(studio).toMatch(/studioActionCard\(/); expect(output).toMatch(/Needs review|statusLabel/); + expect(studio).toContain('data-testid="studio-act-button"'); + expect(studio).toMatch(/Act on this run/); + expect(studio).not.toMatch(/showAgentWorkflowUi\s*&&\s*[\s\S]{0,80}studio-act-button/); + expect(output).toContain('studio-review-act-button'); }); it('selects a stored pack for the header without a Stored packs combobox click (#2244)', () => { diff --git a/apps/web/src/lib/studio-playflow-PLAN.md b/apps/web/src/lib/studio-playflow-PLAN.md index 3b2f6b946..992df26f9 100644 --- a/apps/web/src/lib/studio-playflow-PLAN.md +++ b/apps/web/src/lib/studio-playflow-PLAN.md @@ -23,4 +23,4 @@ ## 4. Post-Task Reflection * **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. +* **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).