# todo-rust

TodoMVC for the CARLOS bakeoff, in Rust. The server renders every page
and handles every action as a plain form POST, so the app works with
JavaScript off. A small ES module (`static/app.js`) sends the same POSTs
with `fetch` and swaps in the updated list, so with JavaScript on it
behaves like the reference TodoMVC.

Live at [https://todo-rust.bfj.oncarlos.com](https://todo-rust.bfj.oncarlos.com).

## The stack, and why

| Piece | Choice | Why |
|---|---|---|
| HTTP server and router | [axum](https://docs.rs/axum) 0.8 on tokio (hyper underneath) | The default choice for a Rust web service today. It serves a unix socket and a TCP port through the same `axum::serve`, and graceful shutdown on SIGTERM is one call |
| Templates | [maud](https://maud.lambda.xyz) | HTML written as Rust macros and checked at compile time. Every interpolated value is escaped unless you opt out, which is what you want for user-typed titles |
| SQLite | [rusqlite](https://docs.rs/rusqlite) with `bundled` | SQLite is compiled from source into the binary, so there is no shared library to find at run time. One connection behind a mutex, used from tokio's blocking pool |
| Allocator | [mimalloc](https://docs.rs/mimalloc) | musl's own allocator is slow when many threads allocate; mimalloc is the usual fix for static musl builds |
| Randomness | [getrandom](https://docs.rs/getrandom) | 128-bit cookie tokens straight from the OS |
| Forms and query strings | [serde](https://serde.rs) (through axum's `Form` and `Query`) | |

Flags are parsed by hand (there are three), and cookies too (there is
one). The shared CSS files are compiled into the binary with
`include_str!`, so they are served byte for byte.

Lists are kept apart by a `todo_list` cookie holding 128 random bits.
A cookie the app didn't issue gets a fresh list. Every query names the
list, so an id from someone else's list is a 404. A POST whose `Origin`
isn't the app's own is a 403. See `AGENTS.md` for the tenancy rules.

### Layout

- `src/main.rs`: flags, socket or TCP listener, SIGTERM.
- `src/app.rs`: routes and handlers, including `GET /`.
- `src/views.rs`: the markup, following `shared/reference.html`.
- `src/db.rs`: schema, migrations (additive, tracked by `PRAGMA user_version`) and queries.
- `static/app.js`: the JavaScript layer.
- `tests/http.rs`: tests that drive the router in-process.

### Without JavaScript

The reference markup has checkboxes and a double-click to edit, neither
of which can post a form. The page adds a submit button over each
checkbox (and over the toggle-all arrow) and a link over each title that
opens `?edit=<id>`. They're transparent and sit exactly on the parts of
the design they stand in for, so the page looks the same either way. A
`<noscript>` stylesheet turns them on; with JavaScript they stay
`display: none`.

## Build, run, test, deploy

Toolchains come from the repo: the Makefile sources `scripts/env.sh`
itself. Build output goes in `.cache/target` and `dist/`.

```sh
make build          # dist/app for this machine
make build-arm64    # dist/app-linux-arm64, static (cargo zigbuild, musl)
make test           # cargo test
dist/app --listen 127.0.0.1:8080 --db "$TMPDIR/rust.db"
../../scripts/test-app.sh http://127.0.0.1:8080 "$TMPDIR/rust-suite.json"
../../scripts/boot-check.sh dist/app-linux-arm64
make deploy MESSAGE="what changed"
```

`make clean` removes `dist/` and this crate's own compiled files.
`make clean-deps` removes all of `.cache/target`, so the next build
recompiles every dependency (the downloaded sources in `~/.cargo` stay).

The version `/api/version` reports is the git short SHA at build time,
stamped in through the `APP_VERSION` environment variable.
