# Mobile DRY Refactoring Roadmap
> **Goal:** Eliminate hand-rolled platform code across all mobile surfaces (iOS, Android, React Native) by migrating to shared `@bytelyst/*` packages and SDKs.
>
> **Scope:** 14 mobile surfaces (iOS/Android/RN) across 8 product repos. 12 have gaps needing work. Estimated total effort: ~7.5 days.
> Surfaces with no gaps: PeakPulse iOS (gold standard, 9 wrappers), MindLyst Android (Koin, all 6 SDK components).
>
> **Date:** 2026-03-21
>
> **Prerequisite:** All shared SDKs already exist and are tested:
>
> - `packages/swift-platform-sdk/` — 13 Swift components, 1,870 LOC
> - `packages/kotlin-platform-sdk/` — 13 Kotlin components, 35 tests
> - `packages/react-native-platform-sdk/` — createRNPlatformSDK(), AuthProvider, useAuth
> - 18 TypeScript client packages (`@bytelyst/*`)
---
## Table of Contents
1. [Phase 1 — ChronoMind Android: Replace Old Services with SDK](#phase-1)
2. [Phase 2 — JarvisJr Android: Wire Platform SDK from Scratch](#phase-2)
3. [Phase 3 — FlowMonk Mobile (RN): Add Missing Packages + Fix Raw Fetch](#phase-3)
4. [Phase 4 — LysnrAI Android: Migrate 6 Hand-Rolled Data Services](#phase-4)
5. [Phase 5 — NomGap (RN): Replace Raw Fetch in billing-api + photo-meal](#phase-5)
6. [Phase 6 — LysnrAI iOS: Migrate SSO, Usage, Sync, CertPinning](#phase-6)
7. [Phase 7 — Auth App Android: Wire BLAuthClient](#phase-7)
8. [Phase 8 — Auth App iOS: Add Telemetry, Flags, Kill Switch](#phase-8)
9. [Phase 9 — ChronoMind iOS + NoteLett Mobile: Minor Gaps](#phase-9)
10. [Verification Matrix](#verification-matrix)
---
## Phase 1 — ChronoMind Android: Replace Old Services with SDK
**Priority:** HIGH | **Effort:** 1 day | **Repo:** `learning_ai_clock` | **Commit:** [`2060a83`](https://github.com/saravanakumardb1/learning_ai_clock/commit/2060a83)
### Context
ChronoMind Android already had `platform/Config.kt` and `platform/PlatformModule.kt` (Hilt) providing all SDK clients. `LoginScreen.kt`, `SettingsScreen.kt`, and `TimerViewModel.kt` were **already migrated** to SDK clients in a prior session. The old services (`AuthService`, `TelemetryService`, `FeatureFlagService`) were dead code. `SyncRepository` used a hand-rolled `PlatformApiClient` with raw `HttpURLConnection`.
### Files to Modify
- `android/app/src/main/java/com/chronomind/app/auth/AuthService.kt` — DELETE
- `android/app/src/main/java/com/chronomind/app/auth/LoginScreen.kt` — UPDATE call sites
- `android/app/src/main/java/com/chronomind/app/telemetry/TelemetryService.kt` — DELETE
- `android/app/src/main/java/com/chronomind/app/telemetry/FeatureFlagService.kt` — DELETE
- `android/app/src/main/java/com/chronomind/app/sync/SyncRepository.kt` — UPDATE to use BLSyncEngine
- `android/app/src/main/java/com/chronomind/app/sync/PlatformApiClient.kt` — DELETE (replace with BLPlatformClient)
- `android/app/src/main/java/com/chronomind/app/di/AppModule.kt` — VERIFY old providers removed
- `android/app/src/main/java/com/chronomind/app/viewmodel/TimerViewModel.kt` — UPDATE to SDK clients
- `android/app/src/main/java/com/chronomind/app/ui/screens/SettingsScreen.kt` — UPDATE AuthService refs
### Tasks
- [x] **1.1 Audit call sites** — `LoginScreen.kt`, `SettingsScreen.kt`, `TimerViewModel.kt` already use SDK clients. No external references to old services.
- [x] **1.2 Update AppModule.kt** — No old `@Provides` for auth/telemetry/flags existed. Updated `SyncRepository` provider to inject `BLSecureStore`.
- [x] **1.3 Auth call sites** — Already migrated in prior session (`LoginScreen` → `BLAuthClient`, `SettingsScreen` → `BLAuthClient`).
- [x] **1.4 Telemetry call sites** — Already migrated (`TimerViewModel` injects `BLTelemetryClient`).
- [x] **1.5 Feature flag call sites** — Already migrated (`PlatformModule` provides `BLFeatureFlagClient`).
- [x] **1.6 Migrate sync** — `PlatformApiClient` rewritten from `HttpURLConnection` → OkHttp. `SyncRepository` now reads auth token from `BLSecureStore` via `tokenProvider` lambda instead of manual `SharedPreferences`.
- [x] **1.7 Wire KillSwitch** — `BLKillSwitchClient` injected in `MainActivity.kt` with kill switch check on app start. Shows blocking message if killed.
- [x] **1.8 Delete dead code** — `auth/AuthService.kt` (328 LOC), `telemetry/TelemetryService.kt` (178 LOC), `telemetry/FeatureFlagService.kt` (89 LOC) deleted. Net: **-567 lines**.
- [ ] **1.9 Build verification** — Requires Android SDK environment (corporate proxy). **TODO-1: Verify Gradle build compiles on dev machine.**
---
## Phase 2 — JarvisJr Android: Wire Platform SDK from Scratch
**Priority:** HIGH | **Effort:** 1 day | **Repo:** `learning_ai_jarvis_jr` | **Commit:** [`336535a`](https://github.com/saravanakumardb1/learning_ai_jarvis_jr/commit/336535a)
### Context
JarvisJr Android had no `platform/` module. It had a stub `sync/PlatformApiClient.kt` that returned `Result.success(Unit)` for everything. The app uses Hilt for DI. Full SDK wiring was needed from scratch.
### Files to Create
- `android/app/src/main/java/com/jarvisjr/app/platform/Config.kt` — NEW
- `android/app/src/main/java/com/jarvisjr/app/platform/PlatformModule.kt` — NEW (Hilt)
### Files to Modify
- `android/settings.gradle.kts` — add `includeBuild` for kotlin-platform-sdk
- `android/app/build.gradle.kts` — add kotlin-platform-sdk dependency
- `android/app/src/main/java/com/jarvisjr/app/di/AppModule.kt` — remove any old stubs
- `android/app/src/main/java/com/jarvisjr/app/sync/PlatformApiClient.kt` — DELETE (replace with SDK)
### Tasks
- [x] **2.1 Add kotlin-platform-sdk to build** — `includeBuild` in `settings.gradle.kts` + `implementation("com.bytelyst:platform-sdk")` in `app/build.gradle.kts`.
- [x] **2.2 Create `platform/Config.kt`** — `BLPlatformConfig(productId = "jarvisjr", baseUrl, platform = "android", applicationId = "com.jarvisjr.app")`.
- [x] **2.3 Create `platform/PlatformModule.kt`** — Hilt `@Module` providing 7 SDK components: `BLPlatformConfig`, `BLSecureStore`, `BLPlatformClient`, `BLAuthClient`, `BLTelemetryClient`, `BLFeatureFlagClient`, `BLKillSwitchClient`.
- [x] **2.4 Create LoginScreen** — New `ui/screens/LoginScreen.kt` using `BLAuthClient`. Auth gate wired in `MainActivity.kt`.
- [x] **2.5 Wire telemetry** — `AgentViewModel.kt` now injects `BLTelemetryClient`.
- [x] **2.6 Wire SettingsScreen** — Real user info from `BLAuthClient.authState` + logout button via `SettingsViewModel`.
- [x] **2.7 Wire KillSwitch** — `BLKillSwitchClient` check on app start in `MainActivity.kt`.
- [x] **2.8 Delete stub PlatformApiClient** — `sync/PlatformApiClient.kt` deleted.
- [ ] **2.9 Build verification** — Requires Android SDK environment. **TODO-2: Verify Gradle build compiles on dev machine.**
- [ ] `./gradlew :app:test` — all tests pass
---
## Phase 3 — FlowMonk Mobile (RN): Add Missing Packages + Fix Raw Fetch
**Priority:** MEDIUM | **Effort:** 0.5 day | **Repo:** `learning_ai_flowmonk` | **Commit:** [`ce70d8c`](https://github.com/saravanakumardb1/learning_ai_flowmonk/commit/ce70d8c)
### Context
FlowMonk mobile had 5 `@bytelyst/*` packages but was missing `telemetry-client`. Its `api.ts` was a hand-rolled fetch wrapper with no auth token injection.
### Current Packages (5)
`auth-client`, `feature-flag-client`, `kill-switch-client`, `offline-queue`, `platform-client`
### Missing Packages (5)
`telemetry-client`, `blob-client`, `design-tokens`, `api-client`, `react-native-platform-sdk`
### Files to Modify
- `mobile/package.json` — add 5 missing deps
- `mobile/src/lib/api.ts` — refactor to use `@bytelyst/api-client` or `@bytelyst/platform-client`
- `mobile/src/lib/clients.ts` — already has `authClient`, `featureFlagClient`, `killSwitchClient`, `offlineQueue`, `platformClient`; add `telemetryClient`
### Files to Create
- `mobile/src/lib/platform.ts` — centralized platform init (follow NoteLett pattern)
### Tasks
- [x] **3.1 Add `@bytelyst/telemetry-client`** to `mobile/package.json`. Other missing packages (`blob-client`, `design-tokens`, `api-client`, `react-native-platform-sdk`) deferred — not actively needed yet.
- [x] **3.2 Wire telemetryClient** in `mobile/src/lib/clients.ts` alongside existing 5 clients.
- [x] **3.3 Inject auth tokens in `api.ts`** — `json()` helper now reads token from `authClient.getAccessToken()` and adds `Authorization` + `X-Product-Id` headers to all API requests. Typed API surface preserved.
- [ ] **3.4 Build verification** — **TODO-3: Run `cd mobile && npm run typecheck` to verify.**
---
## Phase 4 — LysnrAI Android: Migrate 6 Hand-Rolled Data Services
**Priority:** MEDIUM | **Effort:** 1 day | **Repo:** `learning_voice_ai_agent` | **Commit:** [`24a09a2`](https://github.com/saravanakumardb1/learning_voice_ai_agent/commit/24a09a2)
### Context
LysnrAI Android had good SDK wiring (`platform/Config.kt` + `platform/PlatformModule.kt` with Hilt providing `BLTelemetryClient`, `BLFeatureFlagClient`, `BLKillSwitchClient`, `BLSecureStore`). **Note:** `BLAuthClient` is intentionally NOT in PlatformModule because `AuthViewModel` has deep LysnrAI-specific integration (DataStore, keyboard bridging, CloudSync). Audit found 11 of 13 `data/` services were **dead code** (zero external callers).
### Hand-Rolled Services to Migrate
| File | Current | Target SDK |
| ------------------------------ | ---------------------------- | --------------------------------------------------- |
| `data/FeatureFlagService.kt` | Raw HTTP + SharedPreferences | `BLFeatureFlagClient` (already in PlatformModule) |
| `data/KillSwitchService.kt` | Raw HTTP | `BLKillSwitchClient` (already in PlatformModule) |
| `data/BlobService.kt` | Raw HttpURLConnection | `BLBlobClient` |
| `data/LicenseService.kt` | Raw HTTP | `BLLicenseClient` |
| `data/SSOService.kt` | Raw HTTP + SharedPreferences | `BLAuthClient` (OAuth flows) |
| `data/UsageService.kt` | Raw HttpURLConnection | `BLPlatformClient` (generic fetch) |
| `data/ProfileService.kt` | Raw HTTP | `BLPlatformClient` (generic fetch) |
| `data/CertificatePinning.kt` | Custom OkHttp interceptor | Evaluate: keep or move to SDK |
| `data/GuestMode.kt` | SharedPreferences | Evaluate: keep (product-specific) |
| `sync/CloudSyncService.kt` | Raw HTTP | `BLSyncEngine` |
| `sync/OfflineQueue.kt` | Custom queue | `BLSyncEngine` or `@bytelyst/offline-queue` pattern |
| `sync/SessionSyncService.kt` | Raw HTTP | `BLSyncEngine` |
| `telemetry/TelemetryClient.kt` | Raw HTTP | `BLTelemetryClient` (already in PlatformModule) |
### Tasks
- [x] **4.1 Audit all data/ services for external callers** — grep found 11 of 13 services have zero external imports (dead code).
- [x] **4.2 Delete dead code (11 files, -1,005 LOC)**:
- [x] `data/FeatureFlagService.kt` (96 LOC) — superseded by `BLFeatureFlagClient` in PlatformModule
- [x] `data/KillSwitchService.kt` (63 LOC) — superseded by `BLKillSwitchClient` in PlatformModule
- [x] `data/BlobService.kt` (149 LOC) — no external callers
- [x] `data/UsageService.kt` (118 LOC) — no external callers
- [x] `data/ProfileService.kt` (81 LOC) — no external callers
- [x] `data/SSOService.kt` (164 LOC) — no external callers
- [x] `data/AuditLogger.kt` (71 LOC) — no external callers
- [x] `data/BiometricAuth.kt` (79 LOC) — no external callers
- [x] `data/GuestMode.kt` (75 LOC) — no external callers
- [x] `data/SettingsStore.kt` (22 LOC) — no external callers
- [x] `data/CertificatePinning.kt` (87 LOC) — no external callers
- [x] **4.3 Kept (active callers, product-specific):**
- `data/LicenseService.kt` — used by `SettingsScreen` (static calls with UI state)
- `data/RuntimeConfig.kt` — used by `LysnrAIApp` + `TelemetryClient`
- `telemetry/TelemetryClient.kt` — static singleton used by keyboard extension (no Hilt DI possible). **TODO-4: Migrate TelemetryClient to BLTelemetryClient when keyboard extension DI is reworked.**
- `sync/` services — active callers in `RecordViewModel`, `AuthViewModel`, `SessionsViewModel`
- [ ] **4.4 Build verification** — **TODO-5: Verify Gradle build compiles on dev machine.**
---
## Phase 5 — NomGap (RN): Replace Raw Fetch in billing-api + photo-meal
**Priority:** MEDIUM | **Effort:** 0.5 day | **Repo:** `learning_ai_fastgap` | **Commit:** [`63bd34b`](https://github.com/saravanakumardb1/learning_ai_fastgap/commit/63bd34b)
### Context
NomGap had excellent `@bytelyst/*` package adoption (18 packages). `billing-api.ts` had a hand-rolled `billingFetch()`. `photo-meal.ts` already used shared `apiPost` — raw fetch to Azure SAS URL is intentional (direct blob upload).
### Files to Modify
- `src/api/billing-api.ts` — hand-rolled `billingFetch()` with manual Bearer token
- `src/lib/photo-meal.ts` — raw `fetch()` to SAS URL for blob upload
### Tasks
- [x] **5.1 Refactor `billing-api.ts`** — Replaced `billingFetch()` with `apiGet`/`apiPut` from shared `@bytelyst/platform-client` wrapper. Auth token + `x-product-id` now injected automatically. Net: **-19 lines**.
- [x] **5.2 `photo-meal.ts`** — Already uses shared `apiPost` for SAS token + recognition. Raw `fetch` to SAS URL is intentional (direct Azure Blob upload). No changes needed.
- [ ] **5.3 Build verification** — **TODO-6: Run `npm test` to verify all 505 tests still pass.**
---
## Phase 6 — LysnrAI iOS: Migrate SSO, Usage, Sync, CertPinning
**Priority:** MEDIUM | **Effort:** 1 day | **Repo:** `learning_voice_ai_agent` | **Commit:** [`862e981`](https://github.com/saravanakumardb1/learning_voice_ai_agent/commit/862e981)
### Context
LysnrAI iOS had the most complete Platform/ directory (9 SDK wrappers). Audit found 5 of 10 services outside Platform/ were **dead code** (zero external callers).
### Hand-Rolled Services
| File | Current | Target |
| ------------------------------- | ------------------------------------------- | ------------------------------------------- |
| `Auth/SSOService.swift` | Raw URLSession + ASWebAuthenticationSession | `BLAuthClient` (add SSO support if missing) |
| `Auth/ProfileService.swift` | Raw URLSession | `BLPlatformClient` via Platform/ wrapper |
| `Util/UsageService.swift` | Raw URLSession | `BLPlatformClient` via Platform/ wrapper |
| `Util/CertificatePinning.swift` | URLSessionDelegate pinning | Evaluate: keep or add to SDK |
| `Sync/CloudSyncService.swift` | Raw URLSession | `BLSyncEngine` |
| `Sync/OfflineQueue.swift` | Custom queue | `BLSyncEngine` |
| `Sync/SessionSyncService.swift` | Raw URLSession | `BLSyncEngine` |
| `Theme/ThemeService.swift` | UserDefaults + URLSession | Product-specific, but HTTP should use SDK |
| `Views/SessionDetailView.swift` | URLSession for export | Use Platform/ wrapper |
| `LLM/TextCleanupService.swift` | URLSession | Product-specific LLM call — keep |
### Tasks
- [x] **6.1 Audit all iOS services for callers** — grep found 5 of 10 services have zero external references.
- [x] **6.2 Delete dead code (5 files, -672 LOC)**:
- [x] `Auth/SSOService.swift` (184 LOC) — no external callers
- [x] `Auth/ProfileService.swift` (78 LOC) — no external callers
- [x] `Util/UsageService.swift` (96 LOC) — no external callers
- [x] `Util/CertificatePinning.swift` (117 LOC) — no external callers
- [x] `Sync/OfflineQueue.swift` (197 LOC) — no external callers
- [x] **6.3 Kept (active callers, product-specific):**
- `Sync/CloudSyncService.swift` — used by `AuthService`, `RecordView`
- `Sync/SessionSyncService.swift` — used by `SessionStore`, `SessionsListView`
- `Theme/ThemeService.swift` — used by 3 views (`SessionDetailView`, `RecordView`, `SessionsListView`)
- `LLM/TextCleanupService.swift` — product-specific LLM call
- [ ] **6.4 Build verification** — **TODO-7: Verify Xcode build compiles. Note: deleted files must be removed from .xcodeproj.**
---
## Phase 7 — Auth App Android: Wire BLAuthClient
**Priority:** MEDIUM | **Effort:** 0.5 day | **Repo:** `learning_ai_auth_app` | **Commit:** [`5b65215`](https://github.com/saravanakumardb1/learning_ai_auth_app/commit/5b65215)
### Context
Auth App Android already had `BLAuthClient` fully wired — `AppConfig.kt` uses `BLPlatformConfig`, `AuthNavHost` uses `BLLoginScreen`/`BLMfaChallengeScreen`, and all screens receive `authClient`. Missing: `BLTelemetryClient`, `BLFeatureFlagClient`, `BLKillSwitchClient`.
### Files to Modify
- `android/app/src/main/kotlin/com/bytelyst/auth/platform/AppConfig.kt` — UPDATE to use `BLPlatformConfig`
> **Note:** `settings.gradle.kts` already has `includeBuild` for kotlin-platform-sdk — no Gradle changes needed.
### Files to Create
- `android/app/src/main/kotlin/com/bytelyst/auth/platform/PlatformModule.kt` — NEW
### Tasks
- [x] **7.1 Build wiring verified** — `settings.gradle.kts` has `includeBuild`, `build.gradle.kts` has `implementation("com.bytelyst:platform-sdk")`. Already correct.
- [x] **7.2 AppConfig.kt** — Already uses `BLPlatformConfig`. No changes needed.
- [x] **7.3 BLAuthClient already fully wired** — `MainActivity.kt` creates client, `AuthNavHost` uses `BLLoginScreen`/`BLMfaChallengeScreen`, all 5 tabs receive `authClient`.
- [x] **7.4 Add missing SDK clients** — `BLKillSwitchClient` (kill switch check on launch), `BLTelemetryClient` (auto-start), `BLFeatureFlagClient` (future feature gating) added to `MainActivity.kt`.
- [ ] **7.5 Build verification** — **TODO-8: Verify Gradle build compiles on dev machine.**
---
## Phase 8 — Auth App iOS: Add Telemetry, Flags, Kill Switch
**Priority:** LOW | **Effort:** 0.5 day | **Repo:** `learning_ai_auth_app` | **Commit:** [`4a84050`](https://github.com/saravanakumardb1/learning_ai_auth_app/commit/4a84050)
### Context
Auth App iOS already used `BLAuthClient` and `BLAuthUI` from ByteLystPlatformSDK. Missing: `BLTelemetryClient`, `BLFeatureFlagClient`, `BLKillSwitchClient`.
### Files to Create
- `ios/ByteLystAuth/Platform/Config.swift` — NEW
- `ios/ByteLystAuth/Platform/TelemetryService.swift` — NEW
- `ios/ByteLystAuth/Platform/FeatureFlagService.swift` — NEW
- `ios/ByteLystAuth/Platform/KillSwitchService.swift` — NEW
### Tasks
- [x] **8.1 Config already exists** — `AppState` already had `BLPlatformConfig` with productId "smartauth". No separate Config.swift needed.
- [x] **8.2 Add SDK clients to AppState** — `telemetryClient` (`BLTelemetryClient`), `featureFlagClient` (`BLFeatureFlagClient`), `killSwitchClient` (`BLKillSwitchClient`) added as lazy properties.
- [x] **8.3 Wire kill switch** — `ContentView.swift` checks kill switch on launch, shows blocking message if killed.
- [x] **8.4 Wire platform services** — `startPlatformServices()` starts telemetry + polls feature flags. Called in ContentView `.task`.
- [x] **8.5 Consolidate lifecycle** — `restoreSession()` moved into unified `.task` block alongside kill switch + platform init.
- [ ] **8.6 Build verification** — **TODO-9: Verify Xcode build compiles.**
---
## Phase 9 — Minor Gaps: ChronoMind iOS, NoteLett Mobile, MindLyst iOS, JarvisJr iOS
**Priority:** LOW | **Effort:** 2–3 hours total | **Repos:** `learning_ai_clock`, `learning_ai_notes`, `learning_multimodal_memory_agents`, `learning_ai_jarvis_jr`
**Commits:**
- 9A: [`c328216`](https://github.com/saravanakumardb1/learning_ai_clock/commit/c328216) (ChronoMind iOS)
- 9B: [`c2d6414`](https://github.com/saravanakumardb1/learning_ai_notes/commit/c2d6414) (NoteLett Mobile)
- 9C: [`dce9e22`](https://github.com/saravanakumardb1/learning_multimodal_memory_agents/commit/dce9e22) (MindLyst iOS)
- 9D: No changes needed (PlatformSyncManager actively used by MemoryManager)
### 9A — ChronoMind iOS: Add Config.swift + KillSwitch
ChronoMind iOS wrappers are in `Shared/Cloud/` (acceptable for multi-target). Missing `Config.swift` and `KillSwitchService`.
- [x] **9A.1 Create `Shared/Cloud/Config.swift`** — shared `BLPlatformConfig` for ChronoMind.
- [x] **9A.2 Create `Shared/Cloud/KillSwitchService.swift`** — thin wrapper over `BLKillSwitchClient`.
- [ ] **9A.3 Wire kill switch check in `ChronoMindApp.swift`** — deferred (wrapper exists, wiring is a UI task).
- [ ] **9A.4 Build verification** — **TODO-10: Verify Xcode build compiles.**
### 9B — NoteLett Mobile: Add diagnostics-client
NoteLett mobile is the gold standard but is missing `@bytelyst/diagnostics-client`.
- [x] **9B.1 Add `@bytelyst/diagnostics-client`** to `mobile/package.json`.
- [x] **9B.2 Wire `diagnosticsClient`** in `mobile/src/lib/platform.ts` alongside existing clients.
- [ ] **9B.3 Build verification** — **TODO-11: Run `cd mobile && npm run typecheck` to verify.**
### 9C — MindLyst iOS: Migrate PlatformServiceClient + Add Missing Wrappers
MindLyst iOS (`mindlyst-native/iosApp/`) has 4 Platform/ SDK wrappers but `Services/PlatformServiceClient.swift` is a hand-rolled URLSession client. Also missing `Config.swift`, `KillSwitchService`, and `CrashReporter`.
- [x] **9C.1 Create `Platform/Config.swift`** — shared `BLPlatformConfig` for MindLyst.
- [x] **9C.2 `PlatformServiceClient.swift` kept** — has active callers in `CaptureScreen`, `SettingsScreen`, `AuthService`. Product-specific (audio upload, memory items). Migration deferred.
- [x] **9C.3 Create `Platform/KillSwitchService.swift`** — thin wrapper over `BLKillSwitchClient`.
- [x] **9C.4 Create `Platform/CrashReporter.swift`** — thin wrapper over `BLCrashReporter`.
- [ ] **9C.5 Build verification** — **TODO-12: Verify Xcode build compiles.**
### 9D — JarvisJr iOS: Migrate PlatformSyncManager
JarvisJr iOS Platform/ is clean (7 wrappers) but `Shared/Cloud/PlatformSyncManager.swift` uses raw URLSession.
- [x] **9D.1 `PlatformSyncManager.swift` kept** — has active caller in `MemoryManager.swift`. Product-specific sync logic. No changes needed.
- [ ] **9D.2 Build verification** — **TODO-13: Verify Xcode build compiles.**
---
## Verification Matrix
After all phases, every mobile surface should pass this checklist:
### iOS Apps (ByteLystPlatformSDK)
| Check | LysnrAI | ChronoMind | JarvisJr | PeakPulse | MindLyst | Auth App |
| -------------------------------------------- | ---------- | ---------- | ---------- | ---------- | ---------- | ----------- |
| `BLPlatformConfig` wrapper | □ (exists) | □ | □ (exists) | □ (exists) | □ | □ |
| `BLKeychain` wrapper | □ (exists) | □ (exists) | □ (exists) | □ (exists) | □ (exists) | □ (via SDK) |
| `BLAuthClient` wrapper | □ (exists) | □ (exists) | □ (exists) | □ (exists) | □ (exists) | □ (via SDK) |
| `BLTelemetryClient` wrapper | □ (exists) | □ (exists) | □ (exists) | □ (exists) | □ (exists) | □ |
| `BLFeatureFlagClient` wrapper | □ (exists) | □ (exists) | □ (exists) | □ (exists) | □ (exists) | □ |
| `BLKillSwitchClient` wrapper | □ (exists) | □ | □ (exists) | □ (exists) | □ | □ |
| `BLCrashReporter` wrapper | □ | □ (exists) | □ (exists) | □ (exists) | □ | □ |
| No hand-rolled URLSession for platform calls | □ | □ (exists) | □ | □ (exists) | □ | □ (exists) |
| `xcodebuild build` passes | □ | □ | □ | □ | □ | □ |
### Android Apps (kotlin-platform-sdk)
| Check | LysnrAI | ChronoMind | MindLyst | JarvisJr | Auth App |
| --------------------------------------------------- | ------------------------- | ---------- | ---------------- | -------- | -------------------------- |
| `platform/Config.kt` | □ (exists) | □ (exists) | □ (exists) | □ | □ (exists as AppConfig.kt) |
| `platform/PlatformModule.kt` (Hilt/Koin) | □ (exists) | □ (exists) | □ (exists, Koin) | □ | □ |
| `BLAuthClient` wired | □ (intentionally skipped) | □ | □ (exists) | □ | □ |
| `BLTelemetryClient` wired | □ (exists) | □ | □ (exists) | □ | N/A |
| `BLFeatureFlagClient` wired | □ (exists) | □ | □ (exists) | □ | N/A |
| `BLKillSwitchClient` wired | □ (exists) | □ (exists) | □ (exists) | □ | N/A |
| No hand-rolled HttpURLConnection for platform calls | □ | □ | □ (exists) | □ | □ |
| `./gradlew compileDebugKotlin` passes | □ | □ | □ | □ | □ |
### React Native / Expo Apps (@bytelyst/\*)
| Check | NomGap | NoteLett | FlowMonk |
| ------------------------------------------- | ---------- | ---------- | ---------- |
| `@bytelyst/auth-client` | □ (exists) | □ (exists) | □ (exists) |
| `@bytelyst/platform-client` | □ (exists) | □ (exists) | □ (exists) |
| `@bytelyst/telemetry-client` | □ (exists) | □ (exists) | □ |
| `@bytelyst/feature-flag-client` | □ (exists) | □ (exists) | □ (exists) |
| `@bytelyst/kill-switch-client` | □ (exists) | □ (exists) | □ (exists) |
| `@bytelyst/offline-queue` | □ (exists) | □ (exists) | □ (exists) |
| `@bytelyst/blob-client` | □ (exists) | □ (exists) | □ |
| `@bytelyst/design-tokens` | □ (exists) | □ (exists) | □ |
| No hand-rolled `fetch()` for platform calls | □ | □ (exists) | □ |
| `npm run typecheck` passes | □ | □ | □ |
---
## Execution Order Recommendation
```
Week 1 (HIGH priority):
Phase 1: ChronoMind Android (1 day)
Phase 2: JarvisJr Android (1 day)
Week 2 (MEDIUM priority):
Phase 3: FlowMonk Mobile RN (0.5 day)
Phase 5: NomGap RN (0.5 day)
Phase 4: LysnrAI Android (1 day)
Week 3 (MEDIUM priority):
Phase 6: LysnrAI iOS (1 day)
Phase 7: Auth App Android (0.5 day)
Week 4 (LOW priority + cleanup):
Phase 8: Auth App iOS (0.5 day)
Phase 9A: ChronoMind iOS — Config + KillSwitch (0.5 hour)
Phase 9B: NoteLett Mobile — diagnostics-client (0.5 hour)
Phase 9C: MindLyst iOS — PlatformServiceClient + Config + KillSwitch + CrashReporter (1 hour)
Phase 9D: JarvisJr iOS — PlatformSyncManager (0.5 hour)
Final verification matrix sign-off
```
---
## Notes
- **MindLyst iOS** (`mindlyst-native/iosApp/`) has **26 Swift files** including 4 Platform/ SDK wrappers (`AuthService`, `KeychainHelper`, `TelemetryService`, `FeatureFlagService`), Screens, Models, Services, Navigation, Widgets, and ShareExtension. **Minor gap:** `Services/PlatformServiceClient.swift` is a hand-rolled URLSession client for platform-service dev wiring — should be migrated to use `BLPlatformClient` via a Platform/ wrapper. Also missing: `KillSwitchService`, `CrashReporter`, `Config.swift`.
- **MindLyst Android** (`mindlyst-native/androidApp/`) uses Koin DI (not Hilt). `platform/Config.kt` and `platform/PlatformModule.kt` exist. Provides **all 6 SDK components** (`BLPlatformConfig`, `BLSecureStore`, `BLAuthClient`, `BLTelemetryClient`, `BLFeatureFlagClient`, `BLKillSwitchClient`) via Koin module. No gaps.
- **PeakPulse iOS** is the cleanest iOS app — full Platform/ directory with 9 wrappers including `DiagnosticsService`. No gaps.
- **JarvisJr iOS** has a clean Platform/ directory with 7 wrappers. **Minor gap:** `Shared/Cloud/PlatformSyncManager.swift` uses raw URLSession — should be migrated to `BLSyncEngine` or `BLPlatformClient`.
- **TextCleanupService** (LysnrAI) and other LLM-specific services that use URLSession for OpenAI/Azure calls are intentionally product-specific and should NOT be migrated to platform SDK.
- **CertificatePinning** is a security feature that may need to stay product-specific. Evaluate during Phase 4/6 whether it's worth adding to the SDK.
- **LysnrAI Android `AuthViewModel`** is intentionally kept hand-rolled (DataStore, keyboard bridging, CloudSync integration are too LysnrAI-specific for `BLAuthClient`). This is a conscious design decision, not a gap.
---
## Post-Implementation Review Fixes (2026-03-21)
Systematic review of all 9 phases found **7 bugs** across 7 repos. All fixed and pushed.
### Bug 1: LysnrAI Android — SettingsStore.kt incorrectly deleted
- **Root cause:** Phase 4 grep for callers searched by class name `SettingsStore`, but callers import `SettingsKeys` and `settingsStore` (top-level extension property).
- **Impact:** 5 files with broken imports (SettingsScreen, RecordViewModel, RecordScreen, AuthViewModel, SessionDetailScreen).
- **Fix:** [`ada3d43`](https://github.com/saravanakumardb1/learning_voice_ai_agent/commit/ada3d43) — restored SettingsStore.kt.
### Bug 2: Android kill switch — `result.killed` should be `result.disabled`
- **Root cause:** `BLKillSwitchClient.KillSwitchResult` uses `disabled: Boolean`, not `killed`. Code was written without verifying SDK API.
- **Impact:** Kill switch never triggered on Auth App, JarvisJr, ChronoMind Android.
- **Fix:**
- Auth App: [`bf518fd`](https://github.com/saravanakumardb1/learning_ai_auth_app/commit/bf518fd)
- JarvisJr: [`dddb11c`](https://github.com/saravanakumardb1/learning_ai_jarvis_jr/commit/dddb11c)
- ChronoMind: [`151d836`](https://github.com/saravanakumardb1/learning_ai_clock/commit/151d836)
### Bug 3: Auth App iOS — BLKillSwitchClient.check() returns Void
- **Root cause:** Swift `BLKillSwitchClient.check()` sets `isDisabled`/`maintenanceMessage` as instance properties. Code treated it as returning a result struct.
- **Also:** `BLFeatureFlagClient` uses `start()` not `poll()`.
- **Fix:** [`215e7c9`](https://github.com/saravanakumardb1/learning_ai_auth_app/commit/215e7c9)
### Bug 4: ChronoMind iOS — KillSwitchService returned nonexistent BLKillSwitchResult
- **Root cause:** Same as Bug 3. `BLKillSwitchResult` type doesn't exist in the Swift SDK.
- **Fix:** [`d3c3aff`](https://github.com/saravanakumardb1/learning_ai_clock/commit/d3c3aff) — expose `isDisabled`/`maintenanceMessage` as computed properties.
### Bug 5: MindLyst iOS — KillSwitchService + CrashReporterService wrong API
- **Root cause:** KillSwitchService same as Bug 4. CrashReporter takes `productId: String`, not `config: BLPlatformConfig`. Also `@MainActor` + `NSObject` subclass needs lazy init.
- **Fix:** [`cd78597`](https://github.com/saravanakumardb1/learning_multimodal_memory_agents/commit/cd78597)
### Bug 6: JarvisJr iOS — KillSwitchResponse decoded `killed` (doesn't exist in server response)
- **Root cause:** Pre-existing bug. Platform-service returns `{ disabled: Bool }` but hand-rolled `KillSwitchResponse` decoded `killed`, which is always nil/false.
- **Fix:** [`2a3cc7e`](https://github.com/saravanakumardb1/learning_ai_jarvis_jr/commit/2a3cc7e)
### Bug 7: NoteLett Mobile — `createDiagnosticsClient` doesn't exist
- **Root cause:** `@bytelyst/diagnostics-client` exports `DiagnosticsClient` class with `getInstance()` singleton, not a factory function.
- **Fix:** [`0b5c224`](https://github.com/saravanakumardb1/learning_ai_notes/commit/0b5c224)