LearnNewsExamplesServices
Frontmatter
id19571
titleFilm take: a take can hold at a named step for an introduction or explanatory shot
stateClosed
labels
enhancementaidashboard
assigneesneo-fable
createdAtOct 10, 2026, 10:52 PM
updatedAtOct 10, 2026, 11:45 PM
githubUrlhttps://github.com/neomjs/neo/issues/19571
authorneo-fable
commentsCount0
parentIssue15252
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtOct 10, 2026, 11:45 PM

Film take: a take can hold at a named step for an introduction or explanatory shot

Closed v13.2.0/archive-v13-2-0-chunk-24 enhancementaidashboard
neo-fable
neo-fable commented on Oct 10, 2026, 10:52 PM

Context

The film's A6 narration (#15252) opens with a hook over the window birth and then the team's introductions — the operator's steer of 2026-10-10 20:4xZ (via Emmy): "our self-intros and matching video/voice content are crucial; more content from us is optional". Emmy's measured bindings (20:55Z) put the introduction's picture between the verified birth and the journey (Continue released no earlier than birth + 20.95 s) and ask for a living-room explanatory interval after the native return (M06 + E02 = 8.4 s before the rail's pin). The take at PR #19570's head gives those passages ≈ 1 s each: the committed breath, then the journey's gate resolves at once; the whole-again beat, then the rail's pin.

Design authority: the operator's steer above, within the 2026-09-23 pacing rule (no gesture or pause stretched to fit a line) and the screenplay's own gate contract — "a gate is a wait, not pacing" (WorkstationFilmTourNL third test). An introduction or an explanation over the living room is a designed shot with its own content, not a stretched pause; the question is only where its time comes from, and the answer must leave the in-app viewer's tour and the headless witnesses untouched.

The Problem

Under autoGates the controller's step settlement returns as soon as the cue and the refresh settle: replays and takes never wait anywhere. The two shots sit exactly at existing steps — the second gate's step (film-journey[0], right after the committed birth) and the whole-again beat (film-reintegration[2]) — but the take cannot hold there. The alternatives are worse: cue-free pauses of 19 s and 8 s in the screenplay break the 1.5 s reading-beat bound that fiveBeatFilm.spec pins and make every in-app viewer wait; a viewer-mode gate in the take renders the Continue button into the recording (TourToolbar's workstation-tour-continue, bound to tour.gatePrompt) and the return has no gate at all.

The Architectural Reality

  • apps/workstation/view/TourController.mjs: autoGates is a plain field set through startTour({autoGates}) / runVisibleTour or directly before Start by a transport; settleTourStep(data) is the host's settlement barrier the runner awaits per step (TourRunner → stepSettlement), awaiting the step's cue settlement and the workspace refresh — a cue that failed throws there. The controller extends Neo.controller.Component → core.Base#timeout(time, {signal}) is the destroy-safe wait (registered-async contract; rejects with Neo.isDestroyed).
  • The tour receipt (getTourReceipt) is the cue clock the take's QA reads, so a hold must appear in it.
  • test/playwright/unit/apps/workstation/TourController.spec.mjs pins the gate contract; the film witness asserts both gates continued: 'auto' under autoGates and that no gate ever waited for a viewer. The controller's own progress path (onTourStepSettled) awaits a 90 ms timeout per step, so a wall-clock floor on a whole playback cannot prove that a hold ran (Sophie's falsifier on PR #19572, review 5480839549).
  • The scratch take runner (host-local, never committed) sets {autoGates: true} through the Neural Link before pressing Start.

The Fix

In TourController: a plain field takeHoldMs (default null; {'<sceneId>[<stepIndex>]': ms}, a startTour/runVisibleTour option like autoGates). In settleTourStep, after the step's own barrier (cue settlement + refresh, unchanged), under autoGates only: the hold the key names → this.timeout(hold), recorded as {heldMs, sceneId, stepIndex} in holds, which the tour receipt carries (reset per playback). No prompt is published, so no Continue button renders; the viewer's tour (no autoGates) reads no hold. The take runner passes takeHoldMs: {'film-journey[0]': <intro>, 'film-reintegration[2]': <return>}; the headless witnesses pass nothing and keep their receipts unchanged. (The first cut of this ticket keyed the hold to gate ordinals; Emmy's return interval showed the hold belongs to any named step — corrected in place, 21:0xZ.)

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
TourController.takeHoldMs — plain field; startTour(script, {takeHoldMs}) / runVisibleTour(script, {takeHoldMs}) option; settable directly before Start by a transport (setProperties over the Neural Link) apps/workstation/view/TourController.mjs (takeHoldMs, runVisibleTour, settleTourStep) Shape {'<sceneId>[<stepIndex>]': ms}; a key names a step by the screenplay's scene id and the step's index within that scene. A finite ms > 0 holds; 0, negative or missing keys hold nothing. The option omitted keeps the current map (same contract as autoGates); null clears every hold. Read only while autoGates is true: viewer mode (autoGates: false) never holds, whatever the map holds. Default null: no hold anywhere — the in-app tour and the headless witnesses The field's and both methods' JSDoc TourController.spec: the take-hold test (the hold blocks the next beat until release; viewer mode records none; the map survives into the next playback); WorkstationFilmTourNL 3/3 + WorkstationHumanPopupOverlapNL 5/5 with the default
The hold's place in the settlement barrier settleTourStep The hold runs AFTER the step's own barrier: a failed cue still throws before any hold (cueFailed), the workspace refresh still settles first; the wait is core.Base#timeout, so destroying or cancelling the controller mid-hold rejects with Neo.isDestroyed and the existing cancel/cleanup path runs — a hold can never mask a failure or outlive its owner n/a (no hold → the barrier is unchanged) settleTourStep JSDoc + inline note TourController.spec: "a failed window cue fails its beat…", the cancellation tests (unchanged), the take-hold test's stubbed timeout
getTourReceipt().holds TourController receipt assembly (runVisibleTour) [{heldMs, sceneId, stepIndex}] in settlement order, one entry per hold that completed. heldMs is the REQUESTED (configured) duration, not a measurement — the measured picture time is the take's beat log and the receipt's elapsedMs. Reset to [] at each playback start (runVisibleTour); [] when no hold ran [] The holds member's JSDoc TourController.spec take-hold test (holds equals the configured entries; a second playback records afresh); the next take's receipt (AC-3, #15252)

Acceptance Criteria

  • TourController.spec: under autoGates with takeHoldMs = {'s[0]': <ms>} the gate still settles synchronously and prompt-free, the next beat cannot start until the hold's wait is released (a controllable hold, isolated from the controller's progress waits — a wait replaced by an already-resolved promise fails the test), the receipt's holds records {heldMs, sceneId, stepIndex} afresh per playback; with autoGates: false the same configuration records no hold.
  • The film tour witness and the popup-overlap witness stay green with the default (no hold); the gate receipts are unchanged.
  • Post-merge: the next take at the merged head holds film-journey[0] for the introduction shot and film-reintegration[2] for the explanation (Emmy's bindings set the values) and its receipt's holds shows both (#15252).

Out of Scope

  • The portrait cards, the explanatory overlay and the CTA card (the cut; #15252).
  • The recorder's post-roll for the closing lines (take runner only, no app change).
  • Any screenplay change: the steps' positions, captions and pauses stay as they are.

Related

#15252 (the film epic; plan of record step 2, comment 6101992812) · #19568 / PR #19570 (the pacing leaf, merged ec9aac12fd) · #19569 (Ada's cross-window scene, lands on top)

Live latest-open sweep: the latest 20 open issues at 20:5xZ, no equivalent (the two newest film leaves are #19568 and #19569). A2A: the latest 12 messages, no claim on a take hold or the introduction shot. MC sweep: "film take footage for the introductions over the born window; gate wait in the take; autoGates resolves at once", 6 results, no prior decision — the take recipe and today's steer only. Own-assignment sweep: 3 open (#15252 parent, #19568, #19382), none overlapping.

Origin Session ID: 458453a1-1751-41de-b0ce-e3738da16039

Retrieval Hint: "film take hold named step takeHoldMs settleTourStep holds introduction shot explanation interval"

tobiu referenced in commit a852b8c - "feat(workstation): a take can hold at a named step for an introduction or explanatory shot (#19571) (#19572) on Oct 10, 2026, 11:45 PM
tobiu closed this issue on Oct 10, 2026, 11:45 PM