Skip to content

fix(bindx): drop vestigial handle dispose lifecycle that broke late writes#55

Merged
matej21 merged 1 commit into
mainfrom
fix/drop-handle-dispose-lifecycle
Jun 25, 2026
Merged

fix(bindx): drop vestigial handle dispose lifecycle that broke late writes#55
matej21 merged 1 commit into
mainfrom
fix/drop-handle-dispose-lifecycle

Conversation

@matej21

@matej21 matej21 commented Jun 25, 2026

Copy link
Copy Markdown
Member

Problem

Handles are stateless live views over the store: the only subscriptions they create are returned from subscribe() and owned by useSyncExternalStore, not stored on the handle. So dispose() freed nothing — it only set _disposed and armed assertNotDisposed() to throw on legitimate late writes through an accessor a consumer still holds (e.g. the Slate block editor dispatching setValue from an async onChange after the entity version bumped and EntityHandleRenderer recreated the handle).

The dispose machinery was orphaned by the migration away from the eager identityMap subscription model, where handles really did hold _unsubscribe.

Change

  • Remove the dispose lifecycle from handles: dispose/isDisposed/assertNotDisposed in BaseHandle, the dispose() overrides, all assertNotDisposed() call sites, and dispose() from the internal RelationHandleRaw interface.
  • Delete the now-dead core/Disposable.ts.
  • Stop disposing superseded handles in useEntity and <Entity>.
  • reset() stays — it is a real operation.

Tests

Adds a regression test covering a captured field accessor that stays writable across a data-driven re-render.

Split out of #54 — this fix was previously bundled with the issue-#53 orderBy fix; the two are independent and now have separate PRs.

🤖 Generated with Claude Code

…rites

Handles are stateless live views over the store: the only subscriptions they
create are returned from subscribe() and owned by useSyncExternalStore, not
stored on the handle. So dispose() freed nothing — it only set _disposed and
armed assertNotDisposed() to throw on legitimate late writes through an accessor
a consumer still holds (e.g. the Slate block editor dispatching setValue from an
async onChange after the entity version bumped and EntityHandleRenderer recreated
the handle). The dispose machinery was orphaned by the migration away from the
eager identityMap subscription model, where handles really did hold _unsubscribe.

Remove the dispose lifecycle from handles (dispose/isDisposed/assertNotDisposed
in BaseHandle, the dispose() overrides, 25 assertNotDisposed() call sites, and
dispose() from the internal RelationHandleRaw interface), delete the now-dead
core/Disposable.ts, and stop disposing superseded handles in useEntity and
<Entity>. reset() stays — it is a real operation. Add a regression test covering
a captured field accessor that stays writable across a data-driven re-render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EAKx7JbV7Z7EVXofqRgwop
@matej21 matej21 merged commit 2cbb3ad into main Jun 25, 2026
3 checks passed
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