Meshtastic-Android/desktop
GitHub Copilot CLI c1c01d7765 chore(r8): remove redundant keep rules covered by consumer rules
Removes explicit -keep/-dontwarn wildcards whose behavior is already
provided by the library's own bundled consumer-rules.pro, by Room's
generated static references, or by narrower annotation-targeted keeps.
The merged R8 configuration shrinks by 34 lines (1981→1947 googleRelease,
1653→1619 fdroidRelease), giving R8 more tree-shaking freedom.

Removed (covered by library-bundled consumer rules):
- kotlin.Metadata, kotlin.reflect.**, kotlin.coroutines.Continuation,
  kotlinx.coroutines.** (kotlin-stdlib + kotlinx-coroutines-core)
- androidx.datastore.**, androidx.paging.**, androidx.lifecycle.**,
  androidx.navigation3.**, androidx.sqlite.**
- coil3.**, okio.**, co.touchlab.kermit.**,
  com.mikepenz.aboutlibraries.**, com.mikepenz.markdown.**,
  com.juul.kable.**, io.ktor.**, io.ktor.client.engine.java.**
  (HttpClientEngineFactory ServiceLoader keep retained)

Room (room3) narrowed:
- Dropped org.meshtastic.core.database.{dao,entity,Converters}.** and
  **_Impl wildcards. RoomDatabaseConstructor +
  MeshtasticDatabaseConstructor/MeshtasticDatabase keeps retained.
  Room 3.0 KMP generates static references rather than reflective
  lookups, so reachable DAO/entity/_Impl code survives tree-shaking.

Wire protobuf narrowed:
- Dropped com.squareup.wire.**, org.meshtastic.proto.**, meshtastic.**
  wildcards and deleted core/proto/consumer-rules.pro (20 per-class keeps).
- Replaced with targeted -keepclassmembers for ADAPTER fields on
  Message subclasses and ProtoAdapter member preservation. Verified
  no Class.forName lookups into org.meshtastic.proto namespace.

Meshtastic model + DI:
- Dropped org.meshtastic.core.model.** wildcard and deleted
  core/model/consumer-rules.pro (DataPacket is @CommonParcelize'd;
  no reflective access to core.model).
- Replaced org.meshtastic.**.di.** wildcard with a narrow
  @KoinViewModel class annotation keep (the @Module, @ComponentScan,
  @Single, @Factory keeps above already cover the rest of Koin).

Compose resources refinement:
- Narrowed org.meshtastic.core.resources.** to Res + Res$* members
  only (sufficient for the fdroid startup-crash workaround in #5146).

Intentionally retained (documented workarounds / policy):
- -dontoptimize, -dontobfuscate, -printconfiguration
- All Koin annotation keeps + org.koin.**
- kotlinx-serialization @Serializable keeps
- Room RoomDatabaseConstructor + MeshtasticDatabase(-Constructor)
- Ktor HttpClientEngineFactory ServiceLoader keep
- Desktop MainKt + org.meshtastic.desktop.** entry points
- All -dontwarn rules for JVM/Android platform gaps
- app/proguard-rules.pro Compose runtime/ui/animation/foundation/
  material3 keeps (defence-in-depth with -dontoptimize; may interact
  with the parallel CMP freeze RCA investigation).

Verified:
- ./gradlew :app:assembleFdroidRelease — SUCCESS (after each batch)
- ./gradlew :app:assembleGoogleRelease — SUCCESS
- ./gradlew spotlessApply detekt — SUCCESS
- :desktop:createReleaseDistributable — pre-existing failure on
  origin/main (Vico/Skia warnings), unrelated to these changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-17 11:59:39 -05:00
..
src feat(mqtt): migrate to MQTTastic-Client-KMP (#5165) 2026-04-17 15:19:08 +00:00
.gitignore feat: introduce Desktop target and expand Kotlin Multiplatform (KMP) architecture (#4761) 2026-03-12 21:14:49 +00:00
build.gradle.kts chore: review-cleanup fleet (audit + fix + hardening) (#5158) 2026-04-17 00:02:59 +00:00
entitlements.plist feat(desktop): add entitlements and wire MeshConnectionManager into orchestrator (#5127) 2026-04-14 15:16:10 +00:00
proguard-rules.pro chore(r8): remove redundant keep rules covered by consumer rules 2026-04-17 11:59:39 -05:00
README.md fix(proguard): disable shrinking for Compose animation classes (#5116) 2026-04-13 21:55:52 +00:00

:desktop — Meshtastic Desktop

A Compose Desktop application target — the first full non-Android target for the shared KMP module graph. This module serves as:

  1. First multi-target milestone — Proves the KMP architecture supports real application targets beyond Android.
  2. Build smoke-test — Validates that all core:* KMP modules compile and link on a JVM Desktop target.
  3. Shared navigation proof — Uses the same Navigation 3 routes from core:navigation and the same NavDisplay + entryProvider pattern as the Android app, proving the shared backstack architecture works cross-target.
  4. Desktop app scaffold — A working Compose Desktop application with a NavigationRail for top-level destinations and placeholder screens for each feature.

Quick Start

# Run the desktop app
./gradlew :desktop:run

# Run tests
./gradlew :desktop:test

# Package native distribution (DMG/MSI/DEB) — debug (no ProGuard)
./gradlew :desktop:packageDistributionForCurrentOS

# Package native distribution (DMG/MSI/DEB) — release (ProGuard minified)
./gradlew :desktop:packageReleaseDistributionForCurrentOS

ProGuard / Minification

Release builds use ProGuard for tree-shaking (unused code removal), significantly reducing distribution size. Obfuscation is disabled since the project is open-source. Rules are aligned with the Android R8 rules in app/proguard-rules.pro — both targets share the same anti-class-merging philosophy.

Configuration:

  • build.gradle.ktsbuildTypes.release.proguard block enables ProGuard with optimize.set(true) and obfuscate.set(false).
  • proguard-rules.pro — Keep-rules for reflection/JNI-sensitive dependencies (Koin, kotlinx-serialization, Wire protobuf, Room KMP androidx.room3, Ktor, Kable BLE, Coil, SQLite JNI, Compose Multiplatform resources) and an anti-merge rule for Compose animation classes.

Key rules:

  • Compose animation anti-merge (-keep class androidx.compose.animation.** { *; }) — Prevents ProGuard's optimizer from incorrectly tree-shaking or merging animation class hierarchies (e.g. EnterTransition/ExitTransition into *Impl), which causes animations to silently snap. Same rule as Android.
  • Room KMP — Uses androidx.room3 package path (Room KMP 3.x).

Troubleshooting ProGuard issues:

  • If the release build crashes at runtime with ClassNotFoundException or NoSuchMethodError, a library is loading classes via reflection that ProGuard stripped. Add a -keep rule in proguard-rules.pro and the corresponding rule in app/proguard-rules.pro to keep both targets aligned.
  • To debug which classes ProGuard removes, temporarily add -printusage proguard-usage.txt to the rules file and inspect the output in desktop/proguard-usage.txt.
  • To see the full mapping of optimizations applied, add -printseeds proguard-seeds.txt.
  • Run ./gradlew :desktop:runRelease for a quick smoke-test of the minified app before packaging.

Architecture

The module depends on the JVM variants of KMP modules:

  • core:common, core:model, core:di, core:navigation, core:repository
  • core:domain, core:data, core:database, core:datastore, core:prefs
  • core:network, core:resources, core:ui

Navigation: Uses JetBrains multiplatform forks of Navigation 3 (org.jetbrains.androidx.navigation3:navigation3-ui) and Lifecycle (org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose, lifecycle-runtime-compose). A unified SavedStateConfiguration with polymorphic SerializersModule is provided centrally by core:navigation for non-Android NavKey serialization. Desktop utilizes the exact same navigation graph wiring (settingsGraph, nodesGraph, contactsGraph, connectionsGraph) directly from the commonMain of their respective feature modules, maintaining full UI parity.

Coroutines: Requires kotlinx-coroutines-swing for Dispatchers.Main on JVM/Desktop. Without it, any code using lifecycle.coroutineScope or Dispatchers.Main (e.g., NodeRepositoryImpl, RadioConfigRepositoryImpl) will crash at runtime.

DI: A Koin DI graph is bootstrapped in Main.kt with platform-specific implementations injected.

UI: JetBrains Compose for Desktop with Material 3 theming. Desktop acts as a thin host shell, delegating almost entirely to fully shared KMP UI modules. Includes native macOS notification support (via TrayState and bundleID identification) and a monochrome SVG tray icon for a native look and feel.

Notifications: Implements the common NotificationManager interface via DesktopNotificationManager. Repository-level notifications (messages, node events, alerts) are collected in Main.kt and forwarded to the system tray. macOS requires a consistent bundleID (configured in build.gradle.kts) and the NSUserNotificationAlertStyle key in Info.plist for notifications to appear correctly in the distributable.

Localization: Desktop exposes a language picker, persisting the selected BCP-47 tag in UiPreferencesDataSource.locale. Main.kt applies the override to the JVM default Locale and uses a staticCompositionLocalOf-backed recomposition trigger so Compose Multiplatform stringResource() calls update immediately without recreating the Navigation 3 backstack.

Key Files

File Purpose
Main.kt App entry point — Koin bootstrap, Compose Desktop window, theme + locale application
DemoScenario.kt Offline demo data for testing without a connected device
ui/DesktopMainScreen.kt Navigation 3 shell — NavigationRail + NavDisplay
navigation/DesktopNavigation.kt Nav graph entry registrations for all top-level destinations (delegates to shared feature graphs)
radio/DesktopRadioTransportFactory.kt Provides TCP, Serial/USB, and BLE transports
notification/DesktopMeshServiceNotifications.kt Real implementation of notification triggers for Desktop
DesktopNotificationManager.kt Bridge between repository notifications and Compose TrayState
radio/DesktopMeshServiceController.kt Mesh service lifecycle — orchestrates want_config handshake chain
radio/DesktopMessageQueue.kt Message queue for outbound mesh packets
di/DesktopKoinModule.kt Koin module with stub implementations
di/DesktopPlatformModule.kt Platform-specific Koin bindings
stub/NoopStubs.kt No-op implementations for all repository interfaces

What This Validates

Module What's Tested
core:common Base64Factory, NumberFormatter, UrlUtils, DateFormatter, CommonUri
core:model DeviceVersion, Capabilities, SfppHasher, platformRandomBytes, getShortDateTime, Channel.getRandomKey
core:ui Shared Compose components compile and render on Desktop
Build graph All core modules compile and link without Android SDK

Roadmap

  • Implement real navigation with shared core:navigation routes (Navigation 3 shell)
  • Adopt JetBrains multiplatform forks for lifecycle and navigation3
  • Implement native macOS/Desktop notification support with TrayState and system tray
  • Wire feature:settings composables into the nav graph (first real feature — ~30 screens)
  • Wire feature:node composables into the nav graph (node list with shared ViewModel + NodeItem)
  • Wire feature:messaging composables into the nav graph (contacts list with shared ViewModel)
  • Add JetBrains Material 3 Adaptive ListDetailPaneScaffold to node and messaging screens
  • Implement TCP transport (DesktopRadioTransportFactory) with auto-reconnect and backoff retry
  • Implement mesh service controller (DesktopMeshServiceController) with full want_config handshake
  • Create connections screen using shared feature:connections with dynamic transport detection
  • Replace 5 placeholder config screens with real desktop implementations (Device, Position, Network, Security, ExtNotification)
  • Add desktop language picker backed by shared UiPreferencesDataSource.locale with live translation updates
  • Wire remaining feature:* composables (map) into the nav graph
  • Move remaining node detail and message composables from androidMain to commonMain
  • Add serial/USB transport for direct radio connection on Desktop
  • Add BLE transport (via Kable) for direct radio connection on Desktop
  • Add MQTT transport for cloud-connected operation
  • Package as native distributions (DMG, MSI, DEB) via CI release pipeline