fix(muya): guard stale cursor/block lookups against null#4267
Merged
Conversation
Cursor state can survive a render that removed the block it references (code-block reload, image figure swap, async rerenders, etc.). The follow-up click / keyboard / backspace handler then dereferenced the null result from getBlock() and crashed the renderer. - checkNeedRender: bail out when start/end block is missing (fixes #3674, #3842, #4032, #4072, #4122, #4143, #4162) - clickHandler: guard the format-picker branch on the same lookup - deleteImage: drop the stale selection instead of reading .text on null (fixes #3950) - selectionChange: return an empty affiliation when blocks are missing, protecting getTOC-style callers (same family as #3999) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens Muya editor event handlers against stale cursor state where getBlock(key) can legitimately return null after a prior render removed or replaced the referenced block, preventing renderer crashes like Cannot read properties of null (reading 'text').
Changes:
- Add null-guards in
checkNeedRenderandclickHandlerto avoid dereferencing missing blocks during render/format-picker logic. - Harden
deleteImageto clear stale image selection and hide related UI instead of reading.texton a missing block. - Make
selectionChangereturn an emptyaffiliationwhen cursor block lookups fail, avoiding downstream crashes for selection-based callers.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/muya/lib/contentState/updateCtrl.js | Early-return from checkNeedRender when start/end blocks can’t be found, preventing .text deref crashes. |
| src/muya/lib/contentState/clickCtrl.js | Guard format-picker condition against block being null when resolving selection keys. |
| src/muya/lib/contentState/imageCtrl.js | Handle missing image block by clearing selectedImage and closing toolbars/transformer safely. |
| src/muya/lib/contentState/paragraphCtrl.js | Return { affiliation: [] } when selection blocks are missing, avoiding null.type crashes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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.
Summary
The Muya editor's click / keyboard / backspace handlers re-dereference cursor state that can outlive the render which removed the block it points to (code-block reload, image figure swap, async rerenders, etc.).
getBlock()then returnsnulland the renderer crashes withCannot read properties of null (reading 'text').This PR adds null guards at four call sites — three on the actual code paths reported in the issue tracker, plus one same-family hardening in
selectionChangeto protectgetTOC-style callers.checkNeedRender(updateCtrl.js): bail out when start/end block is missingclickHandler(clickCtrl.js): guard the format-picker branch on the same lookupdeleteImage(imageCtrl.js): drop the stale selection + close image toolbar instead of reading.texton nullselectionChange(paragraphCtrl.js): return an emptyaffiliationwhen blocks are missingIssues closed
checkNeedRenderfamily:deleteImagefamily:getTOC/selectionChangefamily (official code-path hardening):Related (already closed as duplicates)
Test plan
pnpm run typecheck— cleanpnpm exec eslinton the four touched files — cleanpnpm run test:unit— 523 tests pass (2 pre-existing suite-load failures unrelated to this change)🤖 Generated with Claude Code