LearnNewsExamplesServices
Frontmatter
id19540
titleRestore Neo class extensibility in Dock window collaborators
stateClosed
labels
enhancementairefactoringarchitecturedashboard
assigneesneo-opus-ada
createdAtOct 10, 2026, 4:42 AM
updatedAtOct 10, 2026, 9:20 PM
githubUrlhttps://github.com/neomjs/neo/issues/19540
authorneo-gpt-emmy
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 19538 Retire the dormant pointer-path vessel conversion and park machinery
blocking[]
closedAtOct 10, 2026, 9:20 PM

Restore Neo class extensibility in Dock window collaborators

Closed v13.2.0/archive-v13-2-0-chunk-24 enhancementairefactoringarchitecturedashboard
neo-gpt-emmy
neo-gpt-emmy commented on Oct 10, 2026, 4:42 AM

Context

The operator challenged the architectural shape of src/dashboard/dock/window/TearOut.mjs and VesselEmbodiment.mjs after merging the bounded drag repair in #19539. This records the inherited debt separately from that behavior fix.

Source examined: 5698517f193fb1064a5aa618ff6741bee946fae6. The same problem was already repaired for WorkspaceSet in #18450, whose scope explicitly excluded TearOut.

The Problem

  • TearOut.mjs:108 exports a factory accepting 15 callbacks and retaining vessel/admission/retirement state and pane maps in a closure. Internal operations such as invalidateAdmission (:158), retireVessel (:174) and livePane (:226) are lexical functions. The returned methods close over those functions instead of participating in Neo prototype dispatch.
  • VesselEmbodiment.mjs:26-40 creates a private records Map and lexical stage; its second factory, createDockVesselProxyEmbodiment (:229), creates another state machine with private active/generation state and lexical retireProxy / resolveRecord. Creating Neo components inside those functions does not make their owner a Neo class.
  • NativeVesselTransaction.mjs:431-486 is a second form of the same problem inside a registered class: effectsFor(descriptor) returns the reshowVessel closure, the one effect left after #19538 (PR #19542). Its body calls the class statics by name (NativeVesselTransaction.toFrameOrigin, .resolveAdmissions, .describeAuthority), so a subclass cannot replace them. Namespace registration does not put the effect body on the class prototype. The one caller, Workstation VesselWorkspace.mjs:300, names the class at its call site; Demo-B no longer constructs it.
  • Workspace.mjs:690 wires the first factory, :1674 spreads its returned object into projection options, and :1587 tears down its records by hand. Demo-B wires the same factories itself (DemoBWorkspace.mjs:519, :620, :633). TearOut explicitly justifies closure calls because spreading changes this.

An exact-source Node probe confirmed TearOut returns an ordinary Object, each construction owns different handler functions, and the module has neither Neo.setupClass nor Neo.gatekeep. Passing behavior tests does not establish namespace, inheritance, overwrite dispatch or native lifetime integration.

The Architectural Reality

Design authority: src/core/Base.mjs defines itself as “The base class for (almost) all classes inside the Neo namespace”; src/Neo.mjs#create and setupClass provide construction, prototype configuration/overwrites and namespace registration. #18450 establishes the relevant extension/lifetime precedent; src/dashboard/dock/window/WorkspaceSet.mjs is the live neighboring Base-derived class.

This concerns stateful Engine collaborators, not a ban on ordinary pure functions. Pane/component identity, native-resource ownership and Group/document authority remain separate. A class conversion must not take ownership of borrowed panes or Group participants.

Ownership constraints from Ada's peer read: TearOut releases borrowed pane handles on destruction; the host's settlePane remains the pane-disposal decision. Native windows retire through the Group's native lifecycle and existing host close seam, never through an independent destructor close. Documents remain participant-owned. Embodiment owns its created placeholders/proxies; staged panes remain borrowed and follow restore/promote. Generation fencing remains instance-local.

Placement: retain the existing src/dashboard/dock/window/ concern and its registered-class sibling pattern; no new subsystem or manager. The Engine has no ai:structure-map script (the explicit command returned Missing script); this is not a passed structure-map check.

The Fix

Replace the surviving stateful factory implementations in TearOut and VesselEmbodiment, and the surviving factory-generated native effects in NativeVesselTransaction.effectsFor, with real Neo collaborators: behavior on prototype methods, deliberate configs versus per-instance fields, standard construction and creator-owned destruction. Make internal calls dispatch through the instance so subclass and overwrite behavior actually executes.

Update production callers and existing tests together; remove redundant callback forwarding and object-spread plumbing where the native collaboration replaces it. Retain genuine host policy boundaries. Do not preserve the factory as an implementation hidden behind a registered facade.

First reconcile the surviving call paths with Ada-owned #19538: delete retired pointer-conversion machinery instead of converting dead code. That cleanup remains its owner's lane. Preserve the accepted repeated tear-out / target-proxy / re-exit behavior and the separate native-titlebar path.

Contract Ledger

Target surface Source of authority Proposed behavior Fallback Docs Evidence
TearOut factory and handlers current TearOut.mjs; Neo.create/setupClass; #18450 actual registered collaborator with executed prototype methods retain current admission/refusal results class/config/method JSDoc subclass and Neo.overwrites change executed behavior
Surviving embodiment factories current VesselEmbodiment.mjs; same Base contract registered, explicitly owned render-state collaborator(s) preserve restore/promote and stale-generation refusal class/ownership JSDoc real dispatch plus same-pane/placeholder controls
Surviving native effect bundle NativeVesselTransaction.effectsFor; the Workstation caller; #19538 move executed effects to overridable prototype methods of their explicit owner preserve native route/restore/refusal behavior; remove dead effects first replace factory-oriented JSDoc override reaches the surviving native effect; no namespace-only false green
Construction and teardown Workspace constructor/destructor and verified callers creators release their instances; borrowed resources keep their authority late work cannot revive a destroyed owner lifecycle JSDoc destroy during pending work; no lost panes or foreign teardown
Drag/document behavior merged #19539; docking design record unchanged observable semantics existing blocked-open/cancel/re-entry behavior existing relevant guides focused units and exact-head Workstation/Demo-B journeys

Decision Record impact

Aligned with the existing Neo class system and Dock ownership direction; no new Group protocol, persisted schema or user interaction policy. Re-read the current docking design record at implementation.

Acceptance Criteria

  • Surviving stateful implementations use registered Neo classes; no closure factory, descriptor-copy facade or cosmetic rename stands in for conversion.
  • Tests prove a subclass override and a Neo.overwrites override reach real internal behavior, including the surviving native effects, rather than only checking namespace presence.
  • Construction, callback wiring and teardown use the new owners; redundant executable factory consumers disappear.
  • Pending admission/render settlement, cancellation and destruction preserve current generation fencing and do not destroy borrowed panes or Group resources.
  • Focused tests and the affected Workstation/Demo-B journeys preserve repeated crossings, pane identity, terminal commit and native-titlebar behavior. State exact source/CI/native evidence separately.
  • The PR accounts for deleted factory/plumbing code, retained host policy and any justified net growth. No newly preserved dead pointer-conversion code.
  • JSDoc describes the actual class, extension points and ownership; it no longer presents test isolation as a reason to bypass Neo.

Out of Scope

A wholesale Dock rewrite, feature changes, weakening release acceptance, another generic abstraction layer, or taking over #19538. This ticket does not claim all remaining Dock modules have been audited.

Avoided Traps

A class wrapper around copied factory methods still defeats overrides (#18450). A blanket PascalCase-to-class rule can misclassify pure data helpers. Base destruction alone does not settle native-resource ownership. Green tests over injected callbacks do not prove extensibility.

Related and ownership

Related: #18450, #18304, #19538, #19539, neomjs/neo-agent-skills#155. Owner: @neo-opus-ada self-selected and is assigned. Sequence: #19538 deletion first, then #19540 conversion of the surviving behavior; one owner coordinates the shared Workstation/Demo-B host wiring. The deletion landed (PR #19542, 2026-10-10 13:43Z). The caller census, re-run at 7c4b2273b8, finds all TearOut behavior and both embodiment factories surviving, and effectsFor reduced to the native title-bar re-show. Emmy retains review/finding custody.

MC prior art: memory c93f166c-c155-4463-8ed7-0d5e993aa332 records the original injected admission-machine rationale; it explains the history, not architectural approval. Live all-state issue searches found the closed WorkspaceSet precedent and no equivalent open repair. Own-assignment sweep found only unrelated Engine lanes. Latest-open and all-state mailbox sweeps are repeated immediately before creation.

Origin Session ID: b56dbc41-6e95-4210-a2ea-8d1f5f3ffcd0 Retrieval Hint: TearOut VesselEmbodiment closure factory prototype override; source 5698517f193fb1064a5aa618ff6741bee946fae6.

tobiu referenced in commit 1c43d51 - "refactor(dock): the tear-out, vessel embodiments and native re-show become registered Neo classes (#19540) (#19564) on Oct 10, 2026, 9:20 PM
tobiu closed this issue on Oct 10, 2026, 9:20 PM