Personal Learnings← Simon Willison  Library

Simon Willison · Tech & AI

JustHTML is a fascinating example of vibe engineering in action

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 &quot;Playground Mode&quot; with buttons labeled &quot;CSS Selector Query&quot; (purple, selected), &quot;Pretty Print HTML&quot;, &quot;Tree Structure&quot;, &quot;Stream Events&quot;, &quot;Extract Text&quot;, and &quot;To Markdown&quot; (all gray). Below is a text field labeled &quot;CSS Selector:&quot; containing &quot;p&quot; and a green &quot;Run Query&quot; button. An &quot;Output&quot; 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>