Markdown Preview silently truncates documents: numbered list with 4-space-indented bullets throws uncaught "RangeError: Invalid content for node listItem"

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

Cursor version: 3.17.19 (commit ae3a2b7231dd)
OS: macOS (Darwin 25.6.0)

Switching a markdown file to Preview mode silently drops everything from a section onward — the document just ends early, with no visible error in the UI. The same file renders correctly on GitHub and in the classic VS Code markdown preview.

The trigger is a numbered list item whose nested bullets are indented 4 spaces and are followed by a blank line and more indented content. Developer Tools console shows an uncaught exception:

Uncaught RangeError: Invalid content for node listItem: <"First bullet with some `
    at checkContent (workbench.desktop.main.js:471)
    at check (workbench.desktop.main.js:470)
    ...

Note the raw backtick in the message — the text reaches ProseMirror without inline parsing.

Root cause (from debugging the minified bundle) — two defects combine:

  1. The custom ordered-list re-tokenizer strips a hard-coded 2 spaces from continuation lines (the markdown-to-editor conversion in workbench.desktop.main.js re-lexes numbered list item bodies, slicing marker indent + 2 characters off each continuation line regardless of the actual content indentation). With standard 4-space-indented child content, 2 stray spaces survive, so when the item body is re-lexed with marked, bullets 2..n and any following paragraphs/fences are absorbed into the first bullet, making it a loose item with mixed text and paragraph tokens.

  2. The listItem converter emits bare text nodes for that mix. When a marked list_item token contains both text and paragraph children, the converter passes all children through generic conversion, and the leading text token becomes a bare inline text node (raw, not inline-parsed) directly inside a listItem node whose schema is paragraph block*. ProseMirror’s validation throws the RangeError, the exception is uncaught, and the rest of the document never renders.

The same mechanism fires for other shapes, e.g. a fenced code block indented inside a bullet that is itself inside a numbered item (item tokens text, code, text).

Impact: silent truncation — no error surfaces to the user, so documents look complete when they aren’t. 4-space indentation under numbered items is extremely common (it’s what many formatters and CommonMark tutorials produce) and renders identically to 3-space on GitHub, so affected users have no reason to suspect their markdown.

Workaround: indent nested bullets under numbered list items by 3 spaces instead of 4, and keep fenced code blocks at the numbered-item level rather than nested inside bullets.

Suggested fixes:

  • Strip the actual measured content indentation (not a hard-coded 2) when re-lexing ordered list item bodies.
  • In the listItem converter, wrap any bare text token in a paragraph before inserting into listItem (schema paragraph block*).
  • Catch conversion/validation errors and render a fallback (or surface an error) instead of silently truncating the document.

Steps to Reproduce

  1. Create repro.md with exactly this content (bullet indentation is 4 spaces — that detail matters):

    # Repro
    
    1. **Step one**: this list item breaks Preview:
        - First bullet with some `inline code` in it.
        - Second bullet.
    
        A follow-up paragraph inside the numbered item.
    
    ## You should see this heading
    
    But in Preview, everything from the section containing the list disappears.
    
  2. Open the file in Cursor and switch to Preview mode.

  3. Observe the document is truncated; open Developer Tools (Help → Toggle Developer Tools) to see the uncaught RangeError.

Expected Behavior

The whole document renders in Preview mode, including the numbered list, its nested bullets, and all following sections — matching how GitHub and VS Code’s webview markdown preview render the identical file. If conversion of a node fails, the error should surface to the user rather than silently truncating the document.

Operating System

MacOS

Version Information

3.17.19 (commit ae3a2b7231dd)

Does this stop you from using Cursor

No - Cursor works, but with this issue

Hey @Andrew_Austin, thanks for the detailed report! This matches a known issue with the built-in Preview renderer that we’re already tracking, and I’ve added your report to it.

In the meantime your workaround is right: 3-space indentation under numbered items avoids it, and the classic preview (Cmd+Shift+V, “Markdown: Open Preview”) renders these files correctly if you’d rather not reformat your documents.

Adding three findings from a day spent bisecting this with ~30 probe files. @Andrew_Austin’s bundle-level analysis is the mechanism and I can’t improve on it — these are empirical, and the first one means the recommended workaround isn’t sufficient.

1. It isn’t only 4-space indentation under numbered items, and 3-space indentation doesn’t avoid it

A verified pair differing in exactly one respect. A renders fully (100 items, 20 KB):

- **Item 001.** Filler prose to give this item a realistic body length...

- **Item 002.** Filler prose to give this item a realistic body length...

B truncates partway through item 020 (50 items, 21 KB):

- **Item 001.** Filler prose to give this item a realistic body length...

  Second paragraph of item 001. Filler prose to give this item a realistic body length...

- **Item 002.** Filler prose to give this item a realistic body length...

Plain - bullets, continuation indented 2 spaces (the correct alignment), no nested bullets, no fenced blocks, no numbered items, nothing over-indented. B still truncates.

So the trigger generalizes to any list item holding more than one block — a second paragraph, a nested list, a blockquote, a table, or a fence. A bullet with sub-bullets is paragraph + list, so that shape qualifies too. “Indent nested bullets 3 spaces under numbered items” will leave a lot of documents still silently truncating, so I would frame the user-facing workaround as one block per list item: fold the item into a single paragraph, or move its rich content into a subsection and leave a link in the item.

2. Whether it truncates also depends on document length, which is why minimal repros are unreliable here

Two conditions have to coincide: an offending item, and enough length for a chunk boundary to land inside one. Neither alone does it — a 14-line file containing an offending item renders fine, and a 107 KB file with no offending item renders to its last line. The cut in my synthetic ladder landed at ~8.2 KB; in real documents it landed past 10 KB. That is consistent with the chunked insertContentAt calls in the stack trace: a boundary falling inside an offending item is what produces the invalid fragment, not the item’s mere presence.

I think this is also the answer to @Jens_Emmerich over on 163908, who found that shortening his file made the bug disappear while the structure stayed identical — the structure was not what changed, the boundary moved. Two practical consequences:

  • “It rendered last time” is worthless as evidence. I watched an unmodified file render fully, then truncate after a mode switch, because an edit elsewhere had moved a boundary.
  • Failing to reproduce on a minimal file means nothing. Repros need to be ~10 KB+, which is awkward in a bug report and is probably why the shape family has looked narrower than it is.

3. Editing inside the dropped region silently discards the edit

This is the part I would flag as severity-raising, and I do not see it in either thread. On any truncating file:

  1. Toggle Preview. The document ends early — no error, no visual seam, no scrollbar hint.
  2. Type into the last visible paragraph. The text appears normally.
  3. Cmd-S. The save is accepted: no warning, no leftover dirty indicator.
  4. Toggle to Markdown, or switch files and back. The edit is gone.

cmp confirms the file on disk is byte-for-byte identical, so this is not the serializer-rewrites-your-file family in 153590 / 155435 / 162540 — nothing is corrupted on disk. The editor accepts keystrokes into a region the failed insert never loaded, then throws them away, while presenting an entirely normal save.

Which is why I would second Andrew’s third suggested fix independently of the other two: catching the conversion error and surfacing “Preview could not render the rest of this document” would prevent the data loss outright, not just improve the diagnostics. It would also save people the afternoon I spent, because every cross-check available to a user says the document is fine — and it is. The markdown is valid, markdown-it / marked / micromark all parse a truncating file without complaint, and Cmd+Shift+V renders it perfectly. In my case a whole section of a working document had been invisible for days with no indication anything was missing.

Finding the offending items across an existing document set

If anyone needs to de-risk a pile of docs before the fix lands, this flags a conservative superset — any list item with more than one block child:

// npm i markdown-it && node find-offenders.js FILE...
const fs = require("fs"), md = require("markdown-it")({ html: true });
for (const f of process.argv.slice(2)) {
  const t = md.parse(fs.readFileSync(f, "utf8"), {});
  for (let i = 0; i < t.length; i++) {
    if (t[i].type !== "list_item_open") continue;
    let depth = 0; const blocks = [];
    for (let j = i + 1; j < t.length; j++) {
      if (t[j].type === "list_item_open") depth++;
      if (t[j].type === "list_item_close") { if (!depth) break; depth--; }
      if (!depth && t[j].level === t[i].level + 1) {
        if (/_open$/.test(t[j].type)) blocks.push(t[j].type.replace(/_open$/, ""));
        else if (["fence", "hr", "html_block", "code_block"].includes(t[j].type)) blocks.push(t[j].type);
      }
    }
    if (blocks.length > 1) console.log(`${f}:${t[i].map[0] + 1}  ${blocks.join(" + ")}`);
  }
}

One gotcha if you adapt it: fence / hr / html_block / code_block are self-closing tokens with no _open, so a version matching only *_open misses a fenced block inside a bullet and reports the file clean.

Version Information

Version: 3.17.21
VS Code Extension API: 1.128.0
Commit: 8f2a112cb2845a97b75fd932ea5c470579ca4060
Date: 2026-08-25T01:05:08.089Z
Build Type: Stable
OS: Darwin arm64 25.5.0 (macOS, Apple Silicon)