Per-screen Cubits (Flutter/bloc; proposal, 00-shared/06) plus the module-wide
FeatureFlagsCubit — the global gating state every other module consumes
(00-shared/06 §4: "FeatureFlagsCubit gates UI per tenant (biometric, SMS, WhatsApp
channels)"; also referenced at auth/13 §9: "FeatureFlagsCubit not used (auth module runs
pre-feature-gate)"). Backed by FeatureFlagsRepository (dio) hitting the endpoints in
12_API_Mapping.md.
Data source:GET /feature-flags/enabled (feature-flags.controller.ts:30-34,
feature-flag.repository.ts:28-30) → keys of enabled:true docs. The admin surfaces also
fetch full docs (GET /feature-flags, :key) — the cubit keeps a light FlagMeta mirror
(key, enabled, module, label) so gated widgets can show labels without an extra fetch.
Synchronous gate API:bool isEnabled(String key) → enabledKeys.contains(key) —
fail-closed: missing key = disabled, mirroring server flag?.enabled ?? false
(feature-flags.service.ts:24). No async at widget build time.
TTL (proposed, mirrors blueprint): refresh every 30 s ((proposed) client value
aligned with CACHE_ARCHITECTURE.md:48 30 s poll; server cache is (planned)) while app
foregrounded; also refresh on app.resume, on connectivity restore, and on
RefreshIndicator (bypasses TTL). When offline: keep last-good set + AppOfflineBanner
(reads for gating continue from memory; writes blocked).
Realtime: no WS topic for flags exists (00-shared/07 §8); (planned) topic
featureflag.changed (user room) once a domain event exists (OQ-7) — until then TTL
polling is the propagation mechanism. On any admin toggle (list/detail), the cubit
optimistically updates its own set on 200 and schedules an immediate refresh.
Registry (proposed): static map key → List<ScreenRef> in the app (used by
AppAffectedMapCard, 07/05 §5). Used only for the affected-features map; gating itself
needs no registry — any widget calls isEnabled(key).
FeatureFlagsCubit: in-memory enabled set (single source for gating); no persistence
— on cold boot it refetches before first gate decisions; until loaded, UI shows skeletons
in gated areas (never a wrong "disabled" flash — gate states are loading|loaded).
List: last-good per filter; detail: none; editor/bulk: none.
TTL 30 s (proposed); RefreshIndicator and admin toggle success force immediate refresh.
No realtime today.(planned): WS topic featureflag.changed to user rooms →
FeatureFlagsCubit applies the new state immediately (subscribes via repository
subscribe(channel) per 00-shared/06 §3.4). Until then, worst-case propagation =
client TTL (30 s) + server cache TTL (30 s (planned)CACHE_ARCHITECTURE.md:48).
AuthCubit: on login/tenant switch → FeatureFlagsCubit reset + load for the new tenant;
on sessionExpired → clear set.
Other modules: biometric/notifications/payments screens call flagsCubit.isEnabled(key)
to hide/show features — no consumer exists in the backend today (OQ-1); the cubit is
the client-side counterpart of the server's isEnabled() primitive
(feature-flags.service.ts:22-25) and of the roadmap's useFeatureFlags() hook
(IMPLEMENTATION_PLAN.md:810-817, (planned)).
Permission changes (00-shared/06 §3.6) rebuild admin routes; flag state is orthogonal.