Colophon
ZigZag was written in public, one conversation at a time. Here’s what was on the workbench.
The stack
| Tool | Version | What it does here |
|---|---|---|
| Zig | 0.15.2 | The language being learned |
| Claude Code | 2.1.71 | AI coding agent — the other half of the conversation |
| Claude Opus 4.6 | claude-opus-4-6 |
The model behind the agent |
| Quarto | 1.8.27 | Turns .qmd files into the book you’re reading |
| Python | 3.13 | Required by Quarto’s Jupyter engine (not by choice) |
| uv | — | Keeps Python contained in a project-local venv |
These are the versions at the start of the project (March 2026). Things will update as the book grows. If a version change matters — if Zig ships a breaking release or Quarto changes how code blocks work — I’ll note it in the relevant chapter.
How the pieces fit together
Claude Code runs in a terminal. It reads files, writes files, runs commands, and holds a conversation while doing all of that. The model powering it — Claude Opus 4.6 — is what actually generates the responses, writes the code, and occasionally goes on philosophical tangents that I choose not to rein in.
Quarto takes the .qmd chapter files (Markdown with some extensions) and renders them into the HTML site you’re reading. Code output is real — generated by actually running zig run at render time, then frozen into the page via Quarto’s freeze: auto feature. CI deploys the frozen results without needing Zig or Python installed.
Zig is Zig. A single binary. No runtime. The entire toolchain fits in a folder you can move around. The compiler, the build system, the test runner — it’s all one thing. We’ll talk about why that matters.
The process
- Open Claude Code in the project directory
- Have a conversation about a Zig concept
- Write and run code during the conversation
- Save the session log to
sessions/ - Turn the interesting parts into a chapter
quarto renderto build the book- Repeat
That’s the whole pipeline. CI handles the rest — a GitHub Actions workflow renders the frozen book and deploys to GitHub Pages.
↯ The Python situation
This Zig book requires Python. Not for anything Zig-related — Quarto uses Jupyter to execute code blocks, and Jupyter is Python.
Quarto itself is a rendering engine. It doesn’t bundle Jupyter — when it encounters an executable {python} cell, it hands it off to Jupyter to run. So the execution chain is: Quarto → Jupyter → Python → subprocess.run → zig run. Without Jupyter installed, Quarto errors on every executable cell.
The natural approach would be to use Jupyter’s bash_kernel to run shell commands directly, cutting Python out of the chain. On Windows, that kernel depends on pexpect, which needs Unix pseudo-terminals, so it doesn’t work. The workaround: each executable block uses a hidden Python cell (#| echo: false) that calls subprocess.run to invoke zig run. The rendered output shows only the Zig command and its result.
Here’s the local setup:
# Create a project-local venv
uv venv
# Install Jupyter (~90 packages)
uv pip install jupyter
# Tell Quarto where to find Python
# Windows doesn't auto-detect .venv — set QUARTO_PYTHON
export QUARTO_PYTHON=".venv/Scripts/python.exe" # or bin/python on Unix
# Render — code blocks execute, output gets frozen
quarto renderThe freeze: auto setting in _quarto.yml caches execution results in _freeze/, which gets committed to git. After that, CI only needs Quarto to render the book — no Python, no Jupyter, no Zig. A .env file at the project root sets QUARTO_PYTHON so you don’t have to export it every time.
One-time setup: uv venv && uv pip install jupyter. Then you don’t think about it again.