high priority low complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

OrgCardWidget renders org logo from `OrgBrandingCache` logo URL using `Image.network` with a placeholder while loading and a fallback icon on error
Org name is displayed in a text element that uses design-token typography (font family, size, weight from token system)
Member count badge is visible and correctly formatted (e.g., '42 members')
When `isActive: true` is passed, the card renders a 2dp highlight border using the org's primary color from `OrgBrandingCache`
When `isActive: false`, no highlight border is shown
The entire card is tappable with a minimum touch target of 48×48dp — verified by inspecting the widget's render box in a widget test
Semantic label is set to '{orgName}, {memberCount} members{, currently selected}' — screen reader reads this as a single coherent announcement
All text/background color combinations pass WCAG 2.2 AA contrast ratio (≥4.5:1 for normal text, ≥3:1 for large text)
Widget accepts an `onTap` callback and invokes it when tapped
Widget works correctly when `memberCount` is null — badge is hidden rather than showing 'null members'
Widget is a `StatelessWidget` with no internal state — all data comes from constructor parameters

Technical Requirements

frameworks
Flutter
Riverpod
data models
organizations (id, name, logo_url, member_count)
OrgBrandingData (primary_color, font_family)
performance requirements
Widget build must complete in a single frame — no async operations in build()
Logo image must use `cacheWidth`/`cacheHeight` to avoid memory waste from oversized network images
security requirements
Logo URL must be rendered via `Image.network` only — no `dart:html` or platform channel image loading
Org name text must be HTML-escaped if ever rendered in a WebView context (not applicable here but note for future reuse)
ui components
OrgCardWidget (this widget)
AppOrgLogo (sub-widget for logo + placeholder + error fallback)
MemberCountBadge (optional sub-widget or inline Text with styling)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Define `OrgCardWidget` as a pure `StatelessWidget` taking named parameters: `orgId`, `orgName`, `logoUrl`, `memberCount`, `primaryColor`, `isActive`, `onTap`. Do NOT consume Riverpod providers inside the widget — pass pre-resolved values from the parent. This keeps the widget reusable and easy to test. For the highlight border, wrap the card `Container` in a `DecoratedBox` with a `BoxDecoration(border: Border.all(color: primaryColor, width: 2))` conditionally applied.

For the 48dp touch target guarantee, wrap the tappable area in a `GestureDetector` inside a `ConstrainedBox(constraints: BoxConstraints(minWidth: 48, minHeight: 48))`. For the semantic label, wrap the card content in a `Semantics` widget with `label: '$orgName, $memberCount members${isActive ? ', currently selected' : ''}'` and `button: true`. For WCAG contrast, use the design token system's pre-validated color pairs — do not use custom ad-hoc colors.

Logo loading: use `Image.network(url, loadingBuilder: ..., errorBuilder: ...)` pattern with a circular progress indicator during load and a generic building icon on error.

Testing Requirements

Widget tests using `flutter_test` and `WidgetTester`. Required test cases: (1) renders org name and member count correctly with valid data, (2) renders placeholder when logo URL is loading, (3) renders fallback icon when logo fails to load, (4) `isActive: true` shows highlight border with correct color, (5) `isActive: false` shows no border, (6) `onTap` callback fires on card tap, (7) semantic label is correct for screen readers — use `tester.getSemantics(find.byType(OrgCardWidget))` and assert `label` field, (8) `memberCount: null` hides the badge without errors, (9) touch target size is at least 48dp — use `tester.getSize(find.byType(OrgCardWidget))` and assert both dimensions ≥ 48. Run `flutter test --coverage` and ensure this widget achieves >90% line coverage.

Component
Organization Card Widget
ui 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.