Skip to content

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).