Skip to content

feat(web): synchronized Step Runner for Studio Deliverables - #2441

Open
kk-agent wants to merge 8 commits into
mainfrom
cursor/studio-step-runner-3c1b
Open

kk-agent wants to merge 8 commits into
mainfrom
cursor/studio-step-runner-3c1b

Conversation

@kk-agent

Copy link
Copy Markdown
Collaborator

Canonical issue

Related to #2331 (Studio IDE rebuild). No open issue describes this cut and this agent token cannot create one (same blocker as #2436 / #2437), so there is no Closes #N yet — please link or open the canonical issue.

Outcome

The Deliverables pane stops being a static reference and becomes a 1-of-N Step Runner modeled on the Pollen Robotics assembly guide: every step is anchored to the video timeline from pack evidence, Next seeks the player, a Step 0 prerequisites checklist precedes Step 1, a Full Runbook Markdown mode exports RUNBOOK.md, and the chat is re-grounded to the active step's transcript window.

Intent-vs-execution audit (see apps/web/src/lib/step-runner-PLAN.md §0): extraction already produced time-anchored evidence (segments, chapters, visuals, keyframes, grounded-spec citations), but Studio presented it as text and nothing in the pane drove the player. This PR closes that gap at the presentation layer without inventing evidence.

Scope

  • Included:
    • lib/runbook-steps.ts — pure compiler: requirements / action_items (or linked SOP) → steps with id, step_number, title, kind, timestamp_range | null, anchor provenance, content_markdown, verification (+ source), inputs/outputs, snippet, keyframe. Anchors only from: extractor start_s/end_s, validated grounded-spec citations, transcript segments quoting named stack.tools evidence or sharing distinctive vocabulary, chapters, visual events. Step 0 prerequisites from stack.tools / stack checks / explicit access & runtime mentions. RUNBOOK.md renderer.
    • components/studio/StepRunner.tsx, StudioRunbookPane.tsx — stepper (Prev/Next/quick-jump, #step-n hash, scroll-to-top), Autoplay-step-clip toggle (cue-only seek when off, pause at clip end when on), "Playing: Step k" follow chip, per-step card, local completion notes, Troubleshoot hook, markdown copy/download.
    • StudioIdeOutput — new first Runbook tab, default when a runbook exists; the seven extraction tabs are unchanged.
    • StudioIdeChat + api/chat/route.ts + chat-pack-grounding.ts — bounded, zod-validated step body field → identity + active step + transcript/visual evidence inside a 15s-padded window (whole-pack SOP/action lists omitted). Fails closed on malformed step or missing pack binding.
    • use-youtube-player.ts — seekTo(seconds, { autoplay }) (default unchanged).
    • Extractor prompt/parser: optional start_s/end_s on requirements and action_items ("omit when not observed — never guess"); client citation now carries bounded code_snippets. No VIDEO_PACK_STRUCTURE_SCHEMA_VERSION bump (would force a paid re-extract of every stored pack); stored packs derive anchors client-side.
    • Video.runbookProgress (local notes), RUNBOOK.md in Export.
  • Explicitly excluded:

Risk

  • Risk level: low
  • Failure mode: a coarse anchor (chapter-level) seeks a few seconds early; a step with weak vocabulary overlap stays unanchored (shown honestly). Default Deliverables tab changes from Events to Runbook when a pack exists.
  • Rollback: revert this PR; stored packs are unaffected (schema is additive, no version bump).

Verification

Head SHA: 7ffcb382c

  • Focused tests: cd apps/web && npx vitest run src/lib/__tests__/runbook-steps.test.ts src/components/studio/__tests__/StepRunner.test.tsx src/components/__tests__/OneLoopStudio.runbook.test.tsx src/lib/__tests__/chat-pack-grounding.test.ts src/app/api/__tests__/chat-pack-grounding-route.test.ts src/lib/__tests__/use-youtube-player.test.ts src/lib/__tests__/emit-video-pack.test.ts src/lib/__tests__/video-pack-extractor.test.ts src/lib/__tests__/action-surface.test.ts → all green
  • Full web suite: npx vitest run → 173 files, 1563 passed, 28 skipped (pre-existing opt-ins)
  • npx tsc --noEmit exit 0 · npx eslint on touched files exit 0 · npx next build exit 0
  • Live route round-trip (one-off, outside the repo): real POST /api/chat, memory pack store seeded with the live pBsT6v-ciO8 pack, real Vercel AI Gateway call → 200 in 1086 ms: "The speaker says, 'Step one: search up SeaDance 2.5, find any provider,' at 4:07–4:14 in the video."
  • Headed Chrome walkthrough on the production build (recording below)
  • Required CI
  • Review threads resolved

Fixture is a verbatim slice of the live pBsT6v-ciO8 pack (GET returned 200 on 2026-10-17); no synthetic rows except where a test needs an unanchored step.

Production evidence

UI + API contract change; preview on Vercel v0-uvai after CI. Not a production E2E. Two things could not be exercised in the agent VM and are environment limits, not code: the YouTube embed returned Error 153 on http://localhost (embed needs an https referer; CSP already allows youtube.com), and the production-mode proxy guard returned 503 "distributed rate limiting is not configured" (no Upstash credentials here). Live Gemini extraction of the new optional start_s/end_s was not run (no GOOGLE_API_KEY); the parser path is unit-tested.

studio-step-runner-walkthrough.mp4

Runbook tab opens on Step 0 prerequisites
Step 1 card with anchored range, kind, verification
Full Runbook Markdown view

Agent handoff

  • One canonical issue is linked (blocked: no open issue; token cannot create one)
  • No competing PR implements the same issue (fix(web): rewire Studio playflow B–G into the IDE #2436 / feat(web): restore Studio Ship this playflow #2437 touch Tools/Intent/toolbar only; this PR is additive there)
  • Acceptance criteria are satisfied (stepper, player sync, Step 0, dual modes, step-scoped chat, schema fields)
  • Required checks pass on the current head
  • Human decision is requested only for product, security, irreversible infrastructure, or production approval — note: this cut is outside the "Origin G.A.T.E. only" scope in AGENTS.md; it was explicitly requested, so Loop should confirm authorization before merge.

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

cursoragent and others added 8 commits October 17, 2026 17:04
Add runbook-steps.ts: a pure compiler that turns pack requirements,
action_items (or the linked SOP) into 1-of-N steps anchored to the video
timeline using only pack evidence - extractor-reported ranges,
grounded-spec citations, transcript segments quoting named tool evidence
or sharing distinctive vocabulary, chapters, and visual events. Steps
without evidence keep timestamp_range null. Includes Step 0 prerequisites
from stack.tools / stack checks, verification from grounded acceptance
criteria or tool checks, RUNBOOK.md rendering, and time/hash lookups.

Requirements and action_items gain optional start_s/end_s (no schema
version bump). Fixture is the live pBsT6v-ciO8 pack slice.

Co-authored-by: KK <kk-agent@users.noreply.github.com>
Ask Gemini for optional start_s/end_s on requirements and action_items
(omit when not observed, never guess) and keep only finite, ordered,
non-negative ranges in the parser. Pass bounded code_snippets through
verifyIdentityPack so the browser pack can show code steps. No stored-pack
schema version bump: existing packs keep working and derive anchors
client-side.

Co-authored-by: KK <kk-agent@users.noreply.github.com>
seekTo(seconds, { autoplay: false }) seeks and explicitly pauses so the
Step Runner can honor an 'Autoplay step clip' toggle. Default behavior
(seek + play) is unchanged; covered by a jsdom hook test with a stubbed
IFrame API.

Co-authored-by: KK <kk-agent@users.noreply.github.com>
Accept a bounded, zod-validated step context on POST /api/chat. When
present (and only with a pack binding), the system prompt becomes
identity + the active step + the transcript/visual evidence inside a
15s-padded window around the step's anchored range; whole-pack SOP and
action lists stay out so one step's troubleshooting does not drag the
full pack back into context. Steps without a verified timestamp fall
back to the whole transcript and say so. Malformed step contexts fail
closed (400) before any model call.

Co-authored-by: KK <kk-agent@users.noreply.github.com>
StepRunner: 'Step n of N' stepper with Previous/Next/quick-jump, Step 0
prerequisites checklist, per-step card (action type, anchored time range
with provenance, keyframe, quote, command/snippet with copy, inputs/
outputs, verification + local completion mark, troubleshoot hook).
Selecting a step scrolls to top, writes #step-n, seeks the player to the
anchored start (cue-only when 'Autoplay step clip' is off, pauses at the
clip end when on), and reports the active step for chat re-grounding.
Unanchored steps disable seek and say why. StudioRunbookPane switches
between the runner and a copy/download RUNBOOK.md reference view.

Co-authored-by: KK <kk-agent@users.noreply.github.com>
Wire the Step Runner into OneLoopStudio: compile the runbook from the
selected pack (+ linked SOP), hand the runner a player slice only when
the loaded video is the pack video, persist checked prerequisites and
completed steps on the Video row, and re-ground the chat composer to the
active step (chip + 'step' body field, clearable; Troubleshoot focuses
the composer). StudioIdeOutput gains a first 'Runbook' tab that opens by
default when a runbook exists; the seven extraction tabs are unchanged.
Export ships the rendered RUNBOOK.md. A pack switch drops a stale
#step-N hash while /studio#step-N deep links still restore the step.

Co-authored-by: KK <kk-agent@users.noreply.github.com>
…seek

Keep 'Step n of N' on one line at the default 340px Deliverables width
(select on its own row, autoplay + progress on one row) and distinguish
'player still loading/failed' from 'player shows a different video'
when seeking is disabled.

Co-authored-by: KK <kk-agent@users.noreply.github.com>
Co-authored-by: KK <kk-agent@users.noreply.github.com>
@vercel

vercel Bot commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
v0-uvai Ready Ready Preview, v0 Oct 17, 2026 5:33pm UTC

@github-actions

Copy link
Copy Markdown
Contributor

🔍 PR Validation

⚠️ Large PR detected (4111 lines changed)

@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: groupthinking/EventRelay/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 9a0c4fbb-a473-42b2-b0e0-8ed50641074e
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kk-agent
kk-agent marked this pull request as ready for review October 17, 2026 20:32
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-17T20:37:54.224656Z 7ffcb38 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Repository reconciliation: this PR does not reference exactly one canonical issue.

Please add a Closes #<issue> reference in the PR description so it can be tracked against the delivery plan.

See governance: #898

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ffcb382c9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

* grounded_spec candidate are copied verbatim. Nothing here is synthetic;
* tests that need an unanchored step add their own rows.
*/
export const PBST6V_VIDEO_ID = "pBsT6v-ciO8";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Replace the noncanonical runbook fixture

The newly added runbook fixture and its dependent tests use pBsT6v-ciO8, but this repository requires all test fixtures to use auJzb1D-fag; replace the fixture data, identities, frame paths, and assertions with the canonical video so this suite follows the repository's locked test-data convention.

AGENTS.md reference: AGENTS.md:L93-L93

Useful? React with 👍 / 👎.

Comment on lines +717 to +719
if (candidate.explicitRange) {
range = candidate.explicitRange;
anchor = 'extracted';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve grounded verification for explicitly timed steps

When a requirement has extractor-provided start_s/end_s and also corresponds to a grounded-spec citation with acceptance criteria, this branch skips citation matching and leaves groundedIds empty. The later acceptance-criteria filter therefore discards the grounded verification and emits a generic comparison or stack check instead; resolve the matching grounded requirement independently while still allowing the explicit range to win as the timestamp anchor.

Useful? React with 👍 / 👎.

Comment on lines +170 to +175
currentTime >= end &&
currentTime < end + CLIP_END_GRACE_SECONDS &&
clipPausedForRef.current !== activeStep.id
) {
clipPausedForRef.current = activeStep.id;
pause();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Re-arm clip stopping when replaying the active step

After autoplay pauses a step once, clipPausedForRef remains set to that step ID. If the user clicks “Seek to” to replay the same step, the guard remains false when playback reaches the end again, so the video continues beyond the selected clip; clear or otherwise re-arm this marker whenever the active step is manually sought.

Useful? React with 👍 / 👎.

Comment on lines +121 to +123
const grounded = await resolveChatPackGrounding(packBinding, {
step: stepContext.status === 'ok' ? stepContext.step : null,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Derive chat step context from the hosted pack

Any caller with a valid pack ID can submit an arbitrary schema-valid step, and this route forwards its client-supplied title, instructions, verification text, tools, and timestamp range directly into the system prompt without confirming that the step exists in the hosted pack. A forged request can therefore inject invented step evidence and select an unrelated transcript window while the response is presented as pack-grounded; resolve the step ID against a server-compiled runbook and reject mismatched client fields.

AGENTS.md reference: AGENTS.md:L72-L72

Useful? React with 👍 / 👎.

Comment on lines 89 to +92
const body: Record<string, unknown> = { query, history };
if (videoId) body.video_id = videoId;
if (packId) body.pack_id = packId;
if (stepContext && (videoId || packId)) body.step = stepContext;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear prior-step history when changing chat scope

When the user chats about one step and then advances to another, the component sends the new stepContext together with the last ten messages from the previous step. The server includes that history in the model conversation, so prior out-of-window questions and answers can override or contaminate the new step-scoped grounding despite the prompt intentionally limiting evidence to the active window; reset or partition history when the active step identity changes.

Useful? React with 👍 / 👎.


export function runbookStepToChatContext(step: RunbookStep, totalSteps: number): ChatStepContext {
return {
id: step.id,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bound generated step IDs to the API schema

The compiler prefixes source IDs with rb- without imposing a length limit, but this serializer forwards the result unchanged while the server rejects IDs longer than 64 characters. A valid 64-character extractor ID therefore becomes a 67-character runbook ID, and every scoped chat or troubleshooting request for that step fails with step_context_invalid; generate a bounded stable ID or make the client and server limits consistent.

Useful? React with 👍 / 👎.

Comment on lines +415 to +422
if (selectionMountedRef.current && parseRunbookStepHash(window.location.hash) !== null) {
window.history.replaceState(
window.history.state,
'',
`${window.location.pathname}${window.location.search}`,
);
}
selectionMountedRef.current = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the initial step hash during pack auto-selection

On a hard load of /studio#step-N, selectedVideoId initially has no value because the persisted store saves videos but not the selection. This effect marks selectionMountedRef on that empty render, so when the stored pack is auto-selected moments later it treats the first real selection as a pack switch and removes the hash before StepRunner can restore it; only arm hash clearing after an actual initial selection has been established.

Useful? React with 👍 / 👎.

Comment on lines +114 to +115
start_s: z.number().nullable().optional(),
end_s: z.number().nullable().optional(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Drop malformed optional ranges without dropping the action

Adding start_s and end_s to the strict action-item parse means an otherwise valid action item is now discarded entirely whenever an extractor or older stored pack supplies either optional timestamp in a nonnumeric form. This contradicts parsePackTimeRange's contract to drop invalid ranges and regresses inputs that were accepted before these keys became schema fields; parse the core action independently and pass the raw optional bounds to parsePackTimeRange.

Useful? React with 👍 / 👎.

completed={current.completedSteps.includes(activeStep.id)}
canSeek={canSeek}
seekBlockedReason={seekBlockedReason}
onSeek={() => {

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 in-card "Seek to" button re-seeks the active step but never resets clipPausedForRef, so the clip-end auto-pause fails to re-arm on replay and playback runs past the step boundary.

Fix on Vercel

This branch was successfully deployed

1 active deployment
Preview — 7ffcb382 Deployed Oct 17, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants