Skip to main content

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:

  1. Sidebar filtering — app-sidebar.tsx reads config.features directly and filters navigation items. Items with a flag property are hidden when that flag is false.
  2. React context — FlagsProvider from flagged wraps the app in main.tsx, consuming config.features as an object map. Flags become available to any component via the useFeature hook, the <Feature> component, or the withFeature HOC. 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:

FlagDefaultControls
chatfalseChat page (LLM streaming)
jobsfalseJobs page (background job queue)
playgroundtruePlayground page (Bedrock inference)
kb_playgroundfalseKnowledge Base Playground page
kitchen_sinktrueKitchen Sink demo page (Samples section)

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.js is committed with defaults (playground: true, kitchen_sink: true, others false).
  • Production — At deploy time, config_helper.py or the configure-web-client MCP tool overwrites dist/config.js with 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​

  1. Add the boolean property to the FeatureFlags interface in lib/config.ts.
  2. Add the default value to the fallback object in lib/config.ts (used when window.__CONFIG__ is not set).
  3. Add the default value to public/config.js and public/config.production-example.js.
  4. Add the flag property to the relevant sidebar item in app-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​

FilePurpose
src/lib/config.tsFeatureFlags interface and fallback defaults
src/main.tsxWraps the app in FlagsProvider, passing config.features directly
src/components/app-sidebar.tsxFilters sidebar items based on config.features
src/pages/SettingsPage.tsxDisplays flag state in the UI
public/config.jsLocal development defaults
public/config.production-example.jsDocuments 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:

  1. Direct config access (config.features.flagName) — Used by app-sidebar.tsx and SettingsPage.tsx. Simpler and appropriate when the consuming component already imports config.

  2. flagged API via FlagsProvider in main.tsx — Available but not currently used. flagged exposes three ways to read a flag inside a component:

    import { useFeature, Feature, withFeature } from 'flagged'

    // Hook — read a flag inside a component body
    function 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 enabled
    const Chat = withFeature('chat')(ChatWidget)

    Use the flagged API 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 config
  • web-client/src/components/app-sidebar.tsx — sidebar flag filtering
  • web-client/src/main.tsx — FlagsProvider wiring (config.features passed directly)
  • web-client/public/config.js — local development defaults
  • flagged — zero-dependency, TypeScript-native React feature-flag library