<feed xmlns='http://www.w3.org/2005/Atom'>
<title>nav.git/test-runner/src/main/kotlin/org/terst/nav/track, branch main</title>
<subtitle>nav — android navigation app
</subtitle>
<id>https://git.terst.org/nav.git/atom?h=main</id>
<link rel='self' href='https://git.terst.org/nav.git/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/'/>
<updated>2026-05-28T07:09:43+00:00</updated>
<entry>
<title>Tech debt: delete com.example.androidapp ghost package, symlink TackDetector, fix forecast current hour</title>
<updated>2026-05-28T07:09:43+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-05-28T07:09:43+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/commit/?id=c498b33300d260bbe64bec49de9ef78d9a322a6d'/>
<id>urn:sha1:c498b33300d260bbe64bec49de9ef78d9a322a6d</id>
<content type='text'>
- Deleted all 18 stale source files in com.example.androidapp (main + test)
  that duplicated org.terst.nav implementations from an earlier namespace
- Fixed the 3 tide model files (TidePrediction/Station/Constituent) whose
  package declarations read com.example.androidapp.data.model despite living
  in org.terst.nav.data.model directories
- Fixed HarmonicTideCalculator.kt imports to match the corrected package
- Fixed TideModelTest.kt package declaration
- Migrated HarmonicTideCalculatorTest to org.terst.nav.tide with correct imports
- Replaced test-runner's TackDetector.kt copy with a symlink to the android-app
  canonical source so the two can never diverge again
- Fixed addGpsPoint to pick the current wall-clock hour's forecast slot instead
  of always using the app-start hour (first item in forecast list)

https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
</content>
</entry>
<entry>
<title>Fix ANR, false tack detections, and empty-state flash on track load</title>
<updated>2026-05-26T07:46:09+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-05-26T07:46:09+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/commit/?id=3673ed0ef79d776c6da3bfa0db9de8b08326fd32'/>
<id>urn:sha1:3673ed0ef79d776c6da3bfa0db9de8b08326fd32</id>
<content type='text'>
ANR: getSavedTracks() had no IO dispatcher — TackDetector ran on main
thread at O(n²) cost (~77M ops for a 2.5h track). Added withContext(IO).

TackDetector: replaced O(n) list.filter with O(log n) binary-search
subList for before/after windows; O(n²) → O(n log n). Raised MIN_DELTA
60°→75° to stop detecting ordinary course corrections as maneuvers.
Removed MAX_DELTA cap entirely — 180° crash-tacks are valid; the
circularMAD stability windows are the real quality gate.

TrackDetailSheet: dropped the arbitrary abs(delta)&gt;70 Tack/Jibe
heuristic. Now uses true wind angle when available for classification;
falls back to "Maneuver" when TWD is absent.

SavedTracksFragment: added isLoadingTracks StateFlow (MainViewModel);
combine() suppresses the empty-state layout while the initial IO load
runs so it no longer flashes "No saved tracks yet" before data arrives.

16/16 TackDetector tests pass.

https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
</content>
</entry>
<entry>
<title>Redesign tack detection: time-based windows + circularMAD stability</title>
<updated>2026-05-26T06:39:05+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-05-26T06:39:05+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/commit/?id=8dbf3f883274fc0ddd16e5507f732629c85e3c91'/>
<id>urn:sha1:8dbf3f883274fc0ddd16e5507f732629c85e3c91</id>
<content type='text'>
Replace the point-count window (HALF_WIN=2) with 30s time-based settle
windows that give consistent reliability regardless of GPS update rate
(1 Hz FULL vs 0.2 Hz ECONOMY). Key changes:

- Before/after windows are time-based (T_SETTLE=30s) around a guard
  zone (T_MANEUVER=30s), not point counts.
- Stability uses circularMAD (&lt;20°) across all fixes in each window,
  replacing the single-pair spread check. This correctly handles
  0°/360° wrap and suppresses random anchor-watch COG noise (MAD≈90°).
- No SOG gate: averaging 30 fixes over 30s reduces noise by √30
  regardless of boat speed. A boat at anchor has MAD≈90° and is
  rejected by the stability check.
- MAX_DELTA raised to 160° (covers deep jibes the old 140° missed).
- START_SKIP reduced to 60s (stability check handles cold-start noise).
- De-duplication uses adjacent-candidate comparison so a long stream
  of raw candidates from one maneuver stays in a single group even if
  it spans &gt;MIN_GAP_MS in total.
- Position refined to max instantaneous heading rate within the
  maneuver zone.
- Mode: CRUISING (conservative). Race mode noted as future backlog.

16/16 unit tests pass at 1 Hz (FULL mode GPS rate).

https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
</content>
</entry>
<entry>
<title>Fix Long overflow in TackDetector: var lastTackMs = Long.MIN_VALUE caused subtraction overflow, filtering every tack</title>
<updated>2026-05-20T22:18:38+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-05-20T22:18:38+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/commit/?id=62cc93086498d57ef7298eddb40cc382dfc10c6d'/>
<id>urn:sha1:62cc93086498d57ef7298eddb40cc382dfc10c6d</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Add saved tracks browser, tack detection, and map zoom-to-bounds</title>
<updated>2026-05-12T23:13:55+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-05-12T23:13:55+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/commit/?id=f881a1f91f4e9f832ab987fce2b5220e100cee68'/>
<id>urn:sha1:f881a1f91f4e9f832ab987fce2b5220e100cee68</id>
<content type='text'>
- TackDetector: sliding-window COG algorithm (60-140deg delta = tack/jibe)
  with stability check to prevent mixed-window false positives; 13 tests green
- TackEvent, SavedTrack: new models wrapping points + summary + detected tacks
- TrackRepository.getSavedTracks(): returns List&lt;SavedTrack&gt; with lazy load
- MapHandler: zoomToTrackBounds (LatLngBounds fit with padding),
  panToPoint (log step-through), updateTackLayer (yellow circle markers)
- MainViewModel: savedTracks + selectedTrack StateFlows, panToPosition SharedFlow
- SavedTracksFragment: RecyclerView list with date/distance/duration/tack count
- TrackDetailSheet: bottom sheet with stats + event log (tacks, speed changes,
  start/end); tapping an entry pans the map to that position
- MainActivity: zoom map on track complete, wire Saved Tracks button in
  instrument sheet, observe panToPosition to drive map, show tack markers
  when a saved track is selected

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Merge origin/main — unite all work onto one branch</title>
<updated>2026-04-23T23:10:11+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-04-23T23:10:11+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/commit/?id=e1db9200e9d44c10450361cc8984a45b2eda87b7'/>
<id>urn:sha1:e1db9200e9d44c10450361cc8984a45b2eda87b7</id>
<content type='text'>
Brings in dev log (NavLogger), UnitPrefs, MapLayerManager, HUD views,
conditions throttling, track save/load pipeline, improved ParticleWindView
(antimeridian-aware, dynamic particle count), Snackbar error surfacing,
and all other main-branch work from the prior session.

Combined with master's hardware source flags, vessel registry, crew
management, thermal alarm, CPA collision alerts, and track stats.

Also documents primary branch policy in CLAUDE.md and .agent/config.md:
all work merges to main, never master.

https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
</content>
</entry>
<entry>
<title>feat: CPA/TCPA collision alerts and live track stats</title>
<updated>2026-04-22T08:45:55+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-04-22T08:45:55+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/commit/?id=87f9cb753da3325d13fdbc442d526567e056d6e1'/>
<id>urn:sha1:87f9cb753da3325d13fdbc442d526567e056d6e1</id>
<content type='text'>
CPA/TCPA alerts:
- CpaAlert data class with severity (CAUTION/WARNING/DANGER) and label()
- CpaThresholds: 0.5/1.0/2.0 nm, max TCPA 30 min
- MainViewModel.updateCpaAlerts() runs on every GPS update and AIS
  sentence; skips stationary contacts; filters TCPA ≤ 0 or &gt; 30 min;
  sorts by TCPA ascending
- SafetyFragment: new COLLISION AVOIDANCE card shows live alert list,
  color-coded by severity; clears to "No traffic alerts" when safe
- MainActivity: observes cpaAlerts → safetyFragment.updateCpaAlerts()

Track stats:
- TrackStats data class: distanceNm, durationMs, avgSogKnots,
  durationFormatted (m:ss / h:mm:ss)
- TrackRepository.haversineNm() + computeStats() using point timestamps
- MainViewModel exposes trackStats StateFlow, cleared on stopTrack()
- activity_main: dark pill overlay bar (tv_track_stats) above map
- MainActivity: shows "● REC  0.00nm  m:ss  x.xkt avg" while recording

Tests: 95 total, all GREEN (+16 new — 9 TrackStats, 7 CpaAlert)

https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
</content>
</entry>
<entry>
<title>feat(ui): surface trip planning and reports in instrument sheet</title>
<updated>2026-04-04T07:10:41+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-04-04T07:10:41+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/commit/?id=9f01ddfba17dda7fb386e83f007c671fec6d5b8e'/>
<id>urn:sha1:9f01ddfba17dda7fb386e83f007c671fec6d5b8e</id>
<content type='text'>
</content>
</entry>
<entry>
<title>fix(ui): make record track FAB visible (#1)</title>
<updated>2026-04-02T21:28:00+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-04-02T21:28:00+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/commit/?id=5416c8ccc9220bc36e7af3febcbdd9d86e88cf30'/>
<id>urn:sha1:5416c8ccc9220bc36e7af3febcbdd9d86e88cf30</id>
<content type='text'>
* Add GpsPosition data class and NMEA RMC parser with tests

- GpsPosition: latitude, longitude, sog (knots), cog (degrees true), timestampMs
- NmeaParser.parseRmc: handles GP/GN talker IDs, void status, malformed input
- SOG/COG default to 0.0 when fields absent; S/W coords are negative
- 13 unit tests: GpsPositionTest (2), NmeaParserTest (11) — all GREEN

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* feat: add harmonic tide height predictions (Section 3.2 / 4.2)

Implement offline harmonic tide prediction as specified in COMPONENT_DESIGN.md:
- TideConstituent: name, speedDegPerHour, amplitudeMeters, phaseDeg
- TidePrediction: timestampMs, heightMeters
- TideStation: id, name, lat, lon, datumOffsetMeters, constituents
- HarmonicTideCalculator: predictHeight(), predictRange(), findHighLow()
  Formula: h(t) = Z0 + Σ [ Hi × cos( ωi × (t − t0) − φi ) ]
- 15 unit tests covering all calculation paths

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* feat: implement isochrone-based weather routing (Section 3.4)

* feat: implement PDF logbook export (Section 4.8)

- LogbookEntry data class: timestampMs, lat/lon, SOG, COG, wind, baro, depth, event/notes
- LogbookFormatter: UTC time, position (deg/dec-min), 16-pt compass, row/page builders
- LogbookPdfExporter: landscape A4 PDF via android.graphics.pdf.PdfDocument with column headers,
  alternating row shading, and table border
- 20 unit tests covering all formatting helpers and data model behaviour

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* feat: offline GRIB staleness checker, ViewModel integration, and UI badge

- Add GribRegion, GribFile data models and GribFileManager interface
- Add InMemoryGribFileManager for testing and default use
- Add GribStalenessChecker with FreshnessResult sealed class (Fresh/Stale/NoData)
- Integrate weatherStaleness StateFlow into MainViewModel (checked after loadWeather)
- Add yellow staleness banner TextView to fragment_map.xml
- Wire staleness banner in MapFragment (shown on Stale, hidden on Fresh/NoData)
- Add GribStalenessCheckerTest (4 TDD tests)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* feat: satellite GRIB download with bandwidth optimisation (§9.1)

Implements weather data download over Iridium satellite links:
- GribParameter enum with SATELLITE_MINIMAL set (wind + pressure only)
- SatelliteDownloadRequest data class (region, params, forecast hours, resolution)
- SatelliteGribDownloader: size/time estimation, abort-on-oversized, pluggable fetcher
- 8 unit tests covering estimation scaling, minimal param set, and download outcomes

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* feat: add AnchorWatchHandler UI with Depth/Rode Out inputs and suggested radius

- Add AnchorWatchState with calculateRecommendedWatchCircleRadius, which
  uses ScopeCalculator.watchCircleRadius (Pythagorean scope formula) and
  falls back to rode length when geometry is invalid
- Add AnchorWatchHandler Fragment with EditText inputs for Depth (m) and
  Rode Out (m); updates suggested watch circle radius live via TextWatcher
- Add fragment_anchor_watch.xml layout
- Wire AnchorWatchHandler into bottom navigation (MainActivity + menu)
- Add AnchorWatchStateTest covering valid geometry, short-rode fallback

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* feat(safety): log wind and current conditions at MOB activation (Section 4.6)

Per COMPONENT_DESIGN.md Section 4.6, the MOB navigation view must display
wind and current conditions at the time of the event.

- MobEvent: add nullable windSpeedKt, windDirectionDeg, currentSpeedKt,
  currentDirectionDeg fields captured at the exact moment of activation
- MobAlarmManager.activate(): accept optional wind/current params and
  forward them into MobEvent (defaults to null for backward compatibility)
- LocationService (new): aggregates live SensorData (resolves true wind via
  TrueWindCalculator) and marine-forecast current conditions; snapshot()
  provides a point-in-time EnvironmentalSnapshot for safety-critical logging
- MobAlarmManagerTest: add tests for wind/current storage and null defaults
- LocationServiceTest (new): covers snapshot, true-wind resolution,
  current-condition updates, and the latestSensor flow

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* feat(gps): implement NMEA/Android GPS sensor fusion in LocationService

Adds priority-based selection between NMEA GPS (dedicated marine GPS,
higher priority) and Android system GPS (fallback) within LocationService.

Selection policy:
1. Prefer NMEA when its most recent fix is fresh (≤ nmeaStalenessThresholdMs, default 5 s)
2. Fall back to Android GPS when NMEA is stale
3. Use stale NMEA only when Android has never reported a fix
4. bestPosition is null until at least one source reports

New public API:
- GpsSource enum (NONE, NMEA, ANDROID)
- LocationService.updateNmeaGps(GpsPosition)
- LocationService.updateAndroidGps(GpsPosition)
- LocationService.bestPosition: StateFlow&lt;GpsPosition?&gt;
- LocationService.activeGpsSource: StateFlow&lt;GpsSource&gt;
- Injectable clockMs parameter for deterministic unit tests

Adds 7 unit tests covering: no-data state, fresh NMEA priority,
stale NMEA fallback, only-NMEA/only-Android scenarios, exact-threshold
edge case, and NMEA recovery after Android takeover.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* feat(gps): add fix-quality (accuracy) tier to GPS sensor fusion

Extend LocationService's source-selection policy with a quality-aware
"marginal staleness" zone between the primary and a new extended
staleness threshold (default 10 s):

  1. Fresh NMEA (≤ primary threshold, 5 s)  →  always prefer NMEA
  2. Marginally stale NMEA (5–10 s)         →  prefer NMEA only when
     GpsPosition.accuracyMeters is strictly better than Android's;
     fall back to Android conservatively when accuracy data is absent
  3. Very stale NMEA (&gt; 10 s)               →  always prefer Android
  4. Only one source available               →  use it regardless of age

Changes:
- GpsPosition: add nullable accuracyMeters field (default null, no
  breaking change to existing callers)
- LocationService: add nmeaExtendedThresholdMs constructor parameter;
  recomputeBestPosition() now implements three-tier logic; extract
  GpsPosition.hasStrictlyBetterAccuracyThan() helper
- LocationServiceTest: expose nmeaExtendedThresholdMs in fusionService
  helper; add posWithAccuracy helper; add 4 new test cases covering
  accuracy-based NMEA preference, worse-accuracy fallback, no-accuracy
  conservative fallback, and very-stale unconditional fallback

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* chore: add .gitignore, pull-crash-logs script, updated agent permissions

- .gitignore: exclude agent artifacts (.claudomator-env, .agent-home/, crash-logs/)
- scripts/pull-crash-logs: download Crashlytics crash reports via Firebase CLI
- .claude/settings.local.json: add Android SDK/emulator/adb permission rules

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* chore: update CI workflow to target main branch

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* fix: resolve all Kotlin compilation errors blocking CI build

- Migrate kapt → KSP (Kotlin 2.0 + kapt is broken; KSP is the supported path)
- Fix duplicate onResume() override in MainActivity
- Fix wrong package imports: com.example.androidapp.data.model → org.terst.nav.data.model
  across GribFileManager, GribStalenessChecker, SatelliteGribDownloader,
  LogbookFormatter, LogbookPdfExporter, IsochroneRouter, AnchorWatchHandler
- Create missing SensorData, ApparentWind, TrueWindData, TrueWindCalculator classes
- Inline missing ScopeCalculator formula (Pythagorean) in AnchorWatchState

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* feat(track): implement GPS track recording with map overlay

- TrackRepository + TrackPoint wired into MainViewModel:
  isRecording/trackPoints StateFlows, startTrack/stopTrack/addGpsPoint
- MapHandler.updateTrackLayer(): lazily initialises a red LineLayer
  and updates GeoJSON polyline from List&lt;TrackPoint&gt;
- fab_record_track FAB in activity_main.xml (top|end of bottom nav);
  icon toggles between ic_track_record and ic_close while recording
- MainActivity feeds every GPS fix into viewModel.addGpsPoint() and
  observes trackPoints to redraw the polyline in real time
- ic_track_record.xml vector drawable (red record dot)
- 8 TrackRepositoryTest tests all GREEN

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* test(ci): share APKs between jobs and expand smoke tests

CI — build job now uploads both APKs as the 'test-apks' artifact.
smoke-test job downloads them and passes -x assembleDebug
-x assembleDebugAndroidTest to skip recompilation (~4 min saved).
Test results uploaded as 'smoke-test-results' artifact on every run.

Smoke tests expanded from 1 → 11 tests covering:
  - MainActivity launches without crash
  - All 4 bottom-nav tabs are displayed
  - Safety tab: Safety Dashboard, ACTIVATE MOB, ANCHOR WATCH visible
  - Log tab: voice-log mic FAB visible
  - Instruments tab: bottom sheet displayed
  - Map tab: returns from overlay, mapView visible
  - MOB FAB: always visible, visible on Safety tab
  - Record Track FAB: displayed, toggles to Stop Recording, toggles back

MainActivity: moved isRecording observer to initializeUI() so the
FAB content description updates without requiring GPS permission
(needed for emulator tests that run without location permission).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* refactor: address simplify review findings

TrackRepository.addPoint() now returns Boolean (true if point was
added). MainViewModel.addGpsPoint() only updates _trackPoints StateFlow
when a point is actually appended — eliminates ~3,600 no-op flow
emissions per hour when not recording.

MainActivity: loadedStyle promoted from nullable field to
MutableStateFlow&lt;Style?&gt;; trackPoints observer uses filterNotNull +
combine so no track points are silently dropped if the style loads
after the first GPS fix.

Smoke tests: replaced 11× ActivityScenario.launch().use{} with a
single @get:Rule ActivityScenarioRule — same isolation, less
boilerplate.

CI: removed redundant app-debug artifact upload (app-debug.apk is
already included inside the test-apks artifact).

Removed stale/placeholder comments from MainActivity.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* fix(ui): anchor record track FAB to instrument sheet to prevent it being hidden

The FAB was anchored to bottom_navigation, placing it in the peek zone
of the instrument bottom sheet (120dp) and making it invisible to users.
Re-anchoring to instrument_bottom_sheet keeps the button visible and
makes it track naturally with sheet drag gestures.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

* fix(ui): anchor MOB FAB to instrument sheet to match record-track FAB

The fab_mob was still anchored to bottom_navigation using the same
top|start pattern that caused fab_record_track to be hidden behind
the instrument sheet's 16dp elevation. Apply the same fix so the
safety-critical MOB button is not occluded.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;

---------

Co-authored-by: Claudomator Agent &lt;agent@claudomator&gt;
Co-authored-by: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
Co-authored-by: Agent &lt;agent@example.com&gt;
Co-authored-by: Claude Agent &lt;agent@claude.ai&gt;</content>
</entry>
<entry>
<title>refactor: address simplify review findings</title>
<updated>2026-03-25T18:23:54+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-03-25T18:23:54+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/commit/?id=e68212991935d33a4baca77d88cd20a82fbcf6a6'/>
<id>urn:sha1:e68212991935d33a4baca77d88cd20a82fbcf6a6</id>
<content type='text'>
TrackRepository.addPoint() now returns Boolean (true if point was
added). MainViewModel.addGpsPoint() only updates _trackPoints StateFlow
when a point is actually appended — eliminates ~3,600 no-op flow
emissions per hour when not recording.

MainActivity: loadedStyle promoted from nullable field to
MutableStateFlow&lt;Style?&gt;; trackPoints observer uses filterNotNull +
combine so no track points are silently dropped if the style loads
after the first GPS fix.

Smoke tests: replaced 11× ActivityScenario.launch().use{} with a
single @get:Rule ActivityScenarioRule — same isolation, less
boilerplate.

CI: removed redundant app-debug artifact upload (app-debug.apk is
already included inside the test-apks artifact).

Removed stale/placeholder comments from MainActivity.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
