fix(site): read setext headings with long underlines

A setext underline long enough to use up the parse bound left no room
for its heading line, so the pair was not read as a heading and a later
heading became the title. A long underline reads the same as its first
three characters, so the pair is now parsed with the underline cut to
three.
This commit is contained in:
Michael Suchacz
2026-10-02 21:55:01 +00:00
parent 87530695f3
commit d4c65cfdf4
2 changed files with 16 additions and 5 deletions
@@ -248,6 +248,14 @@ describe("getThinkingDisclosureDisplay", () => {
});
});
it("uses a setext heading with an underline too long to parse", () => {
expect(
getThinkingDisclosureDisplay(`Title\n${"-".repeat(399)}\n\n## Next`, {
isStreaming: false,
}),
).toEqual({ title: "Thinking about title", body: "## Next" });
});
it.each([
// A stream can pause on the next item's marker, which looks like a
// setext underline.
@@ -238,14 +238,17 @@ const getFirstHeading = (text: string): HeadingMatch | undefined => {
}
if (setextCandidate && /^ {0,3}(=+|-+)[ \t]*$/.test(line)) {
// A line's start decides whether an underline makes it a heading, so
// a line too long to parse is cut to classify the pair.
// A line's start decides whether an underline makes it a heading, and
// a long underline reads the same as its first three characters, so
// both are cut to keep the pair within the parse bound.
const underline = line.trim().slice(0, 3);
const candidate = sliceAtGraphemeBoundary(
setextCandidate.line,
PARSED_SOURCE_MAX_LENGTH - line.length - 1,
PARSED_SOURCE_MAX_LENGTH - underline.length - 1,
);
const [block] =
parseHeadingCandidate(`${candidate}\n${line}`)?.children ?? [];
const [block] = markdownParser.parse(
`${candidate}\n${underline}`,
).children;
if (block?.type === "heading") {
if (candidate !== setextCandidate.line) {
return undefined;