Document 12 of 14 · spikes/render/README.md

Render proof (2026-09-16)

A feasibility check for the riskiest claims in ARCHITECTURE.md, run before any template was built. Not the product; the invoice here is a stand-in with the runtime's four jobs cut down to sixty lines.

./fetch-fonts.sh          # the two OFL faces, cut to static instances
node render.mjs           # prints invoice.html to Letter and A4 through the DevTools protocol
python3 check.py invoice-letter.pdf invoice-a4.pdf

render.mjs drives headless Chromium the way the Cloudflare Browser Run endpoint does: navigate, wait for html[data-ready], then print with preferCSSPageSize and printBackground. No npm dependencies; Node 22's global WebSocket is the CDP client.

What it proved

Claim Result
One HTML file prints Letter and A4 by switching a class Letter 612 by 792 pt, A4 594.96 by 841.92 pt, from the same document. The @page size cannot be selected by a class, so the runtime swaps a <style id="page-size"> element with the @page rule; template.css carries only the default.
The renderer never captures fallback fonts data-ready is set only after document.fonts.ready; document.fonts.check confirmed both faces before the print.
The overflow guard measures real boxes Clean at the defaults; a 60-character client name flags client_name. It also flagged the note box at first, because the box was one line too short for its own default text. That is the guard doing its job on the designer.
Fonts embed in the PDF Only with static instances. With the variable TTFs Chromium wrote 24 Type 3 fonts (glyphs as outlines, no font program, 149 KB). With static instances cut by fonttools it wrote Type 0 fonts with FontFile2 streams and the file fell to 39 KB. Templates ship static instances per weight; the site shell may use the variable face on screen.

Fonts and PDFs are not committed; the script regenerates them.

All documents