From eeed780e51e0a56d80e3083b48080fe8b6092dc5 Mon Sep 17 00:00:00 2001
From: James Rich <2199651+jamesarich@users.noreply.github.com>
Date: Sun, 12 Apr 2026 12:29:05 -0500
Subject: [PATCH] chore(ai): modernize and unify agent tooling and instructions
(#5087)
---
.copilotignore | 27 ++
.gemini/settings.json | 5 +
.../copilot-commit-message-instructions.md | 27 ++
.github/copilot-instructions.md | 8 +-
.github/copilot-pull-request-instructions.md | 18 ++
.../android-source-set.instructions.md | 11 +
.../instructions/build-logic.instructions.md | 10 +
.../instructions/ci-workflows.instructions.md | 14 +
.../instructions/kmp-common.instructions.md | 17 ++
.gitignore | 2 +
.skills/code-review/SKILL.md | 67 +++++
.skills/compose-ui/SKILL.md | 31 +++
.skills/implement-feature/SKILL.md | 37 +++
.skills/kmp-architecture/SKILL.md | 55 ++++
.skills/navigation-and-di/SKILL.md | 37 +++
.skills/project-overview/SKILL.md | 76 ++++++
.skills/testing-ci/SKILL.md | 97 +++++++
AGENTS.md | 255 ++++--------------
CLAUDE.md | 9 +
GEMINI.md | 8 +-
SOUL.md | 2 +-
docs/agent-playbooks/README.md | 52 ----
.../di-navigation3-anti-patterns-playbook.md | 58 ----
.../kmp-source-set-bridging-playbook.md | 45 ----
docs/agent-playbooks/task-playbooks.md | 113 --------
.../testing-and-ci-playbook.md | 88 ------
docs/kmp-status.md | 2 +-
27 files changed, 604 insertions(+), 567 deletions(-)
create mode 100644 .copilotignore
create mode 100644 .gemini/settings.json
create mode 100644 .github/copilot-commit-message-instructions.md
create mode 100644 .github/copilot-pull-request-instructions.md
create mode 100644 .github/instructions/android-source-set.instructions.md
create mode 100644 .github/instructions/build-logic.instructions.md
create mode 100644 .github/instructions/ci-workflows.instructions.md
create mode 100644 .github/instructions/kmp-common.instructions.md
create mode 100644 .skills/code-review/SKILL.md
create mode 100644 .skills/compose-ui/SKILL.md
create mode 100644 .skills/implement-feature/SKILL.md
create mode 100644 .skills/kmp-architecture/SKILL.md
create mode 100644 .skills/navigation-and-di/SKILL.md
create mode 100644 .skills/project-overview/SKILL.md
create mode 100644 .skills/testing-ci/SKILL.md
create mode 100644 CLAUDE.md
delete mode 100644 docs/agent-playbooks/README.md
delete mode 100644 docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md
delete mode 100644 docs/agent-playbooks/kmp-source-set-bridging-playbook.md
delete mode 100644 docs/agent-playbooks/task-playbooks.md
delete mode 100644 docs/agent-playbooks/testing-and-ci-playbook.md
diff --git a/.copilotignore b/.copilotignore
new file mode 100644
index 000000000..02ec3ad1d
--- /dev/null
+++ b/.copilotignore
@@ -0,0 +1,27 @@
+# Ignore build artifacts and generated files from Copilot indexing
+# This saves context window tokens and prevents Copilot from hallucinating off of minified code.
+
+# Build directories
+**/build/**
+.gradle/
+.idea/
+
+# Android generated files
+**/generated/**
+.cxx/
+.externalNativeBuild/
+
+# Git history & worktrees
+.git/
+.worktrees/
+
+# Protobuf (Prevents Copilot from suggesting raw protobuf byte buffers)
+core/proto/
+
+# Environment and secrets
+local.properties
+secrets.properties
+*.jks
+
+# Agent References (Prevents pollution of project space with external code)
+.agent_refs/
diff --git a/.gemini/settings.json b/.gemini/settings.json
new file mode 100644
index 000000000..5e535b215
--- /dev/null
+++ b/.gemini/settings.json
@@ -0,0 +1,5 @@
+{
+ "context": {
+ "fileName": ["AGENTS.md", "GEMINI.md"]
+ }
+}
diff --git a/.github/copilot-commit-message-instructions.md b/.github/copilot-commit-message-instructions.md
new file mode 100644
index 000000000..93c242d16
--- /dev/null
+++ b/.github/copilot-commit-message-instructions.md
@@ -0,0 +1,27 @@
+# GitHub Copilot Commit Message Instructions
+
+
+You are an expert Git maintainer enforcing Conventional Commits.
+
+
+
+1. **Format:** Use the Conventional Commits format: `(): ` (Replace angle brackets with actual text, do NOT output angle brackets).
+2. **Types allowed:**
+ - `feat` (new feature for the user, not a new feature for build script)
+ - `fix` (bug fix for the user, not a fix to a build script)
+ - `docs` (changes to the documentation)
+ - `style` (formatting, missing semi colons, etc; no production code change)
+ - `refactor` (refactoring production code, e.g. KMP migration, extracting to commonMain)
+ - `test` (adding missing tests, refactoring tests; no production code change)
+ - `chore` (updating grunt tasks etc; no production code change)
+3. **Scope:** Use the module or logical component as the scope (e.g., `ui`, `navigation`, `ble`, `firmware`, `deps`, `ai`).
+4. **Subject line:**
+ - Use the imperative, present tense: "change" not "changed" nor "changes".
+ - Do not capitalize the first letter.
+ - Do not use a period (.) at the end.
+ - Keep it under 50 characters if possible.
+5. **Body (Optional but recommended for large diffs):**
+ - Leave one blank line after the subject.
+ - Explain *why* the change was made, not just *what* changed.
+ - If migrating to KMP or extracting to `commonMain`, explicitly state "Decoupled from Android framework".
+
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 2e60f3dff..e856cbe8f 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -1,6 +1,6 @@
-# Meshtastic Android - Agent Guide
+# Meshtastic Android - GitHub Copilot Guide
-**Canonical instructions live in [`AGENTS.md`](../AGENTS.md).** This file exists at `.github/copilot-instructions.md` so GitHub Copilot discovers it automatically.
+> **Note:** The canonical instructions for all AI Agents have been deduplicated.
-See [AGENTS.md](../AGENTS.md) for architecture, conventions, execution protocol, and coding standards.
-See [docs/agent-playbooks/README.md](../docs/agent-playbooks/README.md) for version baselines and task recipes.
+You MUST immediately read and internalize the unified instructions located at the root of the repository in `AGENTS.md`.
+After reading `AGENTS.md`, consult the `.skills/` directory for task-specific playbooks.
diff --git a/.github/copilot-pull-request-instructions.md b/.github/copilot-pull-request-instructions.md
new file mode 100644
index 000000000..8e79d63d2
--- /dev/null
+++ b/.github/copilot-pull-request-instructions.md
@@ -0,0 +1,18 @@
+# GitHub Copilot Pull Request Instructions
+
+
+You are an expert open-source maintainer. Your goal is to write clear, professional, and highly structured Pull Request descriptions based on the provided diffs.
+
+
+
+1. **Remove Boilerplate:** Always delete the "tips" section at the top of the `PULL_REQUEST_TEMPLATE.md` before generating your text.
+2. **Context First:** Start with a clear, 1-2 sentence summary of *why* this change is being made. If the branch name or commits reference an issue (e.g., `fix-1234`), explicitly add `Fixes #1234` or `Resolves #1234`.
+3. **Structured Changes:** Break down the code changes into bullet points categorized by:
+ - ๐ **New Features** (UI, modules, logic)
+ - ๐ ๏ธ **Refactoring & Architecture** (KMP migrations, Koin DI updates)
+ - ๐ **Bug Fixes**
+ - ๐งน **Chores** (Dependencies, formatting, docs)
+4. **Architecture Callouts:** If the diff includes moving files from `androidMain` to `commonMain`, or migrating from Android Views to Compose, highlight this as a "KMP Migration Milestone".
+5. **Testing Callouts:** If the diff includes changes to `commonTest` or mentions tests, add a section called "Testing Performed" and list the tests that were added/modified.
+6. **No "Magic" Text:** Do not invent URLs or insert fake image placeholders. Leave the HTML comment block for images intact so the user can manually add their screenshots.
+
diff --git a/.github/instructions/android-source-set.instructions.md b/.github/instructions/android-source-set.instructions.md
new file mode 100644
index 000000000..6179bc61a
--- /dev/null
+++ b/.github/instructions/android-source-set.instructions.md
@@ -0,0 +1,11 @@
+---
+applyTo: "**/androidMain/**/*.kt"
+---
+
+# Android Source-Set Rules
+
+- This is `androidMain` โ Android framework imports (`android.*`, `java.*`) are allowed here.
+- Do NOT put business logic here. Business logic belongs in `commonMain`.
+- If you find identical pure-Kotlin logic in both `androidMain` and `jvmMain`, extract it to `commonMain`.
+- Use `expect`/`actual` only for small platform primitives. Prefer interfaces + DI.
+- Keep `expect` declarations in `FileIo.kt` and shared helpers in `FileIoUtils.kt` to avoid JVM duplicate class errors.
diff --git a/.github/instructions/build-logic.instructions.md b/.github/instructions/build-logic.instructions.md
new file mode 100644
index 000000000..d61fa34b8
--- /dev/null
+++ b/.github/instructions/build-logic.instructions.md
@@ -0,0 +1,10 @@
+---
+applyTo: "build-logic/**/*.kt"
+---
+
+# Build-Logic Convention Plugin Rules
+
+- Prefer lazy Gradle configuration (`configureEach`, `withPlugin`, provider APIs).
+- Avoid `afterEvaluate` unless there is no viable lazy alternative.
+- Check `gradle/libs.versions.toml` for version catalog aliases before adding new ones.
+- Convention plugins: `meshtastic.kmp.feature`, `meshtastic.kmp.library`, `meshtastic.kmp.jvm.android`, `meshtastic.koin`.
diff --git a/.github/instructions/ci-workflows.instructions.md b/.github/instructions/ci-workflows.instructions.md
new file mode 100644
index 000000000..55a72b328
--- /dev/null
+++ b/.github/instructions/ci-workflows.instructions.md
@@ -0,0 +1,14 @@
+---
+applyTo: "**/*.yml"
+excludeAgent: "code-review"
+---
+
+# CI Workflow Rules
+
+- Prefer explicit Gradle task paths (`app:lintFdroidDebug`) over shorthand (`lintDebug`).
+- CI uses `.github/ci-gradle.properties` โ don't assume local `gradle.properties` values.
+- CI passes `-Pci=true` to enable full processor usage via `maxParallelForks`.
+- Use `fetch-depth: 0` only where needed (spotless ratcheting, version code). Use `fetch-depth: 1` otherwise.
+- Desktop build matrix: `macos-latest`, `windows-latest`, `ubuntu-24.04`, `ubuntu-24.04-arm`.
+- Lightweight jobs (labelers, triage, stale): use `ubuntu-24.04-arm` runners.
+- Gradle-heavy jobs: use `ubuntu-24.04` runners.
diff --git a/.github/instructions/kmp-common.instructions.md b/.github/instructions/kmp-common.instructions.md
new file mode 100644
index 000000000..235d5826d
--- /dev/null
+++ b/.github/instructions/kmp-common.instructions.md
@@ -0,0 +1,17 @@
+---
+applyTo: "**/commonMain/**/*.kt"
+---
+
+# KMP commonMain Rules
+
+- NEVER import `java.*` or `android.*` in `commonMain`.
+- Use `org.meshtastic.core.common.util.ioDispatcher` instead of `Dispatchers.IO`.
+- Use Okio (`BufferedSource`/`BufferedSink`) instead of `java.io.*`.
+- Use `kotlinx.coroutines.sync.Mutex` instead of `java.util.concurrent.locks.*`.
+- Use `atomicfu` or Mutex-guarded `mutableMapOf()` instead of `ConcurrentHashMap`.
+- Use `jetbrains-*` catalog aliases for lifecycle/navigation dependencies.
+- Use `compose-multiplatform-*` catalog aliases for CMP dependencies.
+- Never use plain `androidx.compose` dependencies in `commonMain`.
+- Strings: use `stringResource(Res.string.key)` from `core:resources`. No hardcoded strings.
+- CMP `stringResource` only supports `%N$s` and `%N$d` โ pre-format floats with `NumberFormatter.format()`.
+- Check `gradle/libs.versions.toml` before adding dependencies.
diff --git a/.gitignore b/.gitignore
index 97dbb7b24..8447bc7f7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,3 +53,5 @@ wireless-install.sh
.worktrees/
/firebase-debug.log.jdk/
firebase-debug.log
+.agent_plans/
+.agent_refs/
diff --git a/.skills/code-review/SKILL.md b/.skills/code-review/SKILL.md
new file mode 100644
index 000000000..08caa95be
--- /dev/null
+++ b/.skills/code-review/SKILL.md
@@ -0,0 +1,67 @@
+# Skill: Code Review
+
+## Description
+Perform comprehensive and precise code reviews for the `Meshtastic-Android` project. This skill ensures that incoming changes adhere strictly to the project's architecture guidelines, Kotlin Multiplatform (KMP) conventions, Modern Android Development (MAD) standards, and Jetpack Compose Multiplatform (CMP) best practices.
+
+## Context & Prerequisites
+The `Meshtastic-Android` codebase is a highly modernized Kotlin Multiplatform (KMP) application designed for off-grid, decentralized mesh networks.
+- **Language:** Kotlin (primary), JDK 21 required.
+- **Architecture:** KMP core with Android and Desktop host shells.
+- **UI:** Jetpack Compose Multiplatform (CMP) and Material 3 Adaptive.
+- **Navigation:** JetBrains Navigation 3 (Scene-based).
+- **DI:** Koin Annotations (with K2 compiler plugin).
+- **Async & I/O:** Kotlin Coroutines, Flow, Okio, Ktor.
+
+## Code Review Checklist
+
+When reviewing code, meticulously verify the following categories. Flag any deviations and propose the canonical project pattern as a fix.
+
+### 1. KMP Architecture & Source Set Boundaries
+- [ ] **No Platform Bleed:** Ensure absolutely no `java.*` or `android.*` imports exist in `commonMain` source sets.
+- [ ] **KMP Native Alternatives:** Verify the use of KMP alternatives for standard JVM libraries:
+ - `java.util.concurrent.locks.*` -> `kotlinx.coroutines.sync.Mutex`
+ - `java.util.concurrent.ConcurrentHashMap` -> `atomicfu` or Mutex-guarded `mutableMapOf()`
+ - `java.io.*` -> `Okio` (`BufferedSource`/`BufferedSink`)
+ - `java.util.Locale` -> Kotlin `uppercase()`/`lowercase()` or `expect`/`actual`
+- [ ] **Shared Helpers:** If `androidMain` and `jvmMain` contain identical pure-Kotlin logic, mandate extracting it to a shared function in `commonMain`.
+- [ ] **File Naming Conflicts:** For `expect`/`actual` declarations, ensure files sharing the same package namespace have distinct names (e.g., keep `expect` in `LogExporter.kt` and shared helpers in `LogFormatter.kt`) to avoid duplicate class errors on the JVM target.
+- [ ] **Interface & DI Over `expect`/`actual`:** Check that `expect`/`actual` is reserved for small platform primitives. Interfaces + DI should be preferred for larger capabilities.
+
+### 2. UI & Compose Multiplatform (CMP)
+- [ ] **Compose Multiplatform Resources:** Ensure NO hardcoded strings. Must use `core:resources` (e.g., `stringResource(Res.string.key)` or asynchronous `getStringSuspend(Res.string.key)` for ViewModels/Coroutines). NEVER use blocking `getString()` in a coroutine.
+- [ ] **String Formatting:** CMP only supports `%N$s` and `%N$d`. Flag any float formats (`%N$.1f`) in Compose string resources; they must be pre-formatted using `NumberFormatter.format()` from `core:common`.
+- [ ] **Centralized Dialogs & Alerts:** Flag inline alert-rendering logic. Mandate the use of `AlertHost(alertManager)` or `SharedDialogs` from `core:ui/commonMain`.
+- [ ] **Placeholders:** Require `PlaceholderScreen(name)` from `core:ui/commonMain` for unimplemented desktop/JVM features. No inline placeholders in feature modules.
+- [ ] **Adaptive Layouts:** Verify use of `currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true)` to support desktop/tablet breakpoints (โฅ 1200dp).
+
+### 3. Navigation & State
+- [ ] **Shared Navigation Graphs:** Feature navigation graphs must be defined as extension functions on `EntryProviderScope` in `commonMain` (e.g., `fun EntryProviderScope.settingsGraph(...)`). Flag any graphs defined in platform-specific source sets.
+- [ ] **Navigation Host:** Ensure `MeshtasticNavDisplay` (from `core:ui/commonMain`) is used as the host instead of invoking `NavDisplay` directly. Host modules should not configure `entryDecorators` themselves.
+- [ ] **ViewModel Scoping:** ViewModels obtained via `koinViewModel()` must be inside `entry` blocks to correctly tie to the backstack lifetime.
+
+### 4. Dependency Injection (Koin Annotations)
+- [ ] **Annotation Usage:** Ensure Koin is configured via annotations (`@Single`, `@Factory`, `@KoinViewModel`).
+- [ ] **Root Assembly:** Confirm that the root Koin DI graph is only assembled in host shells (`app` and `desktop`).
+
+### 5. Networking, DB & I/O
+- [ ] **Ktor Strictly:** Check that Ktor is used for all HTTP networking. Flag and reject any usage of OkHttp.
+- [ ] **Image Loading (Coil):** Coil must use `coil-network-ktor3` in host modules. Feature modules should ONLY depend on `libs.coil` (coil-compose) and never configure fetchers.
+- [ ] **Room KMP:** Ensure `factory = { MeshtasticDatabaseConstructor.initialize() }` is used in `Room.databaseBuilder`. DAOs and Entities must reside in `commonMain`.
+- [ ] **Bluetooth (BLE):** All Bluetooth communication must be routed through `core:ble` using Kable abstractions.
+
+### 6. Dependency Catalog Aliases
+- [ ] **JetBrains vs. AndroidX:**
+ - In `commonMain`: Must use `jetbrains-*` aliases (e.g., `jetbrains-lifecycle-*`, `jetbrains-navigation3-ui`).
+ - In `androidMain`: Can use `androidx-*` or `jetbrains-*` as appropriate, but do not mix them up in `commonMain`.
+- [ ] **Compose Multiplatform:** Ensure `compose-multiplatform-*` aliases are used instead of plain `androidx.compose` in all KMP modules.
+
+### 7. Testing
+- [ ] **Shared Test Utilities:** Test fakes, doubles, and utilities should be placed in `core:testing`.
+- [ ] **Libraries:** Verify usage of `Turbine` for Flow testing, `Kotest` for property-based testing, and `Mokkery` for mocking.
+- [ ] **Robolectric Configuration:** Check that Compose UI tests running via Robolectric on JVM are pinned to `@Config(sdk = [34])` to prevent Java 21 / SDK 35 compatibility issues.
+
+## Review Output Guidelines
+1. **Be Specific & Constructive:** Provide exact file references and code snippets illustrating the required project pattern.
+2. **Reference the Docs:** Cite `AGENTS.md` and project architecture playbooks to justify change requests (e.g., "Per AGENTS.md, `java.io.*` cannot be used in `commonMain`; please migrate to Okio").
+3. **Enforce Build Health:** Remind authors to run `./gradlew test allTests` locally to verify changes, especially since KMP `test` tasks are ambiguous.
+4. **Praise Good Patterns:** Acknowledge correct usage of complex architecture requirements, like proper Navigation 3 scene transitions or elegant `commonMain` helper extractions.
diff --git a/.skills/compose-ui/SKILL.md b/.skills/compose-ui/SKILL.md
new file mode 100644
index 000000000..d2e79c542
--- /dev/null
+++ b/.skills/compose-ui/SKILL.md
@@ -0,0 +1,31 @@
+# Skill: Compose Multiplatform (CMP) UI
+
+## Description
+Guidelines for building shared UI, adaptive layouts, and handling strings/resources in Meshtastic-Android. The codebase uses Material 3 Adaptive.
+
+## 1. UI Components & Layouts
+- **Material 3 / Adaptive:** Use `currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true)` to support Large (1200dp) and XL (1600dp) breakpoints. Investigate 3-pane "Power User" scenes using Navigation 3 Scenes and draggable dividers for desktop/tablets.
+- **Dialogs & Alerts:** Use centralized components like `AlertHost(alertManager)` from `core:ui/commonMain`. Do NOT trigger alerts inline or duplicate alert logic. Use `SharedDialogs(uiViewModel)` for general popups.
+- **Placeholders:** Use `PlaceholderScreen(name)` from `core:ui/commonMain` for unimplemented desktop/JVM features.
+- **Theme Picker:** Use `ThemePickerDialog` from `feature:settings/commonMain`.
+- **Platform Implementations:** Inject platform-specific behavior (e.g., Map providers) via `CompositionLocal` from the `app` or `desktop` shells. Do not tightly couple Google Maps/osmdroid dependencies to `commonMain`.
+
+## 2. Strings & Resources
+- **Multiplatform Resources:** MUST use `core:resources` (e.g., `stringResource(Res.string.your_key)`). Never use hardcoded strings.
+- **ViewModels/Coroutines:** Use the asynchronous `getStringSuspend(Res.string.your_key)`. NEVER use blocking `getString()` in a coroutine context.
+- **Formatting Constraints:** CMP `stringResource` only supports `%N$s` (string) and `%N$d` (integer).
+ - **No Float formatting:** Formats like `%N$.1f` pass through unsubstituted. Pre-format in Kotlin using `NumberFormatter.format(value, decimalPlaces)` from `core:common` and pass as a string argument (`%N$s`).
+ - **Percent Literals:** Use bare `%` (not `%%`) for literal percent signs in CMP-consumed strings.
+- **Workflow to Add a String:**
+ 1. Add to `core/resources/src/commonMain/composeResources/values/strings.xml`.
+ 2. Use the generated `org.meshtastic.core.resources.` symbol.
+ 3. Validate UI presentation.
+
+## 3. Tooling & Capabilities
+- **Image Loading:** Use `libs.coil` (Coil Compose) in feature modules. Configuration/Networking for Coil (`coil-network-ktor3`) happens strictly in the `app` and `desktop` host modules.
+- **QR Codes:** Use `rememberQrCodePainter` from `core:ui/commonMain` powered by `qrcode-kotlin`. No ZXing or Android Bitmap APIs in shared code.
+
+## Reference Anchors
+- **Shared Strings:** `core/resources/src/commonMain/composeResources/values/strings.xml`
+- **Platform abstraction contract:** `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt`
+- **Provider wiring:** `app/src/main/kotlin/org/meshtastic/app/MainActivity.kt`
diff --git a/.skills/implement-feature/SKILL.md b/.skills/implement-feature/SKILL.md
new file mode 100644
index 000000000..1efa3caa0
--- /dev/null
+++ b/.skills/implement-feature/SKILL.md
@@ -0,0 +1,37 @@
+# Skill: Implement a Feature
+
+## Description
+A step-by-step workflow for implementing a new feature in the Meshtastic-Android codebase, ensuring KMP compatibility and proper architecture.
+
+## Workflow
+
+### 1. Update Dependencies & Aliases
+- Check `gradle/libs.versions.toml` before adding libraries.
+- Use `jetbrains-*` aliases for lifecycle/navigation/adaptive dependencies in `commonMain`.
+- Use `compose-multiplatform-*` aliases for CMP dependencies.
+
+### 2. Define the State & ViewModels
+- Follow MVI/UDF patterns.
+- Extend shared ViewModel logic in `feature//src/commonMain/kotlin/org/meshtastic/feature//ViewModel.kt`.
+- Use `stateInWhileSubscribed` (from `core:ui`) for sharing state flows.
+- Keep the ViewModel free of Android framework dependencies.
+
+### 3. Build the UI
+- Use Jetpack Compose Multiplatform (CMP).
+- Define strings in `core:resources` (see the `compose-ui` skill).
+- Support adaptive layouts (Large/XL breakpoints).
+
+### 4. Wire Navigation & DI
+- Define typed route objects in `core:navigation`.
+- Export the navigation graph as an extension function on `EntryProviderScope` in `commonMain` (e.g., `fun EntryProviderScope.myFeatureGraph()`).
+- Add the required DI bindings via Koin Annotations (`@Factory`, `@Single`, `@KoinViewModel`) in `commonMain`.
+- **CRITICAL:** Ensure the module is registered in the app root graphs (`AppKoinModule.kt`, `DesktopKoinModule.kt`) and the navigation is injected into the root entry provider in the host shell.
+
+### 5. Validate Platform Separation
+- If you need a platform-specific API (like camera or specific mapping SDK), define an interface in `commonMain`, implement it in the host shell, and inject it via `CompositionLocal` or Koin.
+
+### 6. Verify Locally
+- Run the baseline checks (see `testing-ci` skill):
+ ```bash
+ ./gradlew spotlessCheck detekt assembleDebug test allTests
+ ```
diff --git a/.skills/kmp-architecture/SKILL.md b/.skills/kmp-architecture/SKILL.md
new file mode 100644
index 000000000..805d9f2f9
--- /dev/null
+++ b/.skills/kmp-architecture/SKILL.md
@@ -0,0 +1,55 @@
+# Skill: KMP Architecture & Source-Set Bridging
+
+## Description
+Guidelines on managing Kotlin Multiplatform (KMP) source-sets, expected abstractions, networking, database, and platform integration rules.
+
+## 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.
+- **`app` / `desktop`:** Host shells. Responsible for Koin DI root wiring, `MainKoinModule`, host-level UI themes, and running the `MeshtasticNavDisplay`.
+
+## 2. Bridging Strategies
+- **Interface + DI (Preferred):** Expose an interface in `core:repository` or `core:ui` (e.g. `LocationRepository`, `MapViewProvider`), implement it in `androidMain` or the host `app`, and bind it via Koin or `CompositionLocal`.
+- **`expect`/`actual` (Restricted):** Use only when a platform API cannot be abstracted cleanly (e.g. low-level File I/O mappings, `uppercase()` Locale helpers). Avoid deep class hierarchies using `expect`/`actual`.
+ - **Naming:** Keep `expect` in `FileIo.kt`, but put shared helpers in `FileIoUtils.kt` to prevent JVM duplicate class errors.
+- **Shared Helpers:** Do not duplicate pure Kotlin logic between `androidMain` and `jvmMain`. Extract to a `commonMain` helper.
+
+## 3. Core Libraries & Constraints
+- **Concurrency:** `kotlinx.coroutines`. Use `org.meshtastic.core.common.util.ioDispatcher` over `Dispatchers.IO` directly.
+- **Standard Library Replacements:**
+ - `ConcurrentHashMap` -> `atomicfu` or Mutex-guarded `mutableMapOf()`.
+ - `java.util.concurrent.locks.*` -> `kotlinx.coroutines.sync.Mutex`.
+ - `java.io.*` -> `Okio` (`BufferedSource`/`BufferedSink`).
+- **Networking:** Pure **Ktor**. No OkHttp. Ktor `Logging` plugin for debugging.
+- **BLE:** Route through `core:ble` using **Kable**.
+- **Room KMP:** Use `factory = { MeshtasticDatabaseConstructor.initialize() }` in `Room.databaseBuilder`.
+
+## 4. Hierarchy & Source-Set Conventions
+- **Hierarchy template first:** Prefer Kotlin's default hierarchy template and convention plugins over manual `dependsOn(...)` graphs. Manual source-set wiring should be reserved for cases the template cannot model.
+- **`expect`/`actual` restraint:** Prefer interfaces + DI for platform capabilities; use `expect`/`actual` for small unavoidable platform primitives. Avoid broad expect/actual class hierarchies when an interface-based boundary is sufficient.
+- **Shared helpers over duplicated lambdas:** When `androidMain` and `jvmMain` contain identical pure-Kotlin logic (formatting, action dispatch, validation), extract to `commonMain`. Examples: `formatLogsTo()`, `handleNodeAction()`, `findNodeByNameSuffix()`, `MeshtasticAppShell`, `BaseRadioTransportFactory`.
+
+## 5. Dependency Catalog Aliases
+- **JetBrains fork aliases:** Version catalog aliases for JetBrains-forked AndroidX artifacts use the `jetbrains-*` prefix (e.g., `jetbrains-lifecycle-runtime-compose`, `jetbrains-navigation3-ui`). Plain `androidx-*` aliases are true Google AndroidX artifacts. Never mix them up in `commonMain`.
+- **Compose Multiplatform:** Version catalog aliases for Compose Multiplatform artifacts use the `compose-multiplatform-*` prefix (e.g., `compose-multiplatform-material3`, `compose-multiplatform-foundation`). Never use plain `androidx.compose` dependencies in `commonMain`.
+- **Dependencies:** Always check `gradle/libs.versions.toml` before assuming a library is available.
+
+## 6. I/O & Serialization
+- **Okio standard:** This project standardizes on Okio (`BufferedSource`/`BufferedSink`). JetBrains recommends `kotlinx-io` (built on Okio), but this project has not migrated. Do not introduce `kotlinx-io` without an explicit decision.
+- **Room KMP:** Always use `factory = { MeshtasticDatabaseConstructor.initialize() }` in `Room.databaseBuilder` and `inMemoryDatabaseBuilder`. DAOs and Entities reside in `commonMain`.
+
+## 7. Build-Logic Conventions
+- In `build-logic/convention`, prefer lazy Gradle configuration (`configureEach`, `withPlugin`, provider APIs). Avoid `afterEvaluate` in convention plugins unless there is no viable lazy alternative.
+
+## 8. Onboarding a New Target (Desktop/iOS)
+1. Ensure all new logic compiles against the KMP core (`jvm()`, `iosArm64()`, etc.).
+2. Do not use platform-specific constructs in `commonMain` or you break the iOS/Desktop builds.
+3. Test using `kmpSmokeCompile` to verify cross-platform compilation.
+4. For desktop wiring, copy the pattern in `desktop/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt` and use `NoopStubs.kt` to temporarily mock missing platform implementations.
+
+## Reference Anchors
+- **Shared Okio I/O:** `core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/usecase/settings/ImportProfileUseCase.kt`
+- **Desktop DI Stubs:** `desktop/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt`
+- **Version Catalog:** `gradle/libs.versions.toml`
+- **Convention Plugins:** `build-logic/convention/`
diff --git a/.skills/navigation-and-di/SKILL.md b/.skills/navigation-and-di/SKILL.md
new file mode 100644
index 000000000..557db4717
--- /dev/null
+++ b/.skills/navigation-and-di/SKILL.md
@@ -0,0 +1,37 @@
+# Skill: DI and Navigation 3 Architecture
+
+## Description
+This skill covers dependency injection (Koin Annotations 4.2.x) and JetBrains Navigation 3 (1.1.x) architecture, constraints, and anti-patterns within the Meshtastic-Android KMP codebase.
+
+## Dependency Injection (Koin)
+
+### Guidelines
+1. **Annotations First:** Use `@Module`, `@ComponentScan`, and `@KoinViewModel` annotations directly in `commonMain` shared modules to encapsulate dependency graphs per feature.
+2. **App Root Assembly:** Don't assume feature/core `@Module` classes are active automatically. Ensure they are included by the app root module (`@Module(includes = [...])`) in `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt` and `desktop/.../DesktopKoinModule.kt`.
+3. **No Platform Bleed:** Don't put Android framework dependencies (`Context`, `Activity`, `Application`) into shared `commonMain` business logic. Inject interfaces instead.
+4. **Resolution:** Resolve app-layer wrappers via `koinViewModel()` or injected bindings within Compose navigation graphs.
+
+### Anti-Patterns
+- **A1 Module Compile Safety:** Do **not** enable A1 `compileSafety`. We rely on Koin's A3 full-graph validation (`startKoin` / `VerifyModule`) because of our decoupled Clean Architecture design (interfaces in one module, implemented in another).
+- **Default Parameters:** Do **not** expect Koin to inject default parameters automatically. The K2 plugin's `skipDefaultValues = true` behavior skips parameters with default Kotlin values.
+
+## Navigation 3
+
+### Guidelines
+1. **Types:** Use Navigation 3 types consistently (`NavKey`, `NavBackStack`, `EntryProviderScope`).
+2. **Typed Routes:** Keep route definitions in `core:navigation/src/commonMain/.../Routes.kt` as `@Serializable sealed interface` hierarchies. Don't use ad-hoc strings.
+3. **Graph Assembly:** Define feature navigation graphs as extension functions on `EntryProviderScope` in `commonMain` (e.g., `fun EntryProviderScope.settingsGraph(backStack)`).
+4. **Host Integration:** Use `MeshtasticNavDisplay` (from `core:ui/commonMain`) as the Navigation 3 host. Do not configure decorators manually inside feature modules.
+5. **Back Handlers:** Use `NavigationBackHandler` from `androidx.navigationevent:navigationevent-compose` for back gestures in multiplatform code. Do not use Android's `BackHandler`.
+6. **Deep Links:** Use `DeepLinkRouter.route()` in `core:navigation` to synthesize typed backstacks from RESTful paths.
+
+### Anti-Patterns
+- **Single Backstack for Multiple Tabs:** Do **not** use a single `NavBackStack` list for multiple tabs. Use `MultiBackstack` (from `core:navigation`).
+- **Decorator Reuse Across Tabs:** Do **not** reuse the same `NavEntryDecorator` instances across different backstacks. When rendering an active tab in `MeshtasticNavDisplay`, you **must** supply a fresh set of decorators (using `remember(backStack) { ... }`) bound to the active backstack instance to prevent permanent `ViewModelStore` destruction.
+- **Custom Backstack Mutation:** Do **not** mutate back navigation with custom stacks disconnected from the app backstack. Mutate `NavBackStack` directly with `add(...)` and `removeLastOrNull()`.
+
+## Reference Anchors
+- **App Startup / Koin Bootstrap:** `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt`
+- **DI App Wiring:** `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`
+- **Shared Routes:** `core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt`
+- **Desktop Nav Shell:** `desktop/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt`
diff --git a/.skills/project-overview/SKILL.md b/.skills/project-overview/SKILL.md
new file mode 100644
index 000000000..0ceade61a
--- /dev/null
+++ b/.skills/project-overview/SKILL.md
@@ -0,0 +1,76 @@
+# Skill: Project Overview & Codebase Map
+
+## Description
+High-level project context, module directory, namespacing conventions, environment setup, and troubleshooting for Meshtastic-Android.
+
+## 1. Project Vision & Architecture
+Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, decentralized mesh networks. The goal is to decouple business logic from the Android framework, enabling expansion to iOS and Desktop while maintaining a high-performance native Android experience.
+
+- **Language:** Kotlin (primary), AIDL.
+- **Build System:** Gradle (Kotlin DSL). JDK 21 is REQUIRED.
+- **Target SDK:** API 36. Min SDK: API 26 (Android 8.0).
+- **Flavors:**
+ - `fdroid`: Open source only, no tracking/analytics.
+ - `google`: Includes Google Play Services (Maps) and DataDog analytics (RUM, Session Replay, Compose action tracking, custom `connect` RUM action). 100% sampling, Apple-parity environments ("Local"/"Production").
+- **KMP Modules:** Most `core:*` modules declare `jvm()`, `iosArm64()`, and `iosSimulatorArm64()` targets and compile clean across all.
+- **Android-only Modules:** `core:api` (AIDL), `core:barcode` (CameraX + flavor-specific decoder). Shared contracts abstracted into `core:ui/commonMain`.
+
+## 2. Codebase Map
+
+| Directory | Description |
+| :--- | :--- |
+| `app/` | Main application module. Contains `MainActivity`, Koin DI modules, and app-level logic. Uses package `org.meshtastic.app`. |
+| `build-logic/` | Convention plugins for shared build configuration (e.g., `meshtastic.kmp.feature`, `meshtastic.kmp.library`, `meshtastic.kmp.jvm.android`, `meshtastic.koin`). |
+| `config/` | Detekt static analysis rules (`config/detekt/detekt.yml`) and Spotless formatting config (`config/spotless/.editorconfig`). |
+| `docs/` | Architecture docs and agent playbooks. See `docs/kmp-status.md` and `docs/roadmap.md` for current status. |
+| `core/model` | Domain models and common data structures. |
+| `core:proto` | Protobuf definitions (Git submodule). |
+| `core:common` | Low-level utilities, I/O abstractions (Okio), and common types. |
+| `core:database` | Room KMP database implementation. |
+| `core:datastore` | Multiplatform DataStore for preferences. |
+| `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: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. |
+| `core:service` | KMP service layer; Android bindings stay in `androidMain`. |
+| `core:api` | Public AIDL/API integration module for external clients. |
+| `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/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`, `widget`). All are KMP except `widget`. Use `meshtastic.kmp.feature` convention plugin. |
+| `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. |
+| `desktop/` | Compose Desktop application. Thin host shell relying on feature modules for shared UI. Full Koin DI graph, TCP, Serial/USB, and BLE transports. Versioning via `config.properties` + `GitVersionValueSource`. |
+| `mesh_service_example/` | **DEPRECATED โ scheduled for removal.** Legacy sample app. See `core/api/README.md` for the current integration guide. |
+
+## 3. Namespacing
+- **Standard:** Use the `org.meshtastic.*` namespace for all code.
+- **Legacy:** Maintain the `com.geeksville.mesh` Application ID.
+
+## 4. Environment Setup
+1. **JDK 21 MUST be used** to prevent Gradle sync/build failures.
+2. **Secrets:** Copy `secrets.defaults.properties` to `local.properties`:
+ ```properties
+ MAPS_API_KEY=dummy_key
+ datadogApplicationId=dummy_id
+ datadogClientToken=dummy_token
+ ```
+
+## 5. Troubleshooting
+- **Build Failures:** Check `gradle/libs.versions.toml` for dependency conflicts.
+- **Missing Secrets:** Check `local.properties` (see Environment Setup above).
+- **JDK Version:** JDK 21 is required.
+- **Configuration Cache:** Add `--no-configuration-cache` flag if cache-related issues persist.
+- **Koin Injection Failures:** Verify the KMP component is included in `app` root module wiring (`AppKoinModule`).
+
+## Reference Anchors
+- **KMP Migration Status:** `docs/kmp-status.md`
+- **Roadmap:** `docs/roadmap.md`
+- **Architecture Decision Records:** `docs/decisions/`
+- **Version Catalog:** `gradle/libs.versions.toml`
diff --git a/.skills/testing-ci/SKILL.md b/.skills/testing-ci/SKILL.md
new file mode 100644
index 000000000..8342714de
--- /dev/null
+++ b/.skills/testing-ci/SKILL.md
@@ -0,0 +1,97 @@
+# Skill: Testing and CI Verification
+
+## Description
+Guidelines and commands for verifying code changes locally and understanding the Meshtastic-Android CI pipeline. Use this to determine which testing matrix is needed based on the change type.
+
+## 1) Baseline local verification order
+
+Run in this order for routine changes to ensure code formatting, analysis, and basic compilation:
+
+```bash
+./gradlew clean
+./gradlew spotlessCheck
+./gradlew spotlessApply
+./gradlew detekt
+./gradlew assembleDebug
+./gradlew test allTests
+```
+
+> **Why `test allTests` and not just `test`:**
+> In KMP modules, the `test` task name is **ambiguous**. Gradle matches both `testAndroid` and
+> `testAndroidHostTest` and refuses to run either, silently skipping KMP modules.
+> `allTests` is the `KotlinTestReport` lifecycle task registered by the KMP plugin.
+> Conversely, `allTests` does **not** cover pure-Android modules (`:app`, `:core:api`, etc.), which is why both `test` and `allTests` are needed.
+
+*Note: If testing Compose UI on the JVM (Robolectric) with Java 21, pin tests to `@Config(sdk = [34])` to avoid SDK 35 compatibility crashes.*
+
+## 2) Change-type verification matrix
+
+- `docs-only` changes: Usually no Gradle run required, but run `spotlessCheck` if practical.
+- `UI text/resource` changes: `spotlessCheck`, `detekt`, `assembleDebug`.
+- `feature/commonMain logic` changes: `spotlessCheck`, `detekt`, `test allTests`, `assembleDebug`.
+- `navigation/DI wiring` changes: `spotlessCheck`, `detekt`, `assembleDebug`, `test allTests`, plus flavor unit tests if available.
+ - If touching any KMP module, also run `kmpSmokeCompile`.
+- `worker/service/background` changes: Broad tests, targeted WorkManager checks.
+- `BLE/networking/core repository`: `spotlessCheck`, `detekt`, `assembleDebug`, `test allTests`.
+
+## 3) Flavor and instrumentation checks
+
+Run these when relevant to map, provider, or flavor-specific behavior:
+
+```bash
+./gradlew lintFdroidDebug lintGoogleDebug
+./gradlew testFdroidDebug testGoogleDebug
+./gradlew connectedAndroidTest
+```
+
+## 4) CI Pipeline Architecture
+
+CI is defined in `.github/workflows/reusable-check.yml` and structured as four parallel job groups:
+
+1. **`lint-check`** โ Runs spotless, detekt, Android lint, and KMP smoke compile in a single Gradle invocation (avoids 3x cold-start overhead). Uses `fetch-depth: 0` (full clone) for spotless ratcheting and version code calculation. Produces `cache_read_only` output and computed `version_code` for downstream jobs.
+2. **`test-shards`** โ A 3-shard matrix that runs unit tests in parallel (depends on `lint-check`):
+ - `shard-core`: `allTests` for all `core:*` KMP modules.
+ - `shard-feature`: `allTests` for all `feature:*` KMP modules.
+ - `shard-app`: Explicit test tasks for pure-Android/JVM modules (`app`, `desktop`, `core:barcode`, `mesh_service_example`).
+ Each shard generates Kover XML coverage and uploads test results + coverage to Codecov with per-shard flags.
+ Downstream jobs use `fetch-depth: 1` and receive `VERSION_CODE` from lint-check via env var, enabling shallow clones.
+3. **`android-check`** โ Builds APKs and runs instrumented tests (depends on `lint-check`).
+4. **`build-desktop`** โ Multi-OS matrix (`macos-latest`, `windows-latest`, `ubuntu-24.04`, `ubuntu-24.04-arm`) that builds desktop distributions via `createDistributable` (depends on `lint-check`).
+
+### Runner Strategy (Three Tiers)
+- **`ubuntu-24.04-arm`** โ Lightweight/utility jobs (status checks, labelers, triage, changelog, release metadata, stale, moderation). Benefits from ARM runners' shorter queue times.
+- **`ubuntu-24.04`** โ Main Gradle-heavy jobs (CI `lint-check`/`test-shards`/`android-check`, release builds, Dokka, CodeQL, publish, dependency-submission). Pin for reproducibility.
+- **Desktop runners:** Multi-OS matrix (`macos-latest`, `windows-latest`, `ubuntu-24.04`, `ubuntu-24.04-arm`) for the `build-desktop` job and release packaging.
+
+### CI Gradle Properties
+`gradle.properties` is tuned for local dev (8g heap, 4g Kotlin daemon). CI uses `.github/ci-gradle.properties`, which the `gradle-setup` composite action copies to `~/.gradle/gradle.properties`. Key CI overrides:
+- `org.gradle.daemon=false` (single-use runners)
+- `kotlin.incremental=false` (fresh checkouts)
+- `-Xmx4g` Gradle heap, `-Xmx2g` Kotlin daemon
+- VFS watching disabled, workers capped at 4
+- `org.gradle.isolated-projects=true` for better parallelism
+- Disables unused Android build features (`resvalues`, `shaders`)
+
+### CI Conventions
+- **KMP Smoke Compile:** `./gradlew kmpSmokeCompile` is a lifecycle task (registered in `RootConventionPlugin`) that auto-discovers all KMP modules and depends on their `compileKotlinJvm` + `compileKotlinIosSimulatorArm64` tasks.
+- **`maxParallelForks` CI logic:** `ProjectExtensions.kt` checks `project.findProperty("ci") == "true"` and uses full available processors in CI (4 forks on std runners) vs. half locally. All CI invocations pass `-Pci=true`.
+- **Detekt report formats:** Detekt.kt checks `project.findProperty("ci") == "true"` and disables html, txt, md reports in CI; only xml + sarif are retained for GitHub annotations.
+- **Robolectric SDK caching:** The `gradle-setup` composite action caches `~/.m2/repository/org/robolectric` to prevent flaky `SocketException` on SDK downloads. Cache key is `robolectric-{version}-sdk{level}` โ update when bumping version or SDK level.
+- **`mavenLocal()` gated:** Disabled by default to prevent CI cache poisoning. Pass `-PuseMavenLocal` for local JitPack testing.
+- **JUnit parallel execution:** Enabled project-wide with classes running sequentially (`junit.jupiter.execution.parallel.mode.classes.default=same_thread`) to avoid `Dispatchers.setMain()` races. Cross-module parallelism comes from Gradle forks (`maxParallelForks`).
+- **`test-retry` plugin:** Applied to all module types (maxRetries=2, maxFailures=10).
+- **`fail-fast: false`:** Test sharding does not cancel other shards on failure.
+- **Explicit Gradle task paths:** Prefer `app:lintFdroidDebug` over shorthand `lintDebug` in CI.
+- **Pull request CI:** Main-only (`.github/workflows/pull-request.yml` targets `main`).
+- **Cache writes:** Trusted on `main` and merge queue runs; other refs use read-only cache.
+- **Path filtering:** `check-changes` in `pull-request.yml` must include module dirs plus build/workflow entrypoints (`build-logic/**`, `gradle/**`, `.github/workflows/**`, `gradlew`, `settings.gradle.kts`, etc.).
+- **AboutLibraries:** Runs in `offlineMode` by default (no GitHub/SPDX API calls). Release builds pass `-PaboutLibraries.release=true` via Fastlane/Gradle CLI to enable remote license fetching. Do NOT re-gate on `CI` or `GITHUB_TOKEN` alone.
+
+## 5) Shell & Tooling Conventions
+- **Terminal Pagers:** When running shell commands like `git diff` or `git log`, ALWAYS use `--no-pager` (e.g., `git --no-pager diff`) to prevent getting stuck in an interactive prompt.
+- **Text Search:** Prefer `rg` (ripgrep) over `grep` or `find` for fast text searching across the codebase.
+
+## 6) Agent/Developer Guidance
+- Start with the smallest set that validates your touched area.
+- If unable to run full validation locally, report exactly what ran and what remains.
+- Keep documentation synced in `AGENTS.md` and `.skills/` directories.
diff --git a/AGENTS.md b/AGENTS.md
index b8fe03945..92009df61 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,208 +1,61 @@
-# Meshtastic Android - Agent Guide
+# Meshtastic Android - Unified Agent & Developer Guide
-This file serves as a comprehensive guide for AI agents and developers working on the `Meshtastic-Android` codebase. Use this as your primary reference for understanding the architecture, conventions, and strict rules of this project.
+
+You are an expert Android and Kotlin Multiplatform (KMP) engineer working on Meshtastic-Android, a decentralized mesh networking application. You must maintain strict architectural boundaries, use Modern Android Development (MAD) standards, and adhere to Compose Multiplatform and JetBrains Navigation 3 patterns.
+
-For execution-focused recipes, see `docs/agent-playbooks/README.md`.
+
+- **Project Goal:** Decouple business logic from the Android framework for seamless multi-platform execution (Android, Desktop, iOS) while maintaining a high-performance native Android experience.
+- **Language & Tech:** Kotlin 2.3+ (JDK 21 REQUIRED), Gradle Kotlin DSL, Ktor, Okio, Room KMP.
+- **Core Architecture:**
+ - `commonMain` is pure KMP. `androidMain` is strictly for Android framework bindings.
+ - App root DI and graph assembly live in the `app` and `desktop` host shells.
+- **Skills Directory:** You **MUST** consult the relevant `.skills/` module before executing work:
+ - `.skills/project-overview/` - Codebase map, module directory, namespacing, environment setup, troubleshooting.
+ - `.skills/kmp-architecture/` - Bridging, expect/actual, source-sets, catalog aliases, build-logic conventions.
+ - `.skills/compose-ui/` - Adaptive UI, placeholders, string resources.
+ - `.skills/navigation-and-di/` - JetBrains Navigation 3 & Koin 4.2+ annotations.
+ - `.skills/testing-ci/` - Validation commands, CI pipeline architecture, CI Gradle properties.
+ - `.skills/implement-feature/` - Step-by-step feature workflow.
+ - `.skills/code-review/` - PR validation checklist.
+- **Active Status:** Read `docs/kmp-status.md` and `docs/roadmap.md` to understand the current KMP migration epoch.
+
-## 1. Project Vision & Architecture
-Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, decentralized mesh networks. The goal is to decouple business logic from the Android framework, enabling future expansion to iOS and other platforms while maintaining a high-performance native Android experience.
+
+- **Think First:** Reason through the problem before writing code. For complex KMP tasks involving multiple modules or source sets, outline your approach step-by-step before executing.
+- **Plan Before Execution:** Use the git-ignored `.agent_plans/` directory to write markdown implementation plans (`plan.md`) and Mermaid diagrams (`.mmd`) for complex refactors before modifying code.
+- **Atomic Execution:** Follow your plan step-by-step. Do not jump ahead. Use TDD where feasible (write `commonTest` fakes first).
+- **Baseline Verification:** Always instruct the user (or use your CLI tools) to run the baseline check before finishing:
+ `./gradlew clean spotlessCheck spotlessApply detekt assembleDebug test allTests`
+
-- **Language:** Kotlin (primary), AIDL.
-- **Build System:** Gradle (Kotlin DSL). JDK 21 is REQUIRED.
-- **Target SDK:** API 36. Min SDK: API 26 (Android 8.0).
-- **Flavors:**
- - `fdroid`: Open source only, no tracking/analytics.
- - `google`: Includes Google Play Services (Maps) and DataDog analytics (RUM, Session Replay, Compose action tracking, custom `connect` RUM action). 100% sampling, Apple-parity environments ("Local"/"Production").
-- **Core Architecture:** Modern Android Development (MAD) with KMP core.
- - **KMP Modules:** Most `core:*` modules. All declare `jvm()`, `iosArm64()`, and `iosSimulatorArm64()` targets and compile clean across all.
- - **Android-only Modules:** `core:api` (AIDL), `core:barcode` (CameraX + flavor-specific decoder). Shared contracts abstracted into `core:ui/commonMain`.
- - **UI:** Jetpack Compose Multiplatform (Material 3).
- - **DI:** Koin Annotations with K2 compiler plugin. Root graph assembly is centralized in `app` and `desktop`.
- - **Navigation:** JetBrains Navigation 3 (Scene-based architecture) with shared backstack state. Deep linking uses RESTful paths (e.g. `/nodes/1234`) parsed by `DeepLinkRouter` in `core:navigation`.
- - **Lifecycle:** JetBrains multiplatform `lifecycle-viewmodel-compose` and `lifecycle-runtime-compose`.
- - **Adaptive UI:** Material 3 Adaptive (v1.3+) with support for Large (1200dp) and Extra-large (1600dp) breakpoints.
- - **Database:** Room KMP.
+
+- **Codebase Search:** Use whatever search and navigation tools your environment provides (file search, grep/ripgrep, symbol lookup, semantic search, etc.) to map out project boundaries before coding. Prefer `rg` (ripgrep) over `grep` or `find` for raw text search.
+- **Terminal Pagers:** When running shell commands like `git diff` or `git log`, ALWAYS use `--no-pager` (e.g., `git --no-pager diff`) to prevent getting stuck in an interactive prompt.
+- **Fetch Up-to-Date Docs:** If your environment supports web search, MCP servers, or documentation lookup tools, actively query them for the latest documentation on Koin 4.x, JetBrains Navigation 3, and Compose Multiplatform 1.11.
+- **Clone Reference Repos:** If documentation is insufficient, use shell commands to clone bleeding-edge KMP dependency repositories into the local `.agent_refs/` directory (git-ignored) to inspect their source and test suites. Recommended:
+ - `https://github.com/JetBrains/kotlin-multiplatform-dev-docs` (Official Docs)
+ - `https://github.com/InsertKoinIO/koin` (Koin Annotations 4.x)
+ - `https://github.com/JetBrains/compose-multiplatform` (Navigation 3, Adaptive UI)
+ - `https://github.com/JuulLabs/kable` (BLE)
+ - `https://github.com/coil-kt/coil` (Coil 3 KMP)
+ - `https://github.com/ktorio/ktor` (Ktor Networking)
+- **Formatting Hooks:** Always run `./gradlew spotlessApply` as an automatic formatting hook to fix style violations after editing.
+
-## 2. Codebase Map
+
+`AGENTS.md` is the single source of truth for agent instructions. Agent-specific files redirect here:
+- `.github/copilot-instructions.md` โ Copilot redirect to `AGENTS.md`.
+- `CLAUDE.md` โ Claude Code entry point; imports `AGENTS.md` via `@AGENTS.md` and adds Claude-specific instructions.
+- `GEMINI.md` โ Gemini redirect to `AGENTS.md`. Gemini CLI also configured via `.gemini/settings.json` to read `AGENTS.md` directly.
-| Directory | Description |
-| :--- | :--- |
-| `app/` | Main application module. Contains `MainActivity`, Koin DI modules, and app-level logic. Uses package `org.meshtastic.app`. |
-| `build-logic/` | Convention plugins for shared build configuration (e.g., `meshtastic.kmp.feature`, `meshtastic.kmp.library`, `meshtastic.kmp.jvm.android`, `meshtastic.koin`). |
-| `config/` | Detekt static analysis rules (`config/detekt/detekt.yml`) and Spotless formatting config (`config/spotless/.editorconfig`). |
-| `docs/` | Architecture docs and agent playbooks. See `docs/agent-playbooks/README.md` for version baseline and task recipes. |
-| `core/model` | Domain models and common data structures. |
-| `core:proto` | Protobuf definitions (Git submodule). |
-| `core:common` | Low-level utilities, I/O abstractions (Okio), and common types. |
-| `core:database` | Room KMP database implementation. |
-| `core:datastore` | Multiplatform DataStore for preferences. |
-| `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:di` | Common DI qualifiers and dispatchers. |
-| `core:navigation` | Shared navigation keys/routes for Navigation 3 using `@Serializable sealed interface` hierarchies per feature domain (e.g., `SettingsRoute`, `NodesRoute`). `DeepLinkRouter` for typed backstack synthesis, and `MeshtasticNavSavedStateConfig` with `subclassesOfSealed()` for automatic polymorphic backstack persistence โ new routes are registered at compile time. |
-| `core:ui` | Shared Compose UI components (`MeshtasticAppShell`, `MeshtasticNavDisplay`, `MeshtasticNavigationSuite`, `AlertHost`, `SharedDialogs`, `PlaceholderScreen`, `MainAppBar`, dialogs, preferences) and platform abstractions. |
-| `core:service` | KMP service layer; Android bindings stay in `androidMain`. |
-| `core:api` | Public AIDL/API integration module for external clients. |
-| `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/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`, `widget`). All are KMP with `jvm()` and `ios()` targets except `widget`. Use `meshtastic.kmp.feature` convention plugin. |
-| `feature/wifi-provision` | KMP WiFi provisioning via BLE (Nymea protocol). Scans for provisioning devices, lists available networks, applies credentials. Uses `core:ble` Kable abstractions. |
-| `feature/firmware` | Fully KMP firmware update system: Unified OTA (BLE + WiFi via Kable/Ktor), native Nordic Secure DFU protocol (pure KMP, no Nordic library), USB/UF2 updates, and `FirmwareRetriever` with manifest-based resolution. Desktop is a first-class target. |
-| `desktop/` | Compose Desktop application โ first non-Android KMP target. Thin host shell relying entirely on feature modules for shared UI. Full Koin DI graph, TCP, Serial/USB, and BLE transports with `want_config` handshake. Versioning mirrors Android via `config.properties` + `GitVersionValueSource`; a `generateDesktopBuildConfig` task produces `DesktopBuildConfig.kt` at build time. |
-| `mesh_service_example/` | **DEPRECATED โ scheduled for removal.** Legacy sample app showing `core:api` service integration. Do not add code here. See `core/api/README.md` for the current integration guide. |
+Do NOT duplicate content into agent-specific files. When you modify architecture, module targets, CI tasks, validation commands, or agent workflow rules, update `AGENTS.md`, `.skills/`, `docs/kmp-status.md`, and `docs/decisions/architecture-review-2026-03.md` as needed.
+
-## 3. Development Guidelines & Coding Standards
-
-### A. UI Development (Jetpack Compose)
-- **Material 3:** The app uses Material 3.
-- **Strings:** MUST use the **Compose Multiplatform Resource** library in `core:resources` (`stringResource(Res.string.your_key)`). For ViewModels or non-composable Coroutines, use the asynchronous `getStringSuspend(Res.string.your_key)`. NEVER use hardcoded strings, and NEVER use the blocking `getString()` in a coroutine.
-- **String formatting:** CMP's `stringResource(res, args)` / `getString(res, args)` only support `%N$s` (string) and `%N$d` (integer) positional specifiers. Float formats like `%N$.1f` are NOT supported โ they pass through unsubstituted. For float values, pre-format in Kotlin using `NumberFormatter.format(value, decimalPlaces)` from `core:common` and pass the result as a `%N$s` string arg. Use bare `%` (not `%%`) for literal percent signs in CMP-consumed strings, since CMP does not convert `%%` to `%`. For JVM-only code using `formatString()` (which wraps `String.format()`), full printf specifiers including `%N$.Nf` and `%%` are supported.
-- **Dialogs:** Use centralized components in `core:ui` (e.g., `MeshtasticResourceDialog`).
-- **Alerts:** Use `AlertHost(alertManager)` from `core:ui/commonMain` in each platform host shell (`Main.kt`, `DesktopMainScreen.kt`). For global responses like traceroute and firmware validation, use the specialized common handlers: `TracerouteAlertHandler(uiViewModel)` and `FirmwareVersionCheck(uiViewModel)`. Do NOT duplicate inline alert-rendering logic or trigger alerts directly during composition. For shared QR/contact dialogs, use the `SharedDialogs(uiViewModel)` composable.
-- **Placeholders:** For desktop/JVM features not yet implemented, use `PlaceholderScreen(name)` from `core:ui/commonMain`. Do NOT define inline placeholder composables in feature modules.
-- **Theme Picker:** Use `ThemePickerDialog` and `ThemeOption` from `feature:settings/commonMain`. Do NOT duplicate the theme dialog or enum in platform-specific source sets.
-- **Adaptive Layouts:** Use `currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true)` to support the 2026 Desktop Experience breakpoints. Prioritize **higher information density** and mouse-precision interactions for Desktop and External Display (Android 16 QPR3) targets. **Investigate 3-pane "Power User" scenes** (e.g., Node List + Detail + Map/Charts) using Navigation 3 Scenes, `extraPane()`, and draggable dividers (`VerticalDragHandle` + `paneExpansionState`) for widths โฅ 1200dp.
-- **Platform/Flavor UI:** Inject platform-specific behavior (e.g., map providers) via `CompositionLocal` from `app`.
-
-### B. Logic & Data Layer
-- **KMP Focus:** All business logic must reside in `commonMain` of the respective `core` module.
-- **Platform purity:** Never import `java.*` or `android.*` in `commonMain`. Use KMP alternatives:
- - `java.util.Locale` โ Kotlin `uppercase()` / `lowercase()` or `expect`/`actual`.
- - `java.util.concurrent.ConcurrentHashMap` โ `atomicfu` or `Mutex`-guarded `mutableMapOf()`.
- - `java.util.concurrent.locks.*` โ `kotlinx.coroutines.sync.Mutex`.
- - `java.io.*` โ Okio (`BufferedSource`/`BufferedSink`). Note: JetBrains now recommends `kotlinx-io` as the official Kotlin I/O library (built on Okio). This project standardizes on Okio directly; do not migrate without explicit decision.
- - `kotlinx.coroutines.Dispatchers.IO` โ `org.meshtastic.core.common.util.ioDispatcher` (expect/actual). Note: `Dispatchers.IO` is available in `commonMain` since kotlinx.coroutines 1.8.0, but this project uses the `ioDispatcher` wrapper for consistency.
-- **Shared helpers over duplicated lambdas:** When `androidMain` and `jvmMain` contain identical pure-Kotlin logic (formatting, action dispatch, validation), extract it to a function in `commonMain`. Examples: `formatLogsTo()` in `feature:settings`, `handleNodeAction()` in `feature:node`, `findNodeByNameSuffix()` in `feature:connections`, `MeshtasticAppShell` in `core:ui/commonMain`, and `BaseRadioTransportFactory` in `core:network/commonMain`.
-- **KMP file naming:** In KMP modules, `commonMain` and platform source sets (`androidMain`, `jvmMain`) share the same package namespace. If both contain a file with the same name (e.g., `LogExporter.kt`), the Kotlin/JVM compiler will produce a duplicate class error. Use distinct filenames: keep the `expect` declaration in `LogExporter.kt` and put shared helpers in a separate file like `LogFormatter.kt`.
-- **`jvmAndroidMain` source set:** Modules that share JVM-specific code between Android and Desktop apply the `meshtastic.kmp.jvm.android` convention plugin. This creates a `jvmAndroidMain` source set via Kotlin's hierarchy template API. Used in `core:common`, `core:model`, `core:data`, `core:network`, and `core:ui`.
-- **Hierarchy template first:** Prefer Kotlin's default hierarchy template and convention plugins over manual `dependsOn(...)` graphs. Manual source-set wiring should be reserved for cases the template cannot model.
-- **`expect`/`actual` restraint:** Prefer interfaces + DI for platform capabilities; use `expect`/`actual` for small unavoidable platform primitives. Avoid broad expect/actual class hierarchies when an interface-based boundary is sufficient.
-- **Feature navigation graphs:** Feature modules export Navigation 3 graph functions as extension functions on `EntryProviderScope` in `commonMain` (e.g., `fun EntryProviderScope.settingsGraph(backStack: NavBackStack)`). Host shells (`app`, `desktop`) assemble these into a single `entryProvider` block. Do NOT define navigation graphs in platform-specific source sets.
-- **Concurrency:** Use Kotlin Coroutines and Flow.
-- **Dependency Injection:** Use **Koin Annotations** with the K2 compiler plugin (`koin-plugin` in version catalog). The `koin-annotations` library version is unified with `koin-core` (both use `version.ref = "koin"`). The `KoinConventionPlugin` uses the typed `KoinGradleExtension` to configure the K2 plugin (e.g., `compileSafety.set(false)`). Keep root graph assembly in `app`.
-- **ViewModels:** Follow the MVI/UDF pattern. Use the multiplatform `androidx.lifecycle.ViewModel` in `commonMain`. Both `app` and `desktop` use `MeshtasticNavDisplay` from `core:ui/commonMain`, which configures `ViewModelStoreNavEntryDecorator` + `SaveableStateHolderNavEntryDecorator`. ViewModels obtained via `koinViewModel()` inside `entry` blocks are scoped to the entry's backstack lifetime and cleared on pop.
-- **Navigation host:** Use `MeshtasticNavDisplay` from `core:ui/commonMain` instead of calling `NavDisplay` directly. It provides entry-scoped ViewModel decoration, `DialogSceneStrategy` for dialog entries, and a shared 350 ms crossfade transition. Host modules (`app`, `desktop`) should NOT configure `entryDecorators`, `sceneStrategies`, or `transitionSpec` themselves.
-- **BLE:** All Bluetooth communication must route through `core:ble` using Kable.
-- **Networking:** Pure **Ktor** โ no OkHttp anywhere. Engines: `ktor-client-android` for Android, `ktor-client-java` for desktop/JVM. Use Ktor `Logging` plugin for HTTP debug logging (not OkHttp interceptors). `HttpClient` is provided via Koin in `app/di/NetworkModule` and `core:network/di/CoreNetworkAndroidModule`.
-- **Image Loading (Coil):** Use `coil-network-ktor3` with `KtorNetworkFetcherFactory` on **all** platforms. `ImageLoader` is configured in host modules only (`app` via Koin `@Single`, `desktop` via `setSingletonImageLoaderFactory`). Feature modules depend only on `libs.coil` (coil-compose) for `AsyncImage` โ never add `coil-network-*` or `coil-svg` to feature modules.
-- **Dependencies:** Check `gradle/libs.versions.toml` before assuming a library is available.
-- **AboutLibraries:** Runs in `offlineMode` by default (no GitHub/SPDX API calls). Release builds pass `-PaboutLibraries.release=true` via Fastlane properties (Android) or Gradle CLI (desktop) to enable remote license/funding fetching. Do NOT re-gate on `CI` or `GITHUB_TOKEN` alone โ that burns API calls on every PR check.
-- **JetBrains fork aliases:** Version catalog aliases for JetBrains-forked AndroidX artifacts use the `jetbrains-*` prefix (e.g., `jetbrains-lifecycle-runtime-compose`, `jetbrains-navigation3-ui`). Plain `androidx-*` aliases are true Google AndroidX artifacts. Never mix them up in `commonMain`.
-- **Compose Multiplatform:** Version catalog aliases for Compose Multiplatform artifacts use the `compose-multiplatform-*` prefix (e.g., `compose-multiplatform-material3`, `compose-multiplatform-foundation`). Never use plain `androidx.compose` dependencies in common Main.
-- **Room KMP:** Always use `factory = { MeshtasticDatabaseConstructor.initialize() }` in `Room.databaseBuilder` and `inMemoryDatabaseBuilder`. DAOs and Entities reside in `commonMain`.
-- **QR Codes:** Use `rememberQrCodePainter` from `core:ui/commonMain` (powered by `qrcode-kotlin`) for generating QR codes. Do not use Android Bitmap or ZXing APIs in common code.
-- **Testing:** Write ViewModel and business logic tests in `commonTest`. Use `Turbine` for Flow testing, `Kotest` for property-based testing, and `Mokkery` for mocking. Use `core:testing` shared fakes.
-- **Build-logic conventions:** In `build-logic/convention`, prefer lazy Gradle configuration (`configureEach`, `withPlugin`, provider APIs). Avoid `afterEvaluate` in convention plugins unless there is no viable lazy alternative.
-
-### C. Namespacing
-- **Standard:** Use the `org.meshtastic.*` namespace for all code.
-- **Legacy:** Maintain the `com.geeksville.mesh` Application ID.
-
-## 4. Execution Protocol
-
-### A. Environment Setup
-1. **JDK 21 MUST be used** to prevent Gradle sync/build failures.
-2. **Secrets:** You must copy `secrets.defaults.properties` to `local.properties`:
- ```properties
- MAPS_API_KEY=dummy_key
- datadogApplicationId=dummy_id
- datadogClientToken=dummy_token
- ```
-
-### B. Strict Execution Commands
-Always run commands in the following order to ensure reliability. Do not attempt to bypass `clean` if you are facing build issues.
-
-**Baseline (recommended order):**
-```bash
-./gradlew clean
-./gradlew spotlessCheck
-./gradlew spotlessApply
-./gradlew detekt
-./gradlew assembleDebug
-./gradlew test allTests
-```
-
-**Testing:**
-```bash
-# Full host-side unit test run (required โ see note below):
-./gradlew test allTests
-
-# Pure-Android / pure-JVM modules only (app, desktop, core:api, core:barcode, feature:widget, mesh_service_example):
-./gradlew test
-
-# KMP modules only (all core:* KMP + all feature:* KMP modules โ jvmTest + testAndroidHostTest + iosSimulatorArm64Test):
-./gradlew allTests
-
-# CI-aligned flavor-explicit Android unit tests:
-./gradlew testFdroidDebugUnitTest testGoogleDebugUnitTest
-
-./gradlew connectedAndroidTest # Run instrumented tests
-./gradlew testFdroidDebug testGoogleDebug # Flavor-specific unit tests
-./gradlew lintFdroidDebug lintGoogleDebug # Flavor-specific lint checks
-```
-
-> **Why `test allTests` and not just `test`:**
-> In KMP modules, the `test` task name is **ambiguous** โ Gradle matches both `testAndroid` and
-> `testAndroidHostTest` and refuses to run either, silently skipping all 25 KMP modules.
-> `allTests` is the `KotlinTestReport` lifecycle task registered by the KMP Gradle plugin for each
-> KMP module. It runs `jvmTest`, `testAndroidHostTest` (where declared with `withHostTest {}`), and
-> `iosSimulatorArm64Test` (disabled at execution โ iOS targets are compile-only). Conversely,
-> `allTests` does **not** cover the pure-Android modules (`:app`, `:core:api`, `:core:barcode`,
-> `:feature:widget`, `:mesh_service_example`, `:desktop`), which is why both are needed.
-
-*Note: If testing Compose UI on the JVM (Robolectric) with Java 21, pin your tests to `@Config(sdk = [34])` to avoid SDK 35 compatibility crashes.*
-
-**CI workflow conventions (GitHub Actions):**
-- Reusable CI in `.github/workflows/reusable-check.yml` is structured as four parallel job groups:
- 1. **`lint-check`** โ Runs spotless, detekt, Android lint, and KMP smoke compile in a single Gradle invocation. Uses `fetch-depth: 0` (full clone) for spotless ratcheting and version code calculation. Produces `cache_read_only` output and computed `version_code` for downstream jobs.
- 2. **`test-shards`** โ A 3-shard matrix that runs unit tests in parallel (depends on `lint-check`):
- - `shard-core`: `allTests` for all `core:*` KMP modules.
- - `shard-feature`: `allTests` for all `feature:*` KMP modules.
- - `shard-app`: Explicit test tasks for pure-Android/JVM modules (`app`, `desktop`, `core:barcode`, `mesh_service_example`).
- Each shard generates its own Kover XML coverage and uploads test results + coverage to Codecov with per-shard flags.
- Downstream jobs (test-shards, android-check, build-desktop) use `fetch-depth: 1` and receive `VERSION_CODE` from lint-check via env var, enabling shallow clones.
- 3. **`android-check`** โ Builds APKs and runs instrumented tests (depends on `lint-check`).
- 4. **`build-desktop`** โ Multi-OS matrix (`macos-latest`, `windows-latest`, `ubuntu-24.04`, `ubuntu-24.04-arm`) that builds runnable desktop distributions via `createDistributable` (depends on `lint-check`). The Kotlin/Native host-platform warning on `linux-aarch64` is non-fatal; only JVM targets are compiled for desktop.
-- Test sharding uses `fail-fast: false` so a failure in one shard does not cancel the others.
-- JUnit Platform parallel execution is enabled project-wide with classes running sequentially (`junit.jupiter.execution.parallel.mode.classes.default=same_thread`) to avoid `Dispatchers.setMain()` races (JVM-global singleton used by 19+ ViewModel test classes). Cross-module parallelism comes from Gradle forks (`maxParallelForks`).
-- `test-retry` plugin (maxRetries=2, maxFailures=10) is applied to all module types: `AndroidApplicationConventionPlugin`, `AndroidLibraryConventionPlugin`, and `KmpLibraryConventionPlugin`.
-- Android matrix job runs explicit assemble tasks for `app` and `mesh_service_example`; instrumentation is enabled by input and matrix API.
-- Prefer explicit Gradle task paths in CI (for example `app:lintFdroidDebug`, `app:connectedGoogleDebugAndroidTest`) instead of shorthand tasks like `lintDebug`.
-- Pull request CI is main-only (`.github/workflows/pull-request.yml` targets `main` branch).
-- Gradle cache writes are trusted on `main` and merge queue runs (`merge_group` / `gh-readonly-queue/*`); other refs use read-only cache mode in reusable CI.
-- PR `check-changes` path filtering lives in `.github/workflows/pull-request.yml` and must include module dirs plus build/workflow entrypoints (`build-logic/**`, `gradle/**`, `.github/workflows/**`, `gradlew`, `settings.gradle.kts`, etc.) so CI is not skipped for infra-only changes.
-- **Runner strategy (three tiers):**
- - **`ubuntu-24.04-arm`** โ Lightweight/utility jobs (status checks, labelers, triage, changelog, release metadata, stale, moderation). These run only shell scripts or GitHub API calls and benefit from ARM runners' shorter queue times.
- - **`ubuntu-24.04`** โ Main Gradle-heavy jobs (CI `lint-check`/`test-shards`/`android-check`, release builds, Dokka, CodeQL, publish, dependency-submission). Pin where possible for reproducibility.
- - **Desktop runners:** Reusable CI uses a multi-OS matrix (`macos-latest`, `windows-latest`, `ubuntu-24.04`, `ubuntu-24.04-arm`) for the `build-desktop` job in `.github/workflows/reusable-check.yml`; release packaging matrix remains `[macos-latest, windows-latest, ubuntu-24.04, ubuntu-24.04-arm]`.
-- **CI Gradle properties:** `gradle.properties` is tuned for local dev (8g heap, 4g Kotlin daemon). CI uses `.github/ci-gradle.properties`, which the `gradle-setup` composite action copies to `~/.gradle/gradle.properties` before any Gradle invocation. Key CI overrides: `org.gradle.daemon=false` (single-use runners), `kotlin.incremental=false` (fresh checkouts), `-Xmx4g` Gradle heap, `-Xmx2g` Kotlin daemon, VFS watching disabled, workers capped at 4, `org.gradle.isolated-projects=true` for better parallelism. Disables unused Android build features (`resvalues`, `shaders`). This follows the nowinandroid `ci-gradle.properties` pattern.
-- **CI optimization strategies (2026):** Applied comprehensive CI optimizations (P0-P3):
- - **P0 (merged Gradle invocations):** `lint-check` merges spotlessCheck, detekt, android lint, and kmpSmokeCompile into a single Gradle invocation to avoid 3x cold-start overhead. Uses `filter: 'blob:none'` for blobless git clone. Switches submodules from `'recursive'` to boolean (saves overhead on nested submodule discovery).
- - **P1 (reduced PR overhead):** Added `run_coverage` workflow input (default: true); PRs skip Kover reports via conditional tasks in test-shards matrix. Increased `maxParallelForks` in CI to use all available processors (4 on standard runners) when `ci=true` property is set, vs. half locally for system responsiveness.
- - **P2 (build feature optimization):** Detekt disables non-essential report formats in CI (html, txt, md); retains only xml + sarif for GitHub annotations. Disables unused Android build features (resvalues, shaders) in `ci-gradle.properties`.
- - **P3 (structural improvement):** Removed `verify-check-changes-filter` from `validate-and-build` dependencies; it now runs in parallel as a standalone required check instead of gating the main build.
-- **`maxParallelForks` CI logic:** ProjectExtensions.kt line ~79 checks `project.findProperty("ci") == "true"` and uses full available processors in CI (4 forks on std runners) vs. half locally. All CI invocations pass `-Pci=true` to enable this.
-- **Detekt report formats:** Detekt.kt line ~44 checks `project.findProperty("ci") == "true"` and disables html, txt, md reports in CI; only xml + sarif are required for GitHub reporting.
-- **KMP Smoke Compile:** Use `./gradlew kmpSmokeCompile` instead of listing individual module compile tasks. The `kmpSmokeCompile` lifecycle task (registered in `RootConventionPlugin`) auto-discovers all KMP modules and depends on their `compileKotlinJvm` + `compileKotlinIosSimulatorArm64` tasks.
-- **Robolectric SDK caching:** The `gradle-setup` composite action caches `~/.m2/repository/org/robolectric` to prevent flaky `SocketException` failures when Robolectric downloads instrumented SDK jars. The cache key is `robolectric-{version}-sdk{level}` โ update it when bumping the Robolectric version in `libs.versions.toml` or the SDK level in `robolectric.properties` / `@Config(sdk = ...)`.
-- **`mavenLocal()` gated:** The `mavenLocal()` repository is disabled by default to prevent CI cache poisoning. For local JitPack testing, pass `-PuseMavenLocal` to Gradle.
-- **Terminal Pagers:** When running shell commands like `git diff` or `git log`, ALWAYS use `--no-pager` (e.g., `git --no-pager diff`) to prevent the agent from getting stuck in an interactive prompt.
-- **Text Search:** Prefer using `rg` (ripgrep) over `grep` or `find` for fast text searching across the codebase.
-
-### C. Documentation Sync
-`AGENTS.md` is the single source of truth for agent instructions. `.github/copilot-instructions.md` and `GEMINI.md` are thin stubs that redirect here โ do NOT duplicate content into them.
-
-When you modify architecture, module targets, CI tasks, validation commands, or agent workflow rules, update `AGENTS.md`, `docs/agent-playbooks/*`, `docs/kmp-status.md`, and `docs/decisions/architecture-review-2026-03.md` as needed.
-
-## 5. Troubleshooting
-- **Build Failures:** Check `gradle/libs.versions.toml` for dependency conflicts.
-- **Missing Secrets:** Check `local.properties`.
-- **JDK Version:** JDK 21 is required.
-- **Configuration Cache:** Add `--no-configuration-cache` flag if cache-related issues persist.
-- **Koin Injection Failures:** Verify the KMP component is included in `app` root module wiring (`AppKoinModule`).
\ No newline at end of file
+
+- **No Lazy Coding:** DO NOT use placeholders like `// ... existing code ...`. Always provide complete, valid code blocks for the sections you modify to ensure correct diff application.
+- **No Framework Bleed:** NEVER import `java.*` or `android.*` in `commonMain`.
+- **Koin Annotations:** Use `@Single`, `@Factory`, and `@KoinViewModel` inside `commonMain` instead of manual constructor trees. Do not enable A1 module compile safety.
+- **CMP Over Android:** Use `compose-multiplatform` constraints (e.g., no float formatting in `stringResource`).
+- **Always Check Docs:** If unsure about an abstraction, search `core:ui/commonMain` or `core:navigation/commonMain` before assuming it doesn't exist.
+
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..39958ecd0
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,9 @@
+# Meshtastic Android - Claude Code Guide
+
+@AGENTS.md
+
+## Claude-Specific Instructions
+
+- **Think First:** Always outline your step-by-step reasoning inside `` tags before writing code or shell commands. Claude models perform significantly better on complex KMP tasks when they "think out loud" first.
+- **Skills:** The `.skills/` directory contains task-specific instruction modules. Load them as needed โ only the skill relevant to your current task.
+- **Plan Mode:** Use plan mode for architectural changes spanning multiple modules. Write plans to `.agent_plans/` (git-ignored).
diff --git a/GEMINI.md b/GEMINI.md
index 9076b718e..72a350afb 100644
--- a/GEMINI.md
+++ b/GEMINI.md
@@ -1,6 +1,6 @@
-# Meshtastic Android - Agent Guide
+# Meshtastic Android - Google Gemini Guide
-**Canonical instructions live in [`AGENTS.md`](AGENTS.md).** This file exists as `GEMINI.md` so Google Gemini discovers it automatically.
+> **Note:** The canonical instructions for all AI Agents have been deduplicated.
-See [AGENTS.md](AGENTS.md) for architecture, conventions, execution protocol, and coding standards.
-See [docs/agent-playbooks/README.md](docs/agent-playbooks/README.md) for version baselines and task recipes.
+You MUST immediately read and internalize the unified instructions located at the root of the repository in `AGENTS.md`.
+After reading `AGENTS.md`, consult the `.skills/` directory for task-specific playbooks.
diff --git a/SOUL.md b/SOUL.md
index 793387334..45924b40f 100644
--- a/SOUL.md
+++ b/SOUL.md
@@ -26,6 +26,6 @@ I am an **Android Architect**. My primary purpose is to evolve the Meshtastic-An
I learn from the existing codebase. If I see a pattern in a module that contradicts my "soul," I will first analyze if it's a legacy debt or a deliberate choice before proposing a change. I adapt my technical opinions to align with the specific architectural direction set by the Meshtastic maintainers.
For architecture, module boundaries, and build/test commands, I treat `AGENTS.md` as the source of truth.
-For implementation recipes and verification scope, I use `docs/agent-playbooks/README.md`.
+For implementation recipes and verification scope, I use `.skills/` directory.
diff --git a/docs/agent-playbooks/README.md b/docs/agent-playbooks/README.md
deleted file mode 100644
index 5d25a5509..000000000
--- a/docs/agent-playbooks/README.md
+++ /dev/null
@@ -1,52 +0,0 @@
-# Agent Playbooks
-
-These playbooks are execution-focused guidance for common changes in this repository.
-
-Use `AGENTS.md` as the source of truth for architecture boundaries and required conventions. If guidance conflicts, follow `AGENTS.md` and current code patterns.
-
-## Version baseline for external docs
-
-When checking upstream docs/examples, match these repository-pinned versions from `gradle/libs.versions.toml`:
-
-- Kotlin: `2.3.20`
-- Koin: `4.2.0` (`koin-annotations` `4.2.0` โ uses same version as `koin-core`; compiler plugin `0.4.1`)
-- JetBrains Navigation 3: `1.1.0-beta01` (`org.jetbrains.androidx.navigation3`)
-- JetBrains Lifecycle (multiplatform): `2.11.0-alpha02` (`org.jetbrains.androidx.lifecycle`)
-- AndroidX Lifecycle (Android-only): `2.10.0` (`androidx.lifecycle`)
-- Kotlin Coroutines: `1.10.2`
-- Compose Multiplatform: `1.11.0-beta01`
-- JetBrains Material 3 Adaptive: `1.3.0-alpha06` (`org.jetbrains.compose.material3.adaptive`)
-
-Prefer versioned docs pages that match those versions (for example, Koin `4.2` docs rather than older `4.0/4.1` pages).
-
-## Dependency alias quick-reference
-
-Version catalog aliases split cleanly by fork provenance. **Use the right prefix for the right source set.**
-
-| Alias prefix | Coordinates | Use in |
-|---|---|---|
-| `jetbrains-lifecycle-*` | `org.jetbrains.androidx.lifecycle:*` | `commonMain`, `androidMain` |
-| `jetbrains-navigation3-ui` | `org.jetbrains.androidx.navigation3:navigation3-ui` | `commonMain`, `androidMain` |
-| `jetbrains-navigationevent-*` | `org.jetbrains.androidx.navigationevent:*` | `commonMain`, `androidMain` |
-| `jetbrains-compose-material3-adaptive-*` | `org.jetbrains.compose.material3.adaptive:*` | `commonMain`, `androidMain` |
-| `androidx-lifecycle-process` | `androidx.lifecycle:lifecycle-process` | `androidMain` only โ `ProcessLifecycleOwner` |
-| `androidx-lifecycle-testing` | `androidx.lifecycle:lifecycle-runtime-testing` | `androidUnitTest` only |
-
-> **Note:** JetBrains does not publish a separate `navigation3-runtime` artifact โ `navigation3-ui` is the only artifact. The version catalog only defines `jetbrains-navigation3-ui`. The `lifecycle-runtime-ktx` and `lifecycle-viewmodel-ktx` KTX aliases were removed (extensions merged into base artifacts since Lifecycle 2.8.0).
-
-Quick references:
-
-- Koin annotations (4.2 docs): `https://insert-koin.io/docs/reference/koin-annotations/start`
-- Koin KMP docs: `https://insert-koin.io/docs/reference/koin-annotations/kmp`
-- AndroidX Navigation 3 release notes: `https://developer.android.com/jetpack/androidx/releases/navigation3`
-- Kotlin release notes: `https://kotlinlang.org/docs/releases.html`
-
-## Playbooks
-
-- `docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md` - DI and Navigation 3 mistakes to avoid.
-- `docs/agent-playbooks/kmp-source-set-bridging-playbook.md` - when to use `expect`/`actual` vs interfaces + app wiring.
-- `docs/agent-playbooks/task-playbooks.md` - step-by-step recipes for common implementation tasks, plus code anchor quick reference.
-- `docs/agent-playbooks/testing-and-ci-playbook.md` - which Gradle tasks to run based on change type, plus CI parity.
-
-
-
diff --git a/docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md b/docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md
deleted file mode 100644
index 550fd2079..000000000
--- a/docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md
+++ /dev/null
@@ -1,58 +0,0 @@
-# DI and Navigation 3 Anti-Patterns Playbook
-
-This playbook is a fast guardrail for high-risk mistakes in dependency injection and navigation.
-
-Version note: align guidance with repository-pinned versions in `gradle/libs.versions.toml` (currently Koin `4.2.x` and Navigation 3 JetBrains fork `1.1.x`).
-
-## DI anti-patterns
-
-- Don't put Android framework dependencies (`Context`, `Activity`, `Application`) into shared `commonMain` business logic.
-- Do use `@Module`, `@ComponentScan`, and `@KoinViewModel` annotations directly in `commonMain` shared modules. This provides compile-time safety and encapsulates dependency graphs per feature, which is the recommended 2026 KMP practice for Koin 4.x.
-- Don't instantiate ViewModels or service dependencies manually in Compose or activities.
-- Do resolve app-layer wrappers via Koin (`koinViewModel()` / injected bindings).
-- Don't spread DI graph setup across unrelated modules without registration in app startup.
-- Do ensure modules are reachable from app bootstrap in `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt`.
-- Don't assume feature/core `@Module` classes are active automatically.
-- Do ensure they are included by the app root module (`@Module(includes = [...])`) in `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`.
-- **Don't use Koin K2 Compiler Plugin's A1 Module Compile Safety checks for inverted dependencies.**
-- **Do** leave A1 `compileSafety` disabled in `build-logic/convention/src/main/kotlin/KoinConventionPlugin.kt` (uses typed `KoinGradleExtension`). We rely on Koin's A3 full-graph validation (`startKoin` / `VerifyModule`) to handle our decoupled Clean Architecture design where interfaces are declared in one module and implemented in another.
-- **Don't** expect Koin to inject default parameters automatically. The K2 plugin's `skipDefaultValues = true` (default behavior) will cause Koin to skip parameters that have default Kotlin values.
-
-### Current code anchors (DI)
-
-- App-level module scanning: `app/src/main/kotlin/org/meshtastic/app/MainKoinModule.kt`
-- App startup + Koin init: `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt`
-- Shared ViewModel base: `feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt`
-- Shared base UI ViewModel: `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/viewmodel/UIViewModel.kt`
-
-## Navigation 3 anti-patterns
-
-- Don't reintroduce controller-coupled navigation APIs for shared flow state.
-- Do use Navigation 3 types (`NavKey`, `NavBackStack`, `EntryProviderScope`) consistently.
-- Don't build route identifiers as ad-hoc strings in feature code when typed route keys already exist.
-- Do keep route definitions in `core:navigation` and use typed route objects.
-- Don't mutate back navigation with custom stacks disconnected from app backstack.
-- Do mutate `NavBackStack` with `add(...)` and `removeLastOrNull()`.
-- Don't use Android's `androidx.activity.compose.BackHandler` or custom `PredictiveBackHandler` in multiplatform UI.
-- Do use the official KMP `NavigationBackHandler` from `androidx.navigationevent:navigationevent-compose` for back gestures.
-- Don't parse deep links manually in platform code or push single routes without a backstack.
-- Do use `DeepLinkRouter.route()` in `core:navigation` to synthesize the correct typed backstack from RESTful paths.
-- **Don't use a single `NavBackStack` list for multiple tabs, nor reuse the same `NavEntryDecorator` instances across different backstacks.**
-- **Do** use `MultiBackstack` (from `core:navigation`) to manage independent `NavBackStack` instances per tab. When rendering the active tab in `MeshtasticNavDisplay`, you **must** supply a fresh set of decorators (using `remember(backStack) { ... }`) bound to the active backstack instance. Failure to swap decorators when swapping backstacks causes Navigation 3 to perceive the inactive entries as "popped", permanently destroying their `ViewModelStore` and saved UI state.
-
-### Current code anchors (Navigation 3)
-
-- Typed routes: `core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt`
-- Shared saved-state config: `core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/NavigationConfig.kt`
-- App root backstack + `MeshtasticNavDisplay`: `app/src/main/kotlin/org/meshtastic/app/ui/Main.kt`
-- Shared graph entry provider pattern: `feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt`
-- Desktop Navigation 3 shell: `desktop/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt`
-- Desktop nav graph assembly: `desktop/src/main/kotlin/org/meshtastic/desktop/navigation/DesktopNavigation.kt`
-
-
-## Quick pre-PR checks for DI/navigation edits
-
-- Verify affected graph/module is registered and reachable from app startup.
-- Verify no new Android framework type leaks into `commonMain`.
-- Verify routes/backstack use typed keys and Navigation 3 primitives.
-- Run targeted verification from `docs/agent-playbooks/testing-and-ci-playbook.md`.
diff --git a/docs/agent-playbooks/kmp-source-set-bridging-playbook.md b/docs/agent-playbooks/kmp-source-set-bridging-playbook.md
deleted file mode 100644
index 62753020a..000000000
--- a/docs/agent-playbooks/kmp-source-set-bridging-playbook.md
+++ /dev/null
@@ -1,45 +0,0 @@
-# KMP Source-Set Bridging Playbook
-
-Use this playbook when introducing platform-specific behavior into shared modules.
-
-## 1) Decide if `expect`/`actual` is needed
-
-Use `expect`/`actual` only when a platform API cannot be abstracted cleanly behind an interface passed from app wiring.
-
-- Prefer interface + DI when behavior is already app-owned.
-- Prefer `expect`/`actual` for small platform primitives and utilities.
-
-Examples in current code:
-- `core/common/src/commonMain/kotlin/org/meshtastic/core/common/util/CommonUri.kt`
-- `core/common/src/androidMain/kotlin/org/meshtastic/core/common/util/CommonUri.android.kt`
-- `core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/LocationRepository.kt`
-
-## 2) Keep source-set boundaries strict
-
-- `commonMain`: business logic, shared models, coroutine/Flow orchestration.
-- `androidMain`: Android framework integration (`Context`, system services, Android SDK).
-- `app`: app bootstrap, DI root inclusion, Activity/service wiring, flavor-specific providers.
-
-## 3) Resource and UI bridging rules
-
-- Shared strings/resources must come from `core:resources`.
-- Platform/flavor UI implementations should be injected via `CompositionLocal` from app.
-
-Examples:
-- Contract (main map): `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt`
-- Contract (node tracks): `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalNodeTrackMapProvider.kt`
-- Contract (traceroute): `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalTracerouteMapProvider.kt`
-- Provider wiring: `app/src/main/kotlin/org/meshtastic/app/MainActivity.kt`
-
-## 4) DI and module activation checks
-
-- If a new feature/core module adds Koin annotations, verify it is included by app root module includes.
-- App root includes are defined in `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`.
-
-## 5) Verification checklist
-
-- No Android-only imports in `commonMain`.
-- `expect`/`actual` declarations compile across relevant source sets.
-- Routing/DI still resolves from app startup (`MeshUtilApplication`).
-- Run verification tasks from `docs/agent-playbooks/testing-and-ci-playbook.md` appropriate to touched modules.
-
diff --git a/docs/agent-playbooks/task-playbooks.md b/docs/agent-playbooks/task-playbooks.md
deleted file mode 100644
index 25a856d9f..000000000
--- a/docs/agent-playbooks/task-playbooks.md
+++ /dev/null
@@ -1,113 +0,0 @@
-# Task Playbooks
-
-Use these as practical recipes. Keep edits minimal and aligned with existing module boundaries.
-
-For architecture rules and coding standards, see [`AGENTS.md`](../../AGENTS.md).
-
-## Code Anchor Quick Reference
-
-Key files for discovering established patterns:
-
-| Pattern | Reference File |
-|---|---|
-| App DI wiring | `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt` |
-| App startup / Koin bootstrap | `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt` |
-| Shared ViewModel | `feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt` |
-| `CompositionLocal` platform injection | `app/src/main/kotlin/org/meshtastic/app/MainActivity.kt` |
-| Platform abstraction contract | `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt` |
-| Node track map provider contract | `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalNodeTrackMapProvider.kt` |
-| Traceroute map provider contract | `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalTracerouteMapProvider.kt` |
-| Shared strings resource | `core/resources/src/commonMain/composeResources/values/strings.xml` |
-| Okio shared I/O | `core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/usecase/settings/ImportProfileUseCase.kt` |
-| `stateInWhileSubscribed` | `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/viewmodel/ViewModelExtensions.kt` |
-
-## Playbook A: Add or update a user-visible string
-
-1. Add/update key in `core/resources/src/commonMain/composeResources/values/strings.xml`.
-2. Import generated resource symbol in UI code (`org.meshtastic.core.resources.`).
-3. Use `stringResource(Res.string.)` in Compose.
-4. If the string appears in a shared dialog, prefer `core:ui` dialog components.
-5. Verify no hardcoded user-facing strings were introduced.
-
-Reference examples:
-- `feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt`
-- `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/AlertDialogs.kt`
-
-## Playbook B: Add shared ViewModel logic in a feature module
-
-1. Implement or extend base ViewModel logic in `feature//src/commonMain/...`.
-2. Keep shared class free of Android framework dependencies.
-3. Keep Android framework dependencies out of shared logic; if the module already uses Koin annotations in `commonMain`, keep patterns consistent and ensure app root inclusion.
-4. Update navigation entry points in `feature/*/src/androidMain/kotlin/org/meshtastic/feature/*/navigation/...` to resolve ViewModels with `koinViewModel()`.
-
-Reference examples:
-- Shared base: `feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt`
-- Shared base UI ViewModel: `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/viewmodel/UIViewModel.kt`
-- Navigation usage: `feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt`
-- Desktop navigation usage: `desktop/src/main/kotlin/org/meshtastic/desktop/navigation/DesktopNavigation.kt`
-
-## Playbook C: Add a new dependency or service binding
-
-1. Check `gradle/libs.versions.toml` for existing library and version alias.
-2. Add new dependency to version catalog first (if truly new).
-3. Wire implementation in the owning module (`core:*`, `feature:*`, or `app`) following existing architecture.
-4. Register bindings/modules in app Koin graph where needed.
-5. For Android system integration (WorkManager, service bootstrapping), wire via `MeshUtilApplication` and app-layer modules.
-
-Reference examples:
-- App startup and Koin bootstrap: `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt`
-- App module scan: `app/src/main/kotlin/org/meshtastic/app/MainKoinModule.kt`
-
-## Playbook D: Add or modify navigation flow
-
-1. Define/extend route keys in `core:navigation`.
-2. Implement feature entry/content using Navigation 3 types (`NavKey`, `NavBackStack`, `EntryProviderScope`).
-3. Add graph entries under the relevant feature module's `navigation` package (e.g., `feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation`).
-4. If the entry content depends on platform-specific UI (e.g. Activity context or specific desktop wrappers), use `expect`/`actual` declarations for the content composables.
-5. Use backstack mutation (`add`, `removeLastOrNull`) instead of introducing controller-coupled APIs.
-6. Verify deep-link behavior if route is externally reachable.
-
-Reference examples:
-- Shared graph wiring: `feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt`
-- Android specific content: `feature/settings/src/androidMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsMainScreen.kt`
-- Desktop specific content: `feature/settings/src/jvmMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsMainScreen.kt`
-- Feature intro graph pattern: `feature/intro/src/androidMain/kotlin/org/meshtastic/feature/intro/IntroNavGraph.kt`
-- Desktop nav shell: `desktop/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt`
-- Desktop nav graph assembly: `desktop/src/main/kotlin/org/meshtastic/desktop/navigation/DesktopNavigation.kt`
-
-
-## Playbook E: Add flavor/platform-specific UI implementation
-
-1. Keep shared contracts in `core:ui` or feature shared code.
-2. Inject flavor/platform implementation via `CompositionLocal` from `app`.
-3. Avoid direct dependency from shared modules to Google Maps/osmdroid/other Android SDK-only APIs.
-4. Keep adapter types narrow and stable (interfaces, DTO-like params).
-
-Reference examples:
-- Contract (main map): `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt`
-- Contract (node tracks): `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalNodeTrackMapProvider.kt`
-- Contract (traceroute): `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/LocalTracerouteMapProvider.kt`
-- Provider wiring: `app/src/main/kotlin/org/meshtastic/app/MainActivity.kt`
-- Consumer side: `feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/MapScreen.kt`
-
-## Playbook F: Onboard a new platform target
-
-1. Create a platform application module (e.g., `desktop/`, `ios/`).
-2. Copy `desktop/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt` as the starting stub set. All repository interfaces have no-op implementations there.
-3. Create a `KoinModule` that mirrors `desktop/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt` โ use stubs for unimplemented interfaces, real implementations where available.
-4. Add `kotlinx-coroutines-swing` (JVM/Desktop) or the equivalent platform coroutines dispatcher module. Without it, `Dispatchers.Main` is unavailable and any code using `lifecycle.coroutineScope` will crash at runtime.
-5. Progressively replace stubs with real implementations (e.g., serial transport for desktop, CoreBluetooth for iOS).
-6. Add `()` target to feature modules as needed (all `core:*` modules already declare `jvm()`).
-7. Update CI JVM smoke compile step in `.github/workflows/reusable-check.yml` to include new modules.
-8. If `commonMain` code fails to compile for the new target, it's a KMP migration debt โ fix the shared code, not the target.
-
-Reference examples:
-- Desktop stubs: `desktop/src/main/kotlin/org/meshtastic/desktop/stub/NoopStubs.kt`
-- Desktop DI: `desktop/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt`
-- Desktop Navigation 3 shell: `desktop/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt`
-- Desktop nav graph entries: `desktop/src/main/kotlin/org/meshtastic/desktop/navigation/DesktopNavigation.kt`
-- Desktop shared feature wiring: `feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt`
-- Desktop-specific screen: `feature/settings/src/jvmMain/kotlin/org/meshtastic/feature/settings/DesktopSettingsScreen.kt`
-- Roadmap: `docs/roadmap.md`
-
-
diff --git a/docs/agent-playbooks/testing-and-ci-playbook.md b/docs/agent-playbooks/testing-and-ci-playbook.md
deleted file mode 100644
index a7f0796df..000000000
--- a/docs/agent-playbooks/testing-and-ci-playbook.md
+++ /dev/null
@@ -1,88 +0,0 @@
-# Testing and CI Playbook
-
-Use this matrix to choose the right verification depth for a change.
-
-## 1) Baseline local verification order
-
-Run in this order for routine changes:
-
-```bash
-./gradlew clean
-./gradlew spotlessCheck
-./gradlew spotlessApply
-./gradlew detekt
-./gradlew assembleDebug
-./gradlew test
-```
-
-Notes:
-- This order aligns with repository guidance in `AGENTS.md` and `.github/copilot-instructions.md`.
-- CI runs host verification and Android build/device verification in separate jobs inside `.github/workflows/reusable-check.yml`.
-
-## 2) Change-type matrix
-
-- `docs-only` changes:
- - Usually no Gradle run required.
- - If you touched code examples or command docs, at least run `spotlessCheck` if practical.
- - If you changed architecture, CI, validation commands, or agent workflow guidance, update the mirrored docs in `AGENTS.md`, `.github/copilot-instructions.md`, `GEMINI.md`, and `docs/kmp-status.md` in the same slice.
-- `UI text/resource` changes:
- - `spotlessCheck`, `detekt`, `assembleDebug`.
-- `feature/commonMain logic` changes:
- - `spotlessCheck`, `detekt`, `test`, `assembleDebug`.
-- `navigation/DI wiring` changes (app graph, Koin module/wrapper changes):
- - `spotlessCheck`, `detekt`, `assembleDebug`, `test`, plus `testFdroidDebugUnitTest` and `testGoogleDebugUnitTest` when available locally.
- - If touching any KMP module, also run `kmpSmokeCompile`.
-- `worker/service/background` changes:
- - `spotlessCheck`, `detekt`, `assembleDebug`, `test`, and targeted tests around WorkManager/service behavior.
-- `BLE/networking/core repository` changes:
- - `spotlessCheck`, `detekt`, `assembleDebug`, `test`.
-
-## 3) Flavor and instrumentation checks
-
-Run these when relevant to map/provider/flavor-specific behavior:
-
-```bash
-./gradlew lintFdroidDebug lintGoogleDebug
-./gradlew testFdroidDebug
-./gradlew testGoogleDebug
-./gradlew connectedAndroidTest
-```
-
-## 4) CI parity checks
-
-Current reusable check workflow includes:
-
-- `spotlessCheck detekt`
-- Android lint for all directly runnable Android modules:
- `app:lintFdroidDebug app:lintGoogleDebug core:barcode:lintFdroidDebug core:barcode:lintGoogleDebug core:api:lintDebug mesh_service_example:lintDebug`
- *(Note: `mesh_service_example:lintDebug` is temporary โ the module is deprecated and will be
- removed along with its CI tasks in a future release.)*
-- Host tests plus coverage aggregation:
- `test koverXmlReport app:koverXmlReportFdroidDebug app:koverXmlReportGoogleDebug core:api:koverXmlReportDebug core:barcode:koverXmlReportFdroidDebug core:barcode:koverXmlReportGoogleDebug mesh_service_example:koverXmlReportDebug desktop:koverXmlReport`
- *(Note: `mesh_service_example:koverXmlReportDebug` is temporary โ see above.)*
-- KMP smoke compile lifecycle task (auto-discovers KMP modules and runs JVM + iOS simulator compile checks):
- `kmpSmokeCompile`
-- Android build tasks:
- `app:assembleFdroidDebug app:assembleGoogleDebug mesh_service_example:assembleDebug`
- *(Note: `mesh_service_example:assembleDebug` is temporary โ see above.)*
-- Instrumented tests (when emulator tests are enabled):
- `app:connectedFdroidDebugAndroidTest app:connectedGoogleDebugAndroidTest core:barcode:connectedFdroidDebugAndroidTest core:barcode:connectedGoogleDebugAndroidTest`
-- Coverage uploads happen once from the host job; instrumented test results upload once from the first Android matrix API to avoid duplicate reporting.
-
-Reference: `.github/workflows/reusable-check.yml`
-
-PR workflow note:
-
-- `.github/workflows/pull-request.yml` ignores docs-only changes (`**/*.md`, `docs/**`), so doc-only PRs may skip Android CI by design.
-- PR change detection includes workflow/build/config paths such as `.github/workflows/**`, `desktop/**`, `mesh_service_example/**` (deprecated, will be removed), `config/**`, `gradle/**`, `settings.gradle.kts`, and `test.gradle.kts`.
-- Android CI on PRs runs with `run_instrumented_tests: false`; merge queue keeps the full emulator matrix on API 26 and 35.
-- Gradle cache writes are enabled for trusted refs/events (`main`, `merge_group`, and `gh-readonly-queue/*`); other refs run in read-only cache mode.
-
-## 5) Practical guidance for agents
-
-- Start with the smallest set that validates your touched area.
-- Keep documentation continuously in sync with architecture, CI, and workflow changes; do not defer doc fixes to a later PR.
-- If modifying cross-module contracts (routes, repository interfaces, DI graph), run the broader baseline.
-- If unable to run full validation locally, report exactly what ran and what remains.
-
-
diff --git a/docs/kmp-status.md b/docs/kmp-status.md
index fb9d74175..1f8ce1062 100644
--- a/docs/kmp-status.md
+++ b/docs/kmp-status.md
@@ -176,5 +176,5 @@ Remaining to be extracted from `:app` or unified in `commonMain`:
- Roadmap: [`docs/roadmap.md`](./roadmap.md)
- Agent guide: [`AGENTS.md`](../AGENTS.md)
-- Playbooks: [`docs/agent-playbooks/`](./agent-playbooks/)
+- Agent skills: [`.skills/`](../.skills/)
- Decision records: [`docs/decisions/`](./decisions/)