carlos / movement Public
movement
A rastrillo app. Read the framework's own SKILL.md (repo root, or in the module cache) before writing app code — it is the whole app story in ~15KB. The conventions below are load-bearing; the framework enforces most of them mechanically.
Layout
internal/<app>/— the app:models.go(plain GORM structs + Models),migrations.go+migrations/(Schema, BootSchema),app.go(migrate.Apply + chi router),handlers.go,render.go+templates/. This is SKILL.md's five-file shape.manifest/— the declarative path, optional and equal: TOML or .go Resource manifests, each generating a full screen set undergen/(see manifest/README.md for the mounting recipe). A hand-written action file at a generated path (carrying//go:build rastrillo_actions) takes that one screen over, by existence.gen/— generated when manifests are used; never edit. Regenerate withrastrillo generate.
Rules the family holds hard
- Every query touching user-owned rows goes through
scope.Owned— reads AND writes, transactions included. A row that isn't yours 404s, never 403s. - Never bind a request body onto a GORM model: explicit
map[string]any+.Selectallowlist. - Migrations apply at boot, once each, and are never re-run or reversed.
- Schema changes: edit models.go, then run
rastrillo migration generateand read the SQL it writes — it may be a full table rebuild, not just anADD COLUMN. Never edit a migration that has shipped. Renames are hand-written (rastrillo migration new rename_x) because a rename is indistinguishable from a drop plus an add. - Money is integer cents. A float never touches a value a person will be held to.
- Screens work with JavaScript disabled; destructive actions get their own confirm-page URL.
- The gate is
make ci(vet + gofmt + tests) — the same definition CI runs. Run it before every push.CGO_ENABLED=0throughout: the stack is cgo-free by design.
Serving
rastrillo.Resolve + Serve speak the CARLOS activation contract (socket/addr/db argv, LISTENFDS, $STATEDIRECTORY, sidecar run) — the golden deployment target, not a requirement: ./app -addr :8080 serves anywhere. GET /healthz and GET /api/version are answered by the framework, outside app middleware.
UX conventions (seeded from profile: considered)
Accessible by construction, progressively enhanced, and never dependent on JavaScript to be usable.
These are rastrillo's own conventions, and the profile is named for what it does rather than after anyone else's work. For wider reading on interface quality, impeccable.style, the WAI-ARIA Authoring Practices and Inclusive Components are all worth your time — none of them is endorsed by, or authoritative for, this framework.
- [x] Icons — lucide, inline —
internal/<app>/icons - [x] Fields — label, hint, help and error in one envelope, wired with aria-describedby —
ui/field, ui/field-select, ui/field-text - [x] Destructive actions — their own confirm-page URL, never a modal —
ui/confirm-form - [x] State — never colour alone — always a text label too —
ui/status-pill, ui/badge - [ ] Dates — relative, with the absolute date in a title attribute
- [ ] Errors — server-rendered beside the field, in plain language
- [x] Empty states — say what the screen is for, not just that it is empty —
ui/empty-state
[x] is enforced by a vendored component: use it, do not hand-roll an equivalent. [ ] is guidance to apply by hand. A convention absent from this list is one rastrillo has no opinion about.
Edit any line. This file is the source of truth, not the profile name. The profile only seeded this list at scaffold time; nothing re-reads it, so changing a line here changes what the app does. A convention deleted from this file is not a convention to reinstate.