Repository navigation
feat(web): restore Studio Ship this playflow #2437
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
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 |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ import { | |
| pickOfficialTemplate, | ||
| stackCheckStatus, | ||
| stackCheckStatusLabel, | ||
| studioStackCheckView, | ||
| } from '@/lib/official-templates'; | ||
| import { clsx } from 'clsx'; | ||
| import Nav from '@/components/Nav'; | ||
|
|
@@ -76,6 +77,7 @@ import { | |
| studioPlayerPhase, | ||
| studioPromotePackWorkbench, | ||
| studioRunQuality, | ||
| studioShipCta, | ||
| studioStatusLabel, | ||
| studioStatusMessage, | ||
| studioTranscriptBody, | ||
|
|
@@ -406,6 +408,14 @@ export default function OneLoopStudio({ | |
|
|
||
| const holdReason = deployHoldReason(linkedSop, completedChecks, 'anonymous'); | ||
| const officialTemplate = pickOfficialTemplate(linkedSop); | ||
| const stackCheckViews = stackChecks.map((item) => { | ||
| const status = stackCheckStatus(item, completedChecks, 'anonymous'); | ||
| return { | ||
| ...studioStackCheckView(item, completedChecks, 'anonymous'), | ||
| status, | ||
| statusLabel: stackCheckStatusLabel(status), | ||
| }; | ||
| }); | ||
|
|
||
| useEffect(() => { | ||
| useDashboardStore.persist.rehydrate(); | ||
|
|
@@ -569,6 +579,7 @@ export default function OneLoopStudio({ | |
| hasLinkedSopSteps: Boolean(linkedSop?.steps.length), | ||
| hasProjectScaffold: Boolean(selected?.insights?.project_scaffold), | ||
| }); | ||
| const shipCta = studioShipCta({ canExport: hasPayload, holdReason }); | ||
| const runState = busy ? 'working' : selected ? 'ready' : 'idle'; | ||
| const quality = studioRunQuality( | ||
| selected?.jobId ? { ok: true, status: 200, jobId: selected.jobId } : null, | ||
|
|
@@ -1024,12 +1035,14 @@ export default function OneLoopStudio({ | |
| <div className="flex items-center gap-1.5" role="toolbar" aria-label="Pack actions"> | ||
| <button | ||
| type="button" | ||
| onClick={exportPkg} | ||
| disabled={!hasPayload} | ||
| data-testid="studio-ide-export" | ||
| className="uvai-btn disabled:opacity-40" | ||
| onClick={() => void exportPkg()} | ||
| disabled={!shipCta.enabled} | ||
| data-testid="studio-ide-ship" | ||
| title={shipCta.hint} | ||
| className="uvai-btn uvai-btn-primary inline-flex items-center gap-1.5 disabled:opacity-40" | ||
| > | ||
| Export | ||
| <Rocket className="h-4 w-4" aria-hidden /> | ||
| Ship this | ||
| </button> | ||
| <button | ||
| type="button" | ||
|
|
@@ -1043,10 +1056,20 @@ export default function OneLoopStudio({ | |
| ? 'Verify pack health and open the hosted app, or get recovery steps if the pack is missing.' | ||
| : 'Paste a YouTube URL and run analysis first.' | ||
| } | ||
| className="uvai-btn uvai-btn-primary disabled:opacity-40" | ||
| className="uvai-btn disabled:opacity-40" | ||
| > | ||
| {buildBusy ? 'Building…' : 'Build live'} | ||
| </button> | ||
| {showTranscriptRetry ? ( | ||
| <button | ||
| type="button" | ||
| data-testid="studio-transcript-retry" | ||
| onClick={() => void runAnalysis(url || selected?.url || '')} | ||
|
Comment on lines
+1063
to
+1067
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 a failed video is selected but the user has edited the URL field, Useful? React with 👍 / 👎. |
||
| className="uvai-btn" | ||
| > | ||
| Retry transcript | ||
| </button> | ||
| ) : null} | ||
| </div> | ||
| <p className="w-full font-mono text-[11px]" style={{ color: 'var(--uvai-ink-faint)' }} role="status" data-testid="studio-ide-status"> | ||
| {statusText} | ||
|
|
@@ -1122,6 +1145,15 @@ export default function OneLoopStudio({ | |
| outputPane={ | ||
| <StudioIdeOutput | ||
| video={selected} | ||
| stackChecks={stackCheckViews} | ||
| reviewActions={(workflowActions?.actions ?? []).map((action) => { | ||
| const card = studioActionCard(action); | ||
|
Comment on lines
+1148
to
+1150
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.
These cards cannot appear in the current Studio UI: Useful? React with 👍 / 👎. |
||
| return { | ||
| tool: action.tool, | ||
| status: action.status, | ||
| result: action.result ?? card.detail, | ||
| }; | ||
| })} | ||
| specReview={ | ||
| selected?.videoPack?.pack ? ( | ||
| <GroundedSpecReview | ||
|
|
@@ -1149,7 +1181,7 @@ export default function OneLoopStudio({ | |
| data-testid="studio-export-toast" | ||
| role={exportToast.tone === 'error' ? 'alert' : 'status'} | ||
| className={clsx( | ||
| 'fixed bottom-20 left-1/2 z-40 w-[min(36rem,calc(100%-2rem))] -translate-x-1/2 rounded-lg border px-4 py-3 text-sm shadow-lg', | ||
| 'fixed bottom-4 left-1/2 z-40 w-[min(36rem,calc(100%-2rem))] -translate-x-1/2 rounded-lg border px-4 py-3 text-sm shadow-lg', | ||
| exportToast.tone === 'success' | ||
| ? 'border-amber-300 bg-amber-50 text-amber-800' | ||
| : 'border-red-300 bg-red-50 text-red-800', | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| // @vitest-environment jsdom | ||
| import React from 'react'; | ||
| import { cleanup, fireEvent, render, screen } from '@testing-library/react'; | ||
| import { afterEach, describe, expect, it } from 'vitest'; | ||
| import StudioIdeOutput from '@/components/studio/StudioIdeOutput'; | ||
| import type { Video } from '@/store/dashboard-types'; | ||
|
|
||
| const baseVideo: Video = { | ||
| id: 'row-output', | ||
| title: 'Playflow output', | ||
| url: 'https://www.youtube.com/watch?v=auJzb1D-fag', | ||
| status: 'complete', | ||
| progress: 100, | ||
| transcript: 'Enough transcript text for analysis ready state in Studio tests.', | ||
| }; | ||
|
|
||
| afterEach(() => { | ||
| cleanup(); | ||
| }); | ||
|
|
||
| describe('StudioIdeOutput playflow', () => { | ||
| it('renders pack-named stack checks as skip/unknown rows that do not look like a hold', () => { | ||
| render( | ||
| <StudioIdeOutput | ||
| video={baseVideo} | ||
| stackChecks={[ | ||
| { | ||
| id: 'chk_tool_seadance', | ||
| title: 'Use SeaDance 2.5 as named in the video', | ||
| status: 'unknown', | ||
| statusLabel: 'Unknown — UVAI cannot run this check', | ||
| holdsShip: false, | ||
| }, | ||
| ]} | ||
| />, | ||
| ); | ||
| fireEvent.click(screen.getByTestId('studio-ide-output-tab-tools')); | ||
| const row = screen.getByTestId('studio-stack-check-chk_tool_seadance'); | ||
| expect(row.textContent).toMatch(/SeaDance 2\.5/); | ||
| expect(row.textContent).toMatch(/cannot run this check/i); | ||
| expect(row.textContent).not.toMatch(/production is held/i); | ||
| }); | ||
|
|
||
| it('renders review_action results as cards with status, title, and detail', () => { | ||
| render( | ||
| <StudioIdeOutput | ||
| video={baseVideo} | ||
| reviewActions={[ | ||
| { tool: 'review_action', status: 'proposed', result: 'Review evidence' }, | ||
| ]} | ||
| />, | ||
| ); | ||
| fireEvent.click(screen.getByTestId('studio-ide-output-tab-intent')); | ||
| const card = screen.getByTestId('studio-review-action-card'); | ||
| expect(card.textContent).toMatch(/review this result/i); | ||
| expect(card.textContent).toMatch(/needs review/i); | ||
| expect(card.textContent).toMatch(/Review evidence/); | ||
| }); | ||
| }); |
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.
For the anonymous users targeted by this change, this handler still calls
downloadScaffoldPackage, which posts to/api/workspace/export; that route invokesrequireProFeatureAccess, and its route test confirms non-Pro callers receive a 402 checkout response rather than a ZIP. Consequently the newly enabled primary CTA and its “Download the Video Pack ZIP” hint do not deliver the stated anonymous Ship flow. Either use an export endpoint anonymous users may access or present this as a Pro checkout action.Useful? React with 👍 / 👎.