Simon Willison · Tech & AI
TIER 4 2025-12-14
<p>I recently came across <a href="https://github.com/EmilStenstrom/justhtml">JustHTML</a>, a new Python library for parsing HTML released by Emil Stenström. It's a very interesting piece of software, both as a useful library and as a case study in sophisticated AI-assisted programming.</p> <h4 id="first-impressions-of-justhtml">First impressions of JustHTML</h4> <p>I didn't initially know that JustHTML had been written with AI assistance at all. The README caught my eye due to some attractive characteristics:</p> <ul> <li>It's pure Python. I like libraries that are pure Python (no C extensions or similar) because it makes them easy to use in less conventional Python environments, including Pyodide.</li> <li>"Passes all 9,200+ tests in the official <a href="https://github.com/html5lib/html5lib-tests">html5lib-tests</a> suite (used by browser vendors)" - this instantly caught my attention! HTML5 is a big, complicated but meticulously written specification.</li> <li>100% test coverage. That's not something you see every day.</li> <li>CSS selector queries as a feature. I built a Python library for this <a href="https://github.com/simonw/soupselect">many years ago</a> and I'm always interested in seeing new implementations of that pattern.</li> <li>html5lib has been <a href="https://github.com/mozilla/bleach/issues/698">inconsistently maintained</a> over the last few years, leaving me interested in potential alternatives.</li> <li>It's only 3,000 lines of implementation code (and another ~11,000 of tests.)</li> </ul> <p>I was out and about without a laptop so I decided to put JustHTML through its paces on my phone. I <a href="https://github.com/simonw/tools/pull/156#issue-3726212220">prompted Claude Code for web</a> on my phone and had it build <a href="https://tools.simonwillison.net/justhtml">this Pyodide-powered HTML tool</a> for trying it out:</p> <p style="text-align: center; margin-top: 1em"><img src="https://static.simonwillison.net/static/2025/justhtml.jpeg" style="width:80%;" alt="Screenshot of a web app interface titled "Playground Mode" with buttons labeled "CSS Selector Query" (purple, selected), "Pretty Print HTML", "Tree Structure", "Stream Events", "Extract Text", and "To Markdown" (all gray). Below is a text field labeled "CSS Selector:" containing "p" and a green "Run Query" button. An "Output" section with dark background shows 3 matches in a green badge and displays HTML code" /></p> <p>This was enough for me to convince myself that the core functionality worked as advertised. It's a neat piece of code!</p> <h4 id="turns-out-it-was-almost-all-built-by-llms">Turns out it was almost all built by LLMs</h4> <p>At this point I went looking for some more background information on the library and found Emil's blog entry about it: <a href="https://friendlybit.com/python/writing-justhtml-with-coding-agents/">How I wrote JustHTML using coding agents</a>:</p> <blockquote> <p>Writing a full HTML5 parser is not a short one-shot problem. I have been working on this project for a couple of months on off-hours.</p> <p>Tooling: I used plain VS Code with Github Copilot in Agent mode. I enabled automatic approval of all commands, and then added a blacklist of commands that I always wanted to approve manually. I wrote an <a href="https://github.com/EmilStenstrom/justhtml/blob/main/.github/copilot-instructions.md">agent instruction</a> that told it to keep working, and don't stop to ask questions. Worked well!</p> </blockquote> <p>Emil used several different models - an advantage of working in VS Code Agent mode rather than a provider-locked coding agent like Claude Code or Codex CLI. Claude Sonnet 3.7, Gemini 3 Pro and Claude Opus all get a mention.</p> <h4 id="vibe-engineering-not-vibe-coding">Vibe engineering, not vibe coding</h4> <p>What's most interesting about Emil's 17 step account covering those several months of work is how much software engineering was involved, independent of typing out the actual code.</p> <p>I wrote about <a href="https://simonwillison.net/2025/Oct/7/vibe-engineering/">vibe engineering</a> a while ago as an alternative to vibe coding.</p> <p>Vibe coding is when you have an LLM knock out code without any semblance of code review - great for prototypes and toy projects, definitely not an approach to use for serious libraries or production code.</p> <p>I proposed "vibe engineering" as the grown up version of vibe coding, where expert programmers use coding agents in a professional and responsible way to produce high quality, reliable results.</p> <p>You should absolutely read <a href="https://friendlybit.com/python/writing-justhtml-with-coding-agents/#the-journey">Emil's account</a> in full. A few highlights:</p> <ol> <li>He hooked in the 9,200 test <a href="https://github.com/html5lib/html5lib-tests">html5lib-tests</a> conformance suite almost from the start. There's no better way to construct a new HTML5 parser than using the test suite that the browsers themselves use.</li> <li>He picked the core API design himself - a TagHandler base class with handle_start() etc. methods - and told the model to implement that.</li> <li>He added a comparative benchmark to track performance compared to existing libraries like html5lib, then experimented with a Rust optimization based on those initial numbers.</li> <li>He threw the original code away and started from scratch as a rough port of Servo's excellent <a href="https://github.com/servo/html5ever">html5ever</a> Rust library.</li> <li>He built a custom profiler and new benchmark and let Gemini 3 Pro loose on it, finally achieving micro-optimizations to beat the existing Pure Python libraries.</li> <li>He used coverage to identify and remove unnecessary code.</li> <li>He had his agent build a <a href="https://github.com/EmilStenstrom/justhtml/blob/main/benchmarks/fuzz.py">custom fuzzer</a> to generate vast numbers of invalid HTML documents and harden the parser against them.</li> </ol> <p>This represents a lot of sophisticated development practices, tapping into Emil's deep experience as a software engineer. As described, this feels to me more like a lead architect role than a hands-on coder.</p> <p>It perfectly fits what I was thinking about when I described <strong>vibe engineering</strong>.</p> <p>Setting the coding agent up with the html5lib-tests suite is also a great example of <a href="https://simonwillison.net/2025/Sep/30/designing-agentic-loops/">designing an agentic loop</a>.</p> <h4 id="-the-agent-did-the-typing-">"The agent did the typing"</h4> <p>Emil concluded his article like this:</p> <blockquote> <p>JustHTML is about 3,000 lines of Python with 8,500+ tests passing. I couldn't have written it this quickly without the agent.</p> <p>But "quickly" doesn't mean "without thinking." I spent a lot of time reviewing code, making design decisions, and steering the agent in the right direction. The agent did the typing; I did the thinking.</p> <p>That's probably the right division of labor.</p> </blockquote> <p>I couldn't agree more. Coding agents replace the part of my job that involves typing the code into a computer. I find what's left to be a much more valuable use of my time.</p>