high priority low complexity frontend pending frontend specialist Tier 6

Acceptance Criteria

After LoginSuccess is emitted, AuthService.persistSession() is called exactly once with the session object
persistSession() is called as a side-effect after emit — does not block or delay state emission
On BLoC initialization (constructor or init event), AuthService.restoreSession() is called
If restoreSession() returns a valid non-expired session, LoginSuccess is emitted immediately
If restoreSession() returns null or expired session, LoginFormIdleState is emitted (show login form)
Session restoration path bypasses CredentialValidator and AuthService.signIn() completely
If persistSession() throws, the error is logged but does NOT cause a state change (LoginSuccess already emitted)
Restored session LoginSuccess includes the same session and role fields as a freshly authenticated LoginSuccess
BLoC exposes an initial state of LoginLoading (not LoginFormIdleState) while session restoration check is in progress

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
apis
AuthService.persistSession(session)
AuthService.restoreSession()
data models
AuthSession
UserRole
LoginSuccess
LoginFormIdleState
LoginLoading
performance requirements
Session restoration must complete before the login form is shown — use initial LoginLoading state during check
persistSession() should be fire-and-forget (unawaited) or awaited in a non-blocking way to avoid delaying navigation
security requirements
Persisted session tokens must be stored in platform secure storage (flutter_secure_storage or equivalent) — not SharedPreferences
Session expiry must be validated before emitting LoginSuccess from restored session — never trust stored token blindly
On session restoration failure, clear any stale stored tokens to prevent indefinite retry loops

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Trigger session restoration in the BLoC constructor by adding an AppStarted or CheckSessionEvent and dispatching it immediately in the constructor body. Set the BLoC's initial state to LoginLoading so the UI shows a spinner during the check. Use unawaited() (from dart:async) for persistSession() if you want fire-and-forget, or await it inside a try/catch that only logs on failure. Keep restoration logic inside AuthService — the BLoC should only call restoreSession() and react to the result, not implement token reading itself.

This keeps the BLoC thin and testable.

Testing Requirements

Unit tests using bloc_test. Test (1) BLoC init with valid stored session → emits LoginLoading then LoginSuccess; (2) BLoC init with no stored session → emits LoginLoading then LoginFormIdleState; (3) BLoC init with expired session → emits LoginLoading then LoginFormIdleState; (4) after SignIn success, verify persistSession() is called on mock AuthService; (5) persistSession() throws — verify LoginSuccess was already emitted and no additional state change occurs. Mock AuthService with Mockito/mocktail.

Component
Login Form BLoC
service medium
Epic Risks (2)
high impact medium prob integration

Supabase GoTrue returns HTTP error codes and string messages that may change between SDK versions. Incorrect or incomplete mapping could cause the wrong user-facing message to be shown (e.g., showing a generic error instead of a specific credential error), violating the plain-language feedback acceptance criteria and potentially exposing security-sensitive information.

Mitigation & Contingency

Mitigation: Pin the supabase_flutter SDK to a specific minor version in pubspec.yaml. Write integration tests that mock the Supabase HTTP layer and assert each error code maps to the correct domain exception. Document the mapping table as a constant in AuthService.

Contingency: If an unrecognized error code is received at runtime, catch it as an UnknownAuthException and display a generic safe message. Alert via crash reporting for triage and SDK update.

medium impact medium prob technical

If the user taps the sign-in button multiple times rapidly, concurrent authentication requests could result in race conditions: duplicate network calls, out-of-order state emissions, or multiple session tokens being written to secure storage.

Mitigation & Contingency

Mitigation: Use bloc concurrency transformer (droppable or restartable) to ensure only one authentication event is processed at a time. The BLoC should guard against submission while in LoginLoading state.

Contingency: Add a UI-level disable on the submit button when loading state is active as a secondary guard independent of BLoC concurrency control.