Skip to content

fix: #4899 keep trailing content out of the child list when outdenting a list item - #4913

Merged
Jocs merged 1 commit into
marktext:developfrom
doktorigi:fix/4899-list-outdent-merge-crash
Sep 14, 2026
Merged

Jocs merged 1 commit into
marktext:developfrom
doktorigi:fix/4899-list-outdent-merge-crash

Conversation

@doktorigi

Copy link
Copy Markdown
Contributor

Description

Fixes #4899 — and very likely explains #4826/#4827, which were closed as unreproducible. Following the reporter's keystrokes in a Playwright run against the e2e host page reproduces the exact crash deterministically:

Error: Cannot use numerical key for object container
    at assert (ot-json1)
    at insertChildMut → drop → apply
    at JSONState._apply → JSONState._flushOperationCache

Root cause

Instrumenting JSONState._flushOperationCache during the repro shows the state is already schema-invalid one flush before the crash. The Shift+Tab outdent (_unindentListItem, UnindentType.INDENT) produced:

{ "name": "bullet-list", "children": [ { "name": "paragraph", "text": "c" } ] }

— a paragraph as a direct child of a list block, no list-item wrapper.

In packages/muya/src/block/content/paragraphContent/index.ts, the INDENT branch handles two kinds of leftovers when outdenting an item:

  • listItem.next — remaining sibling list items, which correctly move into a freshly created nested child list; and
  • list.next — blocks that followed the nested list inside the outer item (paragraphs etc.), which are the outdented item's own trailing content — but were appended into that same child list.

ot-json1 doesn't validate the schema, so the corrupt insert applies cleanly; the next edit (the reporter's final Backspace) then produces ops whose composed paths descend into a block object with a numeric array index, which ot-json1 rejects — the deferred flush throws and the renderer reports the crash. That downstream-flush distance is presumably why #4827 was so hard to reproduce.

Fix

Append list.next blocks to the new list item itself, and only create the nested child list when there are sibling list items (listItem.next) to carry. For

- a
  - b
  c

outdenting b now yields c as trailing content of the new - b item (previously: a corrupt list). A side benefit: the outdent no longer leaves an empty bullet-list behind when there are no sibling items.

Tests

  • Unit (unindentTrailingContent.spec.ts, boots real Muya in happy-dom like the existing unindentReplacement.spec.ts): outdent with trailing paragraphs, and with both sibling items + trailing paragraphs; asserts every list child is a list item and the markdown keeps c under the outdented item. Both fail without the fix with bullet-list child must be a list item, got paragraph.
  • E2E (list-paste-outdent-4899.spec.ts): replays the reporter's exact keystrokes (paste a\n\nb\n\nc into a fresh list, - a line up, Shift+Tab, ↓, Backspace) and asserts no renderer errors and no content loss. Fails without the fix with the exact Cannot use numerical key for object container.

muya unit suite: 1439 passed (1 pre-existing unrelated failure in tableChessboard.spec.ts, fails on clean develop too). muya lint, lint:types, check-circular pass. e2e stability/typing/editing groups: no new failures (2 pre-existing env-dependent failures on this Windows machine, identical on clean develop).

🤖 Generated with Claude Code

niyo-oyin added a commit to niyo-oyin/marktext-ja that referenced this pull request Jul 29, 2026
- PR marktext#4946 (includes its base PR marktext#4942): removes the quadratic costs
  that froze large documents and mounts them progressively — a 2.1 MB
  document goes from an indefinite freeze to interactive in seconds.
  Applied verbatim; upstream review addressed 19 findings with 31
  regression tests, all passing here.
- PR marktext#4931 (marktext#4926): deleting at an offset inside a surrogate pair left
  a lone surrogate that crashed text-unicode ("Invalid offset - splits
  unicode bytes"). The content.ts hunk conflicted with our Phase 1
  isComposed guards and was integrated by hand; the new cursor snap
  additionally runs only when no IME composition is open, since moving
  the DOM selection mid-composition would detach the IME anchor.
- PR marktext#4913 (marktext#4899): Shift+Tab outdent with trailing sibling blocks
  corrupted the state tree (paragraphs appended directly into a list),
  crashing the next composed op. muya hunks only — the PR's legacy
  muyajs hunks target the removed engine.
- PR marktext#4911 skipped: the equivalent detached-block guard already exists
  in @muyajs/core's language selector (marktext#4654), verified in source.
- PR marktext#4930 (marktext#4921): CodeMirror instance wrapped in a Vue proxy broke
  source-code mode; markRaw fix applied cleanly.

muya suite 1515/1515, tsc/eslint/madge clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ef4yFuXFbyKNTazkwUtygY
niyo-oyin added a commit to niyo-oyin/marktext-ja that referenced this pull request Jul 29, 2026
- PR marktext#4946 (includes its base PR marktext#4942): removes the quadratic costs
  that froze large documents and mounts them progressively — a 2.1 MB
  document goes from an indefinite freeze to interactive in seconds.
  Applied verbatim; upstream review addressed 19 findings with 31
  regression tests, all passing here.
- PR marktext#4931 (marktext#4926): deleting at an offset inside a surrogate pair left
  a lone surrogate that crashed text-unicode ("Invalid offset - splits
  unicode bytes"). The content.ts hunk conflicted with our Phase 1
  isComposed guards and was integrated by hand; the new cursor snap
  additionally runs only when no IME composition is open, since moving
  the DOM selection mid-composition would detach the IME anchor.
- PR marktext#4913 (marktext#4899): Shift+Tab outdent with trailing sibling blocks
  corrupted the state tree (paragraphs appended directly into a list),
  crashing the next composed op. muya hunks only — the PR's legacy
  muyajs hunks target the removed engine.
- PR marktext#4911 skipped: the equivalent detached-block guard already exists
  in @muyajs/core's language selector (marktext#4654), verified in source.
- PR marktext#4930 (marktext#4921): CodeMirror instance wrapped in a Vue proxy broke
  source-code mode; markRaw fix applied cleanly.

muya suite 1515/1515, tsc/eslint/madge clean.
…utdenting

Shift+Tab on a nested list item whose list had trailing sibling blocks
appended those blocks (paragraphs) directly into the new nested child
list without a list-item wrapper. The state became schema-invalid, and
the next edit's composed op crashed ot-json1 with 'Cannot use numerical
key for object container'. Trailing blocks after the nested list are the
outdented item's own content — append them to the new list item itself,
and only create the child list for remaining sibling list items.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Jocs
Jocs force-pushed the fix/4899-list-outdent-merge-crash branch from edece74 to f8f5999 Compare September 14, 2026 06:01
@Jocs

Jocs commented Sep 14, 2026

Copy link
Copy Markdown
Member

Thanks @doktorigi — the outdent fix is correct. I rebased the branch onto develop (it had never run CI) and kept only the #4899 commit.

  • Kept: fix: #4899 keep trailing content out of the child list when outdenting. On current develop the new unit test fails with bullet-list child must be a list item, got paragraph and the e2e fails with Cannot use numerical key for object container; both pass with the fix. Shift+Tab over ten list shapes (trailing paragraph, blockquote or code block, sibling items, the outdented item's own sublist, ordered and task lists) went from invalid state in 10/10 and a crash in 4/10 to valid state with no crash, and undo still restores the document.
  • Dropped: fix: #4896 guard selectLanguage against a stale code-picker block. It patches the legacy packages/muyajs engine, which the 0.20 desktop app no longer loads, and its desktop test would become the only importer of muya/lib/contentState/codeBlockCtrl while we retire that package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Unexpected renderer process error: Cannot use numerical key for object container

2 participants