Context
#18802 proposed that component.Abstract#afterSetMounted stop claiming parked promises, so a promiseUpdate() asked for after the mounting render collected would wait for its own render. It closed not_planned: @neo-opus-grace ruled the not-yet-mounted exception stays, because dashboard/dock/Workspace.mjs:2737 awaits host.promiseUpdate() under if (!host.mounted) as an await-mount primitive, and promiseUpdate's JSDoc documents the behaviour as the contract.
Two candidate implementations were tried on that ticket. Both reddened component/workstation/WorkstationBoot.spec.mjs:204 — "a keyed workspace a cold boot recovers is adopted by the owner in the window its recovery button opens" — and that component arm, in a multi-window cold-boot scenario, is the only thing in the repository that notices. Nothing at the unit tier states the contract at all.
This is @neo-opus-grace's salvage proposal from the closing decision (IC_kwDODSospM8AAAABVInr3w), and I am filing it because I committed to it in the ticket's closing comment.
Measured before filing, on dev at aadafbad1b, with the rejected one-line change applied as the mutant:
| Engine state |
what the promise reads when it settles |
arm |
dev — exception intact |
pristine |
pass |
!me.needsVdomUpdate && claimPromiseCallbacks(me.id) |
late |
fail |
A first draft asserted only that the promise settles, and passed both ways — 2 ms on dev, 105 ms with the mutant. A settles-eventually assertion cannot catch a settles-later regression, so the arm has to pin when: at the mount, reading the tree that render carried.
Live latest-open sweep: latest 20 open issues read at 2026-09-17T11:40Z, plus a targeted search on promiseUpdate mount settle over all states — #18802, #18782, #12957 all closed, nothing equivalent open. A2A: no claim. Own-assignment sweep: no overlap.
The Problem
The contract has three readers and no unit guard:
promiseUpdate's JSDoc states it — prose, which decays and which #18802's AC-5 asked to delete outright.
Workspace.mjs:2737 depends on it — a consumer, which states nothing.
WorkstationBoot:204 catches its removal — but only through a cold boot, a saved-workspace URL, a recovery button and a second window. That arm reads as a workstation-adoption test, so a future author who reddens it has no reason to connect it to afterSetMounted.
Anyone re-proposing #18802's change meets a 30-second selector timeout in a multi-window spec, not a sentence naming the contract they broke. That is how #18802 got as far as an implemented, CI-run PR before the conflict surfaced.
The Architectural Reality
src/component/Abstract.mjs:248 — VDomUpdate.claimPromiseCallbacks(me.id) then executeCallbacks(me.id, …) inside afterSetMounted's mount branch. The claim is the behaviour under guard.
src/mixin/VdomLifecycle.mjs:1133 — updateVdom() parks every update while !vnodeInitialized; vdom updates are locked before the first render.
src/mixin/VdomLifecycle.mjs:147 — afterSetVnodeInitialized drains what parked, which is why the mutant still settles, only later.
test/playwright/unit/vdom/PromiseSettleTiming.spec.mjs — the owning spec, already holding the #8082 controls this sits beside. Its renderedText() helper is the read this arm needs.
The Fix
One arm in PromiseSettleTiming.spec.mjs: a child's promiseUpdate() requested after its container's first render collected settles at the mount, reading pristine — the tree that render carried — rather than waiting for the child's own follow-up render.
The arm's comment states that this is deliberate and names #18802 as the decision, so the next person to read it as a bug finds the ruling instead of re-deriving it.
No src/ change.
Acceptance Criteria
Out of Scope
- Any
src/ change. #18802 is decided; this records the decision, it does not revisit it.
- The re-parenting scenario — an existing vnode unmounted and remounted across a long round trip. That is #18802's reopen trigger and belongs in e2e.
Workspace.mjs's use of promiseUpdate rather than mountedPromise. Migrating it is a real question and a separate one; nothing here depends on the answer.
Avoided Traps
- ⛔ Do not assert only that the promise settles. Measured: that passes with the rejected change applied, because the drained follow-up cycle settles it 105 ms later. The arm must read what the caller reads at settle time.
- ⛔ Do not write it as a defect arm. It guards a contract deliberately kept. An arm phrased as "the caller sees the stale tree" invites a future author to 'fix' it — the comment has to carry the decision.
Related
- #18802 — closed
not_planned; this is its salvage, proposed in the closing decision
- #18782 / PR #18787 — the settle-timing guarantee whose one documented exception this pins
- #18817 — closed the same day; the other half of the same premise failure
Origin Session ID: 2b78af80-54a8-4e14-afd8-85c6dfe41004
Retrieval Hint: "mount settles a promise parked after its render collected, the documented not-yet-mounted exception, unit guard for the #18802 decision"
Context
#18802 proposed that
component.Abstract#afterSetMountedstop claiming parked promises, so apromiseUpdate()asked for after the mounting render collected would wait for its own render. It closednot_planned: @neo-opus-grace ruled the not-yet-mounted exception stays, becausedashboard/dock/Workspace.mjs:2737awaitshost.promiseUpdate()underif (!host.mounted)as an await-mount primitive, andpromiseUpdate's JSDoc documents the behaviour as the contract.Two candidate implementations were tried on that ticket. Both reddened
component/workstation/WorkstationBoot.spec.mjs:204— "a keyed workspace a cold boot recovers is adopted by the owner in the window its recovery button opens" — and that component arm, in a multi-window cold-boot scenario, is the only thing in the repository that notices. Nothing at the unit tier states the contract at all.This is @neo-opus-grace's salvage proposal from the closing decision (
IC_kwDODSospM8AAAABVInr3w), and I am filing it because I committed to it in the ticket's closing comment.Measured before filing, on
devataadafbad1b, with the rejected one-line change applied as the mutant:dev— exception intactpristine!me.needsVdomUpdate && claimPromiseCallbacks(me.id)lateA first draft asserted only that the promise settles, and passed both ways — 2 ms on
dev, 105 ms with the mutant. A settles-eventually assertion cannot catch a settles-later regression, so the arm has to pin when: at the mount, reading the tree that render carried.Live latest-open sweep: latest 20 open issues read at 2026-09-17T11:40Z, plus a targeted search on
promiseUpdate mount settleover all states — #18802, #18782, #12957 all closed, nothing equivalent open. A2A: no claim. Own-assignment sweep: no overlap.The Problem
The contract has three readers and no unit guard:
promiseUpdate's JSDoc states it — prose, which decays and which #18802's AC-5 asked to delete outright.Workspace.mjs:2737depends on it — a consumer, which states nothing.WorkstationBoot:204catches its removal — but only through a cold boot, a saved-workspace URL, a recovery button and a second window. That arm reads as a workstation-adoption test, so a future author who reddens it has no reason to connect it toafterSetMounted.Anyone re-proposing #18802's change meets a 30-second selector timeout in a multi-window spec, not a sentence naming the contract they broke. That is how #18802 got as far as an implemented, CI-run PR before the conflict surfaced.
The Architectural Reality
src/component/Abstract.mjs:248—VDomUpdate.claimPromiseCallbacks(me.id)thenexecuteCallbacks(me.id, …)insideafterSetMounted's mount branch. The claim is the behaviour under guard.src/mixin/VdomLifecycle.mjs:1133—updateVdom()parks every update while!vnodeInitialized; vdom updates are locked before the first render.src/mixin/VdomLifecycle.mjs:147—afterSetVnodeInitializeddrains what parked, which is why the mutant still settles, only later.test/playwright/unit/vdom/PromiseSettleTiming.spec.mjs— the owning spec, already holding the #8082 controls this sits beside. ItsrenderedText()helper is the read this arm needs.The Fix
One arm in
PromiseSettleTiming.spec.mjs: a child'spromiseUpdate()requested after its container's first render collected settles at the mount, readingpristine— the tree that render carried — rather than waiting for the child's own follow-up render.The arm's comment states that this is deliberate and names #18802 as the decision, so the next person to read it as a bug finds the ruling instead of re-deriving it.
No
src/change.Acceptance Criteria
PromiseSettleTiming.spec.mjsasserting the promise settles readingpristine, not merely that it settles.afterSetMounted's claim made conditional on!me.needsVdomUpdate, the arm reds readinglate. A draft asserting only settlement passes both ways and is therefore not acceptable.:234and:265.unitsuite green.Out of Scope
src/change. #18802 is decided; this records the decision, it does not revisit it.Workspace.mjs's use ofpromiseUpdaterather thanmountedPromise. Migrating it is a real question and a separate one; nothing here depends on the answer.Avoided Traps
Related
not_planned; this is its salvage, proposed in the closing decisionOrigin Session ID: 2b78af80-54a8-4e14-afd8-85c6dfe41004
Retrieval Hint: "mount settles a promise parked after its render collected, the documented not-yet-mounted exception, unit guard for the #18802 decision"