Context
Reported by @neo-opus-grace (A2A, 2026-08-04 ~12:27Z) after it cost two lanes a diagnosis: test/playwright/unit/tab/plugin/Overflow.spec.mjs — the #16434 theme-deferral re-arm witness — fails 3/3 in the full tab suite (npx playwright test test/playwright/unit/tab/ -c test/playwright/playwright.config.unit.mjs --workers=1) while passing 26/26 in isolation, with and without her unrelated diff, on current dev. It also failed her PR #16489 unit CI earlier the same day (pre-#16424-merge line numbering :408), and appeared once as an unreproduced single-flake during the #16424 cycle — where the author's flake sampling reran only the FILE, not the directory, and wrongly exonerated it. Reproduced independently 1-for-1 before filing (30 passed / 1 failed, the exact assertion the first sync arms exactly one deferral listener: expected 1, received 0). Live latest-open sweep at filing: no equivalent among the latest 20; the defect surfaced as A2A traffic only.
The Problem
Order-dependent, deterministic cross-FILE test pollution: whenever any spec file that boots a real Neo app world (here tab/BodyContainer.spec.mjs) runs before tab/plugin/Overflow.spec.mjs in the same reused Playwright worker process, the deferral witness receives 0 theme-listener registrations where isolation yields 1.
The Architectural Reality
src/mixin/VdomLifecycle.mjs:9 captures const {currentWorker} = Neo; at module load time. The theme-deferral gate at :562 then consults currentWorker.countLoadingThemeFiles and registers its themeFilesLoaded once-listener on that same captured object.
Under Playwright worker-process reuse, module state persists across spec files while each file's setup() builds a fresh app world and reassigns Neo.currentWorker. Result: the SECOND file's tests prime Neo.currentWorker (the fresh worker — countLoadingThemeFiles = 1, spied .on) while the gate reads the FIRST file's worker (count 0, unspied) — the deferral branch never fires, zero registrations, deterministic red. In isolation there is exactly one worker world and the identities coincide, hence green. The sibling reads at :313/:449 consult isSharedWorker — a per-process environment constant that stays truthful on the stale object — so the mutable-state read at :562 (and its :1025 twin) is the repair surface. Real-runtime behavior is unaffected by the fix: with a single worker world per process, the live read and the captured binding are the same object.
Secondary hygiene finding in the same suite context (introduced by PR #16490's theme-edge re-projection): the owner theme change unit test's stub control carries no destroy(), so the now-fired projection logs TypeError: me.control.destroy is not a function through the live-owner error path. Harmless to the verdict, noisy in every suite log — the stub gains the method it always implicitly promised.
The Fix
src/mixin/VdomLifecycle.mjs: read Neo.currentWorker live in the theme-deferral gate (:562 block — both the count consult and the listener registration) and in the :1025 twin. No behavioral change in real runtime; repairs the split-identity harness case.
test/playwright/unit/tab/plugin/Overflow.spec.mjs: give the theme-change test's stub control a no-op destroy().
Acceptance Criteria
Out of Scope
- Harness-level per-file worker isolation policy (the live read fixes the class for this gate; a broader isolation discussion is not this ticket).
#16498's removeAt clamp (Grace's lane; found in the same suite session, unrelated seam).
Decision Record impact
none.
Related
#16434 (the witness's origin), #16424 / PR #16490 (line drift + the stub-noise introduction), PR #16489 (first CI casualty), #16498 (co-discovered in the same session).
Origin Session ID: 1913de09-6dc0-4d1e-a9a3-b51c33b46cdc
Retrieval Hint: query_raw_memories("stale currentWorker module binding theme deferral gate worker reuse order-dependent unit suite")
Context
Reported by @neo-opus-grace (A2A, 2026-08-04 ~12:27Z) after it cost two lanes a diagnosis:
test/playwright/unit/tab/plugin/Overflow.spec.mjs— the#16434theme-deferral re-arm witness — fails 3/3 in the full tab suite (npx playwright test test/playwright/unit/tab/ -c test/playwright/playwright.config.unit.mjs --workers=1) while passing 26/26 in isolation, with and without her unrelated diff, on currentdev. It also failed her PR#16489unit CI earlier the same day (pre-#16424-merge line numbering:408), and appeared once as an unreproduced single-flake during the#16424cycle — where the author's flake sampling reran only the FILE, not the directory, and wrongly exonerated it. Reproduced independently 1-for-1 before filing (30 passed / 1 failed, the exact assertionthe first sync arms exactly one deferral listener: expected 1, received 0). Live latest-open sweep at filing: no equivalent among the latest 20; the defect surfaced as A2A traffic only.The Problem
Order-dependent, deterministic cross-FILE test pollution: whenever any spec file that boots a real Neo app world (here
tab/BodyContainer.spec.mjs) runs beforetab/plugin/Overflow.spec.mjsin the same reused Playwright worker process, the deferral witness receives0theme-listener registrations where isolation yields1.The Architectural Reality
src/mixin/VdomLifecycle.mjs:9capturesconst {currentWorker} = Neo;at module load time. The theme-deferral gate at:562then consultscurrentWorker.countLoadingThemeFilesand registers itsthemeFilesLoadedonce-listener on that same captured object.Under Playwright worker-process reuse, module state persists across spec files while each file's
setup()builds a fresh app world and reassignsNeo.currentWorker. Result: the SECOND file's tests primeNeo.currentWorker(the fresh worker —countLoadingThemeFiles = 1, spied.on) while the gate reads the FIRST file's worker (count0, unspied) — the deferral branch never fires, zero registrations, deterministic red. In isolation there is exactly one worker world and the identities coincide, hence green. The sibling reads at:313/:449consultisSharedWorker— a per-process environment constant that stays truthful on the stale object — so the mutable-state read at:562(and its:1025twin) is the repair surface. Real-runtime behavior is unaffected by the fix: with a single worker world per process, the live read and the captured binding are the same object.Secondary hygiene finding in the same suite context (introduced by PR
#16490's theme-edge re-projection): theowner theme changeunit test's stub control carries nodestroy(), so the now-fired projection logsTypeError: me.control.destroy is not a functionthrough the live-owner error path. Harmless to the verdict, noisy in every suite log — the stub gains the method it always implicitly promised.The Fix
src/mixin/VdomLifecycle.mjs: readNeo.currentWorkerlive in the theme-deferral gate (:562block — both the count consult and the listener registration) and in the:1025twin. No behavioral change in real runtime; repairs the split-identity harness case.test/playwright/unit/tab/plugin/Overflow.spec.mjs: give the theme-change test's stub control a no-opdestroy().Acceptance Criteria
test/playwright/unit/tab/) green ×3 consecutive at the fixed head; the deferral witness passes in BOTH suite and isolation contexts.project() threw against a live ownerTypeError from the theme-change stub.:438failure (already receipted above and in Grace's 3/3 table).Out of Scope
#16498'sremoveAtclamp (Grace's lane; found in the same suite session, unrelated seam).Decision Record impact
none.
Related
#16434(the witness's origin),#16424/ PR#16490(line drift + the stub-noise introduction), PR#16489(first CI casualty),#16498(co-discovered in the same session).Origin Session ID: 1913de09-6dc0-4d1e-a9a3-b51c33b46cdc
Retrieval Hint:
query_raw_memories("stale currentWorker module binding theme deferral gate worker reuse order-dependent unit suite")