Skip to content

Fix order-dependent pointer-support flake: registry leaks across test files - #709

Merged
Snoopy147 merged 4 commits into
mainfrom
fix/pointer-support-registry-leak
Aug 23, 2026
Merged

Fix order-dependent pointer-support flake: registry leaks across test files#709
Snoopy147 merged 4 commits into
mainfrom
fix/pointer-support-registry-leak

Conversation

@Snoopy147

@Snoopy147 Snoopy147 commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

The flake

Night-8 private-editor CI (run 32580694134) failed three tests in packages/editor/src/components/tools/shared/pointer-support-cap.test.ts:

  • discovers a plugin-declared top surface without a kind-name list
  • never elects the node the active interaction is placing or moving
  • uses a plugin-declared top as the wall construction surface

all with TypeError: undefined is not an object (evaluating 'definition.capabilities.surfaces') at pointer-support-cap.ts:163. The same tree passed 752/752 locally twice (forced).

Root cause: order-dependent test pollution

nodeRegistry is a module singleton and bun runs a package's test files sequentially in one process. floorplan-registry-layer.test.ts registers capabilities-less fixture definitions (cabinet, cabinet-module, linked-floorplan-test) after a nodeRegistry._reset() and never restores. Bun's test-file order differs between platforms (macOS local vs CI Linux) — when the floorplan file runs before pointer-support-cap.test.ts, the leaked entries hit the top-surface enumeration:

.filter(([, definition]) => definition.capabilities.surfaces?.top !== undefined)

and only the three tests that pass includeNodeTopSurfaces: true crash — exactly the CI signature.

Reproduced deterministically on unmodified main with bun test --randomize --seed=1 on the two-file pair: same three tests, same TypeError, same line.

The fix (both sides + a gate)

  1. Test hygiene — new test-only nodeRegistry._snapshot() (captures definitions + plugin bookkeeping, returns a restore fn). Applied in:
    • floorplan-registry-layer.test.ts — both mutation sites now snapshot/restore
    • wall-drafting.test.ts — the mid-test _reset() no longer strips the registry for later files
  2. Production honestydefinition.capabilities?.surfaces?.top. capabilities is typed required, but the resolver enumerates every registered kind and a plugin bundle can bypass the type at runtime; a minimal definition must read as "no top surface", not crash the election.
  3. Gate — a test registers a capabilities-less definition then calls resolvePointerSupportSurface: no throw, and the platform's declared top is still elected. Verified failing against the unguarded code, passing with the chain.

Bonus: seed-22 randomized verification surfaced a second latent flake of the same class (apply-alignment.test.ts inheriting a leaked building fixture through the scene/viewer store singletons — confirmed pre-existing on unmodified main). Pinned its empty-scene context explicitly.

Verification

  • Repro before fix: seeds 1/2/4/6 fail on the two-file pair with the CI signature; green after
  • Full editor suite bun test --randomize x10 fresh seeds: 753 pass / 0 fail each
  • bunx turbo run test --filter=@pascal-app/editor --force: 753/753 (752 + new gate test)
  • Full repo bunx turbo run test: 13/13 tasks green
  • check-types (editor + core) and Biome on changed files: clean

🤖 Generated with Claude Code


Note

Low Risk
Mostly test isolation plus optional chaining on a registry enumeration path. Production behavior only changes for malformed/minimal definitions that previously threw.

Overview
Stops order-dependent CI flakes from tests mutating the process-wide nodeRegistry (and scene/viewer stores) without restoring them. Bun runs a package’s tests in one process, so a leaked capabilities-less fixture crashed later pointer-support-cap files when file order differed on Linux.

Adds test-only nodeRegistry._snapshot() to capture definitions plus plugin bookkeeping and restore them. Floorplan registry and wall-drafting tests now snapshot/restore around _reset() and throwaway registrations.

resolvePointerSupportSurface now uses optional chaining on capabilities, so a kind without that field is skipped instead of throwing. A gate test registers a capless definition and still elects the platform top. Alignment tests also pin an empty scene/selection so a leaked building fixture cannot break world-axis alignment.

Reviewed by Cursor Bugbot for commit 14eb77a. Bugbot is set up for automated code reviews on this repo. Configure here.

Snoopy147 and others added 4 commits August 22, 2026 23:33
…n tests

nodeRegistry is a module singleton and bun runs a package's test files
sequentially in one process, so a test that registers a throwaway kind
(or _reset()s) without restoring leaks that state into every later test
file. File order varies by platform (macOS vs CI Linux), which turns
such leaks into order-dependent flakes. _snapshot() captures defs +
plugin bookkeeping and returns a restore function for afterEach/finally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
floorplan-registry-layer.test.ts registered capabilities-less fixture
definitions (cabinet, cabinet-module, linked-floorplan-test) into the
shared nodeRegistry singleton without cleanup. When bun's file order put
it before pointer-support-cap.test.ts (order differs macOS vs CI Linux),
the leaked entries crashed the top-surface enumeration at
pointer-support-cap.ts:163 with 'definition.capabilities is undefined' —
the night-8 CI flake (run 32580694134). Reproduced locally with
bun test --randomize (seeds 1/2/4/6 on the two-file pair).

wall-drafting.test.ts also _reset() the registry mid-test, stripping it
for later files — same pollution class, contained the same way.

Both now snapshot/restore via the new nodeRegistry._snapshot().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The top-surface election enumerates every registered kind. capabilities
is typed required on NodeDefinition, but a plugin bundle (or a leaked
test fixture) can ship a minimal definition without it at runtime — one
such entry crashed the resolver with a TypeError at pointer-support-cap
line 163 in the night-8 CI run (32580694134). Defensive optional chain +
a gate test that registers a capabilities-less definition and asserts
the election neither throws nor mis-elects (verified failing against the
unguarded code, passing with the chain).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Surfaced by the randomized-order verification for the registry-leak fix
(bun test --randomize --seed=22): the test assumes no active building,
but the scene/viewer store singletons can carry a selected building
fixture leaked by an earlier test file — getActiveBuildingPose then
crashes on the fixture's missing rotation array. Same order-dependent
pollution class as the night-8 registry flake, different singleton.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Snoopy147
Snoopy147 merged commit 707e321 into main Aug 23, 2026
4 checks passed
@Snoopy147
Snoopy147 deleted the fix/pointer-support-registry-leak branch August 23, 2026 03:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant