high priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

Screen is only reachable by users with role 'org_admin' — any other role is redirected to a no-access screen or back to home
Route guard is implemented at the router level (GoRouter redirect), not only in the widget body
Screen displays a page header with title 'Feature Flags' and the current organization name as subtitle
Flag list section renders all known FeatureFlagKeys with each flag's current enabled/disabled state as a toggle switch
Each flag card shows: flag key (human-readable label), enabled/disabled toggle, rollout phase metadata (if applicable), and last-modified timestamp
Stale-cache indicator banner renders at the top of the screen when the stale-cache signal from FeatureFlagProvider is true — banner includes a 'Refresh' action
Audit log section renders a scrollable list of the last 20 flag change events with actor, flag name, old value, new value, and timestamp
Audit log section shows a loading skeleton while fetching and an empty state when no events exist
Screen is WCAG 2.2 AA compliant: toggle switches have accessible labels, color is not the only indicator of enabled/disabled state, minimum 44x44pt touch targets
Screen uses the existing design token system — no hardcoded colors or font sizes

Technical Requirements

frameworks
Flutter
Riverpod
GoRouter
apis
Supabase REST (read feature_flags table)
Supabase REST (read flag_audit_log table)
data models
FeatureFlag
FeatureFlagKeys (enum)
FlagAuditEvent
RolloutCondition
performance requirements
Flag list renders within 300ms of screen open using cached data from FeatureFlagProvider
Audit log fetched asynchronously — does not block flag list rendering
Screen handles up to 50 flags in the list without jank (use ListView.builder)
security requirements
Route guard must enforce org_admin role at GoRouter level — widget-level checks alone are insufficient
Supabase RLS must restrict feature_flags and flag_audit_log reads to the admin's own organization
Admin screen must not be indexable via deep link by non-admin roles
ui components
PageHeader widget (existing reusable)
StaleCacheIndicatorBanner (new widget)
FlagListTile (new widget — flag key label + toggle + metadata)
AuditLogTile (new widget — actor, flag, change, timestamp)
LoadingSkeleton (existing or new)
EmptyState (existing reusable)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Create the screen at `lib/features/feature_flags/screens/feature_flag_admin_screen.dart`. Implement the GoRouter redirect as: `redirect: (context, state) { final role = ref.read(currentUserRoleProvider); if (role != UserRole.orgAdmin) return '/no-access'; return null; }`. Structure the screen as a `ConsumerWidget` with a `CustomScrollView` containing: (1) `SliverAppBar` or `PageHeader`, (2) stale cache banner as a `SliverToBoxAdapter`, (3) flag list as a `SliverList` with `ListView.builder` semantics, (4) audit log section header + `SliverList`. The flag list reads from `ref.watch(allFeatureFlagsProvider)` (a provider exposing the full list of flags with metadata).

The stale cache banner reads from `ref.watch(featureFlagStaleCacheProvider)`. Audit log is a separate `FutureProvider` fetching from Supabase — does not block the main render. All toggle interactions on this screen dispatch to an admin-specific write provider (defined in a later task), not directly to FeatureFlagProvider. This screen is scaffold only — toggle write actions can be no-ops or stubbed for this task.

Testing Requirements

Widget and integration tests using flutter_test. Test cases: (1) Non-admin role navigates to route → redirected, screen not rendered; (2) Admin role navigates to route → screen renders with flag list; (3) Stale cache signal true → banner visible with Refresh button; (4) Stale cache signal false → banner not visible; (5) All flags displayed with correct enabled/disabled state matching mock provider; (6) Audit log section shows loading skeleton then list after async resolve; (7) Audit log empty state renders when no events; (8) Accessibility test: verify toggle switches have Semantics labels using flutter_test `tester.getSemantics`. Use ProviderScope with mock overrides for FeatureFlagProvider and audit log provider.

Component
Feature Flag Admin Screen
ui medium
Epic Risks (3)
high impact low prob security

The feature flag admin screen allows persisting changes to organization_configs. If the role guard is implemented only client-side (checking role state in Riverpod), a user who manipulates their local role state could toggle flags for their organization without proper server-side authorization, potentially exposing features prematurely.

Mitigation & Contingency

Mitigation: Implement server-side authorization for flag update operations using Supabase RLS UPDATE policies that check the user's role in the memberships table. The client-side guard is UX only; the database enforces the actual restriction.

Contingency: If an unauthorized update is detected, audit the RLS policies and add a Supabase Edge Function as an authorization middleware for flag toggle operations, rejecting requests from non-admin role JWTs.

medium impact medium prob scope

Developers on other feature teams may use FeatureGate incorrectly — for example, wrapping business logic rather than UI, or using it before flag initialization completes — leading to features that are visible but non-functional or cause runtime errors when flags are queried in a loading state.

Mitigation & Contingency

Mitigation: Add assert statements in FeatureGate's build method that throw in debug mode if the provider is still in a loading state. Write developer documentation with a clear usage contract: FeatureGate is UI-only; logic gating must use the provider's isEnabled method directly. Include lint examples in the codebase.

Contingency: If misuse is found in code reviews, add a custom Dart lint rule via custom_lint that flags FeatureGate usage outside of the widget tree, and conduct a codebase audit to find existing violations.

medium impact low prob technical

If the audit log is stored in the same organization_configs table without pagination or archival strategy, high-frequency flag changes during pilot testing could produce an unbounded number of rows, degrading query performance on the admin screen.

Mitigation & Contingency

Mitigation: Store audit log entries in a separate feature_flag_audit_log table with an index on (organization_id, changed_at DESC). Implement cursor-based pagination in the repository and limit the initial load to 50 entries.

Contingency: If table size becomes a performance concern, add a Supabase scheduled function to archive entries older than 90 days to cold storage, and add a database index on changed_at for range queries.