summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-04-03 08:05:24 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-04-03 08:05:24 +0000
commit5d358cd570075d36a61f9a37bb80c64f8a0a7e2a (patch)
treefa03f40d3c6a96da726b591d269c152adbdf7210 /scripts
parent9417a7c6b08da362ad97e85973b7570e05d4f0b5 (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>
Diffstat (limited to 'scripts')
-rw-r--r--scripts/.claude/ct-fix-weather-fallback.yaml64
-rw-r--r--scripts/.claude/ct-remove-mock-tidal.yaml83
-rw-r--r--scripts/.claude/ct-show-swell-direction.yaml75
-rw-r--r--scripts/.claude/ct-show-wind-direction.yaml92
-rw-r--r--scripts/.claude/ct-wind-wiring.txt156
-rw-r--r--scripts/.claude/ct-wire-anchor-to-map.yaml82
-rw-r--r--scripts/.claude/ct-wire-tidal-to-map.yaml75
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"