Fix markdown rendering issues and add lint:md to CI (#73) - #74
Conversation
DESIGN.md had Unicode subscripts/superscripts in code blocks and tables that don't render reliably in monospace fonts. These slipped through because lint-md.mjs only checked inside $$...$$ and ```mermaid blocks, and the check script didn't include lint:md at all. Fixes: - Replace Unicode subscripts/arrows/operators in pseudocode block with ASCII equivalents (v₀ -> v[0], ← -> <-, ≠ -> !=, etc.) - Replace Unicode subscripts in key-layout table with inline LaTeX - Replace Q² with Q2 in table header - Fix same class of issues in RESULTS.md code blocks - Expand lint-md.mjs with Rule 5: scan fenced code blocks for Unicode subscripts, superscripts, math arrows, and math operators - Add lint:md to the check script so CI catches markdown issues https://claude.ai/code/session_017b5kMJ5gECv63yLCHJ3SXP
There was a problem hiding this comment.
Pull request overview
This PR aims to prevent Markdown rendering regressions (especially from Unicode glyphs that don’t render reliably) by normalizing problematic characters in docs and strengthening Markdown linting so CI can catch future issues.
Changes:
- Replaced fragile Unicode math/subscript glyphs with ASCII (and inline LaTeX for subscripts in tables) in
DESIGN.mdandRESULTS.md. - Extended
scripts/lint-md.mjswith a new rule to detect problematic Unicode ranges inside fenced code blocks. - Updated
bun run checkto includelint:md, so Markdown lint runs in CI.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| scripts/lint-md.mjs | Adds fenced-code-block Unicode scanning (Rule 5) and updates linter scope description. |
| package.json | Runs lint:md as part of check (therefore in CI). |
| RESULTS.md | Normalizes Unicode math notation inside fenced blocks to ASCII equivalents. |
| DESIGN.md | Normalizes Unicode glyphs in pseudocode/table headers; uses inline LaTeX for subscripts in a table row. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let buf = []; | ||
| for (let i = 0; i < lines.length; i++) { | ||
| const trimmed = lines[i].trim(); | ||
| if (/^(`{3,}|~{3,})/.test(trimmed)) { | ||
| if (!inside) { | ||
| inside = true; | ||
| isMermaid = trimmed === '```mermaid'; | ||
| buf = []; | ||
| } else { | ||
| if (!isMermaid) blocks.push(buf); | ||
| inside = false; | ||
| } | ||
| } else if (inside) { | ||
| buf.push({ text: lines[i], lineNo: i + 1 }); |
There was a problem hiding this comment.
fencedCodeBlocks() treats any line starting with backticks/tildes as a close fence, without verifying it matches the opening fence character (``` vs ~~~) and length. This can mis-parse valid Markdown that uses different fence lengths/chars (or includes a shorter fence inside a longer fenced block), leading to missed or spurious lint violations. Track the opening fence marker (char + count) and only close when encountering the same marker with >= the opening length (optionally allowing trailing spaces).
| let buf = []; | |
| for (let i = 0; i < lines.length; i++) { | |
| const trimmed = lines[i].trim(); | |
| if (/^(`{3,}|~{3,})/.test(trimmed)) { | |
| if (!inside) { | |
| inside = true; | |
| isMermaid = trimmed === '```mermaid'; | |
| buf = []; | |
| } else { | |
| if (!isMermaid) blocks.push(buf); | |
| inside = false; | |
| } | |
| } else if (inside) { | |
| buf.push({ text: lines[i], lineNo: i + 1 }); | |
| let fenceChar = ''; | |
| let fenceLength = 0; | |
| let buf = []; | |
| for (let i = 0; i < lines.length; i++) { | |
| const line = lines[i]; | |
| const trimmed = line.trim(); | |
| if (!inside) { | |
| // Opening fence: capture marker and info string. | |
| const openMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/); | |
| if (openMatch) { | |
| inside = true; | |
| const marker = openMatch[1]; | |
| fenceChar = marker[0]; | |
| fenceLength = marker.length; | |
| const info = openMatch[2].trim(); | |
| // Treat as Mermaid if info string is "mermaid" or starts with "mermaid ". | |
| isMermaid = info === 'mermaid' || info.startsWith('mermaid '); | |
| buf = []; | |
| } | |
| } else { | |
| // Potential closing fence: must match opening char and have length >= opening. | |
| const closeMatch = trimmed.match(/^([`~]{3,})\s*$/); | |
| if (closeMatch) { | |
| const closeMarker = closeMatch[1]; | |
| if (closeMarker[0] === fenceChar && closeMarker.length >= fenceLength) { | |
| if (!isMermaid) blocks.push(buf); | |
| inside = false; | |
| isMermaid = false; | |
| fenceChar = ''; | |
| fenceLength = 0; | |
| continue; | |
| } | |
| } | |
| buf.push({ text: line, lineNo: i + 1 }); |
| @@ -11,7 +11,7 @@ | |||
| "lint": "eslint --no-inline-config --max-warnings 0 --fix src test --ext .js,.ts,.html,.yml,.yaml && eslint --no-inline-config --max-warnings 0 src test --ext .js,.ts,.html,.yml,.yaml", | |||
| "lint:css": "stylelint --max-warnings 0 --allow-empty-input --fix \"**/*.{css,html}\" && stylelint --max-warnings 0 --allow-empty-input \"**/*.{css,html}\"", | |||
| "lint:md": "bun scripts/lint-md.mjs", | |||
There was a problem hiding this comment.
Now that check runs lint:md in CI, note that scripts/lint-md.mjs (per its current default behavior) only lints *.md in the repo root. This means Markdown files under docs/ (and other subdirs) will still bypass CI linting unless they’re passed explicitly. Consider updating the linter’s default file discovery (or the lint:md script) to include all tracked Markdown files (e.g., recursive walk or git ls-files '*.md').
| "lint:md": "bun scripts/lint-md.mjs", | |
| "lint:md": "bun scripts/lint-md.mjs $(git ls-files \"*.md\")", |
| /** | ||
| * lint-md.mjs — Lints Markdown files for encoding issues that break GitHub | ||
| * rendering of KaTeX math and Mermaid diagrams. | ||
| * rendering of KaTeX math, Mermaid diagrams, code blocks, and tables. |
There was a problem hiding this comment.
The file header now says this linter covers “code blocks, and tables”, but the implemented rules only scan display-math ($$...$$), Mermaid fenced blocks, and non-Mermaid fenced code blocks. Unicode rendering issues inside Markdown tables (outside fenced blocks) still won’t be detected. Either adjust the description to match current behavior, or add a table/prose scanning rule if table linting is intended.
| * rendering of KaTeX math, Mermaid diagrams, code blocks, and tables. | |
| * rendering of KaTeX math, Mermaid diagrams, and code blocks. |
DESIGN.md had Unicode subscripts/superscripts in code blocks and tables that don't render reliably in monospace fonts. These slipped through because lint-md.mjs only checked inside$$...$$ and ```mermaid blocks, and the check script didn't include lint:md at all.
Fixes:
https://claude.ai/code/session_017b5kMJ5gECv63yLCHJ3SXP
Description
Related Issue
Closes #
Checklist
bun run check)bun run test)