# Feature Architecture: API-Driven Feature Flags ## The Problem Your previous system had two overlapping concepts that conflicted: - **Feature Flags** (`features.config.ts`) - Controls individual features - **App Modes** (`NEXT_PUBLIC_GRIDPILOT_MODE`) - Controls overall platform visibility This created confusion because: 1. Development showed only landing page but feature config said everything was enabled 2. It's unclear which system controls what 3. Teams don't know when to use mode vs feature flag ## The Solution: API-Driven Features ### **Single Source of Truth: API** All feature control now comes from the API `/features` endpoint: ```typescript // API Response (from /features endpoint) { "alpha_features": true, "platform.dashboard": true, "platform.leagues": true, "platform.teams": true, "sponsors.portal": true, // ... all other features } ``` ### **FeatureFlagService - The Bridge** The `FeatureFlagService` is the **only** way to check feature availability: ```typescript // ✅ CORRECT: Use FeatureFlagService const service = await FeatureFlagService.fromAPI(); const isEnabled = service.isEnabled('platform.dashboard'); // ❌ WRONG: No more environment mode checks const mode = process.env.NEXT_PUBLIC_GRIDPILOT_MODE; // REMOVED ``` ### **How It Works** 1. **API Layer**: `features.config.ts` defines features per environment 2. **Service Layer**: `FeatureFlagService` fetches and caches features from API 3. **Client Layer**: Components use `FeatureFlagService` or `ModeGuard` for conditional rendering ## Implementation Rules ### **Rule 1: Always Use FeatureFlagService** ```typescript // ✅ CORRECT: Use FeatureFlagService for all feature checks import { FeatureFlagService } from '@/lib/feature/FeatureFlagService'; async function MyComponent() { const service = await FeatureFlagService.fromAPI(); if (service.isEnabled('platform.dashboard')) { return ; } return ; } ``` ### **Rule 2: Use ModeGuard for Conditional Rendering** ```typescript // ✅ CORRECT: Use ModeGuard for client-side conditional rendering import { ModeGuard } from '@/components/shared/ModeGuard'; function Page() { return ( }> ); } ``` ### **Rule 3: Use Hooks for Client Components** ```typescript // ✅ CORRECT: Use hooks in client components import { useFeature, useFeatures } from '@/components/shared/ModeGuard'; function ClientComponent() { const hasDashboard = useFeature('platform.dashboard'); const features = useFeatures(['platform.dashboard', 'platform.leagues']); if (!hasDashboard) return null; return
Dashboard Content
; } ``` ## Configuration Examples ### **API Configuration (features.config.ts)** ```typescript // apps/api/src/config/features.config.ts export const featuresConfig = { development: { // Alpha features enabled for development alpha_features: 'enabled', platform: { dashboard: 'enabled', leagues: 'enabled', teams: 'enabled', races: 'enabled', }, sponsors: { portal: 'enabled', }, }, production: { // Gradual rollout in production alpha_features: 'disabled', platform: { dashboard: 'enabled', leagues: 'enabled', teams: 'enabled', races: 'enabled', }, sponsors: { portal: 'coming_soon', // Not ready yet }, }, }; ``` ### **No More Environment Variables** ```bash # ❌ REMOVED NEXT_PUBLIC_GRIDPILOT_MODE=alpha # ✅ ONLY NEEDED NEXT_PUBLIC_API_BASE_URL=http://localhost:3001 ``` ## Migration Path ### **Before (Confusing)** ```typescript // apps/website/lib/mode.ts (DELETED) export function getAppMode(): AppMode { const mode = process.env.NEXT_PUBLIC_GRIDPILOT_MODE; // ... complex logic } // apps/website/components/shared/ModeGuard.tsx (UPDATED) // Used to check process.env.NEXT_PUBLIC_GRIDPILOT_MODE // Now uses FeatureFlagService ``` ### **After (Clear)** ```typescript // apps/website/lib/feature/FeatureFlagService.ts export class FeatureFlagService { static async fromAPI(): Promise { // Fetches from /features endpoint // Caches results // Provides simple isEnabled() method } } // apps/website/components/shared/ModeGuard.tsx (UPDATED) // Uses FeatureFlagService for all feature checks // No environment variables needed ``` ## Benefits 1. **Single Source of Truth**: API controls all features 2. **No Confusion**: Clear separation between feature availability and platform scope 3. **Runtime Control**: Features can be changed without redeployment 4. **Type Safety**: Feature names are typed and validated 5. **Easy Testing**: Mock the API response for tests ## Quick Reference | Scenario | Implementation | Result | |----------|---------------|--------| | Local dev | API returns all enabled features | Full platform | | CI/CD tests | API returns all enabled features | Full platform | | Staging | API returns controlled features | Controlled rollout | | Production | API returns production features | Public launch | | Feature flags | API config for granular control | Fine-tuned features | ## Files Changed - ✅ `apps/website/lib/config/env.ts` - Removed `NEXT_PUBLIC_GRIDPILOT_MODE` - ✅ `apps/website/lib/mode.ts` - **DELETED** - ✅ `apps/website/lib/mode.test.ts` - **DELETED** - ✅ `apps/website/components/shared/ModeGuard.tsx` - Updated to use API - ✅ `apps/website/env.d.ts` - Removed mode declaration - ✅ All env example files - Updated to remove mode variable - ✅ `apps/website/components/errors/ErrorAnalyticsDashboard.tsx` - Removed appMode - ✅ `apps/website/components/dev/DebugModeToggle.tsx` - Removed appMode - ✅ `apps/website/lib/infrastructure/ErrorReplay.ts` - Removed appMode ## Verification Run these commands to verify the changes: ```bash # Type check npm run typecheck # Lint npm run lint # Tests npm run test # Build npm run build ``` All should pass without references to `NEXT_PUBLIC_GRIDPILOT_MODE` or the deleted mode files.