/** * Transport-neutral interaction model for addressable form controls. * * The registry deliberately knows nothing about chat, agents, WebMCP, or the * DOM. Controls register serializable metadata plus small imperative handles; * adapters translate voice/chat/tutorial requests into commands. */ export type ControlKind = 'text' | 'email' | 'password' | 'number' | 'date' | 'time' | 'datetime' | 'textarea' | 'select' | 'listbox' | 'combobox' | 'multi-select' | 'tags-input' | 'checkbox' | 'radio-group' | 'switch' | 'toggle-button' | 'slider' | 'range-slider' | 'segmented-control' | 'file' | 'custom'; export type ControlSensitivity = 'public' | 'personal' | 'sensitive' | 'secret'; export type ControlCapability = 'read' | 'focus' | 'reveal' | 'highlight' | 'explain' | 'validate' | 'stage' | 'apply' | 'discard' | 'clear' | 'undo'; export interface ControlSubject { type: string; id: string; label?: string; } /** Stable address for a control, following AdminShell's Tool Identity pattern. */ export interface ControlIdentity { formId: string; controlId: string; subject?: ControlSubject; } export interface ControlOption { value: string | number; label: string; disabled?: boolean; } export interface ControlConstraints { required?: boolean; min?: number | string; max?: number | string; step?: number | string; minLength?: number; maxLength?: number; pattern?: string; } /** Optional declarative metadata shared by every control primitive. */ export interface ControlInteractionOptions { /** Stable id inside the enclosing form. Defaults to name, then DOM id. */ id?: string; description?: string; sensitivity?: ControlSensitivity; readable?: boolean; writable?: boolean; subject?: ControlSubject; } export interface ControlMetadata { kind: ControlKind; label?: string; description?: string; sensitivity?: ControlSensitivity; readable?: boolean; writable?: boolean; constraints?: ControlConstraints; options?: ControlOption[]; unit?: string; capabilities?: ControlCapability[]; } export interface ControlRuntimeState { disabled?: boolean; readonly?: boolean; valid?: boolean; validationMessage?: string; } export interface ControlStagedProvenance { source: ControlCommandSource; actorId?: string; sessionId?: string; } /** Reviewable metadata for a proposed value. The value is omitted when redacted. */ export interface ControlStagedEntry { value?: unknown; valueRedacted: boolean; provenance: ControlStagedProvenance; stagedAt: number; revision: number; stale: boolean; valid?: boolean; validationMessage?: string; } export interface ControlSnapshot { identity: ControlIdentity; metadata: ControlMetadata; state: ControlRuntimeState & { value?: unknown; valueRedacted: boolean; stagedValue?: unknown; stagedValueRedacted?: boolean; /** Canonical staged-value contract. Legacy stagedValue fields remain additive aliases. */ staged?: ControlStagedEntry; }; } export type ControlValueValidationResult = boolean | string | { valid: boolean; message?: string; }; export interface ControlRegistration { identity: ControlIdentity; metadata: ControlMetadata; getValue?: () => unknown; /** * Snapshot changed only by direct user edits. Async mutation rollback * restores a newer user value when this optional signal changes. */ getUserEditSnapshot?: () => { revision: number; value: unknown; }; setValue?: (value: unknown) => void | Promise; /** Context-aware alternative to setValue; legacy setters keep one argument. */ setValueWithContext?: (value: unknown, context: ControlExtensionContext) => void | Promise; /** * Resolve a staged intent against the current value without mutating it. * May throw when the control cannot represent the intent canonically. */ prepareValue?: (value: unknown) => unknown; /** * Validate/canonicalize a complete value edited in the staged-review UI. * Without this hook, reviewed edits still pass through prepareValue. */ prepareReviewedValue?: (value: unknown) => unknown; /** Restore a value without re-running a fallible async mutation workflow. */ restoreValue?: (value: unknown, context?: ControlExtensionContext) => void | Promise; /** Return true to affirm an accepted idempotent clear; false rejects it. */ clear?: ((context?: ControlExtensionContext) => void | Promise) | ((context?: ControlExtensionContext) => boolean | Promise); focus?: () => void | Promise; reveal?: () => void | Promise; highlight?: (durationMs?: number) => void | Promise; validate?: (context?: ControlExtensionContext) => boolean | Promise; /** Validate a proposal without mutating the bound value. */ validateValue?: (value: unknown, context?: ControlExtensionContext) => ControlValueValidationResult | Promise; getState?: () => ControlRuntimeState; } export type ControlCommandAction = 'focus' | 'reveal' | 'highlight' | 'explain' | 'validate' | 'stage' | 'apply' | 'discard' | 'clear' | 'undo'; export type ControlCommand = { action: 'focus' | 'reveal' | 'explain' | 'validate' | 'undo'; identity: ControlIdentity; } | { action: 'highlight'; identity: ControlIdentity; durationMs?: number; } | { action: 'stage'; identity: ControlIdentity; value: unknown; } | { action: 'apply'; identity: ControlIdentity; value?: unknown; /** * The supplied value is the complete canonical value edited in a local * staged-review surface, rather than a raw proposal for prepareValue. * Honored only for a current staged revision under a validated local * gesture; it grants no confirmation or policy authority by itself. */ reviewedValueIsCanonical?: true; /** Reject the command when this no longer matches the staged proposal. */ revision?: number; } | { action: 'discard'; identity: ControlIdentity; revision?: number; } | { action: 'clear'; identity: ControlIdentity; }; export type ControlCommandSource = 'user' | 'voice' | 'agent' | 'tutorial' | 'test'; export interface ControlCommandContext { source: ControlCommandSource; /** Advisory confirmation for legacy non-review mutations; never proves a local gesture. */ confirmed?: boolean; /** Output-only audit marker set by the registry after validating a local gesture. */ localGesture?: boolean; actorId?: string; sessionId?: string; } export interface ControlPolicyDecision { allowed: boolean; reason?: string; } /** * Execution utilities supplied to registry extension hooks. Same-control * mutations are rejected before queuing so an extension cannot await its own * mutation and deadlock the ordered command queue. */ export interface ControlExtensionContext { execute(command: ControlCommand, context?: ControlCommandContext): Promise; } export type ControlInteractionPolicy = (command: ControlCommand, context: ControlCommandContext, snapshot: ControlSnapshot, extensionContext?: ControlExtensionContext) => ControlPolicyDecision | Promise; export interface ControlCommandResult { ok: boolean; action: ControlCommandAction; identity: ControlIdentity; snapshot?: ControlSnapshot; reason?: string; } export interface ControlBatchResult { ok: boolean; results: ControlCommandResult[]; } export interface ControlInteractionEvent { type: 'registered' | 'unregistered' | 'refreshed' | 'staged' | 'command'; identity: ControlIdentity; command?: ControlCommand; context?: ControlCommandContext; result?: ControlCommandResult; staged?: ControlStagedEntry; timestamp: number; } export interface ControlInteractionRegistry { register(registration: ControlRegistration): () => void; unregister(identity: ControlIdentity): void; /** Record a direct human edit observed by the owning form. */ recordUserEdit?(identity: ControlIdentity): void; list(formId?: string): ControlSnapshot[]; get(identity: ControlIdentity): ControlSnapshot | undefined; /** Notify consumers that live registration metadata or runtime state changed. */ refresh?(formId?: string): void; execute(command: ControlCommand, context?: ControlCommandContext): Promise; /** Executes in order and always returns an explicit result for every command. */ executeBatch?(commands: ControlCommand[], context?: ControlCommandContext): Promise; subscribe(listener: (event: ControlInteractionEvent) => void): () => void; } export interface CreateControlInteractionRegistryOptions { policy?: ControlInteractionPolicy; now?: () => number; /** Host/test trust hook. Active DOM dispatch is always required independently. */ isLocalGesture?: (event: Event) => boolean; /** @deprecated Reentrant mutations are rejected through ControlExtensionContext. */ reentrantMutationTimeoutMs?: number; } /** Execute one value-changing command from a local DOM event handler. */ export declare function executeLocalControlCommand(registry: ControlInteractionRegistry, command: ControlCommand, event: Event): Promise; /** Execute an ordered best-effort batch from one local DOM event handler. */ export declare function executeLocalControlBatch(registry: ControlInteractionRegistry, commands: ControlCommand[], event: Event): Promise; /** Create an isolated registry; apps choose where and how it is exposed. */ export declare function createControlInteractionRegistry(options?: CreateControlInteractionRegistryOptions): ControlInteractionRegistry; //# sourceMappingURL=control-interaction.d.ts.map