| 1 | # đŸ¤– Builder brief: TodoMVC on CARLOS |
| 2 | |
| 3 | You're building one entry in a bakeoff. The same TodoMVC app is built in |
| 4 | nine language setups and deployed to Carloku, and the results are |
| 5 | compared on a public report page. The point is to show that CARLOS apps, |
| 6 | and the CARLOS Platform, aren't tied to Go. It isn't a race to crown a |
| 7 | winner, so write the app the way an experienced developer in your |
| 8 | language would write it well the first time. |
| 9 | |
| 10 | Your setup, your slug and anything specific to it are in the prompt that |
| 11 | handed you this brief. |
| 12 | |
| 13 | ## Read first |
| 14 | |
| 15 | 1. `CONTRACT.md`: the routes, forms and markup every app shares. It's |
| 16 | binding. |
| 17 | 2. `shared/reference.html`, `shared/base.css`, `shared/index.css`: the |
| 18 | TodoMVC page and styles you serve. |
| 19 | 3. `vendor/carlos-skills/getting-started/SKILL.md` and |
| 20 | `vendor/carlos-skills/building-carlos-apps/SKILL.md` with |
| 21 | `references/platform.md`: what a CARLOS app is and how the `carlos` |
| 22 | CLI deploys one. Read them for the platform. Where they assume Go, |
| 23 | Rastrillo, amadan hosting, canary reviews or Rastrillo's UI, this |
| 24 | brief overrides them. |
| 25 | 4. The TodoMVC app spec, adapted by the contract: |
| 26 | [https://github.com/tastejs/todomvc/blob/master/app-spec.md](https://github.com/tastejs/todomvc/blob/master/app-spec.md). |
| 27 | The server holds the data in SQLite, not `localStorage`, and the |
| 28 | routes are real paths, not `#/`. |
| 29 | |
| 30 | ## What to build |
| 31 | |
| 32 | A server-rendered TodoMVC that works fully with JavaScript turned off, |
| 33 | with a small amount of JavaScript layered on top so it behaves like the |
| 34 | reference TodoMVC when JavaScript is on. There's no client framework and |
| 35 | no JS build step. Plain ES modules are fine, and so is vendored VanJS. |
| 36 | |
| 37 | You choose the stack: HTTP server, router, templating, SQLite binding, |
| 38 | and anything else, as your best recommendation for this language. Explain |
| 39 | the choices in your README. |
| 40 | |
| 41 | ## The platform contract |
| 42 | |
| 43 | - One binary for **linux-arm64**, started as |
| 44 | `<bin> --socket <path> --db <path>`. It listens on that unix socket. |
| 45 | There is no `$PORT`. |
| 46 | - For local runs and tests it also accepts `--listen 127.0.0.1:<port>` |
| 47 | in place of `--socket`. Never bind anything other than `127.0.0.1`. |
| 48 | - It serves `GET /healthz` and `GET /api/version` (see `CONTRACT.md`). |
| 49 | - When it needs its own hostname, it reads `CARLOS_HOST`. It never parses |
| 50 | the socket path. |
| 51 | - **Linked statically, or depending on nothing but libc.** Instances run |
| 52 | sandboxed and see only their own build, so don't count on any other |
| 53 | shared library being there. |
| 54 | - SQLite in WAL mode, one file at `--db`. Migrations only ever add; never |
| 55 | drop or rewrite data. |
| 56 | - Starts fast (instances sleep when idle and wake on the next request) |
| 57 | and exits cleanly on SIGTERM. |
| 58 | - Don't set any memory, CPU or tier fields. Every app runs at the |
| 59 | platform default. |
| 60 | |
| 61 | ## Your directory |
| 62 | |
| 63 | Work only in `apps/<slug>/`. Don't read other apps' directories or the |
| 64 | harness source. Everything else in the repo is shared and read-only to |
| 65 | you. |
| 66 | |
| 67 | Write these files: |
| 68 | |
| 69 | - `AGENTS.md`, **first, before code.** It holds the one rule this app |
| 70 | lives by, and the tenancy declaration: one instance for the whole app, |
| 71 | each visitor's list kept separately by an anonymous cookie, no |
| 72 | sign-in, trust class C (the todo text is stored as plain text; that's |
| 73 | chosen and written down here). |
| 74 | - `README.md`: the stack you chose and why, and how to build, run, test |
| 75 | and deploy. |
| 76 | - `DEPS`: one direct third-party dependency per line. Leave the file |
| 77 | empty if there are none. |
| 78 | - `Makefile` with these targets: |
| 79 | |
| 80 | | Target | Does | |
| 81 | |---|---| |
| 82 | | `make build` | Builds for this machine into `dist/app` | |
| 83 | | `make build-arm64` | Builds the shipped binary into `dist/app-linux-arm64` | |
| 84 | | `make test` | Runs your own tests | |
| 85 | | `make clean` | Removes build output only. Leaves dependency caches alone | |
| 86 | | `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` | |
| 87 | | `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 | |
| 88 | | `make deploy` | Deploys `dist/app-linux-arm64` with `carlos deploy` (see below) and needs no prompts | |
| 89 | |
| 90 | - `STATUS.md`, written last. Its first line is exactly one of: |
| 91 | - `LIVE https://todo-<slug>.bfj.oncarlos.com <version>` |
| 92 | - `BLOCKED <one-line reason>`, followed by a section headed |
| 93 | `## Where it got stuck`. |
| 94 | |
| 95 | Use `BLOCKED needs-arm64-host` only when the app builds and passes |
| 96 | locally but you can't produce a linux-arm64 binary on this machine. |
| 97 | Use `BLOCKED platform: <what's missing>` when Carloku lacks something |
| 98 | the app needs. Don't work around the platform. |
| 99 | |
| 100 | `.cache/`, `dist/` and database files are git-ignored. |
| 101 | |
| 102 | ## Testing |
| 103 | |
| 104 | - Your own tests, in whatever form is normal for your language. |
| 105 | - The shared behaviour suite. Run your app with |
| 106 | `dist/app --listen 127.0.0.1:<port> --db $TMPDIR/<slug>.db`, then run |
| 107 | `scripts/test-app.sh http://127.0.0.1:<port> $TMPDIR/<slug>-suite.json`. |
| 108 | It runs every test with JavaScript on and again with it off. It must |
| 109 | pass before you deploy. |
| 110 | - `qemu-aarch64-static` is on `PATH` (see `docs/toolchains.md`) if you |
| 111 | want to run the arm64 binary locally first. |
| 112 | |
| 113 | ## Deploying to Carloku |
| 114 | |
| 115 | Account `bfj`, app `todo-<slug>`, host `todo-<slug>.bfj.oncarlos.com`. |
| 116 | The first time: |
| 117 | |
| 118 | ``` |
| 119 | carlos apps create --account bfj --app todo-<slug> --build-probe /api/version |
| 120 | carlos instances enable --account bfj --app todo-<slug> |
| 121 | carlos instances create --account bfj --app todo-<slug> --host todo-<slug>.bfj.oncarlos.com --channel edge |
| 122 | ``` |
| 123 | |
| 124 | After that, `make deploy` runs |
| 125 | `carlos deploy --account bfj --app todo-<slug> --target linux-arm64 --message "<what changed>" dist/app-linux-arm64`. |
| 126 | The version is the git short SHA of your commit. Before the first |
| 127 | deploy, run `scripts/boot-check.sh dist/app-linux-arm64`. It starts the |
| 128 | arm64 binary under qemu on a unix socket, checks `/healthz` and |
| 129 | `/api/version`, and checks for a clean exit on SIGTERM. (`carlos vet` |
| 130 | needs operator access, which you don't have.) After deploying, run the |
| 131 | shared suite against `https://todo-<slug>.bfj.oncarlos.com`. It must pass |
| 132 | there too. |
| 133 | |
| 134 | If a `carlos` command refuses or behaves unexpectedly, read its `-h` and |
| 135 | `references/platform.md` before trying something else. |
| 136 | |
| 137 | ## Scratch files |
| 138 | |
| 139 | `$TMPDIR` is shared with other sessions. Make your own directory with |
| 140 | `mktemp -d -p "$TMPDIR"`, keep every scratch file in it, and delete only |
| 141 | that directory. Never delete with a glob in `$TMPDIR`. |
| 142 | |
| 143 | ## Git |
| 144 | |
| 145 | Commit on your branch (`app/<slug>`) as you go, with clear messages |
| 146 | ending in `Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>`. |
| 147 | Don't push and don't merge. |
| 148 | |
| 149 | ## Limits |
| 150 | |
| 151 | You have 4 hours of wall-clock time. If you're stuck before then, stop, |
| 152 | write `BLOCKED` in `STATUS.md` with where you got stuck, and end. |
| 153 | |