Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/web/src/app/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
];

Expand Down
11 changes: 11 additions & 0 deletions apps/web/src/components/OneLoopStudio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,15 @@ export default function OneLoopStudio({
</div>
) : null}
<div className="flex items-center gap-1.5" role="toolbar" aria-label="Pack actions">
<button
type="button"
data-testid="studio-act-button"
onClick={() => void act()}
disabled={actBusy || !hasPayload}
className="uvai-btn disabled:opacity-40"
>
{actBusy ? 'Running tools…' : 'Act on this run'}
</button>
<button
type="button"
onClick={() => void exportPkg()}
Expand Down Expand Up @@ -1145,6 +1154,8 @@ export default function OneLoopStudio({
outputPane={
<StudioIdeOutput
video={selected}
actBusy={actBusy}
onAct={() => void act()}
stackChecks={stackCheckViews}
reviewActions={(workflowActions?.actions ?? []).map((action) => {
const card = studioActionCard(action);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ describe('OneLoopStudio IDE shell', () => {
expect(screen.getByTestId('studio-ide-output-tab-signals')).toBeTruthy();
expect(screen.getByTestId('studio-ide-ship')).toBeTruthy();
expect((screen.getByTestId('studio-ide-ship') as HTMLButtonElement).disabled).toBe(false);
expect(screen.getByTestId('studio-act-button').textContent).toBe('Act on this run');
expect((screen.getByTestId('studio-act-button') as HTMLButtonElement).disabled).toBe(false);
});

it('shows transcript retry when analysis failed and is retryable', () => {
Expand Down
34 changes: 32 additions & 2 deletions apps/web/src/components/studio/StudioIdeOutput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,15 @@ export default function StudioIdeOutput({
specReview,
stackChecks = [],
reviewActions = [],
actBusy,
onAct,
}: {
video: Video | undefined;
specReview?: React.ReactNode;
stackChecks?: StudioStackCheckView[];
reviewActions?: StudioReviewActionInput[];
actBusy?: boolean;
onAct?: () => void;
}) {
const [active, setActive] = useState<OutputSection>('events');

Expand Down Expand Up @@ -224,7 +228,22 @@ export default function StudioIdeOutput({
{active === 'intent' && (
<ul className="flex flex-col gap-3" data-testid="studio-ide-output-intent">
{reviewActions.length === 0 && actionItems.length === 0 ? (
<EmptyNote text="No action items extracted yet — what should the viewer do or make from this video?" />
<li className="uvai-card p-8 text-center">
<p className="text-xs text-slate-400">
No proposed actions yet — act on this run to review findings here.
</p>
{onAct ? (
<button
type="button"
data-testid="studio-action-cta"
onClick={onAct}
disabled={actBusy}
className="uvai-btn uvai-btn-primary mt-4 disabled:opacity-40"
>
{actBusy ? 'Running tools…' : 'Act on this run'}
</button>
) : null}
</li>
) : (
<>
{reviewActions.map((action, i) => {
Expand All @@ -239,12 +258,23 @@ export default function StudioIdeOutput({
<span className="uvai-metric-icon mt-0.5 shrink-0">
<ArrowIcon />
</span>
<div className="min-w-0">
<div className="min-w-0 flex-1">
<p className="text-[11px] font-semibold uppercase tracking-wide text-amber-700">
{card.statusLabel}
</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>
{onAct ? (
<button
type="button"
data-testid="studio-action-cta"
onClick={onAct}
disabled={actBusy}
className="uvai-btn uvai-btn-primary mt-3 disabled:opacity-40"
>
{actBusy ? 'Running tools…' : card.ctaLabel}
</button>
) : null}
</div>
</div>
</li>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @vitest-environment jsdom
import React from 'react';
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';
import StudioIdeOutput from '@/components/studio/StudioIdeOutput';
import type { Video } from '@/store/dashboard-types';

Expand Down Expand Up @@ -56,4 +56,23 @@ describe('StudioIdeOutput playflow', () => {
expect(card.textContent).toMatch(/needs review/i);
expect(card.textContent).toMatch(/Review evidence/);
});

it('renders review_action as a proposed card with Act on this run', () => {
const onAct = vi.fn();
render(
<StudioIdeOutput
video={baseVideo}
onAct={onAct}
reviewActions={[
{ tool: 'review_action', status: 'proposed', result: 'Review evidence' },
]}
/>,
);

fireEvent.click(screen.getByTestId('studio-ide-output-tab-intent'));
expect(screen.getByTestId('studio-review-action-card').textContent).toMatch(/Review this result/);
expect(screen.getByTestId('studio-action-cta').textContent).toBe('Act on this run');
fireEvent.click(screen.getByTestId('studio-action-cta'));
expect(onAct).toHaveBeenCalledTimes(1);
});
});
16 changes: 16 additions & 0 deletions apps/web/src/lib/__tests__/studio-pipeline-status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,13 @@ 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.toMatch(/Durable workflow run history/);
expect(pricing).not.toMatch(/reviewed actions/i);
expect(pricing).toContain('Proposed work from a Video Pack');
expect(pricing).toContain('Workflow run history');
expect(apiDocs).not.toMatch(/durable Studio analysis workflow/);
expect(studio).toContain('data-testid="studio-act-button"');
expect(studio).toContain('Act on this run');
});

it('renders review_action as a card with status, title, and detail', () => {
Expand All @@ -559,18 +565,28 @@ describe('studio-pipeline-status', () => {
expect(review.title).toMatch(/review this result/i);
expect(review.statusLabel).toBe('Needs review');
expect(review.detail).toBe('Review evidence');
expect(review.ctaLabel).toBe('Act on this run');

const proposed = studioActionCard({ tool: 'review_action', status: 'proposed' });
expect(proposed.detail).toMatch(/Proposed finding/);

const tool = studioActionCard({ tool: 'persist_insight', status: 'completed' });
expect(tool.kind).toBe('tool');
expect(tool.title).toMatch(/persist insight/i);
expect(tool.statusLabel).toBe('Done');
expect(tool.detail).toMatch(/no detail/i);
expect(tool.ctaLabel).toBe('Keep on this page');

const studio = readFileSync(join(process.cwd(), 'src/components/OneLoopStudio.tsx'), 'utf8');
const output = readFileSync(join(process.cwd(), 'src/components/studio/StudioIdeOutput.tsx'), 'utf8');
expect(studio).toContain('studioActionCard');
expect(output).toContain('studioActionCard');
expect(output).toContain('data-testid="studio-review-action-card"');
expect(output).toContain('data-testid="studio-action-cta"');
expect(studio).toContain('data-testid="studio-act-button"');
expect(studio).not.toMatch(
/showAgentWorkflowUi[\s\S]{0,400}data-testid="studio-act-button"/,
);
// IDE: actions surface in the output pane Intent tab.
expect(output).toContain("{ id: 'intent', label: 'Intent' }");
});
Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/lib/studio-pipeline-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,7 @@ export function studioActionCard(action: {
statusLabel: string;
detail: string;
kind: 'review' | 'tool';
ctaLabel: string;
} {
const isReview = action.tool === 'review_action';
const statusLabel =
Expand All @@ -656,12 +657,15 @@ export function studioActionCard(action: {
.join(' ');
const detail =
action.result?.trim() ||
(isReview ? 'Open the evidence on this page before you act.' : 'No detail from this tool.');
(isReview
? 'Proposed finding — open the evidence on this page before you act.'
: 'No detail from this tool.');
return {
title,
statusLabel,
detail,
kind: isReview ? 'review' : 'tool',
ctaLabel: isReview ? 'Act on this run' : 'Keep on this page',
};
}

Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/lib/studio-playflow-PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- [x] Transcript retry control when `studioCanRetryTranscript`
- [x] Export toast no longer uses sticky-footer `bottom-20`
- [x] Focused Vitest
- [x] Leftover after #2437: pricing Core copy (G) + ungated Act CTA on review cards (E)

## 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.
Expand All @@ -27,3 +28,4 @@
* **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.
* **Leftover after #2437 (this PR):** Pricing Core copy no longer says durable/reviewed; Act on this run is a toolbar + Intent CTA without `agent-workflow-ui`. Focused Vitest 5 files / 53 passed plus 3 related files / 15 passed. Canonical leftover issue #2440.
Loading