90d8aae51a
Slice 4 — completes the #392 hybrid live-refresh loop. live_events_provider.dart subscribes to /api/events/stream via dio's streaming response mode, parses SSE frames (kind + JSON data + UserID scope), and exposes them as a Riverpod StreamProvider. Heartbeat comments are silently dropped; malformed JSON frames are skipped. The provider auto-rebuilds when auth state changes (token rotation, sign-out → sign-in), so reconnect is implicit. live_events_dispatcher.dart listens to the stream and invalidates the small set of publicly-importable providers we know about: - myQuarantineProvider + homeProvider on any quarantine.* event - homeProvider on any playlist.* event (Home renders the Playlists row) Screen-private providers (library_screen.dart's _liked* / _libraryAlbums / _history, admin screens, etc.) opt in to live-refresh by themselves listening to liveEventsProvider in follow-up commits; the dispatcher stays small and avoids back-edge dependencies on every feature folder. The dispatcher also installs an AppLifecycleState observer for resume-time defensive invalidation. SSE will catch up on its own when the app returns from background, but the invalidate flushes any stale data immediately so the first frame back is fresh. app.dart wires the dispatcher into the post-first-frame callback alongside the other startup activations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>