Skip to content

fix(editor): clear rich text editor when editing shape is deleted#8050

Merged
MitjaBezensek merged 3 commits intomainfrom
mitja/fix-link-editor-stuck-open
Feb 25, 2026
Merged

fix(editor): clear rich text editor when editing shape is deleted#8050
MitjaBezensek merged 3 commits intomainfrom
mitja/fix-link-editor-stuck-open

Conversation

@MitjaBezensek
Copy link
Copy Markdown
Contributor

@MitjaBezensek MitjaBezensek commented Feb 20, 2026

Closes #8049

See the issue for how to repro.

When a remote user deletes a shape that's being edited, cleanupInstancePageState sets editingShapeId = null directly. The defaultSideEffects handler then transitions to select.idle, which fires EditingShape.onExit(). But onExit() had an early return (if (!editingShapeId) return) that bailed because the id was already cleared — so setEditingShape(null) never ran and _currentRichTextEditor was never cleared. The rich text toolbar (and link editor) stayed visible.

Fix

  • Remove the early return in EditingShape.onExit(). The guard originally protected code that used editingShapeId after calling setEditingShape(null), but that logic has since moved into setEditingShape itself. Now setEditingShape(null) always runs on exit, clearing _currentRichTextEditor.
  • Remove the _currentRichTextEditor.set(null) added in the initial commit inside cleanupInstancePageState — it's no longer needed since the state machine exit path handles it.

Change type

  • bugfix

Test plan

  1. Open the same tldraw room in two browser tabs
  2. In tab A, create a text shape, enter edit mode, select text, open the link editor
  3. In tab B, delete the text shape
  4. Verify tab A's link editor disappears

Release notes

  • Fix rich text toolbar staying open when editing shape is deleted by another user

Note

Low Risk
Small, localized change to EditingShape.onExit() cleanup logic; primary risk is unintended tool switching when leaving edit mode, but the new hadEditingShape guard narrows that behavior.

Overview
Fixes a state-machine edge case where leaving select.editing_shape could skip cleanup if editingShapeId had already been cleared externally (e.g. by collaborative deletion).

EditingShape.onExit() now always calls editor.setEditingShape(null) and only reverts the tool-lock-to-text flow when an editing shape actually existed, preventing stale rich-text UI (toolbar/link editor) from remaining visible.

Written by Cursor Bugbot for commit 30daedb. This will update automatically on new commits. Configure here.

@vercel
Copy link
Copy Markdown

vercel bot commented Feb 20, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
examples Ready Ready Preview Feb 25, 2026 2:10pm
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
analytics Ignored Ignored Preview Feb 25, 2026 2:10pm
chat-template Ignored Ignored Preview Feb 25, 2026 2:10pm
tldraw-docs Ignored Ignored Preview Feb 25, 2026 2:10pm
tldraw-shader Ignored Ignored Preview Feb 25, 2026 2:10pm
workflow-template Ignored Ignored Preview Feb 25, 2026 2:10pm

Request Review

@huppy-bot huppy-bot bot added the bugfix Bug fix label Feb 20, 2026
if (prevPageState.editingShapeId && shapesNoLongerInPage.has(prevPageState.editingShapeId)) {
if (!nextPageState) nextPageState = { ...prevPageState }
nextPageState.editingShapeId = null
this._currentRichTextEditor.set(null)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch! we should make this logic change, however, in setEditingShape, not in this side effect path.
i think this used to maybe work but we added if (!this.canEditShape(id)) return this in setEditingShape so maybe the cleanup code isn't running.
there's some lifecycle stuff there (onEditEnd/onEditStart) that i think might get skipped (potentially) if we don't make the logic check there

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed this in the latest commit. To me that looked the right place to do the fix that will trigger the correct clean up, but I may be missing something so I'd love if you could take another look 🙌

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

const { editingShapeId } = this.editor.getCurrentPageState()
if (!editingShapeId) return

// Clear the editing shape
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unconditional edit cleanup triggers redundant updates

Low Severity

EditingShape.onExit() now calls this.editor.setEditingShape(null) unconditionally, even in flows where editingShapeId was already cleared before the transition. This forces an extra Editor.run plus _updateCurrentPageState / _currentRichTextEditor.set(null) updates, potentially causing avoidable reactive work and re-renders during state transitions.

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, think that's correct. We do want to run all the cleanup logic for cases when we are no longer editing a shape.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i think this is good

// Clear the editing shape
this.editor.setEditingShape(null)

updateHoveredShapeId.cancel()
Copy link
Copy Markdown
Contributor Author

@MitjaBezensek MitjaBezensek Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also felt like this additional cleanup should also run?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

Copy link
Copy Markdown
Member

@mimecuvalo mimecuvalo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@MitjaBezensek MitjaBezensek added this pull request to the merge queue Feb 25, 2026
Merged via the queue into main with commit cdde108 Feb 25, 2026
18 checks passed
@MitjaBezensek MitjaBezensek deleted the mitja/fix-link-editor-stuck-open branch February 25, 2026 15:16
steveruizok added a commit that referenced this pull request Feb 27, 2026
Add 12 new entries from PRs merged since v4.4.0:
- Featured: click-through on transparent image pixels (#7942)
- API: enum-to-const-object refactor (#8084)
- Improvements: SVG sanitizer (#7896), save-on-blur (#8037)
- Bug fixes: cross-origin download (#8090), zero-size draw (#8067),
  rich text toolbar cleanup (#8050), zoom threshold (#8040),
  selection foreground fallback (#8011), sticky note SVG shadow (#7934),
  arrow frame clamping (#7932), zero pressure draw (#5693)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Link editor stays open if text shape is deleted while editing link

2 participants