# Architecture Upgrade Gap Analysis — 2026-05-20

Research synthesis cross-referencing Raymond's 1000-line Cursor master prompt ("NexStream_Cursor_Database_Network_UI_State_Upgrade.md") against Android best practices and current project state.

## Research Sources
- Android Developer Architecture Guide (official docs)
- Kotlin coroutines best practices (official docs)
- Android UI layer state holders guide
- Google's NowInAndroid reference architecture
- androidx.tv.material3 1.0.0-rc01 API surface

## What Was Already Done (From Prior Sessions)

| Doc Section | Status | Details |
|---|---|---|
| DB composite indexes (B5) | ✅ | `profileId+mediaId`, `profileId+isWatched+lastPlayedAt` per v2→v3 migration |
| Profile-scoped queries (B4) | ✅ | All hardcoded `profileId=0` fixed across 5 ViewModels |
| DB migrations (B7) | ✅ | `MIGRATION_1_2` + `MIGRATION_2_3` registered with `fallbackToDestructiveMigrationFrom(0)` |
| Addon isolation (C7) | ✅ | `supervisorScope` in `StremioAddonClientImpl` |
| Rate limiting (C8) | ✅ | `TmdbRateLimitInterceptor` (40 req/10s sliding window) |
| Home split-state (D4) | ✅ | Progressive loading, `RowLoadResult` per row |
| Details split-state (D5) | ✅ | `HeroState`, `SourceListState`, `SeasonsState`, `JustWatchState`, `RelatedState` sub-states |
| D-pad focus restoration | ✅ | `SavedStateHandle` in `HomeViewModel` |
| Onboarding launch gate | ✅ | `LaunchViewModel` routes correctly based on `firstLaunch` + `activeProfileId` |

## What Was Implemented (This Session)

| Doc Section | Item | Files Created |
|---|---|---|
| C4 | `NetworkResult<T>` sealed interface | `domain/model/NetworkResult.kt` |
| D3 | `LoadState<T>` sealed interface | `domain/model/LoadState.kt` |
| D3 | `UiEffect` one-shot effects | `domain/model/UiEffect.kt` |
| C9 | `ServiceConnectionState` sealed interface | `domain/model/ServiceConnectionState.kt` |
| D6 | `PlaybackState` sealed interface | `domain/model/PlaybackState.kt` |
| B8 | `EnsureActiveProfileUseCase` | `domain/usecase/EnsureActiveProfileUseCase.kt` |
| C5 | `NetworkTimeouts` centralized constants | `data/di/NetworkTimeouts.kt` |
| C6 | `SensitiveLogSanitizer` | `data/di/SensitiveLogSanitizer.kt` |
| C6 | Sanitizer wired into 4 OkHttp clients | TmdbModule, TraktModule, DebridModule, StremioAddonClientImpl |
| C4 | `NetworkResult` wired into `DebridService.resolveStream()` | `data/debrid/DebridService.kt` |
| B8 | `EnsureActiveProfileUseCase` wired into onboarding | `ui/screens/onboarding/OnboardingViewModel.kt` |
| D6 | `PlaybackState` wired into `PlayerUiState` | `player/PlayerViewModel.kt` |

## What Was Intentionally Skipped

| Item | Reason |
|---|---|
| `NetworkResult` in TraktService | Would break `HomeViewModel` caller (`.getOrNull()`) — `NetworkResult` is for NEW code |
| `NetworkResult` in TmdbRepository | Already has graceful fallback values on error |
| `LoadState` adoption in all ViewModels | Existing split-state models work well — `LoadState` is available for future screens |
| DB cache lifecycle on `WatchProgress` | Already has `lastPlayedAt`; separate `expiresAt` column not needed for current queries |
| Indexes on `addons`/`profiles`/`id_bridge` | Tables are tiny (< 20 rows each) — index maintenance cost > scan benefit |
| Baseline Profiles (Part E) | Out of scope for architecture pass |

## Audit Findings

| Severity | Finding | Action |
|---|---|---|
| ⚠ Low | `Screen.AddonUrl` dead route in `Screen.kt` | Removed — `AddonUrlInput` used inline, route never navigated |
| ℹ Info | `getByImdbId()` in `BridgeDao` is unused | No action — `getByTmdbId` (PK) covers all actual queries |
| ℹ Info | `SettingsAboutScreen` has no ViewModel | Intentional — static info page |

## Verification

```
./gradlew compileDebugKotlin  → PASS (0 errors)
./gradlew testDebugUnitTest   → PASS (17 tests, 0 failures)
./gradlew assembleDebug       → PASS (APK built)
```
