Operating guide for AI coding agents (Claude Code, Codex, Cursor, etc.) working in this repository. Humans may also find it useful as a quick orientation.
For project background, architecture, conventions and backend details, read docs/architecture.md first. This file focuses on what an agent needs to do (and avoid) when changing code.
RingDrill is a Flutter app (Dart 3, SDK ^3.8.0) plus a Dart admin CLI (bin/ringdrill.dart) and a Node.js Netlify backend (netlify/functions/). Targets: Android, iOS, web/PWA, macOS, Linux, Windows. Distribution: Google Play via Shorebird, PWA via Netlify.
flutter pub get # install deps
make build # one-shot codegen (freezed/json_serializable)
make watch # incremental codegen watcher
make i18n # regenerate Flutter localizations from ARB
flutter analyze # static analysis (CI gate)
flutter test # run all tests
flutter run -d chrome # run PWA locally
make web # production web build (canvaskit, offline-first)
make release-android # Shorebird Android release
make patch-android # Shorebird OTA patch
make release-tag VERSION=X.Y.Z+N # bump pubspec, prepend CHANGELOG.md, commit, annotated tag
dart pub global activate -s path . # install CLI from this checkout
ringdrill -h # CLI usage (needs RINGDRILL_ADMIN_TOKEN)Localization is regenerated automatically the next time you flutter run, flutter build or flutter test after editing lib/l10n/app_en.arb or app_nb.arb. Run make i18n (flutter gen-l10n) when you need an explicit regen — make build only covers freezed/json_serializable and does NOT touch app_localizations*.dart.
These are non-negotiable unless the maintainer says otherwise.
- Run codegen, not regex. When you add or change a
@freezedclass, an enum carrying@JsonValue, or anything with apart 'x.g.dart'/part 'x.freezed.dart'directive, runmake build(ordart run build_runner build --delete-conflicting-outputs). Never edit the generated output by hand. - Never edit
*.freezed.dart,*.g.dart,app_localizations*.dartdirectly. They are regenerated and your changes will be lost. Change the source (*.dartmodel files or*.arbfiles) instead. - Keep mobile-safe imports mobile-safe. Any file imported from
lib/main.darton a non-web platform must not transitively importdart:htmlorpackage:web. Use the existingif (dart.library.io)pattern with a stub. Web-only code lives underlib/web/. - Localize every user-visible string. No raw English text in widgets. Add the key to
app_en.arbandapp_nb.arbtogether. If you do not know the Norwegian translation, copy the English string and flag it for review in the PR description. - Respect the analytics consent gate. Do not call Sentry, analytics or any network telemetry outside the consent check in
lib/main.dart. The default is opt-out. - Use
SimpleTimeOfDayfor stored times.TimeOfDayonly inside Flutter widgets. The CLI and any code underlib/data/orlib/models/must useSimpleTimeOfDay. - CLI must stay Flutter-free.
bin/ringdrill.dartand anything it imports (currently onlylib/data/drill_client.dart) must not importpackage:flutter/*. Adding a Flutter import here will breakdart pub global activate. - Drill file format is versioned.
DrillFile.drillSchema1_0is the current schema. Bumping it requires updating the import code inlib/data/drill_file.dart, the Netlify upload handler, and a migration path for existing files. Do not change the schema string without coordinating.- Catalog
tagsare sourced fromprogram.jsonand overwritten on publish (ADR-0043; previously union-merged from?tags=). Missing key →[]. The publish query string carries only operation params (ownerId,programId,version,slug,published).
- Catalog
- Verify before claiming green. Run
flutter analyzeandflutter testbefore reporting a task complete. The stale default-templatetest/widget_test.darthas been removed, so a clean run is now the expected baseline. If a test fails, fix it or flag it rather than asserting all tests pass. - Match existing formatting. This repo follows Dart's default
dart formatstyle andflutter_lints. Do not introduce new lint suppressions without a comment explaining why. - Propose an ADR when you change architecture. If your change introduces, replaces or contradicts an architectural assumption (new dependency category, new file format, new release channel, new backend endpoint, new persistence mechanism, new state-management approach, removal of an existing pattern), add an ADR file under
docs/adrs/in the same change set. Use the next free number and followdocs/adrs/template.md. Update the index indocs/adrs/README.md. Default status isproposed. Only set the status toacceptedwhen the user explicitly instructs you to do so in the same conversation; otherwise leave itproposedfor maintainer review.
Before introducing a new pattern, check docs/adrs/ for an existing decision that already covers it. If one exists and your change conflicts with it, do not silently work around it. Either follow the decision, or propose a new ADR that supersedes it (set the old one's status to superseded by ADR-NNNN in the same change). See ADR-0001 for the process.
- App bootstrap, themes, Sentry/consent gating:
lib/main.dart. - Routing:
buildRouterandMainScreeninlib/views/main_screen.dart. - Domain core:
lib/models/exercise.dart(rotation math is inteamIndex/stationIndexextensions). - Drill timer/phase engine:
lib/services/exercise_service.dart. - File import/export pipeline:
lib/data/drill_file.dartpluslib/services/shared_file_channel.dart. - Backend contract:
netlify.tomlfor routes,netlify/functions/*.jsfor handlers,lib/data/drill_client.dartfor the Dart-side client.
More detail on each of these, plus repo layout and per-layer conventions, is in docs/architecture.md.
Read these before your first commit.
lib/views/is a single flat folder. Keep it that way unless the maintainer asks otherwise.lib/web/program_page_controller.dart,platform_widget.dartandsettings_page.dartshadow files of the same name inlib/views/. The right one is picked via conditional import.sentry.propertiesis gitignored. The Sentry plugin block inpubspec.yamlreferences it for source upload during release builds. Local builds work without it. Do not commit it.untranslated-messages.jsonregenerates on every build. If it shows up ingit status, ignore it.- The Shorebird
app_idinshorebird.yamlis public and safe to commit.