Skip to content

feat: Markdown support — rendered for humans at /d/:id, byte-exact source at /raw - #18

Merged
Hitesh-Sisara merged 1 commit into
mainfrom
feat/markdown-support
Aug 7, 2026
Merged

Hitesh-Sisara merged 1 commit into
mainfrom
feat/markdown-support

Conversation

@Hitesh-Sisara

Copy link
Copy Markdown
Contributor

Agents write plans in Markdown far more often than HTML, but a reviewer wants to open a URL and READ it. A .md upload is now rendered to a self-contained HTML document at /d/:id, while the exact uploaded bytes stay retrievable at /d/:id/raw as text/markdown — so the byte-for-byte promise holds for Markdown too.

Design

  • Accepts markdown, or html+format:"md", or infers from a .md filename, so the CLI just sends file bytes and lets the server decide.
  • Rendered once at upload into a companion R2 object. Rendering per-request would let a marked upgrade silently change an already-published document.
  • Rendering is not sanitising. Markdown permits raw HTML passthrough, so the RENDERED output goes through the same authoritative validator as a direct HTML upload.
  • Hash/dedup key on the SOURCE bytes, not the rendered form.

Two defects found while verifying

  1. GFM task lists emit <input type=checkbox> — a form control the policy blocks — so an ordinary agent checklist was rejected by our own validator. The validator was right; the renderer now substitutes inert ballot glyphs.
  2. Those items showed BOTH a bullet and a ballot box. Confirmed in the DOM (computed list-style-type: disc), fixed with li:has(> .task-box).

Vision-QA honesty note

The aux vision model claimed "unstyled native checkboxes with a green tick" and "inline code unstyled" — both false. The DOM showed 0 <input> and 2 .task-box spans, and the test document had no backticks at all. Only the duplicate-marker finding was real. Every claim was gated on a DOM read before acting.

How tested

  • 198/198 brutal-suite assertions (19 new Markdown ones)
  • <script>, onerror, <iframe>, javascript: links inside .md all rejected 422 with correct codes
  • CLI .md upload → rendered <h1> in browser + /raw byte-for-byte MATCH
  • HTML uploads unaffected: /d and /raw still identical

…urce at /raw

Agents write plans in Markdown far more often than HTML, but a reviewer wants to
open a URL and READ it, not stare at raw asterisks. A .md upload is now rendered
to a self-contained HTML document served at /d/:id, while the exact uploaded bytes
stay retrievable at /d/:id/raw as text/markdown — so the byte-for-byte promise
holds for Markdown too.

- Accepts `markdown`, or `html`+format:"md", or infers from a .md/.markdown/.mdown
  /.mkd filename, so the CLI can just send file bytes and let the server decide.
- Rendered ONCE at upload into a companion R2 object (.rendered.html), never per
  request: rendering at request time would let a `marked` upgrade silently change
  an already-published document.
- Rendering is NOT sanitising. Markdown permits raw HTML passthrough, so the
  RENDERED output goes through the same authoritative validator as a direct HTML
  upload — verified: <script>, onerror, <iframe> and javascript: links in .md all
  rejected 422 with the correct error codes.
- Hash/size/dedup key on the SOURCE bytes, not the rendered form, so a source edit
  that happens to render identically is not mistaken for a duplicate.
- source_format persisted (migration 0002, defaults 'html' so every existing
  version keeps its behaviour); exposed as X-AgentDraft-Format and in the response.
- Self-contained document CSS (no remote fonts/CSS — the content-origin CSP forbids
  it and an offline-readable document is the point).

Two defects found and fixed during verification:
1. GFM task lists render as <input type="checkbox"> by default — a FORM CONTROL the
   policy blocks, so an ordinary agent checklist was rejected by our own validator.
   The validator was right; the fix belongs in the renderer, which now substitutes
   inert Unicode ballot glyphs. Guarded by a test asserting no <input> is emitted.
2. Those glyph items showed BOTH a bullet disc and a ballot box. Confirmed in the
   DOM (computed list-style-type was "disc"), fixed with li:has(> .task-box).

Note on visual QA: the aux vision model reported "unstyled native checkboxes with a
green tick" and "inline code unstyled" — both FALSE. The DOM showed 0 <input>
elements and 2 .task-box spans, and the test document contained no backticks at
all. Only the duplicate-marker finding was real. Every claim was gated on a
browser_console DOM read before acting.

Verified live: 198/198 brutal-suite assertions (19 new Markdown ones), CLI .md
upload -> rendered h1 in browser + /raw byte-for-byte MATCH, HTML uploads
completely unaffected (/d and /raw still identical).
@Hitesh-Sisara
Hitesh-Sisara merged commit ad59295 into main Aug 7, 2026
4 checks passed
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