<feed xmlns='http://www.w3.org/2005/Atom'>
<title>nav.git/test-runner/src/test, 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-27T05:38:24+00:00</updated>
<entry>
<title>Add RACE mode to TackDetector for rapid mark-rounding detection</title>
<updated>2026-05-27T05:38:24+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-05-27T05:38:24+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/commit/?id=4f87ae6cda6131012447a8b2ddde088bb8844b04'/>
<id>urn:sha1:4f87ae6cda6131012447a8b2ddde088bb8844b04</id>
<content type='text'>
Introduces TackMode enum (CRUISING/RACE) and per-mode Config, replacing
the previous single set of hardcoded constants.

RACE config: T_SETTLE=12s, T_MANEUVER=20s, STAB_MAX=30°, MIN_GAP=20s,
START_SKIP=20s — tighter windows for quick successive tacks at race
starts and mark roundings. CRUISING remains the default.

Two new tests demonstrate the behavioral difference: RACE fires earlier
(past 20s cold-start vs 60s) and keeps separate events for a 50s gap
that CRUISING deduplicates (50s &lt; 60s MIN_GAP). 18/18 passing.

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>fix+feat: CI build fixes and hardware source feature flags</title>
<updated>2026-04-22T09:02:46+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-04-22T09:02:46+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/commit/?id=e141e1ef46ebb2a61f452122201a77527a5a1c9e'/>
<id>urn:sha1:e141e1ef46ebb2a61f452122201a77527a5a1c9e</id>
<content type='text'>
CI fixes:
- ParticleWindView: bounds.lonEast → bounds.northEast.longitude
  (MapLibre 13.x API; lonEast not available as property)
- VesselRegistryFragment: Vessel(id="") and CrewMember(id="") →
  include required name="" so copy() compiles

Hardware source feature flags:
- HardwareSource enum: AIS_RECEIVER, NMEA_INSTRUMENTS
- FeatureFlags: SharedPreferences-backed; all sources default OFF
  (app works phone-only out of the box)
- NavApplication: exposes featureFlags singleton
- SafetyFragment: "HARDWARE DATA SOURCES" card with two MaterialSwitch
  toggles; reads initial state from FeatureFlags; calls
  SafetyListener.onHardwareSourceChanged on change
- AIS_RECEIVER flag:
  - MainViewModel.processAisSentence() / refreshAisFromInternet() skip
    when disabled; onHardwareSourceChanged() clears targets + cpaAlerts
- NMEA_INSTRUMENTS flag:
  - LocationService skips nmeaStreamManager.start() on service launch
    when disabled
  - New ACTION_START_NMEA / ACTION_STOP_NMEA intents allow live toggle
  - MainActivity.onHardwareSourceChanged() sends the appropriate intent

Tests: 102 total, all GREEN (+7 FeatureFlagsTest)

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: vessel registry, crew management, and thermal alarm</title>
<updated>2026-04-22T05:35:49+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-04-22T05:35:49+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/commit/?id=eeafe2f0f3a3718bd52e9c333894e1abbc0611ab'/>
<id>urn:sha1:eeafe2f0f3a3718bd52e9c333894e1abbc0611ab</id>
<content type='text'>
- Vessel/CrewMember data classes with full profile fields
  (MMSI, callsign, flag, home port, hull type, dimensions, roles)
- VesselRepository: in-memory CRUD for own vessel, fleet, and crew;
  getOwnVessel(), getSkipper(), getCrewByRole() helpers
- VesselRegistryFragment: own-vessel card + crew list + known-vessels
  list; AlertDialog editors for add/edit; long-press to delete
- ic_vessel.xml sailboat icon; 5th bottom-nav tab "Vessel"
- ThermalMonitor: thermalFlow() using Intent.ACTION_BATTERY_CHANGED
  (battery °C) + PowerManager.OnThermalStatusChangedListener (API 29+);
  ThermalState OK/WARM/HOT/CRITICAL with distinct-until-changed filter
- MainActivity: startThermalMonitoring() sounds mob_alarm + Toast on
  HOT (≥45°C) and CRITICAL (≥50°C); auto-silences on OK/WARM
- MainViewModel: thermalState and thermalReading StateFlows
- 18 VesselRepositoryTest cases — all GREEN (test-runner)
- design.md: updated competitive matrix + feature implementation status

https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
</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>Merge branch 'master' of /site/git.terst.org/repos/nav</title>
<updated>2026-03-16T01:10:08+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-03-16T01:10:08+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/nav.git/commit/?id=9f694c364e547cb767a8f76a0cabf25ee06d6cc7'/>
<id>urn:sha1:9f694c364e547cb767a8f76a0cabf25ee06d6cc7</id>
<content type='text'>
</content>
</entry>
</feed>
