LearnNewsExamplesServices
Frontmatter
id19583
titleA popup re-opened by Undo or a perspective has no road home but close
stateClosed
labels
bugaidashboard
assigneesneo-opus-ada
createdAtOct 11, 2026, 2:13 AM
updatedAtOct 11, 2026, 8:44 AM
githubUrlhttps://github.com/neomjs/neo/issues/19583
authorneo-opus-ada
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 19594 A stack returned home into a tabs node inside a split never settles
blocking[ ] 19569 Film tour: restoring a perspective re-births a closed window
closedAtOct 11, 2026, 8:41 AM

A popup re-opened by Undo or a perspective has no road home but close

neo-opus-ada
neo-opus-ada commented on Oct 11, 2026, 2:13 AM

Context

#19569 measured a window that was re-opened for a restored vessel workspace (openTopologyWorkspace → NativeVesselTransaction.embodyWorkspace), in the full film tour, headed. The re-opened window has no clean way home:

  • the title-bar return is refused;
  • a stack drag brings the stack home but leaves the emptied window open;
  • a scripted close is refused.

A user's own close does return its panes. The measurements are in #19569's status table.

Users reach the same window kind without a perspective. After a popup is closed, the toolbar's Undo re-opens it through replayWorkspaceReturn → embodyWorkspace. Measured headless at a852b8ca6e, the re-opened window loads ?workspace=workstation-vessel:feed, the re-birth config, not the tear-out's ?popout=feed. Two observations on that path have no explanation yet; they are recorded on #19569:

  • 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 Problem

The documented ways home do not hold for a re-opened popup. A user who closes a popup, presses Undo and then drags the stack home is left with an empty window.

The Architectural Reality

  • Design authority:
    • learn/guides/uibuildingblocks/DockLayoutsWindows.md, lifecycle: "four roads home — Undo, Reset, stack return, physical close";
    • apps/workstation/README.md: "Undo reopens the popup with its panes; Redo returns them and closes it again."
  • VesselWorkspace#retireReturnedVessel closes an emptied vessel only through its tear-out record (resolveTearOutVessel(itemId) → nativeWindows.retire).
  • The return that a popup's close performs clears that record; #19569 measured this. The engine's Workspace#onDockWorkspaceReturn releases the tear-out records of the returned items. Nothing re-records them when embodyWorkspace re-opens the window.
  • The re-opened window's identity and capabilities differ from a tear-out's:
    • VesselWorkspace#getNativeWindowOpenConfig opens it as workstation-restored-<uuid> with no nativeCapabilities;
    • a tear-out opens as tearout-<itemId> with {close, position, resize};
    • NativeVesselTransaction.closeVessel checks both the window name and the route's close capability.
  • One close path already reaches a re-opened window. Redo in replayWorkspaceReturn runs clearTopologyIdentity + closeTopologyWindow, fenced against the binding, its generation and the snapshot.

The Fix

Two candidates; a witness decides between them:

  1. Close on empty without a tear-out record. When retireReturnedVessel finds no vessel record for an emptied workspace that still has a bound window, close it through the same fenced topology close that Redo uses. The close path is shared, not copied.
  2. First-class re-embodiment. Open re-born vessel windows under the tear-out name, with the tear-out capabilities, and record native ownership on bind. Then the title-bar return and the scripted close work as well.

The close-on-empty witness can run headless as a direct transfer commit. A title-bar-return witness needs real window geometry, so it runs only where it can stay off the visible screen.

Chosen: candidate 1. The re-born window's identity already lives in its Group binding, and Redo's fenced close already closes exactly this window kind. So the fence moves into NativeVesselTransaction.closeWorkspaceWindow, which both Redo and retireReturnedVessel use. Candidate 2 would teach the item-keyed tear-out registry a second birth, and tearout-<item> is ambiguous for a composed vessel. The title-bar return stays refused (AC-3), with the reason written into the guide. The fork went to Euclid, the owner of #19296's replay code, on 2026-10-11 at 05:11Z.

Witness status

On #19594's fix (840b0f3e10, 2026-10-11 05:1xZ, headless). A direct whole-stack commitCrossWindowTransfer of feed into scale-tabs:

  • A torn-out popup (the control) passes documents-adopted → projections-settled → close-dispatched → close-acknowledged, and the window closes.
  • A popup re-opened by Undo passes documents-adopted → projections-settled, then closeRequested: false, and the emptied window stays open. AC-1 fails on this leaf's own defect: retireReturnedVessel finds no tear-out record.

Earlier readings at c1d500a90c (01:1xZ):

  • Both cases stalled at documents-adopted. That was #19594: scale-tabs sits in a split.
  • The Undo-reopened pane rendered in 3 of 3 runs; the earlier misses (2 of 4) overlapped a concurrent FM smoke.
  • CanvasGroups: window <id> belongs to no canvas group appeared ×5 in 1 of 2 Undo runs.

Corroboration (Mnemosyne's defect-notes, 2026-10-11 02:04Z, headless film witness on the #19584 branch at 00b6f2aa6a). The stall above is probably not a headless limit. Every vessel in my runs held one item torn out alone, and her witness shows that case failing while the journey's composed, multi-item vessel returns fine:

  • A stack return from a single-item vessel never settles: the grip drag arms, then the gesture stays active with main's refresh pending for 200 s.
  • Undoing that tear-out brings the pane home but leaves the emptied window open. That is a third road into this ticket's state, besides the popup close and the perspective restore.
  • A native return of the same vessel adopts nothing. Its detached record is a tab drag (dockGroupNodeId: null), while the vessel's stack root is workstation-vessel-tabs:<item>.

What happened to the three notes:

  • The first is explained by the side-by-side run: what stalls the return is the home node's place in a split, not the item count (#19594).
  • The second is the design: station five of DockLayoutsWindows.md says of Undo, "Redo puts it back in the same window", so the emptied window stays open on purpose.
  • The third (native return) has not been measured since #19594 and is out of scope here.

Acceptance Criteria

  • A popup re-opened by Undo and emptied by a stack transfer to main closes. Its panes stay in main, one owner each.
  • The same holds for a window re-opened by openTopologyWorkspace.
  • A re-opened popup's title-bar return either works, or is refused with the reason stated in the guide.
  • WorkstationPerspectivesNL stays green, and WorkstationNL › "pop-out uses the Group cursor to return and replay the same live pane" gains no new failure. That case is red at baseline from #19559 (bisected: green at 5ccdb192d6^, red from 5ccdb192d6 on), with the header pop-out at the 480×360 fallback. #19597 owns that red, and the case goes green with #19597's fix. Hosted CI selects neither spec, so the receipts are local runs at the PR head.

The evidence is in #19596, merged as 504b41cb01 and approved by Euclid in round 2:

  • AC-1: WorkstationReopenedPopupReturnNL 10/10, red without the change.
  • AC-2: WorkstationTopologyGroupsNL › "a sibling window re-opened for its workspace closes once its stack returns home", red without the change.
  • AC-3: DockLayoutsWindows.md station five.
  • AC-4: at head 79bba03c49, Perspectives passes 2/2, and the Group-cursor case fails identically at base.

The shared close fences the binding, the carrier lineage (Main.clearTopologyIdentity checks generationToken) and the emptied source across its clear.

The earlier AC-4 asked that both Undo-path observations (the Overflow error, the missing pane) be explained or reproduced as specs. It moved out of scope on 2026-10-11 at 05:23Z: these are rendering observations, independent of the close path, and their numbers are captured in defect-note a93f3f742aff4b33.

Out of Scope

  • The Undo-path rendering observations (defect-note a93f3f742aff4b33).
  • The film beat (#19569), which consumes this leaf.
  • Restore semantics.

Related

#19569 (the beat and its measurements) · #19564 (TearOut) · #15252 (the film).

Sweeps:

  • Live latest-open sweep: the latest 20 open issues at 00:12:50Z, plus open-issue searches for "re-opened popup", "reopened popup", "undo reopen", "empty window stays open", "popup way home" and "embodyWorkspace", and open PRs naming embodyWorkspace / retireReturnedVessel. The only hit is #19569, this leaf's source. #19537 (refused popup opens) is adjacent and is a different failure.
  • A2A in-flight sweep: the last 15 messages (to 00:12Z); no claim on re-opened windows.
  • MC sweep: "popup re-opened by undo empty window stays open no way home tear-out record", 5 results; no prior decision.
  • Own-assignment sweep: 5 open. Only #19569 overlaps, and its Fix step 3 calls for this split.

Origin Session ID: a6673f82-f995-4e40-83a9-3093d4ecd357 Retrieval Hint: "re-opened popup undo embodyWorkspace no way home emptied window stays open retireReturnedVessel"

tobiu referenced in commit 504b41c - "fix(workstation): a re-opened popup closes once its stack returns home (#19583) (#19596) on Oct 11, 2026, 8:41 AM
tobiu closed this issue on Oct 11, 2026, 8:41 AM