Skip to content

feat(map): AR mesh view - #7659

Merged
jamesarich merged 35 commits into
mainfrom
feat/mesh-view
Oct 21, 2026
Merged

jamesarich merged 35 commits into
mainfrom
feat/mesh-view

Conversation

@jamesarich

@jamesarich jamesarich commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Finding a node on foot means flipping between the map and the compass. This adds a camera mesh view: point the phone at the horizon and see nodes, waypoints, traceroutes and tracks where they actually are, with how sure each position is.

🌟 New Features

  • AR mesh view (feature/map/.../hud), opened from the map's top bar, a node's position section and compass sheet, the node list's long-press menu, a traceroute's map, and the position log. The node list menu also gains Open Compass.
  • Nodes as their chips and waypoints as their emoji, placed by bearing and elevation over a CameraX preview. Elevation compares altitudes in one frame (ellipsoid, then sea level, then the node's geoid separation). A chip sits on its point; one lifted to clear a nearer one steps aside with a thin leaning leader (dashed with no altitude), at most three rows, so the lift never reads as height. The focused chip says how far above or below you it is and how old its position is.
  • Precision as a fading zone from both positions' accuracy and the heading error, following the map's precision circle setting; the phone's own fix is averaged while still and shown live; targets too close to point at go on a heading-up radar, which also draws the open route or track.
  • Off-screen targets pin to the edge you'd turn or tilt toward. A focused target gets a "Finding" label, a reticle, a turn hint, a haptic tick per step closer and a pulse on lock.
  • Traceroutes as lifted arcs, forward up and return down, weighted and labeled by link SNR, with dashed skips over unpositioned hops. Tracks as breadcrumbs fading with age. Geofences as walls on the ground.
  • Opened for a purpose it shows only that: a node from its detail, compass or the node list, a node and its track from the position log, a traceroute's destination and hops from its map. The map's entry shows the whole mesh, with the map's filter sheet deciding what shows.
  • Heading on google comes from the Fused Orientation Provider, with its live heading error; fdroid and phones without Play services use the rotation vector, then the geomagnetic rotation vector on phones with no gyroscope.
  • First-run coaching, with Chirpy's warning that it uses battery and heats the phone; a camera that fails to open says so and the view keeps working as a compass; location warnings carry their permission card or a settings button; a reminder to look around while walking; the screen may sleep again after ten minutes without being turned or carried; a hot phone thins the pose to about 15 Hz (the fused orientation provider ignores a slower requested period) and, when critical, releases the camera.
  • TalkBack hears each chip's name, distance and clock position, nearest straight ahead first; labels sit on scrims dark enough for 4.5:1 over a bright sky; stale positions say how old they are.
  • A waypoint's dialog hands Edit off to the map.

🛠️ Refactoring & Architecture

  • Node map filter rules move from feature:map to core:model (NodeMapFilter); MapNodePolicy delegates to it.
  • PhoneLocationProvider and MagneticFieldProvider move from feature:node to core:repository, their Android implementations to core:data.
  • The compass shares the mesh view's position uncertainty and fix time helpers (core:model PositionPrecision).
  • rememberMeshHudSupported() in core:ui; MapRoute.MeshHud route; NodesRoute.NodeDetail(openCompass); MapPrefs.meshHudCoachSeen.
  • FusedOrientationSource bound per flavor, so AndroidCameraPoseProvider stays the one pose provider.
  • The mesh view uses center_focus_strong; view_in_ar stays with the 3D model viewer in feat(node): view a device's 3D model, and place it in AR on the google flavor #7639.

Deferred: the ARCore Geospatial API (Cloud project, privacy text, camera frames to Google, no F-Droid), adding FUSED_PROVIDER to the phone's location request until it is measured against the compass jitter fix, and a "Find" action on the map, which has no node info window to hold one.

📝 Documentation

  • Map & Waypoints: an AR mesh view section. Nodes: Open Compass and Open mesh view in the touch & hold menu. A What's New entry.

Testing Performed

  • New: HudGeometryTest, HudTargetsTest, HudPathsTest, HudRadarTest, HudEdgesTest, HudChipsTest, FixAveragerTest, OneEuroQuaternionFilterTest, MeshHudViewModelTest, DeviceAttitudeTest, MeshHudBackStackTest; MapRoute.MeshHud round trip in NavigationConfigTest. HudTargetsTest places a waypoint at 0,0 and treats a 0 altitude as a reading; MeshHudViewModelTest keeps the chosen focus when the screen opens again for the same node.
  • Baseline with -PwarningsAsErrors=true (spotless, detekt, detektTypeResolved, assembleDebug, test, allTests, kmpSmokeCompile).
  • On a Pixel 9 Pro, indoors: chips, compass tape, edge arrows, radar and live precision; a remote node's traceroute and position log resolve their hops and points. Not yet checked on device: outdoor alignment, the Fused Orientation Provider heading against the compass, traceroute arcs in the camera view, geofence walls, TalkBack.

Summary by CodeRabbit

  • New Features

    • Added an Android camera-based Mesh HUD with node, waypoint, route, track, geofence, radar, compass, distance, elevation, and accuracy overlays.
    • Added Mesh HUD access from maps, node details, compass, position logs, and traceroutes.
    • Added filtering, target focus, coaching, and thermal/sensor status indicators.
    • Mesh HUD availability is limited to compatible devices, with fallback behavior for unavailable sensors, camera access, or thermal conditions.
    • Improved location accuracy, altitude data, and orientation guidance.
  • Documentation

    • Added Android Mesh HUD and node-action guidance.
  • Localization

    • Added Mesh HUD interface text and accessibility labels.

@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: meshtastic/Meshtastic-Android/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c12514af-4a6c-4d61-972c-adca3171052b
📥 Commits

Reviewing files that changed from the base of the PR and between 0a5c6e5 and 6945de3.

📒 Files selected for processing: 17
  • .skills/compose-ui/strings-index.txt
  • androidApp/src/main/kotlin/org/meshtastic/app/MainActivity.kt
  • core/model/src/commonMain/kotlin/org/meshtastic/core/model/NodeMapFilter.kt
  • core/resources/src/commonMain/composeResources/values/strings.xml
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/icon/Hardware.kt
  • docs/en/user/map-and-waypoints.md
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/MapNodePolicy.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudPanels.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudPaths.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudRadar.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudTargets.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/MeshHudViewModel.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/navigation/MapNavigation.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/HudPathsTest.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/MeshHudViewModelTest.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/NodeDetailScreens.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/navigation/NodesNavigation.kt
🚧 Files skipped from review as they are similar to previous changes: 3
  • .skills/compose-ui/strings-index.txt
  • docs/en/user/map-and-waypoints.md
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/MapNodePolicy.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

📝 Walkthrough

This change adds an Android camera-based Mesh HUD for nodes, waypoints, routes, and tracks. It adds shared location and filter data, camera and orientation providers, HUD calculations and rendering, navigation entry points, platform support implementations, localization, tests, and documentation.

Changes: Mesh HUD

Layer / File(s) Summary
Shared location, filtering, and preference data
core/model/*, core/repository/*, core/data/..., core/prefs/*, core/testing/*
Adds shared node filtering, position uncertainty utilities, expanded phone-location data, relocated provider contracts, and persisted Mesh HUD coaching state.
Camera preview and orientation sensing
feature/map/..., androidApp/..., desktopApp/...
Adds CameraX preview, fused orientation, sensor fallback, pose conversion, quaternion filtering, thermal state handling, and no-op platform implementations.
HUD computation and rendering
feature/map/src/commonMain/.../hud/*
Adds target, route, track, radar, overlay, chip, permission, coaching, finder, and haptic behavior.
Navigation and entry points
core/navigation/*, feature/map/*, feature/node/*, androidApp/*, core/ui/*
Adds the Mesh HUD route and navigation from map, node, compass, position-log, and traceroute contexts. Adds capability checks and back-stack handling.
Validation, localization, and documentation
feature/map/src/commonTest/*, feature/node/src/commonTest/*, .skills/compose-ui/*, core/resources/*, docs/en/*
Adds tests for HUD calculations, orientation, filtering, and navigation. Adds Mesh HUD strings, an icon, and user documentation.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 6945d

The AR mesh view now reports route hop counts that match what it draws, and its warning glyphs come from string resources. No outstanding issue blocks merging this change.

Pre-merge checks: Passed 4 · Failed 3 · Inconclusive 1

Failed Failed checks: 3 warnings · 1 inconclusive

Check name Status Explanation Resolution
Tests Prove The Path, Not The End State Warning The standing failure remains. The added test feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/HudTargetsTest.kt:126-142 checks the root-sum-of-squares case only with `targets.size ==… Replace the size-only assertion at line 141 with an identity assertion, such as assertEquals(listOf("node-1"), build(phone(accuracyM = 600f), listOf(node)).targets.map { it.subject.key }). Keep the size assertion only as an additional che…
Regression Coverage For Changed Behavior Warning Regression-coverage gaps remain in the reviewed range. The added pure HUD tests do not cover the changed Android, Compose, flow, persistence, location, precision, and navigation paths. - `AndroidCamer… Add the focused Android host, Compose state/semantics, mutable log-flow, navigation integration, DataStore reload, location-provider, and PositionPrecision branch tests listed above. Each test must assert the changed behavior and fail when …
Moved Code Diffed Against Its Original Warning The prior finding remains unresolved. PhoneLocation.altitude changed from non-null Double in the base declaration to nullable Double? in core/repository/PhoneLocationProvider.kt. The moved And… Add an Android mapper test for Location.hasAltitude() == true and false, while keeping the nullable API documentation. Alternatively, retain non-null PhoneLocation.altitude and the old mapper behavior.
Sibling Call Sites And Presence Semantics Inconclusive Investigation is still in progress; no final assessment yet. Continue code and call-site inspection before deciding.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely identifies the primary change: adding an AR mesh view to the map feature.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Tests Prove The Path, Not The End State

Explanation

The standing failure remains. The added test feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/HudTargetsTest.kt:126-142 checks the root-sum-of-squares case only with targets.size == 1 at line 141. A reverted or incorrect implementation could return a different target and still pass. The other inspected changed tests do not use Dispatchers.Unconfined for emission-order assertions, and no fake-store round-trip violation was found.

Resolution

Replace the size-only assertion at line 141 with an identity assertion, such as assertEquals(listOf("node-1"), build(phone(accuracyM = 600f), listOf(node)).targets.map { it.subject.key }). Keep the size assertion only as an additional check if required.

Full details: Regression Coverage For Changed Behavior

Explanation

Regression-coverage gaps remain in the reviewed range. The added pure HUD tests do not cover the changed Android, Compose, flow, persistence, location, precision, and navigation paths. - AndroidCameraPoseProvider (startOrientation, onFusedFailure, onThermal, restartOrientation, and stop) and GoogleFusedOrientationSource.start contain fused fallback, post-start failure, thermal restart and rate reduction, heading-error conversion, Play Services availability, registration failure, and listener cleanup. DeviceAttitudeTest covers only quaternion conversion. Add Android host tests for fused and sensor fallback, failure fallback, thermal transitions, invalid heading errors, request periods, and cleanup. Cover both Google and F-Droid bindings. - rememberMeshHudSupported() and HudCameraPreview change camera capability and CameraX lifecycle behavior. No tests cover camera/sensor capability combinations, bind failures, camera-state errors, missing metadata, crop/intrinsics reporting, surface disposal, or unbinding. Add Android capability and CameraX lifecycle/metadata tests. - MeshHudScreen and HudPanels add permission sequencing, critical-thermal camera fallback, sensor timeout, idle and walking timers, coaching persistence, filter and waypoint actions, radar and route notices, haptics, and TalkBack semantics. The changed range has no Mesh HUD Compose tests. Add state and semantics tests for these branches, including the haptic transition behavior. - MeshHudViewModel.routeSource and trackSource combine logs, snapshots, live responses, packets, filters, and stationary-run merging. The test uses a new FakeMeshLogRepository, whose getMeshPacketsFrom() returns a permanently empty flow, and tests only live-response and snapshot fixtures. Add mutable-flow tests for UUID/request selection, mismatched or empty responses, recorded-position precedence, packet filtering and ordering, track cutoff and merging, and updates. - Navigation wiring adds Mesh HUD entry points and route arguments in MapNavigation, NodesNavigation, AdaptiveNodeListScreen, node detail, node list, compass, traceroute, and position-log screens. Existing tests cover route serialization and the navigateFromNodeDetail list helper, but not callback wiring, all MapRoute.MeshHud arguments, supported/unsupported gates, or openCompass one-time arrival behavior. Add navigation integration and Compose lifecycle tests. - AndroidPhoneLocationProvider.locationUpdates() adds permission/provider handling, best-fix selection, MSL conversion, optional-field mapping, emissions, and listener removal. No test references this provider. Add Android host tests for permission and provider failures, MSL success/no-result/exception, zero and absent optional fields, better-fix decisions, emissions, and cleanup. - MapPrefsImpl.meshHudCoachSeen starts with an eager true value and writes asynchronously to DataStore at lines 162-168. MapPrefsImplTest has no coaching cases. Add a two-instance reload test for the eager default, write completion, persisted value, and reload behavior. - Position.verticalUncertaintyMetersOrNull() and Position.fixTimeSecondsOrNull() add untested branches at lines 80-85. PositionPrecisionTest covers only precision-radius lookup. Add direct tests for positive and absent VDOP/accuracy, GPS timestamp precedence, send-time fallback, and no-time behavior.

Resolution

Add the focused Android host, Compose state/semantics, mutable log-flow, navigation integration, DataStore reload, location-provider, and PositionPrecision branch tests listed above. Each test must assert the changed behavior and fail when the corresponding implementation branch is removed or regressed.

Full details: Moved Code Diffed Against Its Original

Explanation

The prior finding remains unresolved. PhoneLocation.altitude changed from non-null Double in the base declaration to nullable Double? in core/repository/PhoneLocationProvider.kt. The moved Android mapper changed from copying Location.altitude directly to returning null when hasAltitude() is false. This introduces the explicit presence-versus-sentinel-zero compatibility change. The new KDoc documents the change, but the PR adds no test for both altitude-present and altitude-absent Android Location values. Existing location tests cover a different mapper.

Resolution

Add an Android mapper test for Location.hasAltitude() == true and false, while keeping the nullable API documentation. Alternatively, retain non-null PhoneLocation.altitude and the old mapper behavior.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added desktop Desktop target enhancement New feature or request labels Oct 17, 2026
@jamesarich
jamesarich marked this pull request as ready for review October 20, 2026 15:43
@jamesarich jamesarich removed the desktop Desktop target label Oct 20, 2026
@github-actions github-actions Bot added the desktop Desktop target label Oct 20, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository: meshtastic/Meshtastic-Android/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1ae94a8b-5d6c-4d70-9c6a-6fad9a59a22a
📥 Commits

Reviewing files that changed from the base of the PR and between b203dc7 and 91ce3ed.

📒 Files selected for processing: 86
  • .skills/compose-ui/strings-index.txt
  • androidApp/src/fdroid/kotlin/org/meshtastic/app/di/FlavorModule.kt
  • androidApp/src/google/kotlin/org/meshtastic/app/di/FlavorModule.kt
  • androidApp/src/google/kotlin/org/meshtastic/app/map/hud/GoogleFusedOrientationSource.kt
  • androidApp/src/main/kotlin/org/meshtastic/app/MainActivity.kt
  • androidApp/src/test/kotlin/org/meshtastic/app/ui/NavigationAssemblyTest.kt
  • core/data/src/androidMain/kotlin/org/meshtastic/core/data/location/AndroidMagneticFieldProvider.kt
  • core/data/src/androidMain/kotlin/org/meshtastic/core/data/location/AndroidPhoneLocationProvider.kt
  • core/model/src/commonMain/kotlin/org/meshtastic/core/model/NodeMapFilter.kt
  • core/model/src/commonMain/kotlin/org/meshtastic/core/model/util/PositionPrecision.kt
  • core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt
  • core/navigation/src/commonTest/kotlin/org/meshtastic/core/navigation/NavigationConfigTest.kt
  • core/prefs/src/commonMain/kotlin/org/meshtastic/core/prefs/map/MapPrefsImpl.kt
  • core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/AppPreferences.kt
  • core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/MagneticFieldProvider.kt
  • core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/NodeFilterPrefs.kt
  • core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/PhoneLocationProvider.kt
  • core/resources/src/commonMain/composeResources/drawable/ic_center_focus_strong.xml
  • core/resources/src/commonMain/composeResources/values/strings.xml
  • core/testing/src/commonMain/kotlin/org/meshtastic/core/testing/FakeAppPreferences.kt
  • core/ui/detekt-baseline.xml
  • core/ui/src/androidMain/kotlin/org/meshtastic/core/ui/util/MeshHudSupport.android.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/icon/Hardware.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalMapMainScreenProvider.kt
  • core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MeshHudSupport.kt
  • core/ui/src/iosMain/kotlin/org/meshtastic/core/ui/util/MeshHudSupport.ios.kt
  • core/ui/src/jvmMain/kotlin/org/meshtastic/core/ui/util/MeshHudSupport.jvm.kt
  • desktopApp/src/main/kotlin/org/meshtastic/desktop/Main.kt
  • desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopStubsModule.kt
  • desktopApp/src/main/kotlin/org/meshtastic/desktop/stub/CompassStubs.kt
  • docs/en/user.md
  • docs/en/user/map-and-waypoints.md
  • docs/en/user/nodes.md
  • feature/map/build.gradle.kts
  • feature/map/src/androidHostTest/kotlin/org/meshtastic/feature/map/hud/DeviceAttitudeTest.kt
  • feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/hud/AndroidCameraPoseProvider.kt
  • feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/hud/DeviceAttitude.kt
  • feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/hud/FusedOrientationSource.kt
  • feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/hud/HudCameraPreview.android.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/MapNodePolicy.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/MapScreen.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/CameraPoseProvider.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/FixAverager.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudCameraPreview.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudChipLayer.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudChips.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudEdges.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudGeometry.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudOverlay.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudPanels.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudPaths.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudPathsDraw.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudRadar.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudRadarPaths.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudTargets.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/MeshHudScreen.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/MeshHudViewModel.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/OneEuroQuaternionFilter.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/navigation/MapNavigation.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/FixAveragerTest.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/HudChipsTest.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/HudEdgesTest.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/HudGeometryTest.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/HudPathsTest.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/HudRadarTest.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/HudTargetsTest.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/MeshHudViewModelTest.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/OneEuroQuaternionFilterTest.kt
  • feature/map/src/iosMain/kotlin/org/meshtastic/feature/map/hud/HudCameraPreview.ios.kt
  • feature/map/src/jvmMain/kotlin/org/meshtastic/feature/map/hud/HudCameraPreview.jvm.kt
  • feature/node/detekt-baseline.xml
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/compass/CompassViewModel.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/component/CompassBottomSheet.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/component/NodeContextMenu.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/component/PositionSection.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/NodeDetailScreens.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/PositionLogScreens.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/TracerouteMapScreen.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/navigation/AdaptiveNodeListScreen.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/navigation/MeshHudBackStack.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/navigation/NodesNavigation.kt
  • feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/compass/CompassViewModelTest.kt
  • feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/navigation/MeshHudBackStackTest.kt
  • feature/node/src/jvmTest/kotlin/org/meshtastic/feature/node/detail/NodeDetailCompassLifecycleTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.

Comment thread feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudPathsDraw.kt Outdated
Comment thread feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/MeshHudScreen.kt Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Restore the accessibility click action on radar dots. · HudRadar.kt:197-203

feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudRadar.kt:197-203
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Restore the accessibility click action on radar dots.

clearAndSetSemantics replaces the clickable semantics with only a button role and description. Pointer taps still call the callback, but TalkBack activation has no click action for node or waypoint dots. Add the action in the shared RadarDot component.

Suggested fix
+import androidx.compose.ui.semantics.onClick
@@
-private fun RadarDot(subject: HudSubject, description: String, onClick: () -> Unit) {
+private fun RadarDot(subject: HudSubject, description: String, onActivate: () -> Unit) {
@@
-        Modifier.minimumInteractiveComponentSize().clickable(onClick = onClick).clearAndSetSemantics {
+        Modifier.minimumInteractiveComponentSize().clickable(onClick = onActivate).clearAndSetSemantics {
             contentDescription = description
             role = Role.Button
+            onClick {
+                onActivate()
+                true
+            }
         },

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository: meshtastic/Meshtastic-Android/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 692d5bb6-80d0-4e4e-88c5-bc47a469038c
📥 Commits

Reviewing files that changed from the base of the PR and between 91ce3ed and 0b4f201.

📒 Files selected for processing: 6
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudChips.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudPathsDraw.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/MeshHudScreen.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/HudChipsTest.kt
  • feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/compass/CompassViewModel.kt
  • feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/compass/CompassViewModelTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository: meshtastic/Meshtastic-Android/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f0205e7a-b2ee-4610-8eac-2565f4e6da9c
📥 Commits

Reviewing files that changed from the base of the PR and between c9cd291 and 5e4941c.

📒 Files selected for processing: 3
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudPaths.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/MeshHudViewModel.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/HudPathsTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository: meshtastic/Meshtastic-Android/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 28b50a7f-adb4-4525-a32b-493c3f99fa93
📥 Commits

Reviewing files that changed from the base of the PR and between 4cea4b7 and b5a505c.

📒 Files selected for processing: 3
  • .skills/compose-ui/strings-index.txt
  • core/resources/src/commonMain/composeResources/values/strings.xml
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudPanels.kt
🚧 Files skipped from review as they are similar to previous changes: 2
  • .skills/compose-ui/strings-index.txt
  • core/resources/src/commonMain/composeResources/values/strings.xml

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review.

Comment thread feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudPanels.kt Outdated
@github-actions

This comment has been minimized.

@codecov

codecov Bot commented Oct 20, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
10036 1 10035 0
View the top 1 failed test(s) by shortest run time
org.meshtastic.core.ble.AndroidBluetoothRepositoryBondTest::classMethod
Stack Traces | 0.019s run time
java.lang.AssertionError: Failed to fetch maven artifact org.robolectric:android-all-instrumented:14-robolectric-10818077-i7
	at org.robolectric.internal.dependency.MavenArtifactFetcher.fetchArtifact(MavenArtifactFetcher.java:130)
	at org.robolectric.internal.dependency.MavenDependencyResolver.lambda$getLocalArtifactUrls$0(MavenDependencyResolver.java:87)
	at org.robolectric.internal.dependency.MavenDependencyResolver.whileLocked(MavenDependencyResolver.java:107)
	at org.robolectric.internal.dependency.MavenDependencyResolver.getLocalArtifactUrls(MavenDependencyResolver.java:82)
	at org.robolectric.internal.dependency.MavenDependencyResolver.getLocalArtifactUrl(MavenDependencyResolver.java:123)
	at org.robolectric.plugins.LegacyDependencyResolver.getLocalArtifactUrl(LegacyDependencyResolver.java:95)
	at org.robolectric.plugins.DefaultSdkProvider$DefaultSdk.getJarPath(DefaultSdkProvider.java:145)
	at org.robolectric.internal.AndroidSandbox$SdkSandboxClassLoader.<init>(AndroidSandbox.java:128)
	at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:483)
	at org.robolectric.util.inject.Injector.inject(Injector.java:258)
	at org.robolectric.util.inject.Injector.lambda$memoized$1(Injector.java:239)
	at org.robolectric.util.inject.Injector$MemoizingProvider.get(Injector.java:506)
	at org.robolectric.util.inject.Injector.getInstanceInternal(Injector.java:230)
	at org.robolectric.util.inject.Injector.resolveDependencies(Injector.java:305)
	at org.robolectric.util.inject.Injector.inject(Injector.java:256)
	at org.robolectric.util.inject.Injector.lambda$memoized$1(Injector.java:239)
	at org.robolectric.util.inject.Injector$MemoizingProvider.get(Injector.java:506)
	at org.robolectric.util.inject.Injector.getInstanceInternal(Injector.java:230)
	at org.robolectric.util.inject.Injector.getInstance(Injector.java:214)
	at org.robolectric.util.inject.Injector$ScopeBuilderProvider.create(Injector.java:575)
	at org.robolectric.util.inject.Injector$ScopeBuilderProvider.lambda$get$0(Injector.java:558)
	at jdk.proxy3/jdk.proxy3.$Proxy20.build(Unknown Source)
	at org.robolectric.internal.SandboxManager.getAndroidSandbox(SandboxManager.java:68)
	at org.robolectric.RobolectricTestRunner.getAndroidSandbox(RobolectricTestRunner.java:283)
	at org.robolectric.RobolectricTestRunner.getSandbox(RobolectricTestRunner.java:261)
	at org.robolectric.RobolectricTestRunner.getSandbox(RobolectricTestRunner.java:67)
	at org.robolectric.internal.SandboxTestRunner.sandboxGroupingClassBlock(SandboxTestRunner.java:206)
	at org.robolectric.internal.SandboxTestRunner.classBlock(SandboxTestRunner.java:194)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:412)
	at org.robolectric.RobolectricTestRunner.run(RobolectricTestRunner.java:149)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:174)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
	at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
	at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
	at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
	at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
	at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: java.util.concurrent.ExecutionException: java.net.SocketException: Connection reset
	at com.google.common.util.concurrent.AbstractFuture$Failure.newExecutionException(AbstractFuture.java:168)
	at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:298)
	at com.google.common.util.concurrent.AbstractFutureState.blockingGet(AbstractFutureState.java:250)
	at com.google.common.util.concurrent.Platform.get(Platform.java:54)
	at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:263)
	at com.google.common.util.concurrent.AbstractFuture$TrustedFuture.get(AbstractFuture.java:96)
	at org.robolectric.internal.dependency.MavenArtifactFetcher.fetchArtifact(MavenArtifactFetcher.java:122)
	... 55 more
Caused by: java.net.SocketException: Connection reset
	at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:316)
	at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:354)
	at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:798)
	at java.base/java.net.Socket$SocketInputStream.implRead(Socket.java:974)
	at java.base/java.net.Socket$SocketInputStream.read(Socket.java:964)
	at java.base/sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:489)
	at java.base/sun.security.ssl.SSLSocketInputRecord.readHeader(SSLSocketInputRecord.java:483)
	at java.base/sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:160)
	at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:111)
	at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1506)
	at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1421)
	at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:455)
	at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:426)
	at java.base/sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:490)
	at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:190)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1379)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1305)
	at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:223)
	at org.robolectric.internal.dependency.MavenArtifactFetcher$FetchToFileTask.call(MavenArtifactFetcher.java:253)
	at com.google.common.util.concurrent.TrustedListenableFutureTask$TrustedFutureInterruptibleAsyncTask.runInterruptibly(TrustedListenableFutureTask.java:164)
	at com.google.common.util.concurrent.TrustedListenableFutureTask$TrustedFutureInterruptibleAsyncTask.runInterruptibly(TrustedListenableFutureTask.java:147)
	at com.google.common.util.concurrent.InterruptibleTask.run(InterruptibleTask.java:74)
	at com.google.common.util.concurrent.TrustedListenableFutureTask.run(TrustedListenableFutureTask.java:80)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614)
	at java.base/java.lang.Thread.run(Thread.java:1474)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments: 1
feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/MeshHudViewModelTest.kt: 1

264-264: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This test passes even when the track does not keep the node visible.

The view model no longer applies a range limit, and no MapFilterPrefs excludes node-3. As a result, node-3 appears through passesMapFilter even with showTrack(3) removed. The test name says the track pins the node, but the test does not check that. A regression in the pinned path of targets (node.num in settings.pinned) would not fail this test.

To fix this, set a filter that excludes node-3, for example onlyFavorites = true with node(1, favorite = true). The test then fails unless the track keeps node-3 visible.

♻️ Proposed change
     fun `a tracked node stays in the whole mesh view`() = runTest(testDispatcher) {
-        nodeRepository.setNodes(listOf(node(1), node(3, lat = FAR_LAT)))
+        nodeRepository.setNodes(listOf(node(1, favorite = true), node(3, lat = FAR_LAT)))
+        mapPrefs.mapFilters.value = MapFilterPrefs(onlyFavorites = true)
         viewModel.openFor(null)
         viewModel.showTrack(3)
         assertEquals(setOf("node-1", "node-3"), collectTargets().keys)
     }

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository: meshtastic/Meshtastic-Android/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 77179a62-082f-43a0-afd2-022872b9c4bc
📥 Commits

Reviewing files that changed from the base of the PR and between 8eccd96 and 0a5c6e5.

📒 Files selected for processing: 9
  • .skills/compose-ui/strings-index.txt
  • core/resources/src/commonMain/composeResources/values/strings.xml
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudChipLayer.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudOverlay.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudPanels.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/HudTargets.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/MeshHudScreen.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/hud/MeshHudViewModel.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/hud/MeshHudViewModelTest.kt
💤 Files with no reviewable changes: 2
  • core/resources/src/commonMain/composeResources/values/strings.xml
  • .skills/compose-ui/strings-index.txt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

jamesarich and others added 15 commits October 20, 2026 18:32
The map's node filters become a plain NodeMapFilter predicate over Node, so a view outside feature:map can apply the
same rules. MapNodePolicy delegates to it, and MapFilterPrefs converts to it directly.
…to core

The compass's phone location and declination providers become core:repository interfaces with their Android
implementations in core:data, so a view outside feature:node can read the phone's fix and true north the same way.
Point the back camera at the horizon and see the mesh in place: nodes as their chips and waypoints as their emoji,
by bearing and elevation, over a CameraX preview. It opens from the map for everything in range, from node detail and
the compass for one node, from a traceroute's map to draw that route in the air, and from the position log to draw
that node's track.

The pose is the rotation vector's camera axes, smoothed with a 1€ filter, and the field of view comes from the lens
calibration and the stream crop. Elevation compares altitudes in one frame. Precision draws as a fading zone from both
positions' accuracy and the heading error. The phone's own fix is averaged while it is still and shown live, and
targets too close to point at go on a heading-up radar. Off-screen targets pin to the edge to turn or tilt toward, a
focused target gets a reticle, a turn hint and a haptic lock, and dragging the view corrects the compass. Traceroutes
draw as lifted arcs, forward up and return down, weighted by each link's SNR; tracks as breadcrumbs fading with age;
geofences as walls on the ground. It follows the map's filters and a range toggle.

The compass shares the mesh view's position uncertainty and fix time rules.
From node detail or the compass the view shows that node alone; from the position log, that node and its track; from
a traceroute, its destination and hops. Waypoints, geofences, other nodes and the range toggle stay out. Opened from
the map it still shows everything the filters allow.
A scoped view whose nodes all sit inside their own uncertainty showed only radar dots, so a traceroute or track looked
empty. The radar now draws both in plan view, and a node with no live position takes the one its route or track
recorded. The view no longer reports an empty mesh before its first update.

Also: the return leg's stem from the viewer, scoped views ignore the range limit, AltitudeConverterCompat for sea
level altitude, the geofence foot line clipped at the near plane, and an "Open waypoint" click label.
…ecreation

A waypoint at latitude or longitude 0 is a real position, and the nullable
field already carries absence. Reopening the view for the same node, as a
recreated screen does, no longer resets the focus the user chose.
jamesarich and others added 20 commits October 20, 2026 18:33
@jamesarich
jamesarich added this pull request to the merge queue Oct 21, 2026
Merged via the queue into main with commit 9d10187 Oct 21, 2026
18 checks passed
@jamesarich
jamesarich deleted the feat/mesh-view branch October 21, 2026 01:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

desktop Desktop target enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant