I shipped Issue 01 of this newsletter as an HTML file. I didn't have a theory for it at the time. The alternatives felt worse. A Substack draft looked like every other Substack draft. A PDF attachment felt like a 2015 corporate memo. An email died in the inbox. So I asked Claude to write the issue as a self-contained HTML page, pushed it to Vercel, and shared the link.
This month I noticed I'd done the same thing three more times without thinking. A slide deck for a 20-minute internal talk on Tuesday. A PDF status report I sent up to a stakeholder on Thursday. A one-page explainer I wrote for myself on Friday to make sense of an auth flow I was about to extend. All HTML. Each one printed through a headless browser into the surface it actually needed to live in.
I never opened any of these files by hand. I asked Claude to write them, and then I asked Claude (or Codex, depending on which window I was in) to open them in Safari for me. The whole workflow stayed inside one chat. Drafting, previewing, exporting, sending. All of it without leaving the conversation, and without hunting through a Finder window for whichever file I'd just made.
The format kept winning these small decisions and I didn't catch it happening until I looked back at the week. HTML stopped being a thing I send. It became the source file I write, and the browser is the build step that compiles it into whatever output the moment needs.
The pipeline is two pieces
Claude writes a single HTML file. Self-contained, design tokens inlined, no framework, no build step. Then I open that file in a headless browser and tell the browser what to print it as.
# A PDF
chrome --headless --print-to-pdf=report.pdf report.html
# Same file, automated, with page size and margins
await page.pdf({ path: 'report.pdf', format: 'Letter', printBackground: true })
# A slide as a PNG, one per slide
await page.screenshot({ path: 'slide-03.png', fullPage: true })
That is the entire stack. No reveal.js, no MDX, no static site generator, no Figma export, no PowerPoint template. Puppeteer is the most common driver. Playwright works the same way. Even raw chrome --headless from the command line gets you 80% of the way without a Node process at all. The browser is the most capable rendering engine humans have ever built, and anything that knows how to drive it can produce anything the browser can show. The browser can show almost everything.
The small workflow detail that took me a week to internalize: I don't open the file myself after Claude writes it. I ask the same agent that wrote it to open it in Safari. Claude has a shell tool. Codex has a shell tool. open -a Safari ./status-w19.html is one line and it stays inside the chat. So the loop is: write, preview, edit, re-preview, print to PDF, attach to email, send. End to end without me switching applications.
One source. Four surfaces, depending on what the reader needs.
Six things I make this way
1. Slide decks for talks.
I gave a 20-minute talk last week to a 25-person internal audience. Two evenings before, I asked Claude to draft the deck as a single HTML file. Twelve slides, 16:9, light background because the room had bad projector contrast. The deck pulled tokens from the design-system.html I keep around so the type, accent color, and spacing matched what I already use for my newsletter. Presenter notes lived in HTML comments under each slide. I could see them in my editor while building; they vanished in the rendered output.
I previewed in Chrome at a 1280-by-720 viewport. Made four edits. Asked Claude to run chrome --headless --print-to-pdf=deck.pdf deck.html --no-pdf-header-footer. Opened the PDF in Keynote for the actual delivery, because Keynote handles presenter mode and the remote clicker better than the browser does.
Tool count for the talk: Claude, Chrome, Keynote. No template wrangling, no PowerPoint, no reveal.js boilerplate, no Figma. The thing that made this not feel like a hack was the design system reference. The slides looked like mine because they read the same file my newsletter reads. Two slides had charts. Three had diagrams. One had a side-by-side comparison card. All of it inline SVG and CSS grid, all of it printed crisply to PDF, none of it requiring a single template.
Draft a 12-slide deck as one HTML file. 16:9 per slide, dark background. Pull colors and type from ./design-system.html so it looks like my other work. Topic: agent-shaped products, expanding the doors thesis from my newsletter. Presenter notes in <!-- comments --> under each slide. Print-friendly so I can export to PDF.
2. PDF reports and one-pagers.
A senior stakeholder asked for a status report. The Markdown version of the same content felt like a developer's note: monospace fonts, raw bullet lists, no sense that anyone had thought about the document as a document. Notion felt like the inside of a meeting. Google Docs felt like every other Google Doc.
I wrote it as an HTML page sized for US Letter portrait. Proper page margins, a header with the team name and the date on every page, a small bar chart for the metric, a pull-quote on page 2 lifted from a subscriber reply. Puppeteer rendered it with page.pdf({format: 'Letter', printBackground: true, margin: {top: '0.6in', bottom: '0.6in', left: '0.7in', right: '0.7in'}}). I attached the PDF to my email.
The reply came back within four hours and answered the exact question I'd buried on page 2. The bar I cleared wasn't "good-looking PDF." It was "a document that signals someone took it seriously enough to make." I have not opened Google Docs since, for anything that goes up.
What shipped this week.
Issue 02 went live Tuesday morning. Subscribe rate doubled week over week after the share-button wiring landed. Two dark-mode regressions in the index page were pulled forward to next sprint.
Goals next week.
- Ship Issue 03 outline by Wednesday.
- Refresh the landing page index card.
- Decide on monthly vs biweekly cadence.
Subscriber notes.
Highest-engagement reply this week was on the doors framing. Three readers asked for the side-by-side I cut from the draft. Worth restoring in a follow-up.
Open decisions.
- LinkedIn cross-post: yes or no.
- Whether to add an RSS feed before Issue 03.
Render this status report as an HTML page sized for US Letter portrait, two pages with proper page breaks. Header on each page, page numbers in the footer. Pull typography from design-system.html. Include a bar chart for the weekly subscribe count and a pull-quote on page 2. Then call puppeteer.pdf() with format Letter and printBackground true. Write the result to ./out/status-w19.pdf.
3. Explainers I write for myself.
I was about to extend our Supabase auth callback. Reading the code top to bottom takes thirty minutes and I don't retain it; the second time I open the file, I'm starting over. So I asked Claude for a one-page HTML explainer with three constraints: an SVG sequence diagram showing browser, edge function, and database; the two or three lines from our codebase that handle each hop; three mistakes I was most likely to make trying to extend it.
I read it in three minutes and I kept it. The diagram is what my brain cached, not the prose. The "three mistakes" frame is doing more work than it looks like: it forces the model to commit to concrete failure modes I can map onto specific lines, instead of waving at theory in a paragraph.
- Forgetting to verify
code_verifiermatches PKCE on the second hop. - Setting the cookie on the wrong domain (callback subdomain vs app domain).
- Refreshing the session in the edge function but not propagating to the client.
Walk me through the Supabase auth callback flow. Make a sequence diagram in SVG showing browser, edge function, database. Pull in the two or three lines from our codebase that handle each hop. End with the three mistakes I'm most likely to make if I try to extend it. Single HTML file, design tokens from ./design-system.html.
4. Weekly status to my manager.
She opens the HTML version. She doesn't open the Markdown one. That alone justified the switch. The top-line takeaway sits in an accent box; what shipped, what slipped, and what I'm asking her to decide sit in three columns underneath. She reads it before standup, replies with one of the decisions before lunch, and the rest of the week routes itself around the gap. Total time to write: eight minutes from first prompt to attached link.
Shipped
- Issue 02 draft v1
- Index card + read time
- Substack + share wiring
Slipped
- Issue 01 screenshots
- Design system file
Asking for
- Monthly vs biweekly call
- LinkedIn cross-post yes/no
Write my weekly update as a single HTML page. Top-line takeaway in one sentence at the top, three sections for what shipped, what slipped, what I'm asking for. Pull tokens from design-system.html. Keep it scannable in under a minute.
5. Throwaway editors for one-off tasks.
Reordering thirty tickets. Tagging a hundred examples by hand. Tuning an animation curve from "too snappy" to "too soft" to "about right." Cropping the corners of a screenshot. Picking a color from a set of swatches. Dragging is faster than describing the order in a text box, and a slider is faster than describing easing curves.
The UI exists for fifteen minutes. The export button pastes the result back into my next prompt. The HTML file gets deleted at the end of the day. The work survives. The editor doesn't.
Build a tiny drag-and-drop editor for my topic bank. Each idea is a card I can move across Backlog, Drafting, Used, Cut. Pre-sort by your best guess. Add a "copy as Markdown" button that exports the result in the same shape the file came in.
6. The design system file that quietly powers all of the above.
One design-system.html. The first version got bootstrapped by pointing Claude at my marketing site and my app and asking it to find every color, font, spacing token, and component pattern they shared. The output was a single file with swatches I could see, type samples I could read, and spacing chips I could measure. I edited two values and froze the rest.
I reference it from every project now. The slides looked like mine because they read it. The PDF report looked like mine because it read it. The status page looked like mine because it read it. Update one value here, and the change propagates the next time I start a session. It beats pointing Claude at a Figma link, beats asking it to re-derive my palette every time, and beats writing a style guide in Markdown that no one opens twice.
Read the marketing site and the app. Find every color, font, spacing token, and component pattern they share. Write the result to a single design-system.html I can reference from any future project. Render each token as a swatch or a live sample.
When I still reach for Markdown
Not everything moved. The repo this newsletter lives in is still mostly .md files. Research notes. Iteration plans. The topic bank. The style guide. Anything I'll grep, blame, diff, or review across sessions stays Markdown. HTML breaks all of that. Change one word in a paragraph and the diff shows fifty lines of moved markup. Blame gets useless. Code review falls apart.
The split I landed on after a month of this: Markdown for files I'll keep editing. HTML for things I print and send. Most of what I produce now is meant to be read once, so most of it ends up HTML. But the documents I'll open again next week stay .md.
Cheat sheet
The version of this that overshoots says stop using Markdown. That's not where I landed. The version I'd actually put a name on is much smaller. Write the source once. Let the browser decide what surface it becomes. Match the format to how the thing is going to be used, and notice when the answer stopped being the one you always reach for.
Look at the last long document an agent wrote you. Did you actually read it, or skim and accept? Whatever the answer was, the format had a vote.