Files
gridpilot.gg/docs/FEATURE_ARCHITECTURE.md
2026-01-07 22:05:53 +01:00

229 lines
6.0 KiB
Markdown

# 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 <Dashboard />;
}
return <LandingPage />;
}
```
### **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 (
<ModeGuard feature="platform.dashboard" fallback={<LandingPage />}>
<Dashboard />
</ModeGuard>
);
}
```
### **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 <div>Dashboard Content</div>;
}
```
## 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<FeatureFlagService> {
// 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.