From 5eea5211a10499f28c72f2b117e68482723e0605 Mon Sep 17 00:00:00 2001 From: James Rich Date: Tue, 14 Jul 2026 18:36:01 -0500 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=90=9B=20fix:=20stabilize=20tracerout?= =?UTF-8?q?e=20map=20node=20identity=20and=20fix=20log=20metric=20alignmen?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes two bugs in the traceroute UI/rendering path. #6197 — Traceroute map node IDs changing every reload: the google-flavor traceroute marker loop emitted MarkerComposables positionally with no composition key and empty MarkerComposable keys. When displayNodes reorders between reloads, marker slots (and their cached icon bitmaps) get reused positionally, so node chips/positions appear swapped and unstable. Wrap each marker in key(node.num) and pass node.num + short_name as MarkerComposable keys, matching the existing NodeTrack marker pattern. #5743 — Traceroute log metric alignment: the three metric labels (forward hops / return hops / round trip) were laid out in a plain Row that can't fit three items on the narrow card, crushing the last item to ~zero width so it wrapped one character per line (worse with long translated strings). Use FlowRow so items wrap onto a new line cleanly. Co-Authored-By: Claude Opus 4.8 --- .../google/kotlin/org/meshtastic/app/map/MapView.kt | 10 ++++++++-- .../meshtastic/feature/node/metrics/TracerouteLog.kt | 7 +++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt b/androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt index def972c55c..3c697fa6c4 100644 --- a/androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt +++ b/androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt @@ -1262,8 +1262,14 @@ private fun TracerouteMapContent( ) } displayNodes.forEach { node -> - val markerState = rememberUpdatedMarkerState(position = node.position.toLatLng()) - MarkerComposable(state = markerState, zIndex = 4f) { NodeChip(node = node) } + // Key by the stable node num so each marker's composition state (and MarkerComposable's cached + // icon bitmap) stays bound to its node. Without this, reordering displayNodes between reloads + // reuses marker slots positionally and swaps node labels/positions (#6197). node.user.short_name + // is included as a bitmap key so the rendered chip refreshes when node metadata arrives. + key(node.num) { + val markerState = rememberUpdatedMarkerState(position = node.position.toLatLng()) + MarkerComposable(node.num, node.user.short_name, state = markerState, zIndex = 4f) { NodeChip(node = node) } + } } } diff --git a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/TracerouteLog.kt b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/TracerouteLog.kt index 7306151d61..b38a244249 100644 --- a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/TracerouteLog.kt +++ b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/TracerouteLog.kt @@ -24,6 +24,7 @@ import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -267,10 +268,12 @@ private fun TracerouteCardContent(time: String, summaryText: String, icon: Image private fun TracerouteCardMetrics(point: TraceroutePoint) { if (point.forwardHops == null && point.returnHops == null && point.roundTripSeconds == null) return Spacer(modifier = Modifier.height(4.dp)) - Row( + // FlowRow so the three metric labels wrap onto additional lines when they don't fit the card width + // (e.g. long translated strings), rather than the last item being crushed and wrapped per character (#5743). + FlowRow( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically, + verticalArrangement = Arrangement.spacedBy(4.dp), ) { point.forwardHops?.let { hops -> Row(verticalAlignment = Alignment.CenterVertically) { From b06bed57ba69aa0b7ea9c76b38d2bc34efb3f050 Mon Sep 17 00:00:00 2001 From: James Rich Date: Tue, 14 Jul 2026 20:35:36 -0500 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=90=9B=20fix:=20include=20all=20NodeC?= =?UTF-8?q?hip=20inputs=20in=20traceroute=20marker=20keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add node.colors and node.isIgnored to the MarkerComposable keys so the cached marker icon bitmap refreshes when those NodeChip inputs change (not just node.num / short_name). Prevents stale colors or strike-through styling on a marker whose num and short name are unchanged. Addresses CodeRabbit review. Co-Authored-By: Claude Opus 4.8 --- .../kotlin/org/meshtastic/app/map/MapView.kt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt b/androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt index 3c697fa6c4..4108deeeeb 100644 --- a/androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt +++ b/androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt @@ -1264,11 +1264,21 @@ private fun TracerouteMapContent( displayNodes.forEach { node -> // Key by the stable node num so each marker's composition state (and MarkerComposable's cached // icon bitmap) stays bound to its node. Without this, reordering displayNodes between reloads - // reuses marker slots positionally and swaps node labels/positions (#6197). node.user.short_name - // is included as a bitmap key so the rendered chip refreshes when node metadata arrives. + // reuses marker slots positionally and swaps node labels/positions (#6197). The remaining keys + // are every NodeChip input (short name, colors, ignored strike-through) so the rendered chip + // bitmap refreshes when node metadata changes. key(node.num) { val markerState = rememberUpdatedMarkerState(position = node.position.toLatLng()) - MarkerComposable(node.num, node.user.short_name, state = markerState, zIndex = 4f) { NodeChip(node = node) } + MarkerComposable( + node.num, + node.user.short_name, + node.colors, + node.isIgnored, + state = markerState, + zIndex = 4f, + ) { + NodeChip(node = node) + } } } }