# 🤖 Builder brief: TodoMVC on CARLOS

You're building one entry in a bakeoff. The same TodoMVC app is built in
nine language setups and deployed to Carloku, and the results are
compared on a public report page. The point is to show that CARLOS apps,
and the CARLOS Platform, aren't tied to Go. It isn't a race to crown a
winner, so write the app the way an experienced developer in your
language would write it well the first time.

Your setup, your slug and anything specific to it are in the prompt that
handed you this brief.

## Read first

1. `CONTRACT.md`: the routes, forms and markup every app shares. It's
   binding.
2. `shared/reference.html`, `shared/base.css`, `shared/index.css`: the
   TodoMVC page and styles you serve.
3. `vendor/carlos-skills/getting-started/SKILL.md` and
   `vendor/carlos-skills/building-carlos-apps/SKILL.md` with
   `references/platform.md`: what a CARLOS app is and how the `carlos`
   CLI deploys one. Read them for the platform. Where they assume Go,
   Rastrillo, amadan hosting, canary reviews or Rastrillo's UI, this
   brief overrides them.
4. The TodoMVC app spec, adapted by the contract:
   [https://github.com/tastejs/todomvc/blob/master/app-spec.md](https://github.com/tastejs/todomvc/blob/master/app-spec.md).
   The server holds the data in SQLite, not `localStorage`, and the
   routes are real paths, not `#/`.

## What to build

A server-rendered TodoMVC that works fully with JavaScript turned off,
with a small amount of JavaScript layered on top so it behaves like the
reference TodoMVC when JavaScript is on. There's no client framework and
no JS build step. Plain ES modules are fine, and so is vendored VanJS.

You choose the stack: HTTP server, router, templating, SQLite binding,
and anything else, as your best recommendation for this language. Explain
the choices in your README.

## The platform contract

- One binary for **linux-arm64**, started as
  `<bin> --socket <path> --db <path>`. It listens on that unix socket.
  There is no `$PORT`.
- For local runs and tests it also accepts `--listen 127.0.0.1:<port>`
  in place of `--socket`. Never bind anything other than `127.0.0.1`.
- It serves `GET /healthz` and `GET /api/version` (see `CONTRACT.md`).
- When it needs its own hostname, it reads `CARLOS_HOST`. It never parses
  the socket path.
- **Linked statically, or depending on nothing but libc.** Instances run
  sandboxed and see only their own build, so don't count on any other
  shared library being there.
- SQLite in WAL mode, one file at `--db`. Migrations only ever add; never
  drop or rewrite data.
- Starts fast (instances sleep when idle and wake on the next request)
  and exits cleanly on SIGTERM.
- Don't set any memory, CPU or tier fields. Every app runs at the
  platform default.

## Your directory

Work only in `apps/<slug>/`. Don't read other apps' directories or the
harness source. Everything else in the repo is shared and read-only to
you.

Write these files:

- `AGENTS.md`, **first, before code.** It holds the one rule this app
  lives by, and the tenancy declaration: one instance for the whole app,
  each visitor's list kept separately by an anonymous cookie, no
  sign-in, trust class C (the todo text is stored as plain text; that's
  chosen and written down here).
- `README.md`: the stack you chose and why, and how to build, run, test
  and deploy.
- `DEPS`: one direct third-party dependency per line. Leave the file
  empty if there are none.
- `Makefile` with these targets:

| Target | Does |
|---|---|
| `make build` | Builds for this machine into `dist/app` |
| `make build-arm64` | Builds the shipped binary into `dist/app-linux-arm64` |
| `make test` | Runs your own tests |
| `make clean` | Removes build output only. Leaves dependency caches alone |
| `make clean-deps` | Also removes every compiled third-party artifact, but keeps downloaded sources, so the next `make build-arm64` compiles every library from scratch. If your toolchain keeps compiled libraries in a shared per-user store (cabal, opam, zig, swiftpm and so on), point that store inside `apps/<slug>/.cache/` from the Makefile so this target can delete it without touching anything else. **Go is the exception:** never change `GOCACHE`. Instead, `make build-arm64 COLD=1` passes `-a` |
| `make bump` / `make unbump` | `bump` changes the text of one string constant in the file that holds the `GET /` handler, so the next build must recompile that file. `unbump` puts it back |
| `make deploy` | Deploys `dist/app-linux-arm64` with `carlos deploy` (see below) and needs no prompts |

- `STATUS.md`, written last. Its first line is exactly one of:
  - `LIVE https://todo-<slug>.bfj.oncarlos.com <version>`
  - `BLOCKED <one-line reason>`, followed by a section headed
    `## Where it got stuck`.

  Use `BLOCKED needs-arm64-host` only when the app builds and passes
  locally but you can't produce a linux-arm64 binary on this machine.
  Use `BLOCKED platform: <what's missing>` when Carloku lacks something
  the app needs. Don't work around the platform.

`.cache/`, `dist/` and database files are git-ignored.

## Testing

- Your own tests, in whatever form is normal for your language.
- The shared behaviour suite. Run your app with
  `dist/app --listen 127.0.0.1:<port> --db $TMPDIR/<slug>.db`, then run
  `scripts/test-app.sh http://127.0.0.1:<port> $TMPDIR/<slug>-suite.json`.
  It runs every test with JavaScript on and again with it off. It must
  pass before you deploy.
- `qemu-aarch64-static` is on `PATH` (see `docs/toolchains.md`) if you
  want to run the arm64 binary locally first.

## Deploying to Carloku

Account `bfj`, app `todo-<slug>`, host `todo-<slug>.bfj.oncarlos.com`.
The first time:

```
carlos apps create --account bfj --app todo-<slug> --build-probe /api/version
carlos instances enable --account bfj --app todo-<slug>
carlos instances create --account bfj --app todo-<slug> --host todo-<slug>.bfj.oncarlos.com --channel edge
```

After that, `make deploy` runs
`carlos deploy --account bfj --app todo-<slug> --target linux-arm64 --message "<what changed>" dist/app-linux-arm64`.
The version is the git short SHA of your commit. Before the first
deploy, run `scripts/boot-check.sh dist/app-linux-arm64`. It starts the
arm64 binary under qemu on a unix socket, checks `/healthz` and
`/api/version`, and checks for a clean exit on SIGTERM. (`carlos vet`
needs operator access, which you don't have.) After deploying, run the
shared suite against `https://todo-<slug>.bfj.oncarlos.com`. It must pass
there too.

If a `carlos` command refuses or behaves unexpectedly, read its `-h` and
`references/platform.md` before trying something else.

## Scratch files

`$TMPDIR` is shared with other sessions. Make your own directory with
`mktemp -d -p "$TMPDIR"`, keep every scratch file in it, and delete only
that directory. Never delete with a glob in `$TMPDIR`.

## Git

Commit on your branch (`app/<slug>`) as you go, with clear messages
ending in `Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>`.
Don't push and don't merge.

## Limits

You have 4 hours of wall-clock time. If you're stuck before then, stop,
write `BLOCKED` in `STATUS.md` with where you got stuck, and end.
