Skip to content

fix: plugin node kinds miss hover/click subscriptions when they register after mount - #665

Merged
Snoopy147 merged 3 commits into
mainfrom
fix/plugin-kind-hover
Aug 16, 2026
Merged

Snoopy147 merged 3 commits into
mainfrom
fix/plugin-kind-hover

Conversation

@Snoopy147

@Snoopy147 Snoopy147 commented Aug 16, 2026 •

Copy link
Copy Markdown
Contributor

Problem (user-reported on prod)

Hovering a window outlines it; hovering a Bones service box (electric panel / water heater) does nothing. Selection works.

Root cause

The editor + viewer SelectionManagers build their emitter subscription lists from getSelectableKinds() inside effects, snapshotted at mount. Built-in kinds register synchronously at bootstrap, but plugin kinds arrive through async dynamic-import discovery (extendPluginDiscovery(async () => (await import('@pascal-app/plugin-bones')).bonesPlugin) in the community bootstrap) — i.e. after mount. The registry had no change notification, so the subscription lists stayed frozen on the pre-plugin kind set: plugin nodes never got :enter/:leave/:click/:double-click handlers unless an unrelated effect dep (mode, movingNode, isCurveReshape) happened to change first. Hover is the visible casualty; click/paint/pointerdown lists had the same latent staleness.

Dev is unaffected (apps/editor bootstrap uses static imports → registration completes in microtasks before any effect runs), which is why QA on localhost never reproduced it.

Fix (minimal)

  • packages/core/src/registry/registry.ts: _register/_reset bump a monotonic version and notify listeners (getRegistryVersion / onRegistryChange).
  • packages/core/src/registry/use-registry-version.ts: useRegistryVersion() — useSyncExternalStore binding.
  • packages/editor SelectionManager: registryVersion added to the dep arrays of the 5 effects that snapshot getSelectableKinds() (paint, pointerdown, click, hover/double-click, delete-mode).
  • packages/viewer SelectionManager: same for its single subscription effect (deps were []).

A late plugin load now rebuilds the subscriptions; re-running the effects is idempotent (they unsubscribe in cleanup).

Tests

  • New gates in packages/core/src/registry/registry.test.ts: version bump + listener notify + unsubscribe; loadPlugin notifies per kind.
  • packages/core 1007 pass, packages/editor 604 pass, packages/viewer 101 pass; tsc --noEmit clean on all three.

🤖 Generated with Claude Code


Note

Medium Risk
Touches core input/selection wiring for all selectable kinds; behavior change is limited to fixing stale subscriptions after late plugin load, with targeted tests on the registry notification API.

Overview
Fixes plugin nodes that could be selected but not hovered in production when kinds register after the selection managers mount.

The node registry now bumps a monotonic version and notifies subscribers on each registerNode / loadPlugin kind and on test _reset, via getRegistryVersion, onRegistryChange, and useRegistryVersion() (useSyncExternalStore). Editor and viewer SelectionManager effects that snapshot getSelectableKinds() into emitter subscriptions now depend on that version so a late async plugin load re-subscribes for hover, click, double-click, paint, pointerdown, and delete handlers.

Tests cover version bumps, listener notify/unsubscribe, and per-kind notifications from loadPlugin.

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

Snoopy147 and others added 3 commits August 16, 2026 09:11
Plugin kinds register through async dynamic-import discovery, AFTER the
selection managers mount and snapshot getSelectableKinds() into their
emitter subscription lists. In prod (community bootstrap fetches plugin
chunks over the network) a plugin node kind therefore never gets hover /
click / double-click subscriptions unless an unrelated effect dep
happens to change — user-visible as "windows outline on hover, plugin
service boxes don't".

Fix: the registry bumps a monotonic version + notifies listeners on
every registerNode/_reset; a new useRegistryVersion() hook
(useSyncExternalStore) feeds that version into the dependency arrays of
every effect that snapshots the kind list (5 in the editor
SelectionManager, 1 in the viewer SelectionManager), so a late plugin
load rebuilds the subscriptions.

Gates: registry version bump + listener notify + unsubscribe, and
loadPlugin notifying per kind (packages/core registry.test.ts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Snoopy147
Snoopy147 merged commit fcb3b8a into main Aug 16, 2026
4 checks passed
@Snoopy147
Snoopy147 deleted the fix/plugin-kind-hover branch August 16, 2026 14:07
ovurrsl pushed a commit to ovurrsl/editor that referenced this pull request Aug 20, 2026
…ter after mount (pascalorg#665)

* fix: re-derive selectable-kind subscriptions when plugins register late

Plugin kinds register through async dynamic-import discovery, AFTER the
selection managers mount and snapshot getSelectableKinds() into their
emitter subscription lists. In prod (community bootstrap fetches plugin
chunks over the network) a plugin node kind therefore never gets hover /
click / double-click subscriptions unless an unrelated effect dep
happens to change — user-visible as "windows outline on hover, plugin
service boxes don't".

Fix: the registry bumps a monotonic version + notifies listeners on
every registerNode/_reset; a new useRegistryVersion() hook
(useSyncExternalStore) feeds that version into the dependency arrays of
every effect that snapshots the kind list (5 in the editor
SelectionManager, 1 in the viewer SelectionManager), so a late plugin
load rebuilds the subscriptions.

Gates: registry version bump + listener notify + unsubscribe, and
loadPlugin notifying per kind (packages/core registry.test.ts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* read registryVersion in effect bodies (biome exhaustive-deps)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* biome format

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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