critical priority medium complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

FeatureFlagProvider exposes a public synchronous `bool isEnabled(FeatureFlagKeys flag)` method
Method performs O(1) HashMap lookup against the resolved in-memory flag map — no async calls, no Future returns
Rollout evaluator phase conditions (e.g., organization tier, user segment, rollout percentage) are applied before returning the boolean
Method returns false (safe default) for any flag key not present in the resolved map
Method returns false when the provider has not yet been initialized (guard against cold-start race)
Calling isEnabled from within a widget build() method does not trigger unnecessary rebuilds on its own
The resolved map used by isEnabled is the same map populated by the initializer task (task-004) — no duplication of state
Method handles null/unset rollout metadata gracefully, defaulting to the flag's base enabled value
All method calls complete in under 1ms as verified by a Stopwatch assertion in tests

Technical Requirements

frameworks
Flutter
Riverpod
data models
FeatureFlag
RolloutCondition
FeatureFlagKeys (enum)
performance requirements
O(1) lookup complexity — use Map<FeatureFlagKeys, bool> or Map<String, ResolvedFlag>
Zero async overhead — method must be synchronous (no await, no Future)
Method execution under 1ms on device
security requirements
Do not expose raw flag configuration objects through this API — return only bool
Rollout conditions must be evaluated server-side or against local immutable data — not user-modifiable

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

The resolved map should be a final field set during initialization (task-004). Use a `Map` where `ResolvedFlagState` encapsulates both the base `enabled` bool and the `rolloutPhase` metadata. The `isEnabled` method then does: `final state = _resolvedMap[flag]; if (state == null) return false; return state.enabled && _rolloutEvaluator.isPhaseActive(state.rolloutPhase);`. Keep `_rolloutEvaluator` stateless and synchronous — it should only inspect local data (current DateTime, organization ID from session) with no network calls.

In Riverpod, expose the provider as a `Provider` (not `FutureProvider`) so widgets can call `.read(featureFlagProvider).isEnabled(flag)` synchronously. The initialization guard (returning false before init) prevents widgets from showing gated content during the async startup window.

Testing Requirements

Unit tests using flutter_test. Test cases: (1) isEnabled returns true when flag is present and enabled in map; (2) isEnabled returns false when flag is present but disabled; (3) isEnabled returns false for unknown flag key; (4) isEnabled returns false before initialization completes; (5) rollout phase condition 'phase not started' returns false even when flag is enabled; (6) rollout phase condition 'phase active' returns true when flag is enabled; (7) Stopwatch assertion that method completes under 1ms for 1000 consecutive calls. Use mockito/mocktail to mock the underlying resolved flag map and rollout evaluator.

Component
Feature Flag Provider
infrastructure low
Epic Risks (2)
high impact medium prob technical

The feature-flag-initializer must complete before any screen that checks feature flags renders. If the navigation shell is pushed concurrently with initialization (e.g., via a parallel Riverpod provider chain), some screens may query flags before they are loaded and incorrectly receive all-disabled defaults.

Mitigation & Contingency

Mitigation: Gate the main navigation shell render behind the feature-flag-initializer's Future by using a splash/loading screen that awaits the initialization provider. Use Riverpod's ref.watch on an initialization state enum (loading/ready/error) to block rendering.

Contingency: If race conditions are observed in testing, introduce an explicit initialization barrier using a ChangeNotifier or a dedicated `featureFlagsReadyProvider` that the router guard checks before allowing navigation.

medium impact low prob technical

If feature-flag-provider is watched by many widgets simultaneously and a flag map refresh triggers all of them to rebuild at once (e.g., after an organization switch), the app could experience a significant UI jank or dropped frames.

Mitigation & Contingency

Mitigation: Use select() on the provider to have each widget watch only the specific flag key it needs rather than the entire map. Ensure the provider uses equality checks so rebuilds only propagate when the specific flag value changes.

Contingency: If rebuild storms are measured via Flutter DevTools, refactor to a family provider keyed by flag key, so each widget subscribes only to its own flag's changes.