HTMLRadar · Product
Decks moved to HTML. I built the read tracking for it.
2026-08-30 · 4 min read · Product
PDF was built for paper: a page meant to come out the same on every printer, back when the last thing to happen to a document was printing it. The layout froze because it was about to become a physical object.
Printing isn't the last step any more, and a growing share of what I send was generated rather than typed. An agent writes the spec, the weekly update, the research summary, the first pass at the deck. What comes back is HTML, because HTML carries what a plan actually needs: a table that holds its shape, an SVG diagram, a layout that reflows on a phone.
The Claude blog has a good public version of that argument, on why HTML beats markdown as what an agent gives you: a hundred-line markdown file doesn't get read, and the HTML version does.
The other half is that HTML is legible to whatever sits in the next tab. A PDF arrives as an attachment. An HTML link is a page the recipient can read, hand to whichever assistant they use, and ask questions about.
I run an AI company, and at some point last year I stopped sending investor decks as PDFs. The specs, the weekly updates, the board pre-reads and the client proposals followed. Then I wanted to know whether anybody had read one, since the argument for HTML is that it gets read. I had no way to check. My document was live HTML, and I wanted to see how it got read. So I built that, and it's open source.
What it does
HTMLRadar is send-side analytics for HTML documents. You upload an HTML file or paste a URL you already host, create one tracked link per recipient, and send it. The dashboard gives you one row per viewer who opened it: which sections they read, how much active reading time they spent, how far they scrolled. You also get an email the first time someone opens it.
Every share carries its own controls - email gate, password, expiry date, an allow-list by domain or exact address, and revocation. One document, many shares. The password, expiry and allow-list can be changed after you've sent the link.
How it works, in six sentences
The mechanics are the interesting part, so I'll keep this to six sentences.
- A Cloudflare Worker sits at
/r/{slug}, checks that share's gates, fetches your HTML, and injects the tracker with HTMLRewriter on the way through, so the recipient sees your document, not a re-rendered copy of it. - The tracker itself is roughly 8 KB gzipped, and since the source is public you can read the whole thing before you decide whether to trust it.
- Section detection runs a fallback chain over your markup: headings first (
h1,h2,h3, or a selector you configure), then slide or page containers such assection,articleand anything withslideorpagein its class name, and finally paragraph buckets for plain prose. - Time on a section only starts counting once at least 50% of it has stayed visible for one continuous second, and the read signal fires after three qualified seconds, so scrolling fast past a slide never registers as having read it.
- Both section dwell and session active time run a five-second idle watchdog: session time resets on keydown, scroll and touchstart; section dwell also resets on mousedown and wheel; mousemove is deliberately excluded, because a twitching cursor isn't reading.
- Every proxied response carries a
sandboxContent-Security-Policy withoutallow-same-origin, so the recipient's document runs in an opaque origin and can't reach application storage.
What gets recorded
The whole list, so you can decide before you send anything. An email address when the recipient enters one at a gate, or a random browser ID when they don't; then the time of each open, referrer, the browser's user-agent string and the device type, browser and operating system read from it, coarse location, per-section dwell, scroll depth and active reading time, and, if the document has attachments, which ones were downloaded. There's no mouse tracking, no keystroke capture, no DOM snapshots, no session replay and no raw IP address. A recipient can start an opt-out with window.HTMLRadar.optOut() and confirm on the page that opens.
I send decks to investors and clients; I wouldn't send them a link that records more than that.
The part I did not expect to matter
HTMLRadar ships an MCP server, so the agent that wrote the HTML can publish it as a tracked link, and you can ask that same agent the next morning whether anyone opened it. There are three tools over stdio - share_html, get_share_activity and whoami - and they work in Claude Code, Cursor and Codex CLI. In Claude Code a plugin layers a skill on top, so the agent knows when to offer a tracked link and when to stay quiet. I built it as a side path, but it's now the part I use most.
Self-hosting
If you'd rather your documents didn't sit on somebody else's infrastructure, the whole stack self-hosts. You need a Cloudflare account (Workers, R2 and Pages), a Supabase project, and a domain on Cloudflare DNS. Cloudflare and Supabase both have free tiers. Resend, which sends the emails, is optional; without it the first-open trigger writes a skipped row and everything else carries on working. The schema is a folder of numbered, idempotent SQL migrations you paste into the Supabase editor in order. The self-hosting guide is in the repo, and the hosted version is deployed from the same repository.
Where to find it
The hosted version is free for two tracked links, then $15 a month or $150 a year for unlimited ones. Self-hosting has no licence fee and never will, because the code is AGPL.
Source, issues and the roadmap: https://github.com/htmlradar/htmlradar
Happy to hear your thoughts, particularly on the section-detection heuristics - the part I'm least confident about. Plain prose with no headings is hard to bucket well, and I suspect somebody reading this has a better idea than paragraph groups.
Cheers,
Abhinandan
Curious how the pieces fit together? Read how I built HTMLRadar.
← Back to home