--- name: add-feature-flag description: Add a new boolean feature flag (dynamic config key) to cadence-web. Use when asked to add, create, or set up a feature flag, config flag, or `*_ENABLED` toggle. --- # Add a feature flag Feature flags are dynamic config entries whose resolver returns `boolean`. Completeness is enforced by TypeScript — the fixture and the Zod schema map are typed against `dynamicConfigs`, so a missing entry fails `npm run typecheck`. Reference example: `WORKFLOW_DIAGNOSTICS_ENABLED` (added in the same 4 files below). ## Naming - Key: `UPPER_SNAKE_CASE`, ending in `_ENABLED` — e.g. `WORKFLOW_DIAGNOSTICS_ENABLED` - Env var: `CADENCE_` + key — e.g. `CADENCE_WORKFLOW_DIAGNOSTICS_ENABLED` - Resolver file: kebab-case of the key — e.g. `workflow-diagnostics-enabled.ts` - Resolver function: camelCase of the key — e.g. `workflowDiagnosticsEnabled` ## Files to touch Add the new entry directly after a related existing flag in each file (not alphabetically). ### 1. Resolver — `src/config/dynamic/resolvers/.ts` ```ts /** * Returns whether is enabled. * * To enable, set the CADENCE_ env variable to `true`. * For further customization, override the implementation of this resolver. * * @returns {Promise} Whether is enabled. */ export default async function (): Promise { return process.env.CADENCE_ === 'true'; } ``` - Default export, `async`, returns `Promise`. - Off by default; forks override the resolver. - If the flag depends on another flag, import and call that resolver rather than re-reading its env var (see `workflow-diagnostics-in-history-enabled.ts`). Check the cheap env var first. - If the flag needs args (e.g. `{ cluster, domain }`), add `.types.ts` with a `ResolverParams` type (see `cron-list-enabled.types.ts`). ### 2. Register — `src/config/dynamic/dynamic.config.ts` Three edits: ```ts // import (alphabetical by file path) import from './resolvers/'; // type block : ConfigAsyncResolverDefinition; // runtime block : { resolver: , evaluateOn: 'request', isPublic: true, }, ``` Replace `undefined` with the args type if the resolver takes args. `isPublic: true` is what exposes the key to the client via `/api/config`. ### 3. Zod schema — `src/config/dynamic/resolvers/schemas/resolver-schemas.ts` ```ts : { args: z.undefined(), // or z.object({ cluster: z.string(), domain: z.string() }) returnType: z.boolean(), }, ``` ### 4. Test fixture — `src/utils/config/__fixtures__/resolved-config-values.ts` ```ts : false, ``` The `getConfigValue` auto-mock reads from this, so server-side tests see the new key automatically. ### 5. Resolver test (only if the resolver has logic) Skip for a plain env-var check. If the resolver combines conditions, calls another resolver, or takes args, add `src/config/dynamic/resolvers/__tests__/.node.ts`. Mirror `batch-actions-ui-enabled.node.ts`: `jest.mock` the imported resolvers, save/restore the env var in `beforeEach`/`afterEach`. ## Consuming the flag Client: ```ts const { data: isEnabled } = useSuspenseConfigValue(''); // or non-suspense: useConfigValue('', args?) ``` Server (route handlers): `await getConfigValue('')`. Client tests mock the endpoint: ```ts render(, { endpointsMocks: [ { path: '/api/config', httpMethod: 'GET', mockOnce: false, httpResolver: async () => HttpResponse.json(true), }, ], }); ``` ## When the feature ships Once the UI lands: - Add a row to the **Feature flags** table in `README.md` - Add a commented `# CADENCE_=true` under `### Feature flags` in `.env` ## Verify ```bash npm run typecheck # proves fixture + schema are complete npm run test:unit:node .node.ts # if you added a resolver test npm run test:unit:node -- src/route-handlers/get-config src/utils/config src/config/dynamic npm run lint ``` Optional live check: `npm run dev`, then `curl 'http://localhost:8088/api/config?configKey='` → `false`; rerun with `CADENCE_=true npm run dev` → `true`.