Feature Flags
The web-client uses runtime feature flags to control which pages appear in the sidebar navigation. Flags are defined in window.__CONFIG__.features (loaded from public/config.js) and evaluated at render time. Disabled features are hidden from the sidebar but their routes remain accessible via direct URL.
Overview
Feature flags flow through two parallel paths:
- Sidebar filtering —
app-sidebar.tsxreadsconfig.featuresdirectly and filters navigation items. Items with aflagproperty are hidden when that flag isfalse. - React context —
FlagsProviderfromflaggedwraps the app inmain.tsx, consumingconfig.featuresas an object map. Flags become available to any component via theuseFeaturehook, the<Feature>component, or thewithFeatureHOC. This path is currently unused in the codebase but available for component-level gating.
Key Concepts
Flag Definitions
Flags are declared as boolean properties on the FeatureFlags interface in lib/config.ts:
// web-client/src/lib/config.ts
interface FeatureFlags {
chat: boolean
jobs: boolean
playground: boolean
kb_playground: boolean
kitchen_sink: boolean
}
Each flag maps to a page or feature in the sidebar. The current flags and their defaults:
| Flag | Default | Controls |
|---|---|---|
chat | false | Chat page (LLM streaming) |
jobs | false | Jobs page (background job queue) |
playground | true | Playground page (Bedrock inference) |
kb_playground | false | Knowledge Base Playground page |
kitchen_sink | true | Kitchen Sink demo page (Samples section) |
Sidebar-Only Visibility
Feature flags control sidebar navigation visibility only. Routes in routes.ts are always registered regardless of flag state. A user who navigates directly to /chat can access the page even when the chat flag is false. This is intentional — flags are a UI convenience for decluttering the sidebar, not an access control mechanism.
Runtime Configuration
Flags are part of the window.__CONFIG__ object loaded via a <script> tag in index.html before the app bundle executes. This means flag values can change per environment without rebuilding the application:
- Local development —
public/config.jsis committed with defaults (playground: true,kitchen_sink: true, othersfalse). - Production — At deploy time,
config_helper.pyor the configure-web-client MCP tool overwritesdist/config.jswith environment-specific values.
Usage
Flagging a Sidebar Item
To gate a navigation item behind a feature flag, add a flag property to the item definition in app-sidebar.tsx:
// web-client/src/components/app-sidebar.tsx
const data = {
navMain: [
{ title: 'Dashboard', url: '/', icon: IconDashboard },
{ title: 'Chat', url: '/chat', icon: IconMessageCircle, flag: 'chat' as const },
// Items without a flag property are always visible
],
}
The NavMain component filters items before rendering:
// web-client/src/components/app-sidebar.tsx
const visibleItems = items.filter((item) => !item.flag || config.features[item.flag])
Items with no flag property pass the filter unconditionally. Items with a flag property are shown only when config.features[flag] is true.
Adding a New Feature Flag
- Add the boolean property to the
FeatureFlagsinterface inlib/config.ts. - Add the default value to the fallback object in
lib/config.ts(used whenwindow.__CONFIG__is not set). - Add the default value to
public/config.jsandpublic/config.production-example.js. - Add the
flagproperty to the relevant sidebar item inapp-sidebar.tsx.
// Step 1–2: lib/config.ts
interface FeatureFlags {
chat: boolean
jobs: boolean
playground: boolean
kb_playground: boolean
kitchen_sink: boolean
my_feature: boolean // new flag
}
export const config: AppConfig = window.__CONFIG__ ?? {
// ...
features: {
// ...existing flags...
my_feature: false, // new default
},
}
// Step 3: public/config.js
window.__CONFIG__ = {
// ...
features: {
// ...existing flags...
my_feature: false,
},
};
// Step 4: app-sidebar.tsx
{ title: 'My Feature', url: '/my-feature', icon: IconStar, flag: 'my_feature' as const },
Viewing Flag State at Runtime
The Settings page (/settings) displays all current feature flag values in a table. This is useful for verifying which flags are active in a deployed environment.
Extending / Maintaining
Key Files
| File | Purpose |
|---|---|
src/lib/config.ts | FeatureFlags interface and fallback defaults |
src/main.tsx | Wraps the app in FlagsProvider, passing config.features directly |
src/components/app-sidebar.tsx | Filters sidebar items based on config.features |
src/pages/SettingsPage.tsx | Displays flag state in the UI |
public/config.js | Local development defaults |
public/config.production-example.js | Documents all configurable values |
flagged ships its own TypeScript types, so no hand-written type declarations are required. config.features is passed to FlagsProvider as an object map without an adapter.
Two Consumption Paths
The codebase has two mechanisms for reading flags, each suited to a different use case:
-
Direct config access (
config.features.flagName) — Used byapp-sidebar.tsxandSettingsPage.tsx. Simpler and appropriate when the consuming component already importsconfig. -
flaggedAPI viaFlagsProviderinmain.tsx— Available but not currently used.flaggedexposes three ways to read a flag inside a component:import { useFeature, Feature, withFeature } from 'flagged'// Hook — read a flag inside a component bodyfunction ChatButton() {const hasChat = useFeature('chat')return hasChat ? <ChatWidget /> : null}// Render component — declarative gating<Feature name="chat"><ChatWidget /></Feature>// Higher-order component — wrap a component so it renders only when enabledconst Chat = withFeature('chat')(ChatWidget)Use the
flaggedAPI when gating content inside a page that is not itself flag-gated in the sidebar.
Coupling with CloudFormation
Feature flags in the web-client correspond to backend capabilities that may or may not be deployed. For example, the jobs flag should be true only when the queue tier stack is deployed (which provides SQS + DynamoDB jobs table + worker Lambda). The chat flag requires a Bedrock model endpoint. Enabling a flag without the backing infrastructure results in a functional UI that returns API errors.
Relationship to Mock-UX Mode
Feature flags are independent of the MOCK_API data-source switch. A builder developing a new feature typically enables both — the features.* flag to reveal the UI and MOCK_API to serve it from mocks — then flips each separately as the feature matures. See UX-First Mocking for the full workflow.
References
web-client/src/lib/config.ts— flag interface and runtime configweb-client/src/components/app-sidebar.tsx— sidebar flag filteringweb-client/src/main.tsx—FlagsProviderwiring (config.featurespassed directly)web-client/public/config.js— local development defaults- flagged — zero-dependency, TypeScript-native React feature-flag library