diff --git a/main/assets/js/search-data.json b/main/assets/js/search-data.json index 9a5a1fe0bc..5a64164101 100644 --- a/main/assets/js/search-data.json +++ b/main/assets/js/search-data.json @@ -71,7 +71,7 @@ },"10": { "doc": "Adding a Feature Module", "title": "Checklist", - "content": ". | Module directory created | build.gradle.kts with correct plugins and dependencies | Added to settings.gradle.kts | DI module created with @ComponentScan | DI module registered in app and desktop roots | Routes added to Routes.kt | Navigation entries registered | ./gradlew kmpSmokeCompile passes | ./gradlew :feature:my-feature:allTests passes | . ", + "content": ". | 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 | Navigation entries registered | ./gradlew kmpSmokeCompile passes | ./gradlew :feature:my-feature:allTests passes | . ", "url": "/Meshtastic-Android/main/en/developer/adding-a-feature-module.html#checklist", "relUrl": "/en/developer/adding-a-feature-module.html#checklist" @@ -1240,14 +1240,14 @@ },"177": { "doc": "Architecture", "title": "Module Categories", - "content": "androidApp/ — Android Application . The Android application entry point: . | Activity, Application, and Manifest definitions | Koin DI module composition (AppKoinModule) | Flavor-specific bindings (google/, fdroid/) | Android-only integrations (widgets, services) | . desktopApp/ — Desktop JVM Application . The Desktop (Linux/macOS/Windows) entry point: . | Compose Desktop window management | Desktop-specific DI (DesktopKoinModule) | Platform stubs for Android-only capabilities | DesktopRadioTransportFactory and a jSerialComm-based serial transport; the BLE and TCP transport implementations it wires up are shared code — they live in core:network, built on core:ble’s BLE primitives — not desktopApp-owned | . feature/* — Feature Modules . Each feature/ module owns a vertical slice of functionality: . | Module | Responsibility | . | feature:intro | Onboarding/welcome flow | . | feature:messaging | Messages, channels, contacts, quick chat | . | 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:node | Node list, node detail, metrics | . | feature:settings | All configuration screens | . | feature:firmware | Firmware update flow | . | feature:docs | In-app documentation browser | . | feature:wifi-provision | Wi-Fi provisioning | . | feature:widget | Android home screen widgets | . | feature:discovery | Mesh network discovery | . Feature modules: . | Use the meshtastic.kmp.feature convention plugin | Depend on core modules, never on other feature modules | Own their navigation entries and DI registrations | Contain platform-specific implementations in androidMain/jvmMain/iosMain | . core/* — Core Modules . Shared infrastructure used by all features: . | Module | Responsibility | . | core:common | Utilities, extensions, build config | . | core:navigation | Routes, deep links, Navigation 3 | . | core:ui | Shared Compose components, icons, theme | . | core:resources | Shared string resources | . | core:model | Domain models | . | core:data | Data layer abstractions | . | core:domain | Use cases / business logic | . | core:database | Room KMP database | . | core:datastore | DataStore preferences | . | core:prefs | App preferences | . | core:repository | Repository interfaces | . | core:service | Mesh service layer | . | core:di | DI utilities | . | core:network | HTTP/serial/transport | . | core:ble | Bluetooth LE abstractions | . | core:barcode | QR / barcode scanning (channel-share QR codes) | . | core:nfc | NFC read/write support | . | core:takserver | Embedded TAK server integration | . | core:testing | Test utilities | . | core:konsist | Konsist architecture/convention tests | . Protobuf models come from the external org.meshtastic:protobufs Maven artifact (pinned in gradle/libs.versions.toml). ", + "content": "androidApp/ — Android Application . The Android application entry point: . | Activity, Application, and Manifest definitions | Koin DI module composition (AppKoinModule) | Flavor-specific bindings (google/, fdroid/) | Android-only integrations (widgets, services) | . desktopApp/ — Desktop JVM Application . The Desktop (Linux/macOS/Windows) entry point: . | Compose Desktop window management | Desktop-specific DI (DesktopKoinModule) | Platform stubs for Android-only capabilities | DesktopRadioTransportFactory and a jSerialComm-based serial transport; the BLE and TCP transport implementations it wires up are shared code — they live in core:network, built on core:ble’s BLE primitives — not desktopApp-owned | . feature/* — Feature Modules . Each feature/ module owns a vertical slice of functionality: . | Module | Responsibility | . | feature:intro | Onboarding/welcome flow | . | feature:messaging | Messages, channels, contacts, quick chat | . | 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 | . | feature:docs | In-app documentation browser | . | feature:wifi-provision | Wi-Fi provisioning | . | feature:widget | Android home screen widgets | . | feature:discovery | Mesh network discovery | . Feature modules: . | Use the meshtastic.kmp.feature convention plugin | 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 | . core/* — Core Modules . Shared infrastructure used by all features: . | Module | Responsibility | . | core:common | Utilities, extensions, build config | . | core:navigation | Routes, deep links, Navigation 3 | . | core:ui | Shared Compose components, icons, theme | . | core:resources | Shared string resources | . | core:model | Domain models | . | core:data | Data layer abstractions | . | core:domain | Use cases / business logic | . | core:database | Room KMP database | . | core:datastore | DataStore preferences | . | core:prefs | App preferences | . | core:repository | Repository interfaces | . | core:service | Mesh service layer | . | core:di | DI utilities | . | core:network | HTTP/serial/transport | . | core:ble | Bluetooth LE abstractions | . | core:barcode | QR / barcode scanning (channel-share QR codes) | . | core:nfc | NFC read/write support | . | core:takserver | Embedded TAK server integration | . | core:testing | Test utilities | . | core:konsist | Konsist architecture/convention tests | . Protobuf models come from the external org.meshtastic:protobufs Maven artifact (pinned in gradle/libs.versions.toml). ", "url": "/Meshtastic-Android/main/en/developer/architecture.html#module-categories", "relUrl": "/en/developer/architecture.html#module-categories" },"178": { "doc": "Architecture", "title": "KMP Source Sets", - "content": "Each module uses the standard KMP source set hierarchy: . src/ ├── commonMain/ ← Shared code (all platforms) ├── commonTest/ ← Shared tests ├── androidMain/ ← Android-specific ├── jvmMain/ ← Desktop JVM-specific ├── iosMain/ ← iOS-specific └── jvmTest/ ← Desktop test host . Golden Rules: . | No android.* imports in commonMain | Platform-specific code goes in appropriate source set | Prefer interfaces + DI over expect/actual for complex behaviors | Use expect/actual only for simple declarations | . ", + "content": "Each module uses the standard KMP source set hierarchy: . src/ ├── 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 | Prefer interfaces + DI over expect/actual for complex behaviors | Use expect/actual only for simple declarations | . ", "url": "/Meshtastic-Android/main/en/developer/architecture.html#kmp-source-sets", "relUrl": "/en/developer/architecture.html#kmp-source-sets" @@ -1289,7 +1289,7 @@ },"184": { "doc": "Codebase", "title": "Repository Structure", - "content": "Meshtastic-Android/ ├── androidApp/ # Android application module │ ├── src/main/ # Shared Android code │ ├── src/google/ # Google Play flavor (proprietary Google integrations — Gemini, Maps, Play services) │ └── src/fdroid/ # F-Droid flavor (FOSS-only) ├── desktopApp/ # Desktop JVM application ├── feature/ # Feature modules (KMP) │ ├── intro/ │ ├── messaging/ │ ├── connections/ │ ├── map/ │ ├── map-maplibre/ │ ├── node/ │ ├── settings/ │ ├── firmware/ │ ├── docs/ │ ├── wifi-provision/ │ ├── widget/ │ └── discovery/ ├── core/ # Core infrastructure modules (KMP) │ ├── barcode/ │ ├── ble/ │ ├── common/ │ ├── data/ │ ├── database/ │ ├── datastore/ │ ├── di/ │ ├── domain/ │ ├── konsist/ │ ├── model/ │ ├── navigation/ │ ├── network/ │ ├── nfc/ │ ├── prefs/ │ ├── repository/ │ ├── resources/ │ ├── service/ │ ├── takserver/ │ ├── testing/ │ └── ui/ ├── baselineprofile/ # Baseline Profile generation for :androidApp ├── screenshot-tests/ # Compose Preview screenshot tests (visual-regression gate) ├── docs-screenshots/ # Doc-framed composition screenshots (generate-only, not CI-gated) ├── build-logic/ # Convention plugins and build helpers │ └── convention/ ├── docs/ # Documentation source (markdown) │ └── en/ # English source; other locales live under docs/<locale>/user/ │ ├── user/ │ └── developer/ ├── gradle/ # Gradle wrapper and version catalog │ └── libs.versions.toml ├── specs/ # Feature specifications └── .github/workflows/ # CI/CD workflows . ", + "content": "Meshtastic-Android/ ├── androidApp/ # Android application module │ ├── src/main/ # Shared Android code │ ├── src/google/ # Google Play flavor (proprietary Google integrations — Gemini, Maps, Play services) │ └── src/fdroid/ # F-Droid flavor (FOSS-only) ├── desktopApp/ # Desktop JVM application ├── feature/ # Feature modules (KMP) │ ├── intro/ │ ├── messaging/ │ ├── connections/ │ ├── map/ │ ├── map-maplibre/ │ ├── map-terrain/ │ ├── node/ │ ├── settings/ │ ├── firmware/ │ ├── docs/ │ ├── wifi-provision/ │ ├── widget/ │ └── discovery/ ├── core/ # Core infrastructure modules (KMP) │ ├── barcode/ │ ├── ble/ │ ├── common/ │ ├── data/ │ ├── database/ │ ├── datastore/ │ ├── di/ │ ├── domain/ │ ├── konsist/ │ ├── model/ │ ├── navigation/ │ ├── network/ │ ├── nfc/ │ ├── prefs/ │ ├── repository/ │ ├── resources/ │ ├── service/ │ ├── takserver/ │ ├── testing/ │ └── ui/ ├── baselineprofile/ # Baseline Profile generation for :androidApp ├── screenshot-tests/ # Compose Preview screenshot tests (visual-regression gate) ├── docs-screenshots/ # Doc-framed composition screenshots (generate-only, not CI-gated) ├── build-logic/ # Convention plugins and build helpers │ └── convention/ ├── docs/ # Documentation source (markdown) │ └── en/ # English source; other locales live under docs/<locale>/user/ │ ├── user/ │ └── developer/ ├── gradle/ # Gradle wrapper and version catalog │ └── libs.versions.toml ├── specs/ # Feature specifications └── .github/workflows/ # CI/CD workflows . ", "url": "/Meshtastic-Android/main/en/developer/codebase.html#repository-structure", "relUrl": "/en/developer/codebase.html#repository-structure" @@ -1303,7 +1303,7 @@ },"186": { "doc": "Codebase", "title": "Build System", - "content": "Gradle Kotlin DSL . All build files use Kotlin DSL (.gradle.kts). Configuration: . | Version catalog: gradle/libs.versions.toml | Convention plugins: build-logic/convention/ | Settings: settings.gradle.kts | . Convention Plugins . Located in build-logic/convention/src/main/kotlin/. The full set is registered in build-logic/convention/build.gradle.kts; these are the ones a module build applies most often: . | Plugin | Purpose | . | 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.koin | Koin Annotations + K2 compiler plugin | . | meshtastic.kotlinx.serialization | Serialization plugin setup | . | meshtastic.android.room | Room KMP setup and schema location | . | meshtastic.android.screenshot | Compose Preview screenshot testing | . The rest cover the application and library variants, lint, detekt, spotless, Dokka, Kover, AboutLibraries, analytics, secrets, the docs tasks and the root aggregate — read the register(…) block rather than assuming a plugin does or does not exist. Build Variants (Android) . | Flavor | Description | . | google | Google Play distribution; includes proprietary APIs | . | fdroid | F-Droid distribution; FOSS-only dependencies | . Key Gradle Tasks . # Compile check across all KMP targets ./gradlew kmpSmokeCompile # Run all tests ./gradlew allTests # Code quality ./gradlew spotlessCheck detekt # Android build ./gradlew assembleGoogleDebug assembleFdroidDebug # Desktop run ./gradlew :desktopApp:run # Desktop native installers for the current OS (DMG / MSI+EXE / DEB+RPM+AppImage) ./gradlew :desktopApp:packageReleaseDistributionForCurrentOS # API reference (Dokka HTML → build/dokka/html) ./gradlew :dokkaGeneratePublicationHtml . ", + "content": "Gradle Kotlin DSL . All build files use Kotlin DSL (.gradle.kts). Configuration: . | Version catalog: gradle/libs.versions.toml | Convention plugins: build-logic/convention/ | Settings: settings.gradle.kts | . Convention Plugins . Located in build-logic/convention/src/main/kotlin/. The full set is registered in build-logic/convention/build.gradle.kts; these are the ones a module build applies most often: . | Plugin | Purpose | . | 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 | 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 | . | meshtastic.android.screenshot | Compose Preview screenshot testing | . The rest cover the application and library variants, lint, detekt, spotless, Dokka, Kover, AboutLibraries, analytics, secrets, the docs tasks and the root aggregate — read the register(…) block rather than assuming a plugin does or does not exist. Build Variants (Android) . | Flavor | Description | . | google | Google Play distribution; includes proprietary APIs | . | fdroid | F-Droid distribution; FOSS-only dependencies | . Key Gradle Tasks . # Compile check of every KMP module for JVM and iosSimulatorArm64 (excludes :desktopApp) ./gradlew kmpSmokeCompile # Run all tests: allTests covers KMP modules, test covers Android/JVM-only modules; run both ./gradlew test allTests # Code quality ./gradlew spotlessCheck detekt # Android build ./gradlew assembleGoogleDebug assembleFdroidDebug # Desktop run ./gradlew :desktopApp:run # Desktop native installers for the current OS (DMG / MSI+EXE / DEB+RPM+AppImage) ./gradlew :desktopApp:packageReleaseDistributionForCurrentOS # API reference (Dokka HTML → build/dokka/html) ./gradlew :dokkaGeneratePublicationHtml . ", "url": "/Meshtastic-Android/main/en/developer/codebase.html#build-system", "relUrl": "/en/developer/codebase.html#build-system" @@ -31354,7 +31354,7 @@ },"4479": { "doc": "Persistence", "title": "Room KMP Database", - "content": "Module: core:database . The primary structured data store: . | Node information and history | Message history | Waypoints | Telemetry data | Channel set configuration (channel names and LoRa config) | . Key Points . | Uses Room KMP for cross-platform compatibility | Migrations managed through Room’s built-in migration system | DAO interfaces live in core:database | Repository layer in core:repository provides the public API | Full-text message search is backed by an FTS5 content table (PacketFts) over Packet, kept in sync by Room-managed triggers | . What’s Stored in Room . | Entity | Description | . | NodeEntity | All known mesh nodes and their metadata | . | MyNodeEntity | The local node’s own info | . | Packet | Message history (channel and direct), waypoints, and telemetry data | . | PacketFts | FTS5 virtual table mirroring Packet.messageText for full-text message search (Room-managed INSERT/UPDATE/DELETE triggers keep it in sync) | . | ContactSettings | Per-contact mute and read-state | . | ReactionEntity | Emoji reactions on messages | . | MeshLog | Raw mesh protocol logs | . | MetadataEntity | Device metadata (firmware version, hardware model) | . | ChannelSetEntity | The connected radio’s channel set — channel names and LoRa config — one row per device | . | QuickChatAction | User-configured quick-chat messages | . | DeviceHardwareEntity | Cached device hardware catalog | . | FirmwareReleaseEntity | Cached firmware release info | . | TracerouteNodePositionEntity | Traceroute hop position data | . | DiscoverySessionEntity | A Local Mesh Discovery scan session (timestamp, presets scanned, home preset) | . | 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 | . ℹ️ 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. ", + "content": "Module: core:database . The primary structured data store: . | Node information and history | Message history | Waypoints | Telemetry data | Channel set configuration (channel names and LoRa config) | . Key Points . | Uses Room KMP for cross-platform compatibility | Migrations managed through Room’s built-in migration system | DAO interfaces live in core:database | Repository layer in core:repository provides the public API | Full-text message search is backed by an FTS5 content table (PacketFts) over Packet, kept in sync by Room-managed triggers | . What’s Stored in Room . | Entity | Description | . | NodeEntity | All known mesh nodes and their metadata | . | MyNodeEntity | The local node’s own info | . | Packet | Message history (channel and direct), waypoints, and telemetry data | . | PacketFts | FTS5 virtual table mirroring Packet.messageText for full-text message search (Room-managed INSERT/UPDATE/DELETE triggers keep it in sync) | . | ContactSettings | Per-contact mute and read-state | . | ReactionEntity | Emoji reactions on messages | . | MeshLog | Raw mesh protocol logs | . | MetadataEntity | Device metadata (firmware version, hardware model) | . | ChannelSetEntity | The connected radio’s channel set — channel names and LoRa config — one row per device | . | QuickChatAction | User-configured quick-chat messages | . | DeviceHardwareEntity | Cached device hardware catalog | . | FirmwareReleaseEntity | Cached firmware release info | . | TracerouteNodePositionEntity | Traceroute hop position data | . | DiscoverySessionEntity | A Local Mesh Discovery scan session (timestamp, presets scanned, home preset) | . | 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. ", "url": "/Meshtastic-Android/main/en/developer/persistence.html#room-kmp-database", "relUrl": "/en/developer/persistence.html#room-kmp-database" @@ -42001,7 +42001,7 @@ },"6000": { "doc": "Testing", "title": "Test categories", - "content": "KMP unit tests (commonTest) . Shared tests that run on all platforms: ./gradlew allTests . | Business logic tests | Data model validation | Search/ranking algorithm tests | Route serialization tests | . Android host tests . Android-specific tests that run on JVM: ./gradlew test . | ViewModel tests | Repository tests with Room fakes | Android-specific integration tests | . Compose UI tests . Compose Multiplatform UI test framework: . @Test fun myScreenTest() = runComposeUiTest { setContent { MyScreen() } onNodeWithText(\"Expected\").assertIsDisplayed() } . Located in commonTest or jvmTest source sets. Screenshot tests . Uses Android Gradle Plugin’s native (layoutlib) screenshot testing framework, split across two modules: . | :screenshot-tests — the visual-regression gate. CI runs validateDebugScreenshotTest on it; reframing one of these baselines is a real diff to review. Holds atomic, dual-purpose components. | :docs-screenshots — generate-only, not validated in CI. Holds doc-framed compositions whose framing is tuned for the docs site, so reframing a doc image never churns the regression gate. | ./gradlew :screenshot-tests:updateDebugScreenshotTest # record regression goldens ./gradlew :screenshot-tests:validateDebugScreenshotTest # compare against goldens (CI gate) ./gradlew :docs-screenshots:updateDebugScreenshotTest # record doc-framed composition images ./gradlew :screenshot-tests:copyDocsScreenshots # copy doc images from BOTH modules into docs/assets . Rendering is host-deterministic here (layoutlib): a local update produces references byte-identical to CI, so locally-recorded goldens pass validate. See docs/assets/screenshots/README.md for which module a new screenshot belongs in. Store screenshots . The store-listing screenshots (Play, F-Droid, IzzyOnDroid, and the desktop app’s Flathub listing) are taken from the real apps, connected to Demo Mode’s hidden showcase mesh (/connections?address=mshowcase, MockScenario.SHOWCASE in :core:network), rather than drawn. Every screen is reached by its deep link, so the flow does not depend on the display language, and each shot is kept once the window has stopped changing. | Android: :store-screenshots, a UiAutomator 2.4 test module targeting :androidApp. For each surface fastlane supply uploads it sets the display size and density, relaunches the debug app through its shell-only AutomationLauncher alias with skip_onboarding and skip_connect_confirm, and saves the five listing shots, full screen with a SystemUI demo-mode status bar, to /data/local/tmp/store-screenshots/<flavor> on the device. | Desktop: store-screenshots/capture-desktop.sh runs the real desktop debug build on an Xvfb display, one launch per screen with that screen’s deep link, and saves the five Flathub shots. The map needs Skiko’s OpenGL renderer and Skiko refuses any GL adapter named llvmpipe or virgl, so Mesa runs GL through zink over lavapipe. | . On an emulator or device, one flavor at a time: ./gradlew :store-screenshots:connectedFdroidDebugAndroidTest adb pull /data/local/tmp/store-screenshots/fdroid/. fastlane/metadata/android/en-US/images/ . | Folder | Size | Window | Uploaded by | . | phoneScreenshots/ | 1080×1920 @400 dpi | compact: bottom navigation bar | fastlane supply | . | sevenInchScreenshots/ | 1080×1920 @288 dpi | medium: navigation rail, one pane | fastlane supply | . | tenInchScreenshots/ | 2560×1440 @320 dpi | expanded: rail, list beside detail | fastlane supply | . | desktopApp/packaging/linux/screenshots/ | 1280×800 | expanded: rail, list beside detail | Flathub, through the release assets metainfo.xml names | .github/workflows/store-screenshots.yml runs both on hosted runners, with both Android flavors in one job on one emulator (google for the Play listing, fdroid for the committed tree), on every internal release, on demand, and on pull requests that touch the renderer or the showcase mesh. The release pipeline attaches the captures to the release, publishes the Play listing from them on production, and opens a self-merging PR that writes the fdroid and desktop sets back here (RELEASE_PROCESS.md). Baseline Profile / startup performance . The :baselineprofile module (#5735) generates a Baseline Profile for :androidApp, AOT-compiling the hot startup paths so ART doesn’t pay the JIT cost on first launch. It targets the google flavor (the variant most users run). The Macrobenchmark generator (BaselineProfileGenerator) and the before/after benchmark (StartupBenchmark) live in baselineprofile/src/main/kotlin/org/meshtastic/baselineprofile/. Both run on a device/emulator: ./gradlew :androidApp:generateGoogleReleaseBaselineProfile # Generate the profile (commit the output) ./gradlew :androidApp:benchmarkGoogleReleaseBaselineProfile # Quantify the cold-start win . The generated profile is merged into androidApp/src/googleRelease/generated/baselineProfiles/ and packaged into release builds via androidx.profileinstaller. ℹ️ Note: The journey covers cold start only (launch → first frame), because CI has no paired node. Post-connection screens (node list, map, message thread) aren’t yet AOT-compiled. Extending the journey past cold start needs a fake transport or a connected node wired into the harness. ", + "content": "KMP unit tests (commonTest) . 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: ./gradlew allTests . | Business logic tests | Data model validation | Search/ranking algorithm tests | Route serialization tests | . Android host tests . 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: ./gradlew test # pure Android/JVM modules ./gradlew allTests # KMP modules . | ViewModel tests | Repository tests with Room fakes | Android-specific integration tests | . Compose UI tests . Compose Multiplatform UI test framework: . @Test fun myScreenTest() = runComposeUiTest { setContent { MyScreen() } onNodeWithText(\"Expected\").assertIsDisplayed() } . Located in commonTest or jvmTest source sets. Screenshot tests . Uses Android Gradle Plugin’s native (layoutlib) screenshot testing framework, split across two modules: . | :screenshot-tests — the visual-regression gate. CI runs validateDebugScreenshotTest on it; reframing one of these baselines is a real diff to review. Holds atomic, dual-purpose components. | :docs-screenshots — generate-only, not validated in CI. Holds doc-framed compositions whose framing is tuned for the docs site, so reframing a doc image never churns the regression gate. | ./gradlew :screenshot-tests:updateDebugScreenshotTest # record regression goldens ./gradlew :screenshot-tests:validateDebugScreenshotTest # compare against goldens (CI gate) ./gradlew :docs-screenshots:updateDebugScreenshotTest # record doc-framed composition images ./gradlew :screenshot-tests:copyDocsScreenshots # copy doc images from BOTH modules into docs/assets . Rendering is host-deterministic here (layoutlib): a local update produces references byte-identical to CI, so locally-recorded goldens pass validate. See docs/assets/screenshots/README.md for which module a new screenshot belongs in. Store screenshots . The store-listing screenshots (Play, F-Droid, IzzyOnDroid, and the desktop app’s Flathub listing) are taken from the real apps, connected to Demo Mode’s hidden showcase mesh (/connections?address=mshowcase, MockScenario.SHOWCASE in :core:network), rather than drawn. Every screen is reached by its deep link, so the flow does not depend on the display language, and each shot is kept once the window has stopped changing. | Android: :store-screenshots, a UiAutomator 2.4 test module targeting :androidApp. For each surface fastlane supply uploads it sets the display size and density, relaunches the debug app through its shell-only AutomationLauncher alias with skip_onboarding and skip_connect_confirm, and saves the five listing shots, full screen with a SystemUI demo-mode status bar, to /data/local/tmp/store-screenshots/<flavor> on the device. | Desktop: store-screenshots/capture-desktop.sh runs the real desktop debug build on an Xvfb display, one launch per screen with that screen’s deep link, and saves the five Flathub shots. The map needs Skiko’s OpenGL renderer and Skiko refuses any GL adapter named llvmpipe or virgl, so Mesa runs GL through zink over lavapipe. | . On an emulator or device, one flavor at a time: ./gradlew :store-screenshots:connectedFdroidDebugAndroidTest adb pull /data/local/tmp/store-screenshots/fdroid/. fastlane/metadata/android/en-US/images/ . | Folder | Size | Window | Uploaded by | . | phoneScreenshots/ | 1080×1920 @400 dpi | compact: bottom navigation bar | fastlane supply | . | sevenInchScreenshots/ | 1080×1920 @288 dpi | medium: navigation rail, one pane | fastlane supply | . | tenInchScreenshots/ | 2560×1440 @320 dpi | expanded: rail, list beside detail | fastlane supply | . | desktopApp/packaging/linux/screenshots/ | 1280×800 | expanded: rail, list beside detail | Flathub, through the release assets metainfo.xml names | .github/workflows/store-screenshots.yml runs both on hosted runners, with both Android flavors in one job on one emulator (google for the Play listing, fdroid for the committed tree), on every internal release, on demand, and on pull requests that touch the renderer or the showcase mesh. The release pipeline attaches the captures to the release, publishes the Play listing from them on production, and opens a self-merging PR that writes the fdroid and desktop sets back here (RELEASE_PROCESS.md). Baseline Profile / startup performance . The :baselineprofile module (#5735) generates a Baseline Profile for :androidApp, AOT-compiling the hot startup paths so ART doesn’t pay the JIT cost on first launch. It targets the google flavor (the variant most users run). The Macrobenchmark generator (BaselineProfileGenerator) and the before/after benchmark (StartupBenchmark) live in baselineprofile/src/main/kotlin/org/meshtastic/baselineprofile/. Both run on a device/emulator: ./gradlew :androidApp:generateGoogleReleaseBaselineProfile # Generate the profile (commit the output) ./gradlew :androidApp:benchmarkGoogleReleaseBaselineProfile # Quantify the cold-start win . The generated profile is merged into androidApp/src/googleRelease/generated/baselineProfiles/ and packaged into release builds via androidx.profileinstaller. ℹ️ Note: The journey covers cold start only (launch → first frame), because CI has no paired node. Post-connection screens (node list, map, message thread) aren’t yet AOT-compiled. Extending the journey past cold start needs a fake transport or a connected node wired into the harness. ", "url": "/Meshtastic-Android/main/en/developer/testing.html#test-categories", "relUrl": "/en/developer/testing.html#test-categories" @@ -42015,14 +42015,14 @@ },"6002": { "doc": "Testing", "title": "Testing guidelines", - "content": "DO . | Write tests in commonTest when possible (runs everywhere) | Test business logic independently from UI | Use fakes/stubs instead of mocks where practical | Test edge cases: empty states, error states, boundary values | Test deep link routing in DeepLinkRouterTest | Keep tests fast — no network, no disk I/O in unit tests | . DON’T . | Don’t test framework behavior (Compose internals, Room queries) | Don’t create tests that depend on other feature modules | Don’t use Thread.sleep — use coroutine test dispatchers | Don’t rely on test execution order | . ", + "content": "DO . | 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 | Test deep link routing in DeepLinkRouterTest | Keep tests fast — no network, no disk I/O in unit tests | . DON’T . | Don’t test framework behavior (Compose internals, Room queries) | Don’t create tests that depend on other feature modules | Don’t use Thread.sleep — use coroutine test dispatchers | Don’t rely on test execution order | . ", "url": "/Meshtastic-Android/main/en/developer/testing.html#testing-guidelines", "relUrl": "/en/developer/testing.html#testing-guidelines" },"6003": { "doc": "Testing", "title": "Running tests", - "content": "# All KMP tests ./gradlew allTests # Specific module ./gradlew :feature:docs:allTests # Code quality ./gradlew spotlessCheck detekt # Full verification ./gradlew spotlessCheck detekt kmpSmokeCompile test allTests . ", + "content": "# All tests: allTests covers KMP modules, test covers Android/JVM-only modules; run both ./gradlew test allTests # Specific module ./gradlew :feature:docs:allTests # Code quality ./gradlew spotlessCheck detekt # Full verification ./gradlew spotlessCheck detekt kmpSmokeCompile test allTests . ", "url": "/Meshtastic-Android/main/en/developer/testing.html#running-tests", "relUrl": "/en/developer/testing.html#running-tests" diff --git a/main/en/developer/adding-a-feature-module.html b/main/en/developer/adding-a-feature-module.html index 36c03fbbe8..0d3f8c7118 100644 --- a/main/en/developer/adding-a-feature-module.html +++ b/main/en/developer/adding-a-feature-module.html @@ -56,4 +56,4 @@ MyFeatureScreen(onNavigateUp = { backStack.removeLastOrNull() }) } } -

Both the graph sentinel (MyFeatureRoute.MyFeatureGraph) and the primary screen (MyFeatureRoute.MyFeatureHome) navigate to the same composable, so the feature is reachable via either a top-level push or a deep-link graph push — the same pattern feature:wifi-provision and feature:firmware use.

Then wire it up: call myFeatureGraph(backStack) from the shared entryProvider<NavKey> { } block in androidApp’s Main.kt and desktopApp’s DesktopNavigation.kt, alongside the other features’ entries functions. See Navigation Entry Registration.

Source Set Guidelines

Source Set Contains
commonMain Models, ViewModels, shared UI, DI module, navigation
androidMain Android-specific implementations (e.g., platform APIs)
jvmMain Desktop-specific implementations
iosMain iOS-specific implementations
commonTest Shared unit tests

Testing Expectations

Every feature module should have:

Checklist


+

Both the graph sentinel (MyFeatureRoute.MyFeatureGraph) and the primary screen (MyFeatureRoute.MyFeatureHome) navigate to the same composable, so the feature is reachable via either a top-level push or a deep-link graph push — the same pattern feature:wifi-provision and feature:firmware use.

Then wire it up: call myFeatureGraph(backStack) from the shared entryProvider<NavKey> { } block in androidApp’s Main.kt and desktopApp’s DesktopNavigation.kt, alongside the other features’ entries functions. See Navigation Entry Registration.

Source Set Guidelines

Source Set Contains
commonMain Models, ViewModels, shared UI, DI module, navigation
androidMain Android-specific implementations (e.g., platform APIs)
jvmMain Desktop-specific implementations
iosMain iOS-specific implementations
commonTest Shared unit tests

Testing Expectations

Every feature module should have:

Checklist


diff --git a/main/en/developer/architecture.html b/main/en/developer/architecture.html index c1e428cbf0..dca8e0f691 100644 --- a/main/en/developer/architecture.html +++ b/main/en/developer/architecture.html @@ -7,11 +7,15 @@ ├─────────────────────────────────────────────┤ │ Platform (Android/JVM/iOS) │ OS-specific bindings └─────────────────────────────────────────────┘ -

Module Categories

androidApp/ — Android Application

The Android application entry point:

desktopApp/ — Desktop JVM Application

The Desktop (Linux/macOS/Windows) entry point:

feature/* — Feature Modules

Each feature/ module owns a vertical slice of functionality:

Module Responsibility
feature:intro Onboarding/welcome flow
feature:messaging Messages, channels, contacts, quick chat
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:node Node list, node detail, metrics
feature:settings All configuration screens
feature:firmware Firmware update flow
feature:docs In-app documentation browser
feature:wifi-provision Wi-Fi provisioning
feature:widget Android home screen widgets
feature:discovery Mesh network discovery

Feature modules:

core/* — Core Modules

Shared infrastructure used by all features:

Module Responsibility
core:common Utilities, extensions, build config
core:navigation Routes, deep links, Navigation 3
core:ui Shared Compose components, icons, theme
core:resources Shared string resources
core:model Domain models
core:data Data layer abstractions
core:domain Use cases / business logic
core:database Room KMP database
core:datastore DataStore preferences
core:prefs App preferences
core:repository Repository interfaces
core:service Mesh service layer
core:di DI utilities
core:network HTTP/serial/transport
core:ble Bluetooth LE abstractions
core:barcode QR / barcode scanning (channel-share QR codes)
core:nfc NFC read/write support
core:takserver Embedded TAK server integration
core:testing Test utilities
core:konsist Konsist architecture/convention tests

Protobuf models come from the external org.meshtastic:protobufs Maven artifact (pinned in gradle/libs.versions.toml).

KMP Source Sets

Each module uses the standard KMP source set hierarchy:

src/
-├── commonMain/     ← Shared code (all platforms)
-├── commonTest/     ← Shared tests
-├── androidMain/    ← Android-specific
-├── jvmMain/        ← Desktop JVM-specific
-├── iosMain/        ← iOS-specific
-└── jvmTest/        ← Desktop test host
-

Golden Rules:

Dependency Injection

The project uses Koin with annotation processing:

Radio Control

Features issue radio commands through RadioController (core:repository), a composite of four focused sub-interfaces so callers can depend on just the slice they need:

Sub-interface Responsibility
AdminController Config, channels, owner, device lifecycle, editSettings { } transactions
MessagingController Send packets, reactions, shared contacts
NodeController Favorite, ignore, mute, remove nodes
QueryController Telemetry, traceroute, position/user-info queries

RadioControllerImpl (core:service) is the in-process composition root for all targets (Desktop, iOS, single-process Android). It assembles the four sub-controllers via Kotlin interface delegation and adds the cross-cutting concerns (connection state, packet-id, location, device-address switching). Commands are direct suspend calls; admin writes are fire-and-forget because the device is the source of truth (local persistence is an optimistic cache). The layered shape mirrors the meshtastic-sdk AdminApi/TelemetryApi design to ease a future SDK migration.

Service Repository

ServiceRepository is the reactive bridge between the mesh service and all feature/UI layers. It is decomposed into focused provider interfaces following the Interface Segregation Principle:

Interface Responsibility
ConnectionStateProvider Read-only connectionState: StateFlow<ConnectionState>
TracerouteResponseProvider Traceroute response state + clear
NeighborInfoResponseProvider Neighbor info response state + clear
ServiceStateWriter Write-side for handlers (set, emit, clear*)

ServiceRepository extends all four interfaces — consumers inject the narrowest interface they actually need. For example, ContactsViewModel injects only ConnectionStateProvider rather than the entire ServiceRepository, preventing accidental access to write operations from UI code. RadioController also extends ConnectionStateProvider so VMs that already inject a controller sub-interface can read connection state without a separate dependency.

Navigation uses Navigation 3 with typed routes:

See Navigation & Deep Links for details.


+

Module Categories

androidApp/ — Android Application

The Android application entry point:

desktopApp/ — Desktop JVM Application

The Desktop (Linux/macOS/Windows) entry point:

feature/* — Feature Modules

Each feature/ module owns a vertical slice of functionality:

Module Responsibility
feature:intro Onboarding/welcome flow
feature:messaging Messages, channels, contacts, quick chat
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
feature:docs In-app documentation browser
feature:wifi-provision Wi-Fi provisioning
feature:widget Android home screen widgets
feature:discovery Mesh network discovery

Feature modules:

core/* — Core Modules

Shared infrastructure used by all features:

Module Responsibility
core:common Utilities, extensions, build config
core:navigation Routes, deep links, Navigation 3
core:ui Shared Compose components, icons, theme
core:resources Shared string resources
core:model Domain models
core:data Data layer abstractions
core:domain Use cases / business logic
core:database Room KMP database
core:datastore DataStore preferences
core:prefs App preferences
core:repository Repository interfaces
core:service Mesh service layer
core:di DI utilities
core:network HTTP/serial/transport
core:ble Bluetooth LE abstractions
core:barcode QR / barcode scanning (channel-share QR codes)
core:nfc NFC read/write support
core:takserver Embedded TAK server integration
core:testing Test utilities
core:konsist Konsist architecture/convention tests

Protobuf models come from the external org.meshtastic:protobufs Maven artifact (pinned in gradle/libs.versions.toml).

KMP Source Sets

Each module uses the standard KMP source set hierarchy:

src/
+├── 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:

Dependency Injection

The project uses Koin with annotation processing:

Radio Control

Features issue radio commands through RadioController (core:repository), a composite of four focused sub-interfaces so callers can depend on just the slice they need:

Sub-interface Responsibility
AdminController Config, channels, owner, device lifecycle, editSettings { } transactions
MessagingController Send packets, reactions, shared contacts
NodeController Favorite, ignore, mute, remove nodes
QueryController Telemetry, traceroute, position/user-info queries

RadioControllerImpl (core:service) is the in-process composition root for all targets (Desktop, iOS, single-process Android). It assembles the four sub-controllers via Kotlin interface delegation and adds the cross-cutting concerns (connection state, packet-id, location, device-address switching). Commands are direct suspend calls; admin writes are fire-and-forget because the device is the source of truth (local persistence is an optimistic cache). The layered shape mirrors the meshtastic-sdk AdminApi/TelemetryApi design to ease a future SDK migration.

Service Repository

ServiceRepository is the reactive bridge between the mesh service and all feature/UI layers. It is decomposed into focused provider interfaces following the Interface Segregation Principle:

Interface Responsibility
ConnectionStateProvider Read-only connectionState: StateFlow<ConnectionState>
TracerouteResponseProvider Traceroute response state + clear
NeighborInfoResponseProvider Neighbor info response state + clear
ServiceStateWriter Write-side for handlers (set, emit, clear*)

ServiceRepository extends all four interfaces — consumers inject the narrowest interface they actually need. For example, ContactsViewModel injects only ConnectionStateProvider rather than the entire ServiceRepository, preventing accidental access to write operations from UI code. RadioController also extends ConnectionStateProvider so VMs that already inject a controller sub-interface can read connection state without a separate dependency.

Navigation uses Navigation 3 with typed routes:

See Navigation & Deep Links for details.


diff --git a/main/en/developer/codebase.html b/main/en/developer/codebase.html index d2c5d305e4..afe9ba77a8 100644 --- a/main/en/developer/codebase.html +++ b/main/en/developer/codebase.html @@ -10,6 +10,7 @@ │ ├── connections/ │ ├── map/ │ ├── map-maplibre/ +│ ├── map-terrain/ │ ├── node/ │ ├── settings/ │ ├── firmware/ @@ -52,11 +53,11 @@ ├── specs/ # Feature specifications └── .github/workflows/ # CI/CD workflows

Namespacing Convention

All Kotlin packages follow the pattern:

org.meshtastic.{layer}.{module}.{subpackage}
-

Examples:

Build System

Gradle Kotlin DSL

All build files use Kotlin DSL (.gradle.kts). Configuration:

Convention Plugins

Located in build-logic/convention/src/main/kotlin/. The full set is registered in build-logic/convention/build.gradle.kts; these are the ones a module build applies most often:

Plugin Purpose
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.koin Koin Annotations + K2 compiler plugin
meshtastic.kotlinx.serialization Serialization plugin setup
meshtastic.android.room Room KMP setup and schema location
meshtastic.android.screenshot Compose Preview screenshot testing

The rest cover the application and library variants, lint, detekt, spotless, Dokka, Kover, AboutLibraries, analytics, secrets, the docs tasks and the root aggregate — read the register(…) block rather than assuming a plugin does or does not exist.

Build Variants (Android)

Flavor Description
google Google Play distribution; includes proprietary APIs
fdroid F-Droid distribution; FOSS-only dependencies

Key Gradle Tasks

# Compile check across all KMP targets
+

Examples:

Build System

Gradle Kotlin DSL

All build files use Kotlin DSL (.gradle.kts). Configuration:

Convention Plugins

Located in build-logic/convention/src/main/kotlin/. The full set is registered in build-logic/convention/build.gradle.kts; these are the ones a module build applies most often:

Plugin Purpose
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 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
meshtastic.android.screenshot Compose Preview screenshot testing

The rest cover the application and library variants, lint, detekt, spotless, Dokka, Kover, AboutLibraries, analytics, secrets, the docs tasks and the root aggregate — read the register(…) block rather than assuming a plugin does or does not exist.

Build Variants (Android)

Flavor Description
google Google Play distribution; includes proprietary APIs
fdroid F-Droid distribution; FOSS-only dependencies

Key Gradle Tasks

# 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/main/en/developer/persistence.html b/main/en/developer/persistence.html
index 16e884d61b..d7d25605c0 100644
--- a/main/en/developer/persistence.html
+++ b/main/en/developer/persistence.html
@@ -1 +1 @@
-            Persistence | Meshtastic Android                      Skip to main content   Link      Menu      Expand       (external link)    Document      Search       Copy       Copied        
Meshtastic Docs ↗

Persistence

The app’s three persistence layers — Room, DataStore, and core:prefs — and when a contributor should use each.

Room KMP Database

Module: core:database

The primary structured data store:

  • Node information and history
  • Message history
  • Waypoints
  • Telemetry data
  • Channel set configuration (channel names and LoRa config)

Key Points

  • Uses Room KMP for cross-platform compatibility
  • Migrations managed through Room’s built-in migration system
  • DAO interfaces live in core:database
  • Repository layer in core:repository provides the public API
  • Full-text message search is backed by an FTS5 content table (PacketFts) over Packet, kept in sync by Room-managed triggers

What’s Stored in Room

Entity Description
NodeEntity All known mesh nodes and their metadata
MyNodeEntity The local node’s own info
Packet Message history (channel and direct), waypoints, and telemetry data
PacketFts FTS5 virtual table mirroring Packet.messageText for full-text message search (Room-managed INSERT/UPDATE/DELETE triggers keep it in sync)
ContactSettings Per-contact mute and read-state
ReactionEntity Emoji reactions on messages
MeshLog Raw mesh protocol logs
MetadataEntity Device metadata (firmware version, hardware model)
ChannelSetEntity The connected radio’s channel set — channel names and LoRa config — one row per device
QuickChatAction User-configured quick-chat messages
DeviceHardwareEntity Cached device hardware catalog
FirmwareReleaseEntity Cached firmware release info
TracerouteNodePositionEntity Traceroute hop position data
DiscoverySessionEntity A Local Mesh Discovery scan session (timestamp, presets scanned, home preset)
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

ℹ️ 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.

DataStore Preferences

Module: core:datastore

For lightweight key-value preferences:

  • Local radio configuration (LocalConfig)
  • Module configuration (ModuleConfig)
  • Local statistics
  • Recently connected radio addresses

Core Prefs

Module: core:prefs

Higher-level preferences abstraction:

  • User-facing settings
  • App behavior configuration
  • Feature toggles

What Docs Intentionally Skip

The feature:docs module uses no Room or persistent database. Documentation ships as build-time assets versioned with the app binary, so it stays fully offline, is replaced on each update, and needs no migration story. Optional UX state (e.g. last viewed page) could live in core:prefs but isn’t part of the docs data model.

Best Practices

  • Use Room for structured, queryable data that changes at runtime
  • Use DataStore for simple preferences and state
  • Use bundled resources/assets for static content
  • Never store sensitive data (keys, passwords) in plain Room tables
  • Always provide migrations for schema changes

+ Persistence | Meshtastic Android Skip to main content Link Menu Expand (external link) Document Search Copy Copied
Meshtastic Docs ↗

Persistence

The app’s three persistence layers — Room, DataStore, and core:prefs — and when a contributor should use each.

Room KMP Database

Module: core:database

The primary structured data store:

  • Node information and history
  • Message history
  • Waypoints
  • Telemetry data
  • Channel set configuration (channel names and LoRa config)

Key Points

  • Uses Room KMP for cross-platform compatibility
  • Migrations managed through Room’s built-in migration system
  • DAO interfaces live in core:database
  • Repository layer in core:repository provides the public API
  • Full-text message search is backed by an FTS5 content table (PacketFts) over Packet, kept in sync by Room-managed triggers

What’s Stored in Room

Entity Description
NodeEntity All known mesh nodes and their metadata
MyNodeEntity The local node’s own info
Packet Message history (channel and direct), waypoints, and telemetry data
PacketFts FTS5 virtual table mirroring Packet.messageText for full-text message search (Room-managed INSERT/UPDATE/DELETE triggers keep it in sync)
ContactSettings Per-contact mute and read-state
ReactionEntity Emoji reactions on messages
MeshLog Raw mesh protocol logs
MetadataEntity Device metadata (firmware version, hardware model)
ChannelSetEntity The connected radio’s channel set — channel names and LoRa config — one row per device
QuickChatAction User-configured quick-chat messages
DeviceHardwareEntity Cached device hardware catalog
FirmwareReleaseEntity Cached firmware release info
TracerouteNodePositionEntity Traceroute hop position data
DiscoverySessionEntity A Local Mesh Discovery scan session (timestamp, presets scanned, home preset)
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.

DataStore Preferences

Module: core:datastore

For lightweight key-value preferences:

  • Local radio configuration (LocalConfig)
  • Module configuration (ModuleConfig)
  • Local statistics
  • Recently connected radio addresses

Core Prefs

Module: core:prefs

Higher-level preferences abstraction:

  • User-facing settings
  • App behavior configuration
  • Feature toggles

What Docs Intentionally Skip

The feature:docs module uses no Room or persistent database. Documentation ships as build-time assets versioned with the app binary, so it stays fully offline, is replaced on each update, and needs no migration story. Optional UX state (e.g. last viewed page) could live in core:prefs but isn’t part of the docs data model.

Best Practices

  • Use Room for structured, queryable data that changes at runtime
  • Use DataStore for simple preferences and state
  • Use bundled resources/assets for static content
  • Never store sensitive data (keys, passwords) in plain Room tables
  • Always provide migrations for schema changes

diff --git a/main/en/developer/testing.html b/main/en/developer/testing.html index 42bb43933e..1386b935d0 100644 --- a/main/en/developer/testing.html +++ b/main/en/developer/testing.html @@ -1,5 +1,6 @@ - Testing | Meshtastic Android Skip to main content Link Menu Expand (external link) Document Search Copy Copied
Meshtastic Docs ↗

Testing

Testing strategy and practices for the Meshtastic KMP project.

Test categories

KMP unit tests (commonTest)

Shared tests that run on all platforms:

./gradlew allTests
-
  • Business logic tests
  • Data model validation
  • Search/ranking algorithm tests
  • Route serialization tests

Android host tests

Android-specific tests that run on JVM:

./gradlew test
+            Testing | Meshtastic Android                      Skip to main content   Link      Menu      Expand       (external link)    Document      Search       Copy       Copied        
Meshtastic Docs ↗

Testing

Testing strategy and practices for the Meshtastic KMP project.

Test categories

KMP unit tests (commonTest)

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:

./gradlew allTests
+
  • Business logic tests
  • Data model validation
  • Search/ranking algorithm tests
  • Route serialization tests

Android host tests

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:

./gradlew test       # pure Android/JVM modules
+./gradlew allTests   # KMP modules
 
  • ViewModel tests
  • Repository tests with Room fakes
  • Android-specific integration tests

Compose UI tests

Compose Multiplatform UI test framework:

@Test
 fun myScreenTest() = runComposeUiTest {
     setContent { MyScreen() }
@@ -19,8 +20,8 @@ adb pull /data/local/tmp/store-screenshots/fdroid/. fastlane/metadata/android/en
 │   └── MyModelTest.kt
 └── jvmTest/kotlin/org/meshtastic/feature/myfeature/
     └── MyDesktopSpecificTest.kt
-

Testing guidelines

DO

  • Write tests in commonTest when possible (runs everywhere)
  • Test business logic independently from UI
  • Use fakes/stubs instead of mocks where practical
  • Test edge cases: empty states, error states, boundary values
  • Test deep link routing in DeepLinkRouterTest
  • Keep tests fast — no network, no disk I/O in unit tests

DON’T

  • Don’t test framework behavior (Compose internals, Room queries)
  • Don’t create tests that depend on other feature modules
  • Don’t use Thread.sleep — use coroutine test dispatchers
  • Don’t rely on test execution order

Running tests

# All KMP tests
-./gradlew allTests
+

Testing guidelines

DO

  • 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
  • Test deep link routing in DeepLinkRouterTest
  • Keep tests fast — no network, no disk I/O in unit tests

DON’T

  • Don’t test framework behavior (Compose internals, Room queries)
  • Don’t create tests that depend on other feature modules
  • Don’t use Thread.sleep — use coroutine test dispatchers
  • Don’t rely on test execution order

Running tests

# All tests: allTests covers KMP modules, test covers Android/JVM-only modules; run both
+./gradlew test allTests
 
 # Specific module
 ./gradlew :feature:docs:allTests