-----|-------------|------------------|------------------| | DISABLED | Code deployed but inactive | None | Testing phase | | SETTINGS_ONLY | Available in Settings page only | Settings page | User testing | | ENABLED | Available everywhere | Full app | Production |
When to Create a Flag
Create a flag when ALL of these are true:
- • New feature needs gradual rollout
- • Feature is risky and might need quick rollback
- • Feature will be A/B tested
- • Rollout plan exists with success metrics
When to Remove a Flag
Remove a flag when ALL of these are true:
- • Stable in production for 2+ weeks
- • Always enabled (no plan to disable)
- • Not user-configurable preference
- • Not tied to future feature (premium tier, etc.)
Implementation Templates
Step 1: Create the Flag
File: src/core/features.js
export const FEATURE_FLAGS = {
// Existing flags...
NEW_FEATURE: {
phase: 'DISABLED', // Start with DISABLED for new features
description: 'Brief description of what the feature does'
}
};
Description Guidelines:
- •Max 100 characters
- •Clear what/why, not how
- •Include user benefit if applicable
Step 2: Update Flag Usage
import { isFeatureEnabled } from '@/core/features.js';
// In components/views
if (isFeatureEnabled('NEW_FEATURE', 'home')) {
// Show feature on home page
}
// In services
if (isFeatureEnabled('NEW_FEATURE')) {
// Enable backend behavior
}
Context Parameters:
- •
'default'- General usage (most common) - •
'settings'- Settings page only - •
'home'- Home page specific - •
'welcome'- Onboarding flow
Step 3: Add Tests
Test File: src/core/features.test.js
describe('NEW_FEATURE Flag', () => {
it('should return false when DISABLED', () => {
expect(isFeatureEnabled('NEW_FEATURE')).toBe(false);
});
it('should return true when ENABLED', () => {
// Mock flag as ENABLED
vi.stubGlobal('localStorage', {
getItem: () => 'ENABLED'
});
expect(isFeatureEnabled('NEW_FEATURE')).toBe(true);
});
it('should respect SETTINGS_ONLY phase', () => {
// Mock flag as SETTINGS_ONLY
vi.stubGlobal('localStorage', {
getItem: () => 'SETTINGS_ONLY'
});
expect(isFeatureEnabled('NEW_FEATURE', 'settings')).toBe(true);
expect(isFeatureEnabled('NEW_FEATURE', 'home')).toBe(false);
});
});
Step 4: Create Documentation
File: docs/learning/epic10_hygiene/FLAG_NEW_FEATURE.md
# NEW_FEATURE Feature Flag **Status:** DISABLED **Created:** YYYY-MM-DD **Phase:** DISABLED → SETTINGS_ONLY → ENABLED → REMOVED **Remove When:** Feature is stable for 2+ weeks ## Purpose Brief description of why this flag exists. ## Success Metrics - [ ] Feature works as intended - [ ] No performance impact - [ ] User feedback positive - [ ] Telemetry shows usage ## Removal Plan When this flag is removed: - [ ] Remove isFeatureEnabled() checks - [ ] Simplify conditional logic - [ ] Update/remove related tests - [ ] Archive this documentation
Step 5: Update Epic Documentation
File: docs/learning/epic10_hygiene/EPIC10_HYGIENE_PLAN.md
Add to "Future Flag Cleanup" table if applicable:
| Flag | Status | Remove When | Document | |------|--------|-------------|----------| | NEW_FEATURE | DISABLED | 2 weeks after ENABLED | FLAG_NEW_FEATURE.md |
Phase Updates
Moving from DISABLED to SETTINGS_ONLY
- •Update flag phase:
NEW_FEATURE: {
phase: 'SETTINGS_ONLY', // Updated
description: '...'
}
- •Add Settings UI:
// In SettingsView.js
if (isFeatureEnabled('NEW_FEATURE', 'settings')) {
// Add settings toggle for user testing
}
- •
Update tests for SETTINGS_ONLY behavior
- •
Update documentation with test plan
Moving from SETTINGS_ONLY to ENABLED
- •Update flag phase:
NEW_FEATURE: {
phase: 'ENABLED', // Updated
description: '...'
}
- •
Remove Settings-only guard (make available everywhere)
- •
Update tests for ENABLED behavior
- •
Update documentation with success metrics
Flag Removal Process (Hygiene Task)
Follow Epic 10 hygiene standards:
Pre-Removal Checklist
- • Flag has been ENABLED for 2+ weeks
- • No issues reported in telemetry
- • Feature is working as intended
- • No plan to disable this feature
- • Not a user preference setting
Step 1: Create Hygiene Branch
# From main repo directory git worktree add ../saberloop-hygiene hygiene/remove-new-feature-flag cd ../saberloop-hygiene npm install
Step 2: Remove Flag Code
- •Remove from features.js:
export const FEATURE_FLAGS = {
// Remove NEW_FEATURE entry
};
- •Remove isFeatureEnabled() calls:
// Before
if (isFeatureEnabled('NEW_FEATURE')) {
doSomething();
}
// After
doSomething(); // Always execute
- •Simplify conditional logic:
// Before
if (isFeatureEnabled('NEW_FEATURE')) {
return enhancedVersion();
} else {
return basicVersion();
}
// After
return enhancedVersion(); // Always use enhanced
Step 3: Update Tests
- •Remove flag-specific tests from features.test.js
- •Update integration tests to expect new behavior
- •Run full test suite to ensure no regressions
Step 4: Documentation Updates
- •Archive flag documentation: Move to completed folder
- •Update Epic 10 plan with removal date
- •Create learning notes:
docs/learning/epic10_hygiene/PHASE*_LEARNING_NOTES.md
Step 5: Quality Checks
Run complete hygiene checklist:
npm test # Unit tests npm run test:e2e # E2E tests npm run test:mutation # Mutation testing npm run arch:test # Architecture validation npm run typecheck # TypeScript checking
Step 6: Commit and Merge
git add -A git commit -m "refactor(hygiene): remove NEW_FEATURE feature flag - Remove isFeatureEnabled() checks for NEW_FEATURE - Simplify conditional logic in [files] - Remove flag from FEATURE_FLAGS object - Update/remove related tests - Archive flag documentation" git push -u origin hygiene/remove-new-feature-flag # Create PR after review gh pr create --title "chore: remove NEW_FEATURE feature flag"
Step 7: Cleanup
# From main repo git worktree remove ../saberloop-hygiene git branch -d hygiene/remove-new-feature-flag
Common Patterns
Gradual Rollout Pattern
// Phase 1: Settings only (user testing)
if (isFeatureEnabled('NEW_FEATURE', 'settings')) {
showSettingsToggle();
}
// Phase 2: Limited exposure (telemetry gathering)
if (isFeatureEnabled('NEW_FEATURE', 'home')) {
showFeatureOnHomePage();
}
// Phase 3: Full release
if (isFeatureEnabled('NEW_FEATURE')) {
enableFeature();
}
Safe Removal Pattern
// Use feature flag as guard during development
const enhancedFlow = isFeatureEnabled('NEW_FEATURE')
? newEnhancedImplementation()
: existingImplementation();
// After flag removal, the enhanced implementation becomes default
const enhancedFlow = newEnhancedImplementation();
Testing Guidelines
Unit Testing Requirements
Every flag needs these tests:
describe('FEATURE_NAME Flag', () => {
it('should return false when DISABLED', () => {
// Test default behavior
});
it('should return true when ENABLED', () => {
// Test enabled behavior
});
it('should respect SETTINGS_ONLY phase', () => {
// Test settings-only behavior
});
it('should handle test overrides', () => {
// Test localStorage override capability
});
});
Integration Testing
- •Test flag behavior in actual components
- •Verify Settings UI shows/hides correctly
- •Confirm feature works in each phase
- •Check telemetry tracks flag usage
E2E Testing
Add Playwright tests for critical flags:
test('feature flag settings toggle', async ({ page }) => {
await page.goto('/settings');
// Test settings-only flag visibility
const toggle = page.locator('[data-testid="new-feature-toggle"]');
await expect(toggle).toBeVisible();
// Test toggle functionality
await toggle.click();
// Verify behavior changes
});
Troubleshooting
Flag Not Working
- •Check import path:
@/core/features.js - •Verify flag name matches FEATURE_FLAGS object
- •Check context parameter if using SETTINGS_ONLY
- •Test localStorage override for debugging
Test Override Not Working
// For debugging, force a flag state
localStorage.setItem('__test_feature_NEW_FEATURE', 'ENABLED');
// Then test isFeatureEnabled('NEW_FEATURE')
Removal Breaks Something
- •Check for missed isFeatureEnabled() calls
- •Verify conditional logic was properly simplified
- •Run tests with --run flag (not watch mode)
- •Check git diff for unintended changes
Quality Metrics
Track these metrics for flag management:
| Metric | Target | How to Measure |
|---|---|---|
| Flag creation time | < 30 min | From skill start to tests passing |
| Flag removal time | < 2 hours | From hygiene branch to merge |
| Test coverage | 100% | All flag paths tested |
| Documentation completeness | 100% | All fields filled in |
| Zero regressions | Required | All tests pass after removal |
Related Files
- •
src/core/features.js- Main flag definitions - •
src/core/features.test.js- Flag tests - •
docs/learning/epic10_hygiene/EPIC10_HYGIENE_PLAN.md- Epic tracking - •Individual flag documentation files
- •Test files for components using flags
Integration with Other Skills
This skill integrates with:
- •epic-hygiene-process - For removal procedures
- •testing-suite-management - For test creation and validation
- •architecture-compliance - For validating flag usage patterns
Version: 1.0.0
Last Updated: 2026-01-15
Compatible with: Saberloop v2.0.0+