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
6 changes: 6 additions & 0 deletions apps/web/src/app/docs/api/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ export default function ApiDocsPage() {
Pack via <code className="text-teal-300">/api/video/pack</code>, then{' '}
<code className="text-teal-300">/api/workflows/video-to-actions</code>.
</p>
<ol className="mt-4 list-decimal space-y-1 pl-5 text-sm text-ink/60">
<li>Paste a YouTube URL on Home.</li>
<li>Home redirects to <code className="text-teal-300">/studio?video=...</code>.</li>
<li>Studio auto-starts analysis once for that handoff URL.</li>
<li>Transcript and events flow into actions and output publishing.</li>
</ol>
</section>
</main>
<Footer />
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/lib/__tests__/studio-handoff.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,26 @@ describe('submitHomePaste kicks pack emit then hands off to Studio', () => {
});

describe('applyStudioQueryAutoStart (?video= one-shot, Strict Mode safe)', () => {
it('suppresses duplicate start when a Strict Mode remount gets a fresh ref object', () => {
const remountWatchUrl = 'https://www.youtube.com/watch?v=pBsT6v-ciO8';
const firstMountKey = { current: null as string | null };
const remountKey = { current: null as string | null };
const start = vi.fn();
const first = applyStudioQueryAutoStart({
query: remountWatchUrl,
startedKey: firstMountKey,
start,
});
const remount = applyStudioQueryAutoStart({
query: remountWatchUrl,
startedKey: remountKey,
start,
});
expect(first).toBe('started');
expect(remount).toBe('already');
expect(start).toHaveBeenCalledTimes(1);
});

it('starts once with the canonical watch URL across a Strict Mode double effect', () => {
const startedKey = { current: null as string | null };
const start = vi.fn();
Expand Down
13 changes: 10 additions & 3 deletions apps/web/src/lib/studio-handoff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ export function submitHomePaste(raw: string): string | null {
}

export type StudioQueryStartedKey = { current: string | null };
let strictModeAutoStartedVideoId: string | null = null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module-level strictModeAutoStartedVideoId guard is never reset, permanently suppressing auto-start for the last-started video id across remounts within an SPA session.

Fix on Vercel


/**
* One-shot ?video= / ?url= kick. Safe under Strict Mode: the same startedKey
* ref suppresses a second start() for the same video id.
* One-shot ?video= / ?url= kick. Safe under Strict Mode remounts: both the
* caller ref and a module-level key suppress duplicate start() calls.
*/
export type StudioSearchParams = {
get(name: string): string | null;
Expand Down Expand Up @@ -94,8 +95,14 @@ export function applyStudioQueryAutoStart(input: {
return 'invalid';
}
input.onResolved?.(handoff.watchUrl);
if (input.startedKey.current === handoff.videoId) return 'already';
if (
input.startedKey.current === handoff.videoId ||
strictModeAutoStartedVideoId === handoff.videoId
) {
return 'already';
}
input.startedKey.current = handoff.videoId;
strictModeAutoStartedVideoId = handoff.videoId;
input.start(handoff.watchUrl);
return 'started';
}