LearnNewsExamplesServices
Frontmatter
id19569
titleFilm tour: restoring a perspective re-births a closed window
stateOpen
labels
enhancementaitestingdashboard
assigneesneo-opus-ada
createdAtOct 10, 2026, 10:15 PM
updatedAtOct 11, 2026, 9:01 PM
githubUrlhttps://github.com/neomjs/neo/issues/19569
authorneo-opus-ada
commentsCount0
parentIssue15252
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 19583 A popup re-opened by Undo or a perspective has no road home but close
blocking[]

Film tour: restoring a perspective re-births a closed window

Open Backlog/active-chunk-27 enhancementaitestingdashboard
neo-opus-ada
neo-opus-ada commented on Oct 10, 2026, 10:15 PM

Next step (2026-10-11 19:1xZ): place the re-born window from the Group's own hints

The open measurement has a cause in app code. VesselWorkspace#getNativeWindowOpenConfig, the opener behind Undo-after-close and a restore's workspace-open (NativeVesselTransaction.embodyWorkspace), returns a fixed windowFeatures: 'width=700,height=600' and no position. A user who closes a popup and presses Undo gets the window back at the browser's default spot, not where it was.

The Workstation already keeps where it was. dockPlacement (Placement.forGroup) holds hints[workspaceKey] = {dx, dy, fallbackTarget}, which are offsets against the main frame, refreshed on settled moves.

Prescription: getNativeWindowOpenConfig reads dockPlacement.hints[reservation.workspaceKey] and opens at main.outerRect + {dx, dy}. The main window's current outerRect comes from Neo.manager.Window. The engine clamps to the screen, as for tear-out vessels.

Size is not in the hints ({dx, dy} only). Carrying it would change the topology wire record (ADR 0029), so that stays a separate question. With a known position, the headless stack-return ending can then be measured as the film scene needs. This is still post-cut work.

Status (2026-10-11 06:5xZ): unblocked; deferred until after the 13.2 cut

  • #19583 is fixed (#19596, merged as 504b41cb01): a re-born window now closes once its stack comes home. That removes the stack-return ending's open-window gap. The title-bar ending stays refused by design (DockLayoutsWindows.md station five), and the scene no longer needs the scripted close.

  • One measurement for the ending remains open. Driving executeStackReturnStep from a re-born window headless did not get main to a ready claim (05:1xZ, on #19596's branch). Geometry is the likely cause: the re-born window opens at a browser-default position (width=700,height=600, no coordinates). So either the scene places the window before the return, or the ending gets a headed check on the stage.

  • Decision (Ada, Tier 2): deferred until after the 13.2 cut, for three reasons:

    • the scene is optional (operator, 2026-10-10);
    • the film's 13.2 take is being finalized;
    • the branch's film commit conflicts with the film close (#19584, #19587) in fiveBeatFilm.mjs, its unit spec and WorkstationFilmTourNL.

    The branch ada/19569-film-perspective-rebirth (two commits, unpushed) is kept. Its first commit (the cues and the witness) rebases cleanly onto 3ec6e0b945.

The previous status follows.

Status (2026-10-10 22:20Z): witness green; the beat waits for a re-born window's way home

The witness holds. WorkstationPerspectivesNL › "a topology restore returns the pane to its closed vessel, and opening that workspace re-births the window" goes through the film's own cues:

  • the topology capture (it saves the working layout back, otherwise later auto-saves overwrite the capture);
  • the restore, after which the pane sits in the closed vessel's workspace and no window opens;
  • workspace-open, which re-births the window around the same live pane, with one owner throughout.

A user closing the re-born window brings its pane home within a second.

In the full film tour, the scene runs through the restore, the gate and the re-birth, and then has no clean ending:

Ending Outcome Mechanism
Title-bar native-return refused The re-born window has no native drag source. The resolver reads tear-out ownership records, and the first return cleared them.
stack-return (tab-strip drag) the stack comes home, the emptied window stays open VesselWorkspace#retireReturnedVessel closes only a window that has a tear-out vessel record.
Scripted close (Neo.Main.windowNativeClose) refused getNativeWindowOpenConfig passes no nativeCapabilities, so the route grants no close. Tear-out vessels open with {close, position, resize}.

These are lifecycle gaps of a window re-opened for a restored vessel workspace, in code every topology window shares. They need their own leaf before this beat can ship, and that leaf is #19583, which blocks this ticket. Two related sightings:

  • The tear-out's two concurrent vessel mounts leave a rejected mountPromise in the popup state. This predates neomjs/neo#19564.
  • A closeRequested set after the window already detached is inherited by the next window of the same workspace.

Reachable from the toolbar (22:47Z). Undo after a user closes a popup re-opens it through the same NativeVesselTransaction.embodyWorkspace (replayWorkspaceReturn). Measured headless at a852b8ca6e: the re-opened window loads ?workspace=workstation-vessel:feed, not the tear-out's ?popout=feed. So a user reaches the kind of window those endings were measured on without any perspective. The endings themselves still need a headed run on this path; headless, they fail for a torn-out control as well. Two observations on the Undo path, causes not yet known:

  • Both first-batch runs logged Neo.tab.plugin.Overflow: project() threw against a live owner; neither tear-out control did.
  • In 2 of 4 re-opens, the window showed its tab strip without the pane for 30 s.

The film proceeds without this scene; the additions are optional (the operator, 2026-10-10). The work is on the branch ada/19569-film-perspective-rebirth, not pushed.

Context

On 2026-10-10 at 20:0xZ the operator set the film's length to 1:30–2:30, with the 83 s A6 narration kept whole (#15252). The plan of record grows the picture with witnessed addition leaves. This is the one named "the perspective across windows (restore re-births a window — witness first)".

The take candidate at 1c43d51e26 restores a perspective inside the main window only: capture → split → read → restore. A restore that brings a closed window back, with its pane, shows on screen the claim the narration makes about one runtime behind many windows.

The Problem

It is unproven that a perspective restore brings a closed window back in the Workstation. The film must not stage a beat its engine cannot do, so this leaf begins with a witness.

The Architectural Reality

  • DockService#restorePerspective (src/ai/client/DockService.mjs) resolves a name across three sources: declared names, the window-scoped layout.v1 collection, and the Group-scoped topology.v1 collection.
    • A topology record reconciles against the workspaces that are live (DockTopologyReconciler.reconcile). A recorded workspace with no live match comes back as unrestored. The restore itself opens no window.
  • WorkstationTopologyGroupsNL › "cold active selection retains the compact toolbar and programmatic window recovery…":
    • a semantic hydrate keeps a disconnected workspace's document but opens no popup;
    • controller.openTopologyWorkspace(key) then opens that workspace's window.
  • WorkstationPerspectivesNL › "a shipped restore refuses to duplicate a pane its vessel owns…": a restore across a live popup never duplicates a pane.
  • The film: apps/workstation/tour/fiveBeatFilm.mjs runs the perspective-capture / perspective-restore cues. TourController#capturePerspectiveCue and #restorePerspectiveCue drive them through dockService.capturePerspective and restorePerspective on the main workspace. Which collection that capture writes is part of what the witness pins.

The Fix

  1. Witness first, as a Workstation NL e2e spec:

    • tear a pane out into its own window;
    • capture a perspective that includes that window;
    • close the window, returning its pane;
    • restore the perspective.
    • Expect the window back with the same pane, and no pane with two owners.

    Compose the existing seams: topology capture, restore, then openTopologyWorkspace if the restore alone opens nothing. Pin which collection holds the record.

  2. If the witness holds, add the beat to fiveBeatFilm.mjs after the in-main perspective scene, at the hand tempo of #19568. Extend WorkstationFilmTourNL to cover it.

  3. If it fails, record here the mechanism that refuses, and split that mechanism into its own leaf. No beat ships before its witness.

Acceptance Criteria

  • The witness spec passes at the PR head: the restored window carries the captured pane, and every pane keeps exactly one owner.
  • The film tour carries the beat, and WorkstationFilmTourNL stays green with the beat's time in its log.
  • Post-merge: the next headed take (#15252) shows the beat, and its clock is recorded there.

Out of Scope

  • The other four additions on #15252: a third window, the light-theme switch, the maximize beat, and the rail reveal held for its content.
  • Pacing (#19568).
  • Any change to restore semantics beyond what the witness shows is needed.

Related

#15252 (parent, the film), #19568 (pacing), #14800 (the v13.2 cut), #19564.

Sweeps:

  • Live latest-open sweep: the latest 20 open issues at 20:15:06Z; no equivalent.
  • A2A in-flight sweep: Ada's lane-intent at 20:13:50Z; no other claim.
  • Memory sweep: no prior ruling on a cross-window perspective re-birth.
  • Own-assignment sweep: #18985, #16217, #14790 and #13015, all unrelated.

Origin Session ID: 8321fa60-be87-4732-a514-8bfe197fb299 Retrieval Hint: "film perspective restore re-births closed window topology openTopologyWorkspace witness first"