Skip to content

docs(openspec): propose editor-destroy - #195

Open
gohabereg wants to merge 3 commits into
mainfrom
feat/editor-destroy
Open

gohabereg wants to merge 3 commits into
mainfrom
feat/editor-destroy

Conversation

@gohabereg

@gohabereg gohabereg commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Why

There is no way to tear an editor down. Neither Core nor EditorJS has a destroy(), so unmounting an editor (route change, component unmount, several editors on one page, React StrictMode) leaks the editor DOM, the document-wide selectionchange listener, the collaboration WebSocket, timers, and every tool and plugin instance. Several plugins already implement destroy(), and the core/sdk specs already say it's called on teardown, but nothing calls it.

Board card: Clean up (Core APIs).

What Changes

This PR is the OpenSpec change only (openspec/changes/editor-destroy). Implementation follows in stacked PRs.

  • Core#destroy() / EditorJS#destroy(): synchronous, idempotent. Tears down plugins in reverse construction order (unregistering their public APIs), then UndoRedoManager, SelectionManager, BlockRenderer (each block's tool instance and adapter), then the adapter plugin. A throwing step is logged and teardown continues. The model is never mutated, so nothing reaches undo history or collaboration. use()/initialize() throw afterwards.
  • Interrupted initialization: destroy() during a pending initialize() makes it reject with a DOMException named AbortError (the platform convention, as with fetch). The bundle handles its own ready promise: AbortError is ignored, other init errors are logged, so neither is reported as an unhandled rejection; awaiting isReady still rejects.
  • Tools: optional destroy() on BlockTool/InlineTool, called once per instance the editor creates.
  • Adapter plugin: destroy() becomes required on EditorJSAdapterPlugin.
  • Repo-wide listener rule (architecture spec): every component releases listeners through one AbortController; unsubscribe functions returned by onUpdate/onCaretUpdate are tied to its abort event. SDK base classes (BlockToolAdapter) own the controller and expose a protected signal to subclasses.
  • Shared selectionchange watcher: reference-counted, so several editors can be destroyed independently.
  • UI: teardown for every UI plugin is covered by fix(ui): remove blocks holder listeners and detach it on destroy #174, so this change has no UI plugin implementation. The only UI piece kept here is InlineToolbarUI destroying the inline tool instances it creates, done after fix(ui): remove blocks holder listeners and detach it on destroy #174 lands.

Modified capabilities: architecture, core, sdk, dom-adapters, editorjs-bundle. Supersedes the "Core currently does not expose a global destroy()" note in docs/plugins.md (docs are updated in the implementation PR).

Stack

  1. This PR: spec
  2. feat(core): add editor destroy lifecycle #196 — lifecycle and API changes: Core/EditorJS destroy(), core services, tool contracts, BlockToolAdapter controller (targets this branch)
  3. feat: implement destroy() in adapters and plugins #207 — adapters, plugins, e2e tests and the archive (stacked on feat(core): add editor destroy lifecycle #196)

Verification

  • openspec validate editor-destroy --type change passes.
  • tasks.md is intentionally unchecked here; implementation PRs check off their tasks.

🤖 Generated with Claude Code

Add the editor-destroy change: Core/EditorJS destroy() lifecycle,
optional destroy() on tools, required destroy() on the adapter plugin,
and a repo-wide rule that listeners are released through AbortController
(owned by SDK base classes where they exist).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gohabereg and others added 2 commits October 6, 2026 18:30
#174 now covers teardown for every UI plugin (listeners, holders,
popovers, the EditorjsUI wrapper, pending inline toolbar config). Drop
the ui delta and the UI tasks; keep only InlineToolbarUI destroying its
inline tool instances, done after #174 lands.

Co-Authored-By: Claude Opus 5.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