.offrails/blueprints/AGENTS.md

AGENTS.md

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

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

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