diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-04-03 08:05:24 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-04-03 08:05:24 +0000 |
| commit | 5d358cd570075d36a61f9a37bb80c64f8a0a7e2a (patch) | |
| tree | fa03f40d3c6a96da726b591d269c152adbdf7210 | |
| parent | 9417a7c6b08da362ad97e85973b7570e05d4f0b5 (diff) | |
chore(stories): add claudomator stories for unused results and faked data
6 stories covering findings from codebase audit:
- ct-remove-mock-tidal: remove random fake tidal currents near Solent
- ct-wire-tidal-to-map: wire tidalCurrentState to MapHandler (never called)
- ct-wire-anchor-to-map: wire anchorWatchState to map overlay (text-only)
- ct-show-wind-direction: display TWD fetched but not shown in sheet
- ct-show-swell-direction: display swell dir fetched but not shown
- ct-fix-weather-fallback: remove silent SF fallback in WeatherActivity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| -rw-r--r-- | scripts/.claude/ct-fix-weather-fallback.yaml | 64 | ||||
| -rw-r--r-- | scripts/.claude/ct-remove-mock-tidal.yaml | 83 | ||||
| -rw-r--r-- | scripts/.claude/ct-show-swell-direction.yaml | 75 | ||||
| -rw-r--r-- | scripts/.claude/ct-show-wind-direction.yaml | 92 | ||||
| -rw-r--r-- | scripts/.claude/ct-wind-wiring.txt | 156 | ||||
| -rw-r--r-- | scripts/.claude/ct-wire-anchor-to-map.yaml | 82 | ||||
| -rw-r--r-- | scripts/.claude/ct-wire-tidal-to-map.yaml | 75 |
7 files changed, 627 insertions, 0 deletions
diff --git a/scripts/.claude/ct-fix-weather-fallback.yaml b/scripts/.claude/ct-fix-weather-fallback.yaml new file mode 100644 index 0000000..97823c7 --- /dev/null +++ b/scripts/.claude/ct-fix-weather-fallback.yaml @@ -0,0 +1,64 @@ +name: "Fix WeatherActivity silent fallback to San Francisco" +description: "WeatherActivity silently uses lat=37.8, lon=-122.4 (San Francisco Bay) when GPS is unavailable, showing real API weather data for the wrong location with no indication to the user." +agent: + model: "sonnet" + working_dir: "/workspace/nav" + instructions: | + Context + ------- + WeatherActivity has: + (android-app/app/src/main/kotlin/org/terst/nav/ui/WeatherActivity.kt, lines ~23-24) + + private val defaultLat = 37.8 + private val defaultLon = -122.4 + + When GPS permission is denied or location unavailable, loadWeatherAtDefault() calls + viewModel.loadWeather(defaultLat, defaultLon) and displays the result as if it were + the user's actual location weather — with no disclaimer or error state shown. + + Goal + ---- + Instead of silently fetching weather for San Francisco, show an explicit error/unavailable + state when the device location cannot be obtained. Do not fetch any weather data in that path. + + Remove defaultLat/defaultLon and loadWeatherAtDefault() entirely. + When location is unavailable, set UiState.Error("Location unavailable") so the existing + error UI in the layout is shown instead. + + Step 1 — Read the current WeatherActivity + ----------------------------------------- + Read android-app/app/src/main/kotlin/org/terst/nav/ui/WeatherActivity.kt in full + to understand the current permission and location flow before making changes. + + Step 2 — Remove the fallback + ---------------------------- + - Delete the defaultLat and defaultLon fields + - Delete the loadWeatherAtDefault() method (or equivalent fallback call) + - Where loadWeatherAtDefault() was called (on permission denied or location null), + replace with: + viewModel.setError("Location unavailable — enable GPS to load weather") + OR if MainViewModel does not have setError(), use: + // Just leave UiState.Loading — do not fetch for a fake position + + The simplest correct fix: do nothing when location is unavailable. + The existing "Loading" spinner is less harmful than wrong-location data. + + Step 3 — Build + -------------- + cd android-app && ANDROID_HOME=/opt/android-sdk ./gradlew assembleDebug 2>&1 | tail -5 + Confirm BUILD SUCCESSFUL. + + Step 4 — Commit and push + ------------------------ + git add android-app/app/src/main/kotlin/org/terst/nav/ui/WeatherActivity.kt + git commit -m "fix(weather): remove silent fallback to San Francisco coordinates + + When GPS was unavailable, WeatherActivity fetched and displayed real weather + data for lat=37.8 lon=-122.4 (San Francisco Bay) with no indication to the + user. Remove the fallback; leave the loading state when location is unavailable." + git push github main && git push local main +timeout: "15m" +tags: + - "weather" + - "bug" + - "ux" diff --git a/scripts/.claude/ct-remove-mock-tidal.yaml b/scripts/.claude/ct-remove-mock-tidal.yaml new file mode 100644 index 0000000..86d4e76 --- /dev/null +++ b/scripts/.claude/ct-remove-mock-tidal.yaml @@ -0,0 +1,83 @@ +name: "Remove MockTidalCurrentGenerator" +description: "LocationService generates random fake tidal current arrows near a hardcoded Solent coordinate (50.8, -1.3) every 60 seconds regardless of boat position. Remove the mock generator; leave the layer empty until a real tidal API is integrated." +agent: + model: "sonnet" + working_dir: "/workspace/nav" + instructions: | + Context + ------- + In LocationService.onCreate() there is a coroutine loop: + (android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt, lines ~113-119) + + serviceScope.launch { + while (true) { + val currents = MockTidalCurrentGenerator.generateMockCurrents() + _tidalCurrentState.update { it.copy(currents = currents) } + kotlinx.coroutines.delay(60000) + } + } + + MockTidalCurrentGenerator.generateMockCurrents() produces 10 TidalCurrent objects with: + - Random speedKnots (0–5 kn) + - Random directionDegrees (0–360°) + - Positions scattered within 0.05° of lat=50.8, lon=-1.3 (Solent, UK) + regardless of actual boat position. + (android-app/app/src/main/kotlin/org/terst/nav/MockTidalCurrentGenerator.kt) + + TidalCurrentState has an isVisible flag (defaults false) and a currents list. + MapHandler.updateTidalCurrents() respects isVisible — when false it clears the layer. + + Goal + ---- + 1. Remove the mock generator loop from LocationService.onCreate() + 2. Delete MockTidalCurrentGenerator.kt + 3. Ensure _tidalCurrentState starts with isVisible=false and empty currents (the default) + 4. Remove the import of MockTidalCurrentGenerator from LocationService + + Do NOT remove TidalCurrentState, _tidalCurrentState, or the tidalCurrentState public flow — + those are needed when a real tidal API is added later. + Do NOT remove ACTION_TOGGLE_TIDAL_VISIBILITY handling in onStartCommand — keep the + visibility toggle working for future use. + + Step 1 — Remove the mock loop from LocationService + --------------------------------------------------- + In android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt: + + Delete the entire block (including the comment): + // Mock tidal current data generator + serviceScope.launch { + while (true) { + val currents = MockTidalCurrentGenerator.generateMockCurrents() + _tidalCurrentState.update { it.copy(currents = currents) } + kotlinx.coroutines.delay(60000) // Update every minute + } + } + + Remove the import: + import org.terst.nav.MockTidalCurrentGenerator + + Step 2 — Delete MockTidalCurrentGenerator.kt + --------------------------------------------- + Delete android-app/app/src/main/kotlin/org/terst/nav/MockTidalCurrentGenerator.kt + + Step 3 — Build + -------------- + cd android-app && ANDROID_HOME=/opt/android-sdk ./gradlew assembleDebug 2>&1 | tail -5 + Confirm BUILD SUCCESSFUL with no references to MockTidalCurrentGenerator remaining. + + Step 4 — Commit and push + ------------------------ + git add -A + git commit -m "chore: remove MockTidalCurrentGenerator + + The mock generator was producing 10 random tidal current arrows near a + hardcoded Solent coordinate (50.8, -1.3) every 60 seconds regardless of + boat position. Remove it entirely. The tidal overlay infrastructure + (TidalCurrentState, mapHandler.updateTidalCurrents) is retained for when + a real tidal current API is integrated." + git push github main && git push local main +timeout: "15m" +tags: + - "cleanup" + - "tidal" + - "mock-data" diff --git a/scripts/.claude/ct-show-swell-direction.yaml b/scripts/.claude/ct-show-swell-direction.yaml new file mode 100644 index 0000000..6021bf0 --- /dev/null +++ b/scripts/.claude/ct-show-swell-direction.yaml @@ -0,0 +1,75 @@ +name: "Display swell direction in instrument sheet" +description: "MarineConditions.swellDirDeg is fetched from Open-Meteo and stored but never shown. The Swell cell shows height and period but drops the direction." +agent: + model: "sonnet" + working_dir: "/workspace/nav" + instructions: | + Context + ------- + fetchCurrentConditions() populates MarineConditions.swellDirDeg from + swell_wave_direction (degrees true from Open-Meteo). + (android-app/app/src/main/kotlin/org/terst/nav/data/model/MarineConditions.kt, line 13) + + The instrument sheet expanded section has a Swell column: + (android-app/app/src/main/res/layout/layout_instruments_sheet.xml) + <LinearLayout vertical gravity=center> + <TextView style="InstrumentLabel" text="Swell" /> + <TextView id="value_swell_ht" style="InstrumentPrimaryValue" /> <!-- e.g. "0.8 m" --> + <TextView id="value_swell_per" style="InstrumentLabel" /> <!-- e.g. "8 s" --> + </LinearLayout> + + MainActivity's marineConditions collector sets swellHt and swellPer but ignores swellDirDeg: + (android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt, marineConditions collector) + + InstrumentHandler.updateConditions() has swellPer but no swellDir parameter. + (android-app/app/src/main/kotlin/org/terst/nav/ui/InstrumentHandler.kt) + + Goal + ---- + Show swell direction by updating value_swell_per to contain both direction and period + (e.g. "045° / 8 s"), avoiding the need for a fourth TextView in an already-tight column. + Alternatively add a third sub-label TextView — use whichever fits better visually. + + The simplest approach: combine direction and period into value_swell_per as "DIR° / Xs". + + Step 1 — Update updateConditions() in InstrumentHandler + -------------------------------------------------------- + In android-app/app/src/main/kotlin/org/terst/nav/ui/InstrumentHandler.kt, + the swellPer parameter already exists. No layout change needed if we combine + direction + period into that field. + + Step 2 — Update the marineConditions collector in MainActivity + -------------------------------------------------------------- + In android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt, + in observeDataSources(), update the swellPer line in updateConditions() to combine + direction and period: + + swellPer = when { + c.swellDirDeg != null && c.swellPeriodS != null -> + "%.0f° / %.0f s".format(Locale.getDefault(), c.swellDirDeg, c.swellPeriodS) + c.swellPeriodS != null -> + "%.0f s".format(Locale.getDefault(), c.swellPeriodS) + c.swellDirDeg != null -> + "%.0f°".format(Locale.getDefault(), c.swellDirDeg) + else -> "—" + } + + Step 3 — Build + -------------- + cd android-app && ANDROID_HOME=/opt/android-sdk ./gradlew assembleDebug 2>&1 | tail -5 + Confirm BUILD SUCCESSFUL. + + Step 4 — Commit and push + ------------------------ + git add android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt + git commit -m "feat(instruments): show swell direction alongside swell period + + swellDirDeg was fetched from Open-Meteo and stored in MarineConditions + but never displayed. Combines direction and period in the swell sub-label + as e.g. '045° / 8 s'." + git push github main && git push local main +timeout: "15m" +tags: + - "instruments" + - "swell" + - "forecast" diff --git a/scripts/.claude/ct-show-wind-direction.yaml b/scripts/.claude/ct-show-wind-direction.yaml new file mode 100644 index 0000000..3ac9276 --- /dev/null +++ b/scripts/.claude/ct-show-wind-direction.yaml @@ -0,0 +1,92 @@ +name: "Display forecast wind direction (TWD) in instrument sheet" +description: "MarineConditions.windDirDeg is fetched from Open-Meteo and stored in the model but never displayed anywhere. Show it as a sub-label under the TWS cell." +agent: + model: "sonnet" + working_dir: "/workspace/nav" + instructions: | + Context + ------- + fetchCurrentConditions() in WeatherRepository populates MarineConditions.windDirDeg + from the Open-Meteo hourly winddirection_10m field (in degrees true). + (android-app/app/src/main/kotlin/org/terst/nav/data/repository/WeatherRepository.kt, line ~63) + (android-app/app/src/main/kotlin/org/terst/nav/data/model/MarineConditions.kt, line 9) + + In MainActivity.observeDataSources(), the marineConditions collector reads windSpeedKt + (displays as TWS) but never reads windDirDeg: + (android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt, marineConditions collector) + + The instrument sheet layout has a TWS cell: + (android-app/app/src/main/res/layout/layout_instruments_sheet.xml) + <TextView android:id="@+id/value_tws" style="@style/InstrumentPrimaryValue" /> + + Goal + ---- + Show wind direction as a secondary line below the TWS value (e.g. "045°") using the + same pattern used for curr_dir below curr_spd in the conditions section. + + Step 1 — Add value_twd TextView to layout + ------------------------------------------ + In android-app/app/src/main/res/layout/layout_instruments_sheet.xml, + find the TWS LinearLayout: + + <LinearLayout ... android:gravity="center" android:padding="8dp"> + <TextView style="@style/InstrumentLabel" android:text="TWS" /> + <TextView android:id="@+id/value_tws" style="@style/InstrumentPrimaryValue" tools:text="—" /> + </LinearLayout> + + Add a third child TextView for the direction: + + <LinearLayout ... android:gravity="center" android:padding="8dp"> + <TextView style="@style/InstrumentLabel" android:text="TWS" /> + <TextView android:id="@+id/value_tws" style="@style/InstrumentPrimaryValue" tools:text="—" /> + <TextView android:id="@+id/value_twd" style="@style/InstrumentLabel" tools:text="—" /> + </LinearLayout> + + Step 2 — Add valueTwd field to InstrumentHandler + ------------------------------------------------- + In android-app/app/src/main/kotlin/org/terst/nav/ui/InstrumentHandler.kt: + + Add constructor parameter: + private val valueTwd: TextView + + Add twd parameter to updateDisplay(): + twd: String? = null + + Add in the updateDisplay body: + twd?.let { valueTwd.text = it } + + Step 3 — Wire in MainActivity + ------------------------------ + In android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt, + in setupHandlers(), add to InstrumentHandler constructor: + valueTwd = findViewById(R.id.value_twd) + + In setupHandlers(), add "—" initial value: + instrumentHandler?.updateDisplay(..., twd = "—") + + In observeDataSources(), in the marineConditions collector, add: + instrumentHandler?.updateDisplay( + tws = c.windSpeedKt?.let { "%.1f".format(Locale.getDefault(), it) } ?: "—", + twd = c.windDirDeg?.let { "%.0f°".format(Locale.getDefault(), it) } ?: "—" + ) + + Step 4 — Build + -------------- + cd android-app && ANDROID_HOME=/opt/android-sdk ./gradlew assembleDebug 2>&1 | tail -5 + Confirm BUILD SUCCESSFUL. + + Step 5 — Commit and push + ------------------------ + git add android-app/app/src/main/res/layout/layout_instruments_sheet.xml \ + android-app/app/src/main/kotlin/org/terst/nav/ui/InstrumentHandler.kt \ + android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt + git commit -m "feat(instruments): display forecast wind direction (TWD) under TWS cell + + windDirDeg was fetched from Open-Meteo and stored in MarineConditions + but never shown. Adds value_twd sub-label below the TWS primary value." + git push github main && git push local main +timeout: "15m" +tags: + - "instruments" + - "wind" + - "forecast" diff --git a/scripts/.claude/ct-wind-wiring.txt b/scripts/.claude/ct-wind-wiring.txt new file mode 100644 index 0000000..09768ab --- /dev/null +++ b/scripts/.claude/ct-wind-wiring.txt @@ -0,0 +1,156 @@ +Context +------- +MainViewModel.addGpsPoint() (android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt, +line 67) builds TrackPoint with hardcoded wind zeroes: + + windSpeedKnots = 0.0, windAngleDeg = 0.0, isTrueWind = false + +LocationService already exposes live NMEA wind data: + LocationService.nmeaWindDataFlow: SharedFlow<WindData> (companion object, line 362) + +WindData (android-app/app/src/main/kotlin/org/terst/nav/sensors/WindData.kt): + windAngle: Double // degrees, relative or true + windSpeed: Double // knots + isTrueWind: Boolean + timestampMs: Long + +TrackPoint (android-app/app/src/main/kotlin/org/terst/nav/track/TrackPoint.kt) already has +windSpeedKnots, windAngleDeg, isTrueWind fields — they just aren't populated. + +Goal +---- +Cache the latest WindData in MainViewModel and use it when building TrackPoints. +Default to zero wind if no NMEA wind sentence has arrived yet. + +Step 1 — Red: write failing tests +---------------------------------- +Create android-app/app/src/test/kotlin/org/terst/nav/ui/MainViewModelWindTest.kt + +Setup boilerplate needed (Dispatchers.setMain / resetMain with UnconfinedTestDispatcher): + + import androidx.arch.core.executor.testing.InstantTaskExecutorRule + import kotlinx.coroutines.Dispatchers + import kotlinx.coroutines.ExperimentalCoroutinesApi + import kotlinx.coroutines.test.UnconfinedTestDispatcher + import kotlinx.coroutines.test.resetMain + import kotlinx.coroutines.test.runTest + import kotlinx.coroutines.test.setMain + import org.junit.After + import org.junit.Assert.assertEquals + import org.junit.Before + import org.junit.Rule + import org.junit.Test + import org.terst.nav.sensors.WindData + import org.terst.nav.ui.MainViewModel + + @OptIn(ExperimentalCoroutinesApi::class) + class MainViewModelWindTest { + + @get:Rule val instantTask = InstantTaskExecutorRule() + private val dispatcher = UnconfinedTestDispatcher() + + @Before fun setUp() { Dispatchers.setMain(dispatcher) } + @After fun tearDown() { Dispatchers.resetMain() } + + @Test fun `addGpsPoint uses zero wind when no wind update received`() = runTest { + val vm = MainViewModel() + vm.startTrack() + vm.addGpsPoint(37.0, -122.0, 5.0, 90.0) + val pt = vm.trackPoints.value.first() + assertEquals(0.0, pt.windSpeedKnots, 0.001) + assertEquals(0.0, pt.windAngleDeg, 0.001) + assertEquals(false, pt.isTrueWind) + } + + @Test fun `addGpsPoint uses latest wind data after updateWind`() = runTest { + val vm = MainViewModel() + vm.updateWind(WindData(windAngle = 45.0, windSpeed = 15.5, isTrueWind = true, timestampMs = 1000L)) + vm.startTrack() + vm.addGpsPoint(37.0, -122.0, 5.0, 90.0) + val pt = vm.trackPoints.value.first() + assertEquals(15.5, pt.windSpeedKnots, 0.001) + assertEquals(45.0, pt.windAngleDeg, 0.001) + assertEquals(true, pt.isTrueWind) + } + + @Test fun `addGpsPoint reflects wind update between fixes`() = runTest { + val vm = MainViewModel() + vm.startTrack() + vm.addGpsPoint(37.0, -122.0, 5.0, 90.0) + vm.updateWind(WindData(windAngle = 180.0, windSpeed = 8.0, isTrueWind = false, timestampMs = 2000L)) + vm.addGpsPoint(37.1, -122.0, 5.0, 90.0) + val pts = vm.trackPoints.value + assertEquals(0.0, pts[0].windSpeedKnots, 0.001) + assertEquals(8.0, pts[1].windSpeedKnots, 0.001) + } + } + +Run and confirm RED: + cd android-app && ./gradlew :app:testDebugUnitTest --tests "*MainViewModelWind*" + +Step 2 — Green: implement +-------------------------- +In android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt: + +1. Add import: + import org.terst.nav.sensors.WindData + +2. Add field after trackRepository: + private var latestWind: WindData? = null + +3. Add method: + fun updateWind(wind: WindData) { + latestWind = wind + } + +4. Update addGpsPoint() to use latestWind: + fun addGpsPoint(lat: Double, lon: Double, sogKnots: Double, cogDeg: Double) { + val wind = latestWind + val point = TrackPoint( + lat = lat, lon = lon, + sogKnots = sogKnots, cogDeg = cogDeg, + windSpeedKnots = wind?.windSpeed ?: 0.0, + windAngleDeg = wind?.windAngle ?: 0.0, + isTrueWind = wind?.isTrueWind ?: false, + timestampMs = System.currentTimeMillis() + ) + if (trackRepository.addPoint(point)) { + _trackPoints.value = trackRepository.getPoints() + } + } + +Run and confirm GREEN: + cd android-app && ./gradlew :app:testDebugUnitTest --tests "*MainViewModelWind*" + +Step 3 — Wire: feed wind updates from LocationService +------------------------------------------------------ +In android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt, +inside observeDataSources(), add after the anchorWatchState collector: + + lifecycleScope.launch { + LocationService.nmeaWindDataFlow.collect { wind -> + viewModel.updateWind(wind) + } + } + +No additional imports needed (LocationService is already imported). + +Step 4 — Verify all tests pass +------------------------------- +cd android-app && ./gradlew :app:testDebugUnitTest + +Confirm all existing tests still pass alongside the 3 new wind tests. + +Step 5 — Commit and push +------------------------- +git add -A && git commit -m "feat(track): wire NMEA wind data into GPS track points + +MainViewModel caches the latest WindData from LocationService.nmeaWindDataFlow +via updateWind(). addGpsPoint() populates TrackPoint wind fields from the cache, +defaulting to zero if no NMEA wind sentence has arrived yet. + +MainActivity.observeDataSources() feeds LocationService.nmeaWindDataFlow +into viewModel.updateWind() alongside the existing GPS and anchor observers. + +3 new unit tests in MainViewModelWindTest verify zero-default, wind capture, +and mid-track wind update behaviour." && git push origin main diff --git a/scripts/.claude/ct-wire-anchor-to-map.yaml b/scripts/.claude/ct-wire-anchor-to-map.yaml new file mode 100644 index 0000000..9ef8513 --- /dev/null +++ b/scripts/.claude/ct-wire-anchor-to-map.yaml @@ -0,0 +1,82 @@ +name: "Wire anchor watch state to map overlay" +description: "LocationService.anchorWatchState is collected in MainActivity but only updates SafetyFragment text — MapHandler.updateAnchorWatch() is never called, so the anchor point and radius circle never appear on the map" +agent: + model: "sonnet" + working_dir: "/workspace/nav" + instructions: | + Context + ------- + LocationService.anchorWatchState: StateFlow<AnchorWatchState> + (android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt, companion, line ~353) + + MapHandler.updateAnchorWatch(state: AnchorWatchState) is fully implemented — it shows + an anchor icon and radius circle on the map, or clears them when inactive: + (android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt, line ~139) + + In MainActivity.observeDataSources(), anchorWatchState IS collected, but only + to update a text field in SafetyFragment: + LocationService.anchorWatchState.collect { state -> + safetyFragment.updateAnchorStatus(...) + } + There is no call to mapHandler?.updateAnchorWatch(state) anywhere. + + AnchorWatchState: + (android-app/app/src/main/kotlin/org/terst/nav/AnchorWatchData.kt) + data class AnchorWatchState( + val isActive: Boolean, + val anchorLocation: Location?, + val watchCircleRadiusMeters: Double, + val setTimeMillis: Long + ) + + The map style must be loaded before calling updateAnchorWatch — gate on loadedStyleFlow. + + Goal + ---- + In the existing anchorWatchState collector in observeDataSources(), add a call to + mapHandler?.updateAnchorWatch(state) alongside the existing safetyFragment update. + Gate the map call on the style being loaded. + + Step 1 — Update the existing collector + --------------------------------------- + In android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt, + find the anchorWatchState collector in observeDataSources(): + + lifecycleScope.launch { + LocationService.anchorWatchState.collect { state -> + safetyFragment.updateAnchorStatus(if (state.isActive) "Active: ${state.watchCircleRadiusMeters}m" else "Inactive") + } + } + + Replace with: + + lifecycleScope.launch { + LocationService.anchorWatchState.collect { state -> + safetyFragment.updateAnchorStatus(if (state.isActive) "Active: ${state.watchCircleRadiusMeters}m" else "Inactive") + if (loadedStyleFlow.value != null) { + mapHandler?.updateAnchorWatch(state) + } + } + } + + No import changes needed. + + Step 2 — Build + -------------- + cd android-app && ANDROID_HOME=/opt/android-sdk ./gradlew assembleDebug 2>&1 | tail -5 + Confirm BUILD SUCCESSFUL. + + Step 3 — Commit and push + ------------------------ + git add android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt + git commit -m "fix(map): wire anchor watch state to map overlay + + The anchorWatchState collector in observeDataSources() was only updating + SafetyFragment text. Now also calls mapHandler.updateAnchorWatch(state) + when the map style is loaded, showing the anchor icon and radius circle." + git push github main && git push local main +timeout: "15m" +tags: + - "map" + - "anchor" + - "wiring" diff --git a/scripts/.claude/ct-wire-tidal-to-map.yaml b/scripts/.claude/ct-wire-tidal-to-map.yaml new file mode 100644 index 0000000..b5a1923 --- /dev/null +++ b/scripts/.claude/ct-wire-tidal-to-map.yaml @@ -0,0 +1,75 @@ +name: "Wire tidal current state to map overlay" +description: "LocationService.tidalCurrentState is emitted but never collected in MainActivity — MapHandler.updateTidalCurrents() is never called, so the tidal arrow layer is always empty" +agent: + model: "sonnet" + working_dir: "/workspace/nav" + instructions: | + Context + ------- + LocationService exposes a public StateFlow: + LocationService.tidalCurrentState: StateFlow<TidalCurrentState> + (android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt, companion object, line ~354) + + MapHandler has a fully-implemented method: + fun updateTidalCurrents(state: TidalCurrentState) + (android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt, line ~155) + + MainActivity.observeDataSources() + (android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt) + never subscribes to tidalCurrentState, so updateTidalCurrents() is never called. + The tidal arrow overlay on the map is always empty regardless of what data arrives. + + The map style must be loaded before calling updateTidalCurrents — use loadedStyleFlow + as a gate, the same way updateTrackLayer is gated (see the combine() pattern already + in observeDataSources for track points). + + Goal + ---- + Subscribe to LocationService.tidalCurrentState in MainActivity.observeDataSources() + and forward each emission to mapHandler?.updateTidalCurrents(state). + + The subscription should be gated on loadedStyleFlow being non-null (style loaded), + matching the pattern used for track points. + + Step 1 — Add the observer + ------------------------- + In android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt, + inside observeDataSources(), add after the existing collectors: + + lifecycleScope.launch { + loadedStyleFlow.filterNotNull().collect { + LocationService.tidalCurrentState.collect { state -> + mapHandler?.updateTidalCurrents(state) + } + } + } + + Or equivalently using combine(), consistent with the track pattern: + + lifecycleScope.launch { + loadedStyleFlow.filterNotNull() + .combine(LocationService.tidalCurrentState) { _, state -> state } + .collect { state -> mapHandler?.updateTidalCurrents(state) } + } + + No import changes needed — LocationService is already imported. + + Step 2 — Build + -------------- + cd android-app && ANDROID_HOME=/opt/android-sdk ./gradlew assembleDebug 2>&1 | tail -5 + Confirm BUILD SUCCESSFUL. + + Step 3 — Commit and push + ------------------------ + git add android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt + git commit -m "fix(map): wire tidal current state to map overlay in MainActivity + + LocationService.tidalCurrentState was emitted but never collected. + Subscribe in observeDataSources() gated on style load, forwarding + each TidalCurrentState to mapHandler.updateTidalCurrents()." + git push github main && git push local main +timeout: "15m" +tags: + - "map" + - "tidal" + - "wiring" |
