LearnNewsExamplesServices
Frontmatter
id15009
titleDemo-A reveal cue races the deferred re-projection, silently no-ops
stateClosed
labels
bugai
assigneesneo-fable-clio
createdAt1:53 AM
updatedAt2:11 AM
githubUrlhttps://github.com/neomjs/neo/issues/15009
authorneo-fable-clio
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAt2:11 AM
milestonev13.2

Demo-A reveal cue races the deferred re-projection, silently no-ops

neo-fable-clio
neo-fable-clio commented on 1:53 AM

Context

Born from the #14985 investigation (operator-directed continuation). The Demo-A tour's reveal beat has never painted — the merged whitebox spec test/playwright/e2e/dashboard/DemoATourNL.spec.mjs stands red on revealSeen, and that red was attributed to the vdom update wedge (#12946 class) as one of its two witnesses. Tonight's forensics falsify that attribution for THIS surface: the defect is an app-level race in the demo workspace, upstream of vdom entirely.

Live latest-open sweep: checked latest 20 open issues at 2026-07-10T23:47Z; no equivalent found. A2A in-flight claim sweep: last-hour window clean (peers on #14616/#14618/#14938/#14992).

The Problem

Three instrumented runs on dev cf6147455 (full logs in the origin session):

  1. Run 1 — no split-brain exists here. Worker-side samples of Neo.dashboard.DockRevealOverlay during the tour show isVdomUpdating: false, needsVdomUpdate: false, and cls STILL containing neo-dashboard-dock-reveal-overlay-hidden — while the DOM sampler agrees (4672 hidden frames, 0 visible). Worker truth and DOM truth AGREE: the unhide never happened anywhere. Not a lost reply; nothing was ever sent.
  2. Run 3 — the silent link convicted. An instance-field trail inside onTourBeat at the real cue moment:
{
  "beat": 14, "cueItemId": "preview",
  "hostFound": true,
  "railFound": false,
  "railId": null, "railTabIds": [], "ret": null, "err": null,
  "machineState": null, "overlayId": null
}

getReference('dock-host') resolves, but down({ntype: 'dashboard-dock-rail'}) returns null at cue time — the triple-optional-chain swallows it and the reveal dies without a trace. The rail incarnations DO exist moments later (run-1 sampled neo-dashboard-dock-rail-3 alive on a 400ms grid); the cue's instant falls in the rebuild gap.

The Architectural Reality

The race is a three-party timing seam, all by-design behaviors composing into a silent no-op:

  • apps/agentos/childapps/dockdemo/view/DemoAWorkspace.mjs:255onDockZoneDocumentChange() stores the committed document immediately (document truth is always fresh) but defers refreshDockWorkspace() one tick (me.timeout(0)), the normative guard so a committing interaction surface is never destroyed mid-handler.
  • src/ai/client/TourRunner.mjs:276 — the runner fires the beat event (carrying the cue) BEFORE executing the step, and executeDockOperation resolves at document-commit; the runner is document-tier by design and never awaits view-sync.
  • apps/agentos/tour/demoADockChoreography.mjs s3 — three setItemAutoHidden commits back-to-back, then the pause step carrying cue: {type: 'reveal', itemId: 'preview'}. The cue handler (DemoAWorkspace.mjs:488) runs within ~0ms of the third commit; the deferred projection that builds the 3-tab rail has not landed.
  • The 1600ms pause then elapses uselessly — the cue already returned null.

The composing defect is the cue handler's assumption that the projection is synchronous with the commit. It is not, and the workspace ITSELF owns that deferral — so the workspace owns the settle.

The Fix

Settle-aware cue path in DemoAWorkspace (no wall-clock sleeps, no runner changes):

  1. Track the deferred projection as an awaitable: onDockZoneDocumentChange() assigns me.refreshPromise = me.timeout(0).then(() => me.isDestroyed ? null : me.refreshDockWorkspace()).
  2. onTourBeat cue branch becomes async: await me.refreshPromise (tolerates null/settled), guard me.isDestroyed, THEN resolve the rail and feed onTabClick. Awaiting the actual promise is deterministic — the rail exists in the worker tree immediately after refreshDockWorkspace()'s host.insert(0, ...) returns.
  3. JSDoc on both sites naming the race and the settle contract (Anchor & Echo).

The scratch forensic spec (Wedge14985Diag.spec.mjs, untracked) is deleted; the EXISTING merged spec is the regression witness — DemoATourNL.spec.mjs's revealSeen assertion flips green with this fix and pins the contract forever.

Acceptance Criteria

  • onDockZoneDocumentChange tracks the deferred re-projection as refreshPromise; the reveal-cue path awaits it before rail resolution, with an isDestroyed guard.
  • No wall-clock sleeps or retry-polling introduced; the settle is the tracked promise itself.
  • DemoATourNL.spec.mjs passes (revealSeen true) — deterministic across 3 consecutive runs.
  • DemoADragMenuNL.spec.mjs + the dockdemo unit suites stay green.
  • #14985 receives the reattribution comment: the tour-red witness is withdrawn as wedge evidence; the cockpit repro (from #14658/PR #14996 instrumentation) remains its sole standing witness.

Out of Scope

  • The genuine split-brain on the FM cockpit surface (worker-committed sizes vs stale DOM) — that IS #14985 and stays there.
  • The Neural Link fixture service gaps discovered en route (getConsoleLogs and patchCode declared in test/playwright/fixtures.mjs but missing on the underlying services) — separate tooling ticket.
  • Demo-B cue handlers — they call workspace methods on document/store state and do not resolve projected components; unaffected.

Decision Record impact

none (aligned-with ADR 0029 — the holder contract's deferred view-sync stays; the fix consumes it honestly).

Related

#14985 (evidence reattribution), #14984/#14986 (the detector lineage that kept this red honest), #14589 (Demo-A origin), #13158 (QT-parity epic).

Origin Session ID: 748f9b8f-20cd-4360-9a29-c3084d059052 Retrieval Hint: "reveal cue race deferred re-projection railFound false instance-field trail"