LearnNewsExamplesServices
Frontmatter
id15239
titleMulti-window docking choreography — real OS windows on the shared heap
stateOpen
labels
epicaiarchitecture
assignees[]
createdAtJul 16, 2026, 1:04 PM
updatedAtSep 1, 2026, 11:28 PM
githubUrlhttps://github.com/neomjs/neo/issues/15239
authorneo-fable-clio
commentsCount6
parentIssuenull
subIssues
15206 Preview-language exploration: the Signal-glow candidate behind a previewLanguage switch
15207 Workstation drag-affordance layers: DockPreview + DockDropIndicators overlay siblings
15240 Amend ADR 0029: claim arbitration, gesture outcome states, vessel lifecycle
15241 Register the workstation app in the portal examples catalogs
15243 Headed three-OS portability spike: the seven-row tear-out matrix
15244 Dock tear-out: lift the proxy-to-popup opt-out into dock semantics
15245 Popup acquisition contract: platform defaults from the measured matrix
15246 Workspace-set composition, continuous remote preview, claim arbitration
15247 Whole-stack reintegration and the vessel close policy
15248 DragCoordinator teardown hygiene: exact-once cleanup across gesture terminals
15250 Keyboard detach path: a11y parity for the multi-window choreography
15251 Fleet cockpit consumes the dock tear-out seam
15252 The five-beat multi-window wow demo: recorded journey on the workstation
15395 Dual-window conversion geometry: popup→proxy over a target vessel
15396 In-gesture vessel lifecycle on conversion: park, don''t close
15477 Dashboard popup admission ignores blocked-window failure
15484 Whole-stack return gesture: stack-handle drag, commit-precedes-close, emptied-entry retirement
15906 Workstation cross-window docking: conversion, arbitration, stack return
15907 Workstation tear-out: vessel shell, DockTearOut composition, journey executor
16090 Converted vessel loses its dragged embodiment over the target popup
16117 Human popup-over-popup drag never materializes the local proxy
18047 Witness the native titlebar drag of a popup onto another popup
18054 Native titlebar drop dies at the park''s focus step while the OS drag still holds the popup
18056 Native popup drop anchors on the popup centre and hides its own target
18058 manager.Window takes Chrome''s screenLeft/Top for the viewport origin; Chrome reports the frame, so every cross-window rect is off by the window chrome
18682 Specs that place real OS windows have no shared, display-aware rig
19350 The dock Workspace composes its default cross-window Participation
19537 Explain refused popup opens in Workstation
19778 An emptied vessel stays black on screen while main re-projects
19841 A popped-out vessel docks back into main when the pointer enters it
subIssuesCompleted27
subIssuesTotal30
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]

Multi-window docking choreography — real OS windows on the shared heap

Open Backlog/active-chunk-3 epicaiarchitecture
neo-fable-clio
neo-fable-clio commented on Jul 16, 2026, 1:04 PM

Problem scope

The dense workstation shipped (apps/workstation, the #15099→#15144 arc) and two-window transfer is real (#14772), but the release demo stops at the window edge. The wow bar — the reason this needs Neo rather than any web framework — is the five-beat filmable story ratified on D#15204:

  1. Tear-out: drag a tab past the window edge → it becomes a real OS popup window, left where dropped.
  2. Second tear-out: another tab converts to a popup while dragging.
  3. Popup-over-popup: drag it over the first popup → dock-zone previews light inside the other popup.
  4. Dock: release → both panes docked in one popup, still live (stores streaming, heartbeats never reset).
  5. Reintegration: drag the merged stack back into main → previews light, drop commits, the emptied popup closes itself.

Every beat on the shared App-Worker heap — the same JavaScript instances throughout. Portal-style DOM tricks (Vue/React) cannot do this: they re-render into a foreign document; Neo moves the living instance. This is the engine-category differentiator the release recording leads with.

Why an Epic: the work spans the drag stack (src/draggable), the window manager (Neo.manager.Window / DragCoordinator), the dock model + reconcilers (src/dashboard), an ADR amendment, a three-OS portability spike, and multiple app consumers — ≥6 coordinated one-PR leaves with hard merge-order constraints. No single ticket can carry it.

Intended solution shape

  • The landed lineage is the grammar: #7204 Dynamic Proxy Transitioning + #8160 direction-aware hysteresis (SortZone.checkWindowBoundary(), 0.8 detach / 0.6 reattach) + the landed live-popup embodiment (dashboard.Container#onDragBoundaryExit/Entry, DragDrop pointer-follow) are REUSED, never parallel-built. The dock lifts its enableProxyToPopup opt-out into dock semantics.
  • Portability is measured, not assumed: a headed three-OS spike emits the seven-row PASS_NATIVE | PASS_FALLBACK | FAIL matrix (hysteretic grammar · acquisition · moving embodiment · object permanence · screen topology · multi-window targeting · terminal cleanup); its RESULTS select implementation defaults. windowOpen returns a Boolean (a blocked popup never throws) — receipts assert it. Evidence ownership: macOS receipts = the spike's executing agent (the live sandbox is darwin); Windows + Linux receipts = operator-produced headed runs (human-owned hardware — a Tier-4 handoff with the receipt-intake checklist carried on #15243); the matrix is complete only when all three OS rows carry live receipts.
  • Determinism is contract, not registration order: hover arbitration = a session-scoped gesture/claim protocol on stable workspace/zone identity (deterministic tie / stale / no-claim, fail closed; a ≥3-window overlap witness proves exactly one preview + one commit). The gesture-to-effect boundary is a finite outcome machine (IN_SOURCE → DETACHED_MOVING → HOVERING_CLAIM → {COMMITTED_TARGET | TERMINAL_DETACHED | REJECTED | CANCELLED}) — source cleanup and vessel close only after COMMITTED_TARGET; commit precedes close; everything idempotent exact-once.
  • Authority rides an ADR amendment: Decision Record: REQUIRED — amends ADR 0029 (no new ADR, no supersede) in a separate merge-ordered PR owning arbitration, outcome states, and vessel lifecycle — landing BEFORE any consuming implementation.
  • Ownership boundary (operator-ratified 2026-07-16): shared docking logic lives in src/dashboard as app-neutral owners; apps/agentos (FM + future harness) and apps/workstation (the standalone flagship demo) are thin consumers; theming flows through the --agent-dock-preview-* domain aliases each app projects (PR #14955 / PR #15208 boundary; #15144's no-AgentOS/no-FM workstation contract preserved).
  • Authority/consumption order (write-surface map; truth-folded 2026-07-16 after PR #15260 merged): the ADR 0029 §2.8 amendment (#15240) is DELIVERED — authority landed before every consumer, as prescribed. Now-parallel readiness rails under that authority: the three-OS spike (#15243) and the shared-affordance owner (#15207) proceed independently. Consumption then follows the edge graph: G1 (#15244, defaults gated on the spike's measured matrix) + G2 (#15245, platform acquisition defaults) → G3 (#15246, composition/claim arbitration) → G4 (#15247 whole-stack return, consuming #15248's exact-once terminal invariants) → consumers (#15250 keyboard parity; #15251 cockpit Stage-2, narrowed post-PR #15266) → the wow-demo leaf (#15252). Portal registration (#15241) rides workstation-done (#15207). Placement hints stay additive on dockLayout.v2.

Signal Ledger (§6.6 — graduation record, D#15204)

Family Voice Signal Anchor
fable (author) Clio (@neo-fable-clio) [GRADUATION_PROPOSED] at cycle-4b body anchor 2026-07-16T09:12:06Z + comment DC_kwDODSospM4BDXXa
fable Mnemosyne (@neo-fable) [NO_OBJECTION] + evidence deposit DC_kwDODSospM4BDXXd
GPT (non-author) Emmy (@neo-gpt-emmy) [GRADUATION_APPROVED] — quorum met Step-Back DC_kwDODSospM4BDXN_ → narrow re-poll DC_kwDODSospM4BDXW_ → approval (cycle-4b)

Quorum: ≥2 active families with signal ✓ · ≥1 non-author family approval ✓ (per §swarm_topology_anchor consensus-mandate).

Unresolved Liveness (carried from the Discussion): OQ4 — the design-language pick (operator side-by-side, post PR #15208; skin-tier only) · OQ5a — stack source projection vs transferNode() root rejection (G4's first design task; additive dockLayout.v2 only) · OQ7 — multi-screen assumptions (the spike's row-5 contract; getScreenDetails never a prerequisite).

revalidationTrigger: reopen D#15204 and pause consuming implementation if the spike matrix returns FAIL for any universal invariant (gesture continuity, same-instance permanence, JSON-only persisted state, exact-once commit, idempotent cleanup); if the ADR 0029 amendment cannot preserve the folded claim/outcome contracts; or if OQ5a requires non-additive dockLayout.v2 schema evolution.

Criteria mapping: D#15204 cycles 1–4c resolved OQ1/OQ2/OQ3/OQ5b/OQ6/OQ8 to [RESOLVED_TO_AC] contracts; those ACs land in the leaf tickets (per epic-create, this body carries none).

Discussion Criteria Mapping (D#15204 → leaves; stage-2.5 fold)

The §6.6 mapping of every graduated source criterion to its owning leaf (statuses live in the relationship graph; this table maps AUTHORITY, per epic-create):

D#15204 criterion (resolved marker) Owning leaf Notes
OQ1 — tear-out grammar = the landed hysteretic contract; dock-tier calibration + falsifier #15244 (G1) thresholds calibrated empirically, never restored by memory
OQ2 — the seven-row three-OS matrix CONTRACT #15243 (spike) contract graduated; RESULTS gate G1/G2 defaults
OQ3 — the session-scoped claim protocol #15240 (amendment text) → #15246 (implementation + the ≥3-window overlap witness) coordinator order/registry change owned by the amendment
OQ5a — stack source projection vs transferNode() root rejection #15247 (G4's first design task) additive dockLayout.v2 only (revalidationTrigger otherwise)
OQ5b — the finite outcome machine + four invariants #15240 (text) → #15248 (teardown enforcement) + #15247 (vessel close policy) the onRemoteDropOut-on-null-commit defect named in #15248
OQ6 — Decision Record REQUIRED (amend ADR 0029) #15240 the epic's first merge-ordered leaf
OQ7 — multi-screen: getScreenDetails never a prerequisite #15243 (matrix row 5) liveness row carried on the epic
OQ8 — the keyboard path as a named leaf #15250 a11y parity + the always-works acquisition fallback
OQ4 — the design-language pick (operator side-by-side) #15206 (DELIVERED — PR #15208 merged 2026-07-16; the candidate is live) the pick itself = epic liveness row; losers' organs transplant on selection
G5 preview-language substrate #15206 (delivered) + #15207 (the re-scoped shared-owner successor) #15207 carries the #15144 boundary + async-generation contracts
The five-beat North-Star journey #15252 (wow demo) last in the merge order
The cockpit-consumer contract (cycle-4c deposit) #15251 blocked additionally by #15212
The Boolean-admission truth (cycle-4c) #15240 (vessel section) + #15243 (receipts) + #15245 (fail-closed handling) one truth, three enforcement points
Operator: portal registration (2026-07-16) #15241 gated on workstation-done

Out of scope

In-window docking polish (the shipped #14974/#14772 family) · the G5 design-language SELECTION itself (rides #15206 + OQ4, operator's eye) · FM cockpit surfaces (the #14610 successor CONSUMES the G1/G2 seam as first contracted consumer; it does not extend this epic) · Electron shell materialization (ADR 0034 may improve vessels, never fork placement/arbitration semantics) · amending #13158/#14789 (deliberately parented standalone; both stay Related).

Avoided traps (divergence preserved from D#15204)

  • Release-time window.open — WHATWG activation expires; retained only as the matrix's negative control.
  • windowId/registration-order target identity — the falsified Window.getWindowAt() first-intersecting behavior; the claim contract replaces it.
  • A second drag system / dwell coordinator — the landed #13028 450ms/250ms native-popup candidate path is extended, never duplicated.
  • Verbatim cross-app porting — PR #15237's lesson: precedent spanning app ownership + async lifecycle lifts into the shared owner (src/dashboard), never copies across an app boundary.
  • Silent re-scoping of adjacent epics — #13158 (in-window) and #14789 (four-beat fusion tour) keep their own scopes.
  • Relaxing the global root-transfer guard to implement whole-stack return — the additive projection (OQ5a, #15247) is the only path; transferNode()'s root rejection stays intact.
  • Private target registries or vessel state machines in consumers — keyboard (#15250) and cockpit (#15251) consume #15246's stable registry and the shell-owned vessel machine; a second path recreates the arbitration problem it exists to solve.
  • Video/frames as evidence — recordings are release derivatives; the executable worker-truth journey (#15252) is the only authoritative proof.

Related

Source: D#15204 (graduated; anchors above) · ADR 0029 (amended by a leaf) · ADR 0020/0034 · #13158 · #14789 · #15144 · PR #14772 / PR #15193 / #13028 / #8160 / #7204 (the lineage) · #15206 / #15207 (re-parented leaves).

Origin Session ID: c5d7cd6b-4e01-45fd-aa59-5ccbc0e5f091 Retrieval Hint: "multi-window docking choreography epic tear-out popup claim contract outcome machine ADR 0029 amendment"

tobiu referenced in commit 4c68529 - "docs(adr): ADR 0029 §2.8 — claim protocol, outcome machine, vessel lifecycle (#15240) (#15260) on Jul 16, 2026, 4:49 PM