## Playbook B: Add shared ViewModel logic in a feature module
1. Implement or extend base ViewModel logic in `feature/<name>/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()`.
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.
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 `<Platform>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 `<platform>()` 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.