Repository navigation
feat(muya): add runtime setOptions / setFont / setTabSize / setListIndentation - #4393
Conversation
…dentation Every desktop Preferences toggle needs editor options to update live; the legacy engine exposed setOptions(options, needRerender) and the setFont/ setTabSize/setListIndentation helpers, which @muyajs/core lacked. setOptions(options, forceRender) merges into muya.options, reflects the container-level ones (spellcheck attribute, quick-insert hint class), and — when forceRender is set — fully re-renders the document from its current state via ScrollPage.updateState so render-affecting options (superSubScript, footnote, disableHtml, frontmatterType, codeBlockLineNumbers, GitLab compatibility, …) take effect. Unlike setContent, this preserves the undo history; the cursor is captured and restored by path across the re-render. setFont/setTabSize update their options; setListIndentation routes through setOptions with a re-render. Adds happy-dom coverage. Part of the muyajs -> @muyajs/core engine migration (stage A4). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a runtime option-update API to packages/muya’s Muya class to support the ongoing muyajs → @muyajs/core engine migration, enabling desktop preference toggles (and related theme/language flows) to take effect live without recreating the editor.
Changes:
- Introduces
Muya#setOptions(options, forceRender)to merge options at runtime, reflect container-level attributes/classes, and optionally force a full re-render from current state. - Adds convenience runtime setters:
setFont,setTabSize, andsetListIndentation(the latter forces a re-render). - Adds a new happy-dom Vitest suite covering the new runtime option APIs.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const selection = this.editor.selection.getSelection(); | ||
| this.editor.scrollPage?.updateState(this.getState()); | ||
| if (selection) { | ||
| this.editor.selection.setSelection({ | ||
| anchor: selection.anchor, | ||
| focus: selection.focus, | ||
| anchorPath: selection.anchorPath, | ||
| focusPath: selection.focusPath, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Fixed — the path-only restore was indeed broken (Selection._setCursor needs a concrete block's domNode; a bare queryBlock result isn't a Node, so the instanceof Node check failed and the caret was never set). Now resolves the block at the saved anchorPath on the rebuilt tree and calls block.setCursor(begin, end, true) directly, mirroring Editor.updateContents' same-block restore.
There was a problem hiding this comment.
Right — undo() on an empty stack is a trivial no-op. Replaced it: the test now makes an edit (insertParagraph), asserts history.canUndo() is true, then asserts it's STILL true after setOptions(..., true). Note updateState uses the 'api' source (no json-change dispatch), so a forced re-render neither clears history (unlike setContent) nor pollutes it — verified by canUndo staying true.
Address Copilot review: - Cursor restore after forceRender was broken: passing only paths to Selection.setSelection doesn't work (_setCursor needs a concrete block's domNode; a bare queryBlock result is not a Node). Resolve the block at the saved path on the rebuilt tree and call block.setCursor directly, mirroring Editor.updateContents' same-block restore. - The history test was trivial (undo() on an empty stack never throws). Assert history.canUndo() stays true across a forceRender re-render instead — and documents that updateState uses the 'api' source so it neither clears nor pollutes history. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Background
Part of the muyajs → @muyajs/core engine migration. Every desktop
Preferences toggle (and the language-changed / theme paths) relies on editor
options updating live without recreating the editor. The legacy engine
exposed
setOptions(options, needRerender)plussetFont/setTabSize/setListIndentation;@muyajs/corehad none — option changes were a no-opafter construction.
What this adds (on
Muya)setOptions(options, forceRender = false)— mergesoptionsintomuya.options; reflects the container-level ones immediately (thespellcheckattribute, the quick-insert-hint class); and, whenforceRenderis set, fully re-renders the document from its current statevia
ScrollPage.updateStateso render-affecting options take effect(superSubScript, footnote, disableHtml, frontmatterType,
codeBlockLineNumbers, GitLab compatibility, …). The desktop already passes
forceRender: truefor exactly those.setContent, which clears it), andthe cursor is captured via
getSelection()and restored by path across there-render.
setFont({ fontSize, lineHeight }),setTabSize(n)— update thecorresponding options.
setListIndentation(n)— routes throughsetOptions(..., true)so thelayout updates.
The methods are inserted right after
setContent()to stay clear of otherin-flight muya.ts additions.
Tests
New happy-dom spec
src/__tests__/setOptions.spec.ts(5 cases): option merge;forceRenderpreserves document content and leaves undo history intact;spellcheck attribute reflection; setFont/setTabSize; setListIndentation.
Verification
pnpm -C packages/muya lint→ 0 errorspnpm -C packages/muya lint:types✓pnpm -C packages/muya check-circular✓pnpm -C packages/muya test→ 440 passed (+5)pnpm -C packages/muya test:spec→ 1347 passed (conformance unchanged)🤖 Generated with Claude Code