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,
resolveVdomUpdate → VDomUpdate.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
- Extend
addPromiseCallback to register {resolve, reject} pairs (both queue paths in updateVdom pass their reject through).
- 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).
- Unit coverage in
UpdateWedge.spec.mjs: a promiseUpdate() queued onto a failing flight rejects (currently resolves-late/hangs by shape).
Acceptance Criteria
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"
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 apromiseUpdate()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#updateVdomregisters an incoming promise's RESOLVE side only (VDomUpdate.addPromiseCallback(me.id, resolve)atsrc/mixin/VdomLifecycle.mjs:933) before yielding to the merge/queue paths. The REJECT side travels solely as therejectparameter consumed byexecuteVdomUpdate's catch — which only the CYCLE INITIATOR's call chain holds. Consequences:promiseUpdate()that lands while the component is already in-flight (:942branch) or merges into a parent (:937) parks its resolve inVDomUpdate's callback map with NO matching reject anywhere.resolveVdomUpdate→VDomUpdate.executeCallbacksfires 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 dropreject),:360-380(the catch that can only reject the initiator's promise).src/manager/VDomUpdate.mjspromise-callback map: stores bare resolve functions;executeCallbackshas no error-path twin.{resolve, reject}registration + arejectCallbacks(ownerId, err)twin invoked from the catch, or an explicit decision that queued promises are at-least-once resolve-only semantics (thenpromiseUpdate's JSDoc must say so).The Fix
addPromiseCallbackto register{resolve, reject}pairs (both queue paths inupdateVdompass theirrejectthrough).VDomUpdate.rejectCallbacks(ownerId, err); invoke fromexecuteVdomUpdate'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).UpdateWedge.spec.mjs: apromiseUpdate()queued onto a failing flight rejects (currently resolves-late/hangs by shape).Acceptance Criteria
test/playwright/unit/vdom/UpdateWedge.spec.mjs).executeCallbacks/rejectCallbackssymmetry documented inVDomUpdateJSDoc.Out of Scope
syncVnodeTree_vnodewrite-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"