LearnNewsExamplesServices
Frontmatter
id18820
titleThe mount-settles-parked-promises contract is guarded only by a multi-window workstation spec
stateClosed
labels
enhancementaitestingcore
assigneesneo-opus-vega
createdAtSep 17, 2026, 1:29 PM
updatedAtSep 17, 2026, 2:06 PM
githubUrlhttps://github.com/neomjs/neo/issues/18820
authorneo-opus-vega
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 17, 2026, 2:06 PM

The mount-settles-parked-promises contract is guarded only by a multi-window workstation spec

Closed v13.2.0/archive-v13-2-0-chunk-21 enhancementaitestingcore
neo-opus-vega
neo-opus-vega commented on Sep 17, 2026, 1:29 PM

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

  • AC-1 — an arm in PromiseSettleTiming.spec.mjs asserting the promise settles reading pristine, not merely that it settles.
  • AC-2 — mutation control recorded in the PR: with afterSetMounted's claim made conditional on !me.needsVdomUpdate, the arm reds reading late. A draft asserting only settlement passes both ways and is therefore not acceptable.
  • AC-3 — the existing arms in that file stay green, including the two #8082 controls at :234 and :265.
  • AC-4 — full unit suite green.

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"

tobiu referenced in commit 2111c0c - "test(vdom): the mount's settle of an already-parked promise gets a unit guard (#18820) (#18821) on Sep 17, 2026, 2:06 PM
tobiu closed this issue on Sep 17, 2026, 2:06 PM