/** * Feature Manager * * Central registry for SDK features. Features self-describe via descriptors * that declare their config keys, remote config mappings, and class references. * * Responsibilities: * - Registration: features register descriptors before init * - Instance creation: createInstances() constructs instances during init() (pre-boot) * - Starting: initAll() starts created instances at boot via startIfEnabled * - Config propagation: notifyAll() calls onConfigUpdate on each instance * - Descriptor exposure: getDescriptors() lets RemoteConfigManager._apply iterate * * @see docs/patterns/tracker-feature-lifecycle.md */ import type { VTiltConfig } from "../types"; import type { Feature } from "../feature"; /** * Self-describing metadata for a feature. * Declared once at registration time; used by FeatureManager and RemoteConfigManager. */ export interface FeatureDescriptor { /** Unique name (used as map key and for get()) */ name: string; /** VTiltConfig key that holds this feature's config (e.g. "session_recording") */ configKey?: keyof VTiltConfig; /** Remote config mapping — tells _apply how to extract config from RemoteConfig */ remoteConfig?: { /** Key on the RemoteConfig object (e.g. "sessionRecording") */ key: string; /** Transform remote section into the shape written to VTiltConfig[configKey] */ map: (remote: Record) => Record; }; /** Feature class — must have a static extractConfig and a constructor */ FeatureClass: { new (instance: any, config?: any): Feature; extractConfig(config: VTiltConfig): any; }; } /** * Minimal host interface to avoid circular dependency with VTilt. */ export interface FeatureHost { getConfig(): VTiltConfig; } export declare class FeatureManager { private _host; private _descriptors; private _instances; constructor(host: FeatureHost); /** Register a feature descriptor. Call before initAll(). */ register(desc: FeatureDescriptor): void; /** * Create instances for all registered features without starting them. * Safe to call before DOM boot — constructors are lightweight. * Idempotent: skips features that already have an instance. * * Hard-disabled features are skipped (instance not created) when their * `configKey` section has `enabled: false` (e.g. `chat: { enabled: false }`). */ createInstances(): void; /** * Create and start all registered features. * Calls `createInstances()` first (idempotent), then `startIfEnabled()` on each. */ initAll(): void; /** Notify all initialized features of a config change. */ notifyAll(config: VTiltConfig): void; /** Get a feature instance by name. */ get(name: string): T | undefined; /** Register a late-created feature instance (e.g. from a public start*() call). */ set(name: string, instance: Feature): void; /** Expose descriptors so RemoteConfigManager._apply can iterate for remote config mapping. */ getDescriptors(): Map; /** * True when the integrator explicitly hard-disabled this feature in code * config — its `configKey` section has `enabled: false` * (e.g. `chat: { enabled: false }`). * * When `enabled` is omitted (undefined), the feature may still auto-configure * from dashboard settings, so we must construct the instance. */ private _isHardDisabled; }