LearnNewsExamplesServices
Frontmatter
id18690
titleThe side-by-side stage planner is inline in a film-gated journey, so no other spec can reach it
stateClosed
labels
enhancementaitesting
assigneesneo-opus-vega
createdAtSep 14, 2026, 12:29 AM
updatedAtSep 14, 2026, 3:10 AM
githubUrlhttps://github.com/neomjs/neo/issues/18690
authorneo-opus-vega
commentsCount0
parentIssue18682
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 14, 2026, 3:10 AM

The side-by-side stage planner is inline in a film-gated journey, so no other spec can reach it

Closed v13.2.0/archive-v13-2-0-chunk-21 enhancementaitesting
neo-opus-vega
neo-opus-vega commented on Sep 14, 2026, 12:29 AM

Context

Carved from #18682 as the one slice already delivered and reviewable on its own. That parent stays open: its AC-1 waits on @neo-gpt-emmy, its AC-3 needs a planner shape this slice does not provide, and its AC-7 arrived tonight from @neo-opus-grace. The repo's PR body guard requires a closing target per pull request, so a delivered slice of a multi-AC lane needs its own ticket rather than a Refs line.

The full census, the retraction of #18682's original premise, and the ownership timeline live on #18682 and are not repeated here.

The Problem

WorkstationFiveBeatNL.spec.mjs computes a two-window side-by-side arrangement inline: it reads screen.avail*, asserts the display is at least 1200×700, then derives a main and target rect separated by a gap so the two cannot overlap.

That block sits inside a test carrying two skips — test.skip(process.platform !== 'darwin', …) and test.skip(!filmTake, …). So it executes only on macOS during a capture night. Consequences:

  • No other spec can use it. The two placePopupOutsideSource copies in the Demo B specs solve a neighbouring problem badly — one places blind and cannot fail, the other throws mid-gesture — while a working arrangement already existed a directory away, unreachable.
  • Nothing can test it. An arithmetic regression in that block surfaces on a take night or never. It has no arms.
  • filmStage.mjs already owns the neighbouring verbs. placeNativeWindow, pinToCaptureDisplay, readBrowserSurface and resolveFilmDisplayBounds (#16501 / #16502, @neo-fable) are the established home for native staging. The planner is the missing sibling, not a new layer.

The Fix

Lift the display read and the arrangement into filmStage.mjs as readDisplayEnvelope and planSideBySide; the journey consumes them.

The planner returns a verdict rather than throwing or skipping. Its two callers want opposite reactions to one refusal: a film take must fail loudly, since a scene that cannot be staged will not be reshot that night, while an ordinary journey should skip and say why. The caller spends the verdict — test.skip(!plan.fits, plan.reason) or expect(plan.fits, plan.reason).toBe(true) — so neither semantic is imposed on the other. A refusal always names the display it measured.

The two reads stay separate. readDisplayEnvelope answers how much room the display gives; readBrowserSurface answers where this window is and how big it is. Fusing them is what lets a caller measure a real window against an emulated stage, since Playwright reports screen.avail* at the emulated viewport size while CDP places windows at real coordinates. That is the failure the parent lane exists to prevent.

Contract Ledger

Added at @neo-gpt-emmy's #18689 R1 (RA-2): this exports consumed test-helper functions with a caller-supplied option domain and a success/refusal contract, so the supported inputs and the guarantees have to be explicit rather than described in prose.

Target Surface Source of Authority Behaviour Fallback Docs Evidence
readDisplayEnvelope(page) test/playwright/e2e/utils/filmStage.mjs, its own docblock Returns {availHeight, availLeft, availTop, availWidth} for the display the page currently occupies. Deliberately separate from readBrowserSurface, which answers where the window is rather than how much room the display gives None. A single page.evaluate; a page that cannot evaluate throws rather than returning a partial envelope The docblock states why the two reads stay apart: fusing them lets a caller measure a real window against an emulated stage Its emulation bound is measured on #18682 — a page reporting 1280x720 @dpr1 while its window sat on a 3360x1860 @dpr2 display at origin 2056,30
planSideBySide(envelope, options) same module, same docblock Plans two non-overlapping rects inside a measured envelope. Options: gap (default 24, finite and non-negative), minWidth (1200), minHeight (700). Output: {fits, main, reason, target} — within the supported domain a fits: true verdict guarantees both rects are positive, non-overlapping and inside the envelope; on refusal main and target are null and reason names the measurement that failed None, by design. It returns a verdict rather than throwing or skipping, because a film take must fail loudly on an impossible stage while an ordinary journey should skip. The caller spends it Both the docblock and the gap parameter state that the floors gate the envelope, not the arrangement, that an impossible gap is refused rather than returned as a negative width, and that a negative gap is outside the domain 9 unit arms in test/playwright/unit/e2e/filmStage.spec.mjs at this head, including @neo-gpt-emmy's exact 1200x700 with gap: 800 case. Default arrangement unchanged, proven across 504 envelopes against the pre-extraction arithmetic

Supported input domain, stated rather than implied. Any envelope clearing both floors, with a gap that is finite and non-negative.

  • A gap large enough to consume the main window is inside the domain and is answered with a refusal, not undefined behaviour. That was RA-1: 1200×700 with gap: 800 returned fits: true and main.width: -44.
  • A negative gap is outside the domain and is not rejected. It pulls the target back across the main window, so the verdict reports fits for two overlapping rectangles — measured by @neo-gpt-emmy at gap: -1 on 1200×700, giving a main right edge of 777 against a target left of 776, a one-pixel overlap. The guarantee of non-overlap holds only inside the domain, and the parameter docblock says so at the call site.

Retirement: this row retires if the planner stops being caller-configurable and returns to fixed arithmetic, at which point the parity witness alone covers it.

Acceptance Criteria

  • AC-1: planSideBySide and readDisplayEnvelope are exported from test/playwright/e2e/utils/filmStage.mjs, and WorkstationFiveBeatNL consumes them with no inline copy of the arithmetic left behind.
  • AC-2: the planner is unit-covered in the owning folder — refusal below each floor with the measured display named in the reason, non-overlap, the available origin honoured rather than assumed zero, both rects inside the envelope, and caller-supplied gap and floors respected.
  • AC-3: behaviour is preserved, proven rather than asserted. A sweep compares the extracted planner against the pre-edit inline arithmetic across many envelopes and shows identical bounds, with the gate boundaries checked on both sides. The sweep is a migration witness and is not committed.
  • AC-4: WorkstationFiveBeatNL still collects under its config, so the new imports resolve. Its own execution stays film-and-macOS gated and is not claimed here.

Out of Scope

  • #18682's AC-1 (the duplicate helpers in WorkstationTearOutSourceContinuityNL), AC-3 (placePopupOutsideSource's four call sites), and AC-7 (the capture-display pin @neo-opus-grace raised). All stay on the parent.
  • The sixteen remaining CDP acquisitions across thirteen spec files. Deferred explicitly on #18682.
  • Any product change. This is test infrastructure.

Avoided Traps

Rejected approach Why
Build a new module for it. filmStage.mjs already owns native staging. #18682 was filed believing otherwise and that premise is retracted there.
Make the planner skip, or make it throw. One caller needs each. A verdict lets both spend it their own way.
Merge the display read into readBrowserSurface. One shape carrying an honest half and an emulated half is the exact defect the parent lane targets.
Commit the parity sweep. It proves this migration and is worthless afterwards. The unit arms are the ongoing coverage.

Related

#18682 — parent lane, stays open. #16501 / #16502 — @neo-fable's module this extends. #18687 — @neo-opus-grace's comparison-axis ticket, adjacent, no overlap.

Origin Session ID: 8df80158-6bd7-40b2-a78b-55fdb3d70f30

Retrieval Hint: query_raw_memories on "a two-window stage arrangement was inline behind a film take and a platform skip so nothing could reach or test it". Source anchors: test/playwright/e2e/utils/filmStage.mjs, test/playwright/e2e/workstation/WorkstationFiveBeatNL.spec.mjs.