Repository navigation
Conversation
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>
#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>
neSpecc
approved these changes
Oct 30, 2026
Reversean
requested changes
Nov 3, 2026
Review on #196 found that the tool preparation queue kept running after destroy(). Tools still queued were prepared, and a tool whose prepare() settled after teardown was created and announced. D4 now describes the fix: ToolsManager owns an AbortController that destroy() aborts, and each queued job checks it before and after prepare(). A prepare() already running can't be interrupted, so its result is dropped. The core scenario for a destroy during a pending initialize() covers it, and task 6.5 tracks the implementation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review on #195 pointed out that InlineToolFacade.create() returns one cached instance per tool. Formatting, selection, validation and the inline toolbar all share it, so the cleanup D7 proposed would destroy tools still in use, several times over. - Design: D7 is rewritten. Each inline tool's facade owns its shared instance, and ToolsManager destroys it once, as the last step of teardown. Both rejected alternatives are explained. D3 gains the ToolsManager step and D5 the FormattingAdapter change. The context table and the #174 risk are updated. - Specs: the SDK teardown hook requirement states the rule, with a facade scenario. Core gains a scenario for shared inline instances destroyed last, once each. The dom-adapters formatting scenario no longer destroys them. - The proposal and tasks 4.3, 4.4, 5.4 and 7.2 are updated to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reversean
approved these changes
Nov 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
There is no way to tear an editor down. Neither
CorenorEditorJShas adestroy(), so unmounting an editor (route change, component unmount, several editors on one page, React StrictMode) leaks the editor DOM, the document-wideselectionchangelistener, the collaboration WebSocket, timers, and every tool and plugin instance. Several plugins already implementdestroy(), and thecore/sdkspecs 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), thenUndoRedoManager,SelectionManager,BlockRenderer(each block's tool instance and adapter), the adapter plugin, and lastToolsManager. A throwing step is logged and teardown continues. The model is never mutated, so nothing reaches undo history or collaboration.use()/initialize()throw afterwards.destroy()during a pendinginitialize()makes it reject with aDOMExceptionnamedAbortError(the platform convention, as withfetch). Tool preparation stops too:ToolsManageraborts its own controller, so tools still queued aren't prepared and a tool whoseprepare()settles afterwards is neither created nor announced. The bundle handles its own ready promise:AbortErroris ignored, other init errors are logged, so neither is reported as an unhandled rejection; awaitingisReadystill rejects.destroy()onBlockTool/InlineTool, called once per instance the editor creates:BlockRendererowns each per-block instance.InlineToolFacade.create()returns one instance per tool, shared by formatting, selection and the inline toolbar. None of those consumers destroys it. The facade does, once, throughToolsManager, as the last step of teardown (D7).destroy()becomes required onEditorJSAdapterPlugin.architecturespec): every component releases listeners through oneAbortController; unsubscribe functions returned byonUpdate/onCaretUpdateare tied to itsabortevent. SDK base classes (BlockToolAdapter) own the controller and expose aprotected signalto subclasses.selectionchangewatcher: reference-counted, so several editors can be destroyed independently.InlineToolbarUIdoesn't destroy inline tool instances either, because they're shared (D7).Modified capabilities:
architecture,core,sdk,dom-adapters,editorjs-bundle. Supersedes the "Core currently does not expose a globaldestroy()" note indocs/plugins.md(docs are updated in the implementation PR).Stack
Core/EditorJSdestroy(), core services, tool contracts,BlockToolAdaptercontroller (targets this branch)Verification
openspec validate editor-destroy --type changepasses.🤖 Generated with Claude Code