LearnNewsExamplesServices
Frontmatter
id18275
titleA lazy dock pane still constructs twice after its dedup fix shipped
stateClosed
labels
bugaitestingcoredashboard
assigneesneo-opus-ada
createdAtSep 4, 2026, 3:35 PM
updatedAtSep 9, 2026, 9:43 AM
githubUrlhttps://github.com/neomjs/neo/issues/18275
authorneo-opus-ada
commentsCount13
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 9, 2026, 9:43 AM

A lazy dock pane still constructs twice after its dedup fix shipped

Closed v13.2.0/archive-v13-2-0-chunk-19 bugaitestingcoredashboard
neo-opus-ada
neo-opus-ada commented on Sep 4, 2026, 3:35 PM

Context

Found while reading CI for an unrelated reason: dev went red twice during this morning's merge sequence, and both reds are the same arm with the same message. @neo-opus-grace independently recorded the same fingerprint as a defect-note at 03:22Z ("went red-then-green on ONE SHA — a double construction on the lazy-insert path, absorbed by a passing re-run"), which makes this a second, independent occurrence and satisfies the §1e promotion bar.

I read both job logs directly rather than a dashboard, because a passing re-run is exactly what makes this invisible.

Sweeps. Live latest-open sweep: checked the latest 20 open issues at 2026-09-04T13:33Z; no equivalent (#18121 and #18215 are different unstable arms). Own-assignment sweep: 7 open, none on this surface.

Memory Core rationale sweep — deferred at filing, completed at 13:47Z. At filing time query_raw_memories was rejecting with consumer-probe-timeout:EMBEDDING_PROBE_TIMEOUT (backoff 60 s, streak 2), so this ticket was filed on the artifact fallback (git grep over resources/content/**, no equivalent) with the gap stated rather than a sweep claimed. @neo-gpt-emmy diagnosed the outage as an intermittent LM Studio provider timeout — the embedding write canary timed out twice at 30,001 ms around 13:31–13:32Z, with Chroma and the MCP transport healthy throughout. It recovered, so I ran the sweep I owed: 8 results, zero mentioning double construction, constructs twice, loadModule, loadingModules, #18201 or #18084. No prior decision is being re-derived here. Correcting the body rather than appending a comment, because the sweep attestation is a claim a reader checks in the body.

The Problem

Both failures are test/playwright/component/dashboard/DockRailLazyModule.spec.mjs:121, "un-hidden as the only visible item of its tabs node, the lazy item is the active card and loads at once", dying on its last assertion:

expect(instances, 'one construction').toBe(1)
run head merge that produced the head at
33852876846 4425422926 fix(dock): a restored layout brings back the tab you were on (#18255) (#18257) 08:19Z
33854077715 2894daa172 feat(toolbar): an action owns its own toggle mapping, so it exists once (#18241) (#18258) 08:34Z

Both report 1 failed / 233 passed, both in the components job while unit stayed green, and every dev run after 08:48Z is green. The failures are unrelated to what those two PRs changed — neither touches the lazy-load path; they are the commits that happened to be under the runner when the window opened.

This is post-fix, and that is the whole point. 4c5b56a9b1 — "fix(layout): loadModule honours the in-flight marker it already sets (#18201) (#18204)" — landed on dev 2026-09-03T13:55Z and is an ancestor of both failing heads (verified with git merge-base --is-ancestor). #18201 is CLOSED/COMPLETED. So the arm reds after the fix written for it.

It does not reproduce locally. 275 runs of the spec on an M-series box: 15 repeats × 1 worker (75 passed, 52.7 s) and 40 repeats × 8 workers (200 passed, 31.7 s). All green. That is consistent with the fix's own JSDoc, which says the window "is exactly the import's duration, which is why it surfaced as an intermittent double construction under machine load" — a GitHub runner is loaded in a way this box is not. Our dev box is not the architecture.

The Architectural Reality

Two callers legitimately load the same parked item, and src/layout/Card.mjs documents this precisely:

  • Card#afterSetActiveIndex (:120) loads a parked item when its index activates.
  • container.Base#insert (src/container/Base.mjs, added by 13c77b549a for #18084) starts the load itself when the inserted index is already the active one.

#18201's fix added Card#loadingModules, a WeakMap<Object,Promise> keyed by the parked config, so a second caller joins the first load instead of starting its own (Card.mjs:221-236). The map entry is removed in a finally, deliberately, so a rejected import stays retryable.

One observation, offered as a lead and explicitly not as a diagnosis: that guard is in-flight-scoped rather than identity-scoped. It dedups callers who arrive while the promise is pending; the entry is gone once it settles. Whether any caller can arrive holding the same parked config after settle is exactly the thing this ticket does not know, and reading the code did not settle it — afterSetActiveIndex re-reads container.items on entry, which appears to close the obvious version of that window.

The Fix

Unknown. The next step is a reproduction, not an implementation.

I have a symptom on CI, no reproduction anywhere I can watch, and a hypothesis I could not confirm by reading. Writing the arm first is the failure this team already paid for: PR #18267 went red under three independent mutations and still witnessed the opposite of the reported transition, because the mutations only probed the scenario the arm itself constructed.

Candidate approaches for whoever drives this, in cost order:

  1. Widen the window deterministically rather than hoping for load — inject a slow resolver for the lazy module function in the component fixture and see whether a second construction becomes reliable.
  2. Instrument rather than assert: have the fixture record every Neo.create for the parked config with a stack, so the second caller identifies itself instead of being inferred.
  3. Only then decide whether the guard needs to become identity-scoped, and whether that belongs in Card#loadModule or at the insert call site.

Acceptance Criteria

  • AC-1 — the double construction is reproduced somewhere a test can watch it, on demand rather than by waiting for CI. Tier is free; the direction is not. Without this nothing below is actionable.
  • AC-2 — the second caller is named from that reproduction: which code path reached Neo.create for an already-loading or already-loaded parked config, identified from evidence rather than inferred from reading.
  • AC-3 — only then a fix, with an arm red before it and green after, against the reproduction from AC-1.
  • AC-4 — if AC-1 cannot be reproduced, this closes as not-reproducible with the attempts recorded, #18201's fix stays as shipped, and the arm is either quarantined with a named owner or left with its flake documented on #18215's pattern. A negative result is a valid end state.

Out of Scope

  • Reopening or amending #18201. Its fix addressed a real mechanism, its own AC was met, and it is @neo-opus-vega's ticket — §11 authorship respect. This is a successor, not a reversal.
  • Reverting 13c77b549a. The insert-side load is correct and is what makes a lazily-resolved pane materialize at all.
  • Fixing the other unstable arms (#18121, #18215). Same shape, different subjects, separately owned.

Avoided Traps

  • Writing the fix before the reproduction. #18267 is the worked example and it is mine.
  • Reading a green local run as evidence of absence. 275 passes on an unloaded box say nothing about a load-dependent window; they are recorded above as a bound on the reproduction, not as a result.
  • Blaming the merge. Both failures sit on merge commits from a merge order I published this morning, which is a coincidence of timing — neither PR touches this path, and the arm reds independently of what is under it.
  • Treating the passing re-run as the answer. Every one of these was absorbed by a later green. That is the mechanism by which this has stayed open, not evidence that it resolved.

Decision Record impact

none. No ADR is engaged; ai/ and Agent OS surfaces are untouched, so the §1c structure-map gate records N/A.

Related

  • #18201 — the in-flight dedup, CLOSED/COMPLETED; its fix 4c5b56a9b1 is an ancestor of both failures here.
  • #18084 / #18086 — the insert-side parked-config load (13c77b549a) that created the second caller.
  • #18121, #18215 — sibling unstable arms, same absorbed-by-re-run shape, different subjects.
  • #18267 — the closed PR that demonstrates why AC-1 precedes AC-3.

Origin Session ID: 7596538d-5324-419a-b043-95c585d833ea

Retrieval Hint: lazy dock pane constructs twice after the in-flight WeakMap dedup shipped — CI-only, load-dependent, 275 local runs green, reproduction is the open work

tobiu referenced in commit 550a994 - "test(dashboard): the construction-trail JSDoc describes behaviour, not a tracking ref (#18275) on Sep 4, 2026, 3:48 PM
tobiu referenced in commit 57ae888 - "test(dashboard): the one-construction arms carry their cause (#18275) (#18280) on Sep 4, 2026, 5:36 PM