diff --git a/docs/superpowers/plans/2026-10-07-source-citations.md b/docs/superpowers/plans/2026-10-07-source-citations.md index 6be9ae2..b2c6a3d 100644 --- a/docs/superpowers/plans/2026-10-07-source-citations.md +++ b/docs/superpowers/plans/2026-10-07-source-citations.md @@ -1056,18 +1056,26 @@ export function renderModelOutput(markdown: string, options: RenderOptions = {}) const sources = options.sources ?? []; const cited = new Set(); - // Citations run first. The pass produces a markdown link, and the maths and - // markup passes after it would only ever be looking at a marker, which is the - // one thing they now cannot misread. - const segments = splitSegments(repairCodeBlocks(markdown)).map((segment) => { + /** + * Maths first, then citations, then markup. + * + * The order is forced by one collision. A link label has to escape its brackets + * or the parser ends the link text early, and `[\[1\]]` is exactly the shape of + * LaTeX's own `\[...\]` display delimiter, which the maths pass would eat and + * hand back as a bare `1`. So the model's maths is converted before any marker + * becomes a link: the citation pass writes final form markdown, which is not + * input for anything but the markup pass, and that one only reads angle brackets. + */ + const segments = mathPass(splitSegments(repairCodeBlocks(markdown))).map((segment) => { if (segment.fence || sources.length === 0) return segment; const result = linkCitations(segment.lines.join("\n"), sources); for (const index of result.cited) cited.add(index); + return { fence: false, language: "", lines: result.text.split("\n") }; }); - return { text: flatten(htmlPass(mathPass(segments))), cited }; + return { text: flatten(htmlPass(segments)), cited }; } ```