Affected Packages
@tiptap/core, @tiptap/markdown, @tiptap/extension-list
Version(s)
3.23.2, 3.24.0
Bug Description
We have a local patch for this issue, which I ran into when opening a markdown note containing an LLM-generated task list, and which Claude Code then diagnosed, so please take this bug report with a grain of salt:
When a task list item has a wrapped (soft-continued) or nested line indented more than baseIndentSize (2) spaces past the marker, that content renders as an indented code block instead of joining the item's paragraph.
This is a very common authoring style — aligning the continuation under the checkbox text column (- [ ] is 6 columns wide), which is also what many LLMs emit. Plain bullet (-) lists are unaffected (they go through marked's native list tokenizer); only task items hit parseIndentedBlocks. CommonMark renderers (remark/react-markdown) handle the same input correctly, because an indented code block cannot interrupt a paragraph.
Input markdown:
- [ ] Agent tab renders the composer in all states (no session
empty transcript + composer).
Result: the second line (empty transcript + composer).) becomes a code block under the task item instead of being merged into the item's text.
Root cause — in @tiptap/core's parseIndentedBlocks (packages/core/src/utilities/markdown/parseIndentedBlocks.ts), nested content is dedented by a fixed amount:
const dedentedNested = nestedContent
.map(nestedLine => nestedLine.slice(indentLevel + baseIndentSize)) // Remove base indent + 2 spaces
.join("\n");
When the continuation is indented more than baseIndentSize past the marker, residual leading whitespace (≥4 spaces) survives the dedent, and the downstream marked.blockTokens(...) (or the recursive task parser's fallback) parses it as an indented code block.
Minimal, DOM-free repro of the root cause:
import { marked } from "marked";
// Continuation line collected by parseIndentedBlocks for the item above
// (indented to the checkbox text column = 6 spaces).
const nested = [" empty transcript + composer)."];
const indentLevel = 0; // from the item pattern match
const baseIndentSize = 2; // default
// CURRENT behavior: dedent by a FIXED indentLevel + baseIndentSize (=2) -> 4 spaces survive
const dedented = nested.map((l) => l.slice(indentLevel + baseIndentSize)).join("\n");
console.log(JSON.stringify(dedented)); // " empty transcript + composer)."
console.log(new marked.Lexer().blockTokens(dedented).map((t) => t.type));
// -> [ 'code' ] ❌ expected a paragraph
Suggested fix — dedent by the actual minimum indentation of the non-empty nested lines (preserving relative nesting), instead of a fixed indentLevel + baseIndentSize. The mixed bullet/task path in @tiptap/markdown already does exactly this (Math.min(...indents)), so this also removes an internal inconsistency:
const nestedIndents = nestedContent
.filter(nestedLine => nestedLine.trim())
.map(nestedLine => nestedLine.length - nestedLine.trimStart().length);
const nestedDedent = nestedIndents.length > 0
? Math.min(...nestedIndents)
: indentLevel + baseIndentSize;
const dedentedNested = nestedContent
.map(nestedLine => (nestedLine.trim() ? nestedLine.slice(nestedDedent) : ""))
.join("\n");
With this change the repro yields a paragraph, and genuinely-nested sub-lists/sub-tasks still dedent correctly (relative indentation preserved); fenced code blocks (```) are unaffected.
Related: #7220 / PR #7225 — same function (parseIndentedBlocks), different defect.
Browser Used
Chrome
Code Example URL
https://codesandbox.io/p/devbox/adoring-wright-kmqhlt
Expected Behavior
The wrapped/over-indented task-item continuation joins the item's paragraph (matching CommonMark and marked's native list handling), rather than rendering as an indented code block.
Additional Context (Optional)
No response
Dependency Updates
Affected Packages
@tiptap/core, @tiptap/markdown, @tiptap/extension-list
Version(s)
3.23.2, 3.24.0
Bug Description
We have a local patch for this issue, which I ran into when opening a markdown note containing an LLM-generated task list, and which Claude Code then diagnosed, so please take this bug report with a grain of salt:
When a task list item has a wrapped (soft-continued) or nested line indented more than
baseIndentSize(2) spaces past the marker, that content renders as an indented code block instead of joining the item's paragraph.This is a very common authoring style — aligning the continuation under the checkbox text column (
- [ ]is 6 columns wide), which is also what many LLMs emit. Plain bullet (-) lists are unaffected (they go throughmarked's native list tokenizer); only task items hitparseIndentedBlocks. CommonMark renderers (remark/react-markdown) handle the same input correctly, because an indented code block cannot interrupt a paragraph.Input markdown:
Result: the second line (
empty transcript + composer).) becomes a code block under the task item instead of being merged into the item's text.Root cause — in
@tiptap/core'sparseIndentedBlocks(packages/core/src/utilities/markdown/parseIndentedBlocks.ts), nested content is dedented by a fixed amount:When the continuation is indented more than
baseIndentSizepast the marker, residual leading whitespace (≥4 spaces) survives the dedent, and the downstreammarked.blockTokens(...)(or the recursive task parser's fallback) parses it as an indented code block.Minimal, DOM-free repro of the root cause:
Suggested fix — dedent by the actual minimum indentation of the non-empty nested lines (preserving relative nesting), instead of a fixed
indentLevel + baseIndentSize. The mixed bullet/task path in@tiptap/markdownalready does exactly this (Math.min(...indents)), so this also removes an internal inconsistency:With this change the repro yields a
paragraph, and genuinely-nested sub-lists/sub-tasks still dedent correctly (relative indentation preserved); fenced code blocks (```) are unaffected.Related: #7220 / PR #7225 — same function (
parseIndentedBlocks), different defect.Browser Used
Chrome
Code Example URL
https://codesandbox.io/p/devbox/adoring-wright-kmqhlt
Expected Behavior
The wrapped/over-indented task-item continuation joins the item's paragraph (matching CommonMark and marked's native list handling), rather than rendering as an indented code block.
Additional Context (Optional)
No response
Dependency Updates