fix: smooth out iOS core-loop friction (401 re-auth, player exit, HQ swap)#33
Merged
Conversation
…swap) Three friction fixes in the core watch/auth loop. #11 - Global 401 -> graceful re-auth. The Dio onError interceptor now detects a real 401 on any protected (non-auth) endpoint and hands off to AuthNotifier.handleSessionExpired(), which resets to the profile picker (currentUser -> null drives the GoRouter redirect to '/'), clears the stored session, disconnects the websocket, and surfaces a "Session expired - sign in again" SnackBar via a new app-wide ScaffoldMessenger key. Auth/credential endpoints (everything under /api/auth/) are excluded so bad-PIN 401/403s still surface inline. A burst of concurrent 401s resets and notifies exactly once; connection errors (no response) and non-auth 403s do not trigger it. #12 + #15 - Instant player exit + single progress save. _navigateBack now fires the final /progress save unawaited and pops immediately, so leaving is instant regardless of network. A _progressSavedOnExit guard stops dispose() from sending a second save: teardown is exactly one PUT. #19 - Preview->HQ disposal crash. _switchToHq now defers the old preview controller's pause/dispose to a post-frame callback, so the outgoing VideoPlayer never reads a disposed controller in the same frame. Verified: dart format clean, flutter analyze (--fatal-infos --fatal-warnings) clean, flutter test green (72 tests, +3 for handleSessionExpired). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RXMKM1rDWn8wNh93MMUtxY
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This was referenced Jun 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three core play/auth-loop friction fixes from the roadmap audit (NOW #8/#9/#10). Fixes #11, #12, #15, #19.
#11 — Graceful 401 recovery (no more dead-ends)
An expired/revoked session previously trapped the user on "Failed to load" across every tab with a Retry that could never succeed. Now a real 401 on a protected endpoint resets auth and bounces to the profile picker with a "Session expired — sign in again" snackbar.
ApiServiceexposes anonUnauthorizedcallback fired from the DioonErrorinterceptor only whenstatusCode == 401and the request isn't an/auth/endpoint (those return 401/403 for bad credentials and are handled inline). Connection failures carry noresponse, so they're correctly excluded.AuthNotifier.handleSessionExpired()clears state synchronously (router redirects via the existingrefreshListenable), disconnects the WebSocket, and clears stored session — returningtrueonly if a session existed, so a burst of concurrent 401s resets and notifies exactly once.scaffoldMessengerKey(wired intoMaterialApp.router) so the API layer can surface a snackbar.#12 + #15 — Instant player exit, exactly one progress save
_navigateBackno longer awaits the final save (instant exit on slow/dead networks), and a_progressSavedOnExitguard stopsdispose()from saving a second time — so teardown sends exactly one/progressPUT.#19 — Preview→HQ disposal crash
The old preview controller is now disposed in a post-frame callback instead of synchronously mid-frame, so the outgoing
VideoPlayernever reads a disposed controller.Verification
dart format,flutter analyze, andflutter test(incl. a newauth_notifier_test.dartcovering the session-expiry reset/dedup) all pass locally.Supersedes community PRs #28/#29/#31 (cleaner implementations of the same fixes).
🤖 Generated with Claude Code
https://claude.ai/code/session_01RXMKM1rDWn8wNh93MMUtxY