fix(SelectionContext): keep selection tooltip alive after plugin view re-creation#1171
Draft
resure wants to merge 1 commit into
Draft
fix(SelectionContext): keep selection tooltip alive after plugin view re-creation#1171resure wants to merge 1 commit into
resure wants to merge 1 commit into
Conversation
resure
added a commit
to resure/gravity-notes
that referenced
this pull request
Jul 2, 2026
The virtualized note list now renders only an IconPickerButton glyph per row and shares ONE controlled IconPickerPopup (the split lives in IconPicker.tsx) — a per-row picker was a large render cost and an open one died when its row left the virtual window. Plus fixes from a max-effort review of the change: - editorCaret: the no-rect first-line fallback treats block boundaries as line breaks (an empty second bullet/blockquote line no longer yanks ArrowUp to the title), skips ProseMirror-trailingBreak placeholders (a caret at (p,1) on a blank line still hands off), gates on the collapsed range start (backward selections), and scans with comparePoint instead of deep-cloning via cloneContents - NoteList: Enter on a focused row button activates the button instead of opening the note; the ⋯ menu gains the picker's anchor keepalive + close-when-note-leaves; the shared popup is memoized with stable handlers (live-ref) and drops picks whose note id went stale - IconPicker: popup resets query/tab/scroll/highlight on every close so no state leaks across rows; open derived from the anchor - Workspace: F2 no-ops inside any floating layer (.g-popup / [role=dialog]) instead of a per-widget class blocklist - docs: EDITOR_BUG_REPORT.md superseded by the filed upstream PR (gravity-ui/markdown-editor#1171, linked in selectionContextFix.ts); HANDOFF.md follow-ups moved to README (fonts-CSP known limitation, icon-picker backlog); note-icons feature bullet added Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
255ed54 to
00d197b
Compare
…w re-creations and native context menus ProseMirror destroys and re-creates plugin views whenever state.plugins changes identity (any host-side EditorState.create, even one that passes the current plugins through). The spec's destroyed flag stayed true after that, so the mouse release was ignored, the press gate stuck, and the tooltip never showed again. Re-arm the flag in view() and re-evaluate on release without the mousedown snapshot, so a click that leaves the doc/selection unchanged re-shows the tooltip instead of stranding it hidden. Rework the press gate around a single armPressGate helper that owns its document listeners via AbortController: - treat ctrl+click as a context-menu press: macOS reports it with button 0 while the native menu still swallows the mouseup, which left the gate stuck - gate updates during a non-primary press so the browser's right-click word-selection doesn't pop the tooltip up under the open native menu; the gate releases on the next input that reaches the page - keep the gate armed across a mid-press plugin view re-creation instead of resetting it, so a host-side state swap during a drag doesn't un-gate updates - disarm the pending mouseup listener on dragstart instead of leaving it to fire on the next unrelated mouseup - ignore chorded secondary-button releases and releases whose editor was destroyed while the button was held
00d197b to
089e9a7
Compare
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.
Text below and fix itself is mostly ai-generated, but I've also checked suggested fix manually on my project that uses markdown-editor
The floating selection toolbar (WYSIWYG mode, non-empty
selectionContextconfig) goes permanently dead after any host-sideEditorStateswap.The bug
SelectionTooltipkeeps two mutable flags on the extension instance —destroyedand_isMousePressed— and nothing re-arms them when its plugin view is re-created. ProseMirror re-creates every plugin view wheneverstate.pluginschanges identity, andEditorState.createalways builds a fresh plugins array — so any host-side state swap triggers this (e.g. an undo-history reset), even one that passesview.state.pluginsthrough unchanged:destroy()setsdestroyed = true. The view is re-created immediately, butview()never resets the flag._isMousePressed = true, and arms a one-shot documentmouseuplistener.if (this.destroyed) return— so_isMousePressedis never reset.update()exits atif (this._isMousePressed) return. The toolbar never appears again for the lifetime of the editor — no errors, nothing in the console.Minimal reproduction:
Two related flaws in the same handler:
update()skips when doc + selection are unchanged since then. But the mousedown just hid the tooltip — so a press-release cycle that keeps the selection (e.g. re-selecting the same word) strands the toolbar hidden over a live selection.mouseupat all — a right-click on macOS (the native context menu opens during mousedown and swallows the release), or a press that turns into a native drag (ends withdragend) — wedging_isMousePresseduntil the next completed left click.The fix
view(): re-armdestroyedand_isMousePressedwhen the plugin view is (re-)created — plugin views are re-created far more often than plugins are.dragstart.The plugin-view
updatepath keeps itsprevStateshort-circuit — that one is correct (the hide-meta check runs before it).Tests
The new
index.test.tsdrives the plugin through the publicSelectionContextextension with a realEditorViewin jsdom, including a realview.updateState(EditorState.create(...))swap for the main scenario — so it exercises ProseMirror's actual plugin-view re-creation. On currentmainthe baseline test passes and the four regression tests fail (one per flaw); with the fix all five pass. The package unit suite stays green.Found in gravity-notes, which currently ships a vendored copy of this plugin with these fixes; also verified end-to-end there in a packaged app with the plugin's gate decisions instrumented (before:
mousedown → mouseup → (nothing), toolbar dead; after:mousedown → mouseup → SHOW).Summary by Sourcery
Fix SelectionContext tooltip behavior so the floating selection toolbar remains functional after editor state swaps and various mouse interactions.
Bug Fixes:
Tests: