🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@json-render/core

Package Overview
Dependencies
Maintainers
3
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@json-render/core - npm Package Compare versions

Comparing version
0.4.4
to
0.5.0
+366
-166
dist/index.d.mts
import { z } from 'zod';
/**
* Dynamic value - can be a literal or a path reference to data model
* Confirmation dialog configuration
*/
interface ActionConfirm {
title: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
variant?: "default" | "danger";
}
/**
* Action success handler
*/
type ActionOnSuccess = {
navigate: string;
} | {
set: Record<string, unknown>;
} | {
action: string;
};
/**
* Action error handler
*/
type ActionOnError = {
set: Record<string, unknown>;
} | {
action: string;
};
/**
* Action binding — maps an event to an action invocation.
*
* Used inside the `on` field of a UIElement:
* ```json
* { "on": { "press": { "action": "setState", "params": { "path": "/x", "value": 1 } } } }
* ```
*/
interface ActionBinding {
/** Action name (must be in catalog) */
action: string;
/** Parameters to pass to the action handler */
params?: Record<string, DynamicValue>;
/** Confirmation dialog before execution */
confirm?: ActionConfirm;
/** Handler after successful execution */
onSuccess?: ActionOnSuccess;
/** Handler after failed execution */
onError?: ActionOnError;
}
/**
* @deprecated Use ActionBinding instead
*/
type Action = ActionBinding;
/**
* Schema for action confirmation
*/
declare const ActionConfirmSchema: z.ZodObject<{
title: z.ZodString;
message: z.ZodString;
confirmLabel: z.ZodOptional<z.ZodString>;
cancelLabel: z.ZodOptional<z.ZodString>;
variant: z.ZodOptional<z.ZodEnum<{
default: "default";
danger: "danger";
}>>;
}, z.core.$strip>;
/**
* Schema for success handlers
*/
declare const ActionOnSuccessSchema: z.ZodUnion<readonly [z.ZodObject<{
navigate: z.ZodString;
}, z.core.$strip>, z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>;
/**
* Schema for error handlers
*/
declare const ActionOnErrorSchema: z.ZodUnion<readonly [z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>;
/**
* Full action binding schema
*/
declare const ActionBindingSchema: z.ZodObject<{
action: z.ZodString;
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodObject<{
path: z.ZodString;
}, z.core.$strip>]>>>;
confirm: z.ZodOptional<z.ZodObject<{
title: z.ZodString;
message: z.ZodString;
confirmLabel: z.ZodOptional<z.ZodString>;
cancelLabel: z.ZodOptional<z.ZodString>;
variant: z.ZodOptional<z.ZodEnum<{
default: "default";
danger: "danger";
}>>;
}, z.core.$strip>>;
onSuccess: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
navigate: z.ZodString;
}, z.core.$strip>, z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>>;
onError: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>>;
}, z.core.$strip>;
/**
* @deprecated Use ActionBindingSchema instead
*/
declare const ActionSchema: z.ZodObject<{
action: z.ZodString;
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodObject<{
path: z.ZodString;
}, z.core.$strip>]>>>;
confirm: z.ZodOptional<z.ZodObject<{
title: z.ZodString;
message: z.ZodString;
confirmLabel: z.ZodOptional<z.ZodString>;
cancelLabel: z.ZodOptional<z.ZodString>;
variant: z.ZodOptional<z.ZodEnum<{
default: "default";
danger: "danger";
}>>;
}, z.core.$strip>>;
onSuccess: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
navigate: z.ZodString;
}, z.core.$strip>, z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>>;
onError: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>>;
}, z.core.$strip>;
/**
* Action handler function signature
*/
type ActionHandler<TParams = Record<string, unknown>, TResult = unknown> = (params: TParams) => Promise<TResult> | TResult;
/**
* Action definition in catalog
*/
interface ActionDefinition<TParams = Record<string, unknown>> {
/** Zod schema for params validation */
params?: z.ZodType<TParams>;
/** Description for AI */
description?: string;
}
/**
* Resolved action with all dynamic values resolved
*/
interface ResolvedAction {
action: string;
params: Record<string, unknown>;
confirm?: ActionConfirm;
onSuccess?: ActionOnSuccess;
onError?: ActionOnError;
}
/**
* Resolve all dynamic values in an action binding
*/
declare function resolveAction(binding: ActionBinding, stateModel: StateModel): ResolvedAction;
/**
* Interpolate ${path} expressions in a string
*/
declare function interpolateString(template: string, stateModel: StateModel): string;
/**
* Context for action execution
*/
interface ActionExecutionContext {
/** The resolved action */
action: ResolvedAction;
/** The action handler from the host */
handler: ActionHandler;
/** Function to update state model */
setState: (path: string, value: unknown) => void;
/** Function to navigate */
navigate?: (path: string) => void;
/** Function to execute another action */
executeAction?: (name: string) => Promise<void>;
}
/**
* Execute an action with all callbacks
*/
declare function executeAction(ctx: ActionExecutionContext): Promise<void>;
/**
* Helper to create action bindings
*/
declare const actionBinding: {
/** Create a simple action binding */
simple: (actionName: string, params?: Record<string, DynamicValue>) => ActionBinding;
/** Create an action binding with confirmation */
withConfirm: (actionName: string, confirm: ActionConfirm, params?: Record<string, DynamicValue>) => ActionBinding;
/** Create an action binding with success handler */
withSuccess: (actionName: string, onSuccess: ActionOnSuccess, params?: Record<string, DynamicValue>) => ActionBinding;
};
/**
* @deprecated Use actionBinding instead
*/
declare const action: {
/** Create a simple action binding */
simple: (actionName: string, params?: Record<string, DynamicValue>) => ActionBinding;
/** Create an action binding with confirmation */
withConfirm: (actionName: string, confirm: ActionConfirm, params?: Record<string, DynamicValue>) => ActionBinding;
/** Create an action binding with success handler */
withSuccess: (actionName: string, onSuccess: ActionOnSuccess, params?: Record<string, DynamicValue>) => ActionBinding;
};
/**
* Dynamic value - can be a literal or a path reference to state model
*/
type DynamicValue<T = unknown> = T | {

@@ -48,2 +266,9 @@ path: string;

visible?: VisibilityCondition;
/** Event bindings — maps event names to action bindings */
on?: Record<string, ActionBinding | ActionBinding[]>;
/** Repeat children once per item in a state array */
repeat?: {
path: string;
key?: string;
};
}

@@ -101,2 +326,5 @@ /**

elements: Record<string, UIElement>;
/** Optional initial state to seed the state model.
* Components using statePath will read from / write to this state. */
state?: Record<string, unknown>;
}

@@ -111,5 +339,5 @@ /**

/**
* Data model type
* State model type
*/
type DataModel = Record<string, unknown>;
type StateModel = Record<string, unknown>;
/**

@@ -139,5 +367,5 @@ * Component schema definition using Zod

/**
* Resolve a dynamic value against a data model
* Resolve a dynamic value against a state model
*/
declare function resolveDynamicValue<T>(value: DynamicValue<T>, dataModel: DataModel): T | undefined;
declare function resolveDynamicValue<T>(value: DynamicValue<T>, stateModel: StateModel): T | undefined;
/**

@@ -278,3 +506,3 @@ * Get a value from an object by JSON Pointer path (RFC 6901)

interface VisibilityContext {
dataModel: DataModel;
stateModel: StateModel;
authState?: AuthState;

@@ -329,167 +557,25 @@ }

/**
* Confirmation dialog configuration
* A prop expression that resolves to a value based on state.
*
* - `{ $path: string }` reads a value from the state model
* - `{ $cond, $then, $else }` conditionally picks a value
* - Any other value is a literal (passthrough)
*/
interface ActionConfirm {
title: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
variant?: "default" | "danger";
}
/**
* Action success handler
*/
type ActionOnSuccess = {
navigate: string;
type PropExpression<T = unknown> = T | {
$path: string;
} | {
set: Record<string, unknown>;
} | {
action: string;
$cond: VisibilityCondition;
$then: PropExpression<T>;
$else: PropExpression<T>;
};
/**
* Action error handler
* Resolve a single prop value that may contain expressions.
* Recursively resolves $path and $cond/$then/$else expressions.
*/
type ActionOnError = {
set: Record<string, unknown>;
} | {
action: string;
};
declare function resolvePropValue(value: unknown, ctx: VisibilityContext): unknown;
/**
* Rich action definition
* Resolve all prop values in an element's props object.
* Returns a new props object with all expressions resolved.
*/
interface Action {
/** Action name (must be in catalog) */
name: string;
/** Parameters to pass to the action handler */
params?: Record<string, DynamicValue>;
/** Confirmation dialog before execution */
confirm?: ActionConfirm;
/** Handler after successful execution */
onSuccess?: ActionOnSuccess;
/** Handler after failed execution */
onError?: ActionOnError;
}
/**
* Schema for action confirmation
*/
declare const ActionConfirmSchema: z.ZodObject<{
title: z.ZodString;
message: z.ZodString;
confirmLabel: z.ZodOptional<z.ZodString>;
cancelLabel: z.ZodOptional<z.ZodString>;
variant: z.ZodOptional<z.ZodEnum<{
default: "default";
danger: "danger";
}>>;
}, z.core.$strip>;
/**
* Schema for success handlers
*/
declare const ActionOnSuccessSchema: z.ZodUnion<readonly [z.ZodObject<{
navigate: z.ZodString;
}, z.core.$strip>, z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>;
/**
* Schema for error handlers
*/
declare const ActionOnErrorSchema: z.ZodUnion<readonly [z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>;
/**
* Full action schema
*/
declare const ActionSchema: z.ZodObject<{
name: z.ZodString;
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodObject<{
path: z.ZodString;
}, z.core.$strip>]>>>;
confirm: z.ZodOptional<z.ZodObject<{
title: z.ZodString;
message: z.ZodString;
confirmLabel: z.ZodOptional<z.ZodString>;
cancelLabel: z.ZodOptional<z.ZodString>;
variant: z.ZodOptional<z.ZodEnum<{
default: "default";
danger: "danger";
}>>;
}, z.core.$strip>>;
onSuccess: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
navigate: z.ZodString;
}, z.core.$strip>, z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>>;
onError: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>>;
}, z.core.$strip>;
/**
* Action handler function signature
*/
type ActionHandler<TParams = Record<string, unknown>, TResult = unknown> = (params: TParams) => Promise<TResult> | TResult;
/**
* Action definition in catalog
*/
interface ActionDefinition<TParams = Record<string, unknown>> {
/** Zod schema for params validation */
params?: z.ZodType<TParams>;
/** Description for AI */
description?: string;
}
/**
* Resolved action with all dynamic values resolved
*/
interface ResolvedAction {
name: string;
params: Record<string, unknown>;
confirm?: ActionConfirm;
onSuccess?: ActionOnSuccess;
onError?: ActionOnError;
}
/**
* Resolve all dynamic values in an action
*/
declare function resolveAction(action: Action, dataModel: DataModel): ResolvedAction;
/**
* Interpolate ${path} expressions in a string
*/
declare function interpolateString(template: string, dataModel: DataModel): string;
/**
* Context for action execution
*/
interface ActionExecutionContext {
/** The resolved action */
action: ResolvedAction;
/** The action handler from the host */
handler: ActionHandler;
/** Function to update data model */
setData: (path: string, value: unknown) => void;
/** Function to navigate */
navigate?: (path: string) => void;
/** Function to execute another action */
executeAction?: (name: string) => Promise<void>;
}
/**
* Execute an action with all callbacks
*/
declare function executeAction(ctx: ActionExecutionContext): Promise<void>;
/**
* Helper to create actions
*/
declare const action: {
/** Create a simple action */
simple: (name: string, params?: Record<string, DynamicValue>) => Action;
/** Create an action with confirmation */
withConfirm: (name: string, confirm: ActionConfirm, params?: Record<string, DynamicValue>) => Action;
/** Create an action with success handler */
withSuccess: (name: string, onSuccess: ActionOnSuccess, params?: Record<string, DynamicValue>) => Action;
};
declare function resolveElementProps(props: Record<string, unknown>, ctx: VisibilityContext): Record<string, unknown>;

@@ -586,3 +672,3 @@ /**

/** Full data model for resolving paths */
dataModel: DataModel;
stateModel: StateModel;
/** Custom validation functions from catalog */

@@ -619,2 +705,77 @@ customFunctions?: Record<string, ValidationFunction>;

/**
* Severity level for validation issues.
*/
type SpecIssueSeverity = "error" | "warning";
/**
* A single validation issue found in a spec.
*/
interface SpecIssue {
/** Severity: errors should be fixed, warnings are informational */
severity: SpecIssueSeverity;
/** Human-readable description of the issue */
message: string;
/** The element key where the issue was found (if applicable) */
elementKey?: string;
/** Machine-readable issue code for programmatic handling */
code: "missing_root" | "root_not_found" | "missing_child" | "visible_in_props" | "orphaned_element" | "empty_spec" | "on_in_props" | "repeat_in_props";
}
/**
* Result of spec structural validation.
*/
interface SpecValidationIssues {
/** Whether the spec passed validation (no errors; warnings are OK) */
valid: boolean;
/** List of issues found */
issues: SpecIssue[];
}
/**
* Options for validateSpec.
*/
interface ValidateSpecOptions {
/**
* Whether to check for orphaned elements (elements not reachable from root).
* Defaults to false since orphans are harmless (just unused).
*/
checkOrphans?: boolean;
}
/**
* Validate a spec for structural integrity.
*
* Checks for common AI-generation errors:
* - Missing or empty root
* - Root element not found in elements map
* - Children referencing non-existent elements
* - `visible` placed inside `props` instead of on the element
* - Orphaned elements (optional)
*
* @example
* ```ts
* const result = validateSpec(spec);
* if (!result.valid) {
* console.log("Spec errors:", result.issues);
* }
* ```
*/
declare function validateSpec(spec: Spec, options?: ValidateSpecOptions): SpecValidationIssues;
/**
* Auto-fix common spec issues in-place and return a corrected copy.
*
* Currently fixes:
* - `visible` inside `props` → moved to element level
* - `on` inside `props` → moved to element level
* - `repeat` inside `props` → moved to element level
*
* Returns the fixed spec and a list of fixes applied.
*/
declare function autoFixSpec(spec: Spec): {
spec: Spec;
fixes: string[];
};
/**
* Format validation issues into a human-readable string suitable for
* inclusion in a repair prompt sent back to the AI.
*/
declare function formatSpecIssues(issues: SpecIssue[]): string;
/**
* Schema builder primitives

@@ -675,2 +836,4 @@ */

readonly promptTemplate?: PromptTemplate;
/** Default rules baked into the schema (injected before customRules) */
readonly defaultRules?: string[];
/** Create a catalog from this schema */

@@ -736,2 +899,4 @@ createCatalog<TCatalog extends InferCatalogInput<TDef["catalog"]>>(catalog: TCatalog): Catalog$1<TDef, TCatalog>;

promptTemplate?: PromptTemplate<TCatalog>;
/** Default rules baked into the schema (injected before customRules in prompts) */
defaultRules?: string[];
}

@@ -816,2 +981,37 @@ /**

/**
* Options for building a user prompt.
*/
interface UserPromptOptions {
/** The user's text prompt */
prompt: string;
/** Existing spec to refine (triggers patch-only mode) */
currentSpec?: Spec | null;
/** Runtime state context to include */
state?: Record<string, unknown> | null;
/** Maximum length for the user's text prompt (applied before wrapping) */
maxPromptLength?: number;
}
/**
* Build a user prompt for AI generation.
*
* Handles common patterns that every consuming app needs:
* - Truncating the user's prompt to a max length
* - Including the current spec for refinement (patch-only mode)
* - Including runtime state context
*
* @example
* ```ts
* // Fresh generation
* buildUserPrompt({ prompt: "create a todo app" })
*
* // Refinement with existing spec
* buildUserPrompt({ prompt: "add a dark mode toggle", currentSpec: spec })
*
* // With state context
* buildUserPrompt({ prompt: "show my data", state: { todos: [] } })
* ```
*/
declare function buildUserPrompt(options: UserPromptOptions): string;
/**
* Component definition with visibility and validation support

@@ -914,2 +1114,2 @@ */

export { type Action, type ActionConfirm, ActionConfirmSchema, type ActionDefinition, type ActionExecutionContext, type ActionHandler, type ActionOnError, ActionOnErrorSchema, type ActionOnSuccess, ActionOnSuccessSchema, ActionSchema, type AuthState, type Catalog$1 as Catalog, type CatalogConfig, type ComponentDefinition, type ComponentSchema, type DataModel, type DynamicBoolean, DynamicBooleanSchema, type DynamicNumber, DynamicNumberSchema, type DynamicString, DynamicStringSchema, type DynamicValue, DynamicValueSchema, type FlatElement, type InferActionParams, type InferCatalogActions, type InferCatalogComponentProps, type InferCatalogComponents, type InferCatalogInput, type InferComponentProps, type InferSpec, type JsonPatch, type Catalog as LegacyCatalog, type LogicExpression, LogicExpressionSchema, type PatchOp, type PromptContext, type PromptOptions, type PromptTemplate, type ResolvedAction, type Schema, type SchemaBuilder, type SchemaDefinition, type SchemaOptions, type SchemaType, type Spec, type SpecStreamCompiler, type SpecStreamLine, type SpecValidationResult, type SystemPromptOptions, type UIElement, type ValidationCheck, type ValidationCheckResult, ValidationCheckSchema, type ValidationConfig, ValidationConfigSchema, type ValidationContext, type ValidationFunction, type ValidationFunctionDefinition, type ValidationMode, type ValidationResult, type VisibilityCondition, VisibilityConditionSchema, type VisibilityContext, action, addByPath, applySpecStreamPatch, builtInValidationFunctions, check, compileSpecStream, createCatalog, createSpecStreamCompiler, defineCatalog, defineSchema, evaluateLogicExpression, evaluateVisibility, executeAction, findFormValue, generateCatalogPrompt, generateSystemPrompt, getByPath, interpolateString, parseSpecStreamLine, removeByPath, resolveAction, resolveDynamicValue, runValidation, runValidationCheck, setByPath, visibility };
export { type Action, type ActionBinding, ActionBindingSchema, type ActionConfirm, ActionConfirmSchema, type ActionDefinition, type ActionExecutionContext, type ActionHandler, type ActionOnError, ActionOnErrorSchema, type ActionOnSuccess, ActionOnSuccessSchema, ActionSchema, type AuthState, type Catalog$1 as Catalog, type CatalogConfig, type ComponentDefinition, type ComponentSchema, type DynamicBoolean, DynamicBooleanSchema, type DynamicNumber, DynamicNumberSchema, type DynamicString, DynamicStringSchema, type DynamicValue, DynamicValueSchema, type FlatElement, type InferActionParams, type InferCatalogActions, type InferCatalogComponentProps, type InferCatalogComponents, type InferCatalogInput, type InferComponentProps, type InferSpec, type JsonPatch, type Catalog as LegacyCatalog, type LogicExpression, LogicExpressionSchema, type PatchOp, type PromptContext, type PromptOptions, type PromptTemplate, type PropExpression, type ResolvedAction, type Schema, type SchemaBuilder, type SchemaDefinition, type SchemaOptions, type SchemaType, type Spec, type SpecIssue, type SpecIssueSeverity, type SpecStreamCompiler, type SpecStreamLine, type SpecValidationIssues, type SpecValidationResult, type StateModel, type SystemPromptOptions, type UIElement, type UserPromptOptions, type ValidateSpecOptions, type ValidationCheck, type ValidationCheckResult, ValidationCheckSchema, type ValidationConfig, ValidationConfigSchema, type ValidationContext, type ValidationFunction, type ValidationFunctionDefinition, type ValidationMode, type ValidationResult, type VisibilityCondition, VisibilityConditionSchema, type VisibilityContext, action, actionBinding, addByPath, applySpecStreamPatch, autoFixSpec, buildUserPrompt, builtInValidationFunctions, check, compileSpecStream, createCatalog, createSpecStreamCompiler, defineCatalog, defineSchema, evaluateLogicExpression, evaluateVisibility, executeAction, findFormValue, formatSpecIssues, generateCatalogPrompt, generateSystemPrompt, getByPath, interpolateString, parseSpecStreamLine, removeByPath, resolveAction, resolveDynamicValue, resolveElementProps, resolvePropValue, runValidation, runValidationCheck, setByPath, validateSpec, visibility };
import { z } from 'zod';
/**
* Dynamic value - can be a literal or a path reference to data model
* Confirmation dialog configuration
*/
interface ActionConfirm {
title: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
variant?: "default" | "danger";
}
/**
* Action success handler
*/
type ActionOnSuccess = {
navigate: string;
} | {
set: Record<string, unknown>;
} | {
action: string;
};
/**
* Action error handler
*/
type ActionOnError = {
set: Record<string, unknown>;
} | {
action: string;
};
/**
* Action binding — maps an event to an action invocation.
*
* Used inside the `on` field of a UIElement:
* ```json
* { "on": { "press": { "action": "setState", "params": { "path": "/x", "value": 1 } } } }
* ```
*/
interface ActionBinding {
/** Action name (must be in catalog) */
action: string;
/** Parameters to pass to the action handler */
params?: Record<string, DynamicValue>;
/** Confirmation dialog before execution */
confirm?: ActionConfirm;
/** Handler after successful execution */
onSuccess?: ActionOnSuccess;
/** Handler after failed execution */
onError?: ActionOnError;
}
/**
* @deprecated Use ActionBinding instead
*/
type Action = ActionBinding;
/**
* Schema for action confirmation
*/
declare const ActionConfirmSchema: z.ZodObject<{
title: z.ZodString;
message: z.ZodString;
confirmLabel: z.ZodOptional<z.ZodString>;
cancelLabel: z.ZodOptional<z.ZodString>;
variant: z.ZodOptional<z.ZodEnum<{
default: "default";
danger: "danger";
}>>;
}, z.core.$strip>;
/**
* Schema for success handlers
*/
declare const ActionOnSuccessSchema: z.ZodUnion<readonly [z.ZodObject<{
navigate: z.ZodString;
}, z.core.$strip>, z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>;
/**
* Schema for error handlers
*/
declare const ActionOnErrorSchema: z.ZodUnion<readonly [z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>;
/**
* Full action binding schema
*/
declare const ActionBindingSchema: z.ZodObject<{
action: z.ZodString;
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodObject<{
path: z.ZodString;
}, z.core.$strip>]>>>;
confirm: z.ZodOptional<z.ZodObject<{
title: z.ZodString;
message: z.ZodString;
confirmLabel: z.ZodOptional<z.ZodString>;
cancelLabel: z.ZodOptional<z.ZodString>;
variant: z.ZodOptional<z.ZodEnum<{
default: "default";
danger: "danger";
}>>;
}, z.core.$strip>>;
onSuccess: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
navigate: z.ZodString;
}, z.core.$strip>, z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>>;
onError: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>>;
}, z.core.$strip>;
/**
* @deprecated Use ActionBindingSchema instead
*/
declare const ActionSchema: z.ZodObject<{
action: z.ZodString;
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodObject<{
path: z.ZodString;
}, z.core.$strip>]>>>;
confirm: z.ZodOptional<z.ZodObject<{
title: z.ZodString;
message: z.ZodString;
confirmLabel: z.ZodOptional<z.ZodString>;
cancelLabel: z.ZodOptional<z.ZodString>;
variant: z.ZodOptional<z.ZodEnum<{
default: "default";
danger: "danger";
}>>;
}, z.core.$strip>>;
onSuccess: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
navigate: z.ZodString;
}, z.core.$strip>, z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>>;
onError: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>>;
}, z.core.$strip>;
/**
* Action handler function signature
*/
type ActionHandler<TParams = Record<string, unknown>, TResult = unknown> = (params: TParams) => Promise<TResult> | TResult;
/**
* Action definition in catalog
*/
interface ActionDefinition<TParams = Record<string, unknown>> {
/** Zod schema for params validation */
params?: z.ZodType<TParams>;
/** Description for AI */
description?: string;
}
/**
* Resolved action with all dynamic values resolved
*/
interface ResolvedAction {
action: string;
params: Record<string, unknown>;
confirm?: ActionConfirm;
onSuccess?: ActionOnSuccess;
onError?: ActionOnError;
}
/**
* Resolve all dynamic values in an action binding
*/
declare function resolveAction(binding: ActionBinding, stateModel: StateModel): ResolvedAction;
/**
* Interpolate ${path} expressions in a string
*/
declare function interpolateString(template: string, stateModel: StateModel): string;
/**
* Context for action execution
*/
interface ActionExecutionContext {
/** The resolved action */
action: ResolvedAction;
/** The action handler from the host */
handler: ActionHandler;
/** Function to update state model */
setState: (path: string, value: unknown) => void;
/** Function to navigate */
navigate?: (path: string) => void;
/** Function to execute another action */
executeAction?: (name: string) => Promise<void>;
}
/**
* Execute an action with all callbacks
*/
declare function executeAction(ctx: ActionExecutionContext): Promise<void>;
/**
* Helper to create action bindings
*/
declare const actionBinding: {
/** Create a simple action binding */
simple: (actionName: string, params?: Record<string, DynamicValue>) => ActionBinding;
/** Create an action binding with confirmation */
withConfirm: (actionName: string, confirm: ActionConfirm, params?: Record<string, DynamicValue>) => ActionBinding;
/** Create an action binding with success handler */
withSuccess: (actionName: string, onSuccess: ActionOnSuccess, params?: Record<string, DynamicValue>) => ActionBinding;
};
/**
* @deprecated Use actionBinding instead
*/
declare const action: {
/** Create a simple action binding */
simple: (actionName: string, params?: Record<string, DynamicValue>) => ActionBinding;
/** Create an action binding with confirmation */
withConfirm: (actionName: string, confirm: ActionConfirm, params?: Record<string, DynamicValue>) => ActionBinding;
/** Create an action binding with success handler */
withSuccess: (actionName: string, onSuccess: ActionOnSuccess, params?: Record<string, DynamicValue>) => ActionBinding;
};
/**
* Dynamic value - can be a literal or a path reference to state model
*/
type DynamicValue<T = unknown> = T | {

@@ -48,2 +266,9 @@ path: string;

visible?: VisibilityCondition;
/** Event bindings — maps event names to action bindings */
on?: Record<string, ActionBinding | ActionBinding[]>;
/** Repeat children once per item in a state array */
repeat?: {
path: string;
key?: string;
};
}

@@ -101,2 +326,5 @@ /**

elements: Record<string, UIElement>;
/** Optional initial state to seed the state model.
* Components using statePath will read from / write to this state. */
state?: Record<string, unknown>;
}

@@ -111,5 +339,5 @@ /**

/**
* Data model type
* State model type
*/
type DataModel = Record<string, unknown>;
type StateModel = Record<string, unknown>;
/**

@@ -139,5 +367,5 @@ * Component schema definition using Zod

/**
* Resolve a dynamic value against a data model
* Resolve a dynamic value against a state model
*/
declare function resolveDynamicValue<T>(value: DynamicValue<T>, dataModel: DataModel): T | undefined;
declare function resolveDynamicValue<T>(value: DynamicValue<T>, stateModel: StateModel): T | undefined;
/**

@@ -278,3 +506,3 @@ * Get a value from an object by JSON Pointer path (RFC 6901)

interface VisibilityContext {
dataModel: DataModel;
stateModel: StateModel;
authState?: AuthState;

@@ -329,167 +557,25 @@ }

/**
* Confirmation dialog configuration
* A prop expression that resolves to a value based on state.
*
* - `{ $path: string }` reads a value from the state model
* - `{ $cond, $then, $else }` conditionally picks a value
* - Any other value is a literal (passthrough)
*/
interface ActionConfirm {
title: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
variant?: "default" | "danger";
}
/**
* Action success handler
*/
type ActionOnSuccess = {
navigate: string;
type PropExpression<T = unknown> = T | {
$path: string;
} | {
set: Record<string, unknown>;
} | {
action: string;
$cond: VisibilityCondition;
$then: PropExpression<T>;
$else: PropExpression<T>;
};
/**
* Action error handler
* Resolve a single prop value that may contain expressions.
* Recursively resolves $path and $cond/$then/$else expressions.
*/
type ActionOnError = {
set: Record<string, unknown>;
} | {
action: string;
};
declare function resolvePropValue(value: unknown, ctx: VisibilityContext): unknown;
/**
* Rich action definition
* Resolve all prop values in an element's props object.
* Returns a new props object with all expressions resolved.
*/
interface Action {
/** Action name (must be in catalog) */
name: string;
/** Parameters to pass to the action handler */
params?: Record<string, DynamicValue>;
/** Confirmation dialog before execution */
confirm?: ActionConfirm;
/** Handler after successful execution */
onSuccess?: ActionOnSuccess;
/** Handler after failed execution */
onError?: ActionOnError;
}
/**
* Schema for action confirmation
*/
declare const ActionConfirmSchema: z.ZodObject<{
title: z.ZodString;
message: z.ZodString;
confirmLabel: z.ZodOptional<z.ZodString>;
cancelLabel: z.ZodOptional<z.ZodString>;
variant: z.ZodOptional<z.ZodEnum<{
default: "default";
danger: "danger";
}>>;
}, z.core.$strip>;
/**
* Schema for success handlers
*/
declare const ActionOnSuccessSchema: z.ZodUnion<readonly [z.ZodObject<{
navigate: z.ZodString;
}, z.core.$strip>, z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>;
/**
* Schema for error handlers
*/
declare const ActionOnErrorSchema: z.ZodUnion<readonly [z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>;
/**
* Full action schema
*/
declare const ActionSchema: z.ZodObject<{
name: z.ZodString;
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodObject<{
path: z.ZodString;
}, z.core.$strip>]>>>;
confirm: z.ZodOptional<z.ZodObject<{
title: z.ZodString;
message: z.ZodString;
confirmLabel: z.ZodOptional<z.ZodString>;
cancelLabel: z.ZodOptional<z.ZodString>;
variant: z.ZodOptional<z.ZodEnum<{
default: "default";
danger: "danger";
}>>;
}, z.core.$strip>>;
onSuccess: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
navigate: z.ZodString;
}, z.core.$strip>, z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>>;
onError: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
set: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, z.core.$strip>, z.ZodObject<{
action: z.ZodString;
}, z.core.$strip>]>>;
}, z.core.$strip>;
/**
* Action handler function signature
*/
type ActionHandler<TParams = Record<string, unknown>, TResult = unknown> = (params: TParams) => Promise<TResult> | TResult;
/**
* Action definition in catalog
*/
interface ActionDefinition<TParams = Record<string, unknown>> {
/** Zod schema for params validation */
params?: z.ZodType<TParams>;
/** Description for AI */
description?: string;
}
/**
* Resolved action with all dynamic values resolved
*/
interface ResolvedAction {
name: string;
params: Record<string, unknown>;
confirm?: ActionConfirm;
onSuccess?: ActionOnSuccess;
onError?: ActionOnError;
}
/**
* Resolve all dynamic values in an action
*/
declare function resolveAction(action: Action, dataModel: DataModel): ResolvedAction;
/**
* Interpolate ${path} expressions in a string
*/
declare function interpolateString(template: string, dataModel: DataModel): string;
/**
* Context for action execution
*/
interface ActionExecutionContext {
/** The resolved action */
action: ResolvedAction;
/** The action handler from the host */
handler: ActionHandler;
/** Function to update data model */
setData: (path: string, value: unknown) => void;
/** Function to navigate */
navigate?: (path: string) => void;
/** Function to execute another action */
executeAction?: (name: string) => Promise<void>;
}
/**
* Execute an action with all callbacks
*/
declare function executeAction(ctx: ActionExecutionContext): Promise<void>;
/**
* Helper to create actions
*/
declare const action: {
/** Create a simple action */
simple: (name: string, params?: Record<string, DynamicValue>) => Action;
/** Create an action with confirmation */
withConfirm: (name: string, confirm: ActionConfirm, params?: Record<string, DynamicValue>) => Action;
/** Create an action with success handler */
withSuccess: (name: string, onSuccess: ActionOnSuccess, params?: Record<string, DynamicValue>) => Action;
};
declare function resolveElementProps(props: Record<string, unknown>, ctx: VisibilityContext): Record<string, unknown>;

@@ -586,3 +672,3 @@ /**

/** Full data model for resolving paths */
dataModel: DataModel;
stateModel: StateModel;
/** Custom validation functions from catalog */

@@ -619,2 +705,77 @@ customFunctions?: Record<string, ValidationFunction>;

/**
* Severity level for validation issues.
*/
type SpecIssueSeverity = "error" | "warning";
/**
* A single validation issue found in a spec.
*/
interface SpecIssue {
/** Severity: errors should be fixed, warnings are informational */
severity: SpecIssueSeverity;
/** Human-readable description of the issue */
message: string;
/** The element key where the issue was found (if applicable) */
elementKey?: string;
/** Machine-readable issue code for programmatic handling */
code: "missing_root" | "root_not_found" | "missing_child" | "visible_in_props" | "orphaned_element" | "empty_spec" | "on_in_props" | "repeat_in_props";
}
/**
* Result of spec structural validation.
*/
interface SpecValidationIssues {
/** Whether the spec passed validation (no errors; warnings are OK) */
valid: boolean;
/** List of issues found */
issues: SpecIssue[];
}
/**
* Options for validateSpec.
*/
interface ValidateSpecOptions {
/**
* Whether to check for orphaned elements (elements not reachable from root).
* Defaults to false since orphans are harmless (just unused).
*/
checkOrphans?: boolean;
}
/**
* Validate a spec for structural integrity.
*
* Checks for common AI-generation errors:
* - Missing or empty root
* - Root element not found in elements map
* - Children referencing non-existent elements
* - `visible` placed inside `props` instead of on the element
* - Orphaned elements (optional)
*
* @example
* ```ts
* const result = validateSpec(spec);
* if (!result.valid) {
* console.log("Spec errors:", result.issues);
* }
* ```
*/
declare function validateSpec(spec: Spec, options?: ValidateSpecOptions): SpecValidationIssues;
/**
* Auto-fix common spec issues in-place and return a corrected copy.
*
* Currently fixes:
* - `visible` inside `props` → moved to element level
* - `on` inside `props` → moved to element level
* - `repeat` inside `props` → moved to element level
*
* Returns the fixed spec and a list of fixes applied.
*/
declare function autoFixSpec(spec: Spec): {
spec: Spec;
fixes: string[];
};
/**
* Format validation issues into a human-readable string suitable for
* inclusion in a repair prompt sent back to the AI.
*/
declare function formatSpecIssues(issues: SpecIssue[]): string;
/**
* Schema builder primitives

@@ -675,2 +836,4 @@ */

readonly promptTemplate?: PromptTemplate;
/** Default rules baked into the schema (injected before customRules) */
readonly defaultRules?: string[];
/** Create a catalog from this schema */

@@ -736,2 +899,4 @@ createCatalog<TCatalog extends InferCatalogInput<TDef["catalog"]>>(catalog: TCatalog): Catalog$1<TDef, TCatalog>;

promptTemplate?: PromptTemplate<TCatalog>;
/** Default rules baked into the schema (injected before customRules in prompts) */
defaultRules?: string[];
}

@@ -816,2 +981,37 @@ /**

/**
* Options for building a user prompt.
*/
interface UserPromptOptions {
/** The user's text prompt */
prompt: string;
/** Existing spec to refine (triggers patch-only mode) */
currentSpec?: Spec | null;
/** Runtime state context to include */
state?: Record<string, unknown> | null;
/** Maximum length for the user's text prompt (applied before wrapping) */
maxPromptLength?: number;
}
/**
* Build a user prompt for AI generation.
*
* Handles common patterns that every consuming app needs:
* - Truncating the user's prompt to a max length
* - Including the current spec for refinement (patch-only mode)
* - Including runtime state context
*
* @example
* ```ts
* // Fresh generation
* buildUserPrompt({ prompt: "create a todo app" })
*
* // Refinement with existing spec
* buildUserPrompt({ prompt: "add a dark mode toggle", currentSpec: spec })
*
* // With state context
* buildUserPrompt({ prompt: "show my data", state: { todos: [] } })
* ```
*/
declare function buildUserPrompt(options: UserPromptOptions): string;
/**
* Component definition with visibility and validation support

@@ -914,2 +1114,2 @@ */

export { type Action, type ActionConfirm, ActionConfirmSchema, type ActionDefinition, type ActionExecutionContext, type ActionHandler, type ActionOnError, ActionOnErrorSchema, type ActionOnSuccess, ActionOnSuccessSchema, ActionSchema, type AuthState, type Catalog$1 as Catalog, type CatalogConfig, type ComponentDefinition, type ComponentSchema, type DataModel, type DynamicBoolean, DynamicBooleanSchema, type DynamicNumber, DynamicNumberSchema, type DynamicString, DynamicStringSchema, type DynamicValue, DynamicValueSchema, type FlatElement, type InferActionParams, type InferCatalogActions, type InferCatalogComponentProps, type InferCatalogComponents, type InferCatalogInput, type InferComponentProps, type InferSpec, type JsonPatch, type Catalog as LegacyCatalog, type LogicExpression, LogicExpressionSchema, type PatchOp, type PromptContext, type PromptOptions, type PromptTemplate, type ResolvedAction, type Schema, type SchemaBuilder, type SchemaDefinition, type SchemaOptions, type SchemaType, type Spec, type SpecStreamCompiler, type SpecStreamLine, type SpecValidationResult, type SystemPromptOptions, type UIElement, type ValidationCheck, type ValidationCheckResult, ValidationCheckSchema, type ValidationConfig, ValidationConfigSchema, type ValidationContext, type ValidationFunction, type ValidationFunctionDefinition, type ValidationMode, type ValidationResult, type VisibilityCondition, VisibilityConditionSchema, type VisibilityContext, action, addByPath, applySpecStreamPatch, builtInValidationFunctions, check, compileSpecStream, createCatalog, createSpecStreamCompiler, defineCatalog, defineSchema, evaluateLogicExpression, evaluateVisibility, executeAction, findFormValue, generateCatalogPrompt, generateSystemPrompt, getByPath, interpolateString, parseSpecStreamLine, removeByPath, resolveAction, resolveDynamicValue, runValidation, runValidationCheck, setByPath, visibility };
export { type Action, type ActionBinding, ActionBindingSchema, type ActionConfirm, ActionConfirmSchema, type ActionDefinition, type ActionExecutionContext, type ActionHandler, type ActionOnError, ActionOnErrorSchema, type ActionOnSuccess, ActionOnSuccessSchema, ActionSchema, type AuthState, type Catalog$1 as Catalog, type CatalogConfig, type ComponentDefinition, type ComponentSchema, type DynamicBoolean, DynamicBooleanSchema, type DynamicNumber, DynamicNumberSchema, type DynamicString, DynamicStringSchema, type DynamicValue, DynamicValueSchema, type FlatElement, type InferActionParams, type InferCatalogActions, type InferCatalogComponentProps, type InferCatalogComponents, type InferCatalogInput, type InferComponentProps, type InferSpec, type JsonPatch, type Catalog as LegacyCatalog, type LogicExpression, LogicExpressionSchema, type PatchOp, type PromptContext, type PromptOptions, type PromptTemplate, type PropExpression, type ResolvedAction, type Schema, type SchemaBuilder, type SchemaDefinition, type SchemaOptions, type SchemaType, type Spec, type SpecIssue, type SpecIssueSeverity, type SpecStreamCompiler, type SpecStreamLine, type SpecValidationIssues, type SpecValidationResult, type StateModel, type SystemPromptOptions, type UIElement, type UserPromptOptions, type ValidateSpecOptions, type ValidationCheck, type ValidationCheckResult, ValidationCheckSchema, type ValidationConfig, ValidationConfigSchema, type ValidationContext, type ValidationFunction, type ValidationFunctionDefinition, type ValidationMode, type ValidationResult, type VisibilityCondition, VisibilityConditionSchema, type VisibilityContext, action, actionBinding, addByPath, applySpecStreamPatch, autoFixSpec, buildUserPrompt, builtInValidationFunctions, check, compileSpecStream, createCatalog, createSpecStreamCompiler, defineCatalog, defineSchema, evaluateLogicExpression, evaluateVisibility, executeAction, findFormValue, formatSpecIssues, generateCatalogPrompt, generateSystemPrompt, getByPath, interpolateString, parseSpecStreamLine, removeByPath, resolveAction, resolveDynamicValue, resolveElementProps, resolvePropValue, runValidation, runValidationCheck, setByPath, validateSpec, visibility };

@@ -22,3 +22,3 @@ // src/types.ts

]);
function resolveDynamicValue(value, dataModel) {
function resolveDynamicValue(value, stateModel) {
if (value === null || value === void 0) {

@@ -28,3 +28,3 @@ return void 0;

if (typeof value === "object" && "path" in value) {
return getByPath(dataModel, value.path);
return getByPath(stateModel, value.path);
}

@@ -357,3 +357,3 @@ return value;

function evaluateLogicExpression(expr, ctx) {
const { dataModel } = ctx;
const { stateModel } = ctx;
if ("and" in expr) {

@@ -369,3 +369,3 @@ return expr.and.every((subExpr) => evaluateLogicExpression(subExpr, ctx));

if ("path" in expr) {
const value = resolveDynamicValue({ path: expr.path }, dataModel);
const value = resolveDynamicValue({ path: expr.path }, stateModel);
return Boolean(value);

@@ -375,4 +375,4 @@ }

const [left, right] = expr.eq;
const leftValue = resolveDynamicValue(left, dataModel);
const rightValue = resolveDynamicValue(right, dataModel);
const leftValue = resolveDynamicValue(left, stateModel);
const rightValue = resolveDynamicValue(right, stateModel);
return leftValue === rightValue;

@@ -382,4 +382,4 @@ }

const [left, right] = expr.neq;
const leftValue = resolveDynamicValue(left, dataModel);
const rightValue = resolveDynamicValue(right, dataModel);
const leftValue = resolveDynamicValue(left, stateModel);
const rightValue = resolveDynamicValue(right, stateModel);
return leftValue !== rightValue;

@@ -391,7 +391,7 @@ }

left,
dataModel
stateModel
);
const rightValue = resolveDynamicValue(
right,
dataModel
stateModel
);

@@ -407,7 +407,7 @@ if (typeof leftValue === "number" && typeof rightValue === "number") {

left,
dataModel
stateModel
);
const rightValue = resolveDynamicValue(
right,
dataModel
stateModel
);

@@ -423,7 +423,7 @@ if (typeof leftValue === "number" && typeof rightValue === "number") {

left,
dataModel
stateModel
);
const rightValue = resolveDynamicValue(
right,
dataModel
stateModel
);

@@ -439,7 +439,7 @@ if (typeof leftValue === "number" && typeof rightValue === "number") {

left,
dataModel
stateModel
);
const rightValue = resolveDynamicValue(
right,
dataModel
stateModel
);

@@ -461,3 +461,3 @@ if (typeof leftValue === "number" && typeof rightValue === "number") {

if ("path" in condition && !("and" in condition) && !("or" in condition)) {
const value = resolveDynamicValue({ path: condition.path }, ctx.dataModel);
const value = resolveDynamicValue({ path: condition.path }, ctx.stateModel);
return Boolean(value);

@@ -516,2 +516,40 @@ }

// src/props.ts
function isPathExpression(value) {
return typeof value === "object" && value !== null && "$path" in value && typeof value.$path === "string";
}
function isCondExpression(value) {
return typeof value === "object" && value !== null && "$cond" in value && "$then" in value && "$else" in value;
}
function resolvePropValue(value, ctx) {
if (value === null || value === void 0) {
return value;
}
if (isPathExpression(value)) {
return getByPath(ctx.stateModel, value.$path);
}
if (isCondExpression(value)) {
const result = evaluateVisibility(value.$cond, ctx);
return resolvePropValue(result ? value.$then : value.$else, ctx);
}
if (Array.isArray(value)) {
return value.map((item) => resolvePropValue(item, ctx));
}
if (typeof value === "object") {
const resolved = {};
for (const [key, val] of Object.entries(value)) {
resolved[key] = resolvePropValue(val, ctx);
}
return resolved;
}
return value;
}
function resolveElementProps(props, ctx) {
const resolved = {};
for (const [key, value] of Object.entries(props)) {
resolved[key] = resolvePropValue(value, ctx);
}
return resolved;
}
// src/actions.ts

@@ -535,4 +573,4 @@ import { z as z3 } from "zod";

]);
var ActionSchema = z3.object({
name: z3.string(),
var ActionBindingSchema = z3.object({
action: z3.string(),
params: z3.record(z3.string(), DynamicValueSchema).optional(),

@@ -543,28 +581,29 @@ confirm: ActionConfirmSchema.optional(),

});
function resolveAction(action2, dataModel) {
var ActionSchema = ActionBindingSchema;
function resolveAction(binding, stateModel) {
const resolvedParams = {};
if (action2.params) {
for (const [key, value] of Object.entries(action2.params)) {
resolvedParams[key] = resolveDynamicValue(value, dataModel);
if (binding.params) {
for (const [key, value] of Object.entries(binding.params)) {
resolvedParams[key] = resolveDynamicValue(value, stateModel);
}
}
let confirm = action2.confirm;
let confirm = binding.confirm;
if (confirm) {
confirm = {
...confirm,
message: interpolateString(confirm.message, dataModel),
title: interpolateString(confirm.title, dataModel)
message: interpolateString(confirm.message, stateModel),
title: interpolateString(confirm.title, stateModel)
};
}
return {
name: action2.name,
action: binding.action,
params: resolvedParams,
confirm,
onSuccess: action2.onSuccess,
onError: action2.onError
onSuccess: binding.onSuccess,
onError: binding.onError
};
}
function interpolateString(template, dataModel) {
function interpolateString(template, stateModel) {
return template.replace(/\$\{([^}]+)\}/g, (_, path) => {
const value = resolveDynamicValue({ path }, dataModel);
const value = resolveDynamicValue({ path }, stateModel);
return String(value ?? "");

@@ -574,3 +613,3 @@ });

async function executeAction(ctx) {
const { action: action2, handler, setData, navigate, executeAction: executeAction2 } = ctx;
const { action: action2, handler, setState, navigate, executeAction: executeAction2 } = ctx;
try {

@@ -583,3 +622,3 @@ await handler(action2.params);

for (const [path, value] of Object.entries(action2.onSuccess.set)) {
setData(path, value);
setState(path, value);
}

@@ -595,3 +634,3 @@ } else if ("action" in action2.onSuccess && executeAction2) {

const resolvedValue = typeof value === "string" && value === "$error.message" ? error.message : value;
setData(path, resolvedValue);
setState(path, resolvedValue);
}

@@ -606,17 +645,17 @@ } else if ("action" in action2.onError && executeAction2) {

}
var action = {
/** Create a simple action */
simple: (name, params) => ({
name,
var actionBinding = {
/** Create a simple action binding */
simple: (actionName, params) => ({
action: actionName,
params
}),
/** Create an action with confirmation */
withConfirm: (name, confirm, params) => ({
name,
/** Create an action binding with confirmation */
withConfirm: (actionName, confirm, params) => ({
action: actionName,
params,
confirm
}),
/** Create an action with success handler */
withSuccess: (name, onSuccess, params) => ({
name,
/** Create an action binding with success handler */
withSuccess: (actionName, onSuccess, params) => ({
action: actionName,
params,

@@ -626,2 +665,3 @@ onSuccess

};
var action = actionBinding;

@@ -735,7 +775,7 @@ // src/validation.ts

function runValidationCheck(check2, ctx) {
const { value, dataModel, customFunctions } = ctx;
const { value, stateModel, customFunctions } = ctx;
const resolvedArgs = {};
if (check2.args) {
for (const [key, argValue] of Object.entries(check2.args)) {
resolvedArgs[key] = resolveDynamicValue(argValue, dataModel);
resolvedArgs[key] = resolveDynamicValue(argValue, stateModel);
}

@@ -765,3 +805,3 @@ }

const enabled = evaluateLogicExpression(config.enabled, {
dataModel: ctx.dataModel,
stateModel: ctx.stateModel,
authState: ctx.authState

@@ -833,2 +873,151 @@ });

// src/spec-validator.ts
function validateSpec(spec, options = {}) {
const { checkOrphans = false } = options;
const issues = [];
if (!spec.root) {
issues.push({
severity: "error",
message: "Spec has no root element defined.",
code: "missing_root"
});
return { valid: false, issues };
}
if (!spec.elements[spec.root]) {
issues.push({
severity: "error",
message: `Root element "${spec.root}" not found in elements map.`,
code: "root_not_found"
});
}
if (Object.keys(spec.elements).length === 0) {
issues.push({
severity: "error",
message: "Spec has no elements.",
code: "empty_spec"
});
return { valid: false, issues };
}
for (const [key, element] of Object.entries(spec.elements)) {
if (element.children) {
for (const childKey of element.children) {
if (!spec.elements[childKey]) {
issues.push({
severity: "error",
message: `Element "${key}" references child "${childKey}" which does not exist in the elements map.`,
elementKey: key,
code: "missing_child"
});
}
}
}
const props = element.props;
if (props && "visible" in props && props.visible !== void 0) {
issues.push({
severity: "error",
message: `Element "${key}" has "visible" inside "props". It should be a top-level field on the element (sibling of type/props/children).`,
elementKey: key,
code: "visible_in_props"
});
}
if (props && "on" in props && props.on !== void 0) {
issues.push({
severity: "error",
message: `Element "${key}" has "on" inside "props". It should be a top-level field on the element (sibling of type/props/children).`,
elementKey: key,
code: "on_in_props"
});
}
if (props && "repeat" in props && props.repeat !== void 0) {
issues.push({
severity: "error",
message: `Element "${key}" has "repeat" inside "props". It should be a top-level field on the element (sibling of type/props/children).`,
elementKey: key,
code: "repeat_in_props"
});
}
}
if (checkOrphans) {
const reachable = /* @__PURE__ */ new Set();
const walk = (key) => {
if (reachable.has(key)) return;
reachable.add(key);
const el = spec.elements[key];
if (el?.children) {
for (const childKey of el.children) {
if (spec.elements[childKey]) {
walk(childKey);
}
}
}
};
if (spec.elements[spec.root]) {
walk(spec.root);
}
for (const key of Object.keys(spec.elements)) {
if (!reachable.has(key)) {
issues.push({
severity: "warning",
message: `Element "${key}" is not reachable from root "${spec.root}".`,
elementKey: key,
code: "orphaned_element"
});
}
}
}
const hasErrors = issues.some((i) => i.severity === "error");
return { valid: !hasErrors, issues };
}
function autoFixSpec(spec) {
const fixes = [];
const fixedElements = {};
for (const [key, element] of Object.entries(spec.elements)) {
const props = element.props;
let fixed = element;
if (props && "visible" in props && props.visible !== void 0) {
const { visible, ...restProps } = fixed.props;
fixed = {
...fixed,
props: restProps,
visible
};
fixes.push(`Moved "visible" from props to element level on "${key}".`);
}
let currentProps = fixed.props;
if (currentProps && "on" in currentProps && currentProps.on !== void 0) {
const { on, ...restProps } = currentProps;
fixed = {
...fixed,
props: restProps,
on
};
fixes.push(`Moved "on" from props to element level on "${key}".`);
}
currentProps = fixed.props;
if (currentProps && "repeat" in currentProps && currentProps.repeat !== void 0) {
const { repeat, ...restProps } = currentProps;
fixed = {
...fixed,
props: restProps,
repeat
};
fixes.push(`Moved "repeat" from props to element level on "${key}".`);
}
fixedElements[key] = fixed;
}
return {
spec: { root: spec.root, elements: fixedElements },
fixes
};
}
function formatSpecIssues(issues) {
const errors = issues.filter((i) => i.severity === "error");
if (errors.length === 0) return "";
const lines = ["The generated UI spec has the following errors:"];
for (const issue of errors) {
lines.push(`- ${issue.message}`);
}
return lines.join("\n");
}
// src/schema.ts

@@ -858,2 +1047,3 @@ import { z as z5 } from "zod";

promptTemplate: options?.promptTemplate,
defaultRules: options?.defaultRules,
createCatalog(catalog) {

@@ -1014,3 +1204,3 @@ return createCatalogFromSchema(this, catalog);

lines.push(
"Each line is a JSON patch operation. Start with the root, then add each element."
"Each line is a JSON patch operation. Start with /root, then stream /elements and /state patches interleaved so the UI fills in progressively as it streams."
);

@@ -1020,7 +1210,87 @@ lines.push("");

lines.push("");
lines.push(`{"op":"add","path":"/root","value":"card-1"}
{"op":"add","path":"/elements/card-1","value":{"type":"Card","props":{"title":"Dashboard"},"children":["metric-1","chart-1"]}}
{"op":"add","path":"/elements/metric-1","value":{"type":"Metric","props":{"label":"Revenue","valuePath":"analytics.revenue","format":"currency"},"children":[]}}
{"op":"add","path":"/elements/chart-1","value":{"type":"Chart","props":{"type":"bar","dataPath":"analytics.salesByRegion"},"children":[]}}`);
lines.push(`{"op":"add","path":"/root","value":"blog"}
{"op":"add","path":"/elements/blog","value":{"type":"Stack","props":{"direction":"vertical","gap":"md"},"children":["heading","posts-grid"]}}
{"op":"add","path":"/elements/heading","value":{"type":"Heading","props":{"text":"Blog","level":"h1"},"children":[]}}
{"op":"add","path":"/elements/posts-grid","value":{"type":"Grid","props":{"columns":2,"gap":"md"},"repeat":{"path":"/posts","key":"id"},"children":["post-card"]}}
{"op":"add","path":"/elements/post-card","value":{"type":"Card","props":{"title":{"$path":"$item/title"}},"children":["post-meta"]}}
{"op":"add","path":"/elements/post-meta","value":{"type":"Text","props":{"text":{"$path":"$item/author"},"variant":"muted"},"children":[]}}
{"op":"add","path":"/state/posts","value":[]}
{"op":"add","path":"/state/posts/0","value":{"id":"1","title":"Getting Started","author":"Jane","date":"Jan 15"}}
{"op":"add","path":"/state/posts/1","value":{"id":"2","title":"Advanced Tips","author":"Bob","date":"Feb 3"}}
Note: state patches appear right after the elements that use them, so the UI fills in as it streams.`);
lines.push("");
lines.push("INITIAL STATE:");
lines.push(
"Specs include a /state field to seed the state model. Components with statePath read from and write to this state, and $path expressions read from it."
);
lines.push(
"CRITICAL: You MUST include state patches whenever your UI displays data via $path expressions, uses repeat to iterate over arrays, or uses statePath bindings. Without state, $path references resolve to nothing and repeat lists render zero items."
);
lines.push(
"Output state patches right after the elements that reference them, so the UI fills in progressively as it streams."
);
lines.push(
"Stream state progressively - output one patch per array item instead of one giant blob:"
);
lines.push(
' For arrays: {"op":"add","path":"/state/posts/0","value":{"id":"1","title":"First Post",...}} then /state/posts/1, /state/posts/2, etc.'
);
lines.push(
' For scalars: {"op":"add","path":"/state/newTodoText","value":""}'
);
lines.push(
' Initialize the array first if needed: {"op":"add","path":"/state/posts","value":[]}'
);
lines.push(
'When content comes from the state model, use { "$path": "/some/path" } dynamic props to display it instead of hardcoding the same value in both state and props. The state model is the single source of truth.'
);
lines.push(
"Include realistic sample data in state. For blogs: 3-4 posts with titles, excerpts, authors, dates. For product lists: 3-5 items with names, prices, descriptions. Never leave arrays empty."
);
lines.push("");
lines.push("DYNAMIC LISTS (repeat field):");
lines.push(
'Any element can have a top-level "repeat" field to render its children once per item in a state array: { "repeat": { "path": "/arrayPath", "key": "id" } }.'
);
lines.push(
'The element itself renders once (as the container), and its children are expanded once per array item. "path" is the state array path. "key" is an optional field name on each item for stable React keys.'
);
lines.push(
'Example: { "type": "Column", "props": { "gap": 8 }, "repeat": { "path": "/todos", "key": "id" }, "children": ["todo-item"] }'
);
lines.push(
'Inside children of a repeated element, use "$item/field" for per-item paths: statePath:"$item/completed", { "$path": "$item/title" }. Use "$index" for the current array index.'
);
lines.push(
"ALWAYS use the repeat field for lists backed by state arrays. NEVER hardcode individual elements for each array item."
);
lines.push(
'IMPORTANT: "repeat" is a top-level field on the element (sibling of type/props/children), NOT inside props.'
);
lines.push("");
lines.push("ARRAY STATE ACTIONS:");
lines.push(
'Use action "pushState" to append items to arrays. Params: { path: "/arrayPath", value: { ...item }, clearPath: "/inputPath" }.'
);
lines.push(
'Values inside pushState can contain { "path": "/statePath" } references to read current state (e.g. the text from an input field).'
);
lines.push(
'Use "$id" inside a pushState value to auto-generate a unique ID.'
);
lines.push(
'Example: on: { "press": { "action": "pushState", "params": { "path": "/todos", "value": { "id": "$id", "title": { "path": "/newTodoText" }, "completed": false }, "clearPath": "/newTodoText" } } }'
);
lines.push(
`Use action "removeState" to remove items from arrays by index. Params: { path: "/arrayPath", index: N }. Inside a repeated element's children, use "$index" for the current item index.`
);
lines.push(
"For lists where users can add/remove items (todos, carts, etc.), use pushState and removeState instead of hardcoding with setState."
);
lines.push("");
lines.push(
'IMPORTANT: State paths use RFC 6901 JSON Pointer syntax (e.g. "/todos/0/title"). Do NOT use JavaScript-style dot notation (e.g. "/todos.length" is WRONG). To generate unique IDs for new items, use "$id" instead of trying to read array length.'
);
lines.push("");
const components = catalog.data.components;

@@ -1034,4 +1304,5 @@ if (components) {

const childrenStr = hasChildren ? " [accepts children]" : "";
const eventsStr = def.events && def.events.length > 0 ? ` [events: ${def.events.join(", ")}]` : "";
const descStr = def.description ? ` - ${def.description}` : "";
lines.push(`- ${name}: ${propsStr}${descStr}${childrenStr}`);
lines.push(`- ${name}: ${propsStr}${descStr}${childrenStr}${eventsStr}`);
}

@@ -1049,7 +1320,80 @@ lines.push("");

}
lines.push("EVENTS (the `on` field):");
lines.push(
"Elements can have an optional `on` field to bind events to actions. The `on` field is a top-level field on the element (sibling of type/props/children), NOT inside props."
);
lines.push(
'Each key in `on` is an event name (from the component\'s supported events), and the value is an action binding: `{ "action": "<actionName>", "params": { ... } }`.'
);
lines.push("");
lines.push("Example:");
lines.push(
' {"type":"Button","props":{"label":"Save"},"on":{"press":{"action":"setState","params":{"path":"/saved","value":true}}},"children":[]}'
);
lines.push("");
lines.push(
'Action params can use dynamic path references to read from state: { "path": "/statePath" }.'
);
lines.push(
"IMPORTANT: Do NOT put action/actionParams inside props. Always use the `on` field for event bindings."
);
lines.push("");
lines.push("VISIBILITY CONDITIONS:");
lines.push(
"Elements can have an optional `visible` field to conditionally show/hide based on data state. IMPORTANT: `visible` is a top-level field on the element object (sibling of type/props/children), NOT inside props."
);
lines.push(
'Correct: {"type":"Column","props":{"gap":8},"visible":{"eq":[{"path":"/tab"},"home"]},"children":[...]}'
);
lines.push(
'- `{ "eq": [{ "path": "/statePath" }, "value"] }` - visible when state at path equals value'
);
lines.push(
'- `{ "neq": [{ "path": "/statePath" }, "value"] }` - visible when state at path does not equal value'
);
lines.push('- `{ "path": "/statePath" }` - visible when path is truthy');
lines.push(
'- `{ "and": [...] }`, `{ "or": [...] }`, `{ "not": {...} }` - combine conditions'
);
lines.push("- `true` / `false` - always visible/hidden");
lines.push("");
lines.push(
"Use the Pressable component with on.press bound to setState to update state and drive visibility."
);
lines.push(
'Example: A Pressable with on: { "press": { "action": "setState", "params": { "path": "/activeTab", "value": "home" } } } sets state, then a container with visible: { "eq": [{ "path": "/activeTab" }, "home"] } shows only when that tab is active.'
);
lines.push("");
lines.push("DYNAMIC PROPS:");
lines.push(
"Any prop value can be a dynamic expression that resolves based on state. Two forms are supported:"
);
lines.push("");
lines.push(
'1. State binding: `{ "$path": "/statePath" }` - resolves to the value at that state path.'
);
lines.push(
' Example: `"color": { "$path": "/theme/primary" }` reads the color from state.'
);
lines.push("");
lines.push(
'2. Conditional: `{ "$cond": <condition>, "$then": <value>, "$else": <value> }` - evaluates the condition (same syntax as visibility conditions) and picks the matching value.'
);
lines.push(
' Example: `"color": { "$cond": { "eq": [{ "path": "/activeTab" }, "home"] }, "$then": "#007AFF", "$else": "#8E8E93" }`'
);
lines.push(
' Example: `"name": { "$cond": { "eq": [{ "path": "/activeTab" }, "home"] }, "$then": "home", "$else": "home-outline" }`'
);
lines.push("");
lines.push(
"Use dynamic props instead of duplicating elements with opposing visible conditions when only prop values differ."
);
lines.push("");
lines.push("RULES:");
const baseRules = [
"Output ONLY JSONL patches - one JSON object per line, no markdown, no code fences",
'First line sets root: {"op":"add","path":"/root","value":"<root-key>"}',
'First set root: {"op":"add","path":"/root","value":"<root-key>"}',
'Then add each element: {"op":"add","path":"/elements/<key>","value":{...}}',
"Output /state patches right after the elements that use them, one per array item for progressive loading. REQUIRED whenever using $path, repeat, or statePath.",
"ONLY use components listed above",

@@ -1059,3 +1403,4 @@ "Each element value needs: type, props, children (array of child keys)",

];
const allRules = [...baseRules, ...customRules];
const schemaRules = catalog.schema.defaultRules ?? [];
const allRules = [...baseRules, ...schemaRules, ...customRules];
allRules.forEach((rule, i) => {

@@ -1198,2 +1543,52 @@ lines.push(`${i + 1}. ${rule}`);

// src/prompt.ts
function isNonEmptySpec(spec) {
if (!spec || typeof spec !== "object") return false;
const s = spec;
return typeof s.root === "string" && typeof s.elements === "object" && s.elements !== null && Object.keys(s.elements).length > 0;
}
var PATCH_INSTRUCTIONS = `IMPORTANT: The current UI is already loaded. Output ONLY the patches needed to make the requested change:
- To add a new element: {"op":"add","path":"/elements/new-key","value":{...}}
- To modify an existing element: {"op":"replace","path":"/elements/existing-key","value":{...}}
- To remove an element: {"op":"remove","path":"/elements/old-key"}
- To update the root: {"op":"replace","path":"/root","value":"new-root-key"}
- To add children: update the parent element with new children array
DO NOT output patches for elements that don't need to change. Only output what's necessary for the requested modification.`;
function buildUserPrompt(options) {
const { prompt, currentSpec, state, maxPromptLength } = options;
let userText = String(prompt || "");
if (maxPromptLength !== void 0 && maxPromptLength > 0) {
userText = userText.slice(0, maxPromptLength);
}
if (isNonEmptySpec(currentSpec)) {
const parts2 = [];
parts2.push(
`CURRENT UI STATE (already loaded, DO NOT recreate existing elements):`
);
parts2.push(JSON.stringify(currentSpec, null, 2));
parts2.push("");
parts2.push(`USER REQUEST: ${userText}`);
if (state && Object.keys(state).length > 0) {
parts2.push("");
parts2.push(`AVAILABLE STATE:
${JSON.stringify(state, null, 2)}`);
}
parts2.push("");
parts2.push(PATCH_INSTRUCTIONS);
return parts2.join("\n");
}
const parts = [userText];
if (state && Object.keys(state).length > 0) {
parts.push(`
AVAILABLE STATE:
${JSON.stringify(state, null, 2)}`);
}
parts.push(
`
Remember: Output /root first, then interleave /elements and /state patches so the UI fills in progressively as it streams. Output each state patch right after the elements that use it, one per array item.`
);
return parts.join("\n");
}
// src/catalog.ts

@@ -1308,3 +1703,3 @@ import { z as z6 } from "zod";

lines.push("- `true` / `false` - Always visible/hidden");
lines.push('- `{ "path": "/data/path" }` - Visible when path is truthy');
lines.push('- `{ "path": "/state/path" }` - Visible when path is truthy');
lines.push('- `{ "auth": "signedIn" }` - Visible when user is signed in');

@@ -1479,2 +1874,3 @@ lines.push('- `{ "and": [...] }` - All conditions must be true');

export {
ActionBindingSchema,
ActionConfirmSchema,

@@ -1493,4 +1889,7 @@ ActionOnErrorSchema,

action,
actionBinding,
addByPath,
applySpecStreamPatch,
autoFixSpec,
buildUserPrompt,
builtInValidationFunctions,

@@ -1507,2 +1906,3 @@ check,

findFormValue,
formatSpecIssues,
generateCatalogPrompt,

@@ -1516,7 +1916,10 @@ generateSystemPrompt,

resolveDynamicValue,
resolveElementProps,
resolvePropValue,
runValidation,
runValidationCheck,
setByPath,
validateSpec,
visibility
};
//# sourceMappingURL=index.mjs.map
{
"name": "@json-render/core",
"version": "0.4.4",
"version": "0.5.0",
"license": "Apache-2.0",

@@ -5,0 +5,0 @@ "description": "JSON becomes real things. Define your catalog, register your components, let AI generate.",

+126
-0

@@ -176,2 +176,25 @@ # @json-render/core

### Dynamic Props
| Export | Purpose |
|--------|---------|
| `resolvePropValue(value, ctx)` | Resolve a single prop expression |
| `resolveElementProps(props, ctx)` | Resolve all prop expressions in an element |
| `PropExpression<T>` | Type for prop values that may contain expressions |
### User Prompt
| Export | Purpose |
|--------|---------|
| `buildUserPrompt(options)` | Build a user prompt with optional spec refinement and state context |
| `UserPromptOptions` | Options type for `buildUserPrompt` |
### Spec Validation
| Export | Purpose |
|--------|---------|
| `validateSpec(spec, catalog?)` | Validate spec structure and return issues |
| `autoFixSpec(spec)` | Auto-fix common spec issues (returns corrected copy) |
| `formatSpecIssues(issues)` | Format validation issues as readable strings |
### Types

@@ -183,5 +206,108 @@

| `Catalog` | Catalog type |
| `VisibilityCondition` | Visibility condition type (used by `$cond`) |
| `VisibilityContext` | Context for evaluating visibility and prop expressions |
| `SpecStreamLine` | Single patch operation |
| `SpecStreamCompiler` | Streaming compiler interface |
## Dynamic Prop Expressions
Any prop value can be a dynamic expression that resolves based on data state at render time. Expressions are resolved by the renderer before props reach components.
### Data Binding (`$path`)
Read a value directly from the state model:
```json
{
"color": { "$path": "/theme/primary" },
"label": { "$path": "/user/name" }
}
```
### Conditional (`$cond` / `$then` / `$else`)
Evaluate a condition (same syntax as visibility conditions) and pick a value:
```json
{
"color": {
"$cond": { "eq": [{ "path": "/activeTab" }, "home"] },
"$then": "#007AFF",
"$else": "#8E8E93"
},
"name": {
"$cond": { "eq": [{ "path": "/activeTab" }, "home"] },
"$then": "home",
"$else": "home-outline"
}
}
```
`$then` and `$else` can themselves be expressions (recursive):
```json
{
"label": {
"$cond": { "path": "/user/isAdmin" },
"$then": { "$path": "/admin/greeting" },
"$else": "Welcome"
}
}
```
### API
```typescript
import { resolvePropValue, resolveElementProps } from "@json-render/core";
// Resolve a single value
const color = resolvePropValue(
{ $cond: { eq: [{ path: "/active" }, "yes"] }, $then: "blue", $else: "gray" },
{ stateModel: myState }
);
// Resolve all props on an element
const resolved = resolveElementProps(element.props, { stateModel: myState });
```
## User Prompt Builder
Build structured user prompts for AI generation, with support for refinement and state context:
```typescript
import { buildUserPrompt } from "@json-render/core";
// Fresh generation
const prompt = buildUserPrompt({ prompt: "create a todo app" });
// Refinement with existing spec (triggers patch-only mode)
const refinementPrompt = buildUserPrompt({
prompt: "add a dark mode toggle",
currentSpec: existingSpec,
});
// With runtime state context
const contextPrompt = buildUserPrompt({
prompt: "show my data",
state: { todos: [{ text: "Buy milk" }] },
});
```
## Spec Validation
Validate spec structure and auto-fix common issues:
```typescript
import { validateSpec, autoFixSpec, formatSpecIssues } from "@json-render/core";
// Validate a spec
const { valid, issues } = validateSpec(spec, catalog);
// Format issues for display
console.log(formatSpecIssues(issues));
// Auto-fix common issues (returns a corrected copy)
const fixed = autoFixSpec(spec);
```
## Custom Schemas

@@ -188,0 +314,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display