LearnNewsExamplesServices
Frontmatter
id12957
titlePromise callbacks queued onto an in-flight vdom update register success-only
stateClosed
labels
bugaicore
assigneesneo-opus-ada
createdAtJun 12, 2026, 6:15 AM
updatedAtJun 13, 2026, 3:42 PM
githubUrlhttps://github.com/neomjs/neo/issues/12957
authorneo-fable
commentsCount3
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 13, 2026, 3:42 PM

Promise callbacks queued onto an in-flight vdom update register success-only

neo-fable
neo-fable commented on Jun 12, 2026, 6:15 AM

Context

Follow-up leaf from PR #12956 (the #12946 wedge fix), spawned by @neo-gpt's preserved non-blocking review challenge and surfaced empirically while building test/playwright/unit/vdom/UpdateWedge.spec.mjs: the first draft of the rejection test queued a promiseUpdate() onto an already-failing flight and hung — which is how the catch-drain fix (VdomLifecycle#executeVdomUpdate) was discovered and shipped. The drain heals the CONTENT; the promise-shape gap remains.

Release classification: post-release (Approve+Follow-Up follow-up from PR #12956 — non-blocking; the pre-#12956 behavior was strictly worse: permanent hang).

The Problem

VdomLifecycle#updateVdom registers an incoming promise's RESOLVE side only (VDomUpdate.addPromiseCallback(me.id, resolve) at src/mixin/VdomLifecycle.mjs:933) before yielding to the merge/queue paths. The REJECT side travels solely as the reject parameter consumed by executeVdomUpdate's catch — which only the CYCLE INITIATOR's call chain holds. Consequences:

  • A promiseUpdate() that lands while the component is already in-flight (:942 branch) or merges into a parent (:937) parks its resolve in VDomUpdate's callback map with NO matching reject anywhere.
  • If the flight it queued onto FAILS, the queued promise cannot reject. Post-#12956, the catch-drain re-runs the queued update, so against a transient failure the promise resolves late (acceptable); against a deterministically-failing boundary it resolves only when some later cycle succeeds — or never settles if no cycle ever does (the watchdog now at least names the component).
  • Symmetrically, resolveVdomUpdateVDomUpdate.executeCallbacks fires ALL parked resolves for the id — including resolves parked by cycles that semantically failed.

The Architectural Reality

  • src/mixin/VdomLifecycle.mjs:933 (success-only registration), :937-943 (the two queue paths that drop reject), :360-380 (the catch that can only reject the initiator's promise).
  • src/manager/VDomUpdate.mjs promise-callback map: stores bare resolve functions; executeCallbacks has no error-path twin.
  • The shape wants either paired {resolve, reject} registration + a rejectCallbacks(ownerId, err) twin invoked from the catch, or an explicit decision that queued promises are at-least-once resolve-only semantics (then promiseUpdate's JSDoc must say so).

The Fix

  1. Extend addPromiseCallback to register {resolve, reject} pairs (both queue paths in updateVdom pass their reject through).
  2. Add VDomUpdate.rejectCallbacks(ownerId, err); invoke from executeVdomUpdate's catch alongside the existing cleanup, BEFORE the needs-drain retry (so retried content still heals, but promises attached to the failed flight reject honestly).
  3. Unit coverage in UpdateWedge.spec.mjs: a promiseUpdate() queued onto a failing flight rejects (currently resolves-late/hangs by shape).

Acceptance Criteria

  • Queued-promise reject path exists and is exercised by a unit test (extend test/playwright/unit/vdom/UpdateWedge.spec.mjs).
  • executeCallbacks / rejectCallbacks symmetry documented in VDomUpdate JSDoc.
  • No regression in the vdom unit collection (86 tests at filing).

Out of Scope

  • The apply-boundary containment + watchdog (shipped in PR #12956).
  • The silent syncVnodeTree _vnode write-back into collision-filtered children (#12939's lane).

Related

PR #12956 (parent fix; this is its documented Delta), #12946 (closed root ticket), #12939 (sibling lane).

Live latest-open sweep: checked latest 20 open issues at 2026-06-12T04:14Z; no equivalent found. A2A in-flight sweep: clean.

Origin Session ID: e605ce21-3668-445c-bc00-45896aa9a092

Retrieval Hint: "queued promise callbacks success-only reject unreachable vdom update wedge"