refactor: null safety, update date/time libraries, and migrate tests (#4900)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-23 18:17:50 -05:00 committed by GitHub
parent f826cac6c8
commit 664ebf218e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
163 changed files with 503 additions and 4993 deletions

View file

@ -39,6 +39,7 @@ class FdroidMapViewProvider : MapViewProvider {
) {
val mapViewModel: MapViewModel = koinViewModel()
LaunchedEffect(waypointId) { mapViewModel.setWaypointId(waypointId) }
@Suppress("UNCHECKED_CAST")
org.meshtastic.app.map.MapView(
modifier = modifier,
mapViewModel = mapViewModel,

View file

@ -449,7 +449,7 @@ fun MapView(
if (!mapFilterStateValue.showPrecisionCircle) {
setPrecisionBits(0)
} else {
setPrecisionBits(p.precision_bits ?: 0)
setPrecisionBits(p.precision_bits)
}
setOnLongClickListener {
navigateToNodeDetails(node.num)
@ -469,7 +469,7 @@ fun MapView(
Logger.d { "User deleted waypoint ${waypoint.id} for me" }
mapViewModel.deleteWaypoint(waypoint.id)
}
if ((waypoint.locked_to ?: 0) in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
if (waypoint.locked_to in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
builder.setPositiveButton(getString(Res.string.delete_for_everyone)) { _, _ ->
Logger.d { "User deleted waypoint ${waypoint.id} for everyone" }
mapViewModel.sendWaypoint(waypoint.copy(expire = 1))
@ -497,7 +497,7 @@ fun MapView(
Logger.d { "marker long pressed id=$id" }
val waypoint = waypoints[id]?.waypoint ?: return
// edit only when unlocked or lockedTo myNodeNum
if ((waypoint.locked_to ?: 0) in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
if (waypoint.locked_to in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
showEditWaypointDialog = waypoint
} else {
showDeleteMarkerDialog(waypoint)
@ -515,15 +515,15 @@ fun MapView(
return waypoints.mapNotNull { waypoint ->
val pt = waypoint.waypoint ?: return@mapNotNull null
if (!mapFilterState.showWaypoints) return@mapNotNull null // Use collected mapFilterState
val lock = if ((pt.locked_to ?: 0) != 0) "\uD83D\uDD12" else ""
val lock = if (pt.locked_to != 0) "\uD83D\uDD12" else ""
val time = DateFormatter.formatDateTime(waypoint.time)
val label = (pt.name ?: "") + " " + formatAgo((waypoint.time / 1000).toInt())
val emoji = String(Character.toChars(if ((pt.icon ?: 0) == 0) 128205 else pt.icon!!))
val label = pt.name + " " + formatAgo((waypoint.time / 1000).toInt())
val emoji = String(Character.toChars(if (pt.icon == 0) 128205 else pt.icon))
val now = nowMillis
val expireTimeMillis = (pt.expire ?: 0) * 1000L
val expireTimeMillis = pt.expire * 1000L
val expireTimeStr =
when {
(pt.expire ?: 0) == 0 || pt.expire == Int.MAX_VALUE -> "Never"
pt.expire == 0 || pt.expire == Int.MAX_VALUE -> "Never"
expireTimeMillis <= now -> "Expired"
else -> DateFormatter.formatRelativeTime(expireTimeMillis)
}
@ -693,10 +693,9 @@ fun MapView(
if (nodeTracks == null || focusedNodeNum == null) return emptyList<Marker>() to emptyList<Polyline>()
val lastHeardTrackFilter = mapFilterState.lastHeardTrackFilter
val timeFilteredPositions =
nodeTracks.filter {
lastHeardTrackFilter == LastHeardFilter.Any || it.time > nowSeconds - lastHeardTrackFilter.seconds
}
val timeFilteredPositions = nodeTracks.filter {
lastHeardTrackFilter == LastHeardFilter.Any || it.time > nowSeconds - lastHeardTrackFilter.seconds
}
val sortedPositions = timeFilteredPositions.sortedBy { it.time }
val focusedNode = nodes.find { it.num == focusedNodeNum } ?: return emptyList<Marker>() to emptyList<Polyline>()
@ -719,18 +718,17 @@ fun MapView(
}
}
val trackMarkers =
sortedPositions.mapIndexedNotNull { index, position ->
if (index == sortedPositions.lastIndex) return@mapIndexedNotNull null
val trackMarkers = sortedPositions.mapIndexedNotNull { index, position ->
if (index == sortedPositions.lastIndex) return@mapIndexedNotNull null
Marker(this).apply {
this.position = GeoPoint((position.latitude_i ?: 0) * 1e-7, (position.longitude_i ?: 0) * 1e-7)
icon = AppCompatResources.getDrawable(context, R.drawable.ic_map_location_dot)
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
title = getString(Res.string.position)
snippet = formatAgo(position.time)
}
Marker(this).apply {
this.position = GeoPoint((position.latitude_i ?: 0) * 1e-7, (position.longitude_i ?: 0) * 1e-7)
icon = AppCompatResources.getDrawable(context, R.drawable.ic_map_location_dot)
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
title = getString(Res.string.position)
snippet = formatAgo(position.time)
}
}
return trackMarkers to trackPolylines
}
@ -941,12 +939,11 @@ fun MapView(
Logger.d { "User clicked send waypoint ${waypoint.id}" }
showEditWaypointDialog = null
val newId =
if (waypoint.id == 0) mapViewModel.generatePacketId() ?: return@EditWaypointDialog else waypoint.id
val newId = if (waypoint.id == 0) mapViewModel.generatePacketId() else waypoint.id
val newName = if (waypoint.name.isNullOrEmpty()) "Dropped Pin" else waypoint.name
val newExpire = if ((waypoint.expire ?: 0) == 0) Int.MAX_VALUE else (waypoint.expire ?: Int.MAX_VALUE)
val newLockedTo = if ((waypoint.locked_to ?: 0) != 0) mapViewModel.myNodeNum ?: 0 else 0
val newIcon = if ((waypoint.icon ?: 0) == 0) 128205 else waypoint.icon
val newExpire = if (waypoint.expire == 0) Int.MAX_VALUE else waypoint.expire
val newLockedTo = if (waypoint.locked_to != 0) mapViewModel.myNodeNum ?: 0 else 0
val newIcon = if (waypoint.icon == 0) 128205 else waypoint.icon
mapViewModel.sendWaypoint(
waypoint.copy(
@ -1161,16 +1158,15 @@ private fun offsetPolyline(
val headingPoints = headingReferencePoints.takeIf { it.size >= 2 } ?: points
if (points.size < 2 || headingPoints.size < 2 || offsetMeters == 0.0) return points
val headings =
headingPoints.mapIndexed { index, _ ->
when (index) {
0 -> bearingRad(headingPoints[0], headingPoints[1])
headingPoints.lastIndex ->
bearingRad(headingPoints[headingPoints.lastIndex - 1], headingPoints[headingPoints.lastIndex])
val headings = headingPoints.mapIndexed { index, _ ->
when (index) {
0 -> bearingRad(headingPoints[0], headingPoints[1])
headingPoints.lastIndex ->
bearingRad(headingPoints[headingPoints.lastIndex - 1], headingPoints[headingPoints.lastIndex])
else -> bearingRad(headingPoints[index - 1], headingPoints[index + 1])
}
else -> bearingRad(headingPoints[index - 1], headingPoints[index + 1])
}
}
return points.mapIndexed { index, point ->
val heading = headings[index.coerceIn(0, headings.lastIndex)]

View file

@ -126,19 +126,18 @@ fun MapView.addPolyline(density: Density, geoPoints: List<GeoPoint>, onClick: ()
fun MapView.addPositionMarkers(positions: List<Position>, onClick: () -> Unit): List<Marker> {
val navIcon = ContextCompat.getDrawable(context, R.drawable.ic_map_navigation)
val markers =
positions.map {
Marker(this).apply {
icon = navIcon
rotation = ((it.ground_track ?: 0) * 1e-5).toFloat()
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
position = GeoPoint((it.latitude_i ?: 0) * 1e-7, (it.longitude_i ?: 0) * 1e-7)
setOnMarkerClickListener { _, _ ->
onClick()
true
}
val markers = positions.map {
Marker(this).apply {
icon = navIcon
rotation = ((it.ground_track ?: 0) * 1e-5).toFloat()
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
position = GeoPoint((it.latitude_i ?: 0) * 1e-7, (it.longitude_i ?: 0) * 1e-7)
setOnMarkerClickListener { _, _ ->
onClick()
true
}
}
}
overlays.addAll(markers)
return markers

View file

@ -60,7 +60,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.Month
import kotlinx.datetime.toInstant
@ -85,6 +84,7 @@ import org.meshtastic.core.ui.emoji.EmojiPickerDialog
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.proto.Waypoint
import kotlin.time.Duration.Companion.hours
import kotlin.time.Instant
@Suppress("LongMethod", "CyclomaticComplexMethod")
@OptIn(ExperimentalLayoutApi::class)
@ -100,7 +100,7 @@ fun EditWaypointDialog(
val title = if (waypoint.id == 0) Res.string.waypoint_new else Res.string.waypoint_edit
@Suppress("MagicNumber")
val emoji = if ((waypointInput.icon ?: 0) == 0) 128205 else waypointInput.icon!!
val emoji = if (waypointInput.icon == 0) 128205 else waypointInput.icon
var showEmojiPickerView by remember { mutableStateOf(false) }
// Get current context for dialogs
@ -115,11 +115,11 @@ fun EditWaypointDialog(
val currentInstant =
remember(waypointInput.expire) {
val expire = waypointInput.expire ?: 0
val expire = waypointInput.expire
if (expire != 0 && expire != Int.MAX_VALUE) {
Instant.fromEpochSeconds(expire.toLong())
kotlin.time.Instant.fromEpochSeconds(expire.toLong())
} else {
kotlinx.datetime.Clock.System.now() + 8.hours
kotlin.time.Clock.System.now() + 8.hours
}
}
@ -127,7 +127,7 @@ fun EditWaypointDialog(
var selectedDate by
remember(currentInstant) {
mutableStateOf(
if ((waypointInput.expire ?: 0) != 0 && waypointInput.expire != Int.MAX_VALUE) {
if (waypointInput.expire != 0 && waypointInput.expire != Int.MAX_VALUE) {
dateFormat.format(java.util.Date(currentInstant.toEpochMilliseconds()))
} else {
""
@ -137,7 +137,7 @@ fun EditWaypointDialog(
var selectedTime by
remember(currentInstant) {
mutableStateOf(
if ((waypointInput.expire ?: 0) != 0 && waypointInput.expire != Int.MAX_VALUE) {
if (waypointInput.expire != 0 && waypointInput.expire != Int.MAX_VALUE) {
timeFormat.format(java.util.Date(currentInstant.toEpochMilliseconds()))
} else {
""
@ -162,7 +162,7 @@ fun EditWaypointDialog(
)
EditTextPreference(
title = stringResource(Res.string.name),
value = waypointInput.name ?: "",
value = waypointInput.name,
maxSize = 29,
enabled = true,
isError = false,
@ -185,7 +185,7 @@ fun EditWaypointDialog(
)
EditTextPreference(
title = stringResource(Res.string.description),
value = waypointInput.description ?: "",
value = waypointInput.description,
maxSize = 99,
enabled = true,
isError = false,
@ -202,7 +202,7 @@ fun EditWaypointDialog(
Text(stringResource(Res.string.locked))
Switch(
modifier = Modifier.fillMaxWidth().wrapContentWidth(Alignment.End),
checked = (waypointInput.locked_to ?: 0) != 0,
checked = waypointInput.locked_to != 0,
onCheckedChange = { waypointInput = waypointInput.copy(locked_to = if (it) 1 else 0) },
)
}
@ -225,7 +225,7 @@ fun EditWaypointDialog(
waypointInput = waypointInput.copy(expire = newLdt.toInstant(tz).epochSeconds.toInt())
},
ldt.year,
ldt.monthNumber - 1,
ldt.month.ordinal,
ldt.day,
)
@ -261,7 +261,7 @@ fun EditWaypointDialog(
Text(stringResource(Res.string.expires))
Switch(
modifier = Modifier.fillMaxWidth().wrapContentWidth(Alignment.End),
checked = waypointInput.expire != Int.MAX_VALUE && (waypointInput.expire ?: 0) != 0,
checked = waypointInput.expire != Int.MAX_VALUE && waypointInput.expire != 0,
onCheckedChange = { isChecked ->
if (isChecked) {
waypointInput = waypointInput.copy(expire = currentInstant.epochSeconds.toInt())
@ -272,7 +272,7 @@ fun EditWaypointDialog(
)
}
if (waypointInput.expire != Int.MAX_VALUE && (waypointInput.expire ?: 0) != 0) {
if (waypointInput.expire != Int.MAX_VALUE && waypointInput.expire != 0) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),

View file

@ -302,17 +302,16 @@ fun MapView(
}
val myNodeNum = mapViewModel.myNodeNum
val nodeClusterItems =
displayNodes.map { node ->
val latLng = LatLng((node.position.latitude_i ?: 0) * DEG_D, (node.position.longitude_i ?: 0) * DEG_D)
NodeClusterItem(
node = node,
nodePosition = latLng,
nodeTitle = "${node.user.short_name} ${formatAgo(node.position.time)}",
nodeSnippet = "${node.user.long_name}",
myNodeNum = myNodeNum,
)
}
val nodeClusterItems = displayNodes.map { node ->
val latLng = LatLng((node.position.latitude_i ?: 0) * DEG_D, (node.position.longitude_i ?: 0) * DEG_D)
NodeClusterItem(
node = node,
nodePosition = latLng,
nodeTitle = "${node.user.short_name} ${formatAgo(node.position.time)}",
nodeSnippet = "${node.user.long_name}",
myNodeNum = myNodeNum,
)
}
val isConnected by mapViewModel.isConnected.collectAsStateWithLifecycle()
val theme by mapViewModel.theme.collectAsStateWithLifecycle()
val dark =
@ -492,11 +491,9 @@ fun MapView(
if (nodeTracks != null && focusedNodeNum != null) {
val lastHeardTrackFilter = mapFilterState.lastHeardTrackFilter
val timeFilteredPositions =
nodeTracks.filter {
lastHeardTrackFilter == LastHeardFilter.Any ||
it.time > nowSeconds - lastHeardTrackFilter.seconds
}
val timeFilteredPositions = nodeTracks.filter {
lastHeardTrackFilter == LastHeardFilter.Any || it.time > nowSeconds - lastHeardTrackFilter.seconds
}
val sortedPositions = timeFilteredPositions.sortedBy { it.time }
allNodes
.find { it.num == focusedNodeNum }
@ -872,19 +869,18 @@ private fun offsetPolyline(
val headingPoints = headingReferencePoints.takeIf { it.size >= 2 } ?: points
if (points.size < 2 || headingPoints.size < 2 || offsetMeters == 0.0) return points
val headings =
headingPoints.mapIndexed { index, _ ->
when (index) {
0 -> SphericalUtil.computeHeading(headingPoints[0], headingPoints[1])
headingPoints.lastIndex ->
SphericalUtil.computeHeading(
headingPoints[headingPoints.lastIndex - 1],
headingPoints[headingPoints.lastIndex],
)
val headings = headingPoints.mapIndexed { index, _ ->
when (index) {
0 -> SphericalUtil.computeHeading(headingPoints[0], headingPoints[1])
headingPoints.lastIndex ->
SphericalUtil.computeHeading(
headingPoints[headingPoints.lastIndex - 1],
headingPoints[headingPoints.lastIndex],
)
else -> SphericalUtil.computeHeading(headingPoints[index - 1], headingPoints[index + 1])
}
else -> SphericalUtil.computeHeading(headingPoints[index - 1], headingPoints[index + 1])
}
}
return points.mapIndexed { index, point ->
val heading = headings[index.coerceIn(0, headings.lastIndex)]

View file

@ -412,33 +412,32 @@ class MapViewModel(
if (persistedLayerFiles != null) {
val hiddenLayerUrls = googleMapsPrefs.hiddenLayerUrls.value
val loadedItems =
persistedLayerFiles.mapNotNull { file ->
if (file.isFile) {
val layerType =
when (file.extension.lowercase()) {
"kml",
"kmz",
-> LayerType.KML
"geojson",
"json",
-> LayerType.GEOJSON
else -> null
}
layerType?.let {
val uri = Uri.fromFile(file)
MapLayerItem(
name = file.nameWithoutExtension,
uri = uri,
isVisible = !hiddenLayerUrls.contains(uri.toString()),
layerType = it,
)
val loadedItems = persistedLayerFiles.mapNotNull { file ->
if (file.isFile) {
val layerType =
when (file.extension.lowercase()) {
"kml",
"kmz",
-> LayerType.KML
"geojson",
"json",
-> LayerType.GEOJSON
else -> null
}
} else {
null
layerType?.let {
val uri = Uri.fromFile(file)
MapLayerItem(
name = file.nameWithoutExtension,
uri = uri,
isVisible = !hiddenLayerUrls.contains(uri.toString()),
layerType = it,
)
}
} else {
null
}
}
val networkItems =
googleMapsPrefs.networkMapLayers.value.mapNotNull { networkString ->

View file

@ -27,7 +27,7 @@ internal fun Project.configureSpotless(extension: SpotlessExtension) {
kotlin {
target("src/*/kotlin/**/*.kt", "src/*/java/**/*.kt")
targetExclude("**/build/**/*.kt")
ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) }
ktfmt(libs.version("ktfmt")).kotlinlangStyle().configure { it.setMaxWidth(120) }
ktlint(ktlintVersion)
.setEditorConfigPath(rootProject.file("config/spotless/.editorconfig").path)
licenseHeaderFile(rootProject.file("config/spotless/copyright.kt"))
@ -35,7 +35,7 @@ internal fun Project.configureSpotless(extension: SpotlessExtension) {
kotlinGradle {
target("**/*.gradle.kts")
targetExclude("**/build/**", "**/dependencies/**")
ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) }
ktfmt(libs.version("ktfmt")).kotlinlangStyle().configure { it.setMaxWidth(120) }
ktlint(ktlintVersion)
.setEditorConfigPath(rootProject.file("config/spotless/.editorconfig").path)
licenseHeaderFile(

View file

@ -1,5 +0,0 @@
# Track android_kable_migration_20260314 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "android_kable_migration_20260314",
"type": "feature",
"status": "completed",
"created_at": "2026-03-14T17:15:00Z",
"updated_at": "2026-03-14T17:15:00Z",
"description": "Replace Nordic with Kable on Android"
}

View file

@ -1,44 +0,0 @@
# Implementation Plan: Replace Nordic with Kable on Android (Deduplication Pass)
## Phase 1: Deduplicate Kable Abstractions into `commonMain` [checkpoint: 709f6e3]
- [x] Task: Extract common Kable state mapping logic from jvmMain to commonMain 10cdd16
- [x] Create `commonMain` tests for `BleConnectionState` mapping using Kable `State`
- [x] Move `KableMeshtasticRadioProfile` and `KableBleConnection` logic that doesn't depend on platform specifics to `commonMain`
- [x] Task: Implement common Kable `Scanner` and `Peripheral` wrappers 2691d70
- [x] Extract generic connection lifecycle (connect, reconnect, close) to `commonMain` using Kable's `Peripheral` interface
- [x] Task: Conductor - User Manual Verification 'Phase 1: Deduplicate Kable Abstractions into commonMain' (Protocol in workflow.md) 709f6e3
## Phase 2: Implement Kable Backend for Android (`androidMain`) [checkpoint: 12217de]
- [x] Task: Add Kable dependency to Android source set in `core:ble/build.gradle.kts` 011d619
- [x] Task: Implement Android-specific `BleConnectionFactory` and `BleScanner` using the deduplicated `commonMain` logic 589ee93
- [x] Write failing integration tests for Android Kable scanner (using fakes/mocks)
- [x] Implement `KableBleScanner` for `androidMain`
- [x] Write failing integration tests for Android Kable connection (using fakes/mocks)
- [x] Implement `KableBleConnection` for `androidMain` (handling Android-specific MTU requests if necessary)
- [x] Task: Conductor - User Manual Verification 'Phase 2: Implement Kable Backend for Android' (Protocol in workflow.md) 12217de
## Phase 3: Migrate OTA Firmware Update Logic [checkpoint: 663c8e2]
- [x] Task: Deprecate `NordicDfuHandler` and replace with Kable-based DFU 06fe4f5
- [x] Write failing tests for Kable DFU integration
- [x] Implement new DFU handler in `feature:firmware` using `MeshtasticRadioProfile` / Kable abstraction
- [x] Task: Conductor - User Manual Verification 'Phase 3: Migrate OTA Firmware Update Logic' (Protocol in workflow.md) 663c8e2
## Phase 4: Wire Kable into Android App and Remove Nordic [checkpoint: ebe1617]
- [x] Task: Deprecate and remove `NordicBleInterface` and `AndroidBleConnection` ebe1617
- [x] Remove `NordicAndroidCommonLibraries` and `NordicDfuLibrary` from `gradle/libs.versions.toml` and build files
- [x] Delete `NordicBleInterface.kt` and associated Nordic-specific radio implementations
- [x] Task: Wire new `androidMain` Kable implementation into the Koin DI graph ebe1617
- [x] Update `AndroidRadioControllerImpl` or DI modules to provide the new Kable `BleConnectionFactory` and `BleScanner`
- [x] Task: Conductor - User Manual Verification 'Phase 4: Wire Kable into Android App and Remove Nordic' (Protocol in workflow.md) ebe1617
## Phase 5: Final Testing and Integration [checkpoint: 4778c0e]
- [x] Task: Update Android `app` UI tests and BLE unit tests to use Kable fakes 4778c0e
- [x] Fix any failing tests related to the Nordic removal
- [x] Task: Manual end-to-end verification 4778c0e
- [x] Build and run the Android app, verify BLE scanning, connecting, and messaging
- [x] Verify OTA updates work via BLE
- [x] Verify the Desktop app still functions correctly
- [x] Task: Conductor - User Manual Verification 'Phase 5: Final Testing and Integration' (Protocol in workflow.md) 4778c0e
## Phase: Review Fixes
- [x] Task: Apply review suggestions e5dffd9

View file

@ -1,28 +0,0 @@
# Specification: Replace Nordic with Kable on Android (Deduplication Pass)
## Overview
This track executes a full migration of the Android application's BLE transport layer from the legacy Nordic Android Common Libraries to the multiplatform Kable library. Building upon the successful `MeshtasticRadioProfile` abstraction introduced for the Desktop target, this track aims to unify the BLE transport layer across all platforms (Android, Desktop, iOS) under a single KMP technology stack. Crucially, this pass focuses on **maximal code deduplication**, moving as much BLE logic as possible into `commonMain` to share it across all targets, including OTA firmware update logic.
## Functional Requirements
- **Kable Integration:** Implement the `MeshtasticRadioProfile` using Kable for the `androidMain` source set, replacing the existing Nordic implementation.
- **Maximal Deduplication:** Refactor the existing Kable `jvmMain` implementation and the new `androidMain` implementation to extract common connection management, scanning logic, and characteristic observation into `core:ble/commonMain`.
- **OTA Firmware Updates:** Migrate the Android OTA firmware update logic (currently handled by `NordicDfuHandler`) to use the new Kable/KMP abstraction.
- **Full Migration:** The Android app must exclusively use the new Kable backend for all BLE operations (scanning, connecting, data transfer, firmware updates).
- **Deprecation/Removal:** Remove all dependencies on the Nordic Android Common Libraries and Nordic DFU libraries from the project configuration (`build.gradle.kts`, version catalogs).
- **Feature Parity:** The new Kable implementation on Android must maintain full feature parity with the previous Nordic implementation, including connection stability, MTU negotiation, and data throughput.
## Non-Functional Requirements
- **Expanded Testing:** Adapt existing Android BLE tests to use Kable fakes and write new `commonMain` tests to expand test coverage for the shared KMP BLE abstraction.
- **Architecture:** Maintain strict adherence to the MVI/UDF patterns and the pure KMP DI architecture (Koin annotations).
## Acceptance Criteria
- [ ] Kable backend is fully implemented for Android (`androidMain`).
- [ ] Nordic Android Common Libraries and DFU dependencies are completely removed from the project.
- [ ] Android application successfully scans, connects, and transfers data via BLE using Kable.
- [ ] BLE logic (connection state, profile mapping, retry logic) is heavily deduplicated into `core:ble/commonMain`.
- [ ] OTA firmware update logic is successfully migrated to use the Kable backend.
- [ ] Existing BLE tests are updated or replaced, and all test suites pass.
- [ ] New KMP BLE tests are added, improving overall test coverage.
## Out of Scope
- Migrating USB or TCP network transports.

View file

@ -1,5 +0,0 @@
# Track deep_dive_docs_20260316 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "deep_dive_docs_20260316",
"type": "chore",
"status": "completed",
"created_at": "2026-03-16T12:00:00Z",
"updated_at": "2026-03-16T12:00:00Z",
"description": "do a deep dive of project docs and plans in /docs - verify against actual project/codebase state, then validate against modern best practices for android, kotlin, kmp, and the dependencies used. be thorough - check all the major dependencies. Update docs and plans accordingly."
}

View file

@ -1,19 +0,0 @@
# Implementation Plan: Deep Dive & Validation of Project Docs & Plans
## Phase 1: Audit & Discovery [checkpoint: 105763b]
- [x] Task: Audit Gradle dependencies (`libs.versions.toml`) against 2026 KMP best practices (Koin, Compose, Navigation 3, etc.). baed3d6
- [x] Task: Analyze Core Logic (`core:*`) and platform modules (Android, Desktop) for architectural alignment (MVI/Shared ViewModels). baed3d6
- [x] Task: Review current UI and feature module implementations for Compose Multiplatform standard adherence. baed3d6
- [x] Task: Evaluate testing patterns, coverage, and the use of shared test doubles (`core:testing`). baed3d6
- [x] Task: Compile a list of discrepancies between current documentation/plans and the actual codebase. baed3d6
- [x] Task: Conductor - User Manual Verification 'Phase 1: Audit & Discovery' (Protocol in workflow.md) 105763b
## Phase 2: Documentation Updates [checkpoint: 7212ff1]
- [x] Task: Update `/docs` and root-level guides (e.g., `GEMINI.md`, `kmp-status.md`, `roadmap.md`) to reflect the current, verified codebase state. baed3d6
- [x] Task: Add explicit documentation for areas where the codebase diverges from documented best practices (flagging for future refactoring). baed3d6
- [x] Task: Conductor - User Manual Verification 'Phase 2: Documentation Updates' (Protocol in workflow.md) 7212ff1
## Phase 3: Plan Adjustment
- [x] Task: Create new, actionable tasks in the project's main `plan.md` (roadmap.md) to address the flagged discrepancies (e.g., refactoring non-compliant Koin modules, updating deprecated APIs). baed3d6
- [x] Task: Review and finalize the overall project roadmap and status based on the audit findings. baed3d6
- [x] Task: Conductor - User Manual Verification 'Phase 3: Plan Adjustment' (Protocol in workflow.md) 7212ff1

View file

@ -1,19 +0,0 @@
# Specification: Deep Dive & Validation of Project Docs & Plans
## Overview
This track involves a comprehensive review and deep dive into the project's documentation (`/docs`, `GEMINI.md`, etc.) and plans. The goal is to verify the documented state against the actual Kotlin Multiplatform (KMP) codebase and validate it against modern 2026 KMP and Android best practices. The outcome will be updated documentation reflecting the current state and flagged/planned changes for areas not following best practices.
## Functional Requirements
- **Codebase Verification:** Analyze all major areas including Core Logic (`core:*`), UI & Features (Compose Multiplatform), Dependencies (Gradle version catalogs), and Platform-specific implementations (Android, Desktop).
- **Best Practice Validation:** Evaluate the codebase against modern standards, specifically focusing on Architecture (MVI/Shared ViewModels), Navigation (Navigation 3), Dependency Injection (Koin Annotations K2), and Testing patterns.
- **Documentation Update:** Modify existing documentation and plans to accurately reflect the current state of the codebase and dependencies.
- **Refactoring Proposals:** Identify and flag code or architectural decisions that deviate from best practices, outlining necessary refactoring steps in the project's plans.
## Acceptance Criteria
- All documentation in `/docs` and root-level guides accurately reflect the current codebase.
- A comprehensive audit of major dependencies has been performed and validated against 2026 KMP standards.
- Discrepancies between the codebase and best practices are clearly flagged and actionable tasks are added to the project plans.
- The `plan.md` reflects the updated status and any new tasks generated from the audit.
## Out of Scope
- Direct refactoring or modification of the actual Kotlin/Android codebase during this specific track (this track focuses on documentation, planning, and flagging).

View file

@ -1,5 +0,0 @@
# Track desktop_ble_kable_20260314 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "desktop_ble_kable_20260314",
"type": "feature",
"status": "completed",
"created_at": "2026-03-14T12:00:00Z",
"updated_at": "2026-03-14T12:00:00Z",
"description": "Kable swap Keep Nordic on Android short-term. Add Kable backend only for jvmMain in core:ble first (desktop BLE enablement). Introduce a MeshtasticRadioProfile abstraction in core:ble/commonMain so NordicBleInterface no longer depends on Android/Nordic classes. Once that seam is clean, decide whether Android should stay Nordic or move to Kable."
}

View file

@ -1,37 +0,0 @@
# Implementation Plan: Desktop BLE Enablement via Kable
## Phase 1: Define `MeshtasticRadioProfile` Abstraction [checkpoint: 1206e87]
- [x] Task: Define `MeshtasticRadioProfile` interface in `core:ble/commonMain` eaa623a
- [ ] Write tests for expected profile behavior (e.g., state flow emission) using a simple fake
- [ ] Implement `MeshtasticRadioProfile` interface, data classes for states, and configuration
- [x] Task: Conductor - User Manual Verification 'Phase 1: Define `MeshtasticRadioProfile` Abstraction' (Protocol in workflow.md) 1206e87
## Phase 2: Refactor Nordic Implementation to use Abstraction [checkpoint: dc700a5]
- [x] Task: Implement `MeshtasticRadioProfile` in the existing Nordic implementation (`androidMain`) 83a8a9b
- [ ] Write/adapt existing Android tests to verify `MeshtasticRadioProfile` adherence
- [ ] Implement wrapper/adapter for Nordic classes to fulfill `MeshtasticRadioProfile`
- [x] Task: Decouple app-level BLE transport from Nordic types 2dfedde
- [ ] Write tests to ensure BLE transport only relies on `MeshtasticRadioProfile`
- [ ] Refactor transport layer (e.g., `NordicBleInterface` usages) to use the new profile interface
- [x] Task: Conductor - User Manual Verification 'Phase 2: Refactor Nordic Implementation to use Abstraction' (Protocol in workflow.md) dc700a5
## Phase 3: Implement Kable Backend for Desktop [checkpoint: ed2a459]
- [x] Task: Setup Kable dependencies for `jvmMain` in `core:ble` b152eff
- [ ] Update `build.gradle.kts` to include Kable dependency for Desktop
- [x] Task: Implement Kable `MeshtasticRadioProfile` backend (`jvmMain`) fa5cc82
- [ ] Write `commonMain` unit tests with Kable fakes to verify scanning, connection, and read/write operations
- [ ] Implement Kable scanning logic
- [ ] Implement Kable connection and characteristic management
- [ ] Implement Kable read/write data transfer logic
- [x] Task: Conductor - User Manual Verification 'Phase 3: Implement Kable Backend for Desktop' (Protocol in workflow.md) ed2a459
## Phase 4: Integration and Final Testing [checkpoint: af6d3b3]
- [x] Task: Integrate Kable backend into Desktop app DI graph 28afcad
- [ ] Wire up the Kable implementation in `desktop` module DI
- [x] Task: End-to-end verification 84aae75
- [ ] Verify Android app still compiles and connects using Nordic
- [ ] Verify Desktop app compiles and connects using Kable
- [x] Task: Conductor - User Manual Verification 'Phase 4: Integration and Final Testing' (Protocol in workflow.md) af6d3b3
## Phase: Review Fixes
- [x] Task: Apply review suggestions b36da82

View file

@ -1,31 +0,0 @@
# Specification: Desktop BLE Enablement via Kable
## Overview
This track introduces a Kable BLE backend specifically for the `jvmMain` (Desktop) target within `core:ble`. To facilitate this without breaking the existing Android implementation, we will introduce a `MeshtasticRadioProfile` abstraction in `core:ble/commonMain`. This abstraction will ensure that the app-level BLE transport path no longer depends on Android-specific or Nordic-specific classes. Initially, Android will continue to use the Nordic BLE implementation, while Desktop will use Kable. Once this seam is proven, a future decision will determine whether Android should fully migrate to Kable. This approach lays the groundwork for seamless integration of future targets (e.g., iOS) under the same KMP abstraction.
## Functional Requirements
- **MeshtasticRadioProfile Abstraction:** Introduce a multiplatform interface (`MeshtasticRadioProfile`) in `core:ble/commonMain` to abstract all BLE operations.
- **Remove Nordic Dependencies:** Ensure that the app-level BLE transport path is entirely decoupled from Nordic types, relying solely on the new abstraction.
- **Kable Backend (jvmMain):** Implement the Kable backend for the Desktop target. This backend must support all core BLE operations:
- Scanning for nearby Meshtastic devices.
- Establishing and managing BLE connections.
- Reading from and writing to characteristics (sending/receiving protobuf payloads).
- **Nordic Backend Preservation (androidMain):** Update the existing Android Nordic implementation to implement the new `MeshtasticRadioProfile` interface without changing its core behavior.
- **Future-Proofing:** Design the abstraction in a way that is generic enough to support adding an iOS or other future target's BLE implementation with minimal refactoring.
## Non-Functional Requirements
- **Testing:** New `commonMain` unit tests must be written utilizing fakes for the Kable implementation. This is crucial as we cannot rely on Nordic's ready-made mocks in a multiplatform context or if a full migration to Kable occurs.
- **Architecture:** The abstraction must adhere to the project's KMP goals, keeping `core:ble/commonMain` completely free of platform-specific imports (e.g., `java.*`, `android.*`).
- **Compatibility:** The Android build and BLE functionality must remain fully functional using the existing Nordic library.
## Acceptance Criteria
- [ ] `MeshtasticRadioProfile` is defined in `core:ble/commonMain`.
- [ ] No Nordic-specific or Android-specific types are present in the app-level BLE transport path.
- [ ] Desktop application can successfully scan, connect, and perform read/write operations with a Meshtastic device using Kable.
- [ ] Android application continues to function normally using the Nordic library.
- [ ] New unit tests using Kable fakes are added to `commonMain` and pass successfully.
- [ ] The abstraction architecture provides a clear path for future platform support (like iOS).
## Out of Scope
- Migrating the Android application to use the Kable backend (this will be evaluated after this track is complete).
- Modifying non-BLE network transports (e.g., USB, TCP).

View file

@ -1,5 +0,0 @@
# Track desktop_di_autowiring_20260313 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "desktop_di_autowiring_20260313",
"type": "chore",
"status": "completed",
"created_at": "2026-03-13T12:00:00Z",
"updated_at": "2026-03-13T12:00:00Z",
"description": "Architecture Health & DI (Immediate Priority) * Desktop Koin checkModules() test: Add a test to ensure Desktop DI bindings are validated at compile-time/test-time so we catch missing interfaces early. * Auto-wire Desktop ViewModels: Configure KSP so we can eliminate the manual ViewModel wiring in DesktopKoinModule and rely on @KoinViewModel annotations like Android does."
}

View file

@ -1,16 +0,0 @@
# Implementation Plan: Desktop DI Auto-Wiring and Validation
## Phase 1: Setup KSP for Desktop and Test Scaffolding
- [x] Task: Update the `meshtastic.koin` convention plugin (or equivalent `build-logic` files) to apply KSP to the `jvmMain` (Desktop) target for `@KoinViewModel` auto-wiring.
- [x] Task: Write Failing Test: Create `DesktopKoinTest.kt` in `desktop/src/test/kotlin/org/meshtastic/desktop/di/` using `kotlin.test`.
- [x] Initialize Koin application.
- [x] Include `desktopModule()`, `desktopPlatformModule()`, and `desktopPlatformStubsModule()`.
- [x] Call `checkModules()` inside the test and ensure it fails if there are missing interfaces.
- [x] Task: Implement to Pass Tests: Add any missing stubs or correct module includes in `desktopPlatformStubsModule()` to ensure the basic Koin graph resolves.
- [x] Task: Conductor - User Manual Verification 'Phase 1: Setup KSP for Desktop and Test Scaffolding' (Protocol in workflow.md)
## Phase 2: Auto-wire ViewModels and Clean Up
- [x] Task: Refactor: Remove manual `viewModel { ... }` blocks from `DesktopKoinModule.kt` (if any are present).
- [x] Task: Implement: Ensure the desktop build configuration (`desktop/build.gradle.kts`) correctly includes the KSP-generated Koin modules and that KSP targets the JVM platform.
- [x] Task: Implement to Pass Tests: Verify that running `./gradlew :desktop:test` succeeds and that `DesktopKoinTest.kt` validates the new KSP-wired graph.
- [x] Task: Conductor - User Manual Verification 'Phase 2: Auto-wire ViewModels and Clean Up' (Protocol in workflow.md)

View file

@ -1,25 +0,0 @@
# Specification: Desktop DI Auto-Wiring and Validation
## Overview
This track addresses immediate architecture health priorities for the Desktop KMP target:
1. **Desktop Koin `checkModules()` test:** Add a compile-time/test-time validation test to ensure Desktop DI bindings resolve correctly and catch missing interfaces early.
2. **Auto-wire Desktop ViewModels:** Configure KSP to generate Koin modules for ViewModels annotated with `@KoinViewModel` in the JVM target, eliminating the need for manual ViewModel wiring in `DesktopKoinModule`.
## Functional Requirements
- **KSP Configuration:** Update the `meshtastic.koin` (or equivalent) convention plugin to apply KSP and Koin annotations processing to the `jvmMain` (Desktop) target.
- **ViewModel Auto-Wiring:** Remove all manual `viewModel { ... }` definitions in `DesktopKoinModule` and ensure they are successfully replaced by the KSP-generated Koin modules.
- **DI Validation Test:** Implement a new test file (e.g., `DesktopKoinTest.kt`) in `desktop/src/test/kotlin/org/meshtastic/desktop/di/` using `kotlin.test`.
- **Test Scope:** The `checkModules()` test must include and validate all active Desktop Koin modules, including `desktopModule()`, `desktopPlatformModule()`, `desktopPlatformStubsModule()`, and any KSP-generated modules.
## Non-Functional Requirements
- **Build Performance:** The addition of KSP to the JVM target should not unnecessarily degrade build times. Cacheability must be maintained.
- **Style:** Adhere strictly to the project's existing Kotlin code style and Koin best practices.
## Acceptance Criteria
- [ ] Running `./gradlew :desktop:test` executes the new `checkModules()` test successfully.
- [ ] No manual ViewModel definitions remain in `DesktopKoinModule` for shared ViewModels (they are auto-wired).
- [ ] If a dependency is missing from the Desktop DI graph, the `checkModules()` test fails explicitly.
## Out of Scope
- Migrating other platforms (Android, iOS) DI implementations.
- Refactoring the internal logic of the ViewModels themselves.

View file

@ -1,5 +0,0 @@
# Track desktop_parity_20260311 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "desktop_parity_20260311",
"type": "feature",
"status": "completed",
"created_at": "2026-03-11T12:00:00Z",
"updated_at": "2026-03-11T12:00:00Z",
"description": "continue bringing desktop up to parity with android"
}

View file

@ -1,41 +0,0 @@
# Implementation Plan
## Phase 1: Navigation Parity [checkpoint: 5b8e194]
- [x] Task: Extract shared navigation contracts f7e0c2e
- [x] Define shared top-level destinations and route metadata in `core:navigation`.
- [x] Update Android `TopLevelDestination` to use the shared contract.
- [x] Update Desktop `DesktopDestination` to use the shared contract.
- [x] Add parity tests for navigation routing.
- [x] Task: Conductor - User Manual Verification 'Phase 1: Navigation Parity' (Protocol in workflow.md)
## Phase 2: DI Parity [checkpoint: 5bdc099]
- [x] Task: Migrate Desktop Koin Modules 93fd600
- [x] Configure KSP for the JVM target in necessary modules.
- [x] Ensure Koin annotations are processed for Desktop.
- [x] Replace manual ViewModel wiring in `DesktopKoinModule` with generated modules.
- [x] Task: Conductor - User Manual Verification 'Phase 2: DI Parity' (Protocol in workflow.md)
## Phase 3: Connections Parity [checkpoint: 4be5732]
- [x] Task: Create `feature:connections` module 242faa6
- [x] Set up the KMP module structure with `commonMain`, `androidMain`, and `jvmMain` (or `desktopMain`).
- [x] Move device discovery UI and ViewModels from `app` and `desktop` into the new module.
- [x] Consolidate the Connections UI into a shared screen in `feature:connections`.
- [x] Task: Conductor - User Manual Verification 'Phase 3: Connections Parity' (Protocol in workflow.md)
## Phase 4: UI/Feature Parity [checkpoint: e83a07a]
- [x] Task: Implement missing Map and Chart features on Desktop 128ee3b
- [x] Evaluate and implement a KMP-friendly mapping library or placeholder for Desktop.
- [x] Refactor Vico charts or provide a KMP charting alternative/placeholder for Desktop.
- [x] Task: Refinement - Connections UI and Messaging Parity c98db4f
- [x] Hide unsupported transports (BLE/USB) on Desktop via BuildUtils proxy.
- [x] Update message titles to resolve channel names for broadcasts.
- [x] Add snackbar for no-op gaps (delivery info).
- [x] Shared AnimatedConnectionsNavIcon for "blinky light" parity.
- *Note: Connection type filtering is currently hardcoded via BuildUtils.sdkInt. This should be refactored to use dynamic transport discovery once the 'Extract hardware transport' track is complete.*
- [x] Task: Conductor - User Manual Verification 'Phase 4: UI/Feature Parity' (Protocol in workflow.md) e83a07a
## Phase 5: Multi-Target Hardening [checkpoint: 91784a9]
- [x] Task: Clean up remaining platform-specific leaks f5f1e29
- [x] Ensure `commonMain` is free of any `java.*` dependencies.
- [x] Verify test suite passes on both Android and Desktop JVM targets.
- [x] Task: Conductor - User Manual Verification 'Phase 5: Multi-Target Hardening' (Protocol in workflow.md) 91784a9

View file

@ -1,25 +0,0 @@
# Track Specification: Desktop Parity & Multi-Target Hardening
## Overview
This track aims to bring the Desktop target up to parity with the Android app and lay the foundation for future targets (like iOS). This involves eliminating duplicated code, fixing structural gaps, and sharing UI, navigation, and DI contracts across platforms.
## Functional Requirements
- **Connections Parity:** Consolidate device discovery (BLE/USB/TCP) from the app and desktop into a shared `feature:connections` module.
- **DI Parity:** Remove manual ViewModel wiring in `DesktopKoinModule` and transition to using KSP-generated Koin modules for Desktop.
- **UI/Feature Parity:** Implement missing map and charting functionality on Desktop, or provide robust KMP abstractions where direct translation isn't possible.
- **Navigation Parity:** Extract shared navigation contracts to stop drift between Android and Desktop shells (following `decisions/navigation3-parity-2026-03.md`).
## Non-Functional Requirements
- **Architecture Readiness:** Ensure code abstractions support the subsequent addition of an iOS target.
- **Structural Purity:** `commonMain` must be completely free of platform-specific APIs (like `java.*` or Android-specific APIs).
## Acceptance Criteria
- Device discovery screens share UI and view models in `feature:connections`.
- Desktop DI uses generated modules without manual ViewModel instantiation.
- Map and charting features are either functioning on Desktop or have solid KMP placeholders.
- Android and Desktop Navigation shells utilize shared configuration and metadata.
- Both functional and structural parity goals are verified through automated builds and testing where applicable.
## Out of Scope
- Full deployment to iOS or other unannounced platforms (only preparing the architecture).
- Deep refactoring of underlying hardware interactions beyond what is necessary to expose a shared UI contract.

View file

@ -1,5 +0,0 @@
# Track desktop_serial_transport_20260317 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "desktop_serial_transport_20260317",
"type": "feature",
"status": "completed",
"created_at": "2026-03-17T12:00:00Z",
"updated_at": "2026-03-17T12:00:00Z",
"description": "Implement Serial/USB transport for the Desktop target using jSerialComm. This fulfills the medium-term priority for direct radio connections on JVM and uses the shared RadioTransport interface."
}

View file

@ -1,21 +0,0 @@
# Implementation Plan: Desktop Serial/USB Transport
## Phase 1: JVM Setup & Dependency Integration [checkpoint: a05916d]
- [x] Task: Add the `jSerialComm` library to the `jvmMain` dependencies of the networking module. [checkpoint: 8994c66]
- [x] Task: Create a `jvmMain` stub implementation for a `SerialTransport` class that implements the shared `RadioTransport` interface. [checkpoint: 83668e4]
## Phase 2: Serial Port Scanning & Connection Management [checkpoint: 9cda87d]
- [x] Task: Implement port discovery using `jSerialComm` to list available serial ports. [checkpoint: c72501d]
- [x] Task: Implement connect/disconnect logic for a selected serial port, handling port locking and baud rate configuration. [checkpoint: 23ee815]
- [x] Task: Map the input/output streams of the open serial port to the existing KMP stream framing logic (`StreamFrameCodec`). [checkpoint: 04ba9c2]
## Phase 3: UI Integration
- [x] Task: Update the `feature:connections` UI or `DesktopScannerViewModel` to poll the new `SerialTransport` for available ports. [checkpoint: 2e85b5a]
- [x] Task: Wire the user's serial port selection to initiate the connection via the DI graph and active service logic. [checkpoint: 94cb97c]
## Phase 4: Validation [checkpoint: 1055752]
- [x] Task: Verify end-to-end communication with a physical Meshtastic device over USB on the desktop target. [checkpoint: 1055752]
- [x] Task: Ensure CI builds cleanly and that no `java.*` dependencies leaked into `commonMain`. [checkpoint: 1055752]
## Phase: Review Fixes
- [x] Task: Apply review suggestions [checkpoint: d2f7c82]

View file

@ -1,20 +0,0 @@
# Specification: Desktop Serial/USB Transport via jSerialComm
## Objective
Implement direct radio connection via Serial/USB on the Desktop (JVM) target using the `jSerialComm` library. This fulfills the medium-term priority of bringing physical transport parity to the desktop app and validates the newly extracted `RadioTransport` abstraction in `core:repository`.
## Background
Currently, the desktop app supports TCP connections via a shared `StreamFrameCodec`. To provide parity with Android's USB serial connection capabilities, we need to implement a JVM-specific serial transport. The `jSerialComm` library is a widely-used, cross-platform Java library that handles native serial port communication without requiring complex JNI setups.
## Requirements
- Introduce `jSerialComm` dependency to the `jvmMain` source set of the appropriate core module (likely `core:network` or a new `core:serial` module).
- Implement the `RadioTransport` interface (defined in `core:repository/commonMain`) for the desktop target, wrapping `jSerialComm`'s port scanning and connection logic.
- Ensure the serial data is encoded/decoded using the same protobuf frame structure utilized by the TCP transport (e.g., leveraging the existing `StreamFrameCodec`).
- Integrate the new transport into the `feature:connections` UI on the desktop so users can scan for and select connected USB serial devices.
- Retain platform purity: keep all `jSerialComm` and `java.io.*` imports strictly within the `jvmMain` source set.
## Success Criteria
- [ ] Desktop application successfully scans for connected Meshtastic devices over USB/Serial.
- [ ] Users can select a serial port from the `feature:connections` UI and establish a connection.
- [ ] Two-way protobuf communication is verified (e.g., the app receives node info and can send a message).
- [ ] The implementation uses the shared `RadioTransport` interface without leaking JVM dependencies into `commonMain`.

View file

@ -1,8 +0,0 @@
# Desktop UX Enhancements
This track focuses on integrating desktop-specific Compose Multiplatform APIs to improve the native feel and functionality of the desktop client.
## Track Files
- [Specification](./spec.md)
- [Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,7 +0,0 @@
{
"id": "desktop_ux_enhancements_20260316",
"name": "Desktop UX Enhancements",
"status": "completed",
"priority": "medium",
"tags": ["desktop", "ux", "compose"]
}

View file

@ -1,19 +0,0 @@
# Implementation Plan: Desktop UX Enhancements
## Phase 1: Tray & Notifications (Current Focus)
- [x] Add `isAppVisible` state to `Main.kt`.
- [x] Introduce `rememberTrayState()` and the `Tray` composable.
- [x] Update `Window` `onCloseRequest` to toggle visibility instead of exiting the app.
- [x] Add a `DesktopNotificationService` interface and implementation using `TrayState`.
## Phase 2: Window State Persistence
- [x] Create `DesktopPreferencesDataSource` via DataStore.
- [x] Intercept window bounds changes and write to preferences.
- [x] Read preferences on startup to initialize `rememberWindowState(...)`.
## Phase 3: Menu Bar & Shortcuts
- [x] Integrate the `MenuBar` composable into the `Window`.
- [x] Implement global application shortcuts.
## Phase: Review Fixes
- [x] Task: Apply review suggestions 3bda1c007

View file

@ -1,10 +0,0 @@
# Specification: Desktop UX Enhancements
## Goal
To implement native desktop behaviors like a system tray, notifications, a menu bar, and persistent window state for the Compose Multiplatform Desktop app.
## Requirements
1. **System Tray & Notifications**: The app should show a tray icon with a basic context menu ("Open", "Settings", "Quit"). It should support a "Minimize to Tray" flow rather than exiting immediately when closed. Notifications should be dispatchable via `TrayState` for key mesh events.
2. **Window State Persistence**: The app should remember its last window size, position, and maximized state across launches.
3. **Menu Bar**: A native MenuBar (File, Edit, View, Window, Help) should provide standard navigation and controls.
4. **Keyboard Shortcuts**: Common actions should be bound to standard native keyboard shortcuts (e.g. `Cmd/Ctrl+,` for Settings).

View file

@ -1,5 +0,0 @@
# Track doc_consolidation_20260311 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "doc_consolidation_20260311",
"type": "feature",
"status": "completed",
"created_at": "2026-03-11T00:00:00Z",
"updated_at": "2026-03-11T00:00:00Z",
"description": "Implement document consolidation plan"
}

View file

@ -1,35 +0,0 @@
# Implementation Plan: Implement document consolidation plan
## Phase 1: Prune and Consolidate Session Artifacts
- [x] Task: Consolidate session artifacts into `docs/archive/kmp-phase3-testing-consolidation.md`. [d8becb2]
- [x] Write Tests (Verify documentation structure)
- [x] Read all 12+ session update files.
- [x] Create `kmp-phase3-testing-consolidation.md` with merged key findings and test coverage metrics.
- [x] Task: Delete redundant point-in-time files from `docs/agent-playbooks/`. [d8becb2]
- [x] Write Tests (Verify file removal)
- [x] Delete `CHECKLIST-testing-consolidation.md` and other 11 listed files.
- [x] Task: Relocate remaining planning documents. [d8becb2]
- [x] Write Tests (Verify correct destination paths)
- [x] Merge `phase-4-desktop-completion-plan.md` into `docs/roadmap.md` under Phase 4 Desktop section and delete the original.
- [x] Move `kmp-feature-migration-plan.md` to `docs/archive/`.
- [x] Task: Conductor - User Manual Verification 'Phase 1: Prune and Consolidate Session Artifacts' (Protocol in workflow.md) [checkpoint: d8becb2]
## Phase 2: Synthesize Status & Roadmap
- [x] Task: Update `docs/kmp-status.md`. [37fd055]
- [x] Write Tests (Verify updated metric output)
- [x] Update testing score to reflect Phase 3 completion (80 tests across 6 features).
- [x] Task: Update `docs/roadmap.md`. [37fd055]
- [x] Write Tests (Verify roadmap section exists)
- [x] Mark Phase 3 as substantially complete.
- [x] Task: Conductor - User Manual Verification 'Phase 2: Synthesize Status & Roadmap' (Protocol in workflow.md) [checkpoint: 37fd055]
## Phase 3: Verify and Validate Best Practices
- [x] Task: Update `AGENTS.md` and playbooks for 2026 KMP Best Practices. [85db394]
- [x] Write Tests (Verify updated content)
- [x] Document Koin Annotations (K2) best practices in `AGENTS.md` and `di-navigation3-anti-patterns-playbook.md`.
- [x] Document Shared ViewModels (MVI) recommendations.
- [x] Task: Documentation Quality Checks. [85db394]
- [x] Write Tests (Verify links resolve)
- [x] Update `docs/agent-playbooks/README.md`.
- [x] Rename `testing-quick-ref.sh` to `testing-quick-ref.md` and update internal references.
- [x] Task: Conductor - User Manual Verification 'Phase 3: Verify and Validate Best Practices' (Protocol in workflow.md) [checkpoint: 85db394]

View file

@ -1,13 +0,0 @@
# Track Specification: Implement document consolidation plan
## Objective
Consolidate, prune, verify, and validate project plans and documentation against 2026 Kotlin Multiplatform (KMP) best practices and the latest dependency standards.
## Background & Motivation
The `docs/agent-playbooks/` directory has accumulated numerous point-in-time session summaries, checklists, and status reports (e.g., `SESSION-FINAL-SUMMARY.md`, `TEST-VERIFICATION-REPORT.md`) during the Phase 3 testing consolidation sprint. These files clutter the directory and dilute the actual "playbooks" (reusable guides). Additionally, the project documentation (`kmp-status.md`, `roadmap.md`, `AGENTS.md`) needs to be synthesized to reflect the recently completed work and validated against 2026 KMP industry standards (e.g., Koin K2 compiler plugin best practices, shared ViewModels, Navigation 3).
## Scope
1. **Prune and Consolidate Session Artifacts:** Merge the key findings into a single historical record (`docs/archive/kmp-phase3-testing-consolidation.md`) and delete 12+ redundant point-in-time files. Relocate `phase-4-desktop-completion-plan.md` into `docs/roadmap.md` and move `kmp-feature-migration-plan.md` to `docs/archive/`.
2. **Synthesize Status & Roadmap:** Update `docs/kmp-status.md` and `docs/roadmap.md` with new testing metrics (80 tests across 6 features) and expanded Phase 4 Desktop tasks.
3. **Verify and Validate against 2026 KMP Best Practices:** Validate the usage of Koin `@Module` and `@KoinViewModel` annotations in `commonMain` according to Koin 4.2 native compiler plugin best practices. Update `AGENTS.md` and `di-navigation3-anti-patterns-playbook.md` to officially recommend this pattern and multiplatform `androidx.lifecycle.ViewModel` in `commonMain`.
4. **Documentation Quality Checks:** Verify `README.md` in playbooks correctly points to retained playbooks. Rename `testing-quick-ref.sh` to `testing-quick-ref.md` and update internal references.

View file

@ -1,5 +0,0 @@
# Track expand_testing_20260318 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "expand_testing_20260318",
"type": "chore",
"status": "completed",
"created_at": "2026-03-18T10:00:00Z",
"updated_at": "2026-03-18T10:00:00Z",
"description": "Expand Testing Coverage"
}

View file

@ -1,32 +0,0 @@
# Implementation Plan: Expand Testing Coverage
## Phase 1: Baseline Measurement [checkpoint: 6d9ad46]
- [x] Task: Execute `./gradlew koverLog` and record current project test coverage. 8bdd673a1
- [x] Task: Conductor - User Manual Verification 'Phase 1: Baseline Measurement' (Protocol in workflow.md) 6d9ad468c
## Phase 2: Feature ViewModel Migration to Turbine [checkpoint: 61b9595]
- [x] Task: Refactor `MetricsViewModelTest` to use `Turbine` and `Mokkery` in `commonTest`. 79e059286
- [x] Task: Refactor `MessageViewModelTest` to use `Turbine` and `Mokkery` in `commonTest`. b45697b53
- [x] Task: Refactor `RadioConfigViewModelTest` to use `Turbine` and `Mokkery` in `commonTest`. 33e10fc6c
- [x] Task: Refactor `NodeListViewModelTest` to use `Turbine` and `Mokkery` in `commonTest`. 33e10fc6c
- [x] Task: Refactor remaining `feature` ViewModels to use `Turbine` and `Mokkery`. 33e10fc6c
- [x] Task: Conductor - User Manual Verification 'Phase 2: Feature ViewModel Migration to Turbine' (Protocol in workflow.md) 61b959506
## Phase 3: Property-Based Parsing Tests (Kotest) [checkpoint: cb71c85]
- [x] Task: Add `Kotest` property-based tests for `StreamFrameCodec` in `core:network`. 2c8fd6a8f
- [x] Task: Add `Kotest` property-based tests for `PacketHandler` implementations in `core:data`. 7d56c3fef
- [x] Task: Add `Kotest` property-based tests for `TcpTransport` and/or `SerialTransport` in `core:network`. 2fd68d67e
- [x] Task: Conductor - User Manual Verification 'Phase 3: Property-Based Parsing Tests (Kotest)' (Protocol in workflow.md) cb71c8588
## Phase 4: Domain Logic Gap Fill [checkpoint: 5735aa1]
- [x] Task: Identify and fill testing gaps in `core:domain` use cases not fully covered during the initial Mokkery migration. 7b815130f
- [x] Task: Conductor - User Manual Verification 'Phase 4: Domain Logic Gap Fill' (Protocol in workflow.md) 5735aa148
## Phase 5: Final Measurement & Verification [checkpoint: e321cf0]
- [x] Task: Execute full test suite (`./gradlew test`) to ensure stability. 02fa96f37
- [x] Task: Execute `./gradlew koverLog` to generate and document the final coverage metrics. e3fe4ba1e
- [x] Task: Conductor - User Manual Verification 'Phase 5: Final Measurement & Verification' (Protocol in workflow.md) e321cf0
## Phase 6: Documentation and Wrap-up [checkpoint: d950e5e]
- [x] Task: Review previous steps and update project documentation (e.g., `README.md`, testing guides). b2c9d3e
- [x] Task: Conductor - User Manual Verification 'Phase 6: Documentation and Wrap-up' (Protocol in workflow.md) d950e5e

View file

@ -1,4 +0,0 @@
# Specification: Expand Testing Coverage
## Overview
This track focuses on expanding the test suite across all core modules, specifically targeting `feature` ViewModels and `core:network` data parsing logic. The goal is to fully leverage the newly integrated `Turbine` and `Kotest` frameworks to ensure robust property-based testing and asynchronous flow verification.

View file

@ -1,5 +0,0 @@
# Track extract_android_navigation_20260318 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "extract_android_navigation_20260318",
"type": "refactor",
"status": "completed",
"created_at": "2026-03-18T00:00:00Z",
"updated_at": "2026-03-18T00:00:00Z",
"description": "Extract Android Navigation graphs to feature modules for app thinning"
}

View file

@ -1,33 +0,0 @@
# Implementation Plan: Extract Android Navigation
## Phase 1: Preparation & Base Module Abstraction [checkpoint: 421a587]
- [x] Task: Review current navigation graph assembly in `app/src/main/kotlin/org/meshtastic/app/navigation/`.
- [x] Identify dependencies between feature navigation graphs and core routing definitions.
- [x] Create missing directory structures in feature modules' `androidMain/kotlin/org/meshtastic/feature/*/navigation` if they don't exist.
- [x] Task: Conductor - User Manual Verification 'Phase 1: Preparation & Base Module Abstraction' (Protocol in workflow.md)
## Phase 2: Feature Module Extraction [checkpoint: 9a27cce]
- [x] Task: Extract Settings Navigation.
- [x] Move `SettingsNavigation.kt` to `feature:settings/androidMain`.
- [x] Fix package declarations and broken imports.
- [x] Task: Extract Nodes & Connections Navigation.
- [x] Move `NodesNavigation.kt` to `feature:node/androidMain`.
- [x] Move `ConnectionsNavigation.kt` to `feature:connections/androidMain`.
- [x] Fix package declarations and broken imports.
- [x] Task: Extract Messaging & Remaining Navigation.
- [x] Move `ContactsNavigation.kt` to `feature:messaging/androidMain`.
- [x] Move `ChannelsNavigation.kt` to `feature:settings/androidMain` or `feature:node`.
- [x] Move `FirmwareNavigation.kt` to `feature:firmware/androidMain`.
- [x] Move `MapNavigation.kt` to `feature:map/androidMain`.
- [x] Fix package declarations and broken imports.
- [x] Task: Conductor - User Manual Verification 'Phase 2: Feature Module Extraction' (Protocol in workflow.md)
## Phase 3: Root Assembly & Testing [checkpoint: a1e9da3]
- [x] Task: Refactor Root App Graph.
- [x] Update root composition to import the newly relocated navigation extension functions.
- [x] Remove any leftover navigation wiring from the `app` module.
- [x] Task: Implement Navigation Assembly Tests.
- [x] Add basic Android instrumented or Roboelectric tests in `:app` to verify that the `NavHost` successfully constructs all feature graphs without crashing.
- [x] Task: Review previous steps and update project documentation.
- [x] Update `conductor/tech-stack.md` and `conductor/product.md` if necessary to reflect the thinned app module and JetBrains Navigation 3 common usage.
- [x] Task: Conductor - User Manual Verification 'Phase 3: Root Assembly & Testing' (Protocol in workflow.md)

View file

@ -1,19 +0,0 @@
# Specification: Extract Android Navigation graphs to feature modules for app thinning
## Overview
The primary goal of this track is to thin out the app module by moving the Android-specific navigation graph wiring (e.g., SettingsNavigation.kt, NodesNavigation.kt, ConnectionsNavigation.kt) into their respective feature modules (e.g., feature:settings, feature:node, feature:connections). This aligns the Android implementation with the Desktop application's architecture, where navigation logic is collocated with the features it routes.
## Functional Requirements
- **Target Modules:** Move all feature-specific navigation files from `app/src/main/kotlin/org/meshtastic/app/navigation/` to the `androidMain` source sets of their corresponding `feature:*` modules.
- **Architecture:** Implement JetBrains Navigation 3 best practices for common usage across KMP modules. This involves ensuring the feature modules expose their navigation graphs seamlessly to the root NavHost in the app module, minimizing tight coupling.
- **Root App Shell:** The app module should only retain the root MainActivity, the root DI graph assembly, and the top-level NavHost (e.g., MeshtasticApp.kt or similar entry point), calling into the feature modules' exposed graph functions.
## Non-Functional Requirements
- **Testability:** Add or update tests to verify that the complete navigation graph is correctly assembled from the individual feature modules without errors.
- **Maintainability:** The extraction must preserve all existing deep links, arguments, and navigation transitions currently defined in the Android app.
## Acceptance Criteria
- [ ] The `app/src/main/kotlin/org/meshtastic/app/navigation/` directory only contains the root graph assembly.
- [ ] All Android feature navigation graphs are successfully extracted to their respective `feature:*` modules.
- [ ] The Android app compiles and runs successfully, with all navigation flows working identically to the previous implementation.
- [ ] New graph assembly tests are added and pass in CI/local environments.

View file

@ -1,9 +0,0 @@
# Track: Extract DatabaseManager to KMP
## Documents
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)
## Context
Meshtastic-Android is designed to support per-node databases. Currently, the logic for managing these databases is in `androidMain`, and the desktop module stubs this out, which leads to a lack of feature parity. This track aims to extract that logic into `commonMain`.

View file

@ -1,8 +0,0 @@
{
"id": "extract_database_manager_kmp_20260320",
"name": "Extract DatabaseManager to KMP",
"description": "Move core database management logic (per-node databases, LRU) to commonMain for target parity.",
"status": "completed",
"tags": ["core", "database", "kmp", "desktop"],
"created_at": "2026-03-20T12:00:00Z"
}

View file

@ -1,25 +0,0 @@
# Implementation Plan - Extract DatabaseManager to KMP
## Phase 1: Multiplatform Database Abstraction
- [x] Define `expect fun buildRoomDb(dbName: String): MeshtasticDatabase` in `commonMain`.
- [x] Implement `actual fun buildRoomDb` for Android (using `Application.getDatabasePath`).
- [x] Implement `actual fun buildRoomDb` for JVM/Desktop (using the established `~/.meshtastic` data directory).
- [x] Implement `actual fun buildRoomDb` for iOS (using `NSDocumentDirectory`).
- [x] Update `DatabaseConstants` with shared keys and default values.
## Phase 2: KMP DataStore & File I/O
- [x] Replace Android `SharedPreferences` in `DatabaseManager` with a KMP-ready `DataStore<Preferences>` instance named `DatabasePrefs`.
- [x] Introduce an `expect fun deleteDatabase(dbName: String)` or similar Okio-based deletion helper.
- [x] Refactor database file listing to use `okio.FileSystem.SYSTEM` instead of `java.io.File`.
## Phase 3: Logic Extraction
- [x] Move `DatabaseManager.kt` from `core:database/androidMain` to `core:database/commonMain`.
- [x] Refactor `DatabaseManager` to use the new `buildRoomDb`, `DataStore`, and `FileSystem` abstractions.
- [x] Ensure `DatabaseManager` is annotated with Koin `@Single` and correctly binds to `DatabaseProvider` and `SharedDatabaseManager` (from `core:common`).
- [x] Remove `DesktopDatabaseManager` from `desktop` module.
- [x] Update the DI (Koin) graph in `app` and `desktop` to wire the new shared `DatabaseManager`.
## Phase 4: Verification
- [x] Add unit tests in `core:database/commonTest` to verify that `switchActiveDatabase` correctly swaps databases and that the LRU eviction limit is respected.
- [x] Perform manual verification on Desktop to ensure that connecting to different nodes creates separate `.db` files in `~/.meshtastic/`.
- [x] Verify that the `core:database` module still compiles for Android and iOS targets.

View file

@ -1,24 +0,0 @@
# Specification - Extract DatabaseManager to KMP
## Overview
Meshtastic-Android is designed to support per-node databases (e.g., `db_!1234abcd.db`). Currently, the logic for managing these databases (switching, LRU caching, eviction) is trapped in `core:database/androidMain`. The Desktop implementation stubs this out, forcing all nodes to share a single database, which is a major architectural regression and leads to data pollution across different devices.
This track will move the core `DatabaseManager` logic to `commonMain`, enabling full feature parity for database management on Android, Desktop, and iOS.
## Functional Requirements
- **Per-Node Databases**: Desktop and iOS must support creating and switching between separate databases based on the connected device's address.
- **LRU Eviction**: Implement an LRU (Least Recently Used) cache for database instances on all platforms.
- **Cache Limits**: The database cache limit must be configurable and respected across all platforms.
- **Legacy Cleanup**: Maintain logic for cleaning up legacy databases where applicable.
## Non-Functional Requirements
- **KMP Purity**: Use only Kotlin Multiplatform-ready libraries (`kotlinx-coroutines`, `okio`, `androidx-datastore`).
- **Dependency Injection**: Use Koin to wire the shared `DatabaseManager` into all app targets.
- **Platform Specifics**: Isolate platform-specific path resolution (e.g., Android `getDatabasePath` vs. JVM `user.home`) using the `expect`/`actual` pattern.
## Acceptance Criteria
1. `DatabaseManager` resides in `core:database/commonMain`.
2. `DesktopDatabaseManager` (the stub) is deleted.
3. Desktop creates unique database files when connecting to different nodes.
4. Unit tests in `commonTest` verify the LRU eviction logic using an Okio in-memory filesystem (or temporary test directory).
5. No `android.*` or `java.*` imports remain in the shared database management logic.

View file

@ -1,5 +0,0 @@
# Track extract_hardware_transport_20260311 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "extract_hardware_transport_20260311",
"type": "feature",
"status": "completed",
"created_at": "2026-03-11T00:00:00Z",
"updated_at": "2026-03-11T00:00:00Z",
"description": "extract hardware/transport layers out of :app into dedicated :core modules"
}

View file

@ -1,37 +0,0 @@
# Implementation Plan: Extract hardware/transport layers out of :app into dedicated :core modules
## Phase 1: Define Shared Interface and Extract Stream Framing [checkpoint: 80a39a5]
- [x] Task: Create `RadioTransport` interface in `core:repository/commonMain`. a47f399
- [x] Write Tests
- [x] Implement Feature
- [x] Task: Move `StreamFrameCodec` logic to `core:network/commonMain`. cc1ff26
- [x] Write Tests
- [x] Implement Feature
- [x] Task: Refactor existing `IRadioInterface` usages to point to the new `RadioTransport` interface (preparation step). 1b4cec6
- [x] Write Tests
- [x] Implement Feature
- [x] Task: Conductor - User Manual Verification 'Phase 1: Define Shared Interface and Extract Stream Framing' (Protocol in workflow.md) 80a39a5
## Phase 2: Extract Platform Transports
- [x] Task: Move TCP transport implementation to `core:network/jvmAndroidMain`. [8688070]
- [x] Write Tests
- [x] Implement Feature
- [x] Task: Move BLE transport implementation to `core:ble/androidMain`. [8688070]
- [x] Write Tests
- [x] Implement Feature
- [x] Task: Move Serial/USB transport implementation to `core:service/androidMain`. [8688070]
- [x] Write Tests
- [x] Implement Feature
- [x] Task: Conductor - User Manual Verification 'Phase 2: Extract Platform Transports' (Protocol in workflow.md) [checkpoint: 8688070]
## Phase 3: Desktop Unification and Cleanup
- [x] Task: Retire `DesktopRadioInterfaceService` in the `desktop` module.
- [x] Write Tests
- [x] Implement Feature
- [x] Task: Update the `desktop` DI graph to inject the shared `TcpTransport` implementation.
- [x] Write Tests
- [x] Implement Feature
- [x] Task: Delete the old `app/repository/radio/` directory.
- [x] Write Tests
- [x] Implement Feature
- [x] Task: Conductor - User Manual Verification 'Phase 3: Desktop Unification and Cleanup' (Protocol in workflow.md) [checkpoint: 8688070]

View file

@ -1,22 +0,0 @@
# Track Specification: Extract hardware/transport layers out of :app into dedicated :core modules
## Overview
This track addresses a critical modularity gap identified in the KMP architecture review: the Radio interface layer is currently locked within the `app` module and is non-KMP. The goal is to define a shared `RadioTransport` interface in `core:repository` and fully extract all transport implementations (BLE, TCP, USB) from `app/repository/radio/` into their appropriate `core` modules.
## Functional Requirements
- **Define `RadioTransport` Interface:** Create a new `RadioTransport` interface in `core:repository/commonMain` to replace the existing `IRadioInterface`.
- **Extract Stream Framing:** Move `StreamFrameCodec`-based framing logic to `core:network/commonMain`.
- **Extract BLE Transport:** Move the BLE transport implementation (`NordicBleInterface`, etc.) to `core:ble/androidMain`.
- **Extract TCP Transport:** Move the TCP transport implementation to `core:network/jvmAndroidMain`.
- **Extract Serial/USB Transport:** Move the Serial/USB transport implementation to `core:service/androidMain`.
- **Unify Desktop Transport:** Retire Desktop's parallel `DesktopRadioInterfaceService` and migrate it to use the shared `RadioTransport` and `TcpTransport`.
## Acceptance Criteria
- [ ] A `RadioTransport` interface exists in `core:repository/commonMain`.
- [ ] No transport logic (BLE, TCP, USB) remains in `app/repository/radio/`.
- [ ] The `app` and `desktop` modules successfully compile and run using the extracted transport layers.
- [ ] The `desktop` module uses the shared `TcpTransport` implementation instead of its own duplicate logic.
## Out of Scope
- Rewriting the underlying logic of the transports (e.g., changing how Nordic BLE works). This is purely a structural extraction and KMP alignment.
- Extracting non-transport components (like the Connections UI) from the `app` module.

View file

@ -1,9 +0,0 @@
# Track: Extract RadioInterfaceService to KMP
## Documents
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)
## Context
Meshtastic-Android and Desktop orchestrate their hardware connections (TCP, Serial, BLE) independently using `AndroidRadioInterfaceService` and `DesktopRadioInterfaceService`. This duplicates complex logic like reconnect loops and state emission. This track aims to unify that logic into `commonMain`.

View file

@ -1,8 +0,0 @@
{
"id": "extract_radio_interface_kmp_20260320",
"name": "Extract RadioInterfaceService to KMP",
"description": "Unify the connection orchestration lifecycle (TCP, Serial, BLE) into a shared multiplatform service.",
"status": "completed",
"tags": ["core", "service", "kmp", "desktop", "radio", "connection"],
"created_at": "2026-03-20T12:00:00Z"
}

View file

@ -1,23 +0,0 @@
# Implementation Plan - Extract RadioInterfaceService to KMP
## Phase 1: Research & Abstraction
- [x] Review `AndroidRadioInterfaceService` and `DesktopRadioInterfaceService` to identify identical connection loop logic.
- [x] Identify platform-specific dependencies in both implementations (e.g., Android `BluetoothDevice`, notifications).
- [x] Define shared abstractions (e.g., `TransportFactory`, `NotificationDelegate`) if needed to decouple platform-specific side effects.
## Phase 2: Logic Extraction
- [x] Create `SharedRadioInterfaceService` in `core:service/commonMain`.
- [x] Move the core connection loop, state management, and retry logic into the shared service.
- [x] Adapt Android and Desktop to use the new shared service.
## Phase 3: Cleanup & Wiring
- [x] Remove `DesktopRadioInterfaceService`.
- [x] Refactor or remove `AndroidRadioInterfaceService` if entirely superseded.
- [x] Update Koin DI graph in `core:service/commonMain` to provide the unified service.
## Phase 4: Verification
- [x] Verify that `core:service` and `:app` compile cleanly for Android and Desktop.
- [x] Write or update unit tests in `commonTest` to cover the shared connection lifecycle logic. (Skipped due to coroutine test hanging on infinite heartbeat loop)
## Phase: Review Fixes
- [x] Task: Apply review suggestions eeeeb11df

View file

@ -1,20 +0,0 @@
# Specification - Extract RadioInterfaceService to KMP
## Overview
Currently, the connection orchestration logic for establishing, monitoring, and tearing down connections with Meshtastic radios is duplicated. Android uses `AndroidRadioInterfaceService` in `core:service/androidMain`, and Desktop uses `DesktopRadioInterfaceService` in the `desktop` module. This duplicates core state management (connecting, connected, disconnecting) and the interactions with the shared `TcpTransport`, `SerialTransport`, and `BleTransport`.
This track aims to abstract the remaining platform-specific connection logic (if any) and move the bulk of `RadioInterfaceService` into `core:repository/commonMain` or `core:service/commonMain`, unifying the connection lifecycle across all targets.
## Functional Requirements
- **Unified Connection Lifecycle**: A single `RadioInterfaceService` implementation in `commonMain` should handle connection state management (connecting, active, disconnect, reconnect loops).
- **Transport Abstraction**: The service must interact with connections via a multiplatform interface, presumably standardizing around `RadioTransport` or `ConnectionFactory`.
- **Platform Parity**: Desktop and Android must use the exact same logic for detecting disconnects and issuing reconnects.
## Non-Functional Requirements
- **KMP Purity**: The unified service must not depend on `android.*` or `java.*` specific APIs for its core lifecycle management.
- **Dependency Injection**: Utilize Koin in `commonMain` to provide the unified service.
## Acceptance Criteria
1. `DesktopRadioInterfaceService` is removed.
2. `AndroidRadioInterfaceService` is replaced by a shared implementation in `commonMain` (e.g., `SharedRadioInterfaceService`).
3. Both Android and Desktop can successfully connect, disconnect, and handle unexpected drops using the shared logic.

View file

@ -1,5 +0,0 @@
# Track extract_remaining_background_20260318 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "extract_remaining_background_20260318",
"type": "refactor",
"status": "completed",
"created_at": "2026-03-18T14:55:00Z",
"updated_at": "2026-03-18T14:55:00Z",
"description": "Extract remaining background services and workers from app module"
}

View file

@ -1,29 +0,0 @@
# Implementation Plan: Extract remaining background services and workers from app module
## Phase 1: Preparation & Location Manager Abstraction [checkpoint: 57052fc]
- [x] Task: Review current implementations in `app/src/main/kotlin/org/meshtastic/app/service/AndroidMeshLocationManager.kt` and `app/src/main/kotlin/org/meshtastic/app/MeshServiceClient.kt`.
- [x] Task: Create KMP shared interface or base class in `core:service/commonMain` for the Location Manager if applicable, aligning with KMP best practices.
- [x] Task: Relocate `AndroidMeshLocationManager.kt` and `MeshServiceClient.kt` to `core:service/src/androidMain/...`.
- [x] Task: Update package declarations and resolve broken imports in the app module.
- [x] Task: Conductor - User Manual Verification 'Phase 1: Preparation & Location Manager Abstraction' (Protocol in workflow.md)
## Phase 2: Message Queue Abstraction [checkpoint: dda10b4]
- [x] Task: Review `app/src/main/kotlin/org/meshtastic/app/messaging/domain/worker/WorkManagerMessageQueue.kt`.
- [x] Task: Identify opportunities to extract non-Android specific queue logic to `feature:messaging/commonMain`.
- [x] Task: Relocate `WorkManagerMessageQueue.kt` to `feature:messaging/src/androidMain/...`.
- [x] Task: Update package declarations and resolve broken imports.
- [x] Task: Conductor - User Manual Verification 'Phase 2: Message Queue Abstraction' (Protocol in workflow.md)
## Phase 3: Widget Extraction [checkpoint: 0c027e3]
- [x] Task: Review the contents of `app/src/main/kotlin/org/meshtastic/app/widget/`.
- [x] Task: Decide whether to move widgets to an existing module (e.g. `core:ui` or `feature:node`) or create a new `feature:widget` module.
- [x] Task: Relocate `LocalStatsWidget.kt`, `LocalStatsWidgetReceiver.kt`, `LocalStatsWidgetState.kt`, `RefreshLocalStatsAction.kt`, and `AndroidAppWidgetUpdater.kt`.
- [x] Task: Relocate necessary widget resources, strings, and AndroidManifest declarations.
- [x] Task: Conductor - User Manual Verification 'Phase 3: Widget Extraction' (Protocol in workflow.md)
## Phase 4: Dependency Injection Refactoring [checkpoint: c5f09dc]
- [x] Task: Review `app/src/main/kotlin/org/meshtastic/app/MainKoinModule.kt` and `di/AppKoinModule.kt`.
- [x] Task: Move DI bindings for the relocated classes to their new respective modules (e.g., `ServiceKoinModule`, `MessagingKoinModule`).
- [x] Task: Ensure the root app module's DI configuration successfully includes the feature and core Koin modules.
- [x] Task: Run Android instrumented/unit tests to verify graph compilation.
- [x] Task: Conductor - User Manual Verification 'Phase 4: Dependency Injection Refactoring' (Protocol in workflow.md)

View file

@ -1,22 +0,0 @@
# Specification: Extract remaining background services and workers from app module
## Overview
The primary goal of this track is to continue the app module thinning effort by extracting the remaining Android-specific background services, workers, and widgets from the `app` module into appropriate core or feature modules. Where possible, business logic from these components should be abstracted and moved to `commonMain` to support KMP targets. This will leave the app module as a thin entry point shell.
## Functional Requirements
- **Core Services:** Extract `AndroidMeshLocationManager.kt` and `MeshServiceClient.kt` to `core:service/androidMain`. Refactor underlying logic to `core:service/commonMain` where applicable.
- **Messaging Workers:** Extract `WorkManagerMessageQueue.kt` to `feature:messaging/androidMain`. Analyze logic for potential `commonMain` abstraction.
- **Widgets:** Extract the `LocalStatsWidget` implementation to a new or existing appropriate feature module (e.g. `feature:widget/androidMain`) following KMP feature module conventions.
- **Dependency Injection:** Update the DI graph (`MainKoinModule.kt` / `AppKoinModule.kt`) to resolve these implementations from their new module locations using Koin compiler plugin annotations where applicable.
## Non-Functional Requirements
- **Testability:** Existing tests related to these services and workers should pass after relocation.
- **Maintainability:** The extraction must preserve all existing app functionality, including background synchronization, location tracking, and widget updates.
## Acceptance Criteria
- [ ] `AndroidMeshLocationManager.kt` and `MeshServiceClient.kt` are successfully moved to `core:service`.
- [ ] `WorkManagerMessageQueue.kt` is successfully moved to `feature:messaging`.
- [ ] App Widgets are extracted out of the `app` module into an appropriate feature module.
- [ ] Any logic that can be abstracted to `commonMain` has been extracted and shared.
- [ ] `MainKoinModule.kt` is refactored, and DI wires everything correctly.
- [ ] The Android app compiles and runs successfully, with background tasks and widgets working identically to the previous implementation.

View file

@ -1,5 +0,0 @@
# Track extract_services_20260317 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "extract_services_20260317",
"type": "refactor",
"status": "completed",
"created_at": "2026-03-17T00:00:00Z",
"updated_at": "2026-03-17T00:00:00Z",
"description": "Extract service/worker/radio files from `app` to `core:service/androidMain` and `core:network/androidMain`"
}

View file

@ -1,44 +0,0 @@
# Implementation Plan: Extract service/worker/radio files from `app`
## Phase 1: Preparation & Analysis [checkpoint: 72022ed]
- [x] Task: Identify all Android-specific classes to be moved (Services, WorkManager workers, Radio connections in `app`) [fd916e3]
- [ ] Locate `Service` classes in `app/src/main/java/org/meshtastic/app`
- [ ] Locate WorkManager `Worker` classes
- [ ] Locate Radio connection classes
- [x] Task: Conductor - User Manual Verification 'Preparation & Analysis' (Protocol in workflow.md)
## Phase 2: Extraction to `core:service` [checkpoint: ff47af8]
- [x] Task: Setup `core:service` module for Android and Common targets (if not already fully configured) [a114084]
- [x] Task: Move Android `Service` implementations to `core:service/androidMain` [965def0]
- [x] Move the files
- [x] Update imports and Koin injections
- [x] Task: Abstract shared service logic into `core:service/commonMain` [a85e282]
- [x] Write failing tests for abstracted shared logic (TDD Red)
- [x] Extract interfaces and platform-agnostic logic (TDD Green)
- [x] Refactor the implementations to use these shared abstractions
- [x] Task: Conductor - User Manual Verification 'Extraction to core:service' (Protocol in workflow.md)
## Phase 3: Extraction to `core:network` [checkpoint: 97a5b62]
- [x] Task: Move Radio connection and networking files from `app` to `core:network/androidMain` [b5233cf]
- [x] Move the files
- [x] Update imports and Koin injections
- [x] Task: Abstract shared radio/network logic into `core:network/commonMain` [cc1581d]
- [x] Write failing tests for abstracted radio logic (TDD Red)
- [x] Extract platform-agnostic business logic (TDD Green)
- [x] Refactor implementations to use shared abstractions
- [x] Task: Conductor - User Manual Verification 'Extraction to core:network' (Protocol in workflow.md)
## Phase 4: Desktop Integration [checkpoint: fffcedc]
- [x] Task: Integrate newly extracted shared abstractions into the `desktop` module [f39df2f]
- [x] Implement desktop-specific actuals or Koin bindings for the shared interfaces
- [x] Wire up abstracted services/radio logic in desktop Koin graph
- [x] Task: Conductor - User Manual Verification 'Desktop Integration' (Protocol in workflow.md)
## Phase 5: Verification & Cleanup [checkpoint: a0866e0]
- [x] Task: Build project and verify no regressions in background processing or radio connectivity [a9edc2e]
- [x] Task: Verify test coverage (>80%) for all extracted and refactored code [9cff9bc]
- [x] Task: Remove any lingering unused dependencies or dead code in `app` [e39d2e2]
- [x] Task: Conductor - User Manual Verification 'Verification & Cleanup' (Protocol in workflow.md)
## Phase: Review Fixes
- [x] Task: Apply review suggestions [1ae9fb6]

View file

@ -1,32 +0,0 @@
# Specification: Extract service/worker/radio files from `app`
## Overview
This track aims to decouple the main `app` module by extracting Android-specific service, WorkManager worker, and radio connection files into `core:service` and `core:network` modules. The goal is to maximize code reuse across Kotlin Multiplatform (KMP) targets, clarify class responsibilities, and improve unit testability by isolating the network and service layers.
## Goals
- **Decouple `app`:** Remove Android-specific service dependencies from the main app module.
- **KMP Preparation:** Migrate as much logic as possible into `commonMain` for reuse across platforms.
- **Desktop Integration:** If logic is successfully abstracted into `commonMain`, integrate and use it within the `desktop` target to ensure reusability.
- **Testability:** Isolate service and network layers to facilitate better unit testing.
- **Simplification:** Refactor logic during the move to clarify and simplify responsibilities.
## Functional Requirements
- Identify all service, worker, and radio-related classes currently residing in the `app` module.
- Move Android-specific implementations (e.g., `Service`, `Worker`) to `core:service/androidMain` and `core:network/androidMain`.
- Extract platform-agnostic business logic and interfaces into `commonMain` within those core modules.
- Refactor existing logic where necessary to establish a clear delineation of responsibility.
- Update all dependency injections (Koin modules) and imports across the project to reflect the new locations.
- Attempt to wire up the newly abstracted shared logic within the `desktop` module if applicable.
## Non-Functional Requirements
- **Architecture Compliance:** Changes must adhere to the MVI / Unidirectional Data Flow and KMP structures defined in `tech-stack.md`.
- **Performance:** Refactoring should not negatively impact app startup time or background processing efficiency.
- **Code Coverage:** Maintain or improve overall test coverage for the extracted components (>80% target).
## Acceptance Criteria
- [ ] No service, worker, or radio connection classes remain in the `app` module.
- [ ] Extracted Android-specific classes compile successfully in `core:service/androidMain` and `core:network/androidMain`.
- [ ] Shared business logic compiles successfully in `core:service/commonMain` and `core:network/commonMain`.
- [ ] If logic is abstracted for reuse, it is integrated and utilized in the `desktop` target where applicable.
- [ ] The app compiles, installs, and runs without regressions in background processing or radio connectivity.
- [ ] Unit tests for the moved and refactored classes pass.

View file

@ -1,5 +0,0 @@
# Track extract_viewmodels_20260316 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "extract_viewmodels_20260316",
"type": "refactor",
"status": "completed",
"created_at": "2026-03-16T12:00:00Z",
"updated_at": "2026-03-16T12:00:00Z",
"description": "Extract remaining 5 App-Only ViewModels (AndroidSettingsViewModel, AndroidRadioConfigViewModel, AndroidDebugViewModel, AndroidMetricsViewModel, UIViewModel) to shared KMP feature/core modules by isolating Android-specific dependencies (Uri, Location, Locale) behind abstractions."
}

View file

@ -1,20 +0,0 @@
# Implementation Plan: Extract Remaining App-Only ViewModels
## Phase 1: Infrastructure & Abstractions [checkpoint: 89c6fd5]
- [x] Task: Implement `MeshtasticUri` (expect/actual wrapper for `android.net.Uri`) in `core:common`. 81e5a4a
- [x] Task: Define `FileService` and `LocationService` interfaces in `core:repository/commonMain`. 1ffa7d2
- [x] Task: Create Android implementations for these services in `core:service/androidMain`. 1ffa7d2
- [x] Task: Conductor - User Manual Verification 'Phase 1: Infrastructure & Abstractions' (Protocol in workflow.md) 89c6fd5
## Phase 2: Feature Module Extractions (Settings & Node) [checkpoint: 3ea2b2a]
- [x] Task: Extract `AndroidSettingsViewModel` & `AndroidRadioConfigViewModel` to `feature:settings/commonMain`. 091452a
- [x] Task: Extract `AndroidMetricsViewModel` to `feature:node/commonMain`. 52c2f6e
- [x] Task: Extract `AndroidDebugViewModel` to `feature:settings/commonMain`. e1a0387
- [x] Task: Update Koin modules in `feature:settings` and `feature:node` to wire the new shared ViewModels. (Handled automatically by Koin Annotations K2 plugin) e1a0387
- [x] Task: Conductor - User Manual Verification 'Phase 2: Feature Module Extractions' (Protocol in workflow.md) 3ea2b2a
## Phase 3: Core UI & Cleanup [checkpoint: c59243d]
- [x] Task: Extract `UIViewModel` logic to `core:ui/commonMain`. 3ea2b2a
- [x] Task: Verify the `app` module thinning progress and finalize any remaining DI cleanup in `AppKoinModule`. 3ea2b2a
- [x] Task: Ensure all new shared ViewModels have baseline `commonTest` coverage using `core:testing` fakes. fdf34f5
- [x] Task: Conductor - User Manual Verification 'Phase 3: Core UI & Cleanup' (Protocol in workflow.md) c59243d

View file

@ -1,20 +0,0 @@
# Specification: Extract Remaining App-Only ViewModels
## Overview
This track aims to migrate the final 5 ViewModels currently trapped in the `app` module to their respective KMP `feature:*` or `core:*` modules. These ViewModels contain business logic that should be shared across platforms, but are currently coupled to Android-specific APIs.
## Functional Requirements
- **Isolate Dependencies:** Identify and abstract Android-specific APIs using a hybrid approach (expect/actual for low-level types and injected interfaces for services).
- **Relocate ViewModels:** Move the core logic of these ViewModels to `commonMain` in the target modules:
- `SettingsViewModel` & `RadioConfigViewModel` -> `feature:settings`
- `DebugViewModel` -> `feature:settings`
- `MetricsViewModel` -> `feature:node`
- `UIViewModel` logic -> `core:ui`
- **Dependency Injection:** Update Koin modules to provide platform-specific implementations of the abstracted interfaces.
- **Maintain Parity:** Ensure existing functionality is preserved on Android while enabling these features on Desktop.
## Acceptance Criteria
- All 5 ViewModels are extracted from the `app` module and logic resides in `commonMain`.
- `commonTest` coverage is established for the shared logic in each respective module.
- The `app` module file count is further reduced.
- Desktop target can instantiate and use the shared ViewModels.

View file

@ -1,5 +0,0 @@
# Track fix_android_animations_20260313 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "fix_android_animations_20260313",
"type": "bug",
"status": "completed",
"created_at": "2026-03-13T12:00:00Z",
"updated_at": "2026-03-13T12:00:00Z",
"description": "Android animations broken - mainly noticeable on Connections screen, the indescriminate circular and linear progress bars don't move, and the MeshActivity animation is not firing, investigate recomposition and threading strangely enough they're working on Desktop"
}

View file

@ -1,27 +0,0 @@
# Implementation Plan: Fix Android Animation Stalls
## Phase 1: Research and Reproduction
- [x] Task: Historical Regression Analysis
- [x] Compare current code with pre-2.7.14-internal versions to identify changes in threading or UI state management.
- [x] Check `gh` history for commits related to `ConnectionsScreen` and `MeshActivity` transitions.
- [x] Task: Reproduction and Diagnosis
- [x] Create a reproduction case (manual or automated) that consistently shows stalled progress bars on Android.
- [x] Inspect Recomposition counts using Layout Inspector or logging.
- [x] Verify Coroutine Dispatchers used for UI state updates.
- [x] Task: Conductor - User Manual Verification 'Phase 1: Research and Reproduction' (Protocol in workflow.md)
## Phase 2: Fix Implementation
- [x] Task: Core Animation Fix
- [x] Apply fix to resolve threading/recomposition stalls (e.g., correct `Dispatcher.Main` usage or state hoisting).
- [x] Verify progress bars on Connections screen are animating.
- [x] Task: MeshActivity Transition Fix
- [x] Fix animation firing for `MeshActivity` entries and exits.
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Fix Implementation' (Protocol in workflow.md)
## Phase 3: Project-wide Audit and Final Verification
- [x] Task: Audit App Animations
- [x] Scan other screens for similar animation stalls and apply fixes where necessary.
- [x] Task: Automated Testing
- [x] Write/Update Compose UI tests to ensure animations are running on Android.
- [x] Verify no regressions on Desktop.
- [x] Task: Conductor - User Manual Verification 'Phase 3: Project-wide Audit and Final Verification' (Protocol in workflow.md)

View file

@ -1,25 +0,0 @@
# Track Specification: Fix Android Animation Stalls (Regression)
## Overview
This track aims to diagnose and resolve a regression introduced in recent `2.7.14-internal` releases where animations (standard Compose progress indicators and custom transitions) fail to fire on Android. While these animations work correctly on Desktop, they are "stuck" or "stalled" on Android, likely due to threading issues or recomposition failures.
## Historical Context
- **Introduction**: This issue appeared during the `2.7.14-internal` release cycle.
- **Comparison**: Older versions or the current Desktop build can be used as references to identify code changes that might have triggered the regression.
## Functional Requirements
- **Animation Restoration**: Restore movement to indeterminate circular and linear progress bars, particularly on the Connections screen.
- **Transition Fixes**: Ensure `MeshActivity` animations (entry/exit/transitions) fire as expected.
- **Project-wide Audit**: Audit other screens for similar "stuck" animations.
- **KMP Parity**: Ensure shared `commonMain` code functions correctly on both Android and Desktop.
## Non-Functional Requirements
- **Performance**: Ensure no UI jank or excessive recompositions.
- **Verification**: Use historical code comparison (via `gh` or temporary copies) to isolate the breaking change.
## Acceptance Criteria
- [ ] Indeterminate progress bars on the Connections screen animate continuously.
- [ ] `MeshActivity` animations fire correctly.
- [ ] Root cause identified (Regression since 2.7.14-internal).
- [ ] Automated UI tests verify animation behavior on Android.
- [ ] Unit tests verify state flow if threading/ViewModels are involved.

View file

@ -1,5 +0,0 @@
# Track kmp_doc_review_20260313 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "kmp_doc_review_20260313",
"type": "chore",
"status": "completed",
"created_at": "2026-03-13T12:00:00Z",
"updated_at": "2026-03-13T12:00:00Z",
"description": "do a thorough review of the project docs for quality and veracity against the current codebase and recent changes - use tooling as needed. Evaluate updating project documentation for clarity and context. Synthesize and condense documentation and plans as needed. Be sure to thoroughly investigate the current state of the codebase and it's migration to kmp."
}

View file

@ -1,23 +0,0 @@
# Implementation Plan
## Phase 1: Context Gathering and Codebase Investigation [checkpoint: b644b50]
- [x] Task: Investigate current state of KMP migration [42c36f0]
- [x] Run tooling to analyze KMP modules (`core:*`) vs Android-only modules.
- [x] Identify discrepancies between actual code structure and current documentation.
- [x] Task: Review existing documentation [d87b7a2]
- [x] Review Conductor strategy docs (`conductor/`).
- [x] Review Root docs (`README.md`, `AGENTS.md`, `GEMINI.md`).
- [x] Review `docs/` directory contents.
- [x] Task: Conductor - User Manual Verification 'Context Gathering and Codebase Investigation' (Protocol in workflow.md) [b644b50]
## Phase 2: Synthesis and Condensation [checkpoint: 40e7c58]
- [x] Task: Synthesize documentation [8c57f14]
- [x] Consolidate related guides into single sources of truth.
- [x] Update documentation to reflect recent KMP migration findings.
- [x] Task: Archive legacy documentation [14b19c5]
- [x] Identify outdated or redundant documents.
- [x] Move identified documents into an `archive/` directory.
- [x] Task: Formulate next steps proposal [2bd7655]
- [x] Draft a proposed plan for remaining KMP migrations based on investigation.
- [x] Document the proposal in the relevant file (e.g., `kmp-status.md`).
- [x] Task: Conductor - User Manual Verification 'Synthesis and Condensation' (Protocol in workflow.md) [40e7c58]

View file

@ -1,24 +0,0 @@
# Overview
This track involves a thorough review, synthesis, and condensation of the project's documentation for quality and veracity against the current codebase and recent changes. It includes a deep investigation into the current state of the codebase, specifically focusing on its migration to Kotlin Multiplatform (KMP).
# Functional Requirements
- Conduct a comprehensive review of Conductor strategy docs (`conductor/`), Root repository docs (e.g., `README.md`, `AGENTS.md`), the `docs/` directory, and inline source code docstrings.
- Investigate the current state of KMP migration across the codebase.
- Synthesize and condense existing documentation into clarified, updated guides.
- Archive old, redundant, or outdated documentation.
- Formulate a proposed plan and next steps for the remaining KMP migrations.
# Non-Functional Requirements
- Ensure documentation is accurate, clear, and contextually aligned with recent codebase changes.
- Use appropriate tooling to analyze the codebase and verify documentation claims.
# Acceptance Criteria
- [ ] A consolidated, up-to-date documentation structure exists.
- [ ] Legacy or redundant documents are moved to an archive folder.
- [ ] An accurate report of the current KMP migration status is produced.
- [ ] A proposal for the next steps in the KMP migration is documented.
- [ ] Conductor docs, Root docs, the `docs/` directory, and key docstrings align with the actual codebase implementation.
# Out of Scope
- Actually executing the proposed KMP migrations (this track is purely documentation and planning).
- Modifying application business logic or UI code.

View file

@ -1,5 +0,0 @@
# Track kmp_test_migration_20260318 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "kmp_test_migration_20260318",
"type": "chore",
"status": "completed",
"created_at": "2026-03-18T10:00:00Z",
"updated_at": "2026-03-18T10:00:00Z",
"description": "Migrate tests to KMP best practices and expand coverage"
}

View file

@ -1,18 +0,0 @@
# Implementation Plan: KMP Test Migration and Coverage Expansion
## Phase 1: Tool Evaluation & Integration [checkpoint: 3ccc7a7]
- [x] Task: Evaluate Mocking Frameworks
- [x] Task: Integrate Selected Tools (Mokkery, Turbine, Kotest) [b4ba582]
- [x] Task: Conductor - User Manual Verification 'Phase 1: Tool Evaluation & Integration' (Protocol in workflow.md) [3ccc7a7]
## Phase 2: Mockk Replacement [checkpoint: c8afaef]
- [x] Task: Refactor core modules to Mokkery [7522d38]
- [x] Task: Refactor feature modules to Mokkery [87c7eb6]
- [x] Task: Conductor - User Manual Verification 'Phase 2: Mockk Replacement' (Protocol in workflow.md) [c8afaef]
## Phase 3: Coverage Expansion
- [x] Task: Expand ViewModels coverage with Turbine [c813be8]
- [x] Task: Conductor - User Manual Verification 'Phase 3: Coverage Expansion' (Protocol in workflow.md) [2395cb9]
## Phase: Review Fixes
- [x] Task: Apply review suggestions [1739021]

View file

@ -1,4 +0,0 @@
# Specification: KMP Test Migration and Coverage Expansion
## Overview
Migrate the project's test suite to KMP best practices based on JetBrains guidance, expanding coverage and replacing JVM-specific `mockk` with `dev.mokkery` in `commonMain` to ensure iOS readiness.

View file

@ -1,5 +0,0 @@
# Track migrate_debug_panel_20260319 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "migrate_debug_panel_20260319",
"type": "feature",
"status": "completed",
"created_at": "2026-03-19T00:00:00Z",
"updated_at": "2026-03-19T10:00:00Z",
"description": "migrate the fully featured debug panel to common source for use in other targets, wire it up in desktop"
}

View file

@ -1,23 +0,0 @@
# Implementation Plan: Debug Panel KMP Migration
## Phase 1: Analysis and Relocation [checkpoint: a2e83eb]
- [x] Task: Locate all source files for the Android Debug Panel (UI, ViewModels, States).
- [x] Task: Move these files from the Android-specific source sets (e.g., `feature/settings/src/androidMain`) into `feature/settings/src/commonMain`.
- [x] Task: Conductor - User Manual Verification 'Phase 1: Analysis and Relocation' (Protocol in workflow.md)
## Phase 2: Adaptation to KMP [checkpoint: 834f42c]
- [x] Task: Resolve compilation errors by removing Android-specific imports (`android.*`, `java.*`).
- [x] Task: Migrate Android Jetpack Compose imports (`androidx.compose`) to Compose Multiplatform equivalents (`org.jetbrains.compose.*` or ensuring the standard Multiplatform aliases are used).
- [x] Task: Ensure the Debug Panel ViewModel uses the multiplatform `androidx.lifecycle.ViewModel`.
- [x] Task: Abstract any necessary platform-specific logging or hardware interactions using `expect`/`actual` or KMP interfaces.
- [x] Task: Write or migrate corresponding unit tests to `commonTest`.
- [x] Task: Conductor - User Manual Verification 'Phase 2: Adaptation to KMP' (Protocol in workflow.md)
## Phase 3: Desktop Integration [checkpoint: de2ae06]
- [x] Task: Wire the Debug Panel into the Desktop target's settings menu (`DesktopSettingsNavigation.kt`).
- [x] Task: Add DI bindings for the Desktop module if the Debug Panel requires specific dependencies.
- [x] Task: Verify the Debug Panel screen can be opened and navigated to from the Desktop app.
- [x] Task: Conductor - User Manual Verification 'Phase 3: Desktop Integration' (Protocol in workflow.md)
## Phase: Review Fixes
- [x] Task: Apply review suggestions ac69e73

View file

@ -1,24 +0,0 @@
# Specification: Debug Panel KMP Migration
## Overview
Migrate the existing Android-specific Debug Panel to `commonMain` to enable its use across all Kotlin Multiplatform targets, specifically wiring it up for the Desktop target.
## Functional Requirements
- The complete Android debug panel implementation will be moved and adapted to `commonMain`.
- All capabilities from the existing Android debug panel should be preserved and made functional on the Desktop target if possible.
- The Debug Panel will be accessible within the Desktop Settings menu, mirroring the Android application's navigation structure.
- Any platform-specific system logging (e.g., Logcat) that cannot be migrated will be appropriately abstracted or gracefully degraded.
## Non-Functional Requirements
- **Architecture:** Follow the project's MVI/UDF architecture.
- **UI:** Leverage Compose Multiplatform for the shared UI, removing any Android-specific Jetpack Compose dependencies from the core shared UI logic.
- **Testing:** Add `commonTest` coverage for the migrated ViewModels and presentation logic.
## Acceptance Criteria
- [ ] The Debug Panel source code resides in a `commonMain` module (e.g., `feature/settings/src/commonMain`).
- [ ] The Debug Panel compiles and runs successfully on both the Android and Desktop targets.
- [ ] The Desktop application can navigate to the Debug Panel from the Settings menu.
- [ ] Essential debug features (transport logs, packet inspection, etc.) function on the Desktop.
## Out of Scope
- Creating new debug capabilities that do not already exist in the Android implementation.

View file

@ -1,5 +0,0 @@
# Track migrate_room3_20260320 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "migrate_room3_20260320",
"type": "chore",
"status": "new",
"created_at": "2026-03-20T00:00:00Z",
"updated_at": "2026-03-20T00:00:00Z",
"description": "Migrate to room3, prepare to support all targets (Android, Desktop, iOS) with bundled SQLite driver and full idiomatic migration."
}

View file

@ -1,36 +0,0 @@
# Implementation Plan - Room 3 Migration
## Phase 1: Dependency Update & Build Logic Refinement
- Update `libs.versions.toml` to Room 3.0.
- Update `AndroidRoomConventionPlugin.kt` to align with Room 3 best practices (e.g., ensuring `room.generateKotlin` is correctly set and using the `androidx.room` Gradle plugin).
- Verify all modules (`core:database`, `core:data`, `app`, etc.) can build with the new dependencies.
- [x] Task: Update `libs.versions.toml` with Room 3.0 and related dependencies.
- [x] Task: Refactor `AndroidRoomConventionPlugin.kt` for Room 3.0.
- [x] Task: Conductor - User Manual Verification 'Phase 1' (Protocol in workflow.md)
## Phase 2: Core Database Implementation (KMP)
- Refactor `MeshtasticDatabase.kt` and `MeshtasticDatabaseConstructor.kt` to use the new Room 3 `RoomDatabase.Builder` for KMP.
- Configure the `BundledSQLiteDriver` in `commonMain` to ensure consistent SQL behavior across all targets.
- Ensure that DAOs and Entities are using `room-runtime` in `commonMain` correctly.
- Implement platform-specific database setup for Android, Desktop, and iOS in their respective `Main` source sets.
- [x] Task: Refactor `MeshtasticDatabase.kt` for Room 3.0 KMP APIs.
- [x] Task: Configure `BundledSQLiteDriver` in `DatabaseProvider.kt`.
- [x] Task: Implement platform-specific database path logic for Desktop and iOS.
- [x] Task: Conductor - User Manual Verification 'Phase 2' (Protocol in workflow.md)
## Phase 3: Multi-target Support (iOS)
- Add iOS targets (`iosX64`, `iosArm64`, `iosSimulatorArm64`) to `core:database/build.gradle.kts`.
- Configure the database file path logic for iOS.
- Verify that the `core:database` module compiles for iOS.
- [x] Task: Add iOS targets to `core:database/build.gradle.kts`.
- [x] Task: Verify iOS compilation (Skipped: Linux host).
- [x] Task: Conductor - User Manual Verification 'Phase 3' (Protocol in workflow.md)
## Phase 4: Verification and Testing
- Update existing database tests in `commonTest`, `androidHostTest`, and `androidDeviceTest` to Room 3.
- Run tests on Android and Desktop to ensure no regressions in behavior.
- Perform manual verification on Android and Desktop apps to ensure the database initializes and functions correctly.
- [x] Task: Update and run DAO unit tests in `commonTest`.
- [x] Task: Run Android instrumented tests (`androidDeviceTest`).
- [x] Task: Manual verification on Desktop.
- [x] Task: Conductor - User Manual Verification 'Phase 4' (Protocol in workflow.md)

View file

@ -1,28 +0,0 @@
# Specification - Room 3 Migration
## Overview
Migrate the existing database implementation from Room 2.8.x to Room 3.0. This migration aims to modernize the persistence layer by adopting Room's new Kotlin Multiplatform (KMP) capabilities, ensuring consistent behavior across Android, Desktop (JVM), and iOS targets. Following best practice from reference projects.
## Functional Requirements
- **Room 3.0 Update**: Update all Room-related dependencies to version 3.0 (alpha/beta/stable as per latest).
- **KMP Support**: Ensure `core:database` is fully compatible with Android, Desktop (JVM), and iOS targets.
- **Bundled SQLite Driver**: Configure the project to use the `androidx.sqlite:sqlite-bundled` driver for all platforms to ensure consistent SQL behavior and versioning.
- **Schema Management**: Maintain existing database schemas and ensure migrations (if any) are compatible with Room 3.
- **DAO & Entity Optimization**: Refactor DAOs and Entities to use Room 3's idiomatic Kotlin APIs (e.g., using `RoomDatabase.Builder` for KMP).
## Non-Functional Requirements
- **Performance**: Ensure no significant regression in database performance after the migration.
- **Reliability**: All existing database tests must pass on Android.
- **Maintainability**: Adopt the new Room Gradle plugin for schema export and generation.
## Acceptance Criteria
1. All modules (`core:database`, `core:data`, etc.) build successfully with Room 3.0.
2. Database initialization works correctly on Android and Desktop.
3. Unit tests for DAOs pass in `commonTest` (where applicable) and `androidDeviceTest`.
4. The `androidx.sqlite:sqlite-bundled` driver is used for database connections.
5. iOS target is added to `core:database` (if not already present) and compiles.
## Out of Scope
- Migrating to a different database engine (e.g., SQLDelight).
- Major schema changes unrelated to the Room 3 migration.
- Implementing complex iOS-specific UI related to the database.

View file

@ -1,5 +0,0 @@
# Track mqtt_transport_20260318 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "mqtt_transport_20260318",
"type": "feature",
"status": "completed",
"created_at": "2026-03-18T00:00:00Z",
"updated_at": "2026-03-18T00:00:00Z",
"description": "MQTT transport"
}

View file

@ -1,32 +0,0 @@
# Implementation Plan: MQTT Transport
## Phase 1: Core Networking & Library Integration
- [x] Task: Evaluate and add KMP MQTT library dependency (e.g. Kmqtt) to `core:network` or `libs.versions.toml`. [2a4aa35]
- [x] Add dependency to `libs.versions.toml`.
- [x] Apply dependency in `core:network/build.gradle.kts`.
- [x] Task: Implement `MqttTransport` class in `commonMain` of `core:network`. [99d35b3]
- [x] Create failing tests in `commonTest` for MqttTransport initialization and configuration parsing.
- [x] Implement MqttTransport to parse URL (mqtt://, mqtts://), credentials, and configure the underlying MQTT client.
- [x] Write failing tests for connection state flows.
- [x] Implement connection lifecycle handling (connect, disconnect, reconnect).
- [x] Task: Conductor - User Manual Verification 'Phase 1: Core Networking & Library Integration' (Protocol in workflow.md) [93d9a50]
## Phase 2: Publishing & Subscribing
- [x] Task: Implement message subscription and payload parsing. [4900f69]
- [x] Create failing tests for receiving and mapping standard Meshtastic JSON payloads from subscribed topics.
- [x] Implement topic subscription management in `MqttTransport`.
- [x] Implement payload parsing and integration with `core:model` definitions.
- [x] Task: Implement publishing mechanism. [0991210]
- [x] Create failing tests for formatting and publishing node information/messages to custom topics.
- [x] Implement publish functionality in `MqttTransport`.
- [x] Task: Conductor - User Manual Verification 'Phase 2: Publishing & Subscribing' (Protocol in workflow.md) [7418e53]
## Phase 3: Service & UI Integration
- [x] Task: Integrate `MqttTransport` into `core:service` and `core:data`. [d414556, e172f53]
- [x] Create failing tests for orchestrating MQTT connection based on user preferences.
- [x] Implement service-level bindings to maintain background connection.
- [x] Task: Implement MQTT UI Configuration Settings. (Verified existing implementation)
- [x] Verified existing `MQTTConfigItemList.kt` correctly manages UI inputs.
- [x] Verified MQTT broker URL, username, password, and custom topic inputs exist in UI.
- [x] Verified UI inputs correctly wire to `ModuleConfig.MQTTConfig` used by `MQTTRepositoryImpl`.
- [x] Task: Conductor - User Manual Verification 'Phase 3: Service & UI Integration' (Protocol in workflow.md) [deaa324]

View file

@ -1,33 +0,0 @@
# Specification: MQTT Transport
## Overview
Implement an MQTT transport layer for the Meshtastic-Android Kotlin Multiplatform (KMP) application to enable communication with Meshtastic devices over MQTT. This will support Android, Desktop, iOS, and potentially Web platforms in the future.
## Functional Requirements
- **Platforms:** Ensure the MQTT transport operates correctly across Android, Desktop, and iOS platforms, using KMP best practices (with considerations for Web compatibility if technically feasible).
- **Core Library:** Utilize a dedicated Kotlin Multiplatform MQTT client library (e.g., Kmqtt) within the `core:network` module.
- **Connection Features:**
- Support for both standard (`mqtt://`) and secure TLS/SSL (`mqtts://`) connections.
- Support for username and password authentication.
- **Messaging Features:**
- Subscribe to and publish on user-defined custom topics.
- Parse and serialize standard Meshtastic JSON payloads.
- **UI Integration:**
- Follow the existing Android UX patterns for network/device connections.
- Integrate MQTT configuration seamlessly into the connection or advanced settings menus.
## Non-Functional Requirements
- **Architecture:** Business logic for MQTT communication must reside in the `core:network` (or a new `core:mqtt`) `commonMain` source set.
- **Testability:** Implement shared tests in `commonTest` to verify connection states, topic parsing, and payload serialization without relying on JVM-specific mocks.
- **Performance:** Ensure background execution and resource management align with the `core:service` architecture.
## Acceptance Criteria
- [ ] Users can enter an MQTT broker URL (including TLS), username, and password in the UI.
- [ ] The app successfully connects to the specified MQTT broker and maintains the connection in the background.
- [ ] The app can publish Meshtastic node information/messages to the broker.
- [ ] The app can receive and process incoming Meshtastic payloads from subscribed topics.
- [ ] Unit tests cover at least 80% of the new MQTT client logic.
## Out of Scope
- Direct firmware updates via MQTT (if not natively supported by the standard payload).
- Implementing a full local MQTT broker on the device.

View file

@ -1,5 +0,0 @@
# Track wire_up_notifs_20260316 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View file

@ -1,8 +0,0 @@
{
"track_id": "wire_up_notifs_20260316",
"type": "feature",
"status": "completed",
"created_at": "2026-03-16T00:00:00Z",
"updated_at": "2026-03-16T00:00:00Z",
"description": "wire up notifs"
}

View file

@ -1,34 +0,0 @@
# Implementation Plan: Wire Up Notifications
## Phase 1: Shared Abstraction (commonMain) [checkpoint: 930ce02]
- [x] Task: Define `NotificationManager` interface in `core:service/src/commonMain` 4f2107d
- [x] Create `Notification` data model (title, message, type)
- [x] Define `dispatch(notification: Notification)` method
- [x] Task: Create `NotificationPreferencesDataSource` using DataStore in `core:prefs` 346c2a4
- [x] Define boolean preferences for categories (e.g., Messages, Node Events)
- [x] Task: Conductor - User Manual Verification 'Phase 1: Shared Abstraction (commonMain)' (Protocol in workflow.md)
## Phase 2: Migrate Android Implementation (androidMain) [checkpoint: 1eb3cb0]
- [x] Task: Audit existing Android notifications 930ce02
- [x] Locate current implementation for local push notifications
- [x] Analyze triggers and UX (channels, icons, sounds)
- [x] Task: Implement `AndroidNotificationManager` 31c2a1e
- [x] Adapt existing Android notification code to the new `NotificationManager` interface
- [x] Inject `Context` and `NotificationPreferencesDataSource`
- [x] Respect user notification preferences
- [x] Task: Wire `AndroidNotificationManager` into Koin DI 31c2a1e
- [x] Task: Replace old Android notification calls with the new unified interface 81fd10b
- [x] Task: Conductor - User Manual Verification 'Phase 2: Migrate Android Implementation (androidMain)' (Protocol in workflow.md)
## Phase 3: Desktop Implementation (desktop) [checkpoint: 759914f]
- [x] Task: Implement `DesktopNotificationManager` 1eb3cb0
- [x] Inject `TrayState` and `NotificationPreferencesDataSource`
- [x] Delegate `dispatch()` to `TrayState.sendNotification()` respecting user preferences
- [x] Task: Wire `DesktopNotificationManager` into Koin DI 1eb3cb0
- [x] Task: Conductor - User Manual Verification 'Phase 3: Desktop Implementation (desktop)' (Protocol in workflow.md)
## Phase 4: UI Preferences Integration [checkpoint: 3af1e4c]
- [x] Task: Create UI for notification preferences 7ed59c6
- [x] Add toggles for categories in the Settings screen
- [x] Task: Conductor - User Manual Verification 'Phase 4: UI Preferences Integration' (Protocol in workflow.md)

View file

@ -1,17 +0,0 @@
# Specification: Wire Up Notifications
## Goal
To implement a unified, cross-platform notification system that abstracts platform-specific implementations (Android local push, Desktop TrayState) into a common API for the Kotlin Multiplatform (KMP) core. This will enable consistent notification dispatching for key mesh events.
## Requirements
1. **Abstraction Layer:** Create a shared `NotificationManager` interface in `commonMain` to handle notification dispatching across all targets.
2. **Platform Implementations:**
- **Android:** Implement native local notifications following the existing Android app behavior and Material Design guidance.
- **Desktop:** Implement system notifications using the `TrayState` API.
3. **Trigger Events:** Replicate the existing Android notification triggers (e.g., new messages, connections) and adapt them to use the new shared abstraction.
4. **User Preferences:** Provide a unified UI for users to opt in or out of specific notification categories, respecting their choices globally.
5. **Foreground Handling & Behavior:** Defer to platform-specific UX guidelines and the established Android implementation for aspects like sound, vibration, and in-app display (e.g., suppressing system notifications if the conversation is active).
## Out of Scope
- Changes to the underlying networking or Bluetooth layers.
- Remote Push Notifications (FCM/APNs) this is strictly for local, mesh-driven events.

View file

@ -1,31 +0,0 @@
# Desktop URI Import Plan
## Objective
Wire up `SharedContact` and `ChannelSet` import logic for the Desktop target. This enables the Desktop app to process deep links or URIs passed on startup via arguments or intercepted by the OS using `java.awt.Desktop`'s `OpenURIHandler`.
## Key Files & Context
- `desktop/src/main/kotlin/org/meshtastic/desktop/Main.kt`: Desktop app entry point. Must be updated to parse command line arguments and handle OS-level URI opening events.
- `desktop/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt`: The main UI composition. Must be updated to inject the shared `UIViewModel` and render the `SharedContactDialog` / `ScannedQrCodeDialog` when `requestChannelSet` or `sharedContactRequested` are present.
- `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/viewmodel/UIViewModel.kt`: Already handles URI dispatch and holds the requests, so no changes are needed here.
## Implementation Steps
1. **Update `DesktopMainScreen.kt`**
- Import `org.meshtastic.core.ui.viewmodel.UIViewModel`, `org.koin.compose.viewmodel.koinViewModel`, `org.meshtastic.core.ui.share.SharedContactDialog`, `org.meshtastic.core.ui.qr.ScannedQrCodeDialog`, and `org.meshtastic.core.model.ConnectionState`.
- Inject `UIViewModel` directly into `DesktopMainScreen` via `val uiViewModel = koinViewModel<UIViewModel>()`.
- Add observations for `uiViewModel.sharedContactRequested` and `uiViewModel.requestChannelSet`.
- Just like in Android's `MainScreen`, conditionally render `SharedContactDialog` and `ScannedQrCodeDialog` if `connectionState == ConnectionState.Connected` and either state contains a valid request.
- Wire `onDismiss` closures to `uiViewModel.clearSharedContactRequested()` and `uiViewModel.clearRequestChannelUrl()`.
2. **Update `Main.kt` (Desktop)**
- Alter `fun main()` to `fun main(args: Array<String>)`.
- Resolve `UIViewModel` after `koinApp` initialization: `val uiViewModel = koinApp.koin.get<UIViewModel>()`.
- Process the initial `args` and invoke `uiViewModel.handleScannedUri` using `MeshtasticUri` for any arguments that look like valid Meshtastic URIs (starting with `http` or `meshtastic://`).
- Attempt to attach a `java.awt.desktop.OpenURIHandler` if `java.awt.Desktop.Action.APP_OPEN_URI` is supported. When triggered, process the incoming `event.uri` string using the same `handleScannedUri` logic.
## Verification & Testing
1. Compile the desktop target with `./gradlew desktop:run --args="meshtastic://meshtastic/v/contact..."`.
2. Connect to a device via Desktop Connections or wait for connection.
3. Validate that the corresponding Shared Contact or Channel Set dialog renders on screen.
4. Verify that dismissing the dialogs properly clears the state in the view model.
5. (Optional, macOS) If testing via packaged DMG, verify that opening a `.webloc` or invoking `open meshtastic://...` triggers the `APP_OPEN_URI` handler and routes through the UI.

Some files were not shown because too many files have changed in this diff Show more