diff options
Diffstat (limited to '.agent/code-map.md')
| -rw-r--r-- | .agent/code-map.md | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/.agent/code-map.md b/.agent/code-map.md new file mode 100644 index 0000000..eb2f564 --- /dev/null +++ b/.agent/code-map.md @@ -0,0 +1,399 @@ +# Code Map & Context Tips + +**Purpose:** Bootstrap context for any new agent session. Read this before touching code. +**Last updated:** 2026-06-03 + +--- + +## 1. Session Start Checklist + +Run these four commands before anything else: + +```bash +git branch --show-current # must be main (or a feature branch off main) +git checkout main # if not already there +git config core.hooksPath scripts/git-hooks # activate pre-commit hook +git pull origin main # sync latest +``` + +Then read `.agent/worklog.md` for current focus and recently completed work. + +**Merge policy (non-negotiable):** merge to `main` after every complete unit of work, not at session end. + +--- + +## 2. Module Topology + +``` +nav/ +├── android-app/ Android application (API 36, Kotlin) +│ └── app/src/main/kotlin/org/terst/nav/ ← all app source +├── test-runner/ JVM-only test module (no Android SDK) +│ └── src/main/kotlin/org/terst/nav/ ← shared portable code +├── .agent/ Agent config, worklog, design, standards +├── scripts/ +│ ├── git-hooks/pre-commit blocks pushes to master +│ └── .claude/ change templates (ct-*.yaml) +└── .github/workflows/android.yml CI; fires on main pushes only +``` + +**Build note:** `./gradlew assembleDebug` requires network access to Google plugin repo — unavailable in the cloud sandbox. Use `test-runner` for logic tests: + +```bash +cd test-runner && ./gradlew test +``` + +--- + +## 3. Source Package Map + +All source lives under `org.terst.nav`. + +### Root-level (android-app only) + +| File | Role | +|------|------| +| `MainActivity.kt` | Single-activity host; nav graph, SAF picker launcher | +| `NavApplication.kt` | Application class; DI/singleton initialization | +| `MainViewModel.kt` → `ui/MainViewModel.kt` | Top-level state (GPS, NMEA, mode) | +| `LocationService.kt` | Foreground service; provides GPS to background | +| `AnchorAlarmManager.kt` | Anchor drag detection; survives screen lock | +| `PerformanceViewModel.kt` | Real-time sailing metrics (VMG, polar %, wind triangle) | +| `PerformanceViewModelFactory.kt` | Factory for PerformanceViewModel | +| `UnitPrefs.kt` | Unit system prefs (knots/m/s, m/ft, °C/°F) | +| `NmeaConnectionPrefs.kt` | TCP/UDP NMEA connection settings | +| `PolarData.kt` | Polar table data class + interpolation | +| `PolarDiagramView.kt` | Custom View: polar diagram canvas renderer | +| `BarometerSensorManager.kt` | Device barometer reader | +| `BarometerTrendView.kt` | Pressure trend sparkline view | +| `BarometerData.kt` | Barometer data class | +| `MobData.kt` | MOB event data (position, timestamp) | +| `TidalCurrentData.kt` | Tidal current data model | +| `MockTidalCurrentGenerator.kt` | Test-only tidal current stub | +| `PowerMode.kt` | Enum: FULL / ECONOMY / ANCHOR_WATCH | +| `FeatureFlags.kt` → `settings/FeatureFlags.kt` | Runtime feature toggles | + +### `ais/` + +| File | Role | +|------|------| +| `AisRepository.kt` | In-memory AIS target store (MMSI → AisVessel) | +| `AisVessel.kt` | AIS target data class | +| `AisVdmParser.kt` → `nmea/` | Parses !AIVDM sentences | +| `CpaCalculator.kt` | CPA / TCPA computation | +| `CpaAlert.kt` | Alert data class for CPA threshold breach | +| `AisHubApiService.kt` | AISHub internet feed Retrofit service | +| `AisHubSource.kt` | Polls AISHub; feeds AisRepository | + +### `data/` + +| Sub-package | Files | Role | +|-------------|-------|------| +| `api/` | `ApiClient.kt`, `WeatherApiService.kt`, `MarineApiService.kt` | Retrofit/OkHttp setup; weather + marine endpoints | +| `model/` | `BoatPolars`, `GribFile`, `GribRegion`, `GribParameter`, `WeatherResponse`, `MarineResponse`, `MarineConditions`, `WindArrow`, `WindForecast`, `ForecastItem`, `TideStation`, `TideConstituent`, `TidePrediction`, `LogbookEntry`, `SensorData`, `SatelliteDownloadRequest` | Pure data classes, no Android deps | +| `repository/` | `WeatherRepository.kt` | Weather fetch + cache coordination | +| `storage/` | `GribFileManager.kt` | GRIB2 file lifecycle (download, store, delete) | +| `weather/` | `GribStalenessChecker.kt`, `SatelliteGribDownloader.kt` | GRIB freshness logic; satellite GRIB fetch | + +### `db/` + +| File | Role | +|------|------| +| `NavDatabase.kt` | Room database; holds `TrackDao` | + +### `gps/` + +| File | Role | +|------|------| +| `GpsProvider.kt` | Interface; abstracts device GPS vs NMEA GPS | +| `GpsPosition.kt` | Data class: lat, lon, sog, cog, accuracy, timestamp | +| `DeviceGpsProvider.kt` | FusedLocationProvider implementation | + +### `logbook/` + +| File | Role | +|------|------| +| `LogbookRepository.kt` | Interface | +| `InMemoryLogbookRepository.kt` | Thread-safe impl (`@Synchronized` on save/getAll/reload) | +| `LogbookStorage.kt` | JSON persistence to SAF directory | +| `LogEntry.kt` | Log entry data class (type enum, text, photo, position) | +| `LogbookFormatter.kt` | Formats log for display | +| `LogbookPdfExporter.kt` | PDF export | +| `VoiceLogViewModel.kt` | ViewModel for logbook + trip report tab | +| `VoiceLogState.kt` | Sealed state for voice log UI | + +### `nmea/` + +| File | Role | +|------|------| +| `NmeaParser.kt` | Parses all NMEA 0183 sentences (GGA, RMC, MWV, HDG, DBT, VHW, XTE…) | +| `AisVdmParser.kt` | Parses !AIVDM / !AIVDO AIS sentences | +| `NmeaStreamManager.kt` | TCP/UDP connection manager; streams sentences to NmeaParser | + +### `routing/` + +| File | Role | +|------|------| +| `IsochroneRouter.kt` | Isochrone weather routing engine | +| `IsochroneResult.kt` | Result data class with optimal route + isochrones | +| `RoutePoint.kt` | Waypoint data class for routing | + +### `safety/` + +| File | Role | +|------|------| +| `AnchorWatchState.kt` | State sealed class for anchor alarm UI | + +### `sensors/` + +Data-only classes (no logic): `WindData`, `HeadingData`, `BoatSpeedData`, `DepthData`, `AttitudeData`, `NmeaBaroData` + +### `settings/` + +| File | Role | +|------|------| +| `FeatureFlags.kt` | Runtime boolean flags (e.g. night mode, race mode) | + +### `thermal/` + +| File | Role | +|------|------| +| `ThermalMonitor.kt` | Monitors battery temp via `PowerManager`; fires alert if overheating | + +### `tide/` + +| File | Role | +|------|------| +| `HarmonicTideCalculator.kt` | Offline tidal harmonic prediction; no network required | + +### `track/` + +| File | Role | +|------|------| +| `TrackRepository.kt` | Coordinates GPX + Room storage; source of truth = GPX/SAF | +| `TrackStorage.kt` | SAF primary + MediaStore fallback for GPX files | +| `TrackEntity.kt` | Room entity; `toShallowSavedTrack()` for fast list load | +| `TrackDao.kt` | Room DAO | +| `TackDetector.kt` | Time-based tack/jibe detection (CRUISING + RACE modes) | +| `TackEvent.kt` | Detected tack data class | +| `GpxParser.kt` | GPX file → list of TrackPoints | +| `GpxSerializer.kt` | TrackPoints → GPX file | +| `SavedTrack.kt` | In-memory track with points + metadata | +| `TrackSummary.kt` | Lightweight summary (no points) for list display | +| `TrackStats.kt` | Computed stats: distance, max speed, duration, tacks | +| `TrackColors.kt` | Speed-to-color mapping for track rendering | +| `SavedTracksFragment.kt` | List of saved tracks; SAF setup button | +| `TrackDetailSheet.kt` | Bottom sheet: track stats, add note/photo | +| `TrackSummarySheet.kt` | Mini summary bottom sheet | + +### `tripreport/` + +| File | Role | +|------|------| +| `TripReportFragment.kt` | Full trip report view (map + stats + log entries) | +| `TripReportGenerator.kt` | Generates report from track + log data | +| `TripReportViewModel.kt` | ViewModel for trip report | +| `PreTripReportFragment.kt` | Pre-departure checklist / briefing | +| `PreTripReportGenerator.kt` | Generates pre-trip summary (weather, similar trips, watch items) | +| `PreTripReportViewModel.kt` | ViewModel for pre-trip report | +| `PreTripModels.kt` | Data classes for pre-trip report | +| `BoatProfileRepository.kt` | Stores boat profile (name, dimensions, polars) | + +### `vessel/` + +| File | Role | +|------|------| +| `VesselRepository.kt` | Crew + vessel data; in-memory + persistence | + +### `ui/` (top level) + +| File | Role | +|------|------| +| `MainViewModel.kt` | Top-level app state | +| `MobHandler.kt` | MOB activation logic; always-on-top button handler | +| `InstrumentHandler.kt` | Routes NMEA/GPS data to instrument display widgets | +| `MapHandler.kt` | MapLibre map setup, overlay rendering, track display | +| `MapLayerManager.kt` | Layer visibility management | +| `LayerPickerSheet.kt` | Bottom sheet: layer toggle UI | +| `NavLogger.kt` | In-app debug log (primary debugging tool — prefer over ADB) | +| `LocationPermissionHandler.kt` | Runtime location permission request flow | +| `DevLogSheet.kt` | Bottom sheet: shows NavLogger output | +| `DirectionArrowView.kt` | Custom View: compass/bearing arrow | +| `InclinometerView.kt` | Custom View: heel angle indicator | +| `WaveView.kt` | Custom View: wave height visualization | +| `WeatherActivity.kt` | Standalone weather details screen | + +### `ui/map/` + +| File | Role | +|------|------| +| `MapFragment.kt` | Main chart fragment; hosts MapLibre GL map | +| `ParticleWindView.kt` | Animated wind particle overlay on chart | + +### `ui/safety/` + +| File | Role | +|------|------| +| `SafetyFragment.kt` | Safety tab: MOB, anchor alarm (depth + rode inputs, radius display) | + +### `ui/voicelog/` + +| File | Role | +|------|------| +| `VoiceLogFragment.kt` | Log tab: logbook entries + trip report; passes both `onSelect` and `onReport` to `SavedTrackAdapter` | + +### `ui/forecast/` + +| File | Role | +|------|------| +| `ForecastFragment.kt` | Weather forecast tab | +| `ForecastAdapter.kt` | RecyclerView adapter for forecast items | + +### `ui/vessel/` + +| File | Role | +|------|------| +| `VesselRegistryFragment.kt` | Crew registry UI | + +### `ui/doc/` + +| File | Role | +|------|------| +| `DocFragment.kt` | In-app documentation viewer | + +### `ui/learn/` + +| File | Role | +|------|------| +| `LearnFragment.kt` | Sailing knowledge / learning content | + +--- + +## 4. test-runner Module + +JVM-only; mirrors a subset of android-app source with no Android SDK dependencies. Run with `cd test-runner && ./gradlew test`. + +**Shared sources** (canonical copy in android-app, symlinked or mirrored in test-runner): + +| Package | Files | +|---------|-------| +| `ais/` | `AisVessel`, `CpaCalculator`, `CpaAlert` | +| `data/model/` | `GribFile`, `GribRegion` | +| `data/storage/` | `GribFileManager` | +| `gps/` | `GpsPosition`, `GpsProvider` | +| `nmea/` | `NmeaParser`, `AisVdmParser` | +| `settings/` | `HardwareSource` | +| `track/` | `TackDetector` ← **symlink** to android-app canonical source | +| `vessel/` | `Vessel`, `VesselRepository`, `CrewMember` | + +**Important:** `TackDetector.kt` in test-runner is a filesystem symlink (`../../../../../../../../android-app/.../TackDetector.kt`). Edit the android-app copy only — test-runner picks it up automatically. + +--- + +## 5. Key Architectural Invariants + +1. **Track storage source-of-truth = GPX files in `Documents/Nav/` via SAF.** Room (`NavDatabase`) is a read-cache for fast list loading. GPX/SAF is always authoritative. +2. **Offline-first.** Every core feature (navigation, tides, safety) works without connectivity. Never gate core functionality on network. +3. **`getExternalFilesDir()` is never acceptable** for user data. User data must survive uninstall. Use SAF (`Documents/Nav/`) or MediaStore. +4. **`NavLogger` is the primary debug tool.** Log to `NavLogger`, not just Android `Log`, so in-app debug sheet shows it. +5. **MOB button is always visible and non-dismissible.** Never place anything above it in the Z-order. +6. **Logbook `InMemoryLogbookRepository`** has `@Synchronized` on `save()`, `getAll()`, `reload()`. Don't remove synchronization. +7. **`VoiceLogFragment`** must pass both `onSelect` and `onReport` to `SavedTrackAdapter` — omitting either causes a CI failure. +8. **MapLibre Android 11.x**: `Expression.get("color")` silently fails for `lineColor` on `LineLayer`. Use `Expression.step(Expression.get("speed"), ...)` to map speed → color literal. +9. **Speed/temp/depth in UI** must use `UnitPrefs` conversions — never raw SI values. +10. **NMEA sentences from instruments are magnetic.** Convert to true using WMM variation from `HarmonicTideCalculator`'s companion magnetic model — not a hardcoded offset. + +--- + +## 6. Testing Guide + +```bash +# JVM unit tests (no Android SDK required, works in cloud sandbox) +cd test-runner && ./gradlew test + +# Android unit tests (requires local dev env with Android SDK) +cd android-app && ./gradlew test + +# Android instrumented tests (requires device/emulator) +cd android-app && ./gradlew connectedAndroidTest +``` + +Key test files by domain: + +| Domain | Test file | +|--------|-----------| +| Tack detection | `test-runner/.../track/TackDetectorTest.kt` (16 tests CRUISING + 2 RACE) | +| Pre-trip report | `android-app/.../tripreport/PreTripReportGeneratorTest.kt` (18 JVM tests) | +| Tide calculator | `android-app/.../tide/HarmonicTideCalculatorTest.kt` | +| AIS / CPA | `test-runner/.../ais/CpaCalculatorTest.kt` | +| NMEA parsing | `test-runner/.../nmea/NmeaParserTest.kt` | +| GRIB storage | `test-runner/.../data/storage/GribFileManagerTest.kt` | + +--- + +## 7. CI / Merge Workflow + +```bash +# 1. Work on feature branch +git checkout -b feature/my-thing + +# 2. Commit incrementally +git add <files> && git commit -m "..." + +# 3. Push branch +git push -u origin feature/my-thing + +# 4. Merge to main IMMEDIATELY when done (not at session end) +git checkout main +git merge --no-ff feature/my-thing +git push -u origin main + +# 5. Delete feature branch (optional cleanup) +git branch -d feature/my-thing +``` + +CI (`android.yml`) triggers only on pushes to `main`. Firebase App Distribution fires on `main` pushes only. + +--- + +## 8. Context Tips & Gotchas + +### Navigation / GPS +- `DeviceGpsProvider` uses `FusedLocationProviderClient`. External GPS comes via NMEA TCP/UDP through `NmeaStreamManager` → `NmeaParser` → `GpsProvider` interface. +- GPS fix is stale after 10 s — `MainViewModel` shows red "No GPS" banner. + +### NMEA +- `NmeaStreamManager` handles connection lifecycle (reconnect on failure). Don't manage socket lifecycle elsewhere. +- All wind data from NMEA is apparent (`AWS`, `AWA`). True wind is computed in `PerformanceViewModel` using the vector triangle. + +### Tack Detection +- `TackDetector` uses time-based windows (not point-count). `T_SETTLE=30s` before/after apex. +- `circularMAD` stability check handles anchor-swing noise without an SOG gate. +- RACE mode: `T_SETTLE=12s`, `MIN_GAP=20s`, `STAB_MAX=30°`. + +### Anchor Alarm +- Alarm must survive: app background, screen lock, low battery, OS memory pressure. +- `AnchorAlarmManager` runs in `LocationService` (foreground service), not in an Activity. +- UI inputs (depth, rode) embedded in `card_anchor` in `fragment_safety.xml` — there is no separate anchor config screen. + +### Trip Report +- Report auto-generates on tab open; RETRY button appears only on error. +- Track map uses a full-bleed 260dp MapLibre instance embedded in the report fragment. +- Pirate mode Easter egg: long-press title in `TripReportFragment`. + +### GRIB / Weather +- GRIB files stored via `GribFileManager`; staleness checked by `GribStalenessChecker`. +- `addGpsPoint()` matches current UTC hour prefix against `timeIso` for forecast slot lookup. +- Particle wind animation: `ParticleWindView` is a GL overlay on the map. + +### Room / Track Storage +- Room is a read-cache only. On cold start, if Room is empty, GPX files are backfilled. +- `TrackEntity.toShallowSavedTrack()` serves the list view without loading track points. +- `TrackStorage` has SAF primary path with MediaStore fallback; SAF state is a `Flow` observed by `SavedTracksFragment`. + +### Logbook +- `onSafDirectorySelected` must call `logbookRepository.reload()` so entries from the newly-granted directory appear immediately. + +### Units +- `UnitPrefs` is the single source of truth for display units. Always convert through it. +- Distance: nm / km. Speed: knots / m/s. Depth: m / ft. Temperature: °C / °F. |
