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}
+
- {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
+
{card.title}
{card.detail}
+ {card.kind === 'review' && onAct ? (
+
+ ) : 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).