critical priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

FeatureGate widget is a ConsumerWidget (Riverpod) with a public API: `FeatureGate({required FeatureFlagKeys flag, required Widget child, Widget? fallback})`
When the flag is enabled, child widget is rendered
When the flag is disabled, fallback widget is rendered if provided; otherwise SizedBox.shrink() is rendered (zero space)
Widget reactively rebuilds when the flag state changes in FeatureFlagProvider without requiring setState or app restart
Widget does NOT rebuild when unrelated flags change — only watches its own flag key
Widget integrates with the existing design token system for any internal styling
No business logic inside the widget — it only reads from FeatureFlagProvider and delegates rendering
Widget is exported from the feature flags barrel file for clean imports across the app
Widget works correctly in all accessibility modes including screen reader (VoiceOver/TalkBack) without injecting extra semantics

Technical Requirements

frameworks
Flutter
Riverpod
data models
FeatureFlagKeys (enum)
performance requirements
Widget only rebuilds when its specific flag key changes — use ref.watch(featureFlagProvider.select(...))
No async operations in build() — synchronous isEnabled call only
security requirements
Widget must not expose any flag configuration data to child widgets — only renders or hides content
Fallback must never inadvertently reveal gated content (e.g., no partial renders)
ui components
FeatureGate (new widget)
SizedBox.shrink (default fallback)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Implement as a `ConsumerWidget`. Use `ref.watch(featureFlagProvider.select((p) => p.isEnabled(flag)))` to subscribe to only the relevant flag — this prevents rebuilds from other flag changes. The entire widget body is a ternary: `return isEnabled ? child : (fallback ??

const SizedBox.shrink());`. Keep the widget stateless and pure. Place the file at `lib/features/feature_flags/widgets/feature_gate.dart`. Export from `lib/features/feature_flags/feature_flags.dart` barrel.

The `FeatureFlagKeys` enum should already exist from the runtime tasks — import it, do not redefine. This widget is a building block used across the entire app, so keep it absolutely minimal with no side effects.

Testing Requirements

Widget tests using flutter_test WidgetTester. Test cases: (1) Flag enabled → child widget is found in widget tree; (2) Flag disabled, no fallback → SizedBox.shrink found, child not found; (3) Flag disabled, fallback provided → fallback found, child not found; (4) Flag changes from disabled to enabled → widget rebuilds and child appears; (5) Golden test for both enabled and disabled states to catch accidental layout changes. Wrap tests in ProviderScope with mock FeatureFlagProvider via overrides.

Component
FeatureGate Widget
ui low
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.