Skip to content

Markdown rendering for .md files (in-house, zero dependencies) - #5

Merged
jkyberneees merged 9 commits into
mainfrom
loop/feature-20261029-1000-markdown-render
Oct 29, 2026
Merged

jkyberneees merged 9 commits into
mainfrom
loop/feature-20261029-1000-markdown-render

Conversation

@jkyberneees

Copy link
Copy Markdown
Contributor

What

Markdown files (.md, .markdown, case-insensitive) open as a formatted preview instead of highlighted source. View source in the preview (or Preview in source) flips between the two, using the same button and position as the SVG toggle. A palette command, Toggle Markdown Preview, does the same.

Why

Reading a README or a design doc as raw Markdown is slow. The constraint is that the product has zero runtime dependencies and treats repo content as untrusted, so the parser and renderer are written in this repo, and the output is built only with createElement and textContent.

How the expert panel shaped it

Four panelists reviewed the design before planning. Full record: DESIGN.md §14.

  • Interaction designer: reuse the asSource toggle; a line target (search hit, file:line, Go to line) forces source; Find in the preview switches to source; no outline or in-preview find in v1.
  • Application security engineer: DOM building only (no innerHTML); raw HTML shown as text; one safeHref allowlist; relative images only through the authenticated raw endpoint as blob URLs; the CSP is not loosened.
  • Systems/performance engineer: client-side only, no server change; linear parser with hard caps (nesting, nodes, table columns, line length); 1 MiB size gate with "Render anyway"; lexLine highlighting with no file-wide state.
  • Frontend architect and test lead: pure markdown.ts (no DOM), mdrender.ts, and MarkdownView mirroring CodeView; editor.ts and app.ts excluded from line coverage as in Round 4; a seeded fuzz test of the safety invariants.

AC to commit

AC Commit
AC-1 to AC-5 (detection, preview/source toggle, live update, tab.code guards) 2fe4682 feat(markdown): render Markdown files as a preview with a source toggle
(follow-up: leave the preview when a tab turns binary or a directory) c641ab8 fix(markdown): leave the preview when a Markdown tab turns binary or a directory
AC-6 to AC-10 (CommonMark block and inline core, hard limits) ba843e6 feat(markdown): CommonMark block and inline core with hard limits
AC-11 to AC-14 (safe links, repo-relative navigation, fragments, autolinks) 43b6609 feat(markdown): safe links, repo-relative navigation and heading fragments
AC-15 to AC-17 (GFM tables, task lists, strikethrough) 7b12127 feat(markdown): GFM tables, task lists and strikethrough
AC-18, AC-19 (highlighted fences, visible hidden characters) c8766c3 feat(markdown): highlighted fenced code and visible hidden characters
AC-20 to AC-24 (images, size gate, palette command, fuzz safety, docs) 8dccd33 feat(markdown): images, size gate, palette command and docs
(follow-up: Preview toggle renders a file over the size gate, AC-22) 6640dc7 fix(markdown): let the Preview toggle render a file over the size gate

Commits are on this branch since 04780fb (release 1.4.0).

How to test

bun scripts/build.ts --client-only && bun test
  • Unit tests cover the parser, URL and path helpers, the renderer, the view, images, the palette command, and a seeded fuzz test that checks the safety invariants (no script, iframe, object or style tags, no on* or style attributes, no id without the md- prefix, no disallowed href or src schemes).
  • Adversarial inputs (200k *, 100k [, 1 MB single line, and similar) must finish under a time bound, and doubling the input must not triple the time.
  • Coverage on changed lines is 99.1% (diff coverage). src/client/app.ts and src/client/editor.ts are not measured, as in earlier rounds.
  • Manual: open a .md file, flip the toggle, edit the file on disk (scroll and mode should hold), click a relative link and a #heading link, and open a file over 1 MiB.

Assumptions

  • A-1: client-side rendering only; no server or endpoint changes.
  • A-2: 1 MiB default preview cap (the lower of the panel's proposals).
  • A-3: editor.ts and app.ts cannot load under the fake DOM and are excluded from coverage; their logic is kept thin.
  • A-4: small additions to test/helpers/fakedom.ts live under the test path.
  • A-5: the preview mode is per tab and is not persisted in serialize().
  • A-6: allowed image extensions match the server's imageMime table.
  • A-7: lexLine and languageFor in highlight.ts are reused unchanged.
  • A-8: out of scope for v1: raw HTML passthrough, footnotes, math, diagrams, front matter, outline, find inside the preview, remote image consent, chunked rendering.

Known minors

  • R-01 (review): a link to /api/files?x resolves as the repo path api/files and shows as a repo link. Harmless: it only calls stat or the open callback.
  • A few changed lines in markdown.ts, mdimage.ts, mdrender.ts and mdview.ts have no test (listed in last-check.json). Diff coverage is 99.1%, above the 80% floor.
  • Deferred by the panel: copy button on fences, link hover in the status bar, and persisting the mode across restarts.

🤖 Generated with Claude Code

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Failed ❌

View logs ↗
a02ffda 2026-10-29T10:21:37.641Z View logs ↗
  • Build: Failed ❌

View logs ↗
6640dc7 2026-10-29T10:13:36.584Z View logs ↗

Markdown rendering: .md and .markdown files open as a formatted preview with a source toggle, written in-house with no dependencies. CommonMark core plus GFM tables, task lists, strikethrough and autolinks; highlighted fenced code; repo-relative links, heading fragments and local images. Output is built with createElement/textContent only, raw HTML is shown as text, link schemes are allowlisted, remote images never load, and the parser is linear with hard limits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jkyberneees
jkyberneees merged commit 4117609 into main Oct 29, 2026
4 of 5 checks passed
@jkyberneees
jkyberneees deleted the loop/feature-20261029-1000-markdown-render branch October 29, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant