LearnNewsExamplesServices
Frontmatter
id19296
titleMove popup-close return and replay into the Dock engine
stateClosed
labels
enhancementairefactoringarchitecturedashboard
assigneesneo-gpt
createdAtSep 26, 2026, 10:08 PM
updatedAtSep 29, 2026, 10:28 PM
githubUrlhttps://github.com/neomjs/neo/issues/19296
authorneo-gpt-emmy
commentsCount2
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 29, 2026, 10:28 PM

Move popup-close return and replay into the Dock engine

Closed v13.2.0/archive-v13-2-0-chunk-23 enhancementairefactoringarchitecturedashboard
neo-gpt-emmy
neo-gpt-emmy commented on Sep 26, 2026, 10:08 PM

Context

Merged PR #19292 (91fd23336d20cbe17f95d8e357c9f9a15be34181) makes Workstation return a physically closed popup's panes to the main layout in one undoable Group transaction. The operator selected that behavior and then requested this follow-up: why does reusable multi-window transaction behavior live in custom app code, and which part belongs in manager.Transaction versus dashboard/dock/*?

This is a placement and reuse correction to the shipped behavior, not a new close-policy decision.

The Problem

Workstation now assembles an all-pane document transfer, guards it against queued writes and successor bindings, correlates close versus reload, routes reconnect-lease expiry, and implements native undo/redo effects. Another Dock consumer must reproduce that orchestration to offer the same policy.

Moving the whole method into the generic transaction manager would put dock item, home and tab semantics below their existing domain boundary. Moving it verbatim behind an app-shaped engine wrapper would preserve the duplication in another directory.

The Architectural Reality

Read at the merged implementation's source:

Existing owner Responsibility and current seam
src/manager/Transaction.mjs class contract; write, undo, redo Logical Groups, serialized transactions, opaque participants, binding generations and one history cursor. Explicitly knows no dock document or app class.
src/manager/transaction/Commit.mjs#run Captures participant values at queue head, prepares/adopts/compensates atomically, publishes a snapshot, then reports projection/effect failures separately. Replay supplies replayRow.
src/manager/transaction/NativeLifecycle.mjs Group-owned admission, connection and retirement records; source-contributed effects. Its existing onLeaseExpired handles provisional admissions, not the app's formerly-bound recovery path.
src/dashboard/dock/window/WorkspaceSet.mjs#register/transfer Adapts dock reducers to the participant protocol; a transfer already reduces from both captured documents. It owns no parallel membership registry.
src/dashboard/dock/window/NativeVesselTransaction.mjs and Workspace#bindNativeWindowSource Shared native effect routines and Dock composition seams. The closed predecessor #18651 established a callable reuse path rather than requiring all hosts to inherit the same view.
apps/workstation/view/VesselWorkspace.mjs#onNativeWindowRelease/returnClosedPopupWorkspace; PopupWorkspace#projectDockCommit; WorkspaceController#onPopupLeaseExpired Current app-side orchestration to retire or reduce.
src/Main.mjs#windowNativeIsClosed Existing exact-handle physical observation, including unknown outcomes. Reuse it; disconnected is not synonymous with closed.

The Fix

Deliver the extraction and Workstation migration together in one PR.

  1. Keep the transaction authority generic. Reuse Transaction / Commit for the queue, captured state, atomic adoption, cursor and effect receipts. Any necessary generic lifecycle hook belongs with the existing Group owner and carries opaque identities; it must not import Dock or interpret pane-return descriptors.
  2. Put Dock semantics in the Dock adapter. Extend the existing WorkspaceSet preparation path for an atomic all-pane return using model/Operations. Prefer preparation from queue-head captures over transplanting Workstation's precompute/version/retry loop. Compose correlated native replay through the existing Dock window/Workspace seams; evaluate NativeVesselTransaction for the shared effect routine. Do not create another lifecycle registry or hide this solely in an app subclass.
  3. Expose policy, not orchestration. A consumer selects retained headless layout versus return, and supplies the destination workspace and landing policy. Workstation selects return-to-main and preserves its current recorded-home/index and fallback behavior. Unconfigured consumers retain their existing behavior. Keep product names, workspace prefixes, pane factories and user-facing receipts in the app.
  4. Delete the replaced app machinery. Reduce returnClosedPopupWorkspace to policy/receipt glue or remove it; remove the app's replay-specific projectDockCommit override and bound-close lease subscription once shared composition owns them. Report the removed code and remaining app responsibilities.

The exact new callable/config names should be fixed in this ledger at intake after the existing owners are tested. This ticket specifies ownership and behavior; it does not prescribe a new module or a renamed copy of the app class.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
Proposed Dock return preparation on the existing WorkspaceSet participant path ADR 0029 §§2.1, 2.3; current Commit#run / WorkspaceSet#register All source panes return through one Group transaction against current captured documents; one cursor row; identity and ordering preserved Invalid target, reducer refusal, stale binding or failed adoption produces no partial document change; retain recovery authority Callable JSDoc and Dock adoption guide Real Group tests with arbitrary workspace keys; intervening write, successor binding and refusal controls
Proposed opt-in close policy on Dock composition Operator's choice on #19273; ADR 0029 §2.8; current Workspace#bindNativeWindowSource Return or retain, with consumer-supplied destination/landing choice; Workstation uses return-to-main Default preserves other consumers; unknown physical result does not assert closure; lease recovery is separately identified Dock window guide: close/reload/lease behavior and timing Return and retain cases, reload within lease, formerly bound expiry, never-bound admission
Shared close-return replay through Dock window effects Existing Group history/replayRow; ADR 0029 §2.8.3 Undo restores documents and attempts popup embodiment; redo returns documents then closes the exact current render target Popup denial/close refusal remains an observable native outcome; does not roll back semantic truth, duplicate a return or close a successor Effect contract and recovery behavior Undo/redo with native refusal and stale-generation controls; same pane instances
Workstation migration PR #19292 and operator return-to-main decision Keeps app policy and result presentation; consumes the shared mechanism Existing safe behavior remains on rejected return; app does not regain a second lifecycle owner App README plus concise composition example Existing close, reload, multi-pane and Group-cursor browser witnesses at the PR head

Acceptance Criteria

  • The PR explains the manager.Transaction / manager.transaction versus dashboard/dock boundary with the implemented call path. Generic managers contain no Dock imports, app names or tab-placement branches.
  • One shared Dock operation returns all panes atomically and records one Group row; it preserves earlier queued writes, rejects stale/successor bindings and leaves both documents intact on refusal.
  • Close, warm reload, unknown observation, formerly-bound lease expiry and never-bound admission stay distinct. Timing comes from documented policy/current authorities; no second window registry or per-popup worker-wide lifecycle owner is added.
  • Undo/redo preserves pane/component identity and reports failed native effects separately. A failed or duplicate native notification cannot reintegrate twice or close a replacement window.
  • Workstation's orchestration is deleted as described above. The PR gives a before/after deletion account; additional engine lines alone do not satisfy the extraction.
  • An independent minimal Dock host/fixture, without Workstation classes or identifiers, exercises both return and retain policies through the public composition seam. Existing Workstation single-/multi-pane close, popup/main reload and undo/redo browser witnesses pass at the head; render boundaries are observed before identity assertions.
  • Public seam names and failure behavior are finalized in the ledger, documented with a small consumer example, and covered by executable tests. No unchanged consumer silently adopts Workstation's product policy.

Decision Record impact

aligned-with ADR 0029: worker-owned documents, reducer-based changes, and native close as a separate effect. The reusable facility preserves those boundaries. This is not authority to change the persisted topology schema or global close defaults.

Out of Scope

Video/tour work; whole Demo B migration; new persistence formats; another transaction/history subsystem; changing the product's chosen return behavior; the separately owned popup parking/dropzone lane.

Avoided Traps

  • Putting tab/home rules in manager.Transaction because the operation is transactional.
  • A new lifecycle owner alongside NativeLifecycle, or a wrapper that still requires every app to implement the algorithm.
  • Moving the existing retry loop untouched when queue-head participant preparation can remove it.
  • Equating a native close with deletion of a logical workspace, or an unknown observation with confirmed death.
  • Claiming reuse from tests that call the Workstation method through a facade.

Related

#19273 · PR #19292 · #18314 · #18651 · #15247. The latter two concern commanded vessel close/gesture return; this follow-up concerns physical-close-triggered all-pane return and replay. #19101 is a release-note survey, not an implementation duplicate.

unowned-rationale: operator-requested follow-up recorded for the Dock engine queue; no implementation claim is made by filing it. A claimant should confirm the shared seam at intake before editing.

Origin Session ID: 01a0deee-3f9b-7180-ac35-f90129ccaa40

Retrieval Hint: returnClosedPopupWorkspace onPopupLeaseExpired PopupWorkspace projectDockCommit manager.Transaction WorkspaceSet NativeLifecycle; source baseline PR #19292 at ab7671628a5a1446fdeead7676d5a4731c45d301.

Freshness and duplicate checks

Live latest-open sweep: latest 20 open Engine issues in created-descending order at 2026-09-26 immediately before creation at 20:08 UTC (newest #19295); no equivalent extraction. A2A: latest 30 messages across read states immediately before filing; no competing claim. Exact GitHub searches for popup close/return and WorkspaceSet surfaced #18651, #15247 and #19101; their bodies were read and scopes distinguished above. Own-assignment sweep: two open issues (#16151, #15526), neither on this surface.

MC rationale sweep: three-result searches on popup-close return, NativeLifecycle/opaque Group ownership and the current app method names, plus the final problem-noun sweep. Recovered the earlier Group-owned lifecycle and shared-close decisions; no retrieved decision rejects this extraction. KB source/ticket queries were incomplete and used only as discovery hints; current source and ADR 0029 supplied the ownership evidence. Surface names in the ledger were checked directly. No new module placement is prescribed; the structural-pre-flight trigger must run if intake chooses one.

Claimer contract — @neo-gpt, 2026-09-29

Selected discipline: ticket-intake + tech-debt-radar; medium, reversible extraction under ADR 0029. The runtime-owner and finite-JSON probes in the intake comment pin the existing owners. No new persisted format or global close policy is introduced.

Proposed surface Authority / owner Implemented contract to pin Refusal / default Evidence
Transaction release/lease envelopes; NativeLifecycle#isCurrentRelease / #isLeaseExpired / #withReleasedBinding Existing Group binding and reconnect lease Carry the exact released binding/generation through expiry; Transaction#getBinding exposes generationToken, and Dock capture combines lineage plus counter to reject replacement during preparation; Group-owned awaitingClosure and the full pending release-work promise live on that binding, clear on accepted rebind; Dock code queries expiry through the owner and returns a disposition, while only the owner updates closure state; source bindingExpired callback distinguishes formerly bound expiry Old/foreign/successor generations do no work; never-bound admission keeps its existing expiry path Actual manager release/rebind/expiry and pending-work controls; no Dock imports
WorkspaceSet#register optional resolveReturnDescriptor / runtime nativeWindow effect seams, and #returnWorkspace Existing registered Dock participants and context.valuesBefore Reduce all current source items against both queue-head captures; registered target calls the existing landing hook; plain placements and history metadata carry no functions; one Group row No partial adoption on missing/read-only target, reducer/adoption refusal or failed binding guard; duplicate empty return records no extra row Arbitrary workspace keys, earlier queued writes, binding successor and refusal controls
WorkspaceSet#getParticipantForBinding(bindingKey) Existing Group participant membership and registered bindingKey / runtime nativeWindow seams Resolve the unique writable native-window document owner as {workspaceId, participant}, independently of semantic document-key spelling Return null for invalid/missing binding keys, missing/ineligible owners, or multiple eligible owners; no parallel registry Callable JSDoc, independent host and lookup/refusal controls
Workspace.nativeWindowClosePolicy / nativeWindowReturnTargetKey plus #getDockParticipantSeams, #getNativeWindowReturnPlacements, #getNativeWindowRoute, #getNativeWindowOwnerWindowId, #getNativeWindowOpenConfig, #onNativeWindowDetached, #onDockWorkspaceReturn Dock composition over registered participants Opt-in return or retain; an injected WorkspaceSet allows close-policy-only native sources without enabling tear-out gestures; view hooks provide route/presentation and product policy, not transaction algorithms Default null preserves the existing consumer path; unknown observation retains until the separately identified lease boundary Independent return/retain host and the existing Workstation close/reload witnesses
NativeVesselTransaction#releaseWorkspace, #embodyWorkspace, #replayWorkspaceReturn (static callables) Existing Dock native effects and Group history/projection Observe exact native closure; return through WorkspaceSet; reserve/open/revoke retained workspace embodiment; correlated undo/redo wraps ordinary projection and emits native outcomes on the existing effect-receipt channel Open/close refusal remains an effect receipt; semantic truth stays committed; generation checks cannot close a successor Native refusal, duplicate notification and replay controls; same pane instances
Workstation migration Existing return-to-main policy and landing hook Main registration supplies its current landing hook; popup opts in to return-to-main; retain only product URL/name/features, interaction cleanup and receipt glue Remove snapshot/version/retry loop, popup replay override and controller lease subscription Before/after source deletion account and rendered identity battery

The nativeWindow registration value is a runtime effect/accessor descriptor, not a new landing-policy object; resolveDockReturnDescriptor remains the landing policy. No callback enters a semantic descriptor. The history operation label remains returnPopupWorkspace for replay compatibility, while the public all-pane operation is returnWorkspace. Its result exposes ordered itemIds even with history depth zero. Binding keys resolve native slots independently of semantic participant keys. Unknown physical observations wait for the manager's configured reconnect lease; expiry of the same formerly-bound record is a separate recovery cause, including when observation was still pending.

Current source at dev@ede64fd051 still has the same affected owner blobs as the intake baseline; the only intervening merge retires the unrelated conversion exit signal. Created 2026-09-26 / updated 2026-09-26: pre-stale under the 90+14-day workflow, no stale/exemption label. No assignee, parent, blocker or matching open PR exists. ADR successor-risk: adr-aligned. Classification: valid-as-written with this intake-derived ledger. Positive ROI: delete consumer transaction/lifecycle orchestration while giving independent Dock hosts the same operation.

Core idioms loaded: src/Neo.mjs, src/core/Base.mjs, src/state/Provider.mjs, src/data/Model.mjs, src/data/Store.mjs; window ids remain runtime render targets. Prior-art anchor: memory 14b20212-63f9-4070-a0ba-f1665601d03a, session 01a0deee-3f9b-7180-ac35-f90129ccaa40.

Origin Session ID: 01a0ee37-7eaa-7d52-9869-ba5d0de51b43

tobiu referenced in commit 745321d - "feat(dock): share native workspace return and replay (#19296) (#19325) on Sep 29, 2026, 10:28 PM
tobiu closed this issue on Sep 29, 2026, 10:28 PM