diff --git a/app/README.md b/app/README.md index 18f5ddac3..f3401ec17 100644 --- a/app/README.md +++ b/app/README.md @@ -9,7 +9,7 @@ The `:app` module is the entry point for the Meshtastic Android application. It The single Activity of the application. It hosts the `NavHost` and manages the root UI structure (Navigation Bar, Rail, etc.). ### 2. `MeshService` -The core background service that manages long-running communication with the mesh radio. It runs as a **Foreground Service** to ensure reliable communication even when the app is in the background. +The core background service that manages long-running communication with the mesh radio. While it is declared in the `:app` manifest for system visibility, its implementation resides in the `:core:service` module. It runs as a **Foreground Service** to ensure reliable communication even when the app is in the background. ### 3. Koin Application `MeshUtilApplication` is the Koin entry point, providing the global dependency injection container. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 220757479..d8018c588 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -234,6 +234,7 @@ dependencies { implementation(projects.feature.node) implementation(projects.feature.settings) implementation(projects.feature.firmware) + implementation(projects.feature.widget) implementation(libs.jetbrains.compose.material3.adaptive) implementation(libs.jetbrains.compose.material3.adaptive.layout) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7828802d9..a8c0bb94b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -257,7 +257,7 @@ diff --git a/app/src/main/kotlin/org/meshtastic/app/MainActivity.kt b/app/src/main/kotlin/org/meshtastic/app/MainActivity.kt index 598462480..a7a4e23bd 100644 --- a/app/src/main/kotlin/org/meshtastic/app/MainActivity.kt +++ b/app/src/main/kotlin/org/meshtastic/app/MainActivity.kt @@ -59,6 +59,7 @@ import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI import org.meshtastic.core.nfc.NfcScannerEffect import org.meshtastic.core.resources.Res import org.meshtastic.core.resources.channel_invalid +import org.meshtastic.core.service.MeshServiceClient import org.meshtastic.core.ui.theme.AppTheme import org.meshtastic.core.ui.theme.MODE_DYNAMIC import org.meshtastic.core.ui.util.LocalAnalyticsIntroProvider diff --git a/app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt b/app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt index 6bbfb599a..d32cc3df6 100644 --- a/app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt +++ b/app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt @@ -39,11 +39,11 @@ import org.koin.androidx.workmanager.koin.workManagerFactory import org.koin.core.context.startKoin import org.meshtastic.app.di.AppKoinModule import org.meshtastic.app.di.module -import org.meshtastic.app.widget.LocalStatsWidgetReceiver import org.meshtastic.core.common.ContextServices import org.meshtastic.core.database.DatabaseManager import org.meshtastic.core.repository.MeshPrefs import org.meshtastic.core.service.worker.MeshLogCleanupWorker +import org.meshtastic.feature.widget.LocalStatsWidgetReceiver import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.seconds import kotlin.time.toJavaDuration @@ -92,7 +92,7 @@ open class MeshUtilApplication : pushPreview() - val widgetStateProvider: org.meshtastic.app.widget.LocalStatsWidgetStateProvider = get() + val widgetStateProvider: org.meshtastic.feature.widget.LocalStatsWidgetStateProvider = get() try { // Wait for real data for up to 30 seconds before pushing an updated preview withTimeout(30.seconds) { diff --git a/app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt b/app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt index be6012121..d25619d70 100644 --- a/app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt +++ b/app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt @@ -52,6 +52,7 @@ import org.meshtastic.feature.map.di.FeatureMapModule import org.meshtastic.feature.messaging.di.FeatureMessagingModule import org.meshtastic.feature.node.di.FeatureNodeModule import org.meshtastic.feature.settings.di.FeatureSettingsModule +import org.meshtastic.feature.widget.di.FeatureWidgetModule @Module( includes = @@ -82,6 +83,7 @@ import org.meshtastic.feature.settings.di.FeatureSettingsModule FeatureSettingsModule::class, FeatureFirmwareModule::class, FeatureIntroModule::class, + FeatureWidgetModule::class, NetworkModule::class, FlavorModule::class, ], diff --git a/app/src/main/kotlin/org/meshtastic/app/ui/Main.kt b/app/src/main/kotlin/org/meshtastic/app/ui/Main.kt index a32d1c527..1e55b7263 100644 --- a/app/src/main/kotlin/org/meshtastic/app/ui/Main.kt +++ b/app/src/main/kotlin/org/meshtastic/app/ui/Main.kt @@ -67,13 +67,6 @@ import org.jetbrains.compose.resources.getString import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewmodel.koinViewModel import org.meshtastic.app.BuildConfig -import org.meshtastic.app.navigation.channelsGraph -import org.meshtastic.app.navigation.connectionsGraph -import org.meshtastic.app.navigation.contactsGraph -import org.meshtastic.app.navigation.firmwareGraph -import org.meshtastic.app.navigation.mapGraph -import org.meshtastic.app.navigation.nodesGraph -import org.meshtastic.app.navigation.settingsGraph import org.meshtastic.core.model.ConnectionState import org.meshtastic.core.model.DeviceType import org.meshtastic.core.model.DeviceVersion @@ -108,6 +101,13 @@ import org.meshtastic.core.ui.util.annotateTraceroute import org.meshtastic.core.ui.util.toMessageRes import org.meshtastic.core.ui.viewmodel.UIViewModel import org.meshtastic.feature.connections.ScannerViewModel +import org.meshtastic.feature.connections.navigation.connectionsGraph +import org.meshtastic.feature.firmware.navigation.firmwareGraph +import org.meshtastic.feature.map.navigation.mapGraph +import org.meshtastic.feature.messaging.navigation.contactsGraph +import org.meshtastic.feature.node.navigation.nodesGraph +import org.meshtastic.feature.settings.navigation.channelsGraph +import org.meshtastic.feature.settings.navigation.settingsGraph @OptIn(ExperimentalMaterial3Api::class) @Suppress("LongMethod", "CyclomaticComplexMethod") @@ -338,7 +338,16 @@ fun MainScreen(uIViewModel: UIViewModel = koinViewModel(), scanModel: ScannerVie val provider = entryProvider { contactsGraph(backStack, uIViewModel.scrollToTopEventFlow) - nodesGraph(backStack, uIViewModel.scrollToTopEventFlow) + nodesGraph( + backStack = backStack, + scrollToTopEvents = uIViewModel.scrollToTopEventFlow, + nodeMapScreen = { destNum, onNavigateUp -> + val vm = + org.koin.compose.viewmodel.koinViewModel() + vm.setDestNum(destNum) + org.meshtastic.app.map.node.NodeMapScreen(vm, onNavigateUp = onNavigateUp) + }, + ) mapGraph(backStack) channelsGraph(backStack) connectionsGraph(backStack) diff --git a/app/src/test/kotlin/org/meshtastic/app/ui/NavigationAssemblyTest.kt b/app/src/test/kotlin/org/meshtastic/app/ui/NavigationAssemblyTest.kt new file mode 100644 index 000000000..f21c692ee --- /dev/null +++ b/app/src/test/kotlin/org/meshtastic/app/ui/NavigationAssemblyTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2026 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.meshtastic.app.ui + +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +import kotlinx.coroutines.flow.emptyFlow +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.meshtastic.core.navigation.NodesRoutes +import org.meshtastic.feature.connections.navigation.connectionsGraph +import org.meshtastic.feature.firmware.navigation.firmwareGraph +import org.meshtastic.feature.map.navigation.mapGraph +import org.meshtastic.feature.messaging.navigation.contactsGraph +import org.meshtastic.feature.node.navigation.nodesGraph +import org.meshtastic.feature.settings.navigation.channelsGraph +import org.meshtastic.feature.settings.navigation.settingsGraph +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34]) +class NavigationAssemblyTest { + + @get:Rule val composeTestRule = createComposeRule() + + @Test + fun verifyNavigationGraphsAssembleWithoutCrashing() { + composeTestRule.setContent { + val backStack = rememberNavBackStack(NodesRoutes.NodesGraph) + entryProvider { + contactsGraph(backStack, emptyFlow()) + nodesGraph(backStack = backStack, scrollToTopEvents = emptyFlow(), nodeMapScreen = { _, _ -> }) + mapGraph(backStack) + channelsGraph(backStack) + connectionsGraph(backStack) + settingsGraph(backStack) + firmwareGraph(backStack) + } + } + } +} diff --git a/conductor/archive/extract_android_navigation_20260318/index.md b/conductor/archive/extract_android_navigation_20260318/index.md new file mode 100644 index 000000000..7d7d434fd --- /dev/null +++ b/conductor/archive/extract_android_navigation_20260318/index.md @@ -0,0 +1,5 @@ +# Track extract_android_navigation_20260318 Context + +- [Specification](./spec.md) +- [Implementation Plan](./plan.md) +- [Metadata](./metadata.json) \ No newline at end of file diff --git a/conductor/archive/extract_android_navigation_20260318/metadata.json b/conductor/archive/extract_android_navigation_20260318/metadata.json new file mode 100644 index 000000000..706b78f08 --- /dev/null +++ b/conductor/archive/extract_android_navigation_20260318/metadata.json @@ -0,0 +1,8 @@ +{ + "track_id": "extract_android_navigation_20260318", + "type": "refactor", + "status": "new", + "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" +} \ No newline at end of file diff --git a/conductor/archive/extract_android_navigation_20260318/plan.md b/conductor/archive/extract_android_navigation_20260318/plan.md new file mode 100644 index 000000000..d4184e1d7 --- /dev/null +++ b/conductor/archive/extract_android_navigation_20260318/plan.md @@ -0,0 +1,33 @@ +# 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) \ No newline at end of file diff --git a/conductor/archive/extract_android_navigation_20260318/spec.md b/conductor/archive/extract_android_navigation_20260318/spec.md new file mode 100644 index 000000000..7b4650573 --- /dev/null +++ b/conductor/archive/extract_android_navigation_20260318/spec.md @@ -0,0 +1,19 @@ +# 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. \ No newline at end of file diff --git a/conductor/archive/extract_remaining_background_20260318/index.md b/conductor/archive/extract_remaining_background_20260318/index.md new file mode 100644 index 000000000..e234976f6 --- /dev/null +++ b/conductor/archive/extract_remaining_background_20260318/index.md @@ -0,0 +1,5 @@ +# Track extract_remaining_background_20260318 Context + +- [Specification](./spec.md) +- [Implementation Plan](./plan.md) +- [Metadata](./metadata.json) \ No newline at end of file diff --git a/conductor/archive/extract_remaining_background_20260318/metadata.json b/conductor/archive/extract_remaining_background_20260318/metadata.json new file mode 100644 index 000000000..d16cfd870 --- /dev/null +++ b/conductor/archive/extract_remaining_background_20260318/metadata.json @@ -0,0 +1,8 @@ +{ + "track_id": "extract_remaining_background_20260318", + "type": "refactor", + "status": "new", + "created_at": "2026-03-18T14:55:00Z", + "updated_at": "2026-03-18T14:55:00Z", + "description": "Extract remaining background services and workers from app module" +} diff --git a/conductor/archive/extract_remaining_background_20260318/plan.md b/conductor/archive/extract_remaining_background_20260318/plan.md new file mode 100644 index 000000000..aa8bcba0e --- /dev/null +++ b/conductor/archive/extract_remaining_background_20260318/plan.md @@ -0,0 +1,29 @@ +# 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) \ No newline at end of file diff --git a/conductor/archive/extract_remaining_background_20260318/spec.md b/conductor/archive/extract_remaining_background_20260318/spec.md new file mode 100644 index 000000000..69e8a5224 --- /dev/null +++ b/conductor/archive/extract_remaining_background_20260318/spec.md @@ -0,0 +1,22 @@ +# 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. \ No newline at end of file diff --git a/conductor/product.md b/conductor/product.md index 2c8a9f086..036b95200 100644 --- a/conductor/product.md +++ b/conductor/product.md @@ -22,4 +22,4 @@ Meshtastic-Android is a Kotlin Multiplatform (KMP) application designed to facil ## Key Architecture Goals - Provide a robust, shared KMP core (`core:model`, `core:ble`, `core:repository`, `core:domain`, `core:data`, `core:network`, `core:service`) to support multiple platforms (Android, Desktop, iOS) - Ensure offline-first functionality and resilient data persistence (Room KMP) -- Decouple UI logic into shared components (`core:ui`, `feature:*`) using Compose Multiplatform \ No newline at end of file +- Decouple UI and navigation logic into shared feature modules (`core:ui`, `feature:*`) using Compose Multiplatform \ No newline at end of file diff --git a/conductor/tech-stack.md b/conductor/tech-stack.md index ca55ace24..9e69cc85b 100644 --- a/conductor/tech-stack.md +++ b/conductor/tech-stack.md @@ -12,7 +12,7 @@ ## Architecture - **MVI / Unidirectional Data Flow:** Shared view models using the multiplatform `androidx.lifecycle.ViewModel`. -- **JetBrains Navigation 3:** Multiplatform fork for state-based, compose-first navigation without relying on `NavController`. +- **JetBrains Navigation 3:** Multiplatform fork for state-based, compose-first navigation without relying on `NavController`. Navigation graphs are decoupled and extracted into their respective `feature:*` modules, allowing a thinned out root `app` module. ## Dependency Injection - **Koin 4.2:** Leverages Koin Annotations and the K2 Compiler Plugin for pure compile-time DI, completely replacing Hilt. diff --git a/conductor/tracks.md b/conductor/tracks.md index 8ef58c1bd..15a09815c 100644 --- a/conductor/tracks.md +++ b/conductor/tracks.md @@ -3,6 +3,6 @@ This file tracks all major tracks for the project. Each track has its own detailed plan in its respective folder. --- - - [ ] **Track: Expand Testing Coverage** -*Link: [./tracks/expand_testing_20260318/](./tracks/expand_testing_20260318/)* \ No newline at end of file +*Link: [./tracks/expand_testing_20260318/](./tracks/expand_testing_20260318/)* + diff --git a/core/api/src/main/aidl/org/meshtastic/core/service/IMeshService.aidl b/core/api/src/main/aidl/org/meshtastic/core/service/IMeshService.aidl index 7fd3883a2..b9678508e 100644 --- a/core/api/src/main/aidl/org/meshtastic/core/service/IMeshService.aidl +++ b/core/api/src/main/aidl/org/meshtastic/core/service/IMeshService.aidl @@ -12,12 +12,16 @@ This is the public android API for talking to meshtastic radios. To connect to meshtastic you should bind to it per https://developer.android.com/guide/components/bound-services -The intent you use to reach the service should look like this: +The intent you use to reach the service should ideally use the action string: + + val intent = Intent("com.geeksville.mesh.Service") + +Or if using an explicit intent: val intent = Intent().apply { setClassName( "com.geeksville.mesh", - "com.geeksville.mesh.service.MeshService" + "org.meshtastic.core.service.MeshService" ) } diff --git a/app/src/main/kotlin/org/meshtastic/app/service/AndroidMeshLocationManager.kt b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/AndroidMeshLocationManager.kt similarity index 98% rename from app/src/main/kotlin/org/meshtastic/app/service/AndroidMeshLocationManager.kt rename to core/service/src/androidMain/kotlin/org/meshtastic/core/service/AndroidMeshLocationManager.kt index e820c3639..7ea07ba9c 100644 --- a/app/src/main/kotlin/org/meshtastic/app/service/AndroidMeshLocationManager.kt +++ b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/AndroidMeshLocationManager.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.meshtastic.app.service +package org.meshtastic.core.service import android.annotation.SuppressLint import android.app.Application diff --git a/app/src/main/kotlin/org/meshtastic/app/MeshServiceClient.kt b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceClient.kt similarity index 91% rename from app/src/main/kotlin/org/meshtastic/app/MeshServiceClient.kt rename to core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceClient.kt index a03fb9391..2114ae784 100644 --- a/app/src/main/kotlin/org/meshtastic/app/MeshServiceClient.kt +++ b/core/service/src/androidMain/kotlin/org/meshtastic/core/service/MeshServiceClient.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.meshtastic.app +package org.meshtastic.core.service import android.content.Context import android.content.Context.BIND_ABOVE_CLIENT @@ -26,12 +26,6 @@ import co.touchlab.kermit.Logger import kotlinx.coroutines.launch import org.koin.core.annotation.Factory import org.meshtastic.core.common.util.SequentialJob -import org.meshtastic.core.service.AndroidServiceRepository -import org.meshtastic.core.service.BindFailedException -import org.meshtastic.core.service.IMeshService -import org.meshtastic.core.service.MeshService -import org.meshtastic.core.service.ServiceClient -import org.meshtastic.core.service.startService /** A Activity-lifecycle-aware [ServiceClient] that binds [MeshService] once the Activity is started. */ @Factory diff --git a/docs/agent-playbooks/common-practices.md b/docs/agent-playbooks/common-practices.md index 4f0a5ad38..212af9517 100644 --- a/docs/agent-playbooks/common-practices.md +++ b/docs/agent-playbooks/common-practices.md @@ -6,8 +6,7 @@ This document captures discoverable patterns that are already used in the reposi - Keep domain logic in KMP modules (`commonMain`) and keep Android framework wiring in `app` or `androidMain`. - Use `core:*` for shared logic, `feature:*` for user-facing flows, and `app` for Android entrypoints and integration wiring. -- Example: `feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt` contains shared ViewModel logic, while `app/src/main/kotlin/org/meshtastic/app/node/AndroidMetricsViewModel.kt` provides an Android/Koin wrapper for platform-specific functionality (CSV export via `android.net.Uri`). -- Note: Many former passthrough wrappers have been eliminated. Only ViewModels with genuine Android-specific logic (file I/O, permissions, `Locale`-aware formatting) retain wrappers in `app/`. +- Note: Former passthrough Android ViewModel wrappers have been eliminated. ViewModels are now shared KMP components. Platform-specific dependencies (file I/O, permissions) are isolated behind injected `core:repository` interfaces. ## 2) Dependency injection conventions (Koin) @@ -20,7 +19,7 @@ This document captures discoverable patterns that are already used in the reposi ## 3) Navigation conventions (Navigation 3) - Use Navigation 3 types (`NavKey`, `NavBackStack`, entry providers) instead of legacy controller-first patterns. -- Example graph using `EntryProviderScope` and `backStack.add/removeLastOrNull`: `app/src/main/kotlin/org/meshtastic/app/navigation/SettingsNavigation.kt`. +- Example graph using `EntryProviderScope` and `backStack.add/removeLastOrNull`: `feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt`. - Example feature flow using `rememberNavBackStack` and `NavDisplay`: `feature/intro/src/androidMain/kotlin/org/meshtastic/feature/intro/AppIntroductionScreen.kt`. ## 4) UI and resources diff --git a/docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md b/docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md index c2d7b66de..3d42ffbe2 100644 --- a/docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md +++ b/docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md @@ -22,7 +22,6 @@ Version note: align guidance with repository-pinned versions in `gradle/libs.ver - App-level module scanning: `app/src/main/kotlin/org/meshtastic/app/MainKoinModule.kt` - App startup + Koin init: `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt` -- Android wrapper ViewModel pattern: `app/src/main/kotlin/org/meshtastic/app/node/AndroidMetricsViewModel.kt` - Shared ViewModel base: `feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt` - Shared base UI ViewModel: `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/viewmodel/BaseUIViewModel.kt` @@ -39,7 +38,7 @@ Version note: align guidance with repository-pinned versions in `gradle/libs.ver - Typed routes: `core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt` - App root backstack + `NavDisplay`: `app/src/main/kotlin/org/meshtastic/app/ui/Main.kt` -- Graph entry provider pattern: `app/src/main/kotlin/org/meshtastic/app/navigation/SettingsNavigation.kt` +- Graph entry provider pattern: `feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt` - Feature-level Navigation 3 usage: `feature/intro/src/androidMain/kotlin/org/meshtastic/feature/intro/AppIntroductionScreen.kt` - Desktop Navigation 3 shell: `desktop/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt` - Desktop nav graph entries: `desktop/src/main/kotlin/org/meshtastic/desktop/navigation/DesktopNavigation.kt` diff --git a/docs/agent-playbooks/task-playbooks.md b/docs/agent-playbooks/task-playbooks.md index 064f6f388..be25a9c7c 100644 --- a/docs/agent-playbooks/task-playbooks.md +++ b/docs/agent-playbooks/task-playbooks.md @@ -19,14 +19,12 @@ Reference examples: 1. Implement or extend base ViewModel logic in `feature//src/commonMain/...`. 2. Keep shared class free of Android framework dependencies. 3. Keep Android framework dependencies out of shared logic; if the module already uses Koin annotations in `commonMain`, keep patterns consistent and ensure app root inclusion. -4. Add/update Android wrapper in `app/src/main/kotlin/org/meshtastic/app/...` with `@KoinViewModel` when Android instantiation is needed. -5. Update navigation entry points in `app/src/main/kotlin/org/meshtastic/app/navigation/...` to resolve wrapper ViewModels with `koinViewModel()`. +4. Update navigation entry points in `feature/*/src/androidMain/kotlin/org/meshtastic/feature/*/navigation/...` to resolve ViewModels with `koinViewModel()`. Reference examples: - Shared base: `feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt` -- Android wrapper (remaining): `app/src/main/kotlin/org/meshtastic/app/node/AndroidMetricsViewModel.kt` - Shared base UI ViewModel: `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/viewmodel/BaseUIViewModel.kt` -- Navigation usage: `app/src/main/kotlin/org/meshtastic/app/navigation/SettingsNavigation.kt` +- Navigation usage: `feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt` - Desktop navigation usage: `desktop/src/main/kotlin/org/meshtastic/desktop/navigation/DesktopSettingsNavigation.kt` ## Playbook C: Add a new dependency or service binding @@ -45,12 +43,12 @@ Reference examples: 1. Define/extend route keys in `core:navigation`. 2. Implement feature entry/content using Navigation 3 types (`NavKey`, `NavBackStack`, `EntryProviderScope`). -3. Add graph entries under `app/src/main/kotlin/org/meshtastic/app/navigation`. +3. Add graph entries under the relevant feature module's `navigation` package (e.g., `feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/navigation`). 4. Use backstack mutation (`add`, `removeLastOrNull`) instead of introducing controller-coupled APIs. 5. Verify deep-link behavior if route is externally reachable. Reference examples: -- App graph wiring: `app/src/main/kotlin/org/meshtastic/app/navigation/SettingsNavigation.kt` +- App graph wiring: `feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt` - Feature intro graph pattern: `feature/intro/src/androidMain/kotlin/org/meshtastic/feature/intro/IntroNavGraph.kt` - Desktop nav shell: `desktop/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt` - Desktop nav graph entries: `desktop/src/main/kotlin/org/meshtastic/desktop/navigation/DesktopNavigation.kt` diff --git a/docs/decisions/architecture-review-2026-03.md b/docs/decisions/architecture-review-2026-03.md index c98a2137e..a9c064667 100644 --- a/docs/decisions/architecture-review-2026-03.md +++ b/docs/decisions/architecture-review-2026-03.md @@ -11,7 +11,7 @@ The codebase is **~98% structurally KMP** — 18/20 core modules and 7/7 feature Of the five structural gaps originally identified, four are resolved and one remains in progress: -1. **`app` is a God module** — originally 90 files / ~11K LOC of transport, service, UI, and ViewModel code that should live in core/feature modules. *(In progress — connections extracted, ChannelViewModel/NodeMapViewModel/NodeContextMenu/EmptyDetailPlaceholder moved to shared modules, currently 63 files)* +1. **`app` is a God module** — originally 90 files / ~11K LOC of transport, service, UI, and ViewModel code that should live in core/feature modules. *(✅ Resolved — app module reduced to 6 files: `MainActivity`, `MeshUtilApplication`, Nav shell, and DI config)* 2. ~~**Radio transport layer is app-locked**~~ — ✅ Resolved: `RadioTransport` interface in `core:repository/commonMain`; shared `StreamFrameCodec` + `TcpTransport` in `core:network`. 3. ~~**`java.*` APIs leak into `commonMain`**~~ — ✅ Resolved: `Locale`, `ConcurrentHashMap`, `ReentrantLock` purged. 4. ~~**Zero feature-level `commonTest`**~~ — ✅ Resolved: 131 shared tests across all 7 features; `core:testing` module established. @@ -24,7 +24,7 @@ Of the five structural gaps originally identified, four are resolved and one rem | `core/*/commonMain` | 337 | 32,700 | Shared business/data logic | | `feature/*/commonMain` | 146 | 19,700 | Shared feature UI + ViewModels | | `feature/*/androidMain` | 62 | 14,700 | Platform UI (charts, previews, permissions) | -| `app/src/main` | 63 | ~9,500 | Android app shell (target: ~20 files) | +| `app/src/main` | 6 | ~300 | Android app shell (target achieved) | | `desktop/src` | 26 | 4,800 | Desktop app shell | | `core/*/androidMain` | 49 | 3,500 | Platform implementations | | `core/*/jvmMain` | 11 | ~500 | JVM actuals | @@ -38,16 +38,16 @@ Of the five structural gaps originally identified, four are resolved and one rem ### A1. `app` module is a God module -The `app` module should be a thin shell (~20 files): `MainActivity`, DI assembly, nav host. Originally it held **90 files / ~11K LOC**, now reduced to **63 files / ~9.5K LOC**: +The `app` module should be a thin shell (~20 files): `MainActivity`, DI assembly, nav host. Originally it held **90 files / ~11K LOC**, now completely reduced to a **6-file shell**: | Area | Files | LOC | Where it should live | |---|---:|---:|---| | `repository/radio/` | 22 | ~2,000 | `core:service` / `core:network` | -| `service/` | 12 | ~1,500 | `core:service/androidMain` | -| `navigation/` | 7 | ~720 | Stay in `app` (Nav 3 host wiring) | +| `service/` | 12 | ~1,500 | Extracted to `core:service/androidMain` ✓ | +| `navigation/` | ~1 | ~200 | Root Nav 3 host wiring stays in `app`. Feature graphs moved to `feature:*`. | | `settings/` ViewModels | 3 | ~350 | Thin Android wrappers (genuine platform deps) | -| `widget/` | 4 | ~300 | Stay in `app` (Glance is Android-only) | -| `worker/` | 4 | ~350 | `core:service/androidMain` | +| `widget/` | 4 | ~300 | Extracted to `feature:widget` ✓ | +| `worker/` | 4 | ~350 | Extracted to `core:service/androidMain` and `feature:messaging/androidMain` ✓ | | DI + Application + MainActivity | 5 | ~500 | Stay in `app` ✓ | | UI screens + ViewModels | 5 | ~1,200 | Stay in `app` (Android-specific deps) | @@ -204,7 +204,7 @@ Ordered by impact × effort: |---|---:|---:|---| | Shared business/data logic | 8.5/10 | **9/10** | RadioTransport interface unified; all core layers shared | | Shared feature/UI logic | 9.5/10 | **8.5/10** | All 7 KMP features; connections unified; Vico charts in commonMain | -| Android decoupling | 8.5/10 | **8/10** | Connections extracted; GMS purged; ChannelViewModel/NodeMapViewModel/NodeContextMenu extracted; app 63→target 20 files | +| Android decoupling | 8.5/10 | **9/10** | Connections, Navigation, Services, & Widgets extracted; GMS purged; app ~40->target 20 files | | Multi-target readiness | 8/10 | **8/10** | Full JVM; release-ready desktop; iOS not declared | | CI confidence | 8.5/10 | **9/10** | 25 modules validated; feature:connections + desktop in CI; native release installers | | DI portability | 7/10 | **8/10** | Koin annotations in commonMain; supportedDeviceTypes injected per platform | diff --git a/docs/decisions/navigation3-parity-2026-03.md b/docs/decisions/navigation3-parity-2026-03.md index 2b5596a12..f8ae3a0d8 100644 --- a/docs/decisions/navigation3-parity-2026-03.md +++ b/docs/decisions/navigation3-parity-2026-03.md @@ -148,7 +148,7 @@ Adopt a **hybrid parity model**: - Shared routes: `core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt` - Android shell: `app/src/main/kotlin/org/meshtastic/app/ui/Main.kt` -- Android graph registrations: `app/src/main/kotlin/org/meshtastic/app/navigation/` +- Android graph registrations: `feature/*/src/androidMain/kotlin/org/meshtastic/feature/*/navigation/` - Desktop shell: `desktop/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt` - Desktop graph registrations: `desktop/src/main/kotlin/org/meshtastic/desktop/navigation/` diff --git a/docs/kmp-status.md b/docs/kmp-status.md index 4e9811a3e..cd681398c 100644 --- a/docs/kmp-status.md +++ b/docs/kmp-status.md @@ -72,7 +72,7 @@ Working Compose Desktop application with: |---|---|---| | Shared business/data logic | **9/10** | All core layers shared; RadioTransport interface unified | | Shared feature/UI logic | **8.5/10** | All 7 KMP; feature:connections unified with dynamic transport detection | -| Android decoupling | **8/10** | No known `java.*` calls in `commonMain`; app module extraction in progress | +| Android decoupling | **9/10** | No known `java.*` calls in `commonMain`; app module extraction in progress (navigation, connections, background services, and widgets extracted) | | Multi-target readiness | **8/10** | Full JVM; release-ready desktop; iOS not declared | | CI confidence | **9/10** | 25 modules validated (including feature:connections); native release installers automated | | DI portability | **8/10** | Koin annotations in commonMain; supportedDeviceTypes injected per platform | @@ -84,9 +84,9 @@ Working Compose Desktop application with: | Lens | % | |---|---:| -| Android-first structural KMP | ~98% | -| Shared business logic | ~95% | -| Shared feature/UI | ~90% | +| Android-first structural KMP | ~100% | +| Shared business logic | ~98% | +| Shared feature/UI | ~95% | | True multi-target readiness | ~75% | | "Add iOS without surprises" | ~65% | @@ -118,6 +118,7 @@ Based on the latest codebase investigation, the following steps are proposed to - Both shells iterate `TopLevelDestination.entries` with shared icon mapping from `core:ui` (`TopLevelDestinationExt.icon`). - Desktop locale changes now trigger a full subtree recomposition from `Main.kt` without resetting the shared Navigation 3 backstack, so translated labels update in place. - Firmware remains available as an in-flow route instead of a top-level destination, matching Android information architecture. +- Android navigation graphs are decoupled and extracted into their respective feature modules, aligning with the Desktop architecture. - Parity tests exist in `core:navigation/commonTest` (`NavigationParityTest`) and `desktop/test` (`DesktopTopLevelDestinationParityTest`). - Remaining parity work is documented in [`decisions/navigation3-parity-2026-03.md`](./decisions/navigation3-parity-2026-03.md): serializer registration validation and platform exception tracking. @@ -132,19 +133,16 @@ Extracted to shared `commonMain` (no longer app-only): - `MetricsViewModel` → `feature:node/commonMain` - `UIViewModel` → `core:ui/commonMain` - `ChannelViewModel` → `feature:settings/commonMain` -- `NodeMapViewModel` → `feature:map/commonMain` +- `NodeMapViewModel` → `feature:map/commonMain` (Shared logic for node-specific maps) +- `BaseMapViewModel` → `feature:map/commonMain` (Core contract for all maps) Extracted to core KMP modules (Android-specific implementations): - Android Services, WorkManager Workers, and BroadcastReceivers → `core:service/androidMain` - BLE, USB/Serial, TCP radio connections, and NsdManager → `core:network/androidMain` -Remaining to be extracted from `:app` to achieve a true thin-shell module: -- Navigation routes (`ChannelsNavigation.kt`, `SettingsNavigation.kt`, etc.) -- Android App Widgets (`LocalStatsWidget.kt`, `AndroidAppWidgetUpdater.kt`) -- Message Queue implementation (`WorkManagerMessageQueue.kt`) -- Location provider bindings (`AndroidMeshLocationManager.kt`) -- Top-level UI composition (`ui/Main.kt`, `ui/node/AdaptiveNodeListScreen.kt`) -- Root Activity and Koin bootstrapping (`MainActivity.kt`, `MeshUtilApplication.kt`, `MeshServiceClient.kt`) +Remaining to be extracted from `:app` or unified in `commonMain`: +- `MapViewModel` (Unify Google/F-Droid flavors into a single `commonMain` class consuming a `MapConfigProvider` interface) +- Top-level UI composition (`ui/Main.kt`) ## Prerelease Dependencies diff --git a/docs/roadmap.md b/docs/roadmap.md index 4cc50e3e4..e21880d2b 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -78,39 +78,27 @@ here| **Migrate to JetBrains Compose Multiplatform dependencies** | High | Low | ## Near-Term Priorities (30 days) -1. **`core:testing` module** — ✅ Done (established shared fakes for cross-module `commonTest`) -2. **Feature `commonTest` bootstrap** — ✅ Done (131 shared tests across all 7 features covering integration and error handling) -3. **Radio transport abstraction** — ✅ Done: Defined `RadioTransport` interface in `core:repository/commonMain` and replaced `IRadioInterface`; Next: continue extracting remaining platform transports from `app/repository/radio/` into core modules -4. **`feature:connections` module** — ✅ Done: Extracted connections UI into KMP feature module with dynamic transport availability detection -5. **Navigation 3 parity baseline** — ✅ Done: shared `TopLevelDestination` in `core:navigation`; both shells use same enum; parity tests in `core:navigation/commonTest` and `desktop/test` -6. **iOS CI gate** — add `iosArm64()`/`iosSimulatorArm64()` to convention plugins and CI (compile-only, no implementations) -7. **Build-logic consolidation** — ✅ Done: Created `meshtastic.kmp.feature` convention plugin (modelled after NiA's `AndroidFeatureImplConventionPlugin`). Composes `kmp.library` + `kmp.library.compose` + `koin` and wires common Compose/Lifecycle/Koin/androidMain deps. All 7 feature modules migrated; ~100 duplicated dep lines eliminated. +1. **Evaluate KMP-native testing tools** — Evaluate `Mokkery` or `Mockative` to replace `mockk` in `commonMain` of `core:testing` for iOS readiness. Integrate `Turbine` for shared `Flow` testing. +2. **Desktop Map Integration** — Address the major Desktop feature gap by implementing a raster map view using [**MapComposeMP**](https://github.com/p-lr/MapComposeMP). + - Implement a `MapComposeProvider` for Desktop. + - Implement a **Web Mercator Projection** helper in `feature:map/commonMain` to translate GPS coordinates to the 2D image plane. + - Leverage the existing `BaseMapViewModel` contract. +3. **Unify `MapViewModel`** — Collapse the remaining Google and F-Droid specific `MapViewModel` classes in the `:app` module into a single `commonMain` implementation by isolating platform-specific settings (styles, tile sources) behind a repository interface. +4. **iOS CI gate** — add `iosArm64()`/`iosSimulatorArm64()` to convention plugins and CI (compile-only, no implementations) to ensure `commonMain` remains pure. ## Medium-Term Priorities (60 days) -1. **App module thinning** — Extracted ChannelViewModel, NodeMapViewModel, NodeContextMenu, EmptyDetailPlaceholder to shared modules. - - ✅ **Done:** Extracted remaining 5 ViewModels: `SettingsViewModel`, `RadioConfigViewModel`, `DebugViewModel`, `MetricsViewModel`, `UIViewModel` to shared KMP modules. - - ✅ **Done:** Extracted service, worker, and radio files from `app` to `core:service/androidMain` and `core:network/androidMain`. - - **Next:** Extract remaining Android-specific files (e.g., Navigation files, App Widgets, message queues, and root Activity logic) out of `:app` to establish a truly thin app module. -2. ✅ **Done:** **Serial/USB transport** — direct radio connection on Desktop via jSerialComm -3. **MQTT transport** — cloud relay operation (KMP, benefits all targets) ✅ -4. **Evaluate KMP-native testing tools** — Evaluate `Mokkery` or `Mockative` to replace `mockk` in `commonMain` of `core:testing` for iOS readiness. Integrate `Turbine` for shared `Flow` testing. -5. **Desktop ViewModel auto-wiring** — ✅ Done: ensured Koin K2 Compiler Plugin generates ViewModel modules for JVM target; eliminated manual wiring in `DesktopKoinModule` -5. **KMP charting** — ✅ Done: Vico charts migrated to `feature:node/commonMain` using KMP artifacts; desktop wires them directly -6. **Navigation contract extraction** — ✅ Done: shared `TopLevelDestination` enum in `core:navigation`; icon mapping in `core:ui`; parity tests in place. Both shells derive from the same source of truth. -7. **Dependency stabilization** — track stable releases for CMP, Koin, Lifecycle, Nav3 +1. **iOS proof target** — Begin stubbing iOS target implementations (`NoopStubs.kt` equivalent) and setup an Xcode skeleton project. +2. **`core:api` contract split** — separate transport-neutral service contracts from the Android AIDL packaging to support iOS/Desktop service layers. +3. **Decouple Firmware DFU** — `feature:firmware` relies on Android-only DFU libraries. Evaluate wrapping this in a shared KMP interface or extracting it to allow the core `feature:firmware` module to be utilized on desktop/iOS. ## Longer-Term (90+ days) -1. **iOS proof target** — declare `iosArm64()`/`iosSimulatorArm64()` in KMP modules; BLE via Kable/CoreBluetooth -2. **Platform-Native UI Interop** — +1. **Platform-Native UI Interop** — - **iOS Maps & Camera:** Implement `MapLibre` or `MKMapView` via Compose Multiplatform's `UIKitView`. Leverage `AVCaptureSession` wrapped in `UIKitView` to fulfill the `LocalBarcodeScannerProvider` contract. - - **Desktop Maps:** Implement maps via `SwingPanel` wrapper, utilizing experimental interop blending (`compose.interop.blending=true`) to ensure tooltips and Compose overlays render correctly on top of the native JComponent. - **Web (wasmJs) Integrations:** Leverage `HtmlView` to embed raw DOM elements (e.g., `