AGENTS.md — SailEver server (apps/server)¶
How to build, test and find code. What the server does (pages, API, ingestion, data model, emails) is specified in ../../docs/specs/server/, with the contracts shared with the watch apps in ../../docs/specs/shared/. Read the workspace ../../AGENTS.md first (Definition of Done, glossary, spec rules).
Stack¶
Ruby 4.0 (.ruby-version), Rails 8.1, PostgreSQL with PostGIS (brew install postgis osmium-tool locally; track positions are plain decimal lat / lon, PostGIS only holds the OpenStreetMap places), Hotwire + Importmap, Leaflet + OpenSeaMap, Devise for admins, passkeys + magic links for users, Solid Queue jobs, SMTP to OCI Email Delivery and Active Storage on OCI Object Storage in production, Kamal deploys (DEPLOYMENT.md, deploy skills in .agents/skills/).
Commands¶
bin/setup # bundle, db:prepare
bin/dev # server + CSS watcher (RUBY_DEBUG_OPEN=true)
bin/rails test # must be 0 failures
bin/quality --ci # rubocop, reek, brakeman, exhale (duplication / complexity contract in contract/)
The Stop / SubagentStop hook script/agent_gate.sh runs bin/quality --ci and bin/rails test and blocks the hand-back on failure. Never silence a check, add ignores or raise thresholds without the user's approval.
Where things are¶
| Area | Code | Spec |
|---|---|---|
| Routes | config/routes.rb |
server/README.md (URL map) |
Watch API (/api/v1) |
app/controllers/api/v1/ |
server/api.md, shared/track-sync.md, shared/device-api.md |
| SLTB / FIT decoding, metrics, naming, tides | app/services/sailever/, app/services/ingestion/, app/jobs/ |
server/ingestion.md |
| OpenStreetMap places (import, lookup, rename) | app/services/osm_places/, lib/tasks/osm.rake |
server/ingestion.md § OSM places |
| Models | app/models/, db/schema.rb |
server/data-model.md |
Signed-in area (/my) |
app/controllers/, app/views/ |
server/logbook.md, trip.md, track-editor.md, devices.md |
| Copy | config/locales/ |
server/i18n.md |
Visual design reference for the public site: docs/web-redesign/concept/index.html and docs/web-redesign/films/.
Record ids from requests¶
A record id from a request (path, query or form) is only ever resolved through a scope of what the signed-in user may access: current_user.trips.find(id), Trip.visible_to(current_user).find(id). Never Model.find(params…) outside app/controllers/admin/, never *_id in permit(…). Public pages find records by random token or signed id, never by a counted id. Models check that linked records have the same owner (track–trip, trip–boat). bin/quality check scoped_ids and test/integration/access_matrix_test.rb enforce it (ADR 015).
Changelog¶
Every pull request that changes the server adds one entry at the top of config/changelog.yml before it is merged: id: pr-<number>, pr: <number>, title: (the PR title), details: (what changed, short Markdown; no test notes). Open the PR first to know its number, then commit the entry to the same branch. Each deploy records the entries it brought; the owner reads them at /admin/changelog (admin.md § Changelog). There is no changelog email.
Changing behaviour¶
Update the spec in the same change (/spec-update). If the change touches the API or the SLTB format, check both watch apps against it (/port-change).