--- id: 77c0b897-4124-432f-84de-700ff82dcde1 type: code_pattern title: "Two-phase cached loading pattern for API-heavy screens in Rust TUI" tags: [sba-scouting, rust, caching, ratatui, async, tokio, sqlx, standings, api, pattern] importance: 0.8 confidence: 0.8 created: "2026-03-02T00:33:19.472159+00:00" updated: "2026-03-02T00:33:19.472159+00:00" --- # Two-Phase Cached Loading Pattern ## Context Implemented for the standings screen in SBA Scout Rust TUI. Should be reused for other API-heavy screens. ## Pattern ### Phase 1: Instant cache load on mount - Load from SQLite cache on `mount()` — instant display - Cache table (`standings_cache`) stores JSON blob per season with `fetched_at` timestamp - Show "updated Xm ago" indicator to user ### Phase 2: Background refresh - Spawn background `tokio::task` to fetch from live API - Update DB cache after successful fetch - Send `StandingsRefreshed` message back to UI via `mpsc::UnboundedSender` - Show spinner while refreshing ## Key Implementation Details - Use `serde::Serialize` on response types for JSON round-trip through DB (`serde_json::to_string` / `from_str`) - Cache table needs: `season` (key), `data_json` (TEXT), `fetched_at` (DATETIME) - Handle stale cache gracefully — always show cached data immediately, refresh in background ## Critical API Shape Gotcha The Major Domo CLI returns bare arrays, but the actual REST API wraps responses: ```json {"count": N, "standings": [...]} ``` Always verify real API response shape, not just CLI output. Mismatched deserialization will silently fail or panic.