high priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

OrgBrandingCache fetches logo URL, primary color (hex string), font family name, and border radius value from the Supabase `org_branding` table on first selection
Second access to the same org reads from the in-memory Map without triggering a network call (verifiable via mock assertion)
On app cold start, disk cache (shared_preferences or Hive) is read first; a network call is made only if disk cache is absent or stale
When the user switches organizations, the cache invalidates the previous org's in-memory entry and begins pre-warming the new org's branding
Pre-warming completes before the org's home screen is navigated to — no white-flash or unstyled content on first render
When Supabase is unreachable, the cache falls back to hardcoded default design tokens (defined in the app's design token system) without throwing an unhandled exception
Riverpod AsyncNotifier exposes an `OrgBrandingData` value object with typed fields (no raw Map<String, dynamic> leaking to consumers)
Disk cache is cleared when the user logs out or resets the app
Cache serialization/deserialization round-trips without data loss for all supported field types (String, Color hex, double)

Technical Requirements

frameworks
Flutter
Riverpod
shared_preferences or Hive
Supabase Flutter SDK
apis
Supabase REST API — `org_branding` table select
data models
organizations
org_branding (logo_url, primary_color, font_family, border_radius)
performance requirements
In-memory cache hit must return in under 1ms (synchronous Map lookup)
Disk cache read must complete within 50ms on cold start
Network fetch must not block UI navigation — use async pre-warm pattern
security requirements
Logo URLs must be validated as HTTPS before being used in Image.network to prevent mixed-content issues
Disk-cached data must not include sensitive user information — branding data only
Cache keys must be org-scoped (e.g., `branding_cache_{orgId}`) to prevent cross-org cache collisions

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Model the cache as a Riverpod `AsyncNotifier` with a `warmFor(String orgId)` method called during org selection. Use a private `_memoryCache = {}` field for the in-memory layer. For disk persistence, `shared_preferences` is simpler and sufficient for this data size — prefer it over Hive unless Hive is already a project dependency. Define `OrgBrandingData` as an immutable value class with `const` constructor and `==`/`hashCode` overrides.

Default tokens should reference the existing design token system constants (e.g., `AppColors.brandPrimary`) rather than hardcoded hex values. Avoid storing `Color` objects directly to disk — serialize as hex strings and parse on read. Pre-warm pattern: in the org selection screen's `onOrgSelected` handler, `await orgBrandingCache.warmFor(orgId)` before triggering navigation.

Testing Requirements

Unit tests required (see task-007). Additionally verify: (1) `OrgBrandingData` value object equality and copyWith behavior, (2) serialization helpers for disk persistence, (3) Riverpod provider graph — ensure `OrgBrandingCache` provider correctly declares its dependencies.

No widget tests in this task — widget integration is covered by OrgCardWidget tests. Mock the Supabase client using a Dart interface/abstract class to enable deterministic unit testing without network calls.

Component
Organization Branding Cache
data low
Epic Risks (3)
high impact medium prob technical

iOS Keychain and Android Keystore have meaningfully different failure modes and permission models. The secure storage plugin may throw platform-specific exceptions (e.g., biometric enrollment required, Keystore wipe after device re-enrolment) that crash higher-level flows if not caught at the adapter boundary.

Mitigation & Contingency

Mitigation: Wrap all storage plugin calls in try/catch at the adapter layer and expose a typed StorageResult<T> instead of throwing. Write integration tests on real device simulators for both platforms in CI using Fastlane. Document the exception matrix during spike.

Contingency: If a platform-specific failure cannot be handled gracefully, fall back to in-memory-only storage for the current session and surface a non-blocking warning to the user; log the event for investigation.

high impact medium prob integration

Setting a session-level Postgres variable (app.current_org_id) via a Supabase RPC requires that RLS policies on every table reference this variable. If the Supabase project schema has not yet defined these policies, the configurator will set the variable but queries will return unfiltered data, giving a false sense of security.

Mitigation & Contingency

Mitigation: Include a smoke-test RPC in the SupabaseRLSTenantConfigurator that verifies the variable is readable from a policy-scoped query before marking setup as complete. Coordinate with the database migration task to ensure RLS policies reference app.current_org_id before the configurator is shipped.

Contingency: If RLS policies are not in place at integration time, gate all data-fetching components behind a runtime check in SupabaseRLSTenantConfigurator.isRlsScopeVerified(); block data access and surface a developer warning until policies are confirmed.

medium impact medium prob technical

Fetching feature flags from Supabase on every cold start adds network latency before the first branded screen renders. On slow connections this may cause a perceptible blank-screen gap or cause the app to render with default (unflagged) state before flags arrive.

Mitigation & Contingency

Mitigation: Persist the last-known flag set to disk in the FeatureFlagProvider and serve stale-while-revalidate on startup. Gate flag refresh behind a configurable TTL (default 15 minutes) so network calls are not made on every launch.

Contingency: If stale flags cause a feature to appear that should be hidden, add a post-load re-evaluation pass that reconciles the live flag set with the rendered widget tree and triggers a targeted rebuild where needed.