# 31. iOS App — API Client, tRPC Bridge, and Offline Support

meta:
  id: shieldai-unified-restructure-31
  feature: shieldai-unified-restructure
  priority: P1
  depends_on: [shieldai-unified-restructure-28, shieldai-unified-restructure-29, shieldai-unified-restructure-30]
  tags: [ios, swift, api, networking, offline, mobile]

objective:
- Build the API client layer for the iOS app that communicates with the unified monolith's tRPC endpoints. Since tRPC is TypeScript-native, we'll use a thin HTTP bridge approach: the iOS client calls tRPC procedures as typed HTTP JSON requests. Add offline support with local caching and request queuing.

deliverables:
- `iOS/ShieldAI/Services/APIClient.swift` — HTTP client:
  - Wraps `URLSession` with async/await (`async throws` methods)
  - Base URL from environment/config (`https://api.shieldai.com` or local)
  - Automatic auth header injection (JWT from Keychain)
  - Request/response logging in debug builds
  - Retry logic with exponential backoff for network errors
  - Timeout configuration
- `iOS/ShieldAI/Services/TRPCBridge.swift` — tRPC procedure caller:
  - `callProcedure<T: Decodable>(path: String, input: Encodable?) async throws -> T`
  - Serializes input to JSON, sends POST to `/api/trpc/{path}`
  - Deserializes response JSON to Swift `Decodable` type
  - Handles tRPC error format (`{ error: { message, code } }`)
  - Type-safe wrappers for common procedures:
    - `user.me() -> User`
    - `billing.getSubscription() -> Subscription`
    - `darkwatch.getWatchlist() -> [WatchlistItem]`
    - etc.
- `iOS/ShieldAI/Models/` — Swift data models:
  - `User.swift`, `Subscription.swift`, `WatchlistItem.swift`, `Exposure.swift`, `Alert.swift`, `VoiceEnrollment.swift`, `VoiceAnalysis.swift`, `SpamRule.swift`, `PropertyWatchlistItem.swift`, `RemovalRequest.swift`, `BrokerListing.swift`, `NormalizedAlert.swift`, `CorrelationGroup.swift`, `SecurityReport.swift`
  - All models conform to `Codable`, `Identifiable`, `Equatable`
  - Enum types for Swift (e.g., `SubscriptionTier`, `AlertSeverity`, `ExposureSource`)
- `iOS/ShieldAI/Services/CacheManager.swift` — Offline cache:
  - `CacheEntry` with data, timestamp, TTL
  - Stores in `UserDefaults` (small data) or file system (large data)
  - `getCached<T>(key: String) -> T?`
  - `setCached<T>(key: String, value: T, ttl: TimeInterval)`
  - Automatic cache invalidation on TTL expiry
- `iOS/ShieldAI/Services/OfflineQueue.swift` — Request queue:
  - Queues mutations when offline
  - Retries queued requests when connectivity restored
  - Persists queue to disk
  - `addToQueue(request: QueuedRequest)`
  - `processQueue()` — called when network becomes available
- `iOS/ShieldAI/Services/NetworkMonitor.swift` — Connectivity monitoring:
  - Uses `NWPathMonitor` to track network state
  - Publishes `isConnected` boolean
  - Triggers queue processing when connection restored

steps:
1. Create `iOS/ShieldAI/Services/` and `iOS/ShieldAI/Models/` directories.
2. **APIClient**:
   - Create `APIClient` class as `@Observable` or singleton
   - `request<T: Decodable>(endpoint: String, method: String, body: Data?) async throws -> T`
   - Add request interceptor for auth header
   - Add response interceptor for error parsing
   - Use `JSONEncoder`/`JSONDecoder` with custom date formatting
3. **TRPCBridge**:
   - `callProcedure` constructs tRPC batch request format:
     ```json
     { "0": { "json": { "email": "test@example.com" } } }
     ```
   - Parse tRPC response format:
     ```json
     { "0": { "result": { "data": { ... } } } }
     ```
   - Map tRPC error codes to Swift `APIError` enum
   - Create convenience methods for each router procedure
4. **Models**:
   - Define Swift structs matching Drizzle schema shapes
   - Use `CodingKeys` where JSON keys differ from Swift naming
   - Define enums for all database enums (e.g., `SubscriptionTier: String, Codable`)
   - Add computed properties where needed (e.g., `Alert.isCritical: Bool`)
5. **CacheManager**:
   - Use `JSONEncoder` to serialize values
   - Store in `UserDefaults` with key prefix `shieldai.cache.`
   - TTL check: compare `Date()` to stored timestamp
   - Clear all cache on logout
6. **OfflineQueue**:
   - `QueuedRequest` struct: `endpoint`, `method`, `body`, `timestamp`, `retryCount`
   - Store array in `UserDefaults` or JSON file
   - `processQueue`: iterate requests, call APIClient, remove on success, increment retry on failure
   - Max retries: 3, then mark as failed and notify user
7. **NetworkMonitor**:
   - `NWPathMonitor` with `.queue = .main`
   - `@Published var isConnected: Bool`
   - On change to connected: trigger `OfflineQueue.processQueue()`
8. Create `APIConfig.swift`:
   - `baseURL`, `timeout`, `maxRetries` from environment or plist
   - Different values for debug/release builds
9. Test all components with mocked URLSession.

steps:
- Unit: APIClient injects auth header correctly
- Unit: TRPCBridge parses tRPC response format correctly
- Unit: CacheManager stores and retrieves values with TTL
- Unit: OfflineQueue persists requests and processes them in order
- Unit: NetworkMonitor publishes correct connectivity state
- Integration: APIClient successfully calls `user.me` against local dev server

acceptance_criteria:
- [ ] APIClient makes authenticated HTTP requests to tRPC endpoints
- [ ] TRPCBridge correctly serializes tRPC batch input and deserializes responses
- [ ] All common API procedures have type-safe Swift wrappers
- [ ] Network errors trigger retry with exponential backoff
- [ ] CacheManager stores GET responses and returns cached data when offline
- [ ] OfflineQueue persists mutations and retries when connectivity restored
- [ ] NetworkMonitor accurately tracks connectivity state
- [ ] All models are Codable and match backend schema shapes
- [ ] API configuration supports different environments (dev, staging, prod)

validation:
- Point APIClient to local dev server (`http://localhost:3000`)
- Call `user.me()` and verify response parsed into `User` model
- Disconnect network, attempt a mutation, verify it queues
- Reconnect network, verify queued mutation executes automatically
- Verify cache hit by calling same endpoint twice with network disabled
- Run unit tests via Xcode Cmd+U

notes:
- Since tRPC is TypeScript-native, the iOS client cannot use tRPC's type-safe client directly. The HTTP bridge is the pragmatic approach.
- Consider generating Swift models and API wrappers automatically from the tRPC router types using a code generation tool (e.g., custom script parsing tRPC router exports). For now, manual definitions are fine.
- The tRPC batch link sends multiple procedures in one HTTP request. For simplicity, the iOS client can use single-procedure requests (`/api/trpc/user.me`) instead of batching.
- Use `OSLog` for structured logging in debug builds. Avoid printing sensitive data (tokens, passwords).
- For large responses (e.g., full alert history), consider pagination and storing pages in cache separately.
- The offline queue should only persist safe mutations (idempotent or retry-safe). Avoid queuing destructive operations without user confirmation.
