# Architecture Types — 2026-05-20 Upgrade Session

Seven new domain/data types added. All are additive, zero-rewrite, compile-clean.

## NetworkResult<T>
**File:** `domain/model/NetworkResult.kt`
**Purpose:** Unified sealed result for all network operations.

```kotlin
sealed interface NetworkResult<out T> {
    data class Success<T>(val data: T) : NetworkResult<T>
    data class HttpError(code: Int, message: String?, bodyPreview: String?, source: String) : NetworkResult<Nothing>
    data class Timeout(source: String) : NetworkResult<Nothing>
    data class Offline(source: String, message: String?) : NetworkResult<Nothing>
    data class ParseError(source: String, message: String) : NetworkResult<Nothing>
    data class AuthExpired(source: String) : NetworkResult<Nothing>
    data class RateLimited(source: String, retryAfterMs: Long?) : NetworkResult<Nothing>
    data class Unknown(source: String, message: String) : NetworkResult<Nothing>
}
```

**Key API:** `NetworkResult.of("TMDB") { tmdbApi.search(q) }` — maps exceptions to typed variants. Always rethrows `CancellationException`.

**Pitfall:** Changing existing public API from `Result<T>` to `NetworkResult<T>` breaks all callers (`.getOrNull()`, `.onSuccess{}` don't exist on `NetworkResult`). `NetworkResult` is for NEW code. Audit call sites before changing return types.

**Pitfall:** Inline `of()` cannot call private companion functions. Inline code directly for `Retry-After` header parsing.

**Pitfall:** `kotlinx.serialization 1.7.3` has `SerializationException` but NOT `JsonDataException`. Use `SerializationException` which covers all parse errors.

## LoadState<T>
**File:** `domain/model/LoadState.kt`
**Purpose:** Replaces `isLoading: Boolean / error: String? / data: T?` anti-pattern.

```kotlin
sealed interface LoadState<out T> {
    data object Idle : LoadState<Nothing>
    data object Loading : LoadState<Nothing>
    data class Data<T>(value: T, isStale: Boolean = false) : LoadState<T>
    data class Empty(message: String) : LoadState<Nothing>
    data class Error(message: String, recoverable: Boolean = true, cause: String?) : LoadState<Nothing>
}
```

## UiEffect
**File:** `domain/model/UiEffect.kt`
**Purpose:** One-shot navigation/snackbar/external effects. Collect via `SharedFlow` in `LaunchedEffect`.

```kotlin
sealed interface UiEffect {
    data class Navigate(route: String, popUpTo: String? = null) : UiEffect
    data class ShowSnackbar(message: String, isError: Boolean = false) : UiEffect
    data class OpenExternalUrl(url: String) : UiEffect
    data object GoBack : UiEffect
}
```

## ServiceConnectionState
**File:** `domain/model/ServiceConnectionState.kt`
**Purpose:** Typed service auth state replacing ad-hoc status strings.

```kotlin
sealed interface ServiceConnectionState {
    data object NotConfigured : ServiceConnectionState
    data object Connecting : ServiceConnectionState
    data class Connected(accountName: String?) : ServiceConnectionState
    data class NeedsReconnect(reason: String) : ServiceConnectionState
    data class Error(message: String, recoverable: Boolean = true) : ServiceConnectionState
}
```

## PlaybackState
**File:** `domain/model/PlaybackState.kt`
**Purpose:** Explicit player state machine wired into `PlayerUiState.playbackState`.

```kotlin
sealed interface PlaybackState {
    data object Idle : PlaybackState
    data class Validating(sourceName: String, url: String) : PlaybackState
    data class Preparing(source: StreamSource) : PlaybackState
    data class Playing(source: StreamSource, positionMs: Long, durationMs: Long) : PlaybackState
    data class Buffering(source: StreamSource, reason: String?) : PlaybackState
    data class Retrying(failedSource: StreamSource, nextSource: StreamSource, attemptNumber: Int) : PlaybackState
    data object Completed : PlaybackState
    data class Error(message: String, canRetry: Boolean, lastSource: StreamSource?) : PlaybackState
    data class StreamSource(name: String, quality: String?)
}
```

**Current usage:** Additive field in `PlayerUiState`. The `combine` block in `PlayerViewModel` maps ExoPlayer state → `PlaybackState`. Existing `isPlaying`/`isBuffering` fields still work.

## NetworkTimeouts
**File:** `data/di/NetworkTimeouts.kt`
Centralized timeout constants for 7 service categories. Values audited from existing per-module constants.

## SensitiveLogSanitizer
**File:** `data/di/SensitiveLogSanitizer.kt`
Centralized redaction utility. See `sanitizer-wiring-2026-05-20.md` for wiring instructions.

## EnsureActiveProfileUseCase
**File:** `domain/usecase/EnsureActiveProfileUseCase.kt`
**Purpose:** Guarantees valid active profile before Home. Hilt auto-provided (`@Inject` + `@Singleton`).

```kotlin
@Singleton
class EnsureActiveProfileUseCase @Inject constructor(
    private val profileDao: ProfileDao,
    private val preferencesManager: PreferencesManager,
) {
    suspend operator fun invoke(): Int  // returns active profile ID
}
```

**Wired into:** `OnboardingViewModel.skipAll()` and `complete()`. Replaces inline `ensureDefaultProfile()` method.
