Documentation Index
Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
Use this file to discover all available pages before exploring further.
Version: 1.0.0
Status: ✅ Complete
Module: PF (Platform Foundation)
Spec: specs/pf/specs/PF-45-feature-flags-experimentation.md
Overview
PF-45 provides a centralized feature flag system for progressive rollouts, kill-switches, and A/B testing across all cores. Flags are evaluated via:
- React hook —
useFeatureFlag (client-side, TanStack Query–backed)
- RPC —
pf_check_feature_flag (server-side / edge functions)
All flags are tenant-scoped or global. The system fails safe — if evaluation errors, enabled defaults to false.
Consumer Guide: React Hook
Import
import { useFeatureFlag } from '@/platform/feature-flags';
Signature
function useFeatureFlag(flagKey: string): {
enabled: boolean; // Whether the flag is on for the current user/org
variant: string | null; // A/B test variant key, or null for boolean/percentage flags
isLoading: boolean;
error: Error | null;
};
Caching
| Setting | Value |
|---|
staleTime | Derived from feature_flag_cache_ttl_minutes (default: 5 minutes) |
gcTime | 2× staleTime (default: 10 minutes) |
retry | 1 |
| Query key | ['feature-flag', flagKey, userId, orgId] |
| Fail-safe | { enabled: false, variant: null } |
Examples
Boolean flag (kill-switch / feature gate)
import { useFeatureFlag } from '@/platform/feature-flags';
function MyComponent() {
const { enabled } = useFeatureFlag('cl.new_chart_layout');
if (!enabled) return <LegacyChart />;
return <NewChart />;
}
Percentage rollout
const { enabled } = useFeatureFlag('pf.progressive_sidebar');
// `enabled` is true for the configured percentage of users
// Evaluation is deterministic per user — same user always gets the same result
A/B test with variants
const { enabled, variant } = useFeatureFlag('ce.onboarding_experiment');
if (!enabled) return <DefaultOnboarding />;
switch (variant) {
case 'streamlined':
return <StreamlinedOnboarding />;
case 'guided':
return <GuidedOnboarding />;
default:
return <DefaultOnboarding />;
}
Variants are configured as { key: string; weight: number }[] on the flag. The RPC assigns a variant based on weighted random distribution, deterministic per user.
Consumer Guide: RPC (Edge Functions / Server-Side)
Function
pf_check_feature_flag(
p_flag_key TEXT,
p_user_id UUID DEFAULT NULL,
p_organization_id UUID DEFAULT NULL
) RETURNS JSONB
Return shape
{ "enabled": true, "variant": "streamlined" }
or
{ "enabled": false, "variant": null }
Usage from an edge function
import { createClient } from 'npm:@supabase/supabase-js@2';
// Security: Verify request JWT, extract userId/orgId server-side
// Enforce admin/RBAC checks or use row-level security
// Only use service role in trusted backend contexts
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!,
);
const { data, error } = await supabase.rpc('pf_check_feature_flag', {
p_flag_key: 'fa.new_invoice_format',
p_user_id: userId,
p_organization_id: orgId,
});
if (error || !data?.enabled) {
// Fall back to existing behavior
}
Flag Key Convention
All flag keys follow the pattern:
| Core | Example Key | Purpose |
|---|
pf | pf.dark_mode | Platform-wide UI toggle |
cl | cl.new_chart_layout | Clinical module feature |
ce | ce.onboarding_experiment | A/B test in Community Engagement |
fa | fa.new_invoice_format | Finance feature gate |
Keys must be lowercase, underscore-separated, and prefixed with the owning core abbreviation.
Consuming Cores
| Consumer | Flag(s) | Integration Type | Status |
|---|
| CL-35 | Clinical feature gates | React hook | 📝 Planned |
| PF-31 | Platform feature gates | React hook | 📝 Planned |
| PF-52 | Platform feature gates | React hook | 📝 Planned |
Anti-Patterns
❌ Direct table query from another core
// ❌ WRONG: Querying pf_feature_flags directly
const { data } = await supabase
.from('pf_feature_flags')
.select('is_enabled')
.eq('flag_key', 'cl.my_flag');
// ✅ CORRECT: Use the hook or RPC
const { enabled } = useFeatureFlag('cl.my_flag');
❌ Separate caching layer
// ❌ WRONG: Redundant cache — the hook already caches via TanStack Query
const cached = localStorage.getItem('flag_cl.my_flag');
// ✅ CORRECT: Just use the hook; it handles stale/gc times
const { enabled } = useFeatureFlag('cl.my_flag');
❌ Hardcoded flag checks
// ❌ WRONG: Hardcoded boolean instead of flag
const ENABLE_NEW_FEATURE = true;
// ✅ CORRECT: Use a flag so it can be toggled without a deploy
const { enabled } = useFeatureFlag('hr.new_feature');
❌ Flag evaluation in render-critical paths without loading state
// ❌ WRONG: No loading guard — will flash default then switch
return enabled ? <NewUI /> : <OldUI />;
// ✅ CORRECT: Show skeleton while flag is resolving
if (isLoading) return <Skeleton className="h-48 w-full" />;
return enabled ? <NewUI /> : <OldUI />;
- Spec:
specs/pf/specs/PF-45-feature-flags-experimentation.md
- Plan:
specs/pf/plans/PF-45-feature-flags-experimentation-PLAN.md
- Tasks:
specs/pf/tasks/PF-45-TASKS.md
- Platform AGENTS:
src/platform/AGENTS.md § Feature Flags
- Cross-core integrations:
docs/architecture/integrations/CROSS_CORE_INTEGRATIONS.md