Repository navigation
fix(web): rewire Studio playflow B–G into the IDE #2436
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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({ | |
| <Rocket className="h-4 w-4" aria-hidden /> | ||
| Ship this | ||
| </button> | ||
| <button | ||
| type="button" | ||
| data-testid="studio-act-button" | ||
| onClick={() => void act()} | ||
|
Comment on lines
+1059
to
+1060
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In the normal flow, Run already calls Useful? React with 👍 / 👎. |
||
| disabled={actBusy || !canAct} | ||
|
Comment on lines
+1057
to
+1061
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When AGENTS.md reference: AGENTS.md:L64-L68 Useful? React with 👍 / 👎. |
||
| title={ | ||
| canAct | ||
| ? 'Prepare proposed work from this Video Pack. Review before you act.' | ||
| : 'Paste a YouTube URL and run analysis first.' | ||
| } | ||
| className="uvai-btn disabled:opacity-40" | ||
| > | ||
| {actBusy ? 'Acting…' : 'Act on this run'} | ||
| </button> | ||
| <button | ||
| type="button" | ||
| data-testid="studio-build-live-button" | ||
|
|
@@ -1145,6 +1169,7 @@ export default function OneLoopStudio({ | |
| outputPane={ | ||
| <StudioIdeOutput | ||
| video={selected} | ||
| eventsEmptyMessage={eventsEmptyMessage} | ||
| stackChecks={stackCheckViews} | ||
| reviewActions={(workflowActions?.actions ?? []).map((action) => { | ||
| const card = studioActionCard(action); | ||
|
|
@@ -1154,6 +1179,19 @@ export default function OneLoopStudio({ | |
| result: action.result ?? card.detail, | ||
| }; | ||
| })} | ||
| onAct={() => void act()} | ||
| actBusy={actBusy} | ||
| canAct={canAct} | ||
|
Comment on lines
+1182
to
+1184
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
After Act completes for one video, selecting another stored pack does not clear or associate the component-level Useful? React with 👍 / 👎. |
||
| packWorkbench={ | ||
| promotePack ? ( | ||
| <PackWorkbench | ||
| architecture={packFormation.architecture} | ||
| artifacts={packFormation.artifacts} | ||
| onExport={() => void exportPkg()} | ||
|
Comment on lines
+1187
to
+1190
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For a pack with no events and only AGENTS.md reference: AGENTS.md:L34-L39 Useful? React with 👍 / 👎. |
||
| canExport={hasPayload} | ||
| /> | ||
| ) : undefined | ||
| } | ||
| specReview={ | ||
| selected?.videoPack?.pack ? ( | ||
| <GroundedSpecReview | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| 'use client'; | ||
|
|
||
| import { useState } from 'react'; | ||
| import { useState, type ReactNode } from 'react'; | ||
| import type { Video } from '@/store/dashboard-types'; | ||
| import type { ExtractedEvent } from '@/lib/types'; | ||
| import { studioActionCard } from '@/lib/studio-pipeline-status'; | ||
|
|
@@ -42,22 +42,34 @@ const SECTIONS: Array<{ id: OutputSection; label: string }> = [ | |
| export default function StudioIdeOutput({ | ||
| video, | ||
| specReview, | ||
| eventsEmptyMessage, | ||
| stackChecks = [], | ||
| reviewActions = [], | ||
| packWorkbench, | ||
| onAct, | ||
| actBusy = false, | ||
| canAct = false, | ||
| }: { | ||
| video: Video | undefined; | ||
| specReview?: React.ReactNode; | ||
| specReview?: ReactNode; | ||
| eventsEmptyMessage?: string; | ||
| stackChecks?: StudioStackCheckView[]; | ||
| reviewActions?: StudioReviewActionInput[]; | ||
| packWorkbench?: ReactNode; | ||
| onAct?: () => void; | ||
| actBusy?: boolean; | ||
| canAct?: boolean; | ||
| }) { | ||
| const [active, setActive] = useState<OutputSection>('events'); | ||
|
|
||
| const hasContent = Boolean( | ||
| 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 +122,37 @@ export default function StudioIdeOutput({ | |
| ) : ( | ||
| <> | ||
| {active === 'events' && ( | ||
| <ul className="flex flex-col gap-3" data-testid="studio-ide-output-events"> | ||
| <div className="flex flex-col gap-3" data-testid="studio-ide-output-events"> | ||
| {events.length === 0 ? ( | ||
| <EmptyNote text="No events extracted from this video yet." /> | ||
| <EmptyNote | ||
| text={ | ||
| eventsEmptyMessage || | ||
| 'This run has no extracted events. Pack identity stays on this page.' | ||
| } | ||
| /> | ||
| ) : ( | ||
| events.map((e) => ( | ||
| <li key={e.id} className="uvai-card p-4"> | ||
| <div className="flex items-center justify-between gap-2"> | ||
| <span className="inline-flex items-center gap-1.5 rounded-full bg-neutral-100 px-2.5 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-neutral-700"> | ||
| <DotIcon /> | ||
| {e.type} | ||
| </span> | ||
| {e.timestamp ? ( | ||
| <span className="font-mono text-[11px] text-slate-400">{e.timestamp}</span> | ||
| <ul className="flex flex-col gap-3"> | ||
| {events.map((e) => ( | ||
| <li key={e.id} className="uvai-card p-4"> | ||
| <div className="flex items-center justify-between gap-2"> | ||
| <span className="inline-flex items-center gap-1.5 rounded-full bg-neutral-100 px-2.5 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-neutral-700"> | ||
| <DotIcon /> | ||
| {e.type} | ||
| </span> | ||
| {e.timestamp ? ( | ||
| <span className="font-mono text-[11px] text-slate-400">{e.timestamp}</span> | ||
| ) : null} | ||
| </div> | ||
| <p className="mt-2 text-sm font-semibold text-slate-900">{e.title}</p> | ||
| {e.description ? ( | ||
| <p className="mt-1 text-xs leading-relaxed text-slate-500">{e.description}</p> | ||
| ) : null} | ||
| </div> | ||
| <p className="mt-2 text-sm font-semibold text-slate-900">{e.title}</p> | ||
| {e.description ? ( | ||
| <p className="mt-1 text-xs leading-relaxed text-slate-500">{e.description}</p> | ||
| ) : null} | ||
| </li> | ||
| )) | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| )} | ||
| </ul> | ||
| {packWorkbench} | ||
| </div> | ||
| )} | ||
|
|
||
| {active === 'lingo' && ( | ||
|
|
@@ -245,6 +265,17 @@ export default function StudioIdeOutput({ | |
| </p> | ||
| <p className="mt-1 text-sm font-semibold text-slate-900">{card.title}</p> | ||
| <p className="mt-1 text-xs leading-relaxed text-slate-500">{card.detail}</p> | ||
| {card.kind === 'review' && onAct ? ( | ||
| <button | ||
| type="button" | ||
| data-testid="studio-review-act-button" | ||
| onClick={onAct} | ||
| disabled={actBusy || !canAct} | ||
|
Comment on lines
+268
to
+273
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Real Act previews return registered tool names such as Useful? React with 👍 / 👎. |
||
| className="uvai-btn uvai-btn-primary mt-3 disabled:opacity-40" | ||
| > | ||
| {actBusy ? 'Acting…' : 'Act on this run'} | ||
| </button> | ||
| ) : null} | ||
| </div> | ||
| </div> | ||
| </li> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a stored pack is selected and the user types a different valid URL without first clicking Run,
canActvalidates the typed URL viaurl || selected?.url, butact()processesselected?.url || url. The enabled CTA therefore starts the workflow for the old selected video while the input and player show the new one; resolve the source once or disable Act until the typed URL is associated with the selection.Useful? React with 👍 / 👎.