Files
Meshtastic-Android/core/domain
James RichandClaude Sonnet 5 688c2fbc99 feat(connections): enable wasmJs, unblocking all v0 feature modules
The v0 web slice (feature:connections/messaging/node/settings, per
this effort's own architecture decision) all share
KmpFeatureConventionPlugin. Its apply() wired core:testing directly
into every consumer's commonTest, unconditionally, at plugin-apply
time -- before the consuming module's own build.gradle.kts kotlin {}
block (and any nonWebTest source set it creates) has even run.
core:testing has no wasmJs target, so every v0 feature module would
have hit the identical compileTestKotlinWasmJs failure the moment it
opted in, no matter what its own build.gradle.kts did to try to route
around it -- a shared-build-logic problem, not a per-module one.

Fixed by deferring the wiring to target.afterEvaluate, which checks
whether the consuming module ended up with a wasmJs target and a
nonWebTest source set and routes core:testing there instead when both
exist, with a fail-fast check() if a module has one but not the other.
Every non-wasmJs feature module keeps resolving core:testing via
commonTest exactly as before -- verified with a real compile+test run
across all nine other consumers (messaging, node, settings,
map-maplibre, intro, discovery, docs, firmware, wifi-provision), zero
regression.

core:domain (a feature:connections dependency, zero expect/actual,
zero java.*/android.* imports, every dependency already wasmJs-clean)
gets a bare wasmJs() -- mechanical.

feature:connections surfaced a sharper version of the screening test
this session has used for every prior module: "no expect/actual, no
java.*/android.* imports" is necessary but not sufficient.
ScannerViewModel.kt/CommonGetDiscoveredDevicesUseCase.kt directly
referenced core:datastore's RecentAddressesDataSource/
FirmwareRecoveryDataSource -- concrete classes that live in that
module's own nonWebMain (Preferences-backed, no wasmJs variant),
reached transitively rather than through any local expect/actual. Two
new feature-local interfaces (RecentAddressesSource,
PendingFirmwareRecoverySource) seam this off: a nonWebMain adapter
delegates to the real DataStore-backed sources unchanged, and wasmJs
gets an honest no-op (no recent-address history, no firmware-recovery
banner on web this pass) -- same shape as core:service's
TakServerIntegration seam. A real localStorage-backed implementation
is deferred until a webApp module exists to wire one in.

Also fixes an unrelated, pre-existing detekt violation
(NoUnusedImports on ProjectExtensions.kt) surfaced while re-running
build-logic/convention's own lint as part of this pass's verification
-- unrelated to this change's own logic, folded in since it was
already in front of us.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-31 00:59:32 -05:00
..

:core:domain

Overview

The :core:domain module is the business-logic layer of the KMP architecture. It contains exclusively use-case classes — no UI, no platform code, no mutable state. Each use case is a thin orchestrator that coordinates one or more repository/model dependencies to fulfil a single application action.

Targets: Android · JVM · iOS (via meshtastic.kmp.library convention plugin)

Key Responsibilities

  • Orchestrate radio configuration reads/writes (config, module config, channels, owner, position)
  • Manage remote-admin session lifecycle (per-node passkey negotiation)
  • Process radio admin responses and manage mesh log settings
  • Data export (CSV mesh log, profile .zip)
  • Profile and security-config import/install
  • Node database maintenance (clean, reset, selective purge) and OTA capability checks

Source Structure

src/commonMain/kotlin/org/meshtastic/core/domain/
├── di/
│   └── CoreDomainModule.kt          ← Koin @Module + component scan
└── usecase/
    ├── session/
    │   ├── EnsureRemoteAdminSessionUseCase.kt
    │   ├── EnsureSessionResult.kt
    │   └── ObserveRemoteAdminSessionStatusUseCase.kt
    └── settings/
        ├── AdminActionsUseCase.kt
        ├── CleanNodeDatabaseUseCase.kt
        ├── ExportDataUseCase.kt
        ├── ExportProfileUseCase.kt
        ├── ImportProfileUseCase.kt
        ├── ImportSecurityConfigUseCase.kt
        ├── InstallProfileUseCase.kt
        ├── IsOtaCapableUseCase.kt
        ├── ProcessRadioResponseUseCase.kt
        ├── RadioConfigUseCase.kt
        └── SetMeshLogSettingsUseCase.kt

Notable APIs

EnsureRemoteAdminSessionUseCase

Ensures a per-node remote-admin passkey session exists before entering the remote admin UI. Uses a Mutex-guarded inFlight map so that double-taps coalesce onto a single Deferred.

sealed interface EnsureSessionResult {
    data object AlreadyActive   : EnsureSessionResult  // passkey already fresh
    data object Refreshed       : EnsureSessionResult  // metadata response arrived
    data object Timeout         : EnsureSessionResult  // no response within 30 s
    data object Disconnected    : EnsureSessionResult  // radio not connected
}

RadioConfigUseCase

Radio configuration read/write operations, all returning the packetId for async tracking:

Method Description
setOwner / getOwner Node owner info
setConfig / getConfig Config proto (device, position, power, …)
setModuleConfig / getModuleConfig ModuleConfig proto
getChannel / setRemoteChannel Channel configuration
setFixedPosition / removeFixedPosition Fixed GPS position
setRingtone / getRingtone External notification ringtone
setCannedMessages / getCannedMessages Canned message slots

AdminActionsUseCase

reboot(destNum)
shutdown(destNum)
factoryReset(destNum, isLocal)   // also clears local NodeDB when isLocal = true
nodedbReset(destNum, preserveFavorites, isLocal)

ExportDataUseCase

Streams all mesh log packets to a CSV BufferedSink. Columns: date, time, from, sender name/location, received location/elevation, SNR, distance, hop limit, payload.

Dependency Graph

core:domain
  ├── core:repository              (use-case interfaces & contracts)
  ├── core:model                   (domain models)
  ├── org.meshtastic:protobufs     (Meshtastic protobuf types, Maven)
  ├── core:common
  ├── core:database
  ├── core:datastore
  └── core:resources

The generated Mermaid graph below renders project-module edges only — external Maven artifacts such as org.meshtastic:protobufs are not shown, and dashed edges are test-only (e.g. :core:testing).

DI

All use cases are registered via Koin component scan on org.meshtastic.core.domain. No manual binding is needed — annotate a new use case with @Single and it is picked up automatically.

Dependency Graph

graph TB
  :core:domain[domain]:::kmp-library
  :core:domain -.-> :core:repository
  :core:domain -.-> :core:model
  :core:domain -.-> :core:common
  :core:domain -.-> :core:database
  :core:domain -.-> :core:datastore
  :core:domain -.-> :core:resources
  :core:domain -.-> :core:testing

classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-application-compose fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef compose-desktop-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-library-compose fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-library-compose fill:#FFC1CC,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-library fill:#FFC1CC,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;