Fix crash removing link mark when no link is active - #2802
Conversation
The link toolbar's onRemove handler called editor.removeMark()/
removeMarkAtRange() with the result of an unguarded Array.find(),
which is undefined whenever the selection has no active link mark.
Slate's Mark.create() silently defaults an undefined argument to {}
and passes it to Mark.fromJSON(), which throws "Mark.fromJS()
requires a type string." This is reached whenever a user opens the
Link popover and confirms an empty URL field without ever having
applied a link (Enter or the Add button triggers onConfirm, which
delegates to onRemove when the field is blank).
Guard onRemove the same way the neighboring removeMarksOfTypeInRange
helper already does: no-op when there's no active mark to remove.
|
Warning Indent Zero is shutting down on August 7th. Please migrate over to Indent 2.0 to continue getting PR reviews.
|
|
Summary
Fixes MAILSPRING-CLIENT-10 —
Error: Mark.fromJS requires a type string, thrown from deep inside Slate/slate-react(151 occurrences, 63 users impacted, ongoing since May 2026).Root cause
In
app/src/components/composer-editor/toolbar-component-factories.tsx,BuildMarkButtonWithValuePicker'sonRemovehandler looks up the currently active mark and unconditionally passes it to Slate:activeisundefinedwhenever there is no link mark on the current selection.onRemoveis called fromonConfirmany time the user confirms the link popover (presses Enter, or clicks "Add") with an empty URL field — a common path, since the Link button is usually clicked with a collapsed cursor to insert a brand-new link, and it's easy to press Enter/click Add before typing a URL.Slate's
Mark.create(properties = {})silently defaultsundefinedto{}, which passes its "is a plain object" check and reachesMark.fromJSON({}), wheretypeisundefined, throwingMark.fromJS() requires a type string.deep insideslate/slate-react, several frames away from the actual app-code bug — which is why the captured stack trace contains only library frames.The sibling helper
removeMarksOfTypeInRangein the same file already guards this exact case (if (active) { editor.removeMark(active); }), butonRemovewas missing the same check.Fix
Add the missing guard: if there's no active mark to remove, focus the editor and return instead of calling into Slate with
undefined.Test plan
./node_modules/.bin/tsc -p app/tsconfig.json --noEmit— passesnode_modules/.bin/eslint -c .eslintrc app/src/components/composer-editor/toolbar-component-factories.tsx— passesGenerated by Claude Code