Blog
[counts and sizes are read from the specs and the emitted pages of the pull request that ships the course; a page cannot read what you type] Course 12, Vibecoding from zero, has 27 widgets and none runs a script: every widget state is one section of one HTML page, every click is a link to a fragment, and the CSS :target rule shows the one section the address names. A t27 spec writes each page, and a workflow fails when a committed page differs from the emitted one by a single byte. 18 pages are infographics, 9 have state, and the loop page, the largest, is 92 sections and 55,132 bytes.

Course 12 of t27.ai, Vibecoding from zero, has 27 widgets, and none of them runs a script. Each widget is one HTML page. Every state of the widget is one section of that page, every click is a link to a fragment such as #plant, and a CSS rule, :target, shows the one section the address names. The browser does the switching, so there is no JavaScript to load, to break or to review.
No page is typed by hand. Each widget has a t27 spec in apps/website/specs/widgets/views/ with a function such as page_vc_the_loop that fills a buffer with the whole page, through a small writer, html_out, and a shared frame, page_kit. On Actions, t27c gen-text --fn compiles the spec, runs that function and writes its bytes, unchanged, to apps/website/public/widgets/vc-the-loop/index.html. A workflow fails when a committed page differs from the emitted one by a single byte.
Of the 27 pages, 18 are infographics: a table and a flow, two sections with a tab link between them. The other 9 have state: the four phases, the loop, the message loop, planting a bug, reading a diff, secrets out, unstuck, the test judge and the spec-to-bot table. Each of those is written out as a finite-state machine with a cap on its states. The loop widget is the largest: 92 sections, 55,132 bytes of plain HTML.
Work with me
I work contract and part-time on hardware-AI, FPGA/RTL and ML systems — from specification and open toolchains to reproducible measurements.