# AGENTS.md

<!-- Generated by Rust off Rails (generators/new.md). Agents keep the header current. Humans: edit product/, not code. -->

```
APP:            {{app_name}}
FRAMEWORK:      Rust off Rails {{version}} (.offrails/, vendored {{date}})
FRONTEND_MODE:  {{htmx | inertia | spa | api}}
UI_LIBRARY:     {{react | svelte | none}}   (none = no JS UI: htmx without islands, or api)
ISLANDS:        {{yes | no}}
API:            {{none | rest | graphql | both}}   (a versioned API for other programs; api mode is never none)
DEV_DATABASE:   sqlite (storage/development.sqlite3)
PROD_DATABASE:  {{postgres | sqlite}}
DEPLOY_TARGET:  {{later | fly | kamal | railway | render}}
ACCOUNTS:       {{none | <methods>[ invite]: password | magic-link | password+magic-link, then " invite" when sign-up is closed}}  ROLES: {{none | admin+member | …}}  TENANCY: {{per-user | teams | shared | none}}
EMAIL:          dev: local mailbox (/dev/mailbox); prod: {{smtp via <provider> | not yet (logged only)}}
```

## NEVER

Each line is a mistake an agent shipped. The check that catches it is in parentheses.

- **NEVER look a record up without the actor.** `<model>::find_for(db, &user, id)`, never `find(db, id)`; raw `find`/`destroy` stay `pub(in crate::models)` (`guard_test.rs`, `tenancy_test.rs`, `offrails check`).
- **NEVER call work done while `sh .offrails/tools/offrails-check` prints a `FAIL`,** or any Definition of Done command fails or was skipped without saying so.
- **NEVER add a retry, watchdog, reload cap, `sleep` or raised limit without evidence** (the failing request, its status and protocol) and a `docs/decisions.md` line citing it (`.offrails/workflow.md#when-something-fails`).
- **NEVER serve a browser an untrusted certificate, or test login with certificate errors ignored.** Chrome drops `Set-Cookie` after "Proceed (unsafe)", so login bounces. HTTPS in development only with mkcert (`bin/dev-cert`), else the http URL `bin/dev` prints; smokes run with no certificate overrides (`run.sh login`).
- **NEVER ship a light surface, dark text or faint tone without its `dark:` partner** (a `bg-white` card under a dark body: 1.09:1). The palette is in `.offrails/guides/views.md#dark-mode` (`tests/app/theme_test.rs`, `run.sh a11y` in light and dark).
- **NEVER guess the running app's state.** Ask it: `curl -s $URL/dev/introspect.json` (migrations, CSS staleness, protocol, streams) or `cargo run -q -- introspect`.
- **NEVER pick files by loose prefix, depend on `HashMap` order, or hash files per request** (`kernel/assets.rs` uniqueness test).
- **NEVER change a header line (`FRONTEND_MODE`, `ISLANDS`, `UI_LIBRARY`, `TENANCY`, `FRAMEWORK`) without the code to match**, and never leave `FRAMEWORK` behind `.offrails/VERSION` (`offrails check`).
- **NEVER tick an acceptance criterion without a test that cites its id,** or write a Security row without a test name (`offrails check`).
- **NEVER edit `.offrails/` or delete a `docs/decisions.md` line.** Supersede it with a new line (`.offrails/workflow.md#decisions`).
- **NEVER give an `id` or `*_id` a 32-bit column, or let the suite run only on SQLite.** `big_pk_auto`/`big_integer` and `i64`; `INT4` ids made every Postgres write a 500 behind a green SQLite suite (`offrails check` `[ids]`, `[postgres]`).
- **NEVER bind development to `0.0.0.0` or drop the Host check to "make it work".** `/dev` has no login: the mailbox holds every sign-in link. A dev container or a phone sets `APP_BIND=0.0.0.0` and `APP_ALLOWED_HOSTS` on purpose (`offrails check` `[bind]`, `development_refuses_a_rebound_host_name`).
- **NEVER `pkill`/`killall`, or kill by name or pattern.** Stop the PID file you started: `kill "$(cat storage/pids/dev-$PORT.pid)"` for `bin/dev` (it stops its whole tree), `serve-$PORT.pid` for a plain `serve` (`.offrails/cli.md#running-the-app-from-an-agent`). Killing only `bin/dev`'s listener leaves its watchers running.

## You are an agent working on a Rust off Rails app

1. **Read `.offrails/AGENTS.md` now**, then follow its load order. It overrides your general habits.
2. **The human writes no code.** Their intent lives in `product/`. Update the spec before you build, and ask questions per `.offrails/interview.md`.
3. Follow only the `FRONTEND_MODE` branch of each guide, and the `UI_LIBRARY` branch of the JS guides.
4. Done means `.offrails/workflow.md#definition-of-done`, green, with each item's command run. Start with `sh .offrails/tools/offrails-check`.
5. Log decisions not covered by `.offrails/` in `docs/decisions.md`; supersede, never edit.
6. **Start app code from a snippet or a generator (both tested, both yours once copied); hand-write the rest.** `cargo offrails snippets` (`snippet show <name>` to read one), `cargo offrails g --help`; neither is mandatory. Commit after each run, then edit. Keep a feature in its own small files, and mark each line it adds to a shared file with its name (`// posts`).

## Quick commands

```sh
bin/setup                      # first run
bin/dev                        # develop: open the URL it prints (https + HTTP/2 with mkcert, else http){{TS modes only: ", or :5173 via Vite"; htmx: nothing}}; tabs reload on save
sh .offrails/tools/offrails-check   # mechanical gates: header, specs ↔ tests, tenancy, scoped lookups
cargo offrails verify          # after each slice: fmt, CSS, clippy, one build, SQLite + Postgres suites at once
cargo offrails verify --full   # before saying done: + offrails-check, coverage, deny, gitleaks, routes, browser smokes incl. every page after a boosted link, and prove (BASE=…)
BASE=… cargo offrails prove    # screenshots + checks of every page → docs/proof/PROOF.md: link it in your final message
cargo offrails brand           # after editing brand.toml (colors, fonts, radius, logo); components: /dev/components
cargo nextest run <name>       # one test while it's red (SQLite)
docker compose up --build      # the whole app, production-shaped, on Postgres (http://localhost:3000)
docker compose up -d db        # a Postgres for verify (or set TEST_DATABASE_URL in .env)
cargo run -- db migrate        # migrate (any DATABASE_URL)
cargo run -q -- introspect     # the app's state as JSON (development; live parts: $URL/dev/introspect.json)
cargo run -- routes            # list routes (with access levels); rewrites docs/routes.md
cargo offrails snippets        # small tested code to copy: snippet show <name> (read), snippet add <name> (copy + wire)
cargo offrails g --help        # generators: resource, model, controller (path params), page, api, job, mailer, channel, test
cargo offrails list-parts      # parts (mailer, jobs, auth, realtime, dev dashboard): installed or available
cargo offrails add-part <name> # add one when the product needs it (dependencies first; re-running is a no-op)
cargo run -- jobs stats        # with the jobs part: job counts per state (dashboard: /admin/jobs; dev tools: /dev)
```

## App-specific notes

<!-- Agents: add durable, app-specific guidance here (domain vocabulary, tricky invariants). Keep it short and link to product/ specs. -->

This app started from the Rust off Rails template (`.offrails/template/`). Shared files carry one-line `samples: <anchor>` comments (`// samples: config fields`, `{# samples: nav links #}`) where `cargo offrails add-part` inserts a part's wiring. Keep them; they are inert comments.

### Post-upgrade checks

<!-- Agents: list app-specific things /offrails:upgrade must verify after a framework upgrade, e.g. "the footer shows .offrails/VERSION (test: footer_shows_framework_version)". -->
