Context
Filed per @neo-gpt's #14771 RA-1 AC-holder decision B (block PR #15062 on a fresh framework ticket). Narrowed 2026-07-12 per @neo-gpt's intake V-B-A (IC_kwDODSospM8AAAABJxLPOw): a current-runtime Neural Link control falsified the original "concurrent-ancestor" premise — the wedge reproduces with a fully-mounted idle viewport, so ancestor overlap is NOT the root cause. The owning bug is generalized to the parentless auto-mount round-trip; the Qt dock case is retained as the motivating symptom and a regression witness.
#12946 (the original vdom-wedge) is closed — failure-containment precedent, but it did not pin this successful-create / lost-auto-mount-reply path. #5038 defines parentless createNeoInstance() auto-mount as an intentional contract.
The Problem (minimum reducer — idle viewport)
A post-app-mount, parentless initVnode(true) loses its auto-mount round-trip, while the same instance's initVnode(false) succeeds. No ancestor projection/update is required.
Minimum reducer (real browser, App-Worker, neo-viewport-1 fully mounted + idle — @neo-gpt, session d5afc497):
- Create a standalone
Neo.button.Base with explicit appName, live windowId, floating:true, parentId:'document.body'.
initVnode(false) → resolves normally, returns the expected vnode, viewport stays healthy.
initVnode(true) (equivalent standalone button) → times out; the watchdog names the button itself after 5s. Live state: isVdomUpdating:true, mounted:false, vnodeInitialized:false, vnode:null.
- Throughout,
neo-viewport-1 stays isVdomUpdating:false, mounted:true — unaffected.
- Same wedge via the documented
Neo.worker.App#createNeoInstance() parentless path with an explicit live windowId.
Regression witness (the motivating symptom, not the root cause): examples/dashboard/dock @ ≤620px → 7 main-tabs overflow → dashboard.plugin.TabOverflow eagerly mounts a floating overflow control to document.body; the App-Worker logs vdom update wedged: "neo-viewport-1" … over 5000ms (VDomUpdate.mjs:453). The original ticket mis-attributed this to ancestor concurrency; the idle reducer above is the true minimum.
The Architectural Reality
Neo.manager.VDomUpdate models component-tree relationships via parent IDs. A parentId:'document.body' component has no ancestor-component flight for the manager to merge or serialize against — so the "concurrent-ancestor collision" premise is structurally impossible, consistent with the idle-viewport reproduction.
- Parentless auto-mount takes the triangular special path in
src/worker/Manager.mjs#onWorkerMessage(): payload.autoMount → synthesized insertNode → handleDomUpdate() → Main delayed reply. initVnode(false) bypasses that DOM-apply leg and resolves.
- The failure is a lost reply on the VDOM → Main DOM-apply → App triangle for the parentless auto-mount round-trip.
The Fix (prescription — evidence-first trace)
An evidence-first trace through the parentless auto-mount reply triangle: VDOM reply → Main DOM apply (handleDomUpdate synthesized insertNode) → App reply. Identify where the auto-mount round-trip reply is lost for a parentId:'document.body' instance. Not a manager-only concurrency fix (the concurrency premise is falsified) and not a plugin-side timing work-around.
Acceptance Criteria
Out of Scope
- The #14771 overflow-control affordance itself (stays blocked on this; RA-2 retained).
- Any plugin-side microtask / delay work-around.
Decision Record impact
aligned-with Epic #12986 (vdom-delta-stream contract). No ADR challenged or superseded.
Related
- Blocks PR #15062 / #14771 RA-1.
- Precedent (closed):
#12946; contract: #5038 (parentless createNeoInstance auto-mount).
- Parent Epic: #12986.
- Narrowing V-B-A: @neo-gpt
IC_kwDODSospM8AAAABJxLPOw.
Origin Session ID
07be7801-5264-4e6c-b720-89114041a48f
Handoff Retrieval Hints
- Retrieval Hint:
"parentless initVnode(true) auto-mount lost reply document.body floating"
- Minimum reducer: idle-viewport standalone
Neo.button.Base (parentId:'document.body', floating:true) → initVnode(true) wedges, initVnode(false) resolves.
- Anchor:
src/worker/Manager.mjs#onWorkerMessage() (autoMount triangle) + src/manager/VDomUpdate.mjs:453 (watchdog).
Context
Filed per @neo-gpt's #14771 RA-1 AC-holder decision B (block PR #15062 on a fresh framework ticket). Narrowed 2026-07-12 per @neo-gpt's intake V-B-A (
IC_kwDODSospM8AAAABJxLPOw): a current-runtime Neural Link control falsified the original "concurrent-ancestor" premise — the wedge reproduces with a fully-mounted idle viewport, so ancestor overlap is NOT the root cause. The owning bug is generalized to the parentless auto-mount round-trip; the Qt dock case is retained as the motivating symptom and a regression witness.#12946(the original vdom-wedge) is closed — failure-containment precedent, but it did not pin this successful-create / lost-auto-mount-reply path.#5038defines parentlesscreateNeoInstance()auto-mount as an intentional contract.The Problem (minimum reducer — idle viewport)
A post-app-mount, parentless
initVnode(true)loses its auto-mount round-trip, while the same instance'sinitVnode(false)succeeds. No ancestor projection/update is required.Minimum reducer (real browser, App-Worker,
neo-viewport-1fully mounted + idle — @neo-gpt, sessiond5afc497):Neo.button.Basewith explicitappName, livewindowId,floating:true,parentId:'document.body'.initVnode(false)→ resolves normally, returns the expected vnode, viewport stays healthy.initVnode(true)(equivalent standalone button) → times out; the watchdog names the button itself after 5s. Live state:isVdomUpdating:true, mounted:false, vnodeInitialized:false, vnode:null.neo-viewport-1staysisVdomUpdating:false, mounted:true— unaffected.Neo.worker.App#createNeoInstance()parentless path with an explicit livewindowId.Regression witness (the motivating symptom, not the root cause):
examples/dashboard/dock@ ≤620px → 7main-tabsoverflow →dashboard.plugin.TabOverfloweagerly mounts a floating overflow control todocument.body; the App-Worker logsvdom update wedged: "neo-viewport-1" … over 5000ms(VDomUpdate.mjs:453). The original ticket mis-attributed this to ancestor concurrency; the idle reducer above is the true minimum.The Architectural Reality
Neo.manager.VDomUpdatemodels component-tree relationships via parent IDs. AparentId:'document.body'component has no ancestor-component flight for the manager to merge or serialize against — so the "concurrent-ancestor collision" premise is structurally impossible, consistent with the idle-viewport reproduction.src/worker/Manager.mjs#onWorkerMessage():payload.autoMount→ synthesizedinsertNode→handleDomUpdate()→ Main delayed reply.initVnode(false)bypasses that DOM-apply leg and resolves.The Fix (prescription — evidence-first trace)
An evidence-first trace through the parentless auto-mount reply triangle: VDOM reply → Main DOM apply (
handleDomUpdatesynthesizedinsertNode) → App reply. Identify where the auto-mount round-trip reply is lost for aparentId:'document.body'instance. Not a manager-only concurrency fix (the concurrency premise is falsified) and not a plugin-side timing work-around.Acceptance Criteria
initVnode(false)resolves ANDinitVnode(true)mounts + settles for a post-app-mount parentlessfloatingcontrol (both controls green).dashboard.plugin.TabOverflow@ ≤620px) passes after the narrow contract is fixed.Out of Scope
Decision Record impact
aligned-withEpic #12986 (vdom-delta-stream contract). No ADR challenged or superseded.Related
#12946; contract:#5038(parentlesscreateNeoInstanceauto-mount).IC_kwDODSospM8AAAABJxLPOw.Origin Session ID
07be7801-5264-4e6c-b720-89114041a48f
Handoff Retrieval Hints
"parentless initVnode(true) auto-mount lost reply document.body floating"Neo.button.Base(parentId:'document.body',floating:true) →initVnode(true)wedges,initVnode(false)resolves.src/worker/Manager.mjs#onWorkerMessage()(autoMount triangle) +src/manager/VDomUpdate.mjs:453(watchdog).