summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-06-05 00:31:12 +0000
committerClaude <noreply@anthropic.com>2026-06-05 00:31:12 +0000
commit3f7a1a181be300a04a8cfb00022aedc5d97676da (patch)
tree39c6b14dc598e7d56ae66796065bf281b9fbe68d
parent75c61c2b5b3fbda316dc4e82b6027d80ff7132d3 (diff)
parentbc8382014a8cd8cf781a95714b2b18f4920e2dce (diff)
Merge claude/location-retention-regression-AOycc: fix background location retention
- Stop GPS updates entirely when backgrounded and idle (was: drop to 5s ECONOMY) - Add PowerMode.NONE, ACTION_STOP_UPDATES; guard startLocationUpdatesInternal against NONE - Fix _currentPowerMode initial value FULL→NONE for accurate state tracking - Fix code-map GPS section (DeviceGpsProvider dead code, no No-GPS banner, replay=1 caveat) - Add review-intent-reality.md audit prompt + Living Documentation mandates to config.md https://claude.ai/code/session_0126zvB69ccVoFtx2o96oaE2
-rw-r--r--.agent/code-map.md7
-rw-r--r--.agent/config.md3
-rw-r--r--.agent/review-intent-reality.md114
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt12
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt4
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/PowerMode.kt7
6 files changed, 137 insertions, 10 deletions
diff --git a/.agent/code-map.md b/.agent/code-map.md
index eb2f564..1a36542 100644
--- a/.agent/code-map.md
+++ b/.agent/code-map.md
@@ -359,8 +359,11 @@ CI (`android.yml`) triggers only on pushes to `main`. Firebase App Distribution
## 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.
+- `LocationService` (foreground service) uses `FusedLocationProviderClient` for Android GPS. External GPS comes via NMEA TCP/UDP through `NmeaStreamManager` → `NmeaParser`, emitted on `LocationService.nmeaGpsPositionFlow`.
+- `LocationService.recomputeBestPosition()` fuses NMEA + Android GPS: prefers NMEA if < 5 s old, falls back to Android. Result is `bestPosition: StateFlow<GpsPosition?>`.
+- `locationFlow: SharedFlow<GpsPosition>(replay=1)` carries raw Android fixes. **`replay=1` means new subscribers immediately receive the last cached position regardless of age — there is no staleness expiry.** Consumers must check `gpsData.timestampMs` if they care about age.
+- `DeviceGpsProvider` implements `GpsProvider` and has a `fixLostRunnable` (10 s timeout), but is **dead code** — it is never instantiated. `LocationService` does not use the `GpsProvider` interface. There is no "No GPS" banner.
+- Background location: `throttleLocationIfIdle()` (called on `onPause`) sends `ACTION_STOP_UPDATES` to stop `fusedLocationClient` when neither recording nor anchor-watch is active. `restoreLocationMode()` (called on `onResume`) restarts appropriate mode.
### NMEA
- `NmeaStreamManager` handles connection lifecycle (reconnect on failure). Don't manage socket lifecycle elsewhere.
diff --git a/.agent/config.md b/.agent/config.md
index 56d9df5..376cd89 100644
--- a/.agent/config.md
+++ b/.agent/config.md
@@ -14,6 +14,7 @@ This is the primary source of truth for all AI agents working on **Nav**. These
| `mission.md` | **Mission & Values** — Strategic goals and agent personality. |
| `narrative.md` | **Background** — Historical context and evolution of the project. |
| `preferences.md` | **User Prefs** — Living record of user-specific likes/dislikes. |
+| `review-intent-reality.md` | **Audit Prompt** — Structured review to find gaps between documented intent and implemented reality. Run after major merges or when a regression reveals a code-map error. |
## 2. Branch Policy — MANDATORY SESSION START ACTION
@@ -48,3 +49,5 @@ The repo ships a pre-commit hook in `scripts/git-hooks/pre-commit` that hard-blo
1. **Continuous Capture:** Agents MUST proactively update the files in ".agent/" as new decisions, patterns, or user preferences are revealed.
2. **No Stale Instructions:** If a workflow or technical standard evolves, the agent is responsible for reflecting that change in the Master Rulebook immediately.
3. **Worklog Integrity:** The ".agent/worklog.md" must be updated at the start and end of EVERY session.
+4. **Code-Map Accuracy:** `code-map.md` must describe what the code **does**, not what was intended. If a refactor changes a class's role, removes a feature, or leaves dead code, update `code-map.md` in the same commit. Never leave a known-wrong entry — a wrong code-map is worse than no code-map.
+5. **Intent-Reality Audit:** After any significant merge or refactor, check whether existing code-map entries, comments, and `design.md` still reflect reality. When they don't, fix them immediately. For a full audit, run the prompt in `review-intent-reality.md`.
diff --git a/.agent/review-intent-reality.md b/.agent/review-intent-reality.md
new file mode 100644
index 0000000..7df10f2
--- /dev/null
+++ b/.agent/review-intent-reality.md
@@ -0,0 +1,114 @@
+# Intent-vs-Reality Review
+
+**Purpose:** Find all places where documented intent, comments, or code-map descriptions diverge from what the code actually does. Run this whenever a significant refactor lands, before merging a long-running branch, or whenever a regression reveals a code-map inaccuracy.
+
+**How to run:** paste the prompt below into a new agent session with access to the repo. It is intentionally exhaustive — expect 30–60 min of analysis.
+
+---
+
+## Prompt
+
+You are auditing the Nav Android codebase for gaps between **documented intent** and **implemented reality**. A gap is any case where a comment, doc file, code-map entry, or TODO describes behavior that differs from what the code actually does.
+
+Work through each category below. For each finding, report:
+- **File and line** (or doc file + section)
+- **What it claims** (quote the doc/comment)
+- **What the code does** (cite the relevant lines)
+- **Severity**: `MISLEADING` (wrong, will cause future bugs), `STALE` (was true, no longer is), or `ASPIRATIONAL` (planned but not built)
+
+### Category 1 — Code-map vs implementation
+
+Read `.agent/code-map.md` sections 3 (Source Package Map) and 8 (Context Tips & Gotchas). For every class or behavior mentioned:
+
+1. Verify the class exists at the stated path.
+2. Verify the described role matches the actual implementation.
+3. Flag any class listed with a role that has been superseded or is dead code.
+
+Pay special attention to:
+- Classes listed under `gps/` — `DeviceGpsProvider` has historically been misrepresented
+- The "Navigation / GPS" gotcha section — descriptions of flow and staleness behavior
+- Any mention of flows, StateFlows, or SharedFlows — check `replay` values and whether subscribers actually receive stale data
+- Any mention of UI features (banners, dialogs, buttons) — verify they exist in layouts and are wired up
+
+### Category 2 — Dead code with documented intent
+
+Search for code that is defined but never instantiated or called. These represent features that were planned, partially built, or left behind after a refactor:
+
+1. Run: find all classes implementing an interface — check if any implementation is never constructed anywhere (grep for `= MyClass(` and `MyClass(` across all Kotlin files).
+2. Look specifically at:
+ - `gps/GpsProvider.kt` and its implementations
+ - Any `Handler` with a posted `Runnable` that looks like a timeout/watchdog
+ - Any `companion object` constant named `*_TIMEOUT_MS` or `*_THRESHOLD_MS` — verify the timeout is actually used
+3. For each dead class/method, check git log to understand if it was once wired up and was removed in a refactor (use `git log --all -S 'ClassName'` to find commits that removed references).
+
+### Category 3 — Comments that describe the wrong behavior
+
+Scan for inline comments that assert behavior (`// GPS updates every 1 Hz`, `// always fresh`, `// cleared on pause`, `// foreground only`, etc.) and verify the code around them matches the claim:
+
+1. Search for `// TODO` entries — for each one, determine if the TODO was actually completed elsewhere (the code may have been fixed but the comment not removed).
+2. Search for comments referencing specific class names, method names, or behaviors — verify those classes/methods/behaviors still exist with the described properties.
+3. Look for comments in `MainActivity.kt`, `LocationService.kt`, and `MainViewModel.kt` specifically — these are the highest-churn files.
+
+### Category 4 — Architectural invariants not enforced
+
+Read `.agent/code-map.md` section 5 (Key Architectural Invariants). For each invariant:
+
+1. Verify the invariant holds in the current code.
+2. If it was intended to be enforced by a specific mechanism (e.g., `@Synchronized`, a guard variable, a specific flow), verify that mechanism is actually in place.
+3. Flag any invariant that is stated as a rule but has no enforcement — these are regression risks.
+
+Current invariants to check:
+- Track storage source-of-truth = GPX/SAF (Room is read-cache only)
+- `getExternalFilesDir()` never used for user data
+- `NavLogger` as primary debug tool (not just Android `Log`)
+- MOB button always visible and non-dismissible
+- `InMemoryLogbookRepository` has `@Synchronized` on save/getAll/reload
+- `VoiceLogFragment` passes both `onSelect` and `onReport` to `SavedTrackAdapter`
+- MapLibre speed-color uses `Expression.step()` not `Expression.get("color")`
+- NMEA wind data is apparent, converted to true in `PerformanceViewModel`
+- All display values go through `UnitPrefs`
+
+### Category 5 — Design doc vs shipped code
+
+Read `.agent/design.md`. For any feature described as "implemented" or in a "completed" state:
+
+1. Verify the feature exists in the code.
+2. Verify the implementation matches the described design (API shape, data flow, UI layout).
+3. Flag features described as in-progress that appear to have been abandoned without a code-map update.
+
+### Category 6 — Worklog vs current state
+
+Read `.agent/worklog.md` section "Recently Completed". For each completed item:
+
+1. Spot-check that the described change is actually present in the code.
+2. Look for cases where a "completed" item was later reverted or partially undone by a subsequent merge (use `git log --all --oneline -- <file>` to trace history).
+
+---
+
+## Output format
+
+Group findings by category. Use this structure:
+
+```
+## [Category N] — [Category name]
+
+### Finding: [short title]
+- **Doc claim**: "[exact quote]" — `file:line`
+- **Code reality**: [what the code actually does] — `file:line`
+- **Severity**: MISLEADING / STALE / ASPIRATIONAL
+- **Suggested fix**: [one sentence — update the doc, delete the dead code, or implement the feature]
+```
+
+End with a **Summary** section: total findings by severity, and the top 3 highest-risk gaps (most likely to cause future bugs or agent confusion).
+
+---
+
+## After the review
+
+For each `MISLEADING` finding: fix the code-map or comment immediately in the same session. Do not leave the session with any known-wrong documentation.
+
+For `ASPIRATIONAL` findings: add a note to `.agent/worklog.md` backlog if the feature is still wanted, or delete the dead code if it is not.
+
+For `STALE` findings: update the documentation to match current reality.
+
+Commit all documentation fixes as a single commit: `"docs(agent): intent-reality audit — correct N misleading entries"`.
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt b/android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt
index c47c7a5..c1ff60e 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/LocationService.kt
@@ -270,6 +270,10 @@ class LocationService : Service() {
}
ACTION_START_ECONOMY -> setPowerMode(PowerMode.ECONOMY)
ACTION_START_FULL -> setPowerMode(PowerMode.FULL)
+ ACTION_STOP_UPDATES -> serviceScope.launch {
+ stopLocationUpdatesInternal()
+ _currentPowerMode.emit(PowerMode.NONE)
+ }
ACTION_START_ANCHOR_WATCH -> {
val radius = intent.getDoubleExtra(EXTRA_WATCH_RADIUS, AnchorWatchState.DEFAULT_WATCH_CIRCLE_RADIUS_METERS)
serviceScope.launch {
@@ -316,6 +320,7 @@ class LocationService : Service() {
@SuppressLint("MissingPermission")
private fun startLocationUpdatesInternal(powerMode: PowerMode) {
+ if (powerMode == PowerMode.NONE) return
val priority = if (powerMode == PowerMode.FULL)
Priority.PRIORITY_HIGH_ACCURACY
else
@@ -411,8 +416,9 @@ class LocationService : Service() {
companion object {
const val ACTION_START_FOREGROUND_SERVICE = "ACTION_START_FOREGROUND_SERVICE"
const val ACTION_STOP_FOREGROUND_SERVICE = "ACTION_STOP_FOREGROUND_SERVICE"
- const val ACTION_START_ECONOMY = "ACTION_START_ECONOMY"
- const val ACTION_START_FULL = "ACTION_START_FULL"
+ const val ACTION_START_ECONOMY = "ACTION_START_ECONOMY"
+ const val ACTION_START_FULL = "ACTION_START_FULL"
+ const val ACTION_STOP_UPDATES = "ACTION_STOP_UPDATES"
const val ACTION_START_ANCHOR_WATCH = "ACTION_START_ANCHOR_WATCH"
const val ACTION_STOP_ANCHOR_WATCH = "ACTION_STOP_ANCHOR_WATCH"
const val ACTION_UPDATE_WATCH_RADIUS = "ACTION_UPDATE_WATCH_RADIUS"
@@ -469,7 +475,7 @@ class LocationService : Service() {
_currentDirectionDeg.value = directionDeg
}
- private val _currentPowerMode = MutableStateFlow(PowerMode.FULL)
+ private val _currentPowerMode = MutableStateFlow(PowerMode.NONE)
val currentPowerMode: StateFlow<PowerMode> get() = _currentPowerMode
private val _tidalCurrentState = MutableStateFlow(TidalCurrentState())
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt b/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt
index 94b9c81..476fb0d 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt
@@ -505,11 +505,11 @@ class MainActivity : AppCompatActivity(), SafetyFragment.SafetyListener {
private fun locationServiceIntent(action: String) =
Intent(this, LocationService::class.java).apply { this.action = action }
- /** Drop to economy rate when the app goes to the background and nothing needs precision. */
+ /** Stop location updates when the app is backgrounded and nothing needs location. */
private fun throttleLocationIfIdle() {
if (!serviceStarted) return
if (!viewModel.isRecording.value && !LocationService.anchorWatchState.value.isActive) {
- startService(locationServiceIntent(LocationService.ACTION_START_ECONOMY))
+ startService(locationServiceIntent(LocationService.ACTION_STOP_UPDATES))
}
}
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/PowerMode.kt b/android-app/app/src/main/kotlin/org/terst/nav/PowerMode.kt
index 22e1b77..d55c965 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/PowerMode.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/PowerMode.kt
@@ -1,7 +1,8 @@
package org.terst.nav
enum class PowerMode(val gpsUpdateIntervalMillis: Long) {
- FULL(1000L), // 1 Hz
- ECONOMY(5000L), // 0.2 Hz
- ANCHOR_WATCH(10000L) // 0.1 Hz
+ NONE(0L), // no location updates (background idle)
+ FULL(1000L), // 1 Hz, high accuracy
+ ECONOMY(5000L), // 0.2 Hz, balanced accuracy
+ ANCHOR_WATCH(10000L) // 0.1 Hz, balanced accuracy
}