diff --git a/.skills/kmp-architecture/SKILL.md b/.skills/kmp-architecture/SKILL.md index b22bd0a1d1..6239b86986 100644 --- a/.skills/kmp-architecture/SKILL.md +++ b/.skills/kmp-architecture/SKILL.md @@ -11,7 +11,8 @@ Guidelines on managing Kotlin Multiplatform (KMP) source-sets, expected abstract ## 1. Source-Set Boundaries - **`commonMain`:** All business logic, DB entities, API network logic, ViewModels, and UI rendering. NO `java.*` or `android.*` imports. - **`androidMain`:** Android framework integration (`Context`, system services, NFC hardware, BLE Android bindings). -- **`jvmMain` / `jvmAndroidMain`:** Shared JVM code between Android and Desktop. Uses the `meshtastic.kmp.jvm.android` convention plugin to bridge `jvm` and `android` source sets without manual `dependsOn` hacks. +- **`jvmMain`:** Desktop-only JVM code. +- **`jvmAndroidMain`:** JVM code shared between Android and Desktop. Uses the `meshtastic.kmp.jvm.android` convention plugin to bridge `jvm` and `android` source sets without manual `dependsOn` hacks. - **`androidApp` / `desktopApp`:** Host shells. Responsible for Koin DI root wiring (`MainKoinModule`/`AppKoinModule`, `DesktopKoinModule`), host-level UI themes, and running the `MeshtasticNavDisplay`. ## 2. Bridging Strategies diff --git a/.skills/project-overview/SKILL.md b/.skills/project-overview/SKILL.md index 01b78a5144..ca1fdc674a 100644 --- a/.skills/project-overview/SKILL.md +++ b/.skills/project-overview/SKILL.md @@ -8,7 +8,7 @@ description: The Meshtastic-Android codebase map - module directory, namespacing ## Description Module directory, namespacing conventions, environment setup, and troubleshooting for Meshtastic-Android. -- **Build System:** Gradle (Kotlin DSL). JDK 25 REQUIRED. Target SDK: API 36. Min SDK: API 26. +- **Build System:** Gradle (Kotlin DSL). JDK 25 REQUIRED. Target SDK: API 37. Min SDK: API 26. - **Flavors:** `fdroid` (OSS only) · `google` (Maps + DataDog analytics) - **Android-only Modules:** `core:barcode` (CameraX), `feature:widget` (Glance home-screen widget), and `baselineprofile` (Macrobenchmark). Shared contracts are abstracted into `core:ui/commonMain`. @@ -27,7 +27,7 @@ Module directory, namespacing conventions, environment setup, and troubleshootin | `core:repository` | High-level domain interfaces (e.g., `NodeRepository`, `LocationRepository`). | | `core:domain` | Pure KMP business logic and UseCases. | | `core:data` | Core manager implementations and data orchestration. | -| `core:network` | KMP networking layer using Ktor, MQTT abstractions, and shared transport (`StreamFrameCodec`, `TcpTransport`, `SerialTransport`, `BleRadioInterface`). | +| `core:network` | KMP networking layer using Ktor, MQTT abstractions, and shared transport (`StreamFrameCodec`, `TcpTransport`, `SerialTransport`, `BleRadioTransport`). | | `core:di` | Common DI qualifiers and dispatchers. | | `core:navigation` | Shared navigation keys/routes for Navigation 3 using `@Serializable sealed interface` hierarchies. `DeepLinkRouter` for typed backstack synthesis, and `MeshtasticNavSavedStateConfig` with `subclassesOfSealed()` for automatic polymorphic backstack persistence. | | `core:ui` | Shared Compose UI components (`MeshtasticAppShell`, `MeshtasticNavDisplay`, `MeshtasticNavigationSuite`, `AlertHost`, `SharedDialogs`, `PlaceholderScreen`, `MainAppBar`, dialogs, preferences) and platform abstractions. | @@ -35,11 +35,11 @@ Module directory, namespacing conventions, environment setup, and troubleshootin | `core:takserver` | Meshtastic ↔ TAK (ATAK/iTAK) bridge — local CoT server and CoT ⇄ mesh conversion. | | `core:prefs` | KMP preferences layer built on DataStore abstractions. | | `core:barcode` | Barcode scanning (Android-only). | -| `core:nfc` | NFC abstractions (KMP). Android NFC hardware implementation in `androidMain`. | +| `core:nfc` | Android-only NFC hardware implementation (`androidMain`). The shared NFC abstractions are the `LocalNfc*Provider` composition locals in `core:ui`. | | `core/ble/` | Bluetooth Low Energy stack using Kable. | | `core/resources/` | Centralized string and image resources (Compose Multiplatform). | | `core/testing/` | Shared test doubles, fakes, and utilities for `commonTest` across all KMP modules. | -| `feature/` | Feature modules (e.g., `settings`, `map`, `messaging`, `node`, `intro`, `connections`, `firmware`, `wifi-provision`, `discovery`, `docs`, `widget`). Most are KMP and use the `meshtastic.kmp.feature` convention plugin; `widget` (Glance) is Android-only. | +| `feature/` | Feature modules (e.g., `settings`, `map`, `map-maplibre`, `map-terrain`, `messaging`, `node`, `intro`, `connections`, `firmware`, `wifi-provision`, `discovery`, `docs`, `widget`). Most are KMP and use the `meshtastic.kmp.feature` convention plugin; `widget` (Glance) is Android-only. | | `baselineprofile/` | Macrobenchmark Baseline Profile generation for `:androidApp` (AOT-compiled cold-start journey). Android-only. | | `feature/wifi-provision` | KMP WiFi provisioning via BLE (Nymea protocol). Uses `core:ble` Kable abstractions. | | `feature/firmware` | Fully KMP firmware update system: Unified OTA (BLE + WiFi), native Nordic Secure DFU protocol (pure KMP), USB/UF2 updates, and `FirmwareRetriever` with manifest-based resolution. Desktop is a first-class target. | diff --git a/.skills/speckit/SKILL.md b/.skills/speckit/SKILL.md index 3965ece3ed..54fb8a3020 100644 --- a/.skills/speckit/SKILL.md +++ b/.skills/speckit/SKILL.md @@ -113,7 +113,7 @@ specs/ The project constitution at `.specify/memory/constitution.md` defines non-negotiable principles. All specs, plans, and tasks are validated against it during `/speckit.analyze`. -Current constitution (v1.4.1) enforces 7 principles: +Current constitution (v1.4.2) enforces 7 principles: 1. **KMP Core** — Business logic in `commonMain` only 2. **Zero Lint Tolerance** — `spotlessCheck` + `detekt` must pass diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index 149dd5f07c..67bee4a898 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -1,4 +1,28 @@ ### II. Zero Lint Tolerance @@ -315,4 +342,4 @@ summary derived from this constitution. The files `.github/copilot-instructions. Constitution Check confirming all seven principles were evaluated. Complexity violations require explicit justification in the Complexity Tracking table of the plan document. -**Version**: 1.4.1 | **Ratified**: 2026-05-07 | **Last Amended**: 2026-09-26 +**Version**: 1.4.2 | **Ratified**: 2026-05-07 | **Last Amended**: 2026-09-28 diff --git a/.specify/templates/spec-template.md b/.specify/templates/spec-template.md index 081fa7b3c6..cc34ffaebf 100644 --- a/.specify/templates/spec-template.md +++ b/.specify/templates/spec-template.md @@ -148,7 +148,8 @@ |-----------|--------|---------------| | `commonMain` | [New files / Modified files] | All business logic and UI | | `androidMain` | [None / Platform integration only] | [Justification if needed] | -| `jvmMain` | [None / Shared JVM code] | [Justification if needed] | +| `jvmMain` | [None / Desktop-only JVM code] | [Justification if needed] | +| `jvmAndroidMain` | [None / JVM code shared by Android and Desktop] | [Justification if needed] | ## Design Standards Compliance diff --git a/CLAUDE.md b/CLAUDE.md index 11ed943374..74876f1cfa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,11 +31,11 @@ want real Google Maps tiles (`MAPS_API_KEY=…`). `local.properties` is not read ```bash ./gradlew spotlessApply spotlessCheck detekt assembleDebug test allTests ``` -Both `test` and `allTests` are required: `allTests` covers KMP modules (where the bare `test` task is ambiguous and silently skips), `test` covers pure-Android/JVM modules. Add `kmpSmokeCompile` when touching a KMP module. After adding string resources, run `python3 scripts/sort-strings.py`. Change-type matrix and CI architecture: `.skills/testing-ci/SKILL.md`. +Both `test` and `allTests` are required: `allTests` runs each KMP module's `jvmTest` and Android host tests (a KMP module has no `test` task, and naming `:core:data:test` fails as ambiguous), `test` covers pure-Android/JVM modules and skips KMP ones. Add `kmpSmokeCompile` when touching a KMP module. After adding string resources, run `python3 scripts/sort-strings.py`. Change-type matrix and CI architecture: `.skills/testing-ci/SKILL.md`. **Single test:** ```bash ./gradlew :feature:messaging:allTests # one KMP module ./gradlew :androidApp:testFdroidDebugUnitTest # one Android/JVM module -./gradlew :core:data:allTests --tests "*PacketHandlerTest*" # filter to one class/method +./gradlew :core:data:jvmTest --tests "*PacketHandlerTest*" # filter to one class/method (allTests takes no --tests) ``` diff --git a/core/konsist/src/jvmTest/kotlin/org/meshtastic/core/konsist/CommonMainFrameworkBoundaryTest.kt b/core/konsist/src/jvmTest/kotlin/org/meshtastic/core/konsist/CommonMainFrameworkBoundaryTest.kt index e5d694b280..90026adfff 100644 --- a/core/konsist/src/jvmTest/kotlin/org/meshtastic/core/konsist/CommonMainFrameworkBoundaryTest.kt +++ b/core/konsist/src/jvmTest/kotlin/org/meshtastic/core/konsist/CommonMainFrameworkBoundaryTest.kt @@ -28,7 +28,7 @@ import kotlin.test.assertTrue * the JVM (`java.*`) or Android (`android.*`) platform APIs — use the KMP equivalents (Okio, kotlinx-datetime, * atomicfu, Mutex, …) instead. * - * Konsist scans every module's Kotlin source from disk, so this single test covers all 37 modules. It runs on the JVM + * Konsist scans every module's Kotlin source from disk, so this single test covers every module. It runs on the JVM * (Konsist is JVM-only) under the existing `allTests` baseline gate. */ class CommonMainFrameworkBoundaryTest { diff --git a/docs/en/developer/adding-a-feature-module.md b/docs/en/developer/adding-a-feature-module.md index cf3bd8ee2f..f3b6c45790 100644 --- a/docs/en/developer/adding-a-feature-module.md +++ b/docs/en/developer/adding-a-feature-module.md @@ -2,7 +2,7 @@ title: Adding a Feature Module parent: Developer Guide nav_order: 3 -last_updated: 2026-08-29 +last_updated: 2026-09-29 description: Step-by-step guide for creating a new KMP feature module — module directory, build script, DI, routes, navigation entries, and the checklist. aliases: - new-module @@ -146,6 +146,9 @@ Every feature module should have: - [ ] Module directory created - [ ] `build.gradle.kts` with correct plugins and dependencies - [ ] Added to `settings.gradle.kts` +- [ ] Added to `ALL_MODULES_FULL` in `build-logic/convention/src/main/kotlin/RootConventionPlugin.kt`; `python3 scripts/check-module-list.py` fails when a non-exempt module is missing from it, when an entry is absent from the `settings.gradle.kts` includes, or when an exempt module is re-added, and a non-exempt module missing from it is absent from Dokka and Kover aggregation and `kmpSmokeCompile` +- [ ] Added to a test shard in `.github/workflows/reusable-check.yml` (`shard-feature` for a feature module): its `:feature:my-feature:allTests` task in `tasks` and its `koverXmlReport` in `kover`; `python3 scripts/check-test-shards.py` fails for a module with no test task in any shard, unless the module is listed in the script's `COVERED_ELSEWHERE` or `NO_TESTS_YET` sets +- [ ] If the module is user-facing, a page under `docs/en/user/` and a `MODULE_TO_DOCS` entry for it in `scripts/check-doc-coverage.js`; the check fails when a listed module's page is missing, and a module with no entry needs no page - [ ] DI module created with `@ComponentScan` - [ ] DI module registered in app and desktop roots - [ ] Routes added to `Routes.kt` diff --git a/docs/en/developer/architecture.md b/docs/en/developer/architecture.md index 137e010d2b..d370c38532 100644 --- a/docs/en/developer/architecture.md +++ b/docs/en/developer/architecture.md @@ -2,7 +2,7 @@ title: Architecture parent: Developer Guide nav_order: 1 -last_updated: 2026-09-11 +last_updated: 2026-09-28 description: How the Android and Desktop apps split into androidApp/desktopApp, feature modules, and core modules, and how radio control and navigation are layered across them. aliases: - layers @@ -58,6 +58,7 @@ Each `feature/` module owns a vertical slice of functionality: | `feature:connections` | Bluetooth/USB/TCP connection management | | `feature:map` | Map display, waypoints — shared state, policy and the waypoint editor | | `feature:map-maplibre` | MapLibre map surfaces — used by the `fdroid` flavor and Desktop; the `google` flavor uses Google Maps instead. Tile-source definitions and the custom-source editor are in `feature:map`, so both renderers share them | +| `feature:map-terrain` | Offline terrain: elevation decode, hillshade shading and contour lines, shared by both map flavors | | `feature:node` | Node list, node detail, metrics | | `feature:settings` | All configuration screens | | `feature:firmware` | Firmware update flow | @@ -68,7 +69,7 @@ Each `feature/` module owns a vertical slice of functionality: Feature modules: - Use the `meshtastic.kmp.feature` convention plugin -- Depend on `core` modules, never on other `feature` modules +- Depend on `core` modules, not on other `feature` modules; the one exception is `feature:map-maplibre`, which builds on `feature:map` and `feature:map-terrain` - Own their navigation entries and DI registrations - Contain platform-specific implementations in `androidMain`/`jvmMain`/`iosMain` @@ -107,14 +108,22 @@ Each module uses the standard KMP source set hierarchy: ```text src/ -├── commonMain/ ← Shared code (all platforms) -├── commonTest/ ← Shared tests -├── androidMain/ ← Android-specific -├── jvmMain/ ← Desktop JVM-specific -├── iosMain/ ← iOS-specific -└── jvmTest/ ← Desktop test host +├── commonMain/ ← Shared code (all platforms) +├── commonTest/ ← Shared tests +├── androidMain/ ← Android-specific +├── jvmMain/ ← Desktop JVM-specific +├── jvmAndroidMain/ ← Shared by Android and desktop JVM +├── iosMain/ ← iOS-specific +├── nativeMain/ ← Native-target code shared across iOS targets +├── jvmTest/ ← Desktop test host +├── androidHostTest/ ← Android host (JVM) unit tests +└── androidDeviceTest/ ← Instrumented tests (core:database, core:model) ``` +`jvmAndroidMain` exists only in modules that apply `meshtastic.kmp.jvm.android`. `nativeMain`, +`androidHostTest` (modules that call `withHostTest`) and `androidDeviceTest` exist only in the +modules that need them. + **Golden Rules:** - No `android.*` imports in `commonMain` - Platform-specific code goes in appropriate source set diff --git a/docs/en/developer/codebase.md b/docs/en/developer/codebase.md index 7f88252993..c16a3aad59 100644 --- a/docs/en/developer/codebase.md +++ b/docs/en/developer/codebase.md @@ -2,7 +2,7 @@ title: Codebase parent: Developer Guide nav_order: 2 -last_updated: 2026-09-26 +last_updated: 2026-09-28 description: Repository layout, package namespacing, and the Gradle build system — convention plugins, build variants, and key tasks. aliases: - repository-layout @@ -29,6 +29,7 @@ Meshtastic-Android/ │ ├── connections/ │ ├── map/ │ ├── map-maplibre/ +│ ├── map-terrain/ │ ├── node/ │ ├── settings/ │ ├── firmware/ @@ -104,7 +105,7 @@ Located in `build-logic/convention/src/main/kotlin/`. The full set is registered | `meshtastic.kmp.feature` | Standard feature module setup | | `meshtastic.kmp.library` | Shared KMP library module | | `meshtastic.kmp.library.compose` | KMP library that also ships Compose UI | -| `meshtastic.kmp.jvm.android` | JVM + Android target configuration | +| `meshtastic.kmp.jvm.android` | Adds the `jvmAndroidMain` source set shared by the Android and desktop JVM targets | | `meshtastic.koin` | Koin Annotations + K2 compiler plugin | | `meshtastic.kotlinx.serialization` | Serialization plugin setup | | `meshtastic.android.room` | Room KMP setup and schema location | @@ -124,11 +125,11 @@ block rather than assuming a plugin does or does not exist. ### Key Gradle Tasks ```shell -# Compile check across all KMP targets +# Compile check of every KMP module for JVM and iosSimulatorArm64 (excludes :desktopApp) ./gradlew kmpSmokeCompile -# Run all tests -./gradlew allTests +# Run all tests: allTests covers KMP modules, test covers Android/JVM-only modules; run both +./gradlew test allTests # Code quality ./gradlew spotlessCheck detekt diff --git a/docs/en/developer/persistence.md b/docs/en/developer/persistence.md index 603c98451c..64c32d88a5 100644 --- a/docs/en/developer/persistence.md +++ b/docs/en/developer/persistence.md @@ -2,7 +2,7 @@ title: Persistence parent: Developer Guide nav_order: 6 -last_updated: 2026-08-29 +last_updated: 2026-09-28 description: The app's three persistence layers — Room, DataStore, and core:prefs — and when a contributor should use each. aliases: - room @@ -55,6 +55,10 @@ The primary structured data store: | `DiscoveryPresetResultEntity` | Per-preset result within a discovery session | | `DiscoveredNodeEntity` | Nodes found during a discovery preset scan | | `DeviceLinkEntity` | Cached `msh.to` device links from the Meshtastic API | +| `EventFirmwareEditionEntity` | Event-firmware display records cached from the Meshtastic API (`/resource/eventFirmware`) | +| `BootloaderOtaQuirksCacheEntity` | Single-row cache of the nRF52 bootloader/OTA quirk catalog (`/resource/bootloaderOtaQuirks`), stored as one serialized envelope | +| `MaintenanceUf2CacheEntity` | Single-row cache of the maintenance-UF2 manifest (`/resource/maintenanceUf2`), stored as one serialized envelope | +| `MergeMarkerEntity` | Marks a completed `DatabaseMerger` merge so a re-run on the next connection skips it instead of duplicating rows | > ℹ️ **Note:** Waypoints and telemetry are stored within the `Packet` entity (the `port_num` field distinguishes packet types), alongside a `channel` index recording which channel each packet used. Channel *configuration* — names and LoRa settings — lives separately, in `ChannelSetEntity`. diff --git a/docs/en/developer/testing.md b/docs/en/developer/testing.md index ff36aeb454..ffed48873b 100644 --- a/docs/en/developer/testing.md +++ b/docs/en/developer/testing.md @@ -2,7 +2,7 @@ title: Testing parent: Developer Guide nav_order: 7 -last_updated: 2026-09-26 +last_updated: 2026-09-29 description: Testing strategy for the Meshtastic KMP project — test categories, screenshot pipeline, baseline profiles, and CI integration. aliases: - tests @@ -18,7 +18,7 @@ Testing strategy and practices for the Meshtastic KMP project. ### KMP unit tests (`commonTest`) -Shared tests that run on all platforms: +Shared tests written once and run on the JVM and, in modules that declare `withHostTest`, as Android host tests. `commonTest` also compiles for iOS, but iOS test execution is disabled, so no iOS test runs: ```shell ./gradlew allTests @@ -31,10 +31,11 @@ Shared tests that run on all platforms: ### Android host tests -Android-specific tests that run on JVM: +Android-specific tests that run on the JVM. In pure-Android/JVM modules (such as `androidApp`) `test` runs them; in KMP modules that declare `withHostTest {}`, `allTests` runs them through `testAndroidHostTest`: ```shell -./gradlew test +./gradlew test # pure Android/JVM modules +./gradlew allTests # KMP modules ``` - ViewModel tests @@ -126,7 +127,7 @@ feature/my-feature/src/ ### DO -- Write tests in `commonTest` when possible (runs everywhere) +- Write tests in `commonTest` when possible (runs on the JVM, and as an Android host test where the module declares `withHostTest`) - Test business logic independently from UI - Use fakes/stubs instead of mocks where practical - Test edge cases: empty states, error states, boundary values @@ -143,8 +144,8 @@ feature/my-feature/src/ ## Running tests ```shell -# All KMP tests -./gradlew allTests +# All tests: allTests covers KMP modules, test covers Android/JVM-only modules; run both +./gradlew test allTests # Specific module ./gradlew :feature:docs:allTests diff --git a/specs/20260711-153545-message-markdown-styling/quickstart.md b/specs/20260711-153545-message-markdown-styling/quickstart.md index 9043f26685..5c16b6663b 100644 --- a/specs/20260711-153545-message-markdown-styling/quickstart.md +++ b/specs/20260711-153545-message-markdown-styling/quickstart.md @@ -39,8 +39,8 @@ Delegate heavy Gradle to the `gradle-runner` subagent; **git-diff-verify after** Single-module fast loops: ```bash -./gradlew :core:ui:allTests --tests "*InlineMarkdown*" -./gradlew :feature:messaging:allTests --tests "*MessageFormatting*" +./gradlew :core:ui:jvmTest --tests "*InlineMarkdown*" +./gradlew :feature:messaging:jvmTest --tests "*MessageFormatting*" ``` ### Live verification (mandatory — /verify skill)