critical priority low complexity infrastructure pending backend specialist Tier 0

Acceptance Criteria

A single SupabaseAuthClient class exists with a private constructor and a static factory/getter that always returns the same instance
The singleton is initialized exactly once per app lifecycle — verified by logging or test double that constructor is only called once
SUPABASE_URL and SUPABASE_ANON_KEY are read exclusively from environment config, never hardcoded
Accessing the singleton before initialization throws a clear, descriptive StateError with the message 'SupabaseAuthClient not initialized — call SupabaseAuthClient.initialize() first'
The GoTrueClient instance is accessible via a typed getter (e.g., client.auth)
No race condition: concurrent calls to the factory during initialization return the same instance without error
Unit test confirms the singleton identity: two references obtained at different times are identical (identical() returns true)

Technical Requirements

frameworks
Flutter
supabase_flutter
apis
Supabase GoTrue Auth API
data models
AuthSession
User
performance requirements
Initialization completes in under 500ms on a cold start
Subsequent singleton accesses are O(1) — no async work after first init
security requirements
SUPABASE_ANON_KEY must never appear in source code or be committed to version control
The singleton must not expose the raw API key via any public getter
No credentials logged to console in any build variant

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use the established supabase_flutter package pattern: call Supabase.initialize() in main() before runApp(), then access via Supabase.instance.client. If a custom wrapper class is needed (e.g., to allow DI in tests), use a thin facade over Supabase.instance rather than reimplementing GoTrueClient. Dart does not have true thread safety concerns at the VM level for single-isolate apps, but if background isolates are used, pass the client reference explicitly — do not rely on the singleton across isolate boundaries. Mark the initialize() method @visibleForTesting if needed for test overrides.

Testing Requirements

Unit tests using flutter_test: (1) verify singleton identity across multiple getInstance() calls; (2) verify StateError is thrown when accessed before initialize(); (3) verify that initialize() called twice does not re-create the client. Integration test: initialize with real env credentials and assert client.auth is non-null. No mocking of the Supabase client itself in integration tests — use a real staging project.

Component
Supabase Auth Client
infrastructure low
Epic Risks (2)
high impact low prob integration

Supabase client initialization may fail silently in certain Flutter environments if environment variables are missing or the anon key is rotated, leading to runtime null-pointer errors throughout the auth layer.

Mitigation & Contingency

Mitigation: Add explicit assertion checks during app startup that verify the Supabase client is initialized before the router resolves. Document required --dart-define keys in the project README and add a CI step that validates their presence.

Contingency: Implement a fallback initialization error screen with a clear message and a retry button. Log initialization failures to crash reporting immediately.

medium impact medium prob technical

The flutter_secure_storage package behaviour differs between iOS Keychain and Android Keystore implementations. On Android, biometric-enrolled devices may require additional authentication to read stored tokens, causing unexpected session read failures.

Mitigation & Contingency

Mitigation: Test the repository on Android devices with and without biometric enrollment early in development. Use accessibility options in flutter_secure_storage to configure whether biometric authentication is required for storage access.

Contingency: If biometric-gated storage causes regressions, fall back to a non-biometric storage option for session tokens (reserving biometric-gated storage for higher-sensitivity credentials only).