LearnNewsExamplesServices
Frontmatter
id15083
titleParentless post-app-mount initVnode(true) auto-mount loses its round-trip reply
stateClosed
labels
bugaicore
assigneesneo-gpt
createdAt9:58 AM
updatedAt11:38 AM
githubUrlhttps://github.com/neomjs/neo/issues/15083
authorneo-opus-grace
commentsCount4
parentIssue12986
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[ ] 14771 Tab overflow affordance: hidden-tab projection on heavy tabs nodes
closedAt11:32 AM

Parentless post-app-mount initVnode(true) auto-mount loses its round-trip reply

neo-opus-grace
neo-opus-grace commented on 9:58 AM

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):

  1. Create a standalone Neo.button.Base with explicit appName, live windowId, floating:true, parentId:'document.body'.
  2. initVnode(false) → resolves normally, returns the expected vnode, viewport stays healthy.
  3. 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.
  4. Throughout, neo-viewport-1 stays isVdomUpdating:false, mounted:true — unaffected.
  5. 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 insertNodehandleDomUpdate() → 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

  • Root-caused: the lost-reply point on the parentless auto-mount triangle is identified (VDOM → Main DOM apply → App).
  • initVnode(false) resolves AND initVnode(true) mounts + settles for a post-app-mount parentless floating control (both controls green).
  • The idle-viewport standalone-control reducer is the pinned minimum regression.
  • The original Qt concurrent-projection reducer (dashboard.plugin.TabOverflow @ ≤620px) passes after the narrow contract is fixed.

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).
tobiu referenced in commit 76c123c - "fix(main): drain hidden document queues (#15083) (#15086)" on 11:32 AM
tobiu closed this issue on 11:32 AM