/** Portable declarations. Executable readers and source adapters stay outside the catalog. */ export type BindingJsonValue = null | string | number | boolean | readonly BindingJsonValue[] | { readonly [key: string]: BindingJsonValue; }; export interface DataCatalogIdentity { projectId: string; catalogId: string; catalogRevision: string; sourceRevision: string; } export type DataScalarKind = 'string' | 'number' | 'boolean' | 'enum'; export type DataValueType = { kind: 'string' | 'number' | 'boolean'; } | { kind: 'enum'; values: readonly (string | number)[]; } | { kind: 'object'; scopeId: string; } | { kind: 'collection'; itemScopeId: string; itemKeyFieldId: string; }; export interface DataScope { id: string; label: string; kind: 'screen' | 'item'; /** Required for root scopes; item availability follows its declared collection. */ screenIds?: readonly string[]; /** Source DTO identity for browsing; it never becomes an executable expression. */ schema?: { name: string; file?: string; }; } export interface DataField { id: string; scopeId: string; label: string; description?: string; valueType: DataValueType; optional: boolean; nullable: boolean; /** Opaque key interpreted by the host reader/source adapter, never a JS expression. */ sourceRef: string; /** DTO member path shown to the author; the host reader still owns sourceRef. */ path?: string; formatterIds?: readonly string[]; /** Exact captured source identity of an existing repeated container, supplied by the host. */ repeatContainer?: string; } export interface DataSlot { id: string; label: string; /** Absent for intrinsic document slots, such as text content. */ componentName?: string; kind: 'text-content' | 'input-prop' | 'repeated-children' | 'visibility'; valueKinds: readonly (DataScalarKind | 'object' | 'collection')[]; nullable: boolean; optional: boolean; /** Input props remain read-only unless an independently declared action is connected. */ prop?: string; } export interface DataFormatter { id: string; label: string; inputKinds: readonly DataScalarKind[]; outputKind: 'string' | 'number' | 'boolean'; /** An explicit finite vocabulary prevents unvalidated executable options. */ options?: Readonly>; } export interface DataFixtureRef { id: string; label: string; screenId: string; } /** Declaring an event does not implement it or make a field writable. */ export interface DataActionDescriptor { id: string; label: string; screenIds: readonly string[]; /** Host event-slot IDs, in a separate namespace from read/display DataSlot IDs. */ slotIds: readonly string[]; } export interface DataCatalog { version: 1; identity: DataCatalogIdentity; scopes: readonly DataScope[]; fields: readonly DataField[]; slots: readonly DataSlot[]; formatters?: readonly DataFormatter[]; fixtures?: readonly DataFixtureRef[]; actions?: readonly DataActionDescriptor[]; } export type BindingScopeUse = { kind: 'screen'; scopeId: string; } | { kind: 'item'; repeatId: string; }; export type MissingPresentation = { kind: 'empty'; } | { kind: 'hide'; } | { kind: 'text'; text: string; } | { kind: 'error'; }; export type DataCondition = { operator: 'present'; } | { operator: 'equals' | 'not-equals'; value: string | number | boolean; } | { operator: 'greater-than' | 'less-than'; value: number; }; export interface DataBinding { id: string; fieldId: string; scope: BindingScopeUse; target: { nodeId: string; slotId: string; }; formatter?: { id: string; options?: Readonly>; }; missing: MissingPresentation; /** Visibility compares the original field value, before formatting. */ condition?: DataCondition; } export interface RepeatBinding { id: string; templateNodeId: string; collectionFieldId: string; sourceScope: BindingScopeUse; } export interface PendingDataRequest { id: string; target: { nodeId: string; slotId: string; }; description: string; } /** Store this JSON snapshot inside the owning document's single transaction/history. */ export interface DataBindingDocument { version: 1; catalog: DataCatalogIdentity; screenId: string; bindings: readonly DataBinding[]; repeats: readonly RepeatBinding[]; requests?: readonly PendingDataRequest[]; /** Explicit written-content conversions, recorded by the authoring command, never by capture. */ writtenReplacements?: readonly { target: DataBinding['target']; text: string; }[]; } export interface BindingNodeContext { id: string; parentId?: string; /** Intrinsic text content requires an explicit text node; opaque components do not expose it. */ kind?: string; componentName?: string; sourceIdentity?: string; } export interface BindingDiagnostic { severity: 'error'; code: string; message: string; bindingId?: string; repeatId?: string; nodeId?: string; fieldId?: string; } export interface BindingValidationResult { valid: boolean; diagnostics: readonly BindingDiagnostic[]; } /** Runtime-only values. Their identity never becomes a source expression. */ export type BindingValueResult = { kind: 'value'; value: unknown; } | { kind: 'null'; } | { kind: 'absent'; } | { kind: 'unavailable'; message: string; } | { kind: 'error'; message: string; }; export interface BindingRuntimeScope { scopeId: string; value: unknown; /** Stable repeat/key path, scoped under the outer row. */ rowPath: readonly { repeatId: string; key: string | number; }[]; } export interface DataPreviewResolver { readField(field: DataField, scope: BindingRuntimeScope): BindingValueResult; format?(formatter: DataFormatter, value: unknown, options: Readonly>): BindingValueResult; } export interface DataFixtureContext { fixtureId: string; screenId: string; /** Root object values keyed by declared screen scope ID. Never serialized into a binding. */ roots: Readonly>; } /** Browser capabilities; the host loads fixtures and owns all interpretation. */ export interface HostDataCapabilities { catalog: DataCatalog; resolver: DataPreviewResolver; loadFixture(fixtureId: string, options: { signal: AbortSignal; }): Promise; }