diff --git a/site/content/devlog/roro9stack-console/dbg.png b/site/content/devlog/roro9stack-console/dbg.png
new file mode 100644
index 0000000..426022d
Binary files /dev/null and b/site/content/devlog/roro9stack-console/dbg.png differ
diff --git a/site/content/devlog/roro9stack-console/index.md b/site/content/devlog/roro9stack-console/index.md
new file mode 100644
index 0000000..6b5ceb4
--- /dev/null
+++ b/site/content/devlog/roro9stack-console/index.md
@@ -0,0 +1,266 @@
++++
+title = '''It was off'''
+description = '''roro9stack gets a website, and writing down how its Debug Console works shows that only one person could ever use it. So the Debug Build is retired: one firmware, with the console in it, off until its owner switches it on. Then an evening of chasing a console that wouldn't come back and a memory leak, neither of which existed.'''
+date = 2026-10-07T00:30:00+02:00
+
+[extra]
+topics = '''ESP32-S3 · Debugging · Security'''
+read_label = '''Read what was off →'''
+uid = '''debug: on, token set, client connected'''
+dek = "A day of writing for [roro9stack](/devlog/roro9stack/), my firmware for the M5Stack Cardputer: a website, a user guide, and developer docs about the thing I like best in it, a console over Wi-Fi. Documenting it properly made one fact hard to miss: nobody else could have it. Fixing that deleted more than it added, and then cost me an evening looking for two bugs that turned out to be a switch in the Off position and TCP minding its own business."
+byline = '''designed by interrogation, round twelve: fourteen questions thrown away, eight kept'''
+
+[extra.sign]
+label = "Tokens leaked by the feature built to protect them"
+note = "In a screenshot, taken over the console, of the one page that shows the token."
+count = "1"
+tone = "red"
+
+[[extra.cast]]
+name = "The Debug Build"
+role = "retired, v0.3.0 to v0.11.0"
+text = "The same firmware plus a console over Wi-Fi, with its builder's token compiled in. Which is why it could never be published, and why the firmware I tested was never the one I released."
+
+[[extra.cast]]
+name = "Port 3232"
+role = "the Update Service, in every build since v0.3.0"
+text = "Listens on the network in release builds, guarded by a signature. The counter-example to \"in a release, nothing listens\" that had been sitting there all along."
+
+[[extra.cast]]
+name = "The token"
+role = "100 bits, 20 characters"
+text = "Made by the device, shown on one page of Settings and nowhere else. Read off a 240-pixel screen by a human, which is where the trouble started."
+
+[[extra.cast]]
+name = "The dialog"
+role = "\"Switch it on?\""
+text = "Opens with Cancel selected, as a question about a remote control should. Enter, Enter: still off. Works exactly as designed."
+
+[[extra.cast]]
+name = "TIME_WAIT"
+role = "two minutes, per closed connection"
+text = "What TCP does with a connection it has closed, in case a late packet turns up. About 270 bytes each. Looks exactly like a leak if you only watch for one minute."
++++
+
+## TL;DR
+
+- **roro9stack has a website:** [roro9stack.net](/), with an Install page that flashes a Cardputer from the browser, a [user guide](/guide/), [how-tos](/howto/), an [FAQ](/faq/), [developer docs](/dev/) generated from the repository, and this devlog, which moved here.
+- **The Debug Build is gone.** There is one firmware (**v0.12.0**), and the Debug Console is in it: **off** until you switch it on in Settings, with a token the device makes itself.
+- **The token never crosses the network.** The device sends a challenge, the client answers with an HMAC. Five wrong answers close the console for a minute.
+- **It costs** 30 KB of flash and 88 bytes of RAM over the old release build. Off, nothing listens and nothing is allocated.
+- Then I spent an evening on **a console that wouldn't come back on** (it was off) and **a memory leak of 230 bytes per reopening** (it was TCP). Both produced a real fix on the way, and one design change I had to undo.
+- 468 host tests, 12 more than last time. The decision is [ADR 0010](/dev/decisions/0010-debug-console-in-every-build/).
+
+## The cast
+
+{{ cast() }}
+
+## A site, in four phases
+
+The [last post](/devlog/roro9stack-f1-r1/) ended with a device that installs its own releases. That makes it something another person could use, and another person needs somewhere to start that isn't a Gitea README. So the first half of the day was a website: a home page, an Install page, then a guide to each App, how-tos, an FAQ, and developer docs.
+
+Three things from that half are worth keeping.
+
+**Flashing from the browser, without a copy of the firmware.** The Install page uses ESP Web Tools and Web Serial. The obvious way is to copy the firmware image next to the page; then every release needs a rebuild of the site. Instead the page asks Gitea's API for the latest release, in the browser, and hands the flasher a manifest it builds on the spot. That only needed one header: Caddy, in front of Gitea, adds `Access-Control-Allow-Origin: https://roro9stack.net` to the release downloads and to the releases API, which are public anyway. A new release shows up on the page the moment it exists.
+
+**"Generated from the repository" met Zola.** The developer docs were to be built from `docs/`, the README and the firmware's own `help` text, not copied by hand. Zola refuses to read a file outside its own folder, and it resolves symlinks before deciding, so that door is closed too. So a small script writes those pages, they are committed, and CI fails when one is out of date. The command reference is the part I like: it is parsed from the `kHelp` string in `main.cpp`, so the site can't describe a command the firmware doesn't have.
+
+**The posts you're reading had `