summaryrefslogtreecommitdiff
AgeCommit message (Collapse)Author
2026-05-20Fix Kotlin conflict markers left in TrackDetailSheet.ktClaude
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-20Fix XML conflict markers left in layout_track_detail_sheet.xmlClaude
https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-20Fix tack detection and convert track detail to embedded-map fragmentClaude
TackDetector: revert STABILITY_MAX to 30° (was tightened too far to 20°), add 120s start-skip to filter GPS cold-start false positives, keep 45s minimum gap between tacks. TrackDetailSheet: convert from BottomSheetDialogFragment to a regular Fragment with an embedded MapLibreMap in the top half. This fixes the map centering issue (the embedded map is always sized to its own frame), eliminates accidental dismissal when dragging the map, and makes the view self-contained — back navigation restores the track list via the fragment back stack. Tack markers rendered on the embedded map; tapping a log event pans the embedded map directly. https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-19Reduce location resource usage: economy mode by default, lifecycle throttlingClaude
- Start LocationService at ECONOMY (5 s, PRIORITY_BALANCED_POWER_ACCURACY) instead of FULL (1 Hz, PRIORITY_HIGH_ACCURACY); promote to FULL only while a track is actively recording and demote back when recording stops - Use PRIORITY_BALANCED_POWER_ACCURACY for ECONOMY and ANCHOR_WATCH modes so the GPS chip idles and network/passive sources handle light-duty fixes - Add ACTION_START_ECONOMY / ACTION_START_FULL intents so MainActivity can drive mode transitions from lifecycle callbacks - onPause: drop to ECONOMY if not recording or anchor-watching; onResume: restore the appropriate mode so the first visible frame has a fresh rate - Stop-anchor-watch now returns to ECONOMY instead of FULL (anchor drop rarely coincides with active track recording) - Remove ACCESS_BACKGROUND_LOCATION from manifest — the foreground service with foregroundServiceType="location" already covers background access - Add 5 m minimum-distance filter to DeviceGpsProvider so stationary devices don't generate spurious location callbacks https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
2026-05-15fix: recover tracks after reinstall via MediaScanner; add version to startup logClaude
Android clears OWNER_PACKAGE_NAME for all MediaStore entries when an app is uninstalled. Files survive on disk in Documents/Nav/ but the ownership query returns 0 rows after reinstall. Fix: if the first load finds nothing, call MediaScannerConnection.scanFile() on Documents/Nav/. Because our app triggers the scan, the MediaScanner re-registers each file with our package as owner, restoring the query results on the immediate retry. Also log BuildConfig.VERSION_NAME + VERSION_CODE in the startup message so the build is identifiable directly from the in-app debug log. https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
2026-05-15fix: handle application/octet-stream for .gpx on Android 16 (Google Files)Claude
Google Files uses application/octet-stream for .gpx because Android 16's MimeTypeMap has no entry for that extension. The ExternalStorageProvider content URI encodes the full file path, so pathSuffix=".gpx" keeps the ACTION_VIEW filter targeted without matching all binary files. ACTION_SEND (share sheet) filters added for typed MIME variants; code now handles both actions — EXTRA_STREAM for SEND, intent.data for VIEW. For octet-stream and generic XML types, OpenableColumns.DISPLAY_NAME is checked before parsing (uri.lastPathSegment is an opaque ID for content URIs, not the filename). All decisions logged to NavLogger. https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
2026-05-15docs(agent): record user context, storage resilience, and logging preferencesClaude
https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
2026-05-15docs(agent): record Android 16 build target and track storage constraintsClaude
https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
2026-05-15fix: target Android 16 correctly for GPX file handlerClaude
Android's MimeTypeMap does not include application/gpx+xml for .gpx on all devices, so file managers fall back to text/xml or application/xml. Add intent-filters for both XML MIME types and check OpenableColumns DISPLAY_NAME in code before parsing — so Nav appears in the chooser for XML files but only processes ones named *.gpx. Remove the broken file:// pathSuffix filter (irrelevant on Android 16, also syntactically invalid when combined with mimeType in a single <data> element). Remove unnecessary BROWSABLE category from file-open filters. https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
2026-05-15fix: GPX file handler coverage and track loading after reinstallClaude
- AndroidManifest: add application/gpx and file:// intent-filters so Nav appears in the chooser on devices that don't know application/gpx+xml - MainActivity: check file extension as fallback when MIME type is generic - TrackStorage: filter MediaStore fallback query by OWNER_PACKAGE_NAME so the system grants access to our own files without READ_EXTERNAL_STORAGE, which also survives app reinstall (same package name = same ownership) - TrackRepository: add reloadTracks() to force a fresh load from storage - MainViewModel: add reloadPastTracks() called after location permission grant so the first-launch race condition can self-heal https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
2026-05-15fix: saved tracks browser, GPX import persistenceClaude
Saved tracks browser: - TrackDetailSheet converted from BottomSheetDialogFragment to Fragment so it fills the overlay instead of floating over the map - Adapter tap pushes detail fragment onto the back stack; back button returns to the list naturally via popBackStack() - SavedTracksFragment back button calls hideOverlays() (made internal) to close the overlay and return to the map - Layout updated to full-screen: header with back button, RecyclerView fills remaining space via layout_weight instead of fixed 300dp GPX import persistence: - TrackRepository.importTrack() saves parsed points to Documents/Nav/ via TrackStorage (URI is persisted to SharedPreferences) and adds to in-memory list - MainViewModel.addImportedTrack() now calls importTrack() so files opened via the GPX handler load on future launches https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
2026-05-15fix: saved tracks overlay, GPX file handler, nav importClaude
Saved tracks overlay was immediately hidden after showing because bottomNav.selectedItemId = nav_map fires the nav item listener, which calls hideOverlays(). Removed that line — the map tab is already selected when the instrument sheet is open. GPX file handler: - Manifest: intent-filter for application/gpx+xml + singleTop launch mode so onNewIntent is called when app is already running - MainActivity: handleIncomingGpx() called from onCreate + onNewIntent; parses the URI on IO dispatcher and hands points to ViewModel - MainViewModel: addImportedTrack() prepends parsed points to pastTracks and savedTracks state flows (displayed in session only, not persisted) https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G
2026-05-12fix(track): load past tracks via persisted URIs, no MediaStore query needed (#4)Peter Stone
MediaStore.Files.getContentUri("external") queries require READ_EXTERNAL_STORAGE on Android ≤ 12 and have no clean permission path on Android 13+ for non-media files. openInputStream() on a content URI the app owns works on all versions without any permission — so the fix is to persist each URI at save time. On save: call persistUri(uri) after the IS_PENDING=0 update, storing the content URI in SharedPreferences (nav_track_uris). On load: iterate stored URIs first and open each with openInputStream() — no MediaStore query, no permissions needed. The existing MediaStore query is kept as a fallback for tracks saved before this change; any track found there is auto-migrated into SharedPreferences so future launches skip the query too. Invalid URIs (file deleted) are cleaned up from SharedPreferences automatically. https://claude.ai/code/session_01DNjbYxiC1cco83dArNGW3G Co-authored-by: Claude <noreply@anthropic.com>
2026-05-12Merge: fix MediaStore track loadingPeter Stone
2026-05-12fix(track): broaden MediaStore query to find saved GPX filesPeter Stone
RELATIVE_PATH equality matching was returning 0 rows — MediaStore normalises the path differently across Android versions and devices. DISPLAY_NAME LIKE nav_%.gpx is unique enough on its own. Also log path + IS_PENDING per found file to aid future diagnosis. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12Merge: saved tracks browser, tack detection, map zoom-to-boundsPeter Stone
2026-05-12Add saved tracks browser, tack detection, and map zoom-to-boundsPeter Stone
- 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<SavedTrack> 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 <noreply@anthropic.com>
2026-05-07chore: add pre-commit hook blocking commits to masterPeter Stone
Installs scripts/git-hooks/pre-commit which hard-blocks any commit attempted from the master branch. Activate with: git config core.hooksPath scripts/git-hooks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07fix(agent): make branch policy a mandatory session-start actionPeter Stone
Strengthen the branch rule so it's an imperative step-zero command, not just a policy statement that gets read and ignored. Also updates project memory with a feedback entry enforcing the same habit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07refactor: extract GeoThrottle, fix fully-qualified refs, harden enum parsingPeter Stone
- Extract GeoThrottle inner class in MainViewModel; eliminates 6 state vars and duplicate throttle logic in loadConditions/loadWindGrid - Fix android.util.Log.e and kotlin.math.* fully-qualified refs now that imports are present - UnitPrefs: wrap valueOf() in runCatching to handle corrupt pref values - VesselRepository.init: forEach { add() } → mapTo() for both lists - TrackRepository: remove noisy cached-log else-branch that fired on every stopTrack() call Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05fix: remove crash-risk !! operators in UnitPrefs and MainViewModelPeter Stone
UnitPrefs: getString() returns String? even with a non-null default, so !! crashes on corrupted prefs. Replaced with explicit elvis fallback to the same enum default. MainViewModel: forecastResult.getOrThrow() is logically safe (when-branch guards it) but unnecessarily brittle. Replaced with getOrNull() ?: emptyList(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05fix: guard past-track load against SecurityException before permissions grantedPeter Stone
MediaStore.query() throws SecurityException if storage permission hasn't been granted yet. The ViewModel init{} fires before the permission dialog result, so the coroutine crashed the app on first launch. - Wrap contentResolver.query() in runCatching in loadViaMediaStore() - Wrap getPastTracks() call in init{} with runCatching; log failure, don't crash Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05fix(ci): write version to gradle.properties instead of -P flagsPeter Stone
Parentheses in the version name (e.g. "(abc1234 message)") caused gradlew's internal eval to fail with "Syntax error: ( unexpected" because gradlew is /bin/sh and parens have special meaning in eval. Writing the values directly to gradle.properties avoids shell interpretation entirely — Gradle reads it as a plain key=value file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05fix: load past tracks on startup; add debug logging throughoutPeter Stone
Root cause: getPastTracks() was only called inside stopTrack(), so saved tracks were never loaded unless a new track was recorded in the same session. Added init{} block to MainViewModel to load them at startup. Logging added: - getPastTracks(): first-call vs cached, count + point sizes - loadViaMediaStore(): cursor row count, per-file parse attempt, point count per file, any openInputStream null or parse errors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05feat: auto-increment build version with commit summaryPeter Stone
versionCode = github.run_number (increments each CI run) versionName = "1.0.<run> (<short-sha> <commit subject>)" e.g. "1.0.47 (2123351 fix: keep bottom nav visible when panning)" Local builds show "1.0-local (1)" as fallback. Firebase release notes also show the version string. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05fix: keep bottom nav visible when panning; expand drag handle touch targetPeter Stone
Bottom nav: remove it from the following/panning fade cycle — only fabRecenter and fabRecordTrack swap; nav stays visible throughout. Drag handle: 4dp × 36dp was too small and competed with BottomSheet drag gestures. Now 40dp tall via inset drawable (visual bar unchanged, touch area is a usable 40dp × 36dp). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05fix: raise conditions throttle to 20 min / 10 nm to stop 429sPeter Stone
Previous gate (5 min AND 2 nm) meant any pan > 2 nm triggered a fresh fetch regardless of how recently one completed. Raised to 20 min OR 10 nm — marine conditions don't change meaningfully at finer resolution than that. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05fix: hide layers FAB when any overlay is shownPeter Stone
showOverlay/hideOverlay already managed fabRecordTrack visibility; extend the same pattern to fabLayers so it disappears on safety, log, vessel, learn, and anchor watch screens. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05fix: throttle wind/weather fetches to reduce 429sPeter Stone
Camera idle: debounce 1.5s so rapid panning/zooming collapses into one fetch loadWindGrid: skip if center moved < 10 nm AND last fetch < 15 min ago (was unbounded — fired on every camera-idle with no gate at all) loadConditions already had a 5 min + 2 nm gate; unchanged Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04feat: past tracks layer toggle + persist vessel/crew registryPeter Stone
Layers menu: - Add "Past tracks" switch to layer picker sheet — hides/shows previous voyage polylines on the map, persisted across sessions via SharedPrefs Vessel/crew registry: - VesselRepository now takes a Context and persists all vessels and crew to SharedPreferences as JSON (Moshi, same pattern as BoatProfileRepository) - Wire VesselRepository as a singleton in NavApplication so the Fragment and ViewModel share one instance — previously each created its own empty repo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04feat: move Layers from bottom nav to dedicated FABPeter Stone
Replaces the nav_layers bottom nav item with a mini FloatingActionButton anchored below the HUD, giving layers access without occupying a nav slot. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03resolve merge conflict in .claude/settings.local.jsonPeter Stone
Keep HEAD version which has the full set of Android emulator and tooling permissions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23Merge origin/main — unite all work onto one branchClaude
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
2026-04-23fix(ci): remove smoke-test job and artifact uploads entirelyClaude
https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
2026-04-23fix(ci): add retention-days to artifact uploads to prevent quota exhaustionClaude
APKs accumulate across runs with no expiry; 1-day retention for the intermediate test-apks handoff, 7 days for smoke-test results. https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
2026-04-23Merge branch 'claude/vessel-crew-thermal-features-Qbk4z'Claude
Resolves LatLngBounds conflict in ParticleWindView.scatter() — keeps the correct MapLibre 13.0.1 API (southWest/northEast) over the broken flat property accessors. https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
2026-04-22fix(build): replace deprecated LatLngBounds property accessorsClaude
MapLibre 13.0.1 only exposes northEast/southWest LatLng corners; the flat latSouth/latNorth/lonWest/lonEast properties don't exist and cause unresolved reference errors. Replace all occurrences in ParticleWindView and MainActivity with the correct .southWest.latitude / .northEast.* accessors. https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
2026-04-22Merge branch 'claude/hardware-source-flags'Claude
CI fixes + hardware source feature flags. 102 tests, all GREEN. https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
2026-04-22fix+feat: CI build fixes and hardware source feature flagsClaude
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
2026-04-22Merge branch 'claude/cpa-alerts-track-stats'Claude
CPA/TCPA collision alerts (wired CpaCalculator) + live track stats. 95 tests, all GREEN. https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
2026-04-22feat: CPA/TCPA collision alerts and live track statsClaude
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 > 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
2026-04-22Merge branch 'claude/vessel-crew-thermal-features-Qbk4z'Claude
Vessel registry, crew management, and thermal alarm. All 18 new VesselRepositoryTest cases GREEN (59 total across suite). https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
2026-04-22feat: vessel registry, crew management, and thermal alarmClaude
- 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
2026-04-15ci: remove smoke-test jobPeter Stone
2026-04-15ci: upload to Firebase before archiving test APKsPeter Stone
2026-04-14CI: run on claude/** branches; skip Firebase deploy on non-mainClaude
Adds claude/** to push triggers so CI runs on every feature branch push — enabling check-run inspection via GitHub API without requiring a manual main push first. Firebase distribution is already gated on refs/heads/main; adding the event_name check ensures it also won't fire on PRs from main. https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
2026-04-13chore: trigger CI buildPeter Stone
2026-04-13Wire NavLogger into track save/load pipelineClaude
TrackStorage now writes to NavLogger at every decision point: - Before MediaStore insert (shows the filename being written) - Successful save: "saved nav_<ts>.gpx (N bytes) → <uri>" - Every failure path: storage not mounted, insert null, openOutputStream null, write exception, load parse error - After loadViaMediaStore: count of tracks found TrackRepository logs stopTrack summary (pts / duration / distance) and explicitly logs when saveTrack returns false. Long-press the drag handle after injecting a test track to see exactly where the save pipeline breaks. https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
2026-04-12Fix Actions storage: set artifact retention-daysClaude
test-apks: 1 day — only needed for the smoke-test job in the same run smoke-test-results: 7 days — enough for post-failure debugging Default is 90 days; at ~30 MB per run this was filling the 500 MB free-tier limit in ~2 weeks. https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
2026-04-11Throttle conditions API; delay particle view until data loadedClaude
loadConditions now skips the API call if conditions were loaded < 5 min ago and position hasn't moved > 2 nm — prevents 429 from overlapping triggers (startup backstop, GPS first-fix, camera idle). Dev log shows "conditions: skip age=Xs dist=Y.Znm" when throttled. ParticleWindView starts hidden and is revealed only when the first windArrow value arrives, so particles don't flash before the map loads. https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX