carlos / todomvc-bakeoff Public

Clone
git clone https://amadan.net/carlos/todomvc-bakeoff

Plain git — no account needed to clone.

Download

Download this file

LIVE https://todo-nim.bfj.oncarlos.com 290dbb4

Where it stands

  • Deployed 290dbb4 (the second review round's fix) to todo-nim on account bfj, channel edge. carlos deploy confirmed it through the /api/version build probe. Earlier deploys: df803f7, then 6aa1f35.
  • Shared behaviour suite: 67 passed, 3 skipped both locally (dist/app --listen) and against https://todo-nim.bfj.oncarlos.com. The skips are the suite's own: blur and Escape editing with JavaScript off, and the screenshot test with JavaScript off.
  • make test: 17 unit tests pass (store scoping, filters, toggle-all, Unicode and 10,000-character titles, Unicode-whitespace trimming, migrations, cookies, 303s, 403 on a foreign Origin, 404 on a foreign id, the edit view, fragments, static files).
  • scripts/boot-check.sh dist/app-linux-arm64: /healthz and /api/version answer 200 on a unix socket under qemu, and SIGTERM exits 0.
  • The binary is a 1.7 MB static aarch64 ELF (zig cc, musl). SQLite 3.54.1 is compiled in.

Worth knowing

  • src/httpserver.nim is a copy of Nim 2.2.12's std/asynchttpserver with a single change, marked "todo-nim": chunked bodies are limited. When upgrading Nim, re-copy it and re-apply that change.
  • asynchttpserver has no unix-socket mode. unixServer in src/app.nim binds the socket and gives it to the server through std/importutils.privateAccess, and it has to set maxFDs too: without it shouldAcceptRequest is always false and the server silently never accepts. That's the one place this app reaches into the standard library's internals. A listen(domain = AF_UNIX) option upstream would remove it.
  • The README and AGENTS.md prose didn't go through a copy review. This session ran unattended, and that review needs a person.

Review round 2

The one finding is fixed in 290dbb4:

  1. Invalid UTF-8 in titles (security). title=%80 made unicode.strip index out of bounds. The IndexDefect isn't a CatchableError, so it got past the handler and stopped the server. Now action runs unicode.validateUtf8 on the decoded title before anything else touches it, and answers 400 if it isn't valid. That check is bounds-safe. Browsers always send UTF-8, so a real visitor never hits this. A unit test covers truncated 2-, 3- and 4-byte sequences and stray bytes for both add and edit, and the list is unchanged afterwards. Before the fix, title=%80 killed the local binary. After it, the binary and the live host both answer 400 and keep serving.

After the fix the shared suite gave 67 passed and 3 skipped both locally and against the live host, and the boot check passed.

Review round 1

All four findings are fixed in 6aa1f35:

  1. Chunked bodies (security). The parser lives inside the standard library, so the server is now vendored as src/httpserver.nim. Each chunk's declared size is checked against what's left of the body limit before recv allocates anything. Oversized, overflowing and many-small-chunk bodies all get a 413 and the connection closes. The limit is 1 MiB for both chunked and Content-Length bodies (it was 8 MiB); a 10,000-character title needs at most about 120 KB. Checked with raw-socket requests against the binary: it stayed at 7 MB RSS.
  2. Unicode trim. Add and edit now strip the whitespace JavaScript's String.prototype.trim removes (U+00A0, U+2000–U+200A, U+3000, U+FEFF and the rest). A title of only non-breaking spaces adds nothing, and editing to that deletes the item. There's a unit test for both.
  3. Edits across re-renders. render() keeps the item being edited (text, cursor, focus) and restores it on the same item in the new list, and the blur that removing the old field may fire doesn't count as a save. I checked it with Playwright: toggle one item with the response held for 800 ms, double-click another, type, and wait. The edit is still open, focused and holding the text, and Enter saves it.
  4. Zig's caches. The Makefile exports ZIG_GLOBAL_CACHE_DIR and ZIG_LOCAL_CACHE_DIR under .cache/, and clean-deps removes them. After clean-deps, make build-arm64 rebuilt musl and compiler-rt (61 MB of cache) from scratch.

After the fixes the shared suite gave 67 passed and 3 skipped both locally and against the live host, and the boot check passed.

Review findings not fixed

None.