Detail Bug Report
https://app.detail.dev/org_befd6425-a158-4e24-9d4d-1e5c08769515/bugs/bug_1790dd71-9a2b-4692-bedf-77a88bd6cf18
Introduced in #141 by @WilliamAGH on Jul 28, 2026
Summary
- Context:
parseMarkdown in frontend/src/lib/services/markdown.ts preprocesses streamed chat markdown via prepareMarkdownForParsing, which calls nestNumericListFences (in frontend/src/lib/services/numericListFenceNesting.ts) to re-indent fenced code blocks so they stay attached to numeric ordered-list continuations before marked lexes them.
- Bug:
nestNumericListFences computes a single fixed bodyIndentation prefix of max(0, continuation - openingFenceMarkerIndex). When the streamed fence rails are already indented to (or beyond) the list-item continuation column but the literal code body is written at a lower column (e.g. body at column 0 under rails at column 3), bodyIndentation collapses to 0 and the body lines are emitted unchanged.
- Actual vs. expected: The un-indented body lines fall below the list-item continuation indent, so
marked terminates the list item mid-fence: the fenced code block renders empty, the code body leaks out as a loose paragraph, and the following numbered step (e.g. 2. Step two) is rendered as an indented code block instead of an <ol><li>.
- Impact: User-facing markdown corruption: nested code blocks inside numbered steps can render empty, code leaks into plain paragraphs, and subsequent numbered steps are no longer parsed as list items.
Code with Bug
In frontend/src/lib/services/numericListFenceNesting.ts:
if (fenceCandidate) {
bodyIndentation = " ".repeat(
Math.max(0, awaitedContinuationIndentation - fenceCandidate.markerIndex), // <-- BUG 🔴 collapses to 0 when fence rails are already at/beyond continuation column
);
targetIndentation = awaitedContinuationIndentation;
maximumSourceIndentation =
awaitedContinuationIndentation + COMMONMARK_MAX_FENCE_INDENTATION;
fenceState.open(fenceCandidate.marker);
awaitedContinuationIndentation = null;
nestedLines.push(
`${" ".repeat(targetIndentation)}${markdownLine.slice(fenceCandidate.markerIndex)}`,
);
continue;
}
} else {
nestedLines.push(`${bodyIndentation}${markdownLine}`); // <-- BUG 🔴 under-indented body lines pass through and drop below list continuation indent
}
Explanation
The preprocessor only computes one bodyIndentation value based on the opening fence’s column. If the rails are already indented to the list continuation column (e.g. 3 spaces under 1.), bodyIndentation becomes 0. Any code body line with fewer leading spaces than the continuation indentation then remains under-indented, causing marked to close the list item before the body. Result: an empty <pre><code> in the list item, the body rendered outside the list, and subsequent numbered items parsed incorrectly.
Codebase Inconsistency
Existing tests already “rescue” a flush-left body when the opening fence is at column 0 (because bodyIndentation = max(0, 3 - 0) = 3), but the identical flush-left body is not rescued when the opening fence is at the continuation column (because bodyIndentation = max(0, 3 - 3) = 0). This asymmetry indicates an incomplete indentation calculation rather than an intentional refusal to handle such input.
Recommended Fix
Re-indent each fence body line based on its own leading spaces so it lands at (at least) targetIndentation, rather than relying on a single fixed prefix derived from the opening fence position:
const lineIndentation = leadingSpaceCount(markdownLine);
const padding = Math.max(0, targetIndentation - lineIndentation);
nestedLines.push(`${" ".repeat(padding)}${markdownLine}`); // <-- FIX 🟢 lifts under-indented body lines into the list item
History
This bug was introduced in commit e57f73b, which added nestNumericListFences and the bodyIndentation = max(0, continuation - markerIndex) calculation. Later commits preserved this calculation.
Detail Bug Report
https://app.detail.dev/org_befd6425-a158-4e24-9d4d-1e5c08769515/bugs/bug_1790dd71-9a2b-4692-bedf-77a88bd6cf18
Introduced in #141 by @WilliamAGH on Jul 28, 2026
Summary
parseMarkdowninfrontend/src/lib/services/markdown.tspreprocesses streamed chat markdown viaprepareMarkdownForParsing, which callsnestNumericListFences(infrontend/src/lib/services/numericListFenceNesting.ts) to re-indent fenced code blocks so they stay attached to numeric ordered-list continuations beforemarkedlexes them.nestNumericListFencescomputes a single fixedbodyIndentationprefix ofmax(0, continuation - openingFenceMarkerIndex). When the streamed fence rails are already indented to (or beyond) the list-item continuation column but the literal code body is written at a lower column (e.g. body at column 0 under rails at column 3),bodyIndentationcollapses to0and the body lines are emitted unchanged.markedterminates the list item mid-fence: the fenced code block renders empty, the code body leaks out as a loose paragraph, and the following numbered step (e.g.2. Step two) is rendered as an indented code block instead of an<ol><li>.Code with Bug
In
frontend/src/lib/services/numericListFenceNesting.ts:Explanation
The preprocessor only computes one
bodyIndentationvalue based on the opening fence’s column. If the rails are already indented to the list continuation column (e.g. 3 spaces under1.),bodyIndentationbecomes 0. Any code body line with fewer leading spaces than the continuation indentation then remains under-indented, causingmarkedto close the list item before the body. Result: an empty<pre><code>in the list item, the body rendered outside the list, and subsequent numbered items parsed incorrectly.Codebase Inconsistency
Existing tests already “rescue” a flush-left body when the opening fence is at column 0 (because
bodyIndentation = max(0, 3 - 0) = 3), but the identical flush-left body is not rescued when the opening fence is at the continuation column (becausebodyIndentation = max(0, 3 - 3) = 0). This asymmetry indicates an incomplete indentation calculation rather than an intentional refusal to handle such input.Recommended Fix
Re-indent each fence body line based on its own leading spaces so it lands at (at least)
targetIndentation, rather than relying on a single fixed prefix derived from the opening fence position:History
This bug was introduced in commit e57f73b, which added
nestNumericListFencesand thebodyIndentation = max(0, continuation - markerIndex)calculation. Later commits preserved this calculation.