From b1d5fc986c39c76b7cb50379e28e0af1808561c0 Mon Sep 17 00:00:00 2001 From: shipooor Date: Mon, 16 Mar 2026 17:01:27 +0500 Subject: [PATCH 01/12] Extract framework-agnostic core into lang-core package Move parser, prompt generation, validation, and generic library types out of react-lang into a new @openuidev/lang-core package. The generic DefinedComponent and Library allow each framework adapter (React, Svelte, etc.) to narrow the component type parameter independently. --- packages/lang-core/package.json | 39 + packages/lang-core/src/index.ts | 22 + packages/lang-core/src/library.ts | 133 ++++ packages/lang-core/src/parser/index.ts | 7 + packages/lang-core/src/parser/parser.ts | 787 +++++++++++++++++++++ packages/lang-core/src/parser/prompt.ts | 336 +++++++++ packages/lang-core/src/parser/types.ts | 82 +++ packages/lang-core/src/utils/validation.ts | 150 ++++ packages/lang-core/tsconfig.json | 17 + 9 files changed, 1573 insertions(+) create mode 100644 packages/lang-core/package.json create mode 100644 packages/lang-core/src/index.ts create mode 100644 packages/lang-core/src/library.ts create mode 100644 packages/lang-core/src/parser/index.ts create mode 100644 packages/lang-core/src/parser/parser.ts create mode 100644 packages/lang-core/src/parser/prompt.ts create mode 100644 packages/lang-core/src/parser/types.ts create mode 100644 packages/lang-core/src/utils/validation.ts create mode 100644 packages/lang-core/tsconfig.json diff --git a/packages/lang-core/package.json b/packages/lang-core/package.json new file mode 100644 index 000000000..8591b8890 --- /dev/null +++ b/packages/lang-core/package.json @@ -0,0 +1,39 @@ +{ + "name": "@openuidev/lang-core", + "version": "0.1.0", + "description": "Framework-agnostic core for OpenUI Lang: parser, prompt generation, validation, and type definitions", + "license": "MIT", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": ["dist", "README.md"], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "scripts": { + "build": "tsc -p .", + "watch": "tsc -p . --watch", + "lint:check": "eslint ./src", + "lint:fix": "eslint ./src --fix", + "format:fix": "prettier --write ./src", + "format:check": "prettier --check ./src", + "prepare": "pnpm run build", + "ci": "pnpm run lint:check && pnpm run format:check" + }, + "dependencies": { + "zod": "^4.0.0" + }, + "keywords": ["openui", "openui-lang", "parser", "prompt-generation", "validation", "zod", "llm", "generative-ui", "framework-agnostic"], + "homepage": "https://openui.com", + "repository": { + "type": "git", + "url": "https://github.com/thesysdev/openui.git", + "directory": "packages/lang-core" + }, + "bugs": { "url": "https://github.com/thesysdev/openui/issues" }, + "author": "engineering@thesys.dev" +} diff --git a/packages/lang-core/src/index.ts b/packages/lang-core/src/index.ts new file mode 100644 index 000000000..4833ee6db --- /dev/null +++ b/packages/lang-core/src/index.ts @@ -0,0 +1,22 @@ +// ── Library (framework-generic) ── +export { createLibrary, defineComponent } from "./library"; +export type { + ComponentGroup, + ComponentRenderProps, + DefinedComponent, + Library, + LibraryDefinition, + PromptOptions, + SubComponentOf, +} from "./library"; + +// ── Parser ── +export { createParser, createStreamingParser, parse } from "./parser"; +export type { LibraryJSONSchema, Parser, StreamParser } from "./parser"; +export { generatePrompt } from "./parser/prompt"; +export { BuiltinActionType } from "./parser/types"; +export type { ActionEvent, ElementNode, ParseResult, ValidationError } from "./parser/types"; + +// ── Validation ── +export { builtInValidators, parseRules, parseStructuredRules, validate } from "./utils/validation"; +export type { ParsedRule, ValidatorFn } from "./utils/validation"; diff --git a/packages/lang-core/src/library.ts b/packages/lang-core/src/library.ts new file mode 100644 index 000000000..8d0a4b18c --- /dev/null +++ b/packages/lang-core/src/library.ts @@ -0,0 +1,133 @@ +import { z } from "zod"; +import { generatePrompt } from "./parser/prompt"; + +// ─── Sub-component type ────────────────────────────────────────────────────── + +/** + * Runtime shape of a parsed sub-component element as seen by parent renderers. + */ +export type SubComponentOf

= { + type: "element"; + typeName: string; + props: P; + partial: boolean; +}; + +// ─── Renderer types (framework-generic) ────────────────────────────────────── + +/** + * The props passed to every component renderer. + * + * Framework adapters narrow `RenderNode`: + * - React: RenderNode = ReactNode + * - Svelte: RenderNode = Snippet<[unknown]> + * - Vue: RenderNode = VNode + */ +export interface ComponentRenderProps

, RenderNode = unknown> { + props: P; + renderNode: (value: unknown) => RenderNode; +} + +// ─── DefinedComponent (framework-generic) ──────────────────────────────────── + +/** + * A fully defined component. The `C` parameter represents the + * framework-specific component/renderer type. lang-core never + * inspects this value — it is stored opaquely and consumed + * by the framework adapter's Renderer. + */ +export interface DefinedComponent = z.ZodObject, C = unknown> { + name: string; + props: T; + description: string; + component: C; + /** Use in parent schemas: `z.array(ChildComponent.ref)` */ + ref: z.ZodType>>; +} + +/** + * Define a component with name, schema, description, and renderer. + * Registers the Zod schema globally and returns a `.ref` for parent schemas. + */ +export function defineComponent, C>(config: { + name: string; + props: T; + description: string; + component: C; +}): DefinedComponent { + (config.props as any).register(z.globalRegistry, { id: config.name }); + return { + ...config, + ref: config.props as unknown as z.ZodType>>, + }; +} + +// ─── Groups & Prompt ────────────────────────────────────────────────────────── + +export interface ComponentGroup { + name: string; + components: string[]; + notes?: string[]; +} + +export interface PromptOptions { + preamble?: string; + additionalRules?: string[]; + examples?: string[]; +} + +// ─── Library ────────────────────────────────────────────────────────────────── + +export interface Library { + readonly components: Record>; + readonly componentGroups: ComponentGroup[] | undefined; + readonly root: string | undefined; + + prompt(options?: PromptOptions): string; + toJSONSchema(): object; +} + +export interface LibraryDefinition { + components: DefinedComponent[]; + componentGroups?: ComponentGroup[]; + root?: string; +} + +/** + * Create a component library from an array of defined components. + */ +export function createLibrary(input: LibraryDefinition): Library { + const componentsRecord: Record> = {}; + for (const comp of input.components) { + if (!z.globalRegistry.has(comp.props)) { + comp.props.register(z.globalRegistry, { id: comp.name }); + } + componentsRecord[comp.name] = comp; + } + + if (input.root && !componentsRecord[input.root]) { + const available = Object.keys(componentsRecord).join(", "); + throw new Error( + `[createLibrary] Root component "${input.root}" was not found in components. Available components: ${available}`, + ); + } + + const library: Library = { + components: componentsRecord, + componentGroups: input.componentGroups, + root: input.root, + + prompt(options?: PromptOptions): string { + return generatePrompt(library, options); + }, + + toJSONSchema(): object { + const combinedSchema = z.object( + Object.fromEntries(Object.entries(componentsRecord).map(([k, v]) => [k, v.props])) as any, + ); + return z.toJSONSchema(combinedSchema); + }, + }; + + return library; +} diff --git a/packages/lang-core/src/parser/index.ts b/packages/lang-core/src/parser/index.ts new file mode 100644 index 000000000..7cab53b1a --- /dev/null +++ b/packages/lang-core/src/parser/index.ts @@ -0,0 +1,7 @@ +export { BuiltinActionType } from "./types"; +export type { ActionEvent, ElementNode, ParseResult, ValidationError } from "./types"; + +export { createParser, createStreamingParser, parse } from "./parser"; +export type { LibraryJSONSchema, Parser, StreamParser } from "./parser"; + +export { generatePrompt } from "./prompt"; diff --git a/packages/lang-core/src/parser/parser.ts b/packages/lang-core/src/parser/parser.ts new file mode 100644 index 000000000..3134d2d03 --- /dev/null +++ b/packages/lang-core/src/parser/parser.ts @@ -0,0 +1,787 @@ +import type { ParseResult } from "./types"; + +/** + * The JSON Schema document produced by `library.toJSONSchema()`. + * All component schemas live in `$defs`, keyed by component name. + */ +export interface LibraryJSONSchema { + $defs?: Record< + string, + { + properties?: Record; + required?: string[]; + } + >; +} + +export interface ParamDef { + /** Parameter name, e.g. "title", "columns". */ + name: string; + /** Whether the parameter is required by the component. */ + required: boolean; + /** Default value from JSON Schema — used when the required field is missing/null. */ + defaultValue?: unknown; +} + +/** + * Internal parameter map. + */ +export type ParamMap = Map; + +// ───────────────────────────────────────────────────────────────────────────── +// AST node types +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Discriminated union representing every value that can appear in an + * openui-lang expression. The `k` field is the discriminant. + * + * - `Comp` — a component call: `Header("Hello", "Subtitle")` + * - `Str` — a string literal: `"hello"` + * - `Num` — a number literal: `42` or `3.14` + * - `Bool` — a boolean literal: `true` or `false` + * - `Null` — the null literal + * - `Arr` — an array: `[a, b, c]` + * - `Obj` — an object: `{ key: value }` + * - `Ref` — a reference to another statement: `myTable` (resolved later) + * - `Ph` — a placeholder for an unresolvable reference (dropped as null in output) + */ +type ASTNode = + | { k: "Comp"; name: string; args: ASTNode[] } + | { k: "Str"; v: string } + | { k: "Num"; v: number } + | { k: "Bool"; v: boolean } + | { k: "Null" } + | { k: "Arr"; els: ASTNode[] } + | { k: "Obj"; entries: [string, ASTNode][] } + | { k: "Ref"; n: string } + | { k: "Ph"; n: string }; + +const enum T { + Newline = 0, + LParen = 1, // ( + RParen = 2, // ) + LBrack = 3, // [ + RBrack = 4, // ] + LBrace = 5, // { + RBrace = 6, // } + Comma = 7, // , + Colon = 8, // : + Equals = 9, // = + True = 10, + False = 11, + Null = 12, + EOF = 13, + Str = 14, // carries string value + Num = 15, // carries numeric value + Ident = 16, // lowercase identifier — becomes a reference + Type = 17, // PascalCase identifier — becomes a component name or reference +} + +type Token = { t: T; v?: string | number }; + +function autoClose(input: string): { text: string; wasIncomplete: boolean } { + const stack: string[] = []; + let inStr = false, + esc = false; + + for (let i = 0; i < input.length; i++) { + const c = input[i]; + + if (esc) { + esc = false; + continue; + } + if (c === "\\" && inStr) { + esc = true; + continue; + } + if (c === '"') { + inStr = !inStr; + continue; + } + if (inStr) continue; + + if (c === "(" || c === "[" || c === "{") stack.push(c); + else if (c === ")" && stack[stack.length - 1] === "(") stack.pop(); + else if (c === "]" && stack[stack.length - 1] === "[") stack.pop(); + else if (c === "}" && stack[stack.length - 1] === "{") stack.pop(); + } + + const wasIncomplete = inStr || stack.length > 0; + if (!wasIncomplete) return { text: input, wasIncomplete: false }; + + let out = input; + if (inStr) { + if (esc) out += "\\"; + out += '"'; + } // close open string + for ( + let j = stack.length - 1; + j >= 0; + j-- // close brackets in reverse + ) + out += stack[j] === "(" ? ")" : stack[j] === "[" ? "]" : "}"; + + return { text: out, wasIncomplete: true }; +} + +// lexer +function tokenize(src: string): Token[] { + const tokens: Token[] = []; + let i = 0; + const n = src.length; + + while (i < n) { + // Skip horizontal whitespace (not newlines — they're significant) + while (i < n && (src[i] === " " || src[i] === "\t" || src[i] === "\r")) i++; + if (i >= n) break; + + const c = src[i]; + + // ── Single-character punctuation ────────────────────────────────────── + if (c === "\n") { + tokens.push({ t: T.Newline }); + i++; + continue; + } + if (c === "(") { + tokens.push({ t: T.LParen }); + i++; + continue; + } + if (c === ")") { + tokens.push({ t: T.RParen }); + i++; + continue; + } + if (c === "[") { + tokens.push({ t: T.LBrack }); + i++; + continue; + } + if (c === "]") { + tokens.push({ t: T.RBrack }); + i++; + continue; + } + if (c === "{") { + tokens.push({ t: T.LBrace }); + i++; + continue; + } + if (c === "}") { + tokens.push({ t: T.RBrace }); + i++; + continue; + } + if (c === ",") { + tokens.push({ t: T.Comma }); + i++; + continue; + } + if (c === ":") { + tokens.push({ t: T.Colon }); + i++; + continue; + } + if (c === "=") { + tokens.push({ t: T.Equals }); + i++; + continue; + } + + // string literal: "..." + if (c === '"') { + const start = i; + i++; // skip opening quote + + let isClosed = false; + // Fast-forward to the closing quote, respecting escapes + while (i < n) { + if (src[i] === "\\") { + i += 2; // skip backslash and the escaped character + } else if (src[i] === '"') { + i++; // include the closing quote + isClosed = true; + break; + } else { + i++; + } + } + + const rawString = src.slice(start, i); + + try { + // Let JavaScript's native JSON parser handle all unescaping (\n, \t, \uXXXX, etc.) + // If the string is incomplete (streaming), we add a closing quote to parse what we have so far. + const validJsonString = isClosed ? rawString : rawString + '"'; + + tokens.push({ t: T.Str, v: JSON.parse(validJsonString) }); + } catch { + // Fallback if JSON.parse fails (e.g., malformed unicode escape during streaming) + // Strip the quotes and return the raw text so the UI doesn't crash + const stripped = rawString.replace(/^"|"$/g, ""); + tokens.push({ t: T.Str, v: stripped }); + } + continue; + } + + // number literal: 42, -3, 1.5 + const isDigit = c >= "0" && c <= "9"; + const isNegDigit = c === "-" && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9"; + if (isDigit || isNegDigit) { + const start = i; + if (src[i] === "-") i++; // optional minus + while (i < n && src[i] >= "0" && src[i] <= "9") i++; // integer part + if (i < n && src[i] === ".") { + // optional decimal + i++; + while (i < n && src[i] >= "0" && src[i] <= "9") i++; + } + if (i < n && (src[i] === "e" || src[i] === "E")) { + // optional exponent + i++; + if (i < n && (src[i] === "+" || src[i] === "-")) i++; + while (i < n && src[i] >= "0" && src[i] <= "9") i++; + } + tokens.push({ t: T.Num, v: +src.slice(start, i) }); + continue; + } + + // keyword or identifier + const isAlpha = (c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c === "_"; + if (isAlpha) { + const start = i; + while ( + i < n && + ((src[i] >= "a" && src[i] <= "z") || + (src[i] >= "A" && src[i] <= "Z") || + (src[i] >= "0" && src[i] <= "9") || + src[i] === "_") + ) + i++; + + const word = src.slice(start, i); + + if (word === "true") { + tokens.push({ t: T.True }); + continue; + } + if (word === "false") { + tokens.push({ t: T.False }); + continue; + } + if (word === "null") { + tokens.push({ t: T.Null }); + continue; + } + + // PascalCase → component type name; lowercase → variable reference + const kind = c >= "A" && c <= "Z" ? T.Type : T.Ident; + tokens.push({ t: kind, v: word }); + continue; + } + + i++; // skip any other character (e.g. @, #, emojis) + } + + tokens.push({ t: T.EOF }); + return tokens; +} + +interface RawStmt { + id: string; + tokens: Token[]; +} + +/** + * Splits the flat token stream into individual statements. + * + * Each statement has the form `identifier = expression`. Statements are + * separated by newlines at depth 0 (newlines inside brackets are ignored). + * + * Example input tokens for: + * `root = Root([tbl])\ntbl = Table(...)` + * + * Produces two RawStmts: + * { id: "root", tokens: [Root, (, [, tbl, ], )] } + * { id: "tbl", tokens: [Table, (, ..., )] } + * + * Invalid lines (no `=`, or no identifier) are silently skipped. + */ +function split(tokens: Token[]): RawStmt[] { + const stmts: RawStmt[] = []; + let pos = 0; + + while (pos < tokens.length) { + // Skip blank lines + while (pos < tokens.length && tokens[pos].t === T.Newline) pos++; + if (pos >= tokens.length || tokens[pos].t === T.EOF) break; + + // Expect: Ident|Type = expression + const tok = tokens[pos]; + if (tok.t !== T.Ident && tok.t !== T.Type) { + while (pos < tokens.length && tokens[pos].t !== T.Newline && tokens[pos].t !== T.EOF) pos++; + continue; + } + const id = tok.v as string; + pos++; + + // Must be followed by `=` + if (pos >= tokens.length || tokens[pos].t !== T.Equals) { + while (pos < tokens.length && tokens[pos].t !== T.Newline && tokens[pos].t !== T.EOF) pos++; + continue; + } + pos++; + + // Collect expression tokens until a depth-0 newline or EOF + const expr: Token[] = []; + let depth = 0; + while (pos < tokens.length && tokens[pos].t !== T.EOF) { + const tt = tokens[pos].t; + if (tt === T.Newline && depth <= 0) break; // statement boundary + if (tt === T.Newline) { + pos++; + continue; + } // newline inside bracket — skip + if (tt === T.LParen || tt === T.LBrack || tt === T.LBrace) depth++; + else if (tt === T.RParen || tt === T.RBrack || tt === T.RBrace) depth--; + expr.push(tokens[pos++]); + } + + if (expr.length) stmts.push({ id, tokens: expr }); + } + + return stmts; +} + +function parseTokens(tokens: Token[]): ASTNode { + let pos = 0; + const cur = (): Token => tokens[pos] ?? { t: T.EOF }; + const adv = () => pos++; + const eat = (kind: T) => { + if (cur().t === kind) adv(); + }; + + function parseExpr(): ASTNode { + const tok = cur(); + + if (tok.t === T.Type) { + // PascalCase followed by `(` → component call; otherwise a reference + return tokens[pos + 1]?.t === T.LParen + ? parseComp() + : (adv(), { k: "Ref", n: tok.v as string }); + } + if (tok.t === T.Str) { + adv(); + return { k: "Str", v: tok.v as string }; + } + if (tok.t === T.Num) { + adv(); + return { k: "Num", v: tok.v as number }; + } + if (tok.t === T.True) { + adv(); + return { k: "Bool", v: true }; + } + if (tok.t === T.False) { + adv(); + return { k: "Bool", v: false }; + } + if (tok.t === T.Null) { + adv(); + return { k: "Null" }; + } + if (tok.t === T.LBrack) return parseArr(); + if (tok.t === T.LBrace) return parseObj(); + if (tok.t === T.Ident) { + adv(); + return { k: "Ref", n: tok.v as string }; + } + + adv(); + return { k: "Null" }; // unknown token — treat as null + } + + /** Parse `TypeName(arg1, arg2, ...)` */ + function parseComp(): ASTNode { + const name = cur().v as string; + adv(); + eat(T.LParen); + const args: ASTNode[] = []; + while (cur().t !== T.RParen && cur().t !== T.EOF) { + args.push(parseExpr()); + if (cur().t === T.Comma) adv(); + } + eat(T.RParen); + return { k: "Comp", name, args }; + } + + /** Parse `[elem1, elem2, ...]` */ + function parseArr(): ASTNode { + adv(); // skip [ + const els: ASTNode[] = []; + while (cur().t !== T.RBrack && cur().t !== T.EOF) { + els.push(parseExpr()); + if (cur().t === T.Comma) adv(); + } + eat(T.RBrack); + return { k: "Arr", els }; + } + + /** Parse `{ key: value, ... }` */ + function parseObj(): ASTNode { + adv(); // skip { + const entries: [string, ASTNode][] = []; + while (cur().t !== T.RBrace && cur().t !== T.EOF) { + const kt = cur(); + const key = + kt.t === T.Ident || kt.t === T.Str || kt.t === T.Type || kt.t === T.Num + ? (adv(), String(kt.v)) + : (adv(), "?"); + eat(T.Colon); + entries.push([key, parseExpr()]); + if (cur().t === T.Comma) adv(); + } + eat(T.RBrace); + return { k: "Obj", entries }; + } + + return parseExpr(); +} + +function resolveNode( + node: ASTNode, + syms: Map, + unres: string[], + visited: Set, +): ASTNode { + if (node.k === "Ref") { + const { n } = node; + if (visited.has(n)) { + unres.push(n); + return { k: "Ph", n }; + } // cycle + if (!syms.has(n)) { + unres.push(n); + return { k: "Ph", n }; + } // missing + + visited.add(n); + const resolved = resolveNode(syms.get(n)!, syms, unres, visited); + visited.delete(n); + return resolved; + } + + if (node.k === "Comp") + return { + ...node, + args: node.args.map((a) => resolveNode(a, syms, unres, visited)), + }; + if (node.k === "Arr") + return { + ...node, + els: node.els.map((e) => resolveNode(e, syms, unres, visited)), + }; + if (node.k === "Obj") + return { + ...node, + entries: node.entries.map(([k, v]) => [k, resolveNode(v, syms, unres, visited)]), + }; + + // Literals and placeholders pass through unchanged + return node; +} + +type JsonVal = string | number | boolean | null | JsonVal[] | { [k: string]: JsonVal }; + +function toJson( + node: ASTNode, + partial: boolean, + errors: ParseResult["meta"]["validationErrors"], + cat: ParamMap | undefined, +): JsonVal { + if (node.k === "Str") return node.v; + if (node.k === "Num") return node.v; + if (node.k === "Bool") return node.v; + if (node.k === "Null") return null; + if (node.k === "Arr") { + const items: JsonVal[] = []; + for (const e of node.els) { + // Drop unresolved references from arrays to avoid null entries like [null, element] + if (e.k === "Ph") continue; + const value = toJson(e, partial, errors, cat); + // Drop invalid component entries from arrays (e.g. incomplete required props while streaming) + if (e.k === "Comp" && value === null) continue; + items.push(value); + } + return items; + } + if (node.k === "Obj") { + const o: { [k: string]: JsonVal } = {}; + for (const [k, v] of node.entries) o[k] = toJson(v, partial, errors, cat); + return o; + } + if (node.k === "Comp") return mapNode(node, partial, errors, cat) as unknown as JsonVal; + if (node.k === "Ph") return null; + return null; +} + +function mapNode( + node: ASTNode, + partial: boolean, + errors: ParseResult["meta"]["validationErrors"], + cat: ParamMap | undefined, +): ParseResult["root"] { + if (node.k === "Ph") return null; + if (node.k !== "Comp") return null; + + const { name, args } = node; + const def = cat?.get(name); + const props: { [k: string]: JsonVal } = {}; + + if (def) { + // Map positional args → named props using library param order + for (let i = 0; i < def.params.length && i < args.length; i++) + props[def.params[i].name] = toJson(args[i], partial, errors, cat); + + // Validate required props — try defaultValue first before dropping + const missingRequired = def.params.filter( + (p) => p.required && (!(p.name in props) || props[p.name] === null), + ); + if (missingRequired.length) { + const stillInvalid = missingRequired.filter((p) => { + if (p.defaultValue !== undefined) { + props[p.name] = p.defaultValue as JsonVal; + return false; + } + return true; + }); + if (stillInvalid.length) { + for (const p of stillInvalid) + errors.push({ + component: name, + path: `/${p.name}`, + message: + p.name in props + ? `required field "${p.name}" cannot be null` + : `missing required field "${p.name}"`, + }); + return null; + } + } + } else { + // No library entry for this component — preserve all args under _args + props._args = args.map((a) => toJson(a, partial, errors, cat)); + } + + return { type: "element", typeName: name, props, partial }; +} + +function emptyResult(incomplete = true): ParseResult { + return { + root: null, + meta: { + incomplete, + unresolved: [], + statementCount: 0, + validationErrors: [], + }, + }; +} + +function buildResult( + syms: Map, + firstId: string, + wasIncomplete: boolean, + stmtCount: number, + cat: ParamMap | undefined, +): ParseResult { + if (!syms.has(firstId)) return emptyResult(wasIncomplete); + + const unres: string[] = []; + const resolved = resolveNode(syms.get(firstId)!, syms, unres, new Set()); + const errors: ParseResult["meta"]["validationErrors"] = []; + const root = mapNode(resolved, wasIncomplete, errors, cat); + + return { + root, + meta: { + incomplete: wasIncomplete, + unresolved: unres, + statementCount: stmtCount, + validationErrors: errors, + }, + }; +} + +/** + * Parse a complete openui-lang string in one pass. + * + * @param input - Full openui-lang source text (may be partial/streaming) + * @param cat - Optional param map for positional-arg → named-prop mapping + * @returns ParseResult with root ElementNode (or null) and metadata + */ +export function parse(input: string, cat?: ParamMap): ParseResult { + const trimmed = input.trim(); + if (!trimmed) return emptyResult(); + + const { text, wasIncomplete } = autoClose(trimmed); + const stmts = split(tokenize(text)); + if (!stmts.length) return emptyResult(wasIncomplete); + + const syms = new Map(); + let firstId = ""; + for (const s of stmts) { + syms.set(s.id, parseTokens(s.tokens)); + if (!firstId) firstId = s.id; + } + + return buildResult(syms, firstId, wasIncomplete, stmts.length, cat); +} + +export interface StreamParser { + /** Feed the next SSE/stream chunk and get the latest ParseResult. */ + push(chunk: string): ParseResult; + /** Get the latest ParseResult without consuming new data. */ + getResult(): ParseResult; +} + +export function createStreamParser(cat?: ParamMap): StreamParser { + let buf = ""; + let completedEnd = 0; + const completedSyms = new Map(); + + let completedCount = 0; + let firstId = ""; + + function addStmt(text: string) { + for (const s of split(tokenize(text))) { + completedSyms.set(s.id, parseTokens(s.tokens)); + completedCount++; + if (!firstId) firstId = s.id; + } + } + + function scanNewCompleted(): number { + let depth = 0, + inStr = false, + esc = false; + let stmtStart = completedEnd; + + for (let i = completedEnd; i < buf.length; i++) { + const c = buf[i]; + if (esc) { + esc = false; + continue; + } + if (c === "\\" && inStr) { + esc = true; + continue; + } + if (c === '"') { + inStr = !inStr; + continue; + } + if (inStr) continue; + + if (c === "(" || c === "[" || c === "{") depth++; + else if (c === ")" || c === "]" || c === "}") depth--; + else if (c === "\n" && depth <= 0) { + // Depth-0 newline = end of a statement + const t = buf.slice(stmtStart, i).trim(); + if (t) addStmt(t); + stmtStart = i + 1; // next statement begins after this newline + completedEnd = i + 1; // advance the "already processed" watermark + } + } + + return stmtStart; // start of the current pending (incomplete) statement + } + + function currentResult(): ParseResult { + const pendingStart = scanNewCompleted(); + const pendingText = buf.slice(pendingStart).trim(); + + // No pending text — all statements are complete + if (!pendingText) { + if (completedCount === 0) return emptyResult(); + return buildResult(completedSyms, firstId, false, completedCount, cat); + } + + // Autoclose the incomplete last statement so it's syntactically valid + const { text: closed, wasIncomplete } = autoClose(pendingText); + const stmts = split(tokenize(closed)); + + if (!stmts.length) { + if (completedCount === 0) return emptyResult(wasIncomplete); + return buildResult(completedSyms, firstId, wasIncomplete, completedCount, cat); + } + + // Merge: completed cache + re-parsed pending statement + // (Map spread is cheap since completedSyms only grows by one entry at a time) + const allSyms = new Map(completedSyms); + for (const s of stmts) allSyms.set(s.id, parseTokens(s.tokens)); + + const fid = firstId || stmts[0].id; + return buildResult(allSyms, fid, wasIncomplete, completedCount + stmts.length, cat); + } + + return { + push(chunk) { + buf += chunk; + return currentResult(); + }, + getResult: currentResult, + }; +} + +export interface Parser { + parse(input: string): ParseResult; +} + +function compileSchema(schema: LibraryJSONSchema): ParamMap { + const map: ParamMap = new Map(); + const defs = schema.$defs ?? {}; + + for (const [name, def] of Object.entries(defs)) { + const properties = def.properties ?? {}; + const required = def.required ?? []; + const params = Object.keys(properties).map((k) => ({ + name: k, + required: required.includes(k), + defaultValue: (properties[k] as any)?.default, + })); + map.set(name, { params }); + } + + return map; +} + +/** + * Create a parser from a library JSON Schema document. + * Pass `library.toJSONSchema()` to get the schema. + * + * @example + * ```ts + * const parser = createParser(library.toJSONSchema()); + * const result = parser.parse(openuiLangString); + * ``` + */ +export function createParser(schema: LibraryJSONSchema): Parser { + const paramMap = compileSchema(schema); + return { + parse(input: string): ParseResult { + return parse(input, paramMap); + }, + }; +} + +/** + * Create a streaming parser from a library JSON Schema document. + * Pass `library.toJSONSchema()` to get the schema. + */ +export function createStreamingParser(schema: LibraryJSONSchema): StreamParser { + return createStreamParser(compileSchema(schema)); +} diff --git a/packages/lang-core/src/parser/prompt.ts b/packages/lang-core/src/parser/prompt.ts new file mode 100644 index 000000000..11119d678 --- /dev/null +++ b/packages/lang-core/src/parser/prompt.ts @@ -0,0 +1,336 @@ +import { z } from "zod"; +import type { DefinedComponent, Library, PromptOptions } from "../library"; + +const PREAMBLE = `You are an AI assistant that responds using openui-lang, a declarative UI language. Your ENTIRE response must be valid openui-lang code — no markdown, no explanations, just openui-lang.`; + +function syntaxRules(rootName: string): string { + return `## Syntax Rules + +1. Each statement is on its own line: \`identifier = Expression\` +2. \`root\` is the entry point — every program must define \`root = ${rootName}(...)\` +3. Expressions are: strings ("..."), numbers, booleans (true/false), arrays ([...]), objects ({...}), or component calls TypeName(arg1, arg2, ...) +4. Use references for readability: define \`name = ...\` on one line, then use \`name\` later +5. EVERY variable (except root) MUST be referenced by at least one other variable. Unreferenced variables are silently dropped and will NOT render. Always include defined variables in their parent's children/items array. +6. Arguments are POSITIONAL (order matters, not names) +7. Optional arguments can be omitted from the end +8. No operators, no logic, no variables — only declarations +9. Strings use double quotes with backslash escaping`; +} + +function streamingRules(rootName: string): string { + return `## Hoisting & Streaming (CRITICAL) + +openui-lang supports hoisting: a reference can be used BEFORE it is defined. The parser resolves all references after the full input is parsed. + +During streaming, the output is re-parsed on every chunk. Undefined references are temporarily unresolved and appear once their definitions stream in. This creates a progressive top-down reveal — structure first, then data fills in. + +**Recommended statement order for optimal streaming:** +1. \`root = ${rootName}(...)\` — UI shell appears immediately +2. Component definitions — fill in as they stream +3. Data values — leaf content last + +Always write the root = ${rootName}(...) statement first so the UI shell appears immediately, even before child data has streamed in.`; +} + +function importantRules(rootName: string): string { + return `## Important Rules +- ALWAYS start with root = ${rootName}(...) +- Write statements in TOP-DOWN order: root → components → data (leverages hoisting for progressive streaming) +- Each statement on its own line +- No trailing text or explanations — output ONLY openui-lang code +- When asked about data, generate realistic/plausible data +- Choose components that best represent the content (tables for comparisons, charts for trends, forms for input, etc.) +- NEVER define a variable without referencing it from the tree. Every variable must be reachable from root, otherwise it will not render.`; +} + +function getZodDef(schema: unknown): any { + return (schema as any)?._zod?.def; +} + +function getZodType(schema: unknown): string | undefined { + return getZodDef(schema)?.type; +} + +function isOptionalType(schema: unknown): boolean { + return getZodType(schema) === "optional"; +} + +function unwrapOptional(schema: unknown): unknown { + const def = getZodDef(schema); + if (def?.type === "optional") return def.innerType; + return schema; +} + +/** Strip optional wrapper to reach the core schema. */ +function unwrap(schema: unknown): unknown { + return unwrapOptional(schema); +} + +function isArrayType(schema: unknown): boolean { + const s = unwrap(schema); + return getZodType(s) === "array"; +} + +function getArrayInnerType(schema: unknown): unknown | undefined { + const s = unwrap(schema); + const def = getZodDef(s); + if (def?.type === "array") return def.element ?? def.innerType; + return undefined; +} + +function getEnumValues(schema: unknown): string[] | undefined { + const s = unwrap(schema); + const def = getZodDef(s); + if (def?.type !== "enum") return undefined; + if (Array.isArray(def.values)) return def.values; + if (def.entries && typeof def.entries === "object") return Object.keys(def.entries); + return undefined; +} + +function getSchemaId(schema: unknown): string | undefined { + try { + const meta = z.globalRegistry.get(schema as z.ZodType); + return meta?.id; + } catch { + return undefined; + } +} + +function getUnionOptions(schema: unknown): unknown[] | undefined { + const def = getZodDef(schema); + if (def?.type === "union" && Array.isArray(def.options)) return def.options; + return undefined; +} + +function getObjectShape(schema: unknown): Record | undefined { + const def = getZodDef(schema); + if (def?.type === "object" && def.shape && typeof def.shape === "object") + return def.shape as Record; + return undefined; +} + +/** + * Resolve the type annotation for a schema field. + * Returns a human-readable type string for the schema. + * + * Examples: + * - z.string() → "string" + * - z.number() → "number" + * - z.boolean() → "boolean" + * - z.enum(["a","b"]) → '"a" | "b"' + * - z.array(TabItemSchema) → "TabItem[]" + * - z.union([Input, TextArea]) → "Input | TextArea" + * - z.array(z.union([A, B])) → "(A | B)[]" + * - ButtonGroupSchema → "ButtonGroup" + * - z.object({src: z.string()}) → "{src: string}" (inline when unregistered) + */ +function resolveTypeAnnotation(schema: unknown): string | undefined { + const inner = unwrap(schema); + + const directId = getSchemaId(inner); + if (directId) return directId; + + const unionOpts = getUnionOptions(inner); + if (unionOpts) { + const resolved = unionOpts.map((o) => resolveTypeAnnotation(o)); + const names = resolved.filter(Boolean) as string[]; + if (names.length > 0) { + if (names.length < unionOpts.length) { + console.warn( + `[prompt] Partially resolved union: ${names.length}/${unionOpts.length} options resolved`, + ); + } + return names.join(" | "); + } + } + + if (isArrayType(schema)) { + const arrayInner = getArrayInnerType(schema); + if (!arrayInner) return undefined; + + const innerType = resolveTypeAnnotation(arrayInner); + if (innerType) { + const isUnion = getUnionOptions(unwrap(arrayInner)) !== undefined; + return isUnion ? `(${innerType})[]` : `${innerType}[]`; + } + + console.warn( + `[prompt] Could not resolve array element type (inner zod type: "${getZodType(arrayInner) ?? "unknown"}")`, + ); + return undefined; + } + + const zodType = getZodType(inner); + if (zodType === "string") return "string"; + if (zodType === "number") return "number"; + if (zodType === "boolean") return "boolean"; + + const enumVals = getEnumValues(inner); + if (enumVals) return enumVals.map((v) => `"${v}"`).join(" | "); + + if (zodType === "literal") { + const vals = getZodDef(inner)?.values; + if (Array.isArray(vals) && vals.length === 1) { + const v = vals[0]; + return typeof v === "string" ? `"${v}"` : String(v); + } + } + + const shape = getObjectShape(inner); + if (shape) { + const fields = Object.entries(shape).map(([name, fieldSchema]) => { + const opt = isOptionalType(fieldSchema) ? "?" : ""; + const fieldType = resolveTypeAnnotation(fieldSchema as z.ZodType); + return fieldType ? `${name}${opt}: ${fieldType}` : `${name}${opt}`; + }); + return `{${fields.join(", ")}}`; + } + + if (zodType === "lazy") { + console.warn( + `[prompt] z.lazy() schemas are not resolved — remove z.lazy() wrapper from the schema`, + ); + } else if (zodType) { + console.warn(`[prompt] Unresolved schema type: "${zodType}"`); + } + + return undefined; +} + +// ─── Field analysis ─── + +interface FieldInfo { + name: string; + isOptional: boolean; + isArray: boolean; + typeAnnotation?: string; +} + +function analyzeFields(shape: Record): FieldInfo[] { + return Object.entries(shape).map(([name, schema]) => ({ + name, + isOptional: isOptionalType(schema), + isArray: isArrayType(schema), + typeAnnotation: resolveTypeAnnotation(schema), + })); +} + +// ─── Signature generation ─── + +function buildSignature(componentName: string, fields: FieldInfo[]): string { + const params = fields.map((f) => { + if (f.typeAnnotation) { + return f.isOptional ? `${f.name}?: ${f.typeAnnotation}` : `${f.name}: ${f.typeAnnotation}`; + } + if (f.isArray) { + return f.isOptional ? `[${f.name}]?` : `[${f.name}]`; + } + return f.isOptional ? `${f.name}?` : f.name; + }); + return `${componentName}(${params.join(", ")})`; +} + +function buildComponentLine(componentName: string, def: DefinedComponent): string { + const fields = analyzeFields(def.props.shape); + const sig = buildSignature(componentName, fields); + if (def.description) { + return `${sig} — ${def.description}`; + } + return sig; +} + +// ─── Prompt assembly ─── + +function generateComponentSignatures(library: Library): string { + const lines: string[] = [ + "## Component Signatures", + "", + "Arguments marked with ? are optional. Sub-components can be inline or referenced; prefer references for better streaming.", + "The `action` prop type accepts: ContinueConversation (sends message to LLM), OpenUrl (navigates to URL), or Custom (app-defined).", + ]; + + if (library.componentGroups?.length) { + const groupedComponents = new Set(); + + for (const group of library.componentGroups) { + lines.push(""); + lines.push(`### ${group.name}`); + for (const name of group.components) { + if (groupedComponents.has(name)) { + console.warn( + `[prompt] Component "${name}" appears in multiple groups; keeping the first occurrence only.`, + ); + continue; + } + const def = library.components[name]; + if (!def) { + console.warn( + `[prompt] Component "${name}" listed in group "${group.name}" was not found in the library and will be omitted from the prompt.`, + ); + continue; + } + groupedComponents.add(name); + lines.push(buildComponentLine(name, def)); + } + if (group.notes?.length) { + for (const note of group.notes) { + lines.push(note); + } + } + } + + const ungrouped = Object.keys(library.components).filter( + (name) => !groupedComponents.has(name), + ); + if (ungrouped.length) { + lines.push(""); + lines.push("### Ungrouped"); + for (const name of ungrouped) { + const def = library.components[name]; + lines.push(buildComponentLine(name, def)); + } + } + } else { + lines.push(""); + for (const [name, def] of Object.entries(library.components)) { + lines.push(buildComponentLine(name, def)); + } + } + + return lines.join("\n"); +} + +export function generatePrompt(library: Library, options?: PromptOptions): string { + const rootName = library.root ?? "Root"; + const parts: string[] = []; + + parts.push(options?.preamble ?? PREAMBLE); + parts.push(""); + parts.push(syntaxRules(rootName)); + parts.push(""); + parts.push(generateComponentSignatures(library)); + parts.push(""); + parts.push(streamingRules(rootName)); + + const examples = options?.examples; + if (examples?.length) { + parts.push(""); + parts.push("## Examples"); + parts.push(""); + for (const ex of examples) { + parts.push(ex); + parts.push(""); + } + } + + parts.push(importantRules(rootName)); + + if (options?.additionalRules?.length) { + parts.push(""); + for (const rule of options.additionalRules) { + parts.push(`- ${rule}`); + } + } + + return parts.join("\n"); +} diff --git a/packages/lang-core/src/parser/types.ts b/packages/lang-core/src/parser/types.ts new file mode 100644 index 000000000..b3754d0f6 --- /dev/null +++ b/packages/lang-core/src/parser/types.ts @@ -0,0 +1,82 @@ +/** + * A fully resolved component node from the parser. + * + * The parser converts openui-lang text into a tree of these nodes. + * Each node represents one component invocation with its positional + * arguments mapped into named `props` via the library's Zod key order. + */ +export interface ElementNode { + type: "element"; + /** Component name as defined in the library (e.g. "Table", "BarChart"). */ + typeName: string; + /** Named props produced by positional-to-named mapping in the Rust parser. */ + props: Record; + /** + * True when the parser hasn't received all tokens for this node yet + * (streaming in progress). + */ + partial: boolean; +} + +/** + * A prop validation error from the Rust parser. + * When a component has missing required props, it is redacted from the + * output tree (dropped as null) and errors are recorded here. + */ +export interface ValidationError { + /** Component type name, e.g. "Header", "BarChart". */ + component: string; + /** JSON Pointer path within the props object, e.g. "/title", "". */ + path: string; + /** Human-readable error message. */ + message: string; +} + +/** + * Built-in action types for interactive components. + */ +export enum BuiltinActionType { + ContinueConversation = "continue_conversation", + OpenUrl = "open_url", +} + +/** + * Structured action event fired by interactive components. + */ +export interface ActionEvent { + /** Action type. See `BuiltinActionType` for built-in types. */ + type: string; + /** Action-specific params (e.g. { url } for OpenUrl, custom params for Custom). */ + params: Record; + /** Human-readable label for the action (displayed as user message in chat). */ + humanFriendlyMessage: string; + /** Raw form state at the time of the action — all field values. */ + formState?: Record; + /** The form name that triggered this action, if any. */ + formName?: string; +} + +/** + * The output of a single `parser.parse(text)` call. + * + * During streaming, each chunk produces a new ParseResult as the + * accumulated text is re-parsed. The `root` progressively resolves + * from null → partial tree → complete tree. + */ +export interface ParseResult { + /** The root ElementNode (typically a Root component), or null if parsing hasn't produced one yet. */ + root: ElementNode | null; + meta: { + /** True if the parser detected truncated/incomplete input. */ + incomplete: boolean; + /** Names of references used but not yet defined (dropped as null in output). */ + unresolved: string[]; + /** Total number of `identifier = Expression` statements parsed. */ + statementCount: number; + /** + * Prop validation errors. Components with missing required props are + * redacted (dropped as null) and listed here. + */ + validationErrors: ValidationError[]; + }; +} diff --git a/packages/lang-core/src/utils/validation.ts b/packages/lang-core/src/utils/validation.ts new file mode 100644 index 000000000..0902e9f79 --- /dev/null +++ b/packages/lang-core/src/utils/validation.ts @@ -0,0 +1,150 @@ +export interface ParsedRule { + type: string; + arg?: number | string; +} + +/** + * Parse a rule string into a structured rule. + * "required" → { type: "required" } + * "min:8" → { type: "min", arg: 8 } + * "minLength:3" → { type: "minLength", arg: 3 } + * "pattern:^[a-z]" → { type: "pattern", arg: "^[a-z]" } + */ +export function parseRule(rule: string): ParsedRule { + const colonIdx = rule.indexOf(":"); + if (colonIdx === -1) return { type: rule }; + + const type = rule.slice(0, colonIdx); + const rawArg = rule.slice(colonIdx + 1); + const num = Number(rawArg); + return { type, arg: Number.isFinite(num) && rawArg !== "" ? num : rawArg }; +} + +export function parseRules(rules: unknown): ParsedRule[] { + if (!Array.isArray(rules)) return []; + return rules.filter((r): r is string => typeof r === "string").map(parseRule); +} + +export type ValidatorFn = (value: unknown, arg?: number | string) => string | undefined; + +function isEmpty(value: unknown): boolean { + if (value === null || value === undefined || value === "") return true; + if (Array.isArray(value) && value.length === 0) return true; + return false; +} + +export const builtInValidators: Record = { + required: (value) => { + if (isEmpty(value)) return "This field is required"; + if (typeof value === "object" && !Array.isArray(value) && value !== null) { + const vals = Object.values(value); + if (vals.length > 0 && vals.every((v) => typeof v === "boolean") && !vals.some(Boolean)) { + return "At least one option is required"; + } + } + return undefined; + }, + + email: (value) => { + if (isEmpty(value)) return undefined; + if (typeof value !== "string") return "Please enter a valid email"; + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? undefined : "Please enter a valid email"; + }, + + url: (value) => { + if (isEmpty(value)) return undefined; + if (typeof value !== "string") return "Please enter a valid URL"; + try { + new URL(value); + return undefined; + } catch { + return "Please enter a valid URL"; + } + }, + + numeric: (value) => { + if (isEmpty(value)) return undefined; + if (typeof value === "number" && !isNaN(value)) return undefined; + if (typeof value === "string" && !isNaN(parseFloat(value)) && value.trim() !== "") + return undefined; + return "Must be a number"; + }, + + min: (value, arg) => { + if (isEmpty(value)) return undefined; + const n = typeof value === "number" ? value : parseFloat(String(value)); + if (isNaN(n)) return undefined; + const min = Number(arg); + return n >= min ? undefined : `Must be at least ${min}`; + }, + + max: (value, arg) => { + if (isEmpty(value)) return undefined; + const n = typeof value === "number" ? value : parseFloat(String(value)); + if (isNaN(n)) return undefined; + const max = Number(arg); + return n <= max ? undefined : `Must be no more than ${max}`; + }, + + minLength: (value, arg) => { + if (isEmpty(value)) return undefined; + if (typeof value !== "string") return undefined; + const min = Number(arg); + return value.length >= min ? undefined : `Must be at least ${min} characters`; + }, + + maxLength: (value, arg) => { + if (isEmpty(value)) return undefined; + if (typeof value !== "string") return undefined; + const max = Number(arg); + return value.length <= max ? undefined : `Must be no more than ${max} characters`; + }, + + pattern: (value, arg) => { + if (isEmpty(value)) return undefined; + if (typeof value !== "string" || typeof arg !== "string") return undefined; + try { + return new RegExp(arg).test(value) ? undefined : "Invalid format"; + } catch { + return undefined; + } + }, +}; + +/** + * Run all rules against a value. Stop on first error. + * Custom validators are checked first, then built-in ones. + */ +export function validate( + value: unknown, + rules: ParsedRule[], + customValidators?: Record, +): string | undefined { + for (const rule of rules) { + const validator = customValidators?.[rule.type] ?? builtInValidators[rule.type]; + if (!validator) continue; + const error = validator(value, rule.arg); + if (error) return error; + } + return undefined; +} + +/** + * Parse a structured rules object into ParsedRule[]. + * Accepts: { required: true, minLength: 5, email: true, max: 100 } + * Skips keys with false/undefined values. + */ +export function parseStructuredRules(rules: unknown): ParsedRule[] { + if (!rules || typeof rules !== "object" || Array.isArray(rules)) return []; + const obj = rules as Record; + const result: ParsedRule[] = []; + for (const [key, val] of Object.entries(obj)) { + if (val === false || val === undefined || val === null) continue; + if (val === true) { + result.push({ type: key }); + } else { + result.push({ type: key, arg: val }); + } + } + return result; +} diff --git a/packages/lang-core/tsconfig.json b/packages/lang-core/tsconfig.json new file mode 100644 index 000000000..00124d5b9 --- /dev/null +++ b/packages/lang-core/tsconfig.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../tsconfig.json", + "include": ["src/**/*"], + "exclude": ["src/**/__tests__/**", "src/**/*.test.ts"], + "compilerOptions": { + "moduleResolution": "bundler", + "module": "ESNext", + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "noPropertyAccessFromIndexSignature": false, + "noUncheckedIndexedAccess": false, + "noImplicitReturns": false, + "noImplicitOverride": false + } +} From a780a3951d6cbba38fc6b7cd6917407e089b941b Mon Sep 17 00:00:00 2001 From: shipooor Date: Mon, 16 Mar 2026 17:01:32 +0500 Subject: [PATCH 02/12] Update react-lang to use lang-core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace local parser, prompt, and validation code with imports from @openuidev/lang-core. The library.ts becomes a thin wrapper that narrows the generic C parameter to React.FC. Public API is unchanged — all existing examples build without modification. --- packages/react-lang/package.json | 1 + packages/react-lang/src/Renderer.tsx | 2 +- .../react-lang/src/hooks/useFormValidation.ts | 2 +- .../react-lang/src/hooks/useOpenUIState.ts | 4 +- packages/react-lang/src/index.ts | 10 +- packages/react-lang/src/library.ts | 138 +-- packages/react-lang/src/parser/index.ts | 7 - packages/react-lang/src/parser/parser.ts | 787 ------------ packages/react-lang/src/parser/prompt.ts | 336 ------ packages/react-lang/src/parser/types.ts | 82 -- packages/react-lang/src/utils/index.ts | 2 - packages/react-lang/src/utils/validation.ts | 150 --- pnpm-lock.yaml | 1063 +++++++++++++++-- 13 files changed, 982 insertions(+), 1602 deletions(-) delete mode 100644 packages/react-lang/src/parser/index.ts delete mode 100644 packages/react-lang/src/parser/parser.ts delete mode 100644 packages/react-lang/src/parser/prompt.ts delete mode 100644 packages/react-lang/src/parser/types.ts delete mode 100644 packages/react-lang/src/utils/index.ts delete mode 100644 packages/react-lang/src/utils/validation.ts diff --git a/packages/react-lang/package.json b/packages/react-lang/package.json index 53434cae6..20a25e835 100644 --- a/packages/react-lang/package.json +++ b/packages/react-lang/package.json @@ -54,6 +54,7 @@ }, "author": "engineering@thesys.dev", "dependencies": { + "@openuidev/lang-core": "workspace:^", "zod": "^4.0.0" }, "peerDependencies": { diff --git a/packages/react-lang/src/Renderer.tsx b/packages/react-lang/src/Renderer.tsx index ef5997e48..4be5a2b52 100644 --- a/packages/react-lang/src/Renderer.tsx +++ b/packages/react-lang/src/Renderer.tsx @@ -1,8 +1,8 @@ import React, { Component, Fragment, useEffect } from "react"; +import type { ActionEvent, ElementNode, ParseResult } from "@openuidev/lang-core"; import { OpenUIContext, useOpenUI, useRenderNode } from "./context"; import { useOpenUIState } from "./hooks/useOpenUIState"; import type { ComponentRenderer, Library } from "./library"; -import type { ActionEvent, ElementNode, ParseResult } from "./parser/types"; export interface RendererProps { /** Raw response text (openui-lang code). */ diff --git a/packages/react-lang/src/hooks/useFormValidation.ts b/packages/react-lang/src/hooks/useFormValidation.ts index 6b8f566b5..e0e4feb31 100644 --- a/packages/react-lang/src/hooks/useFormValidation.ts +++ b/packages/react-lang/src/hooks/useFormValidation.ts @@ -1,5 +1,5 @@ import { createContext, useCallback, useContext, useMemo, useRef, useState } from "react"; -import { validate, type ParsedRule } from "../utils/validation"; +import { validate, type ParsedRule } from "@openuidev/lang-core"; export interface FormValidationContextValue { errors: Record; diff --git a/packages/react-lang/src/hooks/useOpenUIState.ts b/packages/react-lang/src/hooks/useOpenUIState.ts index 59099fa9a..09802a440 100644 --- a/packages/react-lang/src/hooks/useOpenUIState.ts +++ b/packages/react-lang/src/hooks/useOpenUIState.ts @@ -1,10 +1,8 @@ import type React from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { BuiltinActionType, createParser, type ActionEvent, type ParseResult } from "@openuidev/lang-core"; import type { OpenUIContextValue } from "../context"; import type { Library } from "../library"; -import { createParser } from "../parser/parser"; -import type { ActionEvent, ParseResult } from "../parser/types"; -import { BuiltinActionType } from "../parser/types"; export interface UseOpenUIStateOptions { response: string | null; diff --git a/packages/react-lang/src/index.ts b/packages/react-lang/src/index.ts index 662d02475..978e81a2c 100644 --- a/packages/react-lang/src/index.ts +++ b/packages/react-lang/src/index.ts @@ -16,11 +16,11 @@ export { Renderer } from "./Renderer"; export type { RendererProps } from "./Renderer"; // openui-lang action types -export { BuiltinActionType } from "./parser/types"; -export type { ActionEvent, ElementNode, ParseResult } from "./parser/types"; +export { BuiltinActionType } from "@openuidev/lang-core"; +export type { ActionEvent, ElementNode, ParseResult } from "@openuidev/lang-core"; // openui-lang parser (server-side use) -export { createParser, createStreamingParser, type LibraryJSONSchema } from "./parser"; +export { createParser, createStreamingParser, type LibraryJSONSchema } from "@openuidev/lang-core"; // openui-lang context hooks (for use inside component renderers) export { @@ -42,5 +42,5 @@ export { } from "./hooks/useFormValidation"; export type { FormValidationContextValue } from "./hooks/useFormValidation"; -export { builtInValidators, parseRules, parseStructuredRules, validate } from "./utils/validation"; -export type { ParsedRule, ValidatorFn } from "./utils/validation"; +export { builtInValidators, parseRules, parseStructuredRules, validate } from "@openuidev/lang-core"; +export type { ParsedRule, ValidatorFn } from "@openuidev/lang-core"; diff --git a/packages/react-lang/src/library.ts b/packages/react-lang/src/library.ts index 7c877adb7..bedda9ff2 100644 --- a/packages/react-lang/src/library.ts +++ b/packages/react-lang/src/library.ts @@ -1,42 +1,33 @@ import type { ReactNode } from "react"; import { z } from "zod"; -import { generatePrompt } from "./parser/prompt"; +import { + createLibrary as coreCreateLibrary, + defineComponent as coreDefineComponent, + type ComponentRenderProps as CoreRenderProps, + type DefinedComponent as CoreDefinedComponent, + type Library as CoreLibrary, + type LibraryDefinition as CoreLibraryDefinition, +} from "@openuidev/lang-core"; -// ─── Sub-component type ────────────────────────────────────────────────────── +// Re-export framework-agnostic types unchanged +export type { ComponentGroup, PromptOptions, SubComponentOf } from "@openuidev/lang-core"; -/** - * Runtime shape of a parsed sub-component element as seen by parent renderers. - */ -export type SubComponentOf

= { - type: "element"; - typeName: string; - props: P; - partial: boolean; -}; +// ─── React-specific types ─────────────────────────────────────────────────── -// ─── Renderer types ─────────────────────────────────────────────────────────── - -export interface ComponentRenderProps

> { - props: P; - renderNode: (value: unknown) => ReactNode; -} +export interface ComponentRenderProps

> extends CoreRenderProps {} export type ComponentRenderer

> = React.FC>; -// ─── DefinedComponent ───────────────────────────────────────────────────────── +export type DefinedComponent = z.ZodObject> = CoreDefinedComponent< + T, + ComponentRenderer> +>; -/** - * A fully defined component with name, schema, description, renderer, - * and a `.ref` for type-safe cross-referencing in parent schemas. - */ -export interface DefinedComponent = z.ZodObject> { - name: string; - props: T; - description: string; - component: ComponentRenderer>; - /** Use in parent schemas: `z.array(ChildComponent.ref)` */ - ref: z.ZodType>>; -} +export type Library = CoreLibrary>; + +export type LibraryDefinition = CoreLibraryDefinition>; + +// ─── defineComponent (React) ──────────────────────────────────────────────── /** * Define a component with name, schema, description, and renderer. @@ -67,56 +58,10 @@ export function defineComponent>(config: { description: string; component: ComponentRenderer>; }): DefinedComponent { - (config.props as any).register(z.globalRegistry, { id: config.name }); - return { - ...config, - ref: config.props as unknown as z.ZodType>>, - }; + return coreDefineComponent>>(config); } -// ─── Groups & Prompt ────────────────────────────────────────────────────────── - -export interface ComponentGroup { - name: string; - components: string[]; - notes?: string[]; -} - -export interface PromptOptions { - preamble?: string; - additionalRules?: string[]; - examples?: string[]; -} - -// ─── Library ────────────────────────────────────────────────────────────────── - -export interface Library { - readonly components: Record; - readonly componentGroups: ComponentGroup[] | undefined; - readonly root: string | undefined; - - prompt(options?: PromptOptions): string; - /** - * Returns a single, valid JSON Schema document for the entire library. - * All component schemas are in `$defs`, keyed by component name. - * Sub-schemas shared across components (e.g. `Series`, `CardHeader`) are - * emitted once and referenced via `$ref` — no repetition. - * - * @example - * ```ts - * const schema = library.toJSONSchema(); - * // schema.$defs["Card"] → { properties: {...}, required: [...] } - * // schema.$defs["Series"] → { properties: {...}, required: [...] } - * ``` - */ - toJSONSchema(): object; -} - -export interface LibraryDefinition { - components: DefinedComponent[]; - componentGroups?: ComponentGroup[]; - root?: string; -} +// ─── createLibrary (React) ────────────────────────────────────────────────── /** * Create a component library from an array of defined components. @@ -130,40 +75,5 @@ export interface LibraryDefinition { * ``` */ export function createLibrary(input: LibraryDefinition): Library { - const componentsRecord: Record = {}; - for (const comp of input.components) { - if (!z.globalRegistry.has(comp.props)) { - comp.props.register(z.globalRegistry, { id: comp.name }); - } - componentsRecord[comp.name] = comp; - } - - if (input.root && !componentsRecord[input.root]) { - const available = Object.keys(componentsRecord).join(", "); - throw new Error( - `[createLibrary] Root component "${input.root}" was not found in components. Available components: ${available}`, - ); - } - - const library: Library = { - components: componentsRecord, - componentGroups: input.componentGroups, - root: input.root, - - prompt(options?: PromptOptions): string { - return generatePrompt(library, options); - }, - - toJSONSchema(): object { - // Build one combined z.object so z.toJSONSchema emits all component - // schemas into a shared $defs block — sub-schemas like CardHeader or - // Series are defined once and referenced via $ref everywhere else. - const combinedSchema = z.object( - Object.fromEntries(Object.entries(componentsRecord).map(([k, v]) => [k, v.props])) as any, - ); - return z.toJSONSchema(combinedSchema); - }, - }; - - return library; + return coreCreateLibrary>(input) as Library; } diff --git a/packages/react-lang/src/parser/index.ts b/packages/react-lang/src/parser/index.ts deleted file mode 100644 index 7cab53b1a..000000000 --- a/packages/react-lang/src/parser/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { BuiltinActionType } from "./types"; -export type { ActionEvent, ElementNode, ParseResult, ValidationError } from "./types"; - -export { createParser, createStreamingParser, parse } from "./parser"; -export type { LibraryJSONSchema, Parser, StreamParser } from "./parser"; - -export { generatePrompt } from "./prompt"; diff --git a/packages/react-lang/src/parser/parser.ts b/packages/react-lang/src/parser/parser.ts deleted file mode 100644 index 3134d2d03..000000000 --- a/packages/react-lang/src/parser/parser.ts +++ /dev/null @@ -1,787 +0,0 @@ -import type { ParseResult } from "./types"; - -/** - * The JSON Schema document produced by `library.toJSONSchema()`. - * All component schemas live in `$defs`, keyed by component name. - */ -export interface LibraryJSONSchema { - $defs?: Record< - string, - { - properties?: Record; - required?: string[]; - } - >; -} - -export interface ParamDef { - /** Parameter name, e.g. "title", "columns". */ - name: string; - /** Whether the parameter is required by the component. */ - required: boolean; - /** Default value from JSON Schema — used when the required field is missing/null. */ - defaultValue?: unknown; -} - -/** - * Internal parameter map. - */ -export type ParamMap = Map; - -// ───────────────────────────────────────────────────────────────────────────── -// AST node types -// ───────────────────────────────────────────────────────────────────────────── - -/** - * Discriminated union representing every value that can appear in an - * openui-lang expression. The `k` field is the discriminant. - * - * - `Comp` — a component call: `Header("Hello", "Subtitle")` - * - `Str` — a string literal: `"hello"` - * - `Num` — a number literal: `42` or `3.14` - * - `Bool` — a boolean literal: `true` or `false` - * - `Null` — the null literal - * - `Arr` — an array: `[a, b, c]` - * - `Obj` — an object: `{ key: value }` - * - `Ref` — a reference to another statement: `myTable` (resolved later) - * - `Ph` — a placeholder for an unresolvable reference (dropped as null in output) - */ -type ASTNode = - | { k: "Comp"; name: string; args: ASTNode[] } - | { k: "Str"; v: string } - | { k: "Num"; v: number } - | { k: "Bool"; v: boolean } - | { k: "Null" } - | { k: "Arr"; els: ASTNode[] } - | { k: "Obj"; entries: [string, ASTNode][] } - | { k: "Ref"; n: string } - | { k: "Ph"; n: string }; - -const enum T { - Newline = 0, - LParen = 1, // ( - RParen = 2, // ) - LBrack = 3, // [ - RBrack = 4, // ] - LBrace = 5, // { - RBrace = 6, // } - Comma = 7, // , - Colon = 8, // : - Equals = 9, // = - True = 10, - False = 11, - Null = 12, - EOF = 13, - Str = 14, // carries string value - Num = 15, // carries numeric value - Ident = 16, // lowercase identifier — becomes a reference - Type = 17, // PascalCase identifier — becomes a component name or reference -} - -type Token = { t: T; v?: string | number }; - -function autoClose(input: string): { text: string; wasIncomplete: boolean } { - const stack: string[] = []; - let inStr = false, - esc = false; - - for (let i = 0; i < input.length; i++) { - const c = input[i]; - - if (esc) { - esc = false; - continue; - } - if (c === "\\" && inStr) { - esc = true; - continue; - } - if (c === '"') { - inStr = !inStr; - continue; - } - if (inStr) continue; - - if (c === "(" || c === "[" || c === "{") stack.push(c); - else if (c === ")" && stack[stack.length - 1] === "(") stack.pop(); - else if (c === "]" && stack[stack.length - 1] === "[") stack.pop(); - else if (c === "}" && stack[stack.length - 1] === "{") stack.pop(); - } - - const wasIncomplete = inStr || stack.length > 0; - if (!wasIncomplete) return { text: input, wasIncomplete: false }; - - let out = input; - if (inStr) { - if (esc) out += "\\"; - out += '"'; - } // close open string - for ( - let j = stack.length - 1; - j >= 0; - j-- // close brackets in reverse - ) - out += stack[j] === "(" ? ")" : stack[j] === "[" ? "]" : "}"; - - return { text: out, wasIncomplete: true }; -} - -// lexer -function tokenize(src: string): Token[] { - const tokens: Token[] = []; - let i = 0; - const n = src.length; - - while (i < n) { - // Skip horizontal whitespace (not newlines — they're significant) - while (i < n && (src[i] === " " || src[i] === "\t" || src[i] === "\r")) i++; - if (i >= n) break; - - const c = src[i]; - - // ── Single-character punctuation ────────────────────────────────────── - if (c === "\n") { - tokens.push({ t: T.Newline }); - i++; - continue; - } - if (c === "(") { - tokens.push({ t: T.LParen }); - i++; - continue; - } - if (c === ")") { - tokens.push({ t: T.RParen }); - i++; - continue; - } - if (c === "[") { - tokens.push({ t: T.LBrack }); - i++; - continue; - } - if (c === "]") { - tokens.push({ t: T.RBrack }); - i++; - continue; - } - if (c === "{") { - tokens.push({ t: T.LBrace }); - i++; - continue; - } - if (c === "}") { - tokens.push({ t: T.RBrace }); - i++; - continue; - } - if (c === ",") { - tokens.push({ t: T.Comma }); - i++; - continue; - } - if (c === ":") { - tokens.push({ t: T.Colon }); - i++; - continue; - } - if (c === "=") { - tokens.push({ t: T.Equals }); - i++; - continue; - } - - // string literal: "..." - if (c === '"') { - const start = i; - i++; // skip opening quote - - let isClosed = false; - // Fast-forward to the closing quote, respecting escapes - while (i < n) { - if (src[i] === "\\") { - i += 2; // skip backslash and the escaped character - } else if (src[i] === '"') { - i++; // include the closing quote - isClosed = true; - break; - } else { - i++; - } - } - - const rawString = src.slice(start, i); - - try { - // Let JavaScript's native JSON parser handle all unescaping (\n, \t, \uXXXX, etc.) - // If the string is incomplete (streaming), we add a closing quote to parse what we have so far. - const validJsonString = isClosed ? rawString : rawString + '"'; - - tokens.push({ t: T.Str, v: JSON.parse(validJsonString) }); - } catch { - // Fallback if JSON.parse fails (e.g., malformed unicode escape during streaming) - // Strip the quotes and return the raw text so the UI doesn't crash - const stripped = rawString.replace(/^"|"$/g, ""); - tokens.push({ t: T.Str, v: stripped }); - } - continue; - } - - // number literal: 42, -3, 1.5 - const isDigit = c >= "0" && c <= "9"; - const isNegDigit = c === "-" && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9"; - if (isDigit || isNegDigit) { - const start = i; - if (src[i] === "-") i++; // optional minus - while (i < n && src[i] >= "0" && src[i] <= "9") i++; // integer part - if (i < n && src[i] === ".") { - // optional decimal - i++; - while (i < n && src[i] >= "0" && src[i] <= "9") i++; - } - if (i < n && (src[i] === "e" || src[i] === "E")) { - // optional exponent - i++; - if (i < n && (src[i] === "+" || src[i] === "-")) i++; - while (i < n && src[i] >= "0" && src[i] <= "9") i++; - } - tokens.push({ t: T.Num, v: +src.slice(start, i) }); - continue; - } - - // keyword or identifier - const isAlpha = (c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c === "_"; - if (isAlpha) { - const start = i; - while ( - i < n && - ((src[i] >= "a" && src[i] <= "z") || - (src[i] >= "A" && src[i] <= "Z") || - (src[i] >= "0" && src[i] <= "9") || - src[i] === "_") - ) - i++; - - const word = src.slice(start, i); - - if (word === "true") { - tokens.push({ t: T.True }); - continue; - } - if (word === "false") { - tokens.push({ t: T.False }); - continue; - } - if (word === "null") { - tokens.push({ t: T.Null }); - continue; - } - - // PascalCase → component type name; lowercase → variable reference - const kind = c >= "A" && c <= "Z" ? T.Type : T.Ident; - tokens.push({ t: kind, v: word }); - continue; - } - - i++; // skip any other character (e.g. @, #, emojis) - } - - tokens.push({ t: T.EOF }); - return tokens; -} - -interface RawStmt { - id: string; - tokens: Token[]; -} - -/** - * Splits the flat token stream into individual statements. - * - * Each statement has the form `identifier = expression`. Statements are - * separated by newlines at depth 0 (newlines inside brackets are ignored). - * - * Example input tokens for: - * `root = Root([tbl])\ntbl = Table(...)` - * - * Produces two RawStmts: - * { id: "root", tokens: [Root, (, [, tbl, ], )] } - * { id: "tbl", tokens: [Table, (, ..., )] } - * - * Invalid lines (no `=`, or no identifier) are silently skipped. - */ -function split(tokens: Token[]): RawStmt[] { - const stmts: RawStmt[] = []; - let pos = 0; - - while (pos < tokens.length) { - // Skip blank lines - while (pos < tokens.length && tokens[pos].t === T.Newline) pos++; - if (pos >= tokens.length || tokens[pos].t === T.EOF) break; - - // Expect: Ident|Type = expression - const tok = tokens[pos]; - if (tok.t !== T.Ident && tok.t !== T.Type) { - while (pos < tokens.length && tokens[pos].t !== T.Newline && tokens[pos].t !== T.EOF) pos++; - continue; - } - const id = tok.v as string; - pos++; - - // Must be followed by `=` - if (pos >= tokens.length || tokens[pos].t !== T.Equals) { - while (pos < tokens.length && tokens[pos].t !== T.Newline && tokens[pos].t !== T.EOF) pos++; - continue; - } - pos++; - - // Collect expression tokens until a depth-0 newline or EOF - const expr: Token[] = []; - let depth = 0; - while (pos < tokens.length && tokens[pos].t !== T.EOF) { - const tt = tokens[pos].t; - if (tt === T.Newline && depth <= 0) break; // statement boundary - if (tt === T.Newline) { - pos++; - continue; - } // newline inside bracket — skip - if (tt === T.LParen || tt === T.LBrack || tt === T.LBrace) depth++; - else if (tt === T.RParen || tt === T.RBrack || tt === T.RBrace) depth--; - expr.push(tokens[pos++]); - } - - if (expr.length) stmts.push({ id, tokens: expr }); - } - - return stmts; -} - -function parseTokens(tokens: Token[]): ASTNode { - let pos = 0; - const cur = (): Token => tokens[pos] ?? { t: T.EOF }; - const adv = () => pos++; - const eat = (kind: T) => { - if (cur().t === kind) adv(); - }; - - function parseExpr(): ASTNode { - const tok = cur(); - - if (tok.t === T.Type) { - // PascalCase followed by `(` → component call; otherwise a reference - return tokens[pos + 1]?.t === T.LParen - ? parseComp() - : (adv(), { k: "Ref", n: tok.v as string }); - } - if (tok.t === T.Str) { - adv(); - return { k: "Str", v: tok.v as string }; - } - if (tok.t === T.Num) { - adv(); - return { k: "Num", v: tok.v as number }; - } - if (tok.t === T.True) { - adv(); - return { k: "Bool", v: true }; - } - if (tok.t === T.False) { - adv(); - return { k: "Bool", v: false }; - } - if (tok.t === T.Null) { - adv(); - return { k: "Null" }; - } - if (tok.t === T.LBrack) return parseArr(); - if (tok.t === T.LBrace) return parseObj(); - if (tok.t === T.Ident) { - adv(); - return { k: "Ref", n: tok.v as string }; - } - - adv(); - return { k: "Null" }; // unknown token — treat as null - } - - /** Parse `TypeName(arg1, arg2, ...)` */ - function parseComp(): ASTNode { - const name = cur().v as string; - adv(); - eat(T.LParen); - const args: ASTNode[] = []; - while (cur().t !== T.RParen && cur().t !== T.EOF) { - args.push(parseExpr()); - if (cur().t === T.Comma) adv(); - } - eat(T.RParen); - return { k: "Comp", name, args }; - } - - /** Parse `[elem1, elem2, ...]` */ - function parseArr(): ASTNode { - adv(); // skip [ - const els: ASTNode[] = []; - while (cur().t !== T.RBrack && cur().t !== T.EOF) { - els.push(parseExpr()); - if (cur().t === T.Comma) adv(); - } - eat(T.RBrack); - return { k: "Arr", els }; - } - - /** Parse `{ key: value, ... }` */ - function parseObj(): ASTNode { - adv(); // skip { - const entries: [string, ASTNode][] = []; - while (cur().t !== T.RBrace && cur().t !== T.EOF) { - const kt = cur(); - const key = - kt.t === T.Ident || kt.t === T.Str || kt.t === T.Type || kt.t === T.Num - ? (adv(), String(kt.v)) - : (adv(), "?"); - eat(T.Colon); - entries.push([key, parseExpr()]); - if (cur().t === T.Comma) adv(); - } - eat(T.RBrace); - return { k: "Obj", entries }; - } - - return parseExpr(); -} - -function resolveNode( - node: ASTNode, - syms: Map, - unres: string[], - visited: Set, -): ASTNode { - if (node.k === "Ref") { - const { n } = node; - if (visited.has(n)) { - unres.push(n); - return { k: "Ph", n }; - } // cycle - if (!syms.has(n)) { - unres.push(n); - return { k: "Ph", n }; - } // missing - - visited.add(n); - const resolved = resolveNode(syms.get(n)!, syms, unres, visited); - visited.delete(n); - return resolved; - } - - if (node.k === "Comp") - return { - ...node, - args: node.args.map((a) => resolveNode(a, syms, unres, visited)), - }; - if (node.k === "Arr") - return { - ...node, - els: node.els.map((e) => resolveNode(e, syms, unres, visited)), - }; - if (node.k === "Obj") - return { - ...node, - entries: node.entries.map(([k, v]) => [k, resolveNode(v, syms, unres, visited)]), - }; - - // Literals and placeholders pass through unchanged - return node; -} - -type JsonVal = string | number | boolean | null | JsonVal[] | { [k: string]: JsonVal }; - -function toJson( - node: ASTNode, - partial: boolean, - errors: ParseResult["meta"]["validationErrors"], - cat: ParamMap | undefined, -): JsonVal { - if (node.k === "Str") return node.v; - if (node.k === "Num") return node.v; - if (node.k === "Bool") return node.v; - if (node.k === "Null") return null; - if (node.k === "Arr") { - const items: JsonVal[] = []; - for (const e of node.els) { - // Drop unresolved references from arrays to avoid null entries like [null, element] - if (e.k === "Ph") continue; - const value = toJson(e, partial, errors, cat); - // Drop invalid component entries from arrays (e.g. incomplete required props while streaming) - if (e.k === "Comp" && value === null) continue; - items.push(value); - } - return items; - } - if (node.k === "Obj") { - const o: { [k: string]: JsonVal } = {}; - for (const [k, v] of node.entries) o[k] = toJson(v, partial, errors, cat); - return o; - } - if (node.k === "Comp") return mapNode(node, partial, errors, cat) as unknown as JsonVal; - if (node.k === "Ph") return null; - return null; -} - -function mapNode( - node: ASTNode, - partial: boolean, - errors: ParseResult["meta"]["validationErrors"], - cat: ParamMap | undefined, -): ParseResult["root"] { - if (node.k === "Ph") return null; - if (node.k !== "Comp") return null; - - const { name, args } = node; - const def = cat?.get(name); - const props: { [k: string]: JsonVal } = {}; - - if (def) { - // Map positional args → named props using library param order - for (let i = 0; i < def.params.length && i < args.length; i++) - props[def.params[i].name] = toJson(args[i], partial, errors, cat); - - // Validate required props — try defaultValue first before dropping - const missingRequired = def.params.filter( - (p) => p.required && (!(p.name in props) || props[p.name] === null), - ); - if (missingRequired.length) { - const stillInvalid = missingRequired.filter((p) => { - if (p.defaultValue !== undefined) { - props[p.name] = p.defaultValue as JsonVal; - return false; - } - return true; - }); - if (stillInvalid.length) { - for (const p of stillInvalid) - errors.push({ - component: name, - path: `/${p.name}`, - message: - p.name in props - ? `required field "${p.name}" cannot be null` - : `missing required field "${p.name}"`, - }); - return null; - } - } - } else { - // No library entry for this component — preserve all args under _args - props._args = args.map((a) => toJson(a, partial, errors, cat)); - } - - return { type: "element", typeName: name, props, partial }; -} - -function emptyResult(incomplete = true): ParseResult { - return { - root: null, - meta: { - incomplete, - unresolved: [], - statementCount: 0, - validationErrors: [], - }, - }; -} - -function buildResult( - syms: Map, - firstId: string, - wasIncomplete: boolean, - stmtCount: number, - cat: ParamMap | undefined, -): ParseResult { - if (!syms.has(firstId)) return emptyResult(wasIncomplete); - - const unres: string[] = []; - const resolved = resolveNode(syms.get(firstId)!, syms, unres, new Set()); - const errors: ParseResult["meta"]["validationErrors"] = []; - const root = mapNode(resolved, wasIncomplete, errors, cat); - - return { - root, - meta: { - incomplete: wasIncomplete, - unresolved: unres, - statementCount: stmtCount, - validationErrors: errors, - }, - }; -} - -/** - * Parse a complete openui-lang string in one pass. - * - * @param input - Full openui-lang source text (may be partial/streaming) - * @param cat - Optional param map for positional-arg → named-prop mapping - * @returns ParseResult with root ElementNode (or null) and metadata - */ -export function parse(input: string, cat?: ParamMap): ParseResult { - const trimmed = input.trim(); - if (!trimmed) return emptyResult(); - - const { text, wasIncomplete } = autoClose(trimmed); - const stmts = split(tokenize(text)); - if (!stmts.length) return emptyResult(wasIncomplete); - - const syms = new Map(); - let firstId = ""; - for (const s of stmts) { - syms.set(s.id, parseTokens(s.tokens)); - if (!firstId) firstId = s.id; - } - - return buildResult(syms, firstId, wasIncomplete, stmts.length, cat); -} - -export interface StreamParser { - /** Feed the next SSE/stream chunk and get the latest ParseResult. */ - push(chunk: string): ParseResult; - /** Get the latest ParseResult without consuming new data. */ - getResult(): ParseResult; -} - -export function createStreamParser(cat?: ParamMap): StreamParser { - let buf = ""; - let completedEnd = 0; - const completedSyms = new Map(); - - let completedCount = 0; - let firstId = ""; - - function addStmt(text: string) { - for (const s of split(tokenize(text))) { - completedSyms.set(s.id, parseTokens(s.tokens)); - completedCount++; - if (!firstId) firstId = s.id; - } - } - - function scanNewCompleted(): number { - let depth = 0, - inStr = false, - esc = false; - let stmtStart = completedEnd; - - for (let i = completedEnd; i < buf.length; i++) { - const c = buf[i]; - if (esc) { - esc = false; - continue; - } - if (c === "\\" && inStr) { - esc = true; - continue; - } - if (c === '"') { - inStr = !inStr; - continue; - } - if (inStr) continue; - - if (c === "(" || c === "[" || c === "{") depth++; - else if (c === ")" || c === "]" || c === "}") depth--; - else if (c === "\n" && depth <= 0) { - // Depth-0 newline = end of a statement - const t = buf.slice(stmtStart, i).trim(); - if (t) addStmt(t); - stmtStart = i + 1; // next statement begins after this newline - completedEnd = i + 1; // advance the "already processed" watermark - } - } - - return stmtStart; // start of the current pending (incomplete) statement - } - - function currentResult(): ParseResult { - const pendingStart = scanNewCompleted(); - const pendingText = buf.slice(pendingStart).trim(); - - // No pending text — all statements are complete - if (!pendingText) { - if (completedCount === 0) return emptyResult(); - return buildResult(completedSyms, firstId, false, completedCount, cat); - } - - // Autoclose the incomplete last statement so it's syntactically valid - const { text: closed, wasIncomplete } = autoClose(pendingText); - const stmts = split(tokenize(closed)); - - if (!stmts.length) { - if (completedCount === 0) return emptyResult(wasIncomplete); - return buildResult(completedSyms, firstId, wasIncomplete, completedCount, cat); - } - - // Merge: completed cache + re-parsed pending statement - // (Map spread is cheap since completedSyms only grows by one entry at a time) - const allSyms = new Map(completedSyms); - for (const s of stmts) allSyms.set(s.id, parseTokens(s.tokens)); - - const fid = firstId || stmts[0].id; - return buildResult(allSyms, fid, wasIncomplete, completedCount + stmts.length, cat); - } - - return { - push(chunk) { - buf += chunk; - return currentResult(); - }, - getResult: currentResult, - }; -} - -export interface Parser { - parse(input: string): ParseResult; -} - -function compileSchema(schema: LibraryJSONSchema): ParamMap { - const map: ParamMap = new Map(); - const defs = schema.$defs ?? {}; - - for (const [name, def] of Object.entries(defs)) { - const properties = def.properties ?? {}; - const required = def.required ?? []; - const params = Object.keys(properties).map((k) => ({ - name: k, - required: required.includes(k), - defaultValue: (properties[k] as any)?.default, - })); - map.set(name, { params }); - } - - return map; -} - -/** - * Create a parser from a library JSON Schema document. - * Pass `library.toJSONSchema()` to get the schema. - * - * @example - * ```ts - * const parser = createParser(library.toJSONSchema()); - * const result = parser.parse(openuiLangString); - * ``` - */ -export function createParser(schema: LibraryJSONSchema): Parser { - const paramMap = compileSchema(schema); - return { - parse(input: string): ParseResult { - return parse(input, paramMap); - }, - }; -} - -/** - * Create a streaming parser from a library JSON Schema document. - * Pass `library.toJSONSchema()` to get the schema. - */ -export function createStreamingParser(schema: LibraryJSONSchema): StreamParser { - return createStreamParser(compileSchema(schema)); -} diff --git a/packages/react-lang/src/parser/prompt.ts b/packages/react-lang/src/parser/prompt.ts deleted file mode 100644 index 11119d678..000000000 --- a/packages/react-lang/src/parser/prompt.ts +++ /dev/null @@ -1,336 +0,0 @@ -import { z } from "zod"; -import type { DefinedComponent, Library, PromptOptions } from "../library"; - -const PREAMBLE = `You are an AI assistant that responds using openui-lang, a declarative UI language. Your ENTIRE response must be valid openui-lang code — no markdown, no explanations, just openui-lang.`; - -function syntaxRules(rootName: string): string { - return `## Syntax Rules - -1. Each statement is on its own line: \`identifier = Expression\` -2. \`root\` is the entry point — every program must define \`root = ${rootName}(...)\` -3. Expressions are: strings ("..."), numbers, booleans (true/false), arrays ([...]), objects ({...}), or component calls TypeName(arg1, arg2, ...) -4. Use references for readability: define \`name = ...\` on one line, then use \`name\` later -5. EVERY variable (except root) MUST be referenced by at least one other variable. Unreferenced variables are silently dropped and will NOT render. Always include defined variables in their parent's children/items array. -6. Arguments are POSITIONAL (order matters, not names) -7. Optional arguments can be omitted from the end -8. No operators, no logic, no variables — only declarations -9. Strings use double quotes with backslash escaping`; -} - -function streamingRules(rootName: string): string { - return `## Hoisting & Streaming (CRITICAL) - -openui-lang supports hoisting: a reference can be used BEFORE it is defined. The parser resolves all references after the full input is parsed. - -During streaming, the output is re-parsed on every chunk. Undefined references are temporarily unresolved and appear once their definitions stream in. This creates a progressive top-down reveal — structure first, then data fills in. - -**Recommended statement order for optimal streaming:** -1. \`root = ${rootName}(...)\` — UI shell appears immediately -2. Component definitions — fill in as they stream -3. Data values — leaf content last - -Always write the root = ${rootName}(...) statement first so the UI shell appears immediately, even before child data has streamed in.`; -} - -function importantRules(rootName: string): string { - return `## Important Rules -- ALWAYS start with root = ${rootName}(...) -- Write statements in TOP-DOWN order: root → components → data (leverages hoisting for progressive streaming) -- Each statement on its own line -- No trailing text or explanations — output ONLY openui-lang code -- When asked about data, generate realistic/plausible data -- Choose components that best represent the content (tables for comparisons, charts for trends, forms for input, etc.) -- NEVER define a variable without referencing it from the tree. Every variable must be reachable from root, otherwise it will not render.`; -} - -function getZodDef(schema: unknown): any { - return (schema as any)?._zod?.def; -} - -function getZodType(schema: unknown): string | undefined { - return getZodDef(schema)?.type; -} - -function isOptionalType(schema: unknown): boolean { - return getZodType(schema) === "optional"; -} - -function unwrapOptional(schema: unknown): unknown { - const def = getZodDef(schema); - if (def?.type === "optional") return def.innerType; - return schema; -} - -/** Strip optional wrapper to reach the core schema. */ -function unwrap(schema: unknown): unknown { - return unwrapOptional(schema); -} - -function isArrayType(schema: unknown): boolean { - const s = unwrap(schema); - return getZodType(s) === "array"; -} - -function getArrayInnerType(schema: unknown): unknown | undefined { - const s = unwrap(schema); - const def = getZodDef(s); - if (def?.type === "array") return def.element ?? def.innerType; - return undefined; -} - -function getEnumValues(schema: unknown): string[] | undefined { - const s = unwrap(schema); - const def = getZodDef(s); - if (def?.type !== "enum") return undefined; - if (Array.isArray(def.values)) return def.values; - if (def.entries && typeof def.entries === "object") return Object.keys(def.entries); - return undefined; -} - -function getSchemaId(schema: unknown): string | undefined { - try { - const meta = z.globalRegistry.get(schema as z.ZodType); - return meta?.id; - } catch { - return undefined; - } -} - -function getUnionOptions(schema: unknown): unknown[] | undefined { - const def = getZodDef(schema); - if (def?.type === "union" && Array.isArray(def.options)) return def.options; - return undefined; -} - -function getObjectShape(schema: unknown): Record | undefined { - const def = getZodDef(schema); - if (def?.type === "object" && def.shape && typeof def.shape === "object") - return def.shape as Record; - return undefined; -} - -/** - * Resolve the type annotation for a schema field. - * Returns a human-readable type string for the schema. - * - * Examples: - * - z.string() → "string" - * - z.number() → "number" - * - z.boolean() → "boolean" - * - z.enum(["a","b"]) → '"a" | "b"' - * - z.array(TabItemSchema) → "TabItem[]" - * - z.union([Input, TextArea]) → "Input | TextArea" - * - z.array(z.union([A, B])) → "(A | B)[]" - * - ButtonGroupSchema → "ButtonGroup" - * - z.object({src: z.string()}) → "{src: string}" (inline when unregistered) - */ -function resolveTypeAnnotation(schema: unknown): string | undefined { - const inner = unwrap(schema); - - const directId = getSchemaId(inner); - if (directId) return directId; - - const unionOpts = getUnionOptions(inner); - if (unionOpts) { - const resolved = unionOpts.map((o) => resolveTypeAnnotation(o)); - const names = resolved.filter(Boolean) as string[]; - if (names.length > 0) { - if (names.length < unionOpts.length) { - console.warn( - `[prompt] Partially resolved union: ${names.length}/${unionOpts.length} options resolved`, - ); - } - return names.join(" | "); - } - } - - if (isArrayType(schema)) { - const arrayInner = getArrayInnerType(schema); - if (!arrayInner) return undefined; - - const innerType = resolveTypeAnnotation(arrayInner); - if (innerType) { - const isUnion = getUnionOptions(unwrap(arrayInner)) !== undefined; - return isUnion ? `(${innerType})[]` : `${innerType}[]`; - } - - console.warn( - `[prompt] Could not resolve array element type (inner zod type: "${getZodType(arrayInner) ?? "unknown"}")`, - ); - return undefined; - } - - const zodType = getZodType(inner); - if (zodType === "string") return "string"; - if (zodType === "number") return "number"; - if (zodType === "boolean") return "boolean"; - - const enumVals = getEnumValues(inner); - if (enumVals) return enumVals.map((v) => `"${v}"`).join(" | "); - - if (zodType === "literal") { - const vals = getZodDef(inner)?.values; - if (Array.isArray(vals) && vals.length === 1) { - const v = vals[0]; - return typeof v === "string" ? `"${v}"` : String(v); - } - } - - const shape = getObjectShape(inner); - if (shape) { - const fields = Object.entries(shape).map(([name, fieldSchema]) => { - const opt = isOptionalType(fieldSchema) ? "?" : ""; - const fieldType = resolveTypeAnnotation(fieldSchema as z.ZodType); - return fieldType ? `${name}${opt}: ${fieldType}` : `${name}${opt}`; - }); - return `{${fields.join(", ")}}`; - } - - if (zodType === "lazy") { - console.warn( - `[prompt] z.lazy() schemas are not resolved — remove z.lazy() wrapper from the schema`, - ); - } else if (zodType) { - console.warn(`[prompt] Unresolved schema type: "${zodType}"`); - } - - return undefined; -} - -// ─── Field analysis ─── - -interface FieldInfo { - name: string; - isOptional: boolean; - isArray: boolean; - typeAnnotation?: string; -} - -function analyzeFields(shape: Record): FieldInfo[] { - return Object.entries(shape).map(([name, schema]) => ({ - name, - isOptional: isOptionalType(schema), - isArray: isArrayType(schema), - typeAnnotation: resolveTypeAnnotation(schema), - })); -} - -// ─── Signature generation ─── - -function buildSignature(componentName: string, fields: FieldInfo[]): string { - const params = fields.map((f) => { - if (f.typeAnnotation) { - return f.isOptional ? `${f.name}?: ${f.typeAnnotation}` : `${f.name}: ${f.typeAnnotation}`; - } - if (f.isArray) { - return f.isOptional ? `[${f.name}]?` : `[${f.name}]`; - } - return f.isOptional ? `${f.name}?` : f.name; - }); - return `${componentName}(${params.join(", ")})`; -} - -function buildComponentLine(componentName: string, def: DefinedComponent): string { - const fields = analyzeFields(def.props.shape); - const sig = buildSignature(componentName, fields); - if (def.description) { - return `${sig} — ${def.description}`; - } - return sig; -} - -// ─── Prompt assembly ─── - -function generateComponentSignatures(library: Library): string { - const lines: string[] = [ - "## Component Signatures", - "", - "Arguments marked with ? are optional. Sub-components can be inline or referenced; prefer references for better streaming.", - "The `action` prop type accepts: ContinueConversation (sends message to LLM), OpenUrl (navigates to URL), or Custom (app-defined).", - ]; - - if (library.componentGroups?.length) { - const groupedComponents = new Set(); - - for (const group of library.componentGroups) { - lines.push(""); - lines.push(`### ${group.name}`); - for (const name of group.components) { - if (groupedComponents.has(name)) { - console.warn( - `[prompt] Component "${name}" appears in multiple groups; keeping the first occurrence only.`, - ); - continue; - } - const def = library.components[name]; - if (!def) { - console.warn( - `[prompt] Component "${name}" listed in group "${group.name}" was not found in the library and will be omitted from the prompt.`, - ); - continue; - } - groupedComponents.add(name); - lines.push(buildComponentLine(name, def)); - } - if (group.notes?.length) { - for (const note of group.notes) { - lines.push(note); - } - } - } - - const ungrouped = Object.keys(library.components).filter( - (name) => !groupedComponents.has(name), - ); - if (ungrouped.length) { - lines.push(""); - lines.push("### Ungrouped"); - for (const name of ungrouped) { - const def = library.components[name]; - lines.push(buildComponentLine(name, def)); - } - } - } else { - lines.push(""); - for (const [name, def] of Object.entries(library.components)) { - lines.push(buildComponentLine(name, def)); - } - } - - return lines.join("\n"); -} - -export function generatePrompt(library: Library, options?: PromptOptions): string { - const rootName = library.root ?? "Root"; - const parts: string[] = []; - - parts.push(options?.preamble ?? PREAMBLE); - parts.push(""); - parts.push(syntaxRules(rootName)); - parts.push(""); - parts.push(generateComponentSignatures(library)); - parts.push(""); - parts.push(streamingRules(rootName)); - - const examples = options?.examples; - if (examples?.length) { - parts.push(""); - parts.push("## Examples"); - parts.push(""); - for (const ex of examples) { - parts.push(ex); - parts.push(""); - } - } - - parts.push(importantRules(rootName)); - - if (options?.additionalRules?.length) { - parts.push(""); - for (const rule of options.additionalRules) { - parts.push(`- ${rule}`); - } - } - - return parts.join("\n"); -} diff --git a/packages/react-lang/src/parser/types.ts b/packages/react-lang/src/parser/types.ts deleted file mode 100644 index b3754d0f6..000000000 --- a/packages/react-lang/src/parser/types.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * A fully resolved component node from the parser. - * - * The parser converts openui-lang text into a tree of these nodes. - * Each node represents one component invocation with its positional - * arguments mapped into named `props` via the library's Zod key order. - */ -export interface ElementNode { - type: "element"; - /** Component name as defined in the library (e.g. "Table", "BarChart"). */ - typeName: string; - /** Named props produced by positional-to-named mapping in the Rust parser. */ - props: Record; - /** - * True when the parser hasn't received all tokens for this node yet - * (streaming in progress). - */ - partial: boolean; -} - -/** - * A prop validation error from the Rust parser. - * When a component has missing required props, it is redacted from the - * output tree (dropped as null) and errors are recorded here. - */ -export interface ValidationError { - /** Component type name, e.g. "Header", "BarChart". */ - component: string; - /** JSON Pointer path within the props object, e.g. "/title", "". */ - path: string; - /** Human-readable error message. */ - message: string; -} - -/** - * Built-in action types for interactive components. - */ -export enum BuiltinActionType { - ContinueConversation = "continue_conversation", - OpenUrl = "open_url", -} - -/** - * Structured action event fired by interactive components. - */ -export interface ActionEvent { - /** Action type. See `BuiltinActionType` for built-in types. */ - type: string; - /** Action-specific params (e.g. { url } for OpenUrl, custom params for Custom). */ - params: Record; - /** Human-readable label for the action (displayed as user message in chat). */ - humanFriendlyMessage: string; - /** Raw form state at the time of the action — all field values. */ - formState?: Record; - /** The form name that triggered this action, if any. */ - formName?: string; -} - -/** - * The output of a single `parser.parse(text)` call. - * - * During streaming, each chunk produces a new ParseResult as the - * accumulated text is re-parsed. The `root` progressively resolves - * from null → partial tree → complete tree. - */ -export interface ParseResult { - /** The root ElementNode (typically a Root component), or null if parsing hasn't produced one yet. */ - root: ElementNode | null; - meta: { - /** True if the parser detected truncated/incomplete input. */ - incomplete: boolean; - /** Names of references used but not yet defined (dropped as null in output). */ - unresolved: string[]; - /** Total number of `identifier = Expression` statements parsed. */ - statementCount: number; - /** - * Prop validation errors. Components with missing required props are - * redacted (dropped as null) and listed here. - */ - validationErrors: ValidationError[]; - }; -} diff --git a/packages/react-lang/src/utils/index.ts b/packages/react-lang/src/utils/index.ts deleted file mode 100644 index 6495f6152..000000000 --- a/packages/react-lang/src/utils/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { builtInValidators, parseRules, parseStructuredRules, validate } from "./validation"; -export type { ValidatorFn } from "./validation"; diff --git a/packages/react-lang/src/utils/validation.ts b/packages/react-lang/src/utils/validation.ts deleted file mode 100644 index 0902e9f79..000000000 --- a/packages/react-lang/src/utils/validation.ts +++ /dev/null @@ -1,150 +0,0 @@ -export interface ParsedRule { - type: string; - arg?: number | string; -} - -/** - * Parse a rule string into a structured rule. - * "required" → { type: "required" } - * "min:8" → { type: "min", arg: 8 } - * "minLength:3" → { type: "minLength", arg: 3 } - * "pattern:^[a-z]" → { type: "pattern", arg: "^[a-z]" } - */ -export function parseRule(rule: string): ParsedRule { - const colonIdx = rule.indexOf(":"); - if (colonIdx === -1) return { type: rule }; - - const type = rule.slice(0, colonIdx); - const rawArg = rule.slice(colonIdx + 1); - const num = Number(rawArg); - return { type, arg: Number.isFinite(num) && rawArg !== "" ? num : rawArg }; -} - -export function parseRules(rules: unknown): ParsedRule[] { - if (!Array.isArray(rules)) return []; - return rules.filter((r): r is string => typeof r === "string").map(parseRule); -} - -export type ValidatorFn = (value: unknown, arg?: number | string) => string | undefined; - -function isEmpty(value: unknown): boolean { - if (value === null || value === undefined || value === "") return true; - if (Array.isArray(value) && value.length === 0) return true; - return false; -} - -export const builtInValidators: Record = { - required: (value) => { - if (isEmpty(value)) return "This field is required"; - if (typeof value === "object" && !Array.isArray(value) && value !== null) { - const vals = Object.values(value); - if (vals.length > 0 && vals.every((v) => typeof v === "boolean") && !vals.some(Boolean)) { - return "At least one option is required"; - } - } - return undefined; - }, - - email: (value) => { - if (isEmpty(value)) return undefined; - if (typeof value !== "string") return "Please enter a valid email"; - return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? undefined : "Please enter a valid email"; - }, - - url: (value) => { - if (isEmpty(value)) return undefined; - if (typeof value !== "string") return "Please enter a valid URL"; - try { - new URL(value); - return undefined; - } catch { - return "Please enter a valid URL"; - } - }, - - numeric: (value) => { - if (isEmpty(value)) return undefined; - if (typeof value === "number" && !isNaN(value)) return undefined; - if (typeof value === "string" && !isNaN(parseFloat(value)) && value.trim() !== "") - return undefined; - return "Must be a number"; - }, - - min: (value, arg) => { - if (isEmpty(value)) return undefined; - const n = typeof value === "number" ? value : parseFloat(String(value)); - if (isNaN(n)) return undefined; - const min = Number(arg); - return n >= min ? undefined : `Must be at least ${min}`; - }, - - max: (value, arg) => { - if (isEmpty(value)) return undefined; - const n = typeof value === "number" ? value : parseFloat(String(value)); - if (isNaN(n)) return undefined; - const max = Number(arg); - return n <= max ? undefined : `Must be no more than ${max}`; - }, - - minLength: (value, arg) => { - if (isEmpty(value)) return undefined; - if (typeof value !== "string") return undefined; - const min = Number(arg); - return value.length >= min ? undefined : `Must be at least ${min} characters`; - }, - - maxLength: (value, arg) => { - if (isEmpty(value)) return undefined; - if (typeof value !== "string") return undefined; - const max = Number(arg); - return value.length <= max ? undefined : `Must be no more than ${max} characters`; - }, - - pattern: (value, arg) => { - if (isEmpty(value)) return undefined; - if (typeof value !== "string" || typeof arg !== "string") return undefined; - try { - return new RegExp(arg).test(value) ? undefined : "Invalid format"; - } catch { - return undefined; - } - }, -}; - -/** - * Run all rules against a value. Stop on first error. - * Custom validators are checked first, then built-in ones. - */ -export function validate( - value: unknown, - rules: ParsedRule[], - customValidators?: Record, -): string | undefined { - for (const rule of rules) { - const validator = customValidators?.[rule.type] ?? builtInValidators[rule.type]; - if (!validator) continue; - const error = validator(value, rule.arg); - if (error) return error; - } - return undefined; -} - -/** - * Parse a structured rules object into ParsedRule[]. - * Accepts: { required: true, minLength: 5, email: true, max: 100 } - * Skips keys with false/undefined values. - */ -export function parseStructuredRules(rules: unknown): ParsedRule[] { - if (!rules || typeof rules !== "object" || Array.isArray(rules)) return []; - const obj = rules as Record; - const result: ParsedRule[] = []; - for (const [key, val] of Object.entries(obj)) { - if (val === false || val === undefined || val === null) continue; - if (val === true) { - result.push({ type: key }); - } else { - result.push({ type: key, arg: val }); - } - } - return result; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2da2e98dd..20db47a28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -421,66 +421,36 @@ importers: specifier: ^5 version: 5.9.3 - examples/vercel-ai-chat: - dependencies: - '@ai-sdk/openai': - specifier: ^3.0.41 - version: 3.0.41(zod@4.3.6) - '@ai-sdk/react': - specifier: ^3.0.118 - version: 3.0.118(react@19.2.3)(zod@4.3.6) - '@openuidev/cli': - specifier: workspace:* - version: link:../../packages/openui-cli - '@openuidev/react-lang': - specifier: workspace:* - version: link:../../packages/react-lang - '@openuidev/react-ui': + examples/svelte-chat: + dependencies: + '@openuidev/svelte-lang': specifier: workspace:* - version: link:../../packages/react-ui - ai: - specifier: ^6.0.116 - version: 6.0.116(zod@4.3.6) - lucide-react: - specifier: ^0.575.0 - version: 0.575.0(react@19.2.3) - next: - specifier: 16.1.6 - version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.89.2) - react: - specifier: 19.2.3 - version: 19.2.3 - react-dom: - specifier: 19.2.3 - version: 19.2.3(react@19.2.3) + version: link:../../packages/svelte-lang zod: - specifier: ^4.3.6 + specifier: ^4.0.0 version: 4.3.6 devDependencies: - '@tailwindcss/postcss': - specifier: ^4 - version: 4.2.1 - '@types/node': - specifier: ^20 - version: 20.19.35 - '@types/react': - specifier: ^19 - version: 19.2.14 - '@types/react-dom': - specifier: ^19 - version: 19.2.3(@types/react@19.2.14) - eslint: - specifier: ^9 - version: 9.29.0(jiti@2.6.1) - eslint-config-next: - specifier: 16.1.6 - version: 16.1.6(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3) - tailwindcss: - specifier: ^4 - version: 4.2.1 - typescript: - specifier: ^5 - version: 5.9.3 + '@sveltejs/adapter-auto': + specifier: ^4.0.0 + version: 4.0.0(@sveltejs/kit@2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))) + '@sveltejs/kit': + specifier: ^2.0.0 + version: 2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + '@sveltejs/vite-plugin-svelte': + specifier: ^5.0.0 + version: 5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + svelte: + specifier: ^5.0.0 + version: 5.53.12 + vite: + specifier: ^6.0.0 + version: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + + packages/lang-core: + dependencies: + zod: + specifier: ^4.0.0 + version: 4.3.6 packages/openui-cli: dependencies: @@ -528,6 +498,9 @@ importers: packages/react-lang: dependencies: + '@openuidev/lang-core': + specifier: workspace:^ + version: link:../lang-core react: specifier: '>=19.0.0' version: 19.2.4 @@ -759,6 +732,43 @@ importers: specifier: ^5.0.0 version: 5.4.19(@types/node@22.15.32)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0) + packages/svelte-lang: + dependencies: + '@openuidev/lang-core': + specifier: workspace:^ + version: link:../lang-core + zod: + specifier: ^4.0.0 + version: 4.3.6 + devDependencies: + '@sveltejs/package': + specifier: ^2.3.0 + version: 2.5.7(svelte@5.53.12)(typescript@5.9.3) + '@sveltejs/vite-plugin-svelte': + specifier: ^5.0.0 + version: 5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + '@testing-library/svelte': + specifier: ^5.2.0 + version: 5.3.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + jsdom: + specifier: ^26.1.0 + version: 26.1.0 + svelte: + specifier: ^5.0.0 + version: 5.53.12 + svelte-check: + specifier: ^4.0.0 + version: 4.4.5(picomatch@4.0.3)(svelte@5.53.12)(typescript@5.9.3) + typescript: + specifier: ^5.0.0 + version: 5.9.3 + vite: + specifier: ^6.0.0 + version: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vitest: + specifier: ^3.0.0 + version: 3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + packages: '@0no-co/graphql.web@1.2.0': @@ -814,6 +824,9 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + '@asamuzakjp/css-color@5.0.1': resolution: {integrity: sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -1386,10 +1399,21 @@ packages: peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + '@csstools/color-helpers@6.0.2': resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} engines: {node: '>=20.19.0'} + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-calc@3.1.1': resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==} engines: {node: '>=20.19.0'} @@ -1397,6 +1421,13 @@ packages: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-color-parser@4.0.2': resolution: {integrity: sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==} engines: {node: '>=20.19.0'} @@ -1404,6 +1435,12 @@ packages: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-parser-algorithms@4.0.0': resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} engines: {node: '>=20.19.0'} @@ -1413,6 +1450,10 @@ packages: '@csstools/css-syntax-patches-for-csstree@1.1.0': resolution: {integrity: sha512-H4tuz2nhWgNKLt1inYpoVCfbJbMwX/lQKp3g69rrrIMIYlFD9+zTykOKhNR8uGrAmbS/kT9n6hTFkmDkxLgeTA==} + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + '@csstools/css-tokenizer@4.0.0': resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} @@ -2127,89 +2168,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -2442,15 +2499,9 @@ packages: '@jridgewell/source-map@0.3.6': resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -2504,48 +2555,56 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-gnu@16.1.6': resolution: {integrity: sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@15.5.12': resolution: {integrity: sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-arm64-musl@16.1.6': resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@15.5.12': resolution: {integrity: sha512-jSLvgdRRL/hrFAPqEjJf1fFguC719kmcptjNVDJl26BnJIpjL3KH5h6mzR4mAweociLQaqvt4UyzfbFjgAdDcw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-gnu@16.1.6': resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@15.5.12': resolution: {integrity: sha512-/uaF0WfmYqQgLfPmN6BvULwxY0dufI2mlN2JbOKqqceZh1G4hjREyi7pg03zjfyS6eqNemHAZPSoP84x17vo6w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-linux-x64-musl@16.1.6': resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@15.5.12': resolution: {integrity: sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg==} @@ -2697,36 +2756,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -2758,6 +2823,9 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@posthog/core@1.23.2': resolution: {integrity: sha512-zTDdda9NuSHrnwSOfFMxX/pyXiycF4jtU1kTr8DL61dHhV+7LF6XF1ndRZZTuaGGbfbb/GJYkEsjEX9SXfNZeQ==} @@ -3903,56 +3971,67 @@ packages: resolution: {integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.43.0': resolution: {integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.43.0': resolution: {integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.43.0': resolution: {integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.43.0': resolution: {integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': resolution: {integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.43.0': resolution: {integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.43.0': resolution: {integrity: sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.43.0': resolution: {integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.43.0': resolution: {integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.43.0': resolution: {integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.43.0': resolution: {integrity: sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==} @@ -4196,6 +4275,54 @@ packages: peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 + '@sveltejs/acorn-typescript@1.0.9': + resolution: {integrity: sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==} + peerDependencies: + acorn: ^8.9.0 + + '@sveltejs/adapter-auto@4.0.0': + resolution: {integrity: sha512-kmuYSQdD2AwThymQF0haQhM8rE5rhutQXG4LNbnbShwhMO4qQGnKaaTy+88DuNSuoQDi58+thpq8XpHc1+oEKQ==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + + '@sveltejs/kit@2.55.0': + resolution: {integrity: sha512-MdFRjevVxmAknf2NbaUkDF16jSIzXMWd4Nfah0Qp8TtQVoSp3bV4jKt8mX7z7qTUTWvgSaxtR0EG5WJf53gcuA==} + engines: {node: '>=18.13'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.0.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: ^5.3.3 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + typescript: + optional: true + + '@sveltejs/package@2.5.7': + resolution: {integrity: sha512-qqD9xa9H7TDiGFrF6rz7AirOR8k15qDK/9i4MIE8te4vWsv5GEogPks61rrZcLy+yWph+aI6pIj2MdoK3YI8AQ==} + engines: {node: ^16.14 || >=18} + hasBin: true + peerDependencies: + svelte: ^3.44.0 || ^4.0.0 || ^5.0.0-next.1 + + '@sveltejs/vite-plugin-svelte-inspector@4.0.1': + resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^5.0.0 + svelte: ^5.0.0 + vite: ^6.0.0 + + '@sveltejs/vite-plugin-svelte@5.1.1': + resolution: {integrity: sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.0.0 + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -4241,24 +4368,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.1': resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.1': resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.1': resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.1': resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} @@ -4308,24 +4439,28 @@ packages: engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@takumi-rs/core-linux-arm64-musl@0.68.17': resolution: {integrity: sha512-4CiEF518wDnujF0fjql2XN6uO+OXl0svy0WgAF2656dCx2gJtWscaHytT2rsQ0ZmoFWE0dyWcDW1g/FBVPvuvA==} engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@takumi-rs/core-linux-x64-gnu@0.68.17': resolution: {integrity: sha512-jm8lTe2E6Tfq2b97GJC31TWK1JAEv+MsVbvL9DCLlYcafgYFlMXDUnOkZFMjlrmh0HcFAYDaBkniNDgIQfXqzg==} engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@takumi-rs/core-linux-x64-musl@0.68.17': resolution: {integrity: sha512-nbdzQgC4ywzltDDV1fer1cKswwGE+xXZHdDiacdd7RM5XBng209Bmo3j1iv9dsX+4xXhByzCCGbxdWhhHqVXmw==} engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@takumi-rs/core-win32-arm64-msvc@0.68.17': resolution: {integrity: sha512-kE4F0LRmuhSwiNkFG7dTY9ID8+B7zb97QedyN/IO2fBJmRQDkqCGcip2gloh8YPPhCuKGjCqqqh2L+Tg9PKW7w==} @@ -4371,6 +4506,25 @@ packages: resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + '@testing-library/svelte-core@1.0.0': + resolution: {integrity: sha512-VkUePoLV6oOYwSUvX6ShA8KLnJqZiYMIbP2JW2t0GLWLkJxKGvuH5qrrZBV/X7cXFnLGuFQEC7RheYiZOW68KQ==} + engines: {node: '>=16'} + peerDependencies: + svelte: ^3 || ^4 || ^5 || ^5.0.0-next.0 + + '@testing-library/svelte@5.3.1': + resolution: {integrity: sha512-8Ez7ZOqW5geRf9PF5rkuopODe5RGy3I9XR+kc7zHh26gBiktLaxTfKmhlGaSHYUOTQE7wFsLMN9xCJVCszw47w==} + engines: {node: '>= 10'} + peerDependencies: + svelte: ^3 || ^4 || ^5 || ^5.0.0-next.0 + vite: '*' + vitest: '*' + peerDependenciesMeta: + vite: + optional: true + vitest: + optional: true + '@testing-library/user-event@14.5.2': resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==} engines: {node: '>=12', npm: '>=6'} @@ -4398,6 +4552,9 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/d3-array@3.2.1': resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} @@ -4645,41 +4802,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -4716,9 +4881,23 @@ packages: '@vitest/expect@2.0.5': resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.0.18': resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/mocker@4.0.18': resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} peerDependencies: @@ -4736,18 +4915,30 @@ packages: '@vitest/pretty-format@2.1.9': resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.0.18': resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/runner@4.0.18': resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/snapshot@4.0.18': resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} '@vitest/spy@2.0.5': resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.0.18': resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} @@ -4757,6 +4948,9 @@ packages: '@vitest/utils@2.1.9': resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.0.18': resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} @@ -4933,6 +5127,10 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.1: + resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} + engines: {node: '>= 0.4'} + aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} @@ -5163,6 +5361,10 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -5404,6 +5606,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + core-js-compat@3.48.0: resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} @@ -5443,6 +5649,10 @@ packages: engines: {node: '>=4'} hasBin: true + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + cssstyle@6.2.0: resolution: {integrity: sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==} engines: {node: '>=20'} @@ -5497,6 +5707,10 @@ packages: damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + data-urls@7.0.0: resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -5562,6 +5776,9 @@ packages: decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + dedent-js@1.0.1: + resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -5620,6 +5837,9 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + devalue@5.6.4: + resolution: {integrity: sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==} + devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -5966,6 +6186,9 @@ packages: jiti: optional: true + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + espree@10.4.0: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5979,6 +6202,13 @@ packages: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrap@2.2.4: + resolution: {integrity: sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==} + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -6599,6 +6829,10 @@ packages: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + html-encoding-sniffer@6.0.0: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -6627,6 +6861,10 @@ packages: hyphenate-style-name@1.1.0: resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} @@ -6659,6 +6897,9 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -6815,6 +7056,9 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -6930,6 +7174,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@3.14.2: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true @@ -6949,6 +7196,15 @@ packages: resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} engines: {node: '>=12.0.0'} + jsdom@26.1.0: + resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsdom@28.1.0: resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -7008,6 +7264,10 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + lan-network@0.1.7: resolution: {integrity: sha512-mnIlAEMu4OyEvUNdzco9xpuB9YVcPkQec+QsgycBCtPZvEqWPCDPfbAE4OJMdBBWpZWtpCn1xw9jJYlwjWI5zQ==} hasBin: true @@ -7065,24 +7325,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.31.1: resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} @@ -7111,6 +7375,9 @@ packages: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} engines: {node: '>=6.11.5'} + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -7597,6 +7864,14 @@ packages: react-dom: optional: true + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -7745,6 +8020,9 @@ packages: nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + ob1@0.83.3: resolution: {integrity: sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA==} engines: {node: '>=20.19.4'} @@ -8535,6 +8813,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -8544,6 +8825,10 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -8585,15 +8870,13 @@ packages: scroll-into-view-if-needed@3.1.0: resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} - hasBin: true - semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -8614,6 +8897,9 @@ packages: resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} engines: {node: '>= 0.8.0'} + set-cookie-parser@3.0.1: + resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -8680,6 +8966,10 @@ packages: simple-plist@1.3.1: resolution: {integrity: sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -8840,6 +9130,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + structured-headers@0.4.1: resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==} @@ -8895,10 +9188,23 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - swr@2.4.1: - resolution: {integrity: sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==} + svelte-check@4.4.5: + resolution: {integrity: sha512-1bSwIRCvvmSHrlK52fOlZmVtUZgil43jNL/2H18pRpa+eQjzGt6e3zayxhp1S7GajPFKNM/2PMCG+DZFHlG9fw==} + engines: {node: '>= 18.0.0'} + hasBin: true + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' + + svelte2tsx@0.7.52: + resolution: {integrity: sha512-svdT1FTrCLpvlU62evO5YdJt/kQ7nxgQxII/9BpQUvKr+GJRVdAXNVw8UWOt0fhoe5uWKyU0WsUTMRVAtRbMQg==} peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 + typescript: ^4.9.4 || ^5.0.0 + + svelte@5.53.12: + resolution: {integrity: sha512-4x/uk4rQe/d7RhfvS8wemTfNjQ0bJbKvamIzRBfTe2eHHjzBZ7PZicUQrC2ryj83xxEacfA1zHKd1ephD1tAxA==} + engines: {node: '>=18'} symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -8975,6 +9281,9 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -8983,10 +9292,18 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + tinyrainbow@1.2.0: resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + tinyrainbow@3.0.3: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} @@ -8995,9 +9312,20 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + tldts-core@7.0.25: resolution: {integrity: sha512-ZjCZK0rppSBu7rjHYDYsEaMOIbbT+nWF57hKkv4IUmZWBNrBWBOjIElc0mKRgLM8bm7x/BBlof6t2gi/Oq/Asw==} + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + tldts@7.0.25: resolution: {integrity: sha512-keinCnPbwXEUG3ilrWQZU+CqcTTzHq9m2HhoUP2l7Xmi8l1LuijAXLpAJ5zRW+ifKTNscs4NdCkfkDCBYm352w==} hasBin: true @@ -9013,6 +9341,14 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + tough-cookie@6.0.0: resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} engines: {node: '>=16'} @@ -9020,6 +9356,10 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + tr46@6.0.0: resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} engines: {node: '>=20'} @@ -9288,6 +9628,11 @@ packages: victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + vite@5.4.19: resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -9319,6 +9664,46 @@ packages: terser: optional: true + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vite@7.3.1: resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -9359,6 +9744,42 @@ packages: yaml: optional: true + vitefu@1.1.2: + resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vitest@4.0.18: resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -9430,6 +9851,10 @@ packages: resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==} engines: {node: '>=8'} + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + webidl-conversions@8.0.1: resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} engines: {node: '>=20'} @@ -9451,9 +9876,18 @@ packages: webpack-cli: optional: true + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + whatwg-fetch@3.6.20: resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + whatwg-mimetype@5.0.0: resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} engines: {node: '>=20'} @@ -9462,6 +9896,10 @@ packages: resolution: {integrity: sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==} engines: {node: '>=10'} + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + whatwg-url@16.0.1: resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -9607,6 +10045,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zimmerframe@1.1.4: + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} + zod-validation-error@4.0.2: resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} engines: {node: '>=18.0.0'} @@ -9689,8 +10130,16 @@ snapshots: '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 '@asamuzakjp/css-color@5.0.1': dependencies: @@ -9777,8 +10226,8 @@ snapshots: dependencies: '@babel/parser': 7.27.5 '@babel/types': 7.27.6 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/generator@7.29.1': @@ -10430,15 +10879,29 @@ snapshots: - '@chromatic-com/playwright' - react + '@csstools/color-helpers@5.1.0': {} + '@csstools/color-helpers@6.0.2': optional: true + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 optional: true + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-color-parser@4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/color-helpers': 6.0.2 @@ -10447,6 +10910,10 @@ snapshots: '@csstools/css-tokenizer': 4.0.0 optional: true + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-tokenizer': 4.0.0 @@ -10455,6 +10922,8 @@ snapshots: '@csstools/css-syntax-patches-for-csstree@1.1.0': optional: true + '@csstools/css-tokenizer@3.0.4': {} + '@csstools/css-tokenizer@4.0.0': optional: true @@ -11405,13 +11874,13 @@ snapshots: '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/remapping@2.3.5': dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/resolve-uri@3.1.2': {} @@ -11422,15 +11891,8 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/sourcemap-codec@1.5.0': {} - '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -11703,6 +12165,8 @@ snapshots: '@pkgr/core@0.2.9': {} + '@polka/url@1.0.0-next.29': {} + '@posthog/core@1.23.2': dependencies: cross-spawn: 7.0.6 @@ -13811,7 +14275,7 @@ snapshots: jsdoc-type-pratt-parser: 4.1.0 process: 0.11.10 recast: 0.23.11 - semver: 7.7.2 + semver: 7.7.4 util: 0.12.5 ws: 8.18.2 optionalDependencies: @@ -13910,6 +14374,69 @@ snapshots: dependencies: storybook: 8.6.14(prettier@3.5.3) + '@sveltejs/acorn-typescript@1.0.9(acorn@8.16.0)': + dependencies: + acorn: 8.16.0 + + '@sveltejs/adapter-auto@4.0.0(@sveltejs/kit@2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))': + dependencies: + '@sveltejs/kit': 2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + import-meta-resolve: 4.2.0 + + '@sveltejs/kit@2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': + dependencies: + '@standard-schema/spec': 1.1.0 + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + '@types/cookie': 0.6.0 + acorn: 8.16.0 + cookie: 0.6.0 + devalue: 5.6.4 + esm-env: 1.2.2 + kleur: 4.1.5 + magic-string: 0.30.21 + mrmime: 2.0.1 + set-cookie-parser: 3.0.1 + sirv: 3.0.2 + svelte: 5.53.12 + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + optionalDependencies: + '@opentelemetry/api': 1.9.0 + typescript: 5.9.3 + + '@sveltejs/package@2.5.7(svelte@5.53.12)(typescript@5.9.3)': + dependencies: + chokidar: 5.0.0 + kleur: 4.1.5 + sade: 1.8.1 + semver: 7.7.4 + svelte: 5.53.12 + svelte2tsx: 0.7.52(svelte@5.53.12)(typescript@5.9.3) + transitivePeerDependencies: + - typescript + + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': + dependencies: + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + debug: 4.4.3 + svelte: 5.53.12 + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + transitivePeerDependencies: + - supports-color + + '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + debug: 4.4.3 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.21 + svelte: 5.53.12 + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vitefu: 1.1.2(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + transitivePeerDependencies: + - supports-color + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -14059,6 +14586,19 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 + '@testing-library/svelte-core@1.0.0(svelte@5.53.12)': + dependencies: + svelte: 5.53.12 + + '@testing-library/svelte@5.3.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': + dependencies: + '@testing-library/dom': 10.4.0 + '@testing-library/svelte-core': 1.0.0(svelte@5.53.12) + svelte: 5.53.12 + optionalDependencies: + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': dependencies: '@testing-library/dom': 10.4.0 @@ -14096,6 +14636,8 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/cookie@0.6.0': {} + '@types/d3-array@3.2.1': {} '@types/d3-color@3.1.3': {} @@ -14227,8 +14769,7 @@ snapshots: '@types/stack-utils@2.0.3': {} - '@types/trusted-types@2.0.7': - optional: true + '@types/trusted-types@2.0.7': {} '@types/unist@2.0.11': {} @@ -14415,6 +14956,14 @@ snapshots: chai: 5.2.0 tinyrainbow: 1.2.0 + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.0 + tinyrainbow: 2.0.0 + '@vitest/expect@4.0.18': dependencies: '@standard-schema/spec': 1.1.0 @@ -14424,13 +14973,21 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': + '@vitest/mocker@3.2.4(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + + '@vitest/mocker@4.0.18(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) '@vitest/pretty-format@2.0.5': dependencies: @@ -14440,15 +14997,31 @@ snapshots: dependencies: tinyrainbow: 1.2.0 + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + '@vitest/pretty-format@4.0.18': dependencies: tinyrainbow: 3.0.3 + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 + '@vitest/runner@4.0.18': dependencies: '@vitest/utils': 4.0.18 pathe: 2.0.3 + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.21 + pathe: 2.0.3 + '@vitest/snapshot@4.0.18': dependencies: '@vitest/pretty-format': 4.0.18 @@ -14459,6 +15032,10 @@ snapshots: dependencies: tinyspy: 3.0.2 + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + '@vitest/spy@4.0.18': {} '@vitest/utils@2.0.5': @@ -14474,6 +15051,12 @@ snapshots: loupe: 3.1.4 tinyrainbow: 1.2.0 + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.1.4 + tinyrainbow: 2.0.0 + '@vitest/utils@4.0.18': dependencies: '@vitest/pretty-format': 4.0.18 @@ -14671,6 +15254,8 @@ snapshots: dependencies: dequal: 2.0.3 + aria-query@5.3.1: {} + aria-query@5.3.2: {} array-buffer-byte-length@1.0.2: @@ -14981,6 +15566,8 @@ snapshots: bytes@3.1.2: {} + cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -15207,6 +15794,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie@0.6.0: {} + core-js-compat@3.48.0: dependencies: browserslist: 4.28.1 @@ -15254,6 +15843,11 @@ snapshots: cssesc@3.0.0: {} + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + cssstyle@6.2.0: dependencies: '@asamuzakjp/css-color': 5.0.1 @@ -15304,6 +15898,11 @@ snapshots: damerau-levenshtein@1.0.8: {} + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + data-urls@7.0.0: dependencies: whatwg-mimetype: 5.0.0 @@ -15352,13 +15951,14 @@ snapshots: decimal.js-light@2.5.1: {} - decimal.js@10.6.0: - optional: true + decimal.js@10.6.0: {} decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 + dedent-js@1.0.1: {} + deep-eql@5.0.2: {} deep-extend@0.6.0: {} @@ -15400,6 +16000,8 @@ snapshots: detect-node-es@1.1.0: {} + devalue@5.6.4: {} + devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -15952,6 +16554,8 @@ snapshots: transitivePeerDependencies: - supports-color + esm-env@1.2.2: {} + espree@10.4.0: dependencies: acorn: 8.15.0 @@ -15964,6 +16568,15 @@ snapshots: dependencies: estraverse: 5.3.0 + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrap@2.2.4: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@typescript-eslint/types': 8.56.1 + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 @@ -16700,6 +17313,10 @@ snapshots: dependencies: lru-cache: 10.4.3 + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + html-encoding-sniffer@6.0.0: dependencies: '@exodus/bytes': 1.15.0 @@ -16725,7 +17342,6 @@ snapshots: debug: 4.4.3 transitivePeerDependencies: - supports-color - optional: true https-proxy-agent@7.0.6: dependencies: @@ -16740,6 +17356,10 @@ snapshots: hyphenate-style-name@1.1.0: {} + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -16763,6 +17383,8 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-meta-resolve@4.2.0: {} + imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -16908,8 +17530,11 @@ snapshots: is-plain-obj@4.1.0: {} - is-potential-custom-element-name@1.0.1: - optional: true + is-potential-custom-element-name@1.0.1: {} + + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.8 is-regex@1.2.1: dependencies: @@ -17071,6 +17696,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@3.14.2: dependencies: argparse: 1.0.10 @@ -17088,6 +17715,33 @@ snapshots: jsdoc-type-pratt-parser@4.1.0: {} + jsdom@26.1.0: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.23 + parse5: 7.3.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.18.2 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsdom@28.1.0: dependencies: '@acemir/cssom': 0.9.31 @@ -17159,6 +17813,8 @@ snapshots: kleur@3.0.3: {} + kleur@4.1.5: {} + lan-network@0.1.7: {} language-subtag-registry@0.3.23: {} @@ -17236,6 +17892,8 @@ snapshots: loader-runner@4.3.0: {} + locate-character@3.0.0: {} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -17297,11 +17955,11 @@ snapshots: magic-string@0.27.0: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 magic-string@0.30.17: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 magic-string@0.30.21: dependencies: @@ -18202,6 +18860,10 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) + mri@1.2.0: {} + + mrmime@2.0.1: {} + ms@2.0.0: {} ms@2.1.3: {} @@ -18363,6 +19025,8 @@ snapshots: nullthrows@1.1.1: {} + nwsapi@2.2.23: {} + ob1@0.83.3: dependencies: flow-enums-runtime: 0.0.6 @@ -19495,6 +20159,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.43.0 fsevents: 2.3.3 + rrweb-cssom@0.8.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -19507,6 +20173,10 @@ snapshots: dependencies: tslib: 2.8.1 + sade@1.8.1: + dependencies: + mri: 1.2.0 + safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 @@ -19543,7 +20213,6 @@ snapshots: saxes@6.0.0: dependencies: xmlchars: 2.2.0 - optional: true scheduler@0.27.0: {} @@ -19558,9 +20227,9 @@ snapshots: dependencies: compute-scroll-into-view: 3.1.1 - semver@6.3.1: {} + scule@1.3.0: {} - semver@7.7.2: {} + semver@6.3.1: {} semver@7.7.4: {} @@ -19597,6 +20266,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-cookie-parser@3.0.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -19714,6 +20385,12 @@ snapshots: bplist-parser: 0.3.1 plist: 3.1.0 + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + sisteransi@1.0.5: {} skin-tone@2.0.0: @@ -19878,6 +20555,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + structured-headers@0.4.1: {} style-to-js@1.1.17: @@ -19943,14 +20624,45 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - swr@2.4.1(react@19.2.3): + svelte-check@4.4.5(picomatch@4.0.3)(svelte@5.53.12)(typescript@5.9.3): dependencies: - dequal: 2.0.3 - react: 19.2.3 - use-sync-external-store: 1.6.0(react@19.2.3) + '@jridgewell/trace-mapping': 0.3.31 + chokidar: 4.0.3 + fdir: 6.5.0(picomatch@4.0.3) + picocolors: 1.1.1 + sade: 1.8.1 + svelte: 5.53.12 + typescript: 5.9.3 + transitivePeerDependencies: + - picomatch - symbol-tree@3.2.4: - optional: true + svelte2tsx@0.7.52(svelte@5.53.12)(typescript@5.9.3): + dependencies: + dedent-js: 1.0.1 + scule: 1.3.0 + svelte: 5.53.12 + typescript: 5.9.3 + + svelte@5.53.12: + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) + '@types/estree': 1.0.8 + '@types/trusted-types': 2.0.7 + acorn: 8.16.0 + aria-query: 5.3.1 + axobject-query: 4.1.0 + clsx: 2.1.1 + devalue: 5.6.4 + esm-env: 1.2.2 + esrap: 2.2.4 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.21 + zimmerframe: 1.1.4 + + symbol-tree@3.2.4: {} synckit@0.11.12: dependencies: @@ -20042,6 +20754,8 @@ snapshots: tinybench@2.9.0: {} + tinyexec@0.3.2: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -20049,15 +20763,27 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinypool@1.1.1: {} + tinyrainbow@1.2.0: {} + tinyrainbow@2.0.0: {} + tinyrainbow@3.0.3: {} tinyspy@3.0.2: {} + tinyspy@4.0.4: {} + + tldts-core@6.1.86: {} + tldts-core@7.0.25: optional: true + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + tldts@7.0.25: dependencies: tldts-core: 7.0.25 @@ -20071,6 +20797,12 @@ snapshots: toidentifier@1.0.1: {} + totalist@3.0.1: {} + + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + tough-cookie@6.0.0: dependencies: tldts: 7.0.25 @@ -20078,6 +20810,10 @@ snapshots: tr46@0.0.3: {} + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + tr46@6.0.0: dependencies: punycode: 2.3.1 @@ -20269,7 +21005,7 @@ snapshots: unplugin@1.16.1: dependencies: - acorn: 8.15.0 + acorn: 8.16.0 webpack-virtual-modules: 0.6.2 unrs-resolver@1.11.1: @@ -20415,6 +21151,27 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + vite-node@3.2.4(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite@5.4.19(@types/node@22.15.32)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0): dependencies: esbuild: 0.21.5 @@ -20427,6 +21184,24 @@ snapshots: sass: 1.89.2 terser: 5.43.0 + vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.43.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 25.3.2 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.31.1 + sass: 1.89.2 + terser: 5.43.0 + tsx: 4.20.3 + yaml: 2.8.0 + vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: esbuild: 0.27.3 @@ -20444,11 +21219,59 @@ snapshots: terser: 5.43.0 tsx: 4.20.3 yaml: 2.8.0 + optional: true + + vitefu@1.1.2(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)): + optionalDependencies: + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + + vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.0 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 25.3.2 + jsdom: 26.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/mocker': 4.0.18(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18 @@ -20465,7 +21288,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 @@ -20489,7 +21312,6 @@ snapshots: w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 - optional: true walker@1.0.8: dependencies: @@ -20516,6 +21338,8 @@ snapshots: webidl-conversions@5.0.0: {} + webidl-conversions@7.0.0: {} + webidl-conversions@8.0.1: optional: true @@ -20554,8 +21378,14 @@ snapshots: - esbuild - uglify-js + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + whatwg-fetch@3.6.20: {} + whatwg-mimetype@4.0.0: {} + whatwg-mimetype@5.0.0: optional: true @@ -20565,6 +21395,11 @@ snapshots: punycode: 2.3.1 webidl-conversions: 5.0.0 + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + whatwg-url@16.0.1: dependencies: '@exodus/bytes': 1.15.0 @@ -20665,8 +21500,7 @@ snapshots: simple-plist: 1.3.1 uuid: 7.0.3 - xml-name-validator@5.0.0: - optional: true + xml-name-validator@5.0.0: {} xml2js@0.6.0: dependencies: @@ -20677,8 +21511,7 @@ snapshots: xmlbuilder@15.1.1: {} - xmlchars@2.2.0: - optional: true + xmlchars@2.2.0: {} xtend@4.0.2: {} @@ -20704,6 +21537,8 @@ snapshots: yocto-queue@0.1.0: {} + zimmerframe@1.1.4: {} + zod-validation-error@4.0.2(zod@4.3.6): dependencies: zod: 4.3.6 From fc85d2d39964f14cdacee4451c676027b20b3fb9 Mon Sep 17 00:00:00 2001 From: shipooor Date: Mon, 16 Mar 2026 17:01:38 +0500 Subject: [PATCH 03/12] Add Svelte 5 renderer (svelte-lang) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce @openuidev/svelte-lang — a Svelte 5 port of react-lang built on top of @openuidev/lang-core. Uses runes ($state, $derived, $effect, $props), snippets for renderNode, getContext/setContext for the OpenUI context, and for error handling. Includes 30 passing tests. --- packages/svelte-lang/README.md | 189 ++++++++++++++++++ packages/svelte-lang/package.json | 78 ++++++++ .../src/__tests__/Renderer.test.ts | 127 ++++++++++++ .../svelte-lang/src/__tests__/library.test.ts | 127 ++++++++++++ .../src/__tests__/validation.test.ts | 127 ++++++++++++ .../svelte-lang/src/lib/RenderNode.svelte | 48 +++++ packages/svelte-lang/src/lib/Renderer.svelte | 162 +++++++++++++++ .../svelte-lang/src/lib/context.svelte.ts | 150 ++++++++++++++ packages/svelte-lang/src/lib/index.ts | 70 +++++++ packages/svelte-lang/src/lib/library.ts | 69 +++++++ .../svelte-lang/src/lib/validation.svelte.ts | 101 ++++++++++ packages/svelte-lang/svelte.config.js | 6 + packages/svelte-lang/tsconfig.json | 11 + packages/svelte-lang/vite.config.ts | 13 ++ 14 files changed, 1278 insertions(+) create mode 100644 packages/svelte-lang/README.md create mode 100644 packages/svelte-lang/package.json create mode 100644 packages/svelte-lang/src/__tests__/Renderer.test.ts create mode 100644 packages/svelte-lang/src/__tests__/library.test.ts create mode 100644 packages/svelte-lang/src/__tests__/validation.test.ts create mode 100644 packages/svelte-lang/src/lib/RenderNode.svelte create mode 100644 packages/svelte-lang/src/lib/Renderer.svelte create mode 100644 packages/svelte-lang/src/lib/context.svelte.ts create mode 100644 packages/svelte-lang/src/lib/index.ts create mode 100644 packages/svelte-lang/src/lib/library.ts create mode 100644 packages/svelte-lang/src/lib/validation.svelte.ts create mode 100644 packages/svelte-lang/svelte.config.js create mode 100644 packages/svelte-lang/tsconfig.json create mode 100644 packages/svelte-lang/vite.config.ts diff --git a/packages/svelte-lang/README.md b/packages/svelte-lang/README.md new file mode 100644 index 000000000..4627ba62d --- /dev/null +++ b/packages/svelte-lang/README.md @@ -0,0 +1,189 @@ +# @openuidev/svelte-lang + +Svelte 5 runtime for [OpenUI](https://openui.com) — define component libraries, generate model prompts, and render structured UI from streaming LLM output. + +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/thesysdev/openui/blob/main/LICENSE) + +## Install + +```bash +npm install @openuidev/svelte-lang +# or +pnpm add @openuidev/svelte-lang +``` + +**Peer dependencies:** `svelte >=5.0.0` + +## Overview + +`@openuidev/svelte-lang` provides three core capabilities: + +1. **Define components** — Use `defineComponent` and `createLibrary` to declare what the model is allowed to generate, with typed props via Zod schemas. +2. **Generate prompts** — Call `library.prompt()` to produce a system prompt that instructs the model how to emit OpenUI Lang output. +3. **Render output** — Use `` to parse and progressively render streamed OpenUI Lang into Svelte components. + +## Quick Start + +### 1. Define a component + +```svelte + + + +

+ Hello, {props.name}! +
+``` + +```ts +import { defineComponent } from "@openuidev/svelte-lang"; +import { z } from "zod"; +import Greeting from "./Greeting.svelte"; + +const GreetingDef = defineComponent({ + name: "Greeting", + description: "Displays a greeting message", + props: z.object({ + name: z.string().describe("The person's name"), + mood: z.enum(["happy", "excited"]).optional().describe("Tone of the greeting"), + }), + component: Greeting, +}); +``` + +### 2. Create a library + +```ts +import { createLibrary } from "@openuidev/svelte-lang"; + +const library = createLibrary({ + components: [GreetingDef, CardDef, TableDef /* ... */], + root: "Card", // optional default root component +}); +``` + +### 3. Generate a system prompt + +```ts +const systemPrompt = library.prompt({ + preamble: "You are a helpful assistant.", + additionalRules: ["Always greet the user by name."], + examples: [""], +}); +``` + +### 4. Render streamed output + +```svelte + + + console.log("Action:", event)} +/> +``` + +## API Reference + +### Component Definition + +| Export | Description | +| :--- | :--- | +| `defineComponent(config)` | Define a single component with a name, Zod props schema, description, and Svelte component | +| `createLibrary(definition)` | Create a library from an array of defined components | + +### Rendering + +| Export | Description | +| :--- | :--- | +| `Renderer` | Svelte component that parses and renders OpenUI Lang output | + +**`RendererProps`:** + +| Prop | Type | Description | +| :--- | :--- | :--- | +| `response` | `string \| null` | Raw OpenUI Lang text from the model | +| `library` | `Library` | Component library from `createLibrary()` | +| `isStreaming` | `boolean` | Whether the model is still streaming (disables form interactions) | +| `onAction` | `(event: ActionEvent) => void` | Callback when a component triggers an action | +| `onStateUpdate` | `(state: Record) => void` | Callback when form field values change | +| `initialState` | `Record` | Initial form state for hydration | +| `onParseResult` | `(result: ParseResult \| null) => void` | Callback when the parse result changes | + +### Parser (Server-Side) + +| Export | Description | +| :--- | :--- | +| `createParser(library)` | Create a one-shot parser for complete OpenUI Lang text | +| `createStreamingParser(library)` | Create an incremental parser for streaming input | + +### Context Getters + +Use these inside component renderers to interact with the rendering context: + +| Function | Description | +| :--- | :--- | +| `getIsStreaming()` | Whether the model is still streaming | +| `getTriggerAction()` | Trigger an action event | +| `getGetFieldValue()` | Get a form field's current value | +| `getSetFieldValue()` | Set a form field's value | +| `useSetDefaultValue()` | Set a field's default value | +| `getFormName()` | Get the current form's name | + +> **Note:** Svelte components receive `renderNode` as a snippet prop instead of via context. This avoids stale-closure issues and is idiomatic Svelte 5. + +### Form Validation + +| Export | Description | +| :--- | :--- | +| `getFormValidation()` | Access form validation state | +| `createFormValidation()` | Create a form validation context | +| `validate(value, rules)` | Run validation rules against a value | +| `builtInValidators` | Built-in validators (required, email, min, max, etc.) | + +### Types + +```ts +import type { + Library, + LibraryDefinition, + DefinedComponent, + ComponentRenderer, + ComponentRenderProps, + ComponentGroup, + PromptOptions, + RendererProps, + ActionEvent, + ElementNode, + ParseResult, + LibraryJSONSchema, +} from "@openuidev/svelte-lang"; +``` + +## JSON Schema Output + +Libraries can also produce a JSON Schema representation of their components: + +```ts +const schema = library.toJSONSchema(); +// schema["$defs"]["Card"] → { properties: {...}, required: [...] } +// schema["$defs"]["Greeting"] → { properties: {...}, required: [...] } +``` + +## Documentation + +Full documentation, guides, and the language specification are available at **[openui.com](https://openui.com)**. + +## License + +[MIT](https://github.com/thesysdev/openui/blob/main/LICENSE) diff --git a/packages/svelte-lang/package.json b/packages/svelte-lang/package.json new file mode 100644 index 000000000..12ec22803 --- /dev/null +++ b/packages/svelte-lang/package.json @@ -0,0 +1,78 @@ +{ + "name": "@openuidev/svelte-lang", + "version": "0.1.0", + "description": "Define component libraries, generate LLM system prompts, and render streaming OpenUI Lang output in Svelte 5 — the Svelte runtime for OpenUI generative UI", + "license": "MIT", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "svelte": "./dist/index.js", + "files": [ + "dist", + "README.md" + ], + "exports": { + ".": { + "svelte": "./dist/index.js", + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "scripts": { + "build": "svelte-package", + "watch": "svelte-package --watch", + "lint:check": "eslint ./src", + "lint:fix": "eslint ./src --fix", + "format:fix": "prettier --write ./src", + "format:check": "prettier --check ./src", + "prepare": "pnpm run build", + "check": "svelte-check --tsconfig ./tsconfig.json", + "test": "vitest run", + "ci": "pnpm run lint:check && pnpm run format:check" + }, + "keywords": [ + "openui", + "generative-ui", + "svelte", + "svelte5", + "llm", + "streaming", + "renderer", + "parser", + "ai", + "components", + "prompt-generation", + "zod", + "ui-generation", + "model-driven-ui", + "openui-lang" + ], + "homepage": "https://openui.com", + "repository": { + "type": "git", + "url": "https://github.com/thesysdev/openui.git", + "directory": "packages/svelte-lang" + }, + "bugs": { + "url": "https://github.com/thesysdev/openui/issues" + }, + "author": "engineering@thesys.dev", + "dependencies": { + "@openuidev/lang-core": "workspace:^", + "zod": "^4.0.0" + }, + "peerDependencies": { + "svelte": ">=5.0.0" + }, + "devDependencies": { + "@sveltejs/package": "^2.3.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@testing-library/svelte": "^5.2.0", + "jsdom": "^26.1.0", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "^5.0.0", + "vite": "^6.0.0", + "vitest": "^3.0.0" + } +} diff --git a/packages/svelte-lang/src/__tests__/Renderer.test.ts b/packages/svelte-lang/src/__tests__/Renderer.test.ts new file mode 100644 index 000000000..23ac87969 --- /dev/null +++ b/packages/svelte-lang/src/__tests__/Renderer.test.ts @@ -0,0 +1,127 @@ +import { describe, it, expect, vi } from "vitest"; +import { render } from "@testing-library/svelte"; +import { z } from "zod"; +import { tick } from "svelte"; +import Renderer from "../lib/Renderer.svelte"; +import { defineComponent, createLibrary } from "../lib/library.js"; + +// Dummy renderer — never actually renders DOM, used for parser/callback tests +const DummyComponent = (() => null) as any; + +const TextContent = defineComponent({ + name: "TextContent", + props: z.object({ text: z.string() }), + description: "Displays text content", + component: DummyComponent, +}); + +const library = createLibrary({ + components: [TextContent], + root: "TextContent", +}); + +// openui-lang uses assignment syntax: `identifier = Component(args)` +const VALID_RESPONSE = 'root = TextContent("Hello world")'; + +// ─── Renderer ─────────────────────────────────────────────────────────────── + +describe("Renderer", () => { + it("renders without errors when response is null", () => { + const { container } = render(Renderer, { + props: { + response: null, + library, + }, + }); + + // Should render an empty container (no crash) + expect(container).toBeDefined(); + }); + + it("renders without errors when response is empty string", () => { + const { container } = render(Renderer, { + props: { + response: "", + library, + }, + }); + + expect(container).toBeDefined(); + }); + + it("calls onParseResult with null when response is null", async () => { + const onParseResult = vi.fn(); + + render(Renderer, { + props: { + response: null, + library, + onParseResult, + }, + }); + + // $effect runs asynchronously — flush microtasks + await tick(); + + expect(onParseResult).toHaveBeenCalledWith(null); + }); + + it("calls onParseResult with a ParseResult when given valid openui-lang", async () => { + const onParseResult = vi.fn(); + + render(Renderer, { + props: { + response: VALID_RESPONSE, + library, + onParseResult, + }, + }); + + await tick(); + + expect(onParseResult).toHaveBeenCalled(); + const result = onParseResult.mock.calls[onParseResult.mock.calls.length - 1]![0]; + expect(result).not.toBeNull(); + expect(result.root).toBeDefined(); + expect(result.root).not.toBeNull(); + }); + + it("parse result contains the correct component typeName", async () => { + const onParseResult = vi.fn(); + + render(Renderer, { + props: { + response: VALID_RESPONSE, + library, + onParseResult, + }, + }); + + await tick(); + + const result = onParseResult.mock.calls[onParseResult.mock.calls.length - 1]![0]; + expect(result?.root?.typeName).toBe("TextContent"); + }); + + it("defaults isStreaming to false", () => { + // Should not throw when isStreaming is omitted + const { container } = render(Renderer, { + props: { + response: null, + library, + }, + }); + expect(container).toBeDefined(); + }); + + it("accepts isStreaming prop without errors", () => { + const { container } = render(Renderer, { + props: { + response: null, + library, + isStreaming: true, + }, + }); + expect(container).toBeDefined(); + }); +}); diff --git a/packages/svelte-lang/src/__tests__/library.test.ts b/packages/svelte-lang/src/__tests__/library.test.ts new file mode 100644 index 000000000..f37f63364 --- /dev/null +++ b/packages/svelte-lang/src/__tests__/library.test.ts @@ -0,0 +1,127 @@ +import { describe, it, expect } from "vitest"; +import { z } from "zod"; +import { defineComponent, createLibrary } from "../lib/library.js"; + +// Dummy renderer — never actually called in these tests +const DummyComponent = (() => null) as any; + +function makeComponent(name: string, schema: z.ZodObject, description: string) { + return defineComponent({ + name, + props: schema, + description, + component: DummyComponent, + }); +} + +// ─── defineComponent ──────────────────────────────────────────────────────── + +describe("defineComponent", () => { + it("returns an object with name, props, description, component, and ref", () => { + const schema = z.object({ label: z.string() }); + const result = defineComponent({ + name: "Badge", + props: schema, + description: "A simple badge", + component: DummyComponent, + }); + + expect(result.name).toBe("Badge"); + expect(result.props).toBe(schema); + expect(result.description).toBe("A simple badge"); + expect(result.component).toBe(DummyComponent); + expect(result.ref).toBeDefined(); + }); + + it("registers the Zod schema in the global registry", () => { + const schema = z.object({ title: z.string() }); + const comp = defineComponent({ + name: "Heading", + props: schema, + description: "A heading element", + component: DummyComponent, + }); + + // After defineComponent, the schema should be in the global registry + expect(z.globalRegistry.has(comp.props)).toBe(true); + }); +}); + +// ─── createLibrary ────────────────────────────────────────────────────────── + +describe("createLibrary", () => { + const TextContent = makeComponent( + "TextContent", + z.object({ text: z.string() }), + "Displays text content", + ); + + const Container = makeComponent( + "Container", + z.object({ title: z.string() }), + "A container with a title", + ); + + it("creates a library with a components record", () => { + const lib = createLibrary({ components: [TextContent, Container] }); + + expect(lib.components.TextContent).toBe(TextContent); + expect(lib.components.Container).toBe(Container); + expect(Object.keys(lib.components)).toHaveLength(2); + }); + + it("stores root and componentGroups", () => { + const lib = createLibrary({ + components: [TextContent], + root: "TextContent", + componentGroups: [{ name: "Display", components: ["TextContent"] }], + }); + + expect(lib.root).toBe("TextContent"); + expect(lib.componentGroups).toEqual([{ name: "Display", components: ["TextContent"] }]); + }); + + it("throws if root component is not found in components", () => { + expect(() => + createLibrary({ + components: [TextContent], + root: "NonExistent", + }), + ).toThrow(/Root component "NonExistent" was not found/); + }); + + it("prompt() returns a string containing component descriptions", () => { + const lib = createLibrary({ + components: [TextContent, Container], + root: "TextContent", + }); + + const prompt = lib.prompt(); + expect(typeof prompt).toBe("string"); + expect(prompt.length).toBeGreaterThan(0); + // The prompt should mention at least one component name + expect(prompt).toContain("TextContent"); + }); + + it("toJSONSchema() returns an object with $defs", () => { + const lib = createLibrary({ + components: [TextContent], + root: "TextContent", + }); + + const schema = lib.toJSONSchema() as Record; + expect(schema).toBeDefined(); + expect(typeof schema).toBe("object"); + expect(schema["$defs"]).toBeDefined(); + expect(typeof schema["$defs"]).toBe("object"); + }); + + it("works without a root component", () => { + const lib = createLibrary({ components: [TextContent] }); + + expect(lib.root).toBeUndefined(); + // prompt/schema should still work + expect(typeof lib.prompt()).toBe("string"); + expect(lib.toJSONSchema()).toBeDefined(); + }); +}); diff --git a/packages/svelte-lang/src/__tests__/validation.test.ts b/packages/svelte-lang/src/__tests__/validation.test.ts new file mode 100644 index 000000000..16a7f8cb7 --- /dev/null +++ b/packages/svelte-lang/src/__tests__/validation.test.ts @@ -0,0 +1,127 @@ +import { describe, it, expect } from "vitest"; +import { + builtInValidators, + parseRules, + parseStructuredRules, + validate, +} from "../lib/validation.svelte.js"; + +// ─── builtInValidators ────────────────────────────────────────────────────── + +describe("builtInValidators", () => { + it("has all expected validators", () => { + const expected = [ + "required", + "email", + "url", + "numeric", + "min", + "max", + "minLength", + "maxLength", + "pattern", + ]; + for (const name of expected) { + expect(builtInValidators[name]).toBeDefined(); + expect(typeof builtInValidators[name]).toBe("function"); + } + }); +}); + +// ─── parseRules ───────────────────────────────────────────────────────────── + +describe("parseRules", () => { + it("parses simple rule strings into ParsedRule objects", () => { + const result = parseRules(["required", "email"]); + expect(result).toEqual([{ type: "required" }, { type: "email" }]); + }); + + it("parses rules with numeric arguments", () => { + const result = parseRules(["min:8", "maxLength:100"]); + expect(result).toEqual([ + { type: "min", arg: 8 }, + { type: "maxLength", arg: 100 }, + ]); + }); + + it("parses rules with string arguments", () => { + const result = parseRules(["pattern:^[a-z]+"]); + expect(result).toEqual([{ type: "pattern", arg: "^[a-z]+" }]); + }); + + it("returns an empty array for non-array input", () => { + expect(parseRules(null)).toEqual([]); + expect(parseRules(undefined)).toEqual([]); + expect(parseRules("required")).toEqual([]); + }); + + it("filters out non-string entries", () => { + const result = parseRules(["required", 42, null, "email"]); + expect(result).toEqual([{ type: "required" }, { type: "email" }]); + }); +}); + +// ─── validate ─────────────────────────────────────────────────────────────── + +describe("validate", () => { + it("returns error string for required on empty value", () => { + const rules = [{ type: "required" }]; + const error = validate("", rules); + expect(error).toBe("This field is required"); + }); + + it("returns undefined when valid value passes required", () => { + const rules = [{ type: "required" }]; + expect(validate("hello", rules)).toBeUndefined(); + }); + + it("validates email format", () => { + const rules = [{ type: "email" }]; + expect(validate("bad-email", rules)).toBe("Please enter a valid email"); + expect(validate("test@email.com", rules)).toBeUndefined(); + }); + + it("validates min/max with numeric arguments", () => { + expect(validate(3, [{ type: "min", arg: 5 }])).toBe("Must be at least 5"); + expect(validate(10, [{ type: "min", arg: 5 }])).toBeUndefined(); + expect(validate(20, [{ type: "max", arg: 10 }])).toBe("Must be no more than 10"); + expect(validate(5, [{ type: "max", arg: 10 }])).toBeUndefined(); + }); + + it("stops on first error with multiple rules", () => { + const rules = [{ type: "required" }, { type: "email" }]; + // Empty string triggers "required" first, not "email" + expect(validate("", rules)).toBe("This field is required"); + }); + + it("returns undefined when no rules match", () => { + expect(validate("anything", [{ type: "nonExistentRule" }])).toBeUndefined(); + }); +}); + +// ─── parseStructuredRules ─────────────────────────────────────────────────── + +describe("parseStructuredRules", () => { + it("parses an object of rules into ParsedRule array", () => { + const result = parseStructuredRules({ required: true, minLength: 5 }); + expect(result).toContainEqual({ type: "required" }); + expect(result).toContainEqual({ type: "minLength", arg: 5 }); + }); + + it("skips false/undefined/null values", () => { + const result = parseStructuredRules({ + required: true, + email: false, + max: undefined, + min: null, + }); + expect(result).toEqual([{ type: "required" }]); + }); + + it("returns empty array for non-object input", () => { + expect(parseStructuredRules(null)).toEqual([]); + expect(parseStructuredRules(undefined)).toEqual([]); + expect(parseStructuredRules([])).toEqual([]); + expect(parseStructuredRules("string")).toEqual([]); + }); +}); diff --git a/packages/svelte-lang/src/lib/RenderNode.svelte b/packages/svelte-lang/src/lib/RenderNode.svelte new file mode 100644 index 000000000..18dcceab7 --- /dev/null +++ b/packages/svelte-lang/src/lib/RenderNode.svelte @@ -0,0 +1,48 @@ + + +{#if node && Comp} + + + {#snippet failed()} + + {/snippet} + +{/if} diff --git a/packages/svelte-lang/src/lib/Renderer.svelte b/packages/svelte-lang/src/lib/Renderer.svelte new file mode 100644 index 000000000..32275a7d0 --- /dev/null +++ b/packages/svelte-lang/src/lib/Renderer.svelte @@ -0,0 +1,162 @@ + + +{#snippet renderNode(value: unknown)} + {#if value == null} + + {:else if typeof value === "string"} + {value} + {:else if typeof value === "number" || typeof value === "boolean"} + {String(value)} + {:else if Array.isArray(value)} + {#each value as item} + {@render renderNode(item)} + {/each} + {:else if typeof value === "object" && (value as any).type === "element"} + + {/if} +{/snippet} + +{#if result?.root} + +{/if} diff --git a/packages/svelte-lang/src/lib/context.svelte.ts b/packages/svelte-lang/src/lib/context.svelte.ts new file mode 100644 index 000000000..04627c367 --- /dev/null +++ b/packages/svelte-lang/src/lib/context.svelte.ts @@ -0,0 +1,150 @@ +import { getContext, setContext } from "svelte"; +import type { Library } from "./library.js"; + +// ─── Action config ─── + +export interface ActionConfig { + type?: string; + params?: Record; +} + +// ─── OpenUI context ─── + +/** + * Shared context provided by to all rendered components. + * + * Note: `renderNode` is passed as a snippet prop, NOT via context. + * This avoids the stale-closure problem and matches Svelte's snippet model. + */ +export interface OpenUIContextValue { + /** The active component library (schema + renderers). */ + library: Library; + + /** + * Trigger an action. Components call this to fire structured ActionEvents. + * + * @param userMessage Human-readable label ("Submit Application") + * @param formName Optional form name — if provided, form state for this form is included + * @param action Optional custom action config { type, params } + */ + triggerAction: (userMessage: string, formName?: string, action?: ActionConfig) => void; + + /** Whether the LLM is currently streaming content. */ + isStreaming: boolean; + + /** Get a form field value. Returns undefined if not set. */ + getFieldValue: (formName: string | undefined, name: string) => any; + + /** + * Set a form field value. + * + * @param formName The form's name prop + * @param componentType The component type (e.g. "Input", "Select", "RadioGroup") + * @param name The field's name prop + * @param value The new value + * @param shouldTriggerSaveCallback When true, persists the updated state via updateMessage. + * Text inputs should pass `false` on change and `true` on blur. + * Discrete inputs (Select, RadioGroup, etc.) should always pass `true`. + */ + setFieldValue: ( + formName: string | undefined, + componentType: string | undefined, + name: string, + value: any, + shouldTriggerSaveCallback?: boolean, + ) => void; +} + +const OPENUI_CONTEXT_KEY = Symbol("openui-context"); +const FORM_NAME_CONTEXT_KEY = Symbol("openui-form-name"); + +// ─── Context setters ─── + +export function setOpenUIContext(value: OpenUIContextValue): void { + setContext(OPENUI_CONTEXT_KEY, value); +} + +export function setFormNameContext(formName: string | undefined): void { + setContext(FORM_NAME_CONTEXT_KEY, formName); +} + +// ─── Context getters ─── + +/** + * Access the full OpenUI context. Throws if used outside a . + */ +export function getOpenUIContext(): OpenUIContextValue { + const ctx = getContext(OPENUI_CONTEXT_KEY); + if (!ctx) { + throw new Error("getOpenUIContext must be used within a component."); + } + return ctx; +} + +/** + * Get the triggerAction function for firing structured action events. + */ +export function getTriggerAction() { + return getOpenUIContext().triggerAction; +} + +/** + * Whether the LLM is currently streaming content. + * Returns a getter — use as `getIsStreaming()` for reactive reads. + */ +export function getIsStreaming(): boolean { + return getOpenUIContext().isStreaming; +} + +/** + * Get a form field value from the form state context. + */ +export function getGetFieldValue() { + return getOpenUIContext().getFieldValue; +} + +/** + * Get the setFieldValue function for updating form field values. + */ +export function getSetFieldValue() { + return getOpenUIContext().setFieldValue; +} + +/** + * Get the current form name (set by the nearest parent Form component). + * Returns undefined if not inside a Form. + */ +export function getFormName(): string | undefined { + return getContext(FORM_NAME_CONTEXT_KEY); +} + +// ─── Default value helper ─── + +/** + * Persists a component's default/initial value into form state once streaming + * finishes — but only if the user hasn't already set a value. + */ +export function useSetDefaultValue({ + formName, + componentType, + name, + existingValue, + defaultValue, + shouldTriggerSaveCallback = false, +}: { + formName?: string; + componentType: string; + name: string; + existingValue: any; + defaultValue: any; + shouldTriggerSaveCallback?: boolean; +}): void { + const setFieldValue = getSetFieldValue(); + const ctx = getOpenUIContext(); + + $effect(() => { + if (!ctx.isStreaming && existingValue === undefined && defaultValue !== undefined) { + setFieldValue(formName, componentType, name, defaultValue, shouldTriggerSaveCallback); + } + }); +} diff --git a/packages/svelte-lang/src/lib/index.ts b/packages/svelte-lang/src/lib/index.ts new file mode 100644 index 000000000..1f9be9f19 --- /dev/null +++ b/packages/svelte-lang/src/lib/index.ts @@ -0,0 +1,70 @@ +// ─── Component definition ─── + +export { createLibrary, defineComponent } from "./library.js"; +export type { + ComponentGroup, + ComponentRenderProps, + ComponentRenderer, + DefinedComponent, + Library, + LibraryDefinition, + PromptOptions, + SubComponentOf, +} from "./library.js"; + +// ─── Renderer ─── + +import type { Library } from "./library.js"; +import type { ActionEvent, ParseResult } from "@openuidev/lang-core"; + +export { default as Renderer } from "./Renderer.svelte"; + +/** Props accepted by the Renderer component. */ +export interface RendererProps { + response: string | null; + library: Library; + isStreaming?: boolean; + onAction?: (event: ActionEvent) => void; + onStateUpdate?: (state: Record) => void; + initialState?: Record; + onParseResult?: (result: ParseResult | null) => void; +} + +// ─── Context (for use inside component renderers) ─── + +export { + getFormName, + getGetFieldValue, + getIsStreaming, + getOpenUIContext, + getSetFieldValue, + getTriggerAction, + setFormNameContext, + setOpenUIContext, + useSetDefaultValue, +} from "./context.svelte.js"; +export type { ActionConfig, OpenUIContextValue } from "./context.svelte.js"; + +// ─── Form validation ─── + +export { + createFormValidation, + getFormValidation, + setFormValidationContext, +} from "./validation.svelte.js"; +export type { FormValidationContextValue } from "./validation.svelte.js"; + +export { + builtInValidators, + parseRules, + parseStructuredRules, + validate, +} from "./validation.svelte.js"; +export type { ParsedRule, ValidatorFn } from "./validation.svelte.js"; + +// ─── Re-exports from lang-core (parser, types) ─── + +export { BuiltinActionType } from "@openuidev/lang-core"; +export type { ActionEvent, ElementNode, ParseResult } from "@openuidev/lang-core"; + +export { createParser, createStreamingParser, type LibraryJSONSchema } from "@openuidev/lang-core"; diff --git a/packages/svelte-lang/src/lib/library.ts b/packages/svelte-lang/src/lib/library.ts new file mode 100644 index 000000000..6e2b4fa28 --- /dev/null +++ b/packages/svelte-lang/src/lib/library.ts @@ -0,0 +1,69 @@ +import type { Component, Snippet } from "svelte"; +import { z } from "zod"; +import { + createLibrary as coreCreateLibrary, + defineComponent as coreDefineComponent, + type ComponentRenderProps as CoreRenderProps, + type DefinedComponent as CoreDefinedComponent, + type Library as CoreLibrary, + type LibraryDefinition as CoreLibraryDefinition, +} from "@openuidev/lang-core"; + +// Re-export framework-agnostic types unchanged +export type { ComponentGroup, PromptOptions, SubComponentOf } from "@openuidev/lang-core"; + +// ─── Svelte-specific types ────────────────────────────────────────────────── + +export interface ComponentRenderProps

> + extends CoreRenderProps> {} + +export type ComponentRenderer

> = Component>; + +export type DefinedComponent = z.ZodObject> = + CoreDefinedComponent>>; + +export type Library = CoreLibrary>; + +export type LibraryDefinition = CoreLibraryDefinition>; + +// ─── defineComponent (Svelte) ─────────────────────────────────────────────── + +/** + * Define a component with name, schema, description, and renderer. + * Registers the Zod schema globally and returns a `.ref` for parent schemas. + * + * @example + * ```ts + * const TabItem = defineComponent({ + * name: "TabItem", + * props: z.object({ value: z.string(), trigger: z.string(), content: z.array(ContentChildUnion) }), + * description: "Tab panel", + * component: TabItemRenderer, + * }); + * ``` + */ +export function defineComponent>(config: { + name: string; + props: T; + description: string; + component: ComponentRenderer>; +}): DefinedComponent { + return coreDefineComponent>>(config); +} + +// ─── createLibrary (Svelte) ───────────────────────────────────────────────── + +/** + * Create a component library from an array of defined components. + * + * @example + * ```ts + * const library = createLibrary({ + * components: [TabItem, Tabs, Card], + * root: "Card", + * }); + * ``` + */ +export function createLibrary(input: LibraryDefinition): Library { + return coreCreateLibrary>(input) as Library; +} diff --git a/packages/svelte-lang/src/lib/validation.svelte.ts b/packages/svelte-lang/src/lib/validation.svelte.ts new file mode 100644 index 000000000..a86c0ab2c --- /dev/null +++ b/packages/svelte-lang/src/lib/validation.svelte.ts @@ -0,0 +1,101 @@ +import { getContext, setContext } from "svelte"; +import { + builtInValidators, + parseRules, + parseStructuredRules, + validate, + type ParsedRule, + type ValidatorFn, +} from "@openuidev/lang-core"; + +// ─── Re-exports from lang-core ─── + +export { builtInValidators, parseRules, parseStructuredRules, validate }; +export type { ParsedRule, ValidatorFn }; + +// ─── Form validation context ─── + +export interface FormValidationContextValue { + errors: Record; + validateField: (name: string, value: unknown, rules: ParsedRule[]) => boolean; + registerField: (name: string, rules: ParsedRule[], getValue: () => unknown) => void; + unregisterField: (name: string) => void; + validateForm: () => boolean; + clearFieldError: (name: string) => void; +} + +const FORM_VALIDATION_CONTEXT_KEY = Symbol("openui-form-validation"); + +/** + * Create a form validation instance backed by Svelte 5 $state. + * + * Call this in the Form component's ` + + + + diff --git a/examples/svelte-chat/src/lib/components/Card.svelte b/examples/svelte-chat/src/lib/components/Card.svelte new file mode 100644 index 000000000..9188fe02a --- /dev/null +++ b/examples/svelte-chat/src/lib/components/Card.svelte @@ -0,0 +1,32 @@ + + +

+ {#if props.title} +

{props.title}

+ {/if} + {#if props.children} + {@render renderNode(props.children)} + {/if} +
+ + diff --git a/examples/svelte-chat/src/lib/components/Stack.svelte b/examples/svelte-chat/src/lib/components/Stack.svelte new file mode 100644 index 000000000..79396f996 --- /dev/null +++ b/examples/svelte-chat/src/lib/components/Stack.svelte @@ -0,0 +1,22 @@ + + +
+ {#if props.children} + {@render renderNode(props.children)} + {/if} +
+ + diff --git a/examples/svelte-chat/src/lib/components/TextContent.svelte b/examples/svelte-chat/src/lib/components/TextContent.svelte new file mode 100644 index 000000000..589e52272 --- /dev/null +++ b/examples/svelte-chat/src/lib/components/TextContent.svelte @@ -0,0 +1,14 @@ + + +

{props.text ?? ""}

+ + diff --git a/examples/svelte-chat/src/lib/library.ts b/examples/svelte-chat/src/lib/library.ts new file mode 100644 index 000000000..7ffdec88e --- /dev/null +++ b/examples/svelte-chat/src/lib/library.ts @@ -0,0 +1,47 @@ +import { z } from "zod"; +import { defineComponent, createLibrary } from "@openuidev/svelte-lang"; +import Stack from "./components/Stack.svelte"; +import Card from "./components/Card.svelte"; +import TextContent from "./components/TextContent.svelte"; +import Button from "./components/Button.svelte"; + +const TextContentDef = defineComponent({ + name: "TextContent", + props: z.object({ text: z.string() }), + description: "Displays text content", + component: TextContent, +}); + +const ButtonDef = defineComponent({ + name: "Button", + props: z.object({ + label: z.string(), + action: z.string().optional(), + }), + description: "A clickable button that triggers an action", + component: Button, +}); + +const CardDef = defineComponent({ + name: "Card", + props: z.object({ + title: z.string(), + children: z.array(z.union([TextContentDef.ref, ButtonDef.ref])), + }), + description: "A card container with a title and child content", + component: Card, +}); + +const StackDef = defineComponent({ + name: "Stack", + props: z.object({ + children: z.array(z.union([CardDef.ref, TextContentDef.ref, ButtonDef.ref])), + }), + description: "Vertical layout container", + component: Stack, +}); + +export const library = createLibrary({ + components: [TextContentDef, ButtonDef, CardDef, StackDef], + root: "Stack", +}); diff --git a/examples/svelte-chat/src/routes/+layout.ts b/examples/svelte-chat/src/routes/+layout.ts new file mode 100644 index 000000000..5b22971b3 --- /dev/null +++ b/examples/svelte-chat/src/routes/+layout.ts @@ -0,0 +1,2 @@ +// Disable SSR — the openui-lang parser operates client-side only +export const ssr = false; diff --git a/examples/svelte-chat/src/routes/+page.svelte b/examples/svelte-chat/src/routes/+page.svelte new file mode 100644 index 000000000..6b18eb036 --- /dev/null +++ b/examples/svelte-chat/src/routes/+page.svelte @@ -0,0 +1,215 @@ + + +
+
+

OpenUI Svelte Chat

+

Powered by @openuidev/svelte-lang

+
+ +
+ {#each messages as msg} + {#if msg.role === "user"} +
+

{msg.content}

+
+ {:else} +
+ +
+ {/if} + {/each} + + {#if currentResponse !== null} +
+ +
+ {/if} +
+ +
+ + +
+
+ + diff --git a/examples/svelte-chat/svelte.config.js b/examples/svelte-chat/svelte.config.js new file mode 100644 index 000000000..f21eeff58 --- /dev/null +++ b/examples/svelte-chat/svelte.config.js @@ -0,0 +1,9 @@ +import adapter from "@sveltejs/adapter-auto"; +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; + +export default { + preprocess: vitePreprocess(), + kit: { + adapter: adapter(), + }, +}; diff --git a/examples/svelte-chat/tsconfig.json b/examples/svelte-chat/tsconfig.json new file mode 100644 index 000000000..e2018a00b --- /dev/null +++ b/examples/svelte-chat/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "strict": true, + "moduleResolution": "bundler" + } +} diff --git a/examples/svelte-chat/vite.config.ts b/examples/svelte-chat/vite.config.ts new file mode 100644 index 000000000..6b9eb5d39 --- /dev/null +++ b/examples/svelte-chat/vite.config.ts @@ -0,0 +1,6 @@ +import { sveltekit } from "@sveltejs/kit/vite"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [sveltekit()], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20db47a28..2ea4d7fe5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -446,6 +446,67 @@ importers: specifier: ^6.0.0 version: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + examples/vercel-ai-chat: + dependencies: + '@ai-sdk/openai': + specifier: ^3.0.41 + version: 3.0.41(zod@4.3.6) + '@ai-sdk/react': + specifier: ^3.0.118 + version: 3.0.118(react@19.2.3)(zod@4.3.6) + '@openuidev/cli': + specifier: workspace:* + version: link:../../packages/openui-cli + '@openuidev/react-lang': + specifier: workspace:* + version: link:../../packages/react-lang + '@openuidev/react-ui': + specifier: workspace:* + version: link:../../packages/react-ui + ai: + specifier: ^6.0.116 + version: 6.0.116(zod@4.3.6) + lucide-react: + specifier: ^0.575.0 + version: 0.575.0(react@19.2.3) + next: + specifier: 16.1.6 + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.89.2) + react: + specifier: 19.2.3 + version: 19.2.3 + react-dom: + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@tailwindcss/postcss': + specifier: ^4 + version: 4.2.1 + '@types/node': + specifier: ^20 + version: 20.19.35 + '@types/react': + specifier: ^19 + version: 19.2.14 + '@types/react-dom': + specifier: ^19 + version: 19.2.3(@types/react@19.2.14) + eslint: + specifier: ^9 + version: 9.29.0(jiti@2.6.1) + eslint-config-next: + specifier: 16.1.6 + version: 16.1.6(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3) + tailwindcss: + specifier: ^4 + version: 4.2.1 + typescript: + specifier: ^5 + version: 5.9.3 + packages/lang-core: dependencies: zod: @@ -6202,10 +6263,6 @@ packages: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} - esrap@2.2.4: resolution: {integrity: sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==} @@ -9206,6 +9263,11 @@ packages: resolution: {integrity: sha512-4x/uk4rQe/d7RhfvS8wemTfNjQ0bJbKvamIzRBfTe2eHHjzBZ7PZicUQrC2ryj83xxEacfA1zHKd1ephD1tAxA==} engines: {node: '>=18'} + swr@2.4.1: + resolution: {integrity: sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -16356,7 +16418,7 @@ snapshots: dependencies: debug: 3.2.7 is-core-module: 2.16.1 - resolve: 1.22.10 + resolve: 1.22.11 transitivePeerDependencies: - supports-color @@ -16568,10 +16630,6 @@ snapshots: dependencies: estraverse: 5.3.0 - esquery@1.7.0: - dependencies: - estraverse: 5.3.0 - esrap@2.2.4: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -20662,6 +20720,12 @@ snapshots: magic-string: 0.30.21 zimmerframe: 1.1.4 + swr@2.4.1(react@19.2.3): + dependencies: + dequal: 2.0.3 + react: 19.2.3 + use-sync-external-store: 1.6.0(react@19.2.3) + symbol-tree@3.2.4: {} synckit@0.11.12: From bb51afba560e9a5daa95a57e9e96dff5f150411d Mon Sep 17 00:00:00 2001 From: abhithesys Date: Tue, 24 Mar 2026 11:02:10 +0530 Subject: [PATCH 05/12] Add eslint/tsconfig.test setup and move parser tests to lang-core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This covers: - New eslint.config.cjs and tsconfig.test.json for lang-core (matching react-lang pattern) - Parser test file moved from react-lang to lang-core - package.json updated with vitest dev dependency and formatting - src/index.ts export updated (ValidationError → ValidationErrorCode) --- packages/lang-core/eslint.config.cjs | 72 +++++++++++++++++++ packages/lang-core/package.json | 27 +++++-- packages/lang-core/src/index.ts | 2 +- .../src/parser/__tests__/parser.test.ts | 2 +- packages/lang-core/tsconfig.test.json | 9 +++ pnpm-lock.yaml | 65 ++--------------- 6 files changed, 110 insertions(+), 67 deletions(-) create mode 100644 packages/lang-core/eslint.config.cjs rename packages/{react-lang => lang-core}/src/parser/__tests__/parser.test.ts (98%) create mode 100644 packages/lang-core/tsconfig.test.json diff --git a/packages/lang-core/eslint.config.cjs b/packages/lang-core/eslint.config.cjs new file mode 100644 index 000000000..3f9a9e7c4 --- /dev/null +++ b/packages/lang-core/eslint.config.cjs @@ -0,0 +1,72 @@ +const tseslint = require("@typescript-eslint/eslint-plugin"); +const typescript = require("@typescript-eslint/parser"); +const prettier = require("eslint-config-prettier"); +const unusedImports = require("eslint-plugin-unused-imports"); +const eslintPluginPrettier = require("eslint-plugin-prettier"); + +module.exports = [ + { + files: ["**/__tests__/**/*.{ts,tsx}", "**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"], + languageOptions: { + parser: typescript, + parserOptions: { + project: "./tsconfig.test.json", + sourceType: "module", + }, + }, + }, + { + files: ["**/*.{ts,tsx}"], + ignores: [ + "**/*.stories.tsx", + "**/__tests__/**/*.{ts,tsx}", + "**/*.test.{ts,tsx}", + "**/*.spec.{ts,tsx}", + ], + languageOptions: { + parser: typescript, + parserOptions: { + project: "./tsconfig.json", + sourceType: "module", + }, + }, + plugins: { + "@typescript-eslint": tseslint, + "unused-imports": unusedImports, + prettier: eslintPluginPrettier, + }, + rules: { + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "off", + "no-undefined": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + vars: "all", + varsIgnorePattern: "^_", + args: "after-used", + argsIgnorePattern: "^_", + }, + ], + "@typescript-eslint/no-use-before-define": [ + "error", + { + functions: false, + classes: false, + variables: false, + }, + ], + "unused-imports/no-unused-imports": "error", + "no-console": [ + "error", + { + allow: ["error", "warn", "info"], + }, + ], + ...eslintPluginPrettier.configs.recommended.rules, + }, + }, + prettier, +]; diff --git a/packages/lang-core/package.json b/packages/lang-core/package.json index 8591b8890..ee5e500b9 100644 --- a/packages/lang-core/package.json +++ b/packages/lang-core/package.json @@ -6,7 +6,10 @@ "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", - "files": ["dist", "README.md"], + "files": [ + "dist", + "README.md" + ], "exports": { ".": { "types": "./dist/index.d.ts", @@ -15,6 +18,7 @@ } }, "scripts": { + "test": "vitest run", "build": "tsc -p .", "watch": "tsc -p . --watch", "lint:check": "eslint ./src", @@ -27,13 +31,28 @@ "dependencies": { "zod": "^4.0.0" }, - "keywords": ["openui", "openui-lang", "parser", "prompt-generation", "validation", "zod", "llm", "generative-ui", "framework-agnostic"], + "keywords": [ + "openui", + "openui-lang", + "parser", + "prompt-generation", + "validation", + "zod", + "llm", + "generative-ui", + "framework-agnostic" + ], "homepage": "https://openui.com", "repository": { "type": "git", "url": "https://github.com/thesysdev/openui.git", "directory": "packages/lang-core" }, - "bugs": { "url": "https://github.com/thesysdev/openui/issues" }, - "author": "engineering@thesys.dev" + "bugs": { + "url": "https://github.com/thesysdev/openui/issues" + }, + "author": "engineering@thesys.dev", + "devDependencies": { + "vitest": "^4.0.18" + } } diff --git a/packages/lang-core/src/index.ts b/packages/lang-core/src/index.ts index 4833ee6db..f0696373d 100644 --- a/packages/lang-core/src/index.ts +++ b/packages/lang-core/src/index.ts @@ -15,7 +15,7 @@ export { createParser, createStreamingParser, parse } from "./parser"; export type { LibraryJSONSchema, Parser, StreamParser } from "./parser"; export { generatePrompt } from "./parser/prompt"; export { BuiltinActionType } from "./parser/types"; -export type { ActionEvent, ElementNode, ParseResult, ValidationError } from "./parser/types"; +export type { ActionEvent, ElementNode, ParseResult, ValidationErrorCode } from "./parser/types"; // ── Validation ── export { builtInValidators, parseRules, parseStructuredRules, validate } from "./utils/validation"; diff --git a/packages/react-lang/src/parser/__tests__/parser.test.ts b/packages/lang-core/src/parser/__tests__/parser.test.ts similarity index 98% rename from packages/react-lang/src/parser/__tests__/parser.test.ts rename to packages/lang-core/src/parser/__tests__/parser.test.ts index bd91ede82..1ea6cfb7d 100644 --- a/packages/react-lang/src/parser/__tests__/parser.test.ts +++ b/packages/lang-core/src/parser/__tests__/parser.test.ts @@ -27,7 +27,7 @@ const schema: ParamMap = new Map([ // ── Helpers ─────────────────────────────────────────────────────────────────── const errors = (input: string) => parse(input, schema).meta.errors; -const codes = (input: string) => errors(input).map((e) => e.code); +const codes = (input: string) => errors(input).map((e: { code: string }) => e.code); // ── unknown-component ──────────────────────────────────────────────────────── diff --git a/packages/lang-core/tsconfig.test.json b/packages/lang-core/tsconfig.test.json new file mode 100644 index 000000000..60b3001d5 --- /dev/null +++ b/packages/lang-core/tsconfig.test.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94b29713e..81f225024 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -512,6 +512,10 @@ importers: zod: specifier: ^4.0.0 version: 4.3.6 + devDependencies: + vitest: + specifier: ^4.0.18 + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) packages/openui-cli: dependencies: @@ -2229,105 +2233,89 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -2616,56 +2604,48 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@next/swc-linux-arm64-gnu@16.1.6': resolution: {integrity: sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@next/swc-linux-arm64-musl@15.5.12': resolution: {integrity: sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@next/swc-linux-arm64-musl@16.1.6': resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@next/swc-linux-x64-gnu@15.5.12': resolution: {integrity: sha512-jSLvgdRRL/hrFAPqEjJf1fFguC719kmcptjNVDJl26BnJIpjL3KH5h6mzR4mAweociLQaqvt4UyzfbFjgAdDcw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@next/swc-linux-x64-gnu@16.1.6': resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@next/swc-linux-x64-musl@15.5.12': resolution: {integrity: sha512-/uaF0WfmYqQgLfPmN6BvULwxY0dufI2mlN2JbOKqqceZh1G4hjREyi7pg03zjfyS6eqNemHAZPSoP84x17vo6w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@next/swc-linux-x64-musl@16.1.6': resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@next/swc-win32-arm64-msvc@15.5.12': resolution: {integrity: sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg==} @@ -2817,42 +2797,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -4032,67 +4006,56 @@ packages: resolution: {integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.43.0': resolution: {integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.43.0': resolution: {integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.43.0': resolution: {integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.43.0': resolution: {integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': resolution: {integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.43.0': resolution: {integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.43.0': resolution: {integrity: sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.43.0': resolution: {integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.43.0': resolution: {integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.43.0': resolution: {integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.43.0': resolution: {integrity: sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==} @@ -4429,28 +4392,24 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.1': resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.1': resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.1': resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.1': resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} @@ -4500,28 +4459,24 @@ packages: engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@takumi-rs/core-linux-arm64-musl@0.68.17': resolution: {integrity: sha512-4CiEF518wDnujF0fjql2XN6uO+OXl0svy0WgAF2656dCx2gJtWscaHytT2rsQ0ZmoFWE0dyWcDW1g/FBVPvuvA==} engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@takumi-rs/core-linux-x64-gnu@0.68.17': resolution: {integrity: sha512-jm8lTe2E6Tfq2b97GJC31TWK1JAEv+MsVbvL9DCLlYcafgYFlMXDUnOkZFMjlrmh0HcFAYDaBkniNDgIQfXqzg==} engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@takumi-rs/core-linux-x64-musl@0.68.17': resolution: {integrity: sha512-nbdzQgC4ywzltDDV1fer1cKswwGE+xXZHdDiacdd7RM5XBng209Bmo3j1iv9dsX+4xXhByzCCGbxdWhhHqVXmw==} engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@takumi-rs/core-win32-arm64-msvc@0.68.17': resolution: {integrity: sha512-kE4F0LRmuhSwiNkFG7dTY9ID8+B7zb97QedyN/IO2fBJmRQDkqCGcip2gloh8YPPhCuKGjCqqqh2L+Tg9PKW7w==} @@ -4863,49 +4818,41 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] - libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -7382,28 +7329,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.31.1: resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} From 241c1055058c66e006fc3a0f7774ca93fc8cd7a1 Mon Sep 17 00:00:00 2001 From: abhithesys Date: Tue, 24 Mar 2026 18:16:15 +0530 Subject: [PATCH 06/12] example --- examples/svelte-chat/.env.example | 1 + examples/svelte-chat/.gitignore | 6 + examples/svelte-chat/README.md | 75 +++ examples/svelte-chat/package.json | 44 +- examples/svelte-chat/src/app.css | 12 +- examples/svelte-chat/src/app.d.ts | 7 + examples/svelte-chat/src/app.html | 2 +- .../src/generated/system-prompt.txt | 54 ++ .../src/lib/components/Button.svelte | 24 +- .../src/lib/components/Card.svelte | 23 +- .../src/lib/components/Stack.svelte | 10 +- .../src/lib/components/TextContent.svelte | 9 +- examples/svelte-chat/src/lib/library.ts | 58 +-- examples/svelte-chat/src/lib/tools.ts | 117 +++++ .../svelte-chat/src/routes/+layout.svelte | 7 + examples/svelte-chat/src/routes/+layout.ts | 1 - examples/svelte-chat/src/routes/+page.svelte | 371 ++++++------- .../src/routes/api/chat/+server.ts | 24 + examples/svelte-chat/vite.config.ts | 3 +- packages/svelte-lang/src/lib/library.ts | 39 +- pnpm-lock.yaml | 487 +++++++++++++++--- 21 files changed, 997 insertions(+), 377 deletions(-) create mode 100644 examples/svelte-chat/.env.example create mode 100644 examples/svelte-chat/.gitignore create mode 100644 examples/svelte-chat/README.md create mode 100644 examples/svelte-chat/src/app.d.ts create mode 100644 examples/svelte-chat/src/generated/system-prompt.txt create mode 100644 examples/svelte-chat/src/lib/tools.ts create mode 100644 examples/svelte-chat/src/routes/+layout.svelte create mode 100644 examples/svelte-chat/src/routes/api/chat/+server.ts diff --git a/examples/svelte-chat/.env.example b/examples/svelte-chat/.env.example new file mode 100644 index 000000000..e9839107a --- /dev/null +++ b/examples/svelte-chat/.env.example @@ -0,0 +1 @@ +OPENAI_API_KEY=your-openai-api-key-here diff --git a/examples/svelte-chat/.gitignore b/examples/svelte-chat/.gitignore new file mode 100644 index 000000000..ca1328b5d --- /dev/null +++ b/examples/svelte-chat/.gitignore @@ -0,0 +1,6 @@ +node_modules +.svelte-kit +build +.env +.env.* +!.env.example diff --git a/examples/svelte-chat/README.md b/examples/svelte-chat/README.md new file mode 100644 index 000000000..8bfdd2751 --- /dev/null +++ b/examples/svelte-chat/README.md @@ -0,0 +1,75 @@ +# OpenUI Svelte Chat + +A chat application built with [SvelteKit](https://svelte.dev/docs/kit), [Vercel AI SDK](https://ai-sdk.dev), and [`@openuidev/svelte-lang`](../../packages/svelte-lang/) — demonstrating how to render structured LLM output as live Svelte components. + +## How it works + +1. **User sends a message** via the chat input +2. **Server streams a response** using the Vercel AI SDK with OpenAI, guided by a system prompt written in openui-lang syntax +3. **`@openuidev/svelte-lang` Renderer** parses the streaming openui-lang text and renders it as Svelte components in real time +4. **Tool calls** (weather, stocks, math, web search) are displayed inline with status indicators + +## Setup + +### Prerequisites + +- Node.js 18+ +- [pnpm](https://pnpm.io/) +- An OpenAI API key + +### Install dependencies + +From the monorepo root: + +```bash +pnpm install +``` + +### Configure environment + +```bash +cp .env.example .env +``` + +Edit `.env` and add your OpenAI API key: + +``` +OPENAI_API_KEY=sk-... +``` + +### Run + +```bash +pnpm --filter svelte-chat dev +``` + +Open [http://localhost:5173](http://localhost:5173). + +## Project structure + +``` +src/ +├── routes/ +│ ├── +page.svelte # Chat UI with AI SDK Chat class + OpenUI Renderer +│ ├── +layout.svelte # Root layout (imports Tailwind) +│ ├── +layout.ts # Disables SSR (client-side rendering) +│ └── api/chat/+server.ts # AI SDK streaming endpoint +├── lib/ +│ ├── library.ts # OpenUI component definitions (Stack, Card, TextContent, Button) +│ ├── tools.ts # AI tool definitions (weather, stocks, math, search) +│ └── components/ # Svelte component renderers +│ ├── Stack.svelte +│ ├── Card.svelte +│ ├── TextContent.svelte +│ └── Button.svelte +└── generated/ + └── system-prompt.txt # LLM system prompt describing the openui-lang syntax +``` + +## Adding components + +1. Create a Svelte component in `src/lib/components/` +2. Define it with `defineComponent()` in `src/lib/library.ts` +3. Add its signature to `src/generated/system-prompt.txt` + +See the [`@openuidev/svelte-lang` README](../../packages/svelte-lang/README.md) for the full API. diff --git a/examples/svelte-chat/package.json b/examples/svelte-chat/package.json index 0f1aa6f0a..b2a5d92fa 100644 --- a/examples/svelte-chat/package.json +++ b/examples/svelte-chat/package.json @@ -1,21 +1,27 @@ { - "name": "svelte-chat", - "private": true, - "type": "module", - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "@openuidev/svelte-lang": "workspace:*", - "zod": "^4.0.0" - }, - "devDependencies": { - "@sveltejs/adapter-auto": "^4.0.0", - "@sveltejs/kit": "^2.0.0", - "@sveltejs/vite-plugin-svelte": "^5.0.0", - "svelte": "^5.0.0", - "vite": "^6.0.0" - } + "name": "svelte-chat", + "private": true, + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@ai-sdk/openai": "^3.0.41", + "@ai-sdk/svelte": "^3.0.0", + "@openuidev/svelte-lang": "workspace:*", + "ai": "^6.0.116", + "zod": "^4.3.6" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^4.0.0", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tailwindcss/vite": "^4", + "svelte": "^5.0.0", + "tailwindcss": "^4", + "typescript": "^5", + "vite": "^6.0.0" + } } diff --git a/examples/svelte-chat/src/app.css b/examples/svelte-chat/src/app.css index 1bfb00806..f1d8c73cd 100644 --- a/examples/svelte-chat/src/app.css +++ b/examples/svelte-chat/src/app.css @@ -1,11 +1 @@ -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -body { - font-family: system-ui, sans-serif; - background: #f5f5f5; - color: #1a1a1a; -} +@import "tailwindcss"; diff --git a/examples/svelte-chat/src/app.d.ts b/examples/svelte-chat/src/app.d.ts new file mode 100644 index 000000000..f52e41baa --- /dev/null +++ b/examples/svelte-chat/src/app.d.ts @@ -0,0 +1,7 @@ +/// + +declare global { + namespace App {} +} + +export {}; diff --git a/examples/svelte-chat/src/app.html b/examples/svelte-chat/src/app.html index c28dcd7db..3fff5cf8a 100644 --- a/examples/svelte-chat/src/app.html +++ b/examples/svelte-chat/src/app.html @@ -6,7 +6,7 @@ OpenUI Svelte Chat %sveltekit.head% - +
%sveltekit.body%
diff --git a/examples/svelte-chat/src/generated/system-prompt.txt b/examples/svelte-chat/src/generated/system-prompt.txt new file mode 100644 index 000000000..87db17ce5 --- /dev/null +++ b/examples/svelte-chat/src/generated/system-prompt.txt @@ -0,0 +1,54 @@ +You are an AI assistant that responds using openui-lang, a declarative UI language. Your ENTIRE response must be valid openui-lang code — no markdown, no explanations, just openui-lang. + +## Syntax Rules + +1. Each statement is on its own line: `identifier = Expression` +2. `root` is the entry point — every program must define `root = Stack(...)` +3. Expressions are: strings ("..."), numbers, booleans (true/false), arrays ([...]), objects ({...}), or component calls TypeName(arg1, arg2, ...) +4. Use references for readability: define `name = ...` on one line, then use `name` later +5. EVERY variable (except root) MUST be referenced by at least one other variable. Unreferenced variables are silently dropped and will NOT render. Always include defined variables in their parent's children/items array. +6. Arguments are POSITIONAL (order matters, not names) +7. Optional arguments can be omitted from the end +8. No operators, no logic, no variables — only declarations +9. Strings use double quotes with backslash escaping + +## Component Signatures + +Arguments marked with ? are optional. + +Stack(children: array) — Vertical layout container. Use as the root. +Card(title: string, children: array) — A card container with a title and child components. +TextContent(text: string) — Displays a block of text. Supports markdown formatting within the string. +Button(label: string, action?: string) — A clickable button. The label is shown to the user and used as the follow-up message. + +## Rules + +- Always use Stack as the root component. +- Group related content in Card components with descriptive titles. +- Use TextContent for all text output. You can use markdown within the text string. +- Use Button for suggested follow-up actions the user might want to take. +- For multi-section responses, use multiple Card components inside the root Stack. +- Prefer using references for readability and better streaming performance. +- Keep TextContent strings focused — use multiple TextContent components for different paragraphs or points. +- Never nest Stack inside Stack directly. + +## Examples + +User: What is Svelte? + +t1 = TextContent("Svelte is a modern JavaScript framework that shifts work from the browser to a compile step. Unlike React or Vue, Svelte compiles your components into efficient imperative code that directly manipulates the DOM.") +t2 = TextContent("**No virtual DOM** — Svelte updates the DOM surgically when state changes, resulting in excellent runtime performance.") +t3 = TextContent("**Less boilerplate** — Svelte's syntax is concise and intuitive, letting you write less code to achieve the same results.") +t4 = TextContent("**Built-in reactivity** — Simple variable assignments trigger UI updates. No hooks or special APIs needed.") +intro = Card("What is Svelte?", [t1]) +features = Card("Key Features", [t2, t3, t4]) +cta = Button("Tell me about Svelte 5") +root = Stack([intro, features, cta]) + +User: What's the weather like? + +t1 = TextContent("I can look up the current weather for any city. Just tell me which location you're interested in!") +card = Card("Weather Lookup", [t1]) +b1 = Button("Weather in New York") +b2 = Button("Weather in Tokyo") +root = Stack([card, b1, b2]) diff --git a/examples/svelte-chat/src/lib/components/Button.svelte b/examples/svelte-chat/src/lib/components/Button.svelte index db6f23867..4610c74a2 100644 --- a/examples/svelte-chat/src/lib/components/Button.svelte +++ b/examples/svelte-chat/src/lib/components/Button.svelte @@ -9,25 +9,13 @@ - - diff --git a/examples/svelte-chat/src/lib/components/Card.svelte b/examples/svelte-chat/src/lib/components/Card.svelte index 9188fe02a..6d52a10e7 100644 --- a/examples/svelte-chat/src/lib/components/Card.svelte +++ b/examples/svelte-chat/src/lib/components/Card.svelte @@ -7,26 +7,13 @@ }: { props: { title?: string; children?: unknown }; renderNode: Snippet<[unknown]> } = $props(); -
+
{#if props.title} -

{props.title}

+

{props.title}

{/if} {#if props.children} - {@render renderNode(props.children)} +
+ {@render renderNode(props.children)} +
{/if}
- - diff --git a/examples/svelte-chat/src/lib/components/Stack.svelte b/examples/svelte-chat/src/lib/components/Stack.svelte index 79396f996..da7273c36 100644 --- a/examples/svelte-chat/src/lib/components/Stack.svelte +++ b/examples/svelte-chat/src/lib/components/Stack.svelte @@ -7,16 +7,8 @@ }: { props: { children?: unknown }; renderNode: Snippet<[unknown]> } = $props(); -
+
{#if props.children} {@render renderNode(props.children)} {/if}
- - diff --git a/examples/svelte-chat/src/lib/components/TextContent.svelte b/examples/svelte-chat/src/lib/components/TextContent.svelte index 589e52272..ba49ae912 100644 --- a/examples/svelte-chat/src/lib/components/TextContent.svelte +++ b/examples/svelte-chat/src/lib/components/TextContent.svelte @@ -4,11 +4,4 @@ let { props }: { props: { text?: string }; renderNode: Snippet<[unknown]> } = $props(); -

{props.text ?? ""}

- - +

{props.text ?? ""}

diff --git a/examples/svelte-chat/src/lib/library.ts b/examples/svelte-chat/src/lib/library.ts index 7ffdec88e..173e4a18f 100644 --- a/examples/svelte-chat/src/lib/library.ts +++ b/examples/svelte-chat/src/lib/library.ts @@ -1,47 +1,47 @@ +import { createLibrary, defineComponent } from "@openuidev/svelte-lang"; import { z } from "zod"; -import { defineComponent, createLibrary } from "@openuidev/svelte-lang"; -import Stack from "./components/Stack.svelte"; +import Button from "./components/Button.svelte"; import Card from "./components/Card.svelte"; +import Stack from "./components/Stack.svelte"; import TextContent from "./components/TextContent.svelte"; -import Button from "./components/Button.svelte"; const TextContentDef = defineComponent({ - name: "TextContent", - props: z.object({ text: z.string() }), - description: "Displays text content", - component: TextContent, + name: "TextContent", + props: z.object({ text: z.string() }), + description: "Displays a block of text. Supports markdown formatting within the string.", + component: TextContent, }); const ButtonDef = defineComponent({ - name: "Button", - props: z.object({ - label: z.string(), - action: z.string().optional(), - }), - description: "A clickable button that triggers an action", - component: Button, + name: "Button", + props: z.object({ + label: z.string(), + action: z.string().optional(), + }), + description: "A clickable button that triggers an action", + component: Button, }); const CardDef = defineComponent({ - name: "Card", - props: z.object({ - title: z.string(), - children: z.array(z.union([TextContentDef.ref, ButtonDef.ref])), - }), - description: "A card container with a title and child content", - component: Card, + name: "Card", + props: z.object({ + title: z.string(), + children: z.array(z.union([TextContentDef.ref, ButtonDef.ref])), + }), + description: "A card container with a title and child components", + component: Card, }); const StackDef = defineComponent({ - name: "Stack", - props: z.object({ - children: z.array(z.union([CardDef.ref, TextContentDef.ref, ButtonDef.ref])), - }), - description: "Vertical layout container", - component: Stack, + name: "Stack", + props: z.object({ + children: z.array(z.union([CardDef.ref, TextContentDef.ref, ButtonDef.ref])), + }), + description: "Vertical layout container", + component: Stack, }); export const library = createLibrary({ - components: [TextContentDef, ButtonDef, CardDef, StackDef], - root: "Stack", + components: [TextContentDef, ButtonDef, CardDef, StackDef], + root: "Stack", }); diff --git a/examples/svelte-chat/src/lib/tools.ts b/examples/svelte-chat/src/lib/tools.ts new file mode 100644 index 000000000..bf7b106d6 --- /dev/null +++ b/examples/svelte-chat/src/lib/tools.ts @@ -0,0 +1,117 @@ +import { tool } from "ai"; +import { z } from "zod"; + +export const tools = { + get_weather: tool({ + description: "Get current weather for a location.", + inputSchema: z.object({ + location: z.string().describe("City name"), + }), + execute: async ({ location }) => { + await new Promise((r) => setTimeout(r, 800)); + const knownTemps: Record = { + tokyo: 22, + "san francisco": 18, + london: 14, + "new york": 25, + paris: 19, + sydney: 27, + mumbai: 33, + berlin: 16, + }; + const conditions = ["Sunny", "Partly Cloudy", "Cloudy", "Light Rain", "Clear Skies"]; + const temp = knownTemps[location.toLowerCase()] ?? Math.floor(Math.random() * 30 + 5); + const condition = conditions[Math.floor(Math.random() * conditions.length)]; + return { + location, + temperature_celsius: temp, + temperature_fahrenheit: Math.round(temp * 1.8 + 32), + condition, + humidity_percent: Math.floor(Math.random() * 40 + 40), + wind_speed_kmh: Math.floor(Math.random() * 25 + 5), + forecast: [ + { day: "Tomorrow", high: temp + 2, low: temp - 4, condition: "Partly Cloudy" }, + { day: "Day After", high: temp + 1, low: temp - 3, condition: "Sunny" }, + ], + }; + }, + }), + + get_stock_price: tool({ + description: "Get stock price for a ticker symbol.", + inputSchema: z.object({ + symbol: z.string().describe("Ticker symbol, e.g. AAPL"), + }), + execute: async ({ symbol }) => { + await new Promise((r) => setTimeout(r, 600)); + const s = symbol.toUpperCase(); + const knownPrices: Record = { + AAPL: 189.84, + GOOGL: 141.8, + TSLA: 248.42, + MSFT: 378.91, + AMZN: 178.25, + NVDA: 875.28, + META: 485.58, + }; + const price = knownPrices[s] ?? Math.floor(Math.random() * 500 + 20); + const change = parseFloat((Math.random() * 8 - 4).toFixed(2)); + return { + symbol: s, + price: parseFloat((price + change).toFixed(2)), + change, + change_percent: parseFloat(((change / price) * 100).toFixed(2)), + volume: `${(Math.random() * 50 + 10).toFixed(1)}M`, + day_high: parseFloat((price + Math.abs(change) + 1.5).toFixed(2)), + day_low: parseFloat((price - Math.abs(change) - 1.2).toFixed(2)), + }; + }, + }), + + calculate: tool({ + description: "Evaluate a math expression.", + inputSchema: z.object({ + expression: z.string().describe("Math expression to evaluate"), + }), + execute: async ({ expression }) => { + await new Promise((r) => setTimeout(r, 300)); + try { + const sanitized = expression.replace( + /[^0-9+\-*/().%\s,Math.sqrtpowabsceilfloorround]/g, + "", + ); + const result = new Function(`return (${sanitized})`)(); + return { expression, result: Number(result) }; + } catch { + return { expression, error: "Invalid expression" }; + } + }, + }), + + search_web: tool({ + description: "Search the web for information.", + inputSchema: z.object({ + query: z.string().describe("Search query"), + }), + execute: async ({ query }) => { + await new Promise((r) => setTimeout(r, 1000)); + return { + query, + results: [ + { + title: `Top result for "${query}"`, + snippet: `Comprehensive overview of ${query} with the latest information.`, + }, + { + title: `${query} - Latest News`, + snippet: `Recent developments and updates related to ${query}.`, + }, + { + title: `Understanding ${query}`, + snippet: `An in-depth guide explaining everything about ${query}.`, + }, + ], + }; + }, + }), +}; diff --git a/examples/svelte-chat/src/routes/+layout.svelte b/examples/svelte-chat/src/routes/+layout.svelte new file mode 100644 index 000000000..d701e67c8 --- /dev/null +++ b/examples/svelte-chat/src/routes/+layout.svelte @@ -0,0 +1,7 @@ + + +{@render children()} diff --git a/examples/svelte-chat/src/routes/+layout.ts b/examples/svelte-chat/src/routes/+layout.ts index 5b22971b3..a3d15781a 100644 --- a/examples/svelte-chat/src/routes/+layout.ts +++ b/examples/svelte-chat/src/routes/+layout.ts @@ -1,2 +1 @@ -// Disable SSR — the openui-lang parser operates client-side only export const ssr = false; diff --git a/examples/svelte-chat/src/routes/+page.svelte b/examples/svelte-chat/src/routes/+page.svelte index 6b18eb036..245a048a9 100644 --- a/examples/svelte-chat/src/routes/+page.svelte +++ b/examples/svelte-chat/src/routes/+page.svelte @@ -1,215 +1,220 @@ -
-
-

OpenUI Svelte Chat

-

Powered by @openuidev/svelte-lang

+
+
+

OpenUI Svelte Chat

+

+ Powered by @openuidev/svelte-lang & Vercel AI SDK +

-
- {#each messages as msg} - {#if msg.role === "user"} -
-

{msg.content}

+
+ {#if chat.messages.length === 0} +
+
+

+ Welcome to OpenUI Chat +

+

+ Ask anything — responses are rendered as structured UI components. +

- {:else} -
- +
+ {#each starters as starter} + + {/each}
- {/if} - {/each} - - {#if currentResponse !== null} -
- +
+ {:else} +
+ {#each chat.messages as message, i} + {#if message.role === "user"} +
+
+ {#each message.parts as part} + {#if part.type === "text"} +

{part.text}

+ {/if} + {/each} +
+
+ {:else if message.role === "assistant"} + {@const textContent = getTextContent(message.parts)} + {@const toolParts = message.parts.filter(isToolPart)} + {@const isLast = i === chat.messages.length - 1} +
+
+ AI +
+
+ {#each toolParts as tp} + {@const state = (tp as any).state} + {@const done = state === "output-available"} +
+ {#if done} + + + + {:else} +
+ {/if} + {getToolName(tp)} +
+ {/each} + {#if textContent} + + {/if} +
+
+ {/if} + {/each} + + {#if isLoading && (chat.messages.length === 0 || chat.messages[chat.messages.length - 1]?.role === "user")} +
+
+ AI +
+
+
+
+
+
+
+ {/if} + +
{/if}
-
- - +
+
+ + {#if isLoading} + + {:else} + + {/if} +
- - diff --git a/examples/svelte-chat/src/routes/api/chat/+server.ts b/examples/svelte-chat/src/routes/api/chat/+server.ts new file mode 100644 index 000000000..3a89fb2bb --- /dev/null +++ b/examples/svelte-chat/src/routes/api/chat/+server.ts @@ -0,0 +1,24 @@ +import { OPENAI_API_KEY } from "$env/static/private"; +import { tools } from "$lib/tools"; +import { createOpenAI } from "@ai-sdk/openai"; +import { convertToModelMessages, stepCountIs, streamText } from "ai"; +import { readFileSync } from "fs"; +import { join } from "path"; + +const openai = createOpenAI({ apiKey: OPENAI_API_KEY }); + +const systemPrompt = readFileSync(join(process.cwd(), "src/generated/system-prompt.txt"), "utf-8"); + +export async function POST({ request }: { request: Request }) { + const { messages } = await request.json(); + + const result = streamText({ + model: openai("gpt-4o"), + system: systemPrompt, + messages: await convertToModelMessages(messages), + tools, + stopWhen: stepCountIs(5), + }); + + return result.toUIMessageStreamResponse(); +} diff --git a/examples/svelte-chat/vite.config.ts b/examples/svelte-chat/vite.config.ts index 6b9eb5d39..b0741a1f2 100644 --- a/examples/svelte-chat/vite.config.ts +++ b/examples/svelte-chat/vite.config.ts @@ -1,6 +1,7 @@ import { sveltekit } from "@sveltejs/kit/vite"; +import tailwindcss from "@tailwindcss/vite"; import { defineConfig } from "vite"; export default defineConfig({ - plugins: [sveltekit()], + plugins: [tailwindcss(), sveltekit()], }); diff --git a/packages/svelte-lang/src/lib/library.ts b/packages/svelte-lang/src/lib/library.ts index 6e2b4fa28..93c7f4e31 100644 --- a/packages/svelte-lang/src/lib/library.ts +++ b/packages/svelte-lang/src/lib/library.ts @@ -1,26 +1,29 @@ -import type { Component, Snippet } from "svelte"; -import { z } from "zod"; import { - createLibrary as coreCreateLibrary, - defineComponent as coreDefineComponent, - type ComponentRenderProps as CoreRenderProps, - type DefinedComponent as CoreDefinedComponent, - type Library as CoreLibrary, - type LibraryDefinition as CoreLibraryDefinition, + createLibrary as coreCreateLibrary, + defineComponent as coreDefineComponent, + type DefinedComponent as CoreDefinedComponent, + type Library as CoreLibrary, + type LibraryDefinition as CoreLibraryDefinition, } from "@openuidev/lang-core"; +import type { Component, Snippet } from "svelte"; +import { z } from "zod"; // Re-export framework-agnostic types unchanged export type { ComponentGroup, PromptOptions, SubComponentOf } from "@openuidev/lang-core"; // ─── Svelte-specific types ────────────────────────────────────────────────── -export interface ComponentRenderProps

> - extends CoreRenderProps> {} +export interface ComponentRenderProps

> { + props: P; + renderNode: Snippet<[unknown]>; +} export type ComponentRenderer

> = Component>; -export type DefinedComponent = z.ZodObject> = - CoreDefinedComponent>>; +export type DefinedComponent = z.ZodObject> = CoreDefinedComponent< + T, + ComponentRenderer> +>; export type Library = CoreLibrary>; @@ -43,12 +46,12 @@ export type LibraryDefinition = CoreLibraryDefinition>; * ``` */ export function defineComponent>(config: { - name: string; - props: T; - description: string; - component: ComponentRenderer>; + name: string; + props: T; + description: string; + component: ComponentRenderer>; }): DefinedComponent { - return coreDefineComponent>>(config); + return coreDefineComponent>>(config); } // ─── createLibrary (Svelte) ───────────────────────────────────────────────── @@ -65,5 +68,5 @@ export function defineComponent>(config: { * ``` */ export function createLibrary(input: LibraryDefinition): Library { - return coreCreateLibrary>(input) as Library; + return coreCreateLibrary>(input) as Library; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 81f225024..a74aae76c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -64,7 +64,7 @@ importers: version: 16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) fumadocs-mdx: specifier: 14.2.8 - version: 14.2.8(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react@19.2.4)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 14.2.8(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react@19.2.4)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) fumadocs-ui: specifier: 16.6.5 version: 16.6.5(@takumi-rs/image-response@0.68.17)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1) @@ -423,28 +423,46 @@ importers: examples/svelte-chat: dependencies: + '@ai-sdk/openai': + specifier: ^3.0.41 + version: 3.0.41(zod@4.3.6) + '@ai-sdk/svelte': + specifier: ^3.0.0 + version: 3.0.159(svelte@5.53.12)(zod@4.3.6) '@openuidev/svelte-lang': specifier: workspace:* version: link:../../packages/svelte-lang + ai: + specifier: ^6.0.116 + version: 6.0.116(zod@4.3.6) zod: - specifier: ^4.0.0 + specifier: ^4.3.6 version: 4.3.6 devDependencies: '@sveltejs/adapter-auto': specifier: ^4.0.0 - version: 4.0.0(@sveltejs/kit@2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))) + version: 4.0.0(@sveltejs/kit@2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))) '@sveltejs/kit': specifier: ^2.0.0 - version: 2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) '@sveltejs/vite-plugin-svelte': specifier: ^5.0.0 - version: 5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + '@tailwindcss/vite': + specifier: ^4 + version: 4.2.2(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) svelte: specifier: ^5.0.0 version: 5.53.12 + tailwindcss: + specifier: ^4 + version: 4.2.1 + typescript: + specifier: ^5 + version: 5.9.3 vite: specifier: ^6.0.0 - version: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + version: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) examples/vercel-ai-chat: dependencies: @@ -515,7 +533,7 @@ importers: devDependencies: vitest: specifier: ^4.0.18 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) packages/openui-cli: dependencies: @@ -556,7 +574,7 @@ importers: version: 6.22.0(ws@8.18.2)(zod@4.3.6) vitest: specifier: ^4.0.18 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) packages/react-lang: dependencies: @@ -575,7 +593,7 @@ importers: version: 19.2.14 vitest: specifier: ^4.0.18 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) packages/react-ui: dependencies: @@ -711,7 +729,7 @@ importers: version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@8.6.14(prettier@3.5.3))(typescript@5.9.3) '@storybook/react-vite': specifier: ^8.5.3 - version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.43.0)(storybook@8.6.14(prettier@3.5.3))(typescript@5.9.3)(vite@5.4.19(@types/node@22.15.32)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)) + version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.43.0)(storybook@8.6.14(prettier@3.5.3))(typescript@5.9.3)(vite@5.4.19(@types/node@22.15.32)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)) '@storybook/test': specifier: ^8.5.3 version: 8.6.14(storybook@8.6.14(prettier@3.5.3)) @@ -795,7 +813,7 @@ importers: version: 4.20.3 vite: specifier: ^5.0.0 - version: 5.4.19(@types/node@22.15.32)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0) + version: 5.4.19(@types/node@22.15.32)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0) packages/svelte-lang: dependencies: @@ -811,10 +829,10 @@ importers: version: 2.5.7(svelte@5.53.12)(typescript@5.9.3) '@sveltejs/vite-plugin-svelte': specifier: ^5.0.0 - version: 5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) '@testing-library/svelte': specifier: ^5.2.0 - version: 5.3.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + version: 5.3.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) jsdom: specifier: ^26.1.0 version: 26.1.0 @@ -829,10 +847,10 @@ importers: version: 5.9.3 vite: specifier: ^6.0.0 - version: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + version: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) vitest: specifier: ^3.0.0 - version: 3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + version: 3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) packages: @@ -853,6 +871,12 @@ packages: '@ag-ui/core@0.0.45': resolution: {integrity: sha512-Ccsarxb23TChONOWXDbNBqp1fIbOSMht8g7w6AsSYBTtdOwZ7h7AkjNkr3LSdVv+RbT30JMdSLtieJE0YepNPg==} + '@ai-sdk/gateway@2.0.63': + resolution: {integrity: sha512-zFV9ZY6gnflkQ9FGFmWoWH59DSSSP0fj8BAMME6wyNosdUC3nOvqDPUpciNzM0KkZcvHRN9t+dWHl2LVmDv4cQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/gateway@3.0.66': resolution: {integrity: sha512-SIQ0YY0iMuv+07HLsZ+bB990zUJ6S4ujORAh+Jv1V2KGNn73qQKnGO0JBk+w+Res8YqOFSycwDoWcFlQrVxS4A==} engines: {node: '>=18'} @@ -865,12 +889,22 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/provider-utils@3.0.22': + resolution: {integrity: sha512-fFT1KfUUKktfAFm5mClJhS1oux9tP2qgzmEZVl5UdwltQ1LO/s8hd7znVrgKzivwv1s1FIPza0s9OpJaNB/vHw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/provider-utils@4.0.19': resolution: {integrity: sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/provider@2.0.1': + resolution: {integrity: sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng==} + engines: {node: '>=18'} + '@ai-sdk/provider@3.0.8': resolution: {integrity: sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==} engines: {node: '>=18'} @@ -881,6 +915,15 @@ packages: peerDependencies: react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1 + '@ai-sdk/svelte@3.0.159': + resolution: {integrity: sha512-IcytnYV2w8DGki1n7/hhoEfrwNfoi623gK3PaBZXk1Wis4u21eXJB1MVssTsT1JHKXyN/0OQluDJsE5FGtzGOA==} + peerDependencies: + svelte: ^5.31.0 + zod: ^3.25.76 || ^4.1.8 + peerDependenciesMeta: + zod: + optional: true + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -4357,60 +4400,117 @@ packages: '@tailwindcss/node@4.2.1': resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==} + '@tailwindcss/node@4.2.2': + resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} + '@tailwindcss/oxide-android-arm64@4.2.1': resolution: {integrity: sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==} engines: {node: '>= 20'} cpu: [arm64] os: [android] + '@tailwindcss/oxide-android-arm64@4.2.2': + resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + '@tailwindcss/oxide-darwin-arm64@4.2.1': resolution: {integrity: sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==} engines: {node: '>= 20'} cpu: [arm64] os: [darwin] + '@tailwindcss/oxide-darwin-arm64@4.2.2': + resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + '@tailwindcss/oxide-darwin-x64@4.2.1': resolution: {integrity: sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==} engines: {node: '>= 20'} cpu: [x64] os: [darwin] + '@tailwindcss/oxide-darwin-x64@4.2.2': + resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + '@tailwindcss/oxide-freebsd-x64@4.2.1': resolution: {integrity: sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==} engines: {node: '>= 20'} cpu: [x64] os: [freebsd] + '@tailwindcss/oxide-freebsd-x64@4.2.2': + resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': resolution: {integrity: sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==} engines: {node: '>= 20'} cpu: [arm] os: [linux] + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': resolution: {integrity: sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + '@tailwindcss/oxide-linux-arm64-musl@4.2.1': resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + '@tailwindcss/oxide-linux-x64-gnu@4.2.1': resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + '@tailwindcss/oxide-linux-x64-musl@4.2.1': resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + '@tailwindcss/oxide-wasm32-wasi@4.2.1': resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} engines: {node: '>=14.0.0'} @@ -4423,25 +4523,58 @@ packages: - '@emnapi/wasi-threads' - tslib + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': resolution: {integrity: sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==} engines: {node: '>= 20'} cpu: [arm64] os: [win32] + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + '@tailwindcss/oxide-win32-x64-msvc@4.2.1': resolution: {integrity: sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==} engines: {node: '>= 20'} cpu: [x64] os: [win32] + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + '@tailwindcss/oxide@4.2.1': resolution: {integrity: sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==} engines: {node: '>= 20'} + '@tailwindcss/oxide@4.2.2': + resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} + engines: {node: '>= 20'} + '@tailwindcss/postcss@4.2.1': resolution: {integrity: sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw==} + '@tailwindcss/vite@4.2.2': + resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + '@takumi-rs/core-darwin-arm64@0.68.17': resolution: {integrity: sha512-toMlVnB19J+eUVDOV3mhVOqFn+fMazycMZoeQSJQR/GXtog2peRp2dKqSE8w6gi7nGUOrZzX4u4L+eX+vwnfww==} engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'} @@ -5052,6 +5185,12 @@ packages: resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} engines: {node: '>= 8.0.0'} + ai@5.0.159: + resolution: {integrity: sha512-e0qokVtX2eearCpJGVeSETC186HaTEPvZOHMfZcDMAG4E+s3rsTfRj2pupaxUfrIOpQ7ci0WFhBEMVPTWrOpMQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + ai@6.0.116: resolution: {integrity: sha512-7yM+cTmyRLeNIXwt4Vj+mrrJgVQ9RMIW5WO0ydoLoYkewIvsMcvUmqS4j2RJTUXaF1HphwmSKUMQ/HypNRGOmA==} engines: {node: '>=18'} @@ -7300,70 +7439,140 @@ packages: cpu: [arm64] os: [android] + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + lightningcss-darwin-arm64@1.31.1: resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + lightningcss-darwin-x64@1.31.1: resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + lightningcss-freebsd-x64@1.31.1: resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + lightningcss-linux-arm-gnueabihf@1.31.1: resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + lightningcss-linux-arm64-gnu@1.31.1: resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + lightningcss-win32-arm64-msvc@1.31.1: resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + lightningcss-win32-x64-msvc@1.31.1: resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + lightningcss@1.31.1: resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} engines: {node: '>= 12.0.0'} + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -9229,6 +9438,9 @@ packages: tailwindcss@4.2.1: resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==} + tailwindcss@4.2.2: + resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} + tapable@2.3.0: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} @@ -10097,6 +10309,13 @@ snapshots: rxjs: 7.8.1 zod: 3.25.76 + '@ai-sdk/gateway@2.0.63(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 2.0.1 + '@ai-sdk/provider-utils': 3.0.22(zod@4.3.6) + '@vercel/oidc': 3.1.0 + zod: 4.3.6 + '@ai-sdk/gateway@3.0.66(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 @@ -10110,6 +10329,13 @@ snapshots: '@ai-sdk/provider-utils': 4.0.19(zod@4.3.6) zod: 4.3.6 + '@ai-sdk/provider-utils@3.0.22(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 2.0.1 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.0.6 + zod: 4.3.6 + '@ai-sdk/provider-utils@4.0.19(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 @@ -10117,6 +10343,10 @@ snapshots: eventsource-parser: 3.0.6 zod: 4.3.6 + '@ai-sdk/provider@2.0.1': + dependencies: + json-schema: 0.4.0 + '@ai-sdk/provider@3.0.8': dependencies: json-schema: 0.4.0 @@ -10131,6 +10361,14 @@ snapshots: transitivePeerDependencies: - zod + '@ai-sdk/svelte@3.0.159(svelte@5.53.12)(zod@4.3.6)': + dependencies: + '@ai-sdk/provider-utils': 3.0.22(zod@4.3.6) + ai: 5.0.159(zod@4.3.6) + svelte: 5.53.12 + optionalDependencies: + zod: 4.3.6 + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -11862,12 +12100,12 @@ snapshots: '@types/yargs': 17.0.35 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0(typescript@5.9.3)(vite@5.4.19(@types/node@22.15.32)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0(typescript@5.9.3)(vite@5.4.19(@types/node@22.15.32)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0))': dependencies: glob: 10.4.5 magic-string: 0.27.0 react-docgen-typescript: 2.4.0(typescript@5.9.3) - vite: 5.4.19(@types/node@22.15.32)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0) + vite: 5.4.19(@types/node@22.15.32)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0) optionalDependencies: typescript: 5.9.3 @@ -14254,13 +14492,13 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@storybook/builder-vite@8.6.14(storybook@8.6.14(prettier@3.5.3))(vite@5.4.19(@types/node@22.15.32)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0))': + '@storybook/builder-vite@8.6.14(storybook@8.6.14(prettier@3.5.3))(vite@5.4.19(@types/node@22.15.32)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0))': dependencies: '@storybook/csf-plugin': 8.6.14(storybook@8.6.14(prettier@3.5.3)) browser-assert: 1.2.1 storybook: 8.6.14(prettier@3.5.3) ts-dedent: 2.2.0 - vite: 5.4.19(@types/node@22.15.32)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0) + vite: 5.4.19(@types/node@22.15.32)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0) '@storybook/components@8.6.14(storybook@8.6.14(prettier@3.5.3))': dependencies: @@ -14327,11 +14565,11 @@ snapshots: react-dom: 19.2.4(react@19.2.4) storybook: 8.6.14(prettier@3.5.3) - '@storybook/react-vite@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.43.0)(storybook@8.6.14(prettier@3.5.3))(typescript@5.9.3)(vite@5.4.19(@types/node@22.15.32)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0))': + '@storybook/react-vite@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.43.0)(storybook@8.6.14(prettier@3.5.3))(typescript@5.9.3)(vite@5.4.19(@types/node@22.15.32)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.9.3)(vite@5.4.19(@types/node@22.15.32)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.9.3)(vite@5.4.19(@types/node@22.15.32)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)) '@rollup/pluginutils': 5.2.0(rollup@4.43.0) - '@storybook/builder-vite': 8.6.14(storybook@8.6.14(prettier@3.5.3))(vite@5.4.19(@types/node@22.15.32)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)) + '@storybook/builder-vite': 8.6.14(storybook@8.6.14(prettier@3.5.3))(vite@5.4.19(@types/node@22.15.32)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)) '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@8.6.14(prettier@3.5.3))(typescript@5.9.3) find-up: 5.0.0 magic-string: 0.30.17 @@ -14341,7 +14579,7 @@ snapshots: resolve: 1.22.10 storybook: 8.6.14(prettier@3.5.3) tsconfig-paths: 4.2.0 - vite: 5.4.19(@types/node@22.15.32)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0) + vite: 5.4.19(@types/node@22.15.32)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0) optionalDependencies: '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.5.3)) transitivePeerDependencies: @@ -14383,16 +14621,16 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/adapter-auto@4.0.0(@sveltejs/kit@2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))': + '@sveltejs/adapter-auto@4.0.0(@sveltejs/kit@2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))': dependencies: - '@sveltejs/kit': 2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + '@sveltejs/kit': 2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) import-meta-resolve: 4.2.0 - '@sveltejs/kit@2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': + '@sveltejs/kit@2.55.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 0.6.0 @@ -14404,7 +14642,7 @@ snapshots: set-cookie-parser: 3.0.1 sirv: 3.0.2 svelte: 5.53.12 - vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) optionalDependencies: '@opentelemetry/api': 1.9.0 typescript: 5.9.3 @@ -14420,25 +14658,25 @@ snapshots: transitivePeerDependencies: - typescript - '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) debug: 4.4.3 svelte: 5.53.12 - vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': + '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)))(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) debug: 4.4.3 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.21 svelte: 5.53.12 - vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) - vitefu: 1.1.2(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vitefu: 1.1.2(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) transitivePeerDependencies: - supports-color @@ -14458,42 +14696,88 @@ snapshots: source-map-js: 1.2.1 tailwindcss: 4.2.1 + '@tailwindcss/node@4.2.2': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.19.0 + jiti: 2.6.1 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.2 + '@tailwindcss/oxide-android-arm64@4.2.1': optional: true + '@tailwindcss/oxide-android-arm64@4.2.2': + optional: true + '@tailwindcss/oxide-darwin-arm64@4.2.1': optional: true + '@tailwindcss/oxide-darwin-arm64@4.2.2': + optional: true + '@tailwindcss/oxide-darwin-x64@4.2.1': optional: true + '@tailwindcss/oxide-darwin-x64@4.2.2': + optional: true + '@tailwindcss/oxide-freebsd-x64@4.2.1': optional: true + '@tailwindcss/oxide-freebsd-x64@4.2.2': + optional: true + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': optional: true + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + optional: true + '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': optional: true + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + optional: true + '@tailwindcss/oxide-linux-arm64-musl@4.2.1': optional: true + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + optional: true + '@tailwindcss/oxide-linux-x64-gnu@4.2.1': optional: true + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + optional: true + '@tailwindcss/oxide-linux-x64-musl@4.2.1': optional: true + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + optional: true + '@tailwindcss/oxide-wasm32-wasi@4.2.1': optional: true + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + optional: true + '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': optional: true + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + optional: true + '@tailwindcss/oxide-win32-x64-msvc@4.2.1': optional: true + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + optional: true + '@tailwindcss/oxide@4.2.1': optionalDependencies: '@tailwindcss/oxide-android-arm64': 4.2.1 @@ -14509,6 +14793,21 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.2.1 '@tailwindcss/oxide-win32-x64-msvc': 4.2.1 + '@tailwindcss/oxide@4.2.2': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-x64': 4.2.2 + '@tailwindcss/oxide-freebsd-x64': 4.2.2 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-x64-musl': 4.2.2 + '@tailwindcss/oxide-wasm32-wasi': 4.2.2 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 + '@tailwindcss/postcss@4.2.1': dependencies: '@alloc/quick-lru': 5.2.0 @@ -14517,6 +14816,13 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.2.1 + '@tailwindcss/vite@4.2.2(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': + dependencies: + '@tailwindcss/node': 4.2.2 + '@tailwindcss/oxide': 4.2.2 + tailwindcss: 4.2.2 + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + '@takumi-rs/core-darwin-arm64@0.68.17': optional: true @@ -14595,14 +14901,14 @@ snapshots: dependencies: svelte: 5.53.12 - '@testing-library/svelte@5.3.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': + '@testing-library/svelte@5.3.1(svelte@5.53.12)(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@testing-library/dom': 10.4.0 '@testing-library/svelte-core': 1.0.0(svelte@5.53.12) svelte: 5.53.12 optionalDependencies: - vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': dependencies: @@ -14978,21 +15284,21 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@3.2.4(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': + '@vitest/mocker@3.2.4(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) - '@vitest/mocker@4.0.18(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': + '@vitest/mocker@4.0.18(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) '@vitest/pretty-format@2.0.5': dependencies: @@ -15181,6 +15487,14 @@ snapshots: dependencies: humanize-ms: 1.2.1 + ai@5.0.159(zod@4.3.6): + dependencies: + '@ai-sdk/gateway': 2.0.63(zod@4.3.6) + '@ai-sdk/provider': 2.0.1 + '@ai-sdk/provider-utils': 3.0.22(zod@4.3.6) + '@opentelemetry/api': 1.9.0 + zod: 4.3.6 + ai@6.0.116(zod@4.3.6): dependencies: '@ai-sdk/gateway': 3.0.66(zod@4.3.6) @@ -16932,7 +17246,7 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-mdx@14.2.8(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react@19.2.4)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)): + fumadocs-mdx@14.2.8(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react@19.2.4)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0 @@ -16958,7 +17272,7 @@ snapshots: '@types/react': 19.2.14 next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2) react: 19.2.4 - vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - supports-color @@ -17841,36 +18155,69 @@ snapshots: lightningcss-android-arm64@1.31.1: optional: true + lightningcss-android-arm64@1.32.0: + optional: true + lightningcss-darwin-arm64@1.31.1: optional: true + lightningcss-darwin-arm64@1.32.0: + optional: true + lightningcss-darwin-x64@1.31.1: optional: true + lightningcss-darwin-x64@1.32.0: + optional: true + lightningcss-freebsd-x64@1.31.1: optional: true + lightningcss-freebsd-x64@1.32.0: + optional: true + lightningcss-linux-arm-gnueabihf@1.31.1: optional: true + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + lightningcss-linux-arm64-gnu@1.31.1: optional: true + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + lightningcss-linux-arm64-musl@1.31.1: optional: true + lightningcss-linux-arm64-musl@1.32.0: + optional: true + lightningcss-linux-x64-gnu@1.31.1: optional: true + lightningcss-linux-x64-gnu@1.32.0: + optional: true + lightningcss-linux-x64-musl@1.31.1: optional: true + lightningcss-linux-x64-musl@1.32.0: + optional: true + lightningcss-win32-arm64-msvc@1.31.1: optional: true + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + lightningcss-win32-x64-msvc@1.31.1: optional: true + lightningcss-win32-x64-msvc@1.32.0: + optional: true + lightningcss@1.31.1: dependencies: detect-libc: 2.1.2 @@ -17887,6 +18234,22 @@ snapshots: lightningcss-win32-arm64-msvc: 1.31.1 lightningcss-win32-x64-msvc: 1.31.1 + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -20706,6 +21069,8 @@ snapshots: tailwindcss@4.2.1: {} + tailwindcss@4.2.2: {} + tapable@2.3.0: {} tar@7.5.11: @@ -21158,13 +21523,13 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-node@3.2.4(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): + vite-node@3.2.4(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - '@types/node' - jiti @@ -21179,7 +21544,7 @@ snapshots: - tsx - yaml - vite@5.4.19(@types/node@22.15.32)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0): + vite@5.4.19(@types/node@22.15.32)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0): dependencies: esbuild: 0.21.5 postcss: 8.5.6 @@ -21187,11 +21552,11 @@ snapshots: optionalDependencies: '@types/node': 22.15.32 fsevents: 2.3.3 - lightningcss: 1.31.1 + lightningcss: 1.32.0 sass: 1.89.2 terser: 5.43.0 - vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): + vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -21203,13 +21568,13 @@ snapshots: '@types/node': 25.3.2 fsevents: 2.3.3 jiti: 2.6.1 - lightningcss: 1.31.1 + lightningcss: 1.32.0 sass: 1.89.2 terser: 5.43.0 tsx: 4.20.3 yaml: 2.8.0 - vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): + vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) @@ -21221,22 +21586,22 @@ snapshots: '@types/node': 25.3.2 fsevents: 2.3.3 jiti: 2.6.1 - lightningcss: 1.31.1 + lightningcss: 1.32.0 sass: 1.89.2 terser: 5.43.0 tsx: 4.20.3 yaml: 2.8.0 optional: true - vitefu@1.1.2(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)): + vitefu@1.1.2(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)): optionalDependencies: - vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) - vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/mocker': 3.2.4(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -21254,8 +21619,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) - vite-node: 3.2.4(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 @@ -21275,10 +21640,10 @@ snapshots: - tsx - yaml - vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): + vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/mocker': 4.0.18(vite@6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18 @@ -21295,7 +21660,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) + vite: 6.4.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 From f7e126f62ec4dc7129356d0544724c24f63763ba Mon Sep 17 00:00:00 2001 From: abhithesys Date: Tue, 24 Mar 2026 20:24:53 +0530 Subject: [PATCH 07/12] format svelte-lang --- .../src/__tests__/Renderer.test.ts | 214 ++++++++--------- .../svelte-lang/src/__tests__/library.test.ts | 220 +++++++++--------- .../src/__tests__/validation.test.ts | 210 ++++++++--------- .../svelte-lang/src/lib/context.svelte.ts | 140 +++++------ packages/svelte-lang/src/lib/index.ts | 64 ++--- .../svelte-lang/src/lib/validation.svelte.ts | 130 +++++------ 6 files changed, 489 insertions(+), 489 deletions(-) diff --git a/packages/svelte-lang/src/__tests__/Renderer.test.ts b/packages/svelte-lang/src/__tests__/Renderer.test.ts index 23ac87969..846eb9d82 100644 --- a/packages/svelte-lang/src/__tests__/Renderer.test.ts +++ b/packages/svelte-lang/src/__tests__/Renderer.test.ts @@ -1,23 +1,23 @@ -import { describe, it, expect, vi } from "vitest"; import { render } from "@testing-library/svelte"; -import { z } from "zod"; import { tick } from "svelte"; +import { describe, expect, it, vi } from "vitest"; +import { z } from "zod"; import Renderer from "../lib/Renderer.svelte"; -import { defineComponent, createLibrary } from "../lib/library.js"; +import { createLibrary, defineComponent } from "../lib/library.js"; // Dummy renderer — never actually renders DOM, used for parser/callback tests const DummyComponent = (() => null) as any; const TextContent = defineComponent({ - name: "TextContent", - props: z.object({ text: z.string() }), - description: "Displays text content", - component: DummyComponent, + name: "TextContent", + props: z.object({ text: z.string() }), + description: "Displays text content", + component: DummyComponent, }); const library = createLibrary({ - components: [TextContent], - root: "TextContent", + components: [TextContent], + root: "TextContent", }); // openui-lang uses assignment syntax: `identifier = Component(args)` @@ -26,102 +26,102 @@ const VALID_RESPONSE = 'root = TextContent("Hello world")'; // ─── Renderer ─────────────────────────────────────────────────────────────── describe("Renderer", () => { - it("renders without errors when response is null", () => { - const { container } = render(Renderer, { - props: { - response: null, - library, - }, - }); - - // Should render an empty container (no crash) - expect(container).toBeDefined(); - }); - - it("renders without errors when response is empty string", () => { - const { container } = render(Renderer, { - props: { - response: "", - library, - }, - }); - - expect(container).toBeDefined(); - }); - - it("calls onParseResult with null when response is null", async () => { - const onParseResult = vi.fn(); - - render(Renderer, { - props: { - response: null, - library, - onParseResult, - }, - }); - - // $effect runs asynchronously — flush microtasks - await tick(); - - expect(onParseResult).toHaveBeenCalledWith(null); - }); - - it("calls onParseResult with a ParseResult when given valid openui-lang", async () => { - const onParseResult = vi.fn(); - - render(Renderer, { - props: { - response: VALID_RESPONSE, - library, - onParseResult, - }, - }); - - await tick(); - - expect(onParseResult).toHaveBeenCalled(); - const result = onParseResult.mock.calls[onParseResult.mock.calls.length - 1]![0]; - expect(result).not.toBeNull(); - expect(result.root).toBeDefined(); - expect(result.root).not.toBeNull(); - }); - - it("parse result contains the correct component typeName", async () => { - const onParseResult = vi.fn(); - - render(Renderer, { - props: { - response: VALID_RESPONSE, - library, - onParseResult, - }, - }); - - await tick(); - - const result = onParseResult.mock.calls[onParseResult.mock.calls.length - 1]![0]; - expect(result?.root?.typeName).toBe("TextContent"); - }); - - it("defaults isStreaming to false", () => { - // Should not throw when isStreaming is omitted - const { container } = render(Renderer, { - props: { - response: null, - library, - }, - }); - expect(container).toBeDefined(); - }); - - it("accepts isStreaming prop without errors", () => { - const { container } = render(Renderer, { - props: { - response: null, - library, - isStreaming: true, - }, - }); - expect(container).toBeDefined(); - }); + it("renders without errors when response is null", () => { + const { container } = render(Renderer, { + props: { + response: null, + library, + }, + }); + + // Should render an empty container (no crash) + expect(container).toBeDefined(); + }); + + it("renders without errors when response is empty string", () => { + const { container } = render(Renderer, { + props: { + response: "", + library, + }, + }); + + expect(container).toBeDefined(); + }); + + it("calls onParseResult with null when response is null", async () => { + const onParseResult = vi.fn(); + + render(Renderer, { + props: { + response: null, + library, + onParseResult, + }, + }); + + // $effect runs asynchronously — flush microtasks + await tick(); + + expect(onParseResult).toHaveBeenCalledWith(null); + }); + + it("calls onParseResult with a ParseResult when given valid openui-lang", async () => { + const onParseResult = vi.fn(); + + render(Renderer, { + props: { + response: VALID_RESPONSE, + library, + onParseResult, + }, + }); + + await tick(); + + expect(onParseResult).toHaveBeenCalled(); + const result = onParseResult.mock.calls[onParseResult.mock.calls.length - 1]![0]; + expect(result).not.toBeNull(); + expect(result.root).toBeDefined(); + expect(result.root).not.toBeNull(); + }); + + it("parse result contains the correct component typeName", async () => { + const onParseResult = vi.fn(); + + render(Renderer, { + props: { + response: VALID_RESPONSE, + library, + onParseResult, + }, + }); + + await tick(); + + const result = onParseResult.mock.calls[onParseResult.mock.calls.length - 1]![0]; + expect(result?.root?.typeName).toBe("TextContent"); + }); + + it("defaults isStreaming to false", () => { + // Should not throw when isStreaming is omitted + const { container } = render(Renderer, { + props: { + response: null, + library, + }, + }); + expect(container).toBeDefined(); + }); + + it("accepts isStreaming prop without errors", () => { + const { container } = render(Renderer, { + props: { + response: null, + library, + isStreaming: true, + }, + }); + expect(container).toBeDefined(); + }); }); diff --git a/packages/svelte-lang/src/__tests__/library.test.ts b/packages/svelte-lang/src/__tests__/library.test.ts index f37f63364..085abaf61 100644 --- a/packages/svelte-lang/src/__tests__/library.test.ts +++ b/packages/svelte-lang/src/__tests__/library.test.ts @@ -1,127 +1,127 @@ -import { describe, it, expect } from "vitest"; +import { describe, expect, it } from "vitest"; import { z } from "zod"; -import { defineComponent, createLibrary } from "../lib/library.js"; +import { createLibrary, defineComponent } from "../lib/library.js"; // Dummy renderer — never actually called in these tests const DummyComponent = (() => null) as any; function makeComponent(name: string, schema: z.ZodObject, description: string) { - return defineComponent({ - name, - props: schema, - description, - component: DummyComponent, - }); + return defineComponent({ + name, + props: schema, + description, + component: DummyComponent, + }); } // ─── defineComponent ──────────────────────────────────────────────────────── describe("defineComponent", () => { - it("returns an object with name, props, description, component, and ref", () => { - const schema = z.object({ label: z.string() }); - const result = defineComponent({ - name: "Badge", - props: schema, - description: "A simple badge", - component: DummyComponent, - }); - - expect(result.name).toBe("Badge"); - expect(result.props).toBe(schema); - expect(result.description).toBe("A simple badge"); - expect(result.component).toBe(DummyComponent); - expect(result.ref).toBeDefined(); - }); - - it("registers the Zod schema in the global registry", () => { - const schema = z.object({ title: z.string() }); - const comp = defineComponent({ - name: "Heading", - props: schema, - description: "A heading element", - component: DummyComponent, - }); - - // After defineComponent, the schema should be in the global registry - expect(z.globalRegistry.has(comp.props)).toBe(true); - }); + it("returns an object with name, props, description, component, and ref", () => { + const schema = z.object({ label: z.string() }); + const result = defineComponent({ + name: "Badge", + props: schema, + description: "A simple badge", + component: DummyComponent, + }); + + expect(result.name).toBe("Badge"); + expect(result.props).toBe(schema); + expect(result.description).toBe("A simple badge"); + expect(result.component).toBe(DummyComponent); + expect(result.ref).toBeDefined(); + }); + + it("registers the Zod schema in the global registry", () => { + const schema = z.object({ title: z.string() }); + const comp = defineComponent({ + name: "Heading", + props: schema, + description: "A heading element", + component: DummyComponent, + }); + + // After defineComponent, the schema should be in the global registry + expect(z.globalRegistry.has(comp.props)).toBe(true); + }); }); // ─── createLibrary ────────────────────────────────────────────────────────── describe("createLibrary", () => { - const TextContent = makeComponent( - "TextContent", - z.object({ text: z.string() }), - "Displays text content", - ); - - const Container = makeComponent( - "Container", - z.object({ title: z.string() }), - "A container with a title", - ); - - it("creates a library with a components record", () => { - const lib = createLibrary({ components: [TextContent, Container] }); - - expect(lib.components.TextContent).toBe(TextContent); - expect(lib.components.Container).toBe(Container); - expect(Object.keys(lib.components)).toHaveLength(2); - }); - - it("stores root and componentGroups", () => { - const lib = createLibrary({ - components: [TextContent], - root: "TextContent", - componentGroups: [{ name: "Display", components: ["TextContent"] }], - }); - - expect(lib.root).toBe("TextContent"); - expect(lib.componentGroups).toEqual([{ name: "Display", components: ["TextContent"] }]); - }); - - it("throws if root component is not found in components", () => { - expect(() => - createLibrary({ - components: [TextContent], - root: "NonExistent", - }), - ).toThrow(/Root component "NonExistent" was not found/); - }); - - it("prompt() returns a string containing component descriptions", () => { - const lib = createLibrary({ - components: [TextContent, Container], - root: "TextContent", - }); - - const prompt = lib.prompt(); - expect(typeof prompt).toBe("string"); - expect(prompt.length).toBeGreaterThan(0); - // The prompt should mention at least one component name - expect(prompt).toContain("TextContent"); - }); - - it("toJSONSchema() returns an object with $defs", () => { - const lib = createLibrary({ - components: [TextContent], - root: "TextContent", - }); - - const schema = lib.toJSONSchema() as Record; - expect(schema).toBeDefined(); - expect(typeof schema).toBe("object"); - expect(schema["$defs"]).toBeDefined(); - expect(typeof schema["$defs"]).toBe("object"); - }); - - it("works without a root component", () => { - const lib = createLibrary({ components: [TextContent] }); - - expect(lib.root).toBeUndefined(); - // prompt/schema should still work - expect(typeof lib.prompt()).toBe("string"); - expect(lib.toJSONSchema()).toBeDefined(); - }); + const TextContent = makeComponent( + "TextContent", + z.object({ text: z.string() }), + "Displays text content", + ); + + const Container = makeComponent( + "Container", + z.object({ title: z.string() }), + "A container with a title", + ); + + it("creates a library with a components record", () => { + const lib = createLibrary({ components: [TextContent, Container] }); + + expect(lib.components.TextContent).toBe(TextContent); + expect(lib.components.Container).toBe(Container); + expect(Object.keys(lib.components)).toHaveLength(2); + }); + + it("stores root and componentGroups", () => { + const lib = createLibrary({ + components: [TextContent], + root: "TextContent", + componentGroups: [{ name: "Display", components: ["TextContent"] }], + }); + + expect(lib.root).toBe("TextContent"); + expect(lib.componentGroups).toEqual([{ name: "Display", components: ["TextContent"] }]); + }); + + it("throws if root component is not found in components", () => { + expect(() => + createLibrary({ + components: [TextContent], + root: "NonExistent", + }), + ).toThrow(/Root component "NonExistent" was not found/); + }); + + it("prompt() returns a string containing component descriptions", () => { + const lib = createLibrary({ + components: [TextContent, Container], + root: "TextContent", + }); + + const prompt = lib.prompt(); + expect(typeof prompt).toBe("string"); + expect(prompt.length).toBeGreaterThan(0); + // The prompt should mention at least one component name + expect(prompt).toContain("TextContent"); + }); + + it("toJSONSchema() returns an object with $defs", () => { + const lib = createLibrary({ + components: [TextContent], + root: "TextContent", + }); + + const schema = lib.toJSONSchema() as Record; + expect(schema).toBeDefined(); + expect(typeof schema).toBe("object"); + expect(schema["$defs"]).toBeDefined(); + expect(typeof schema["$defs"]).toBe("object"); + }); + + it("works without a root component", () => { + const lib = createLibrary({ components: [TextContent] }); + + expect(lib.root).toBeUndefined(); + // prompt/schema should still work + expect(typeof lib.prompt()).toBe("string"); + expect(lib.toJSONSchema()).toBeDefined(); + }); }); diff --git a/packages/svelte-lang/src/__tests__/validation.test.ts b/packages/svelte-lang/src/__tests__/validation.test.ts index 16a7f8cb7..4b319dac0 100644 --- a/packages/svelte-lang/src/__tests__/validation.test.ts +++ b/packages/svelte-lang/src/__tests__/validation.test.ts @@ -1,127 +1,127 @@ -import { describe, it, expect } from "vitest"; +import { describe, expect, it } from "vitest"; import { - builtInValidators, - parseRules, - parseStructuredRules, - validate, + builtInValidators, + parseRules, + parseStructuredRules, + validate, } from "../lib/validation.svelte.js"; // ─── builtInValidators ────────────────────────────────────────────────────── describe("builtInValidators", () => { - it("has all expected validators", () => { - const expected = [ - "required", - "email", - "url", - "numeric", - "min", - "max", - "minLength", - "maxLength", - "pattern", - ]; - for (const name of expected) { - expect(builtInValidators[name]).toBeDefined(); - expect(typeof builtInValidators[name]).toBe("function"); - } - }); + it("has all expected validators", () => { + const expected = [ + "required", + "email", + "url", + "numeric", + "min", + "max", + "minLength", + "maxLength", + "pattern", + ]; + for (const name of expected) { + expect(builtInValidators[name]).toBeDefined(); + expect(typeof builtInValidators[name]).toBe("function"); + } + }); }); // ─── parseRules ───────────────────────────────────────────────────────────── describe("parseRules", () => { - it("parses simple rule strings into ParsedRule objects", () => { - const result = parseRules(["required", "email"]); - expect(result).toEqual([{ type: "required" }, { type: "email" }]); - }); - - it("parses rules with numeric arguments", () => { - const result = parseRules(["min:8", "maxLength:100"]); - expect(result).toEqual([ - { type: "min", arg: 8 }, - { type: "maxLength", arg: 100 }, - ]); - }); - - it("parses rules with string arguments", () => { - const result = parseRules(["pattern:^[a-z]+"]); - expect(result).toEqual([{ type: "pattern", arg: "^[a-z]+" }]); - }); - - it("returns an empty array for non-array input", () => { - expect(parseRules(null)).toEqual([]); - expect(parseRules(undefined)).toEqual([]); - expect(parseRules("required")).toEqual([]); - }); - - it("filters out non-string entries", () => { - const result = parseRules(["required", 42, null, "email"]); - expect(result).toEqual([{ type: "required" }, { type: "email" }]); - }); + it("parses simple rule strings into ParsedRule objects", () => { + const result = parseRules(["required", "email"]); + expect(result).toEqual([{ type: "required" }, { type: "email" }]); + }); + + it("parses rules with numeric arguments", () => { + const result = parseRules(["min:8", "maxLength:100"]); + expect(result).toEqual([ + { type: "min", arg: 8 }, + { type: "maxLength", arg: 100 }, + ]); + }); + + it("parses rules with string arguments", () => { + const result = parseRules(["pattern:^[a-z]+"]); + expect(result).toEqual([{ type: "pattern", arg: "^[a-z]+" }]); + }); + + it("returns an empty array for non-array input", () => { + expect(parseRules(null)).toEqual([]); + expect(parseRules(undefined)).toEqual([]); + expect(parseRules("required")).toEqual([]); + }); + + it("filters out non-string entries", () => { + const result = parseRules(["required", 42, null, "email"]); + expect(result).toEqual([{ type: "required" }, { type: "email" }]); + }); }); // ─── validate ─────────────────────────────────────────────────────────────── describe("validate", () => { - it("returns error string for required on empty value", () => { - const rules = [{ type: "required" }]; - const error = validate("", rules); - expect(error).toBe("This field is required"); - }); - - it("returns undefined when valid value passes required", () => { - const rules = [{ type: "required" }]; - expect(validate("hello", rules)).toBeUndefined(); - }); - - it("validates email format", () => { - const rules = [{ type: "email" }]; - expect(validate("bad-email", rules)).toBe("Please enter a valid email"); - expect(validate("test@email.com", rules)).toBeUndefined(); - }); - - it("validates min/max with numeric arguments", () => { - expect(validate(3, [{ type: "min", arg: 5 }])).toBe("Must be at least 5"); - expect(validate(10, [{ type: "min", arg: 5 }])).toBeUndefined(); - expect(validate(20, [{ type: "max", arg: 10 }])).toBe("Must be no more than 10"); - expect(validate(5, [{ type: "max", arg: 10 }])).toBeUndefined(); - }); - - it("stops on first error with multiple rules", () => { - const rules = [{ type: "required" }, { type: "email" }]; - // Empty string triggers "required" first, not "email" - expect(validate("", rules)).toBe("This field is required"); - }); - - it("returns undefined when no rules match", () => { - expect(validate("anything", [{ type: "nonExistentRule" }])).toBeUndefined(); - }); + it("returns error string for required on empty value", () => { + const rules = [{ type: "required" }]; + const error = validate("", rules); + expect(error).toBe("This field is required"); + }); + + it("returns undefined when valid value passes required", () => { + const rules = [{ type: "required" }]; + expect(validate("hello", rules)).toBeUndefined(); + }); + + it("validates email format", () => { + const rules = [{ type: "email" }]; + expect(validate("bad-email", rules)).toBe("Please enter a valid email"); + expect(validate("test@email.com", rules)).toBeUndefined(); + }); + + it("validates min/max with numeric arguments", () => { + expect(validate(3, [{ type: "min", arg: 5 }])).toBe("Must be at least 5"); + expect(validate(10, [{ type: "min", arg: 5 }])).toBeUndefined(); + expect(validate(20, [{ type: "max", arg: 10 }])).toBe("Must be no more than 10"); + expect(validate(5, [{ type: "max", arg: 10 }])).toBeUndefined(); + }); + + it("stops on first error with multiple rules", () => { + const rules = [{ type: "required" }, { type: "email" }]; + // Empty string triggers "required" first, not "email" + expect(validate("", rules)).toBe("This field is required"); + }); + + it("returns undefined when no rules match", () => { + expect(validate("anything", [{ type: "nonExistentRule" }])).toBeUndefined(); + }); }); // ─── parseStructuredRules ─────────────────────────────────────────────────── describe("parseStructuredRules", () => { - it("parses an object of rules into ParsedRule array", () => { - const result = parseStructuredRules({ required: true, minLength: 5 }); - expect(result).toContainEqual({ type: "required" }); - expect(result).toContainEqual({ type: "minLength", arg: 5 }); - }); - - it("skips false/undefined/null values", () => { - const result = parseStructuredRules({ - required: true, - email: false, - max: undefined, - min: null, - }); - expect(result).toEqual([{ type: "required" }]); - }); - - it("returns empty array for non-object input", () => { - expect(parseStructuredRules(null)).toEqual([]); - expect(parseStructuredRules(undefined)).toEqual([]); - expect(parseStructuredRules([])).toEqual([]); - expect(parseStructuredRules("string")).toEqual([]); - }); + it("parses an object of rules into ParsedRule array", () => { + const result = parseStructuredRules({ required: true, minLength: 5 }); + expect(result).toContainEqual({ type: "required" }); + expect(result).toContainEqual({ type: "minLength", arg: 5 }); + }); + + it("skips false/undefined/null values", () => { + const result = parseStructuredRules({ + required: true, + email: false, + max: undefined, + min: null, + }); + expect(result).toEqual([{ type: "required" }]); + }); + + it("returns empty array for non-object input", () => { + expect(parseStructuredRules(null)).toEqual([]); + expect(parseStructuredRules(undefined)).toEqual([]); + expect(parseStructuredRules([])).toEqual([]); + expect(parseStructuredRules("string")).toEqual([]); + }); }); diff --git a/packages/svelte-lang/src/lib/context.svelte.ts b/packages/svelte-lang/src/lib/context.svelte.ts index 04627c367..a69e41d1e 100644 --- a/packages/svelte-lang/src/lib/context.svelte.ts +++ b/packages/svelte-lang/src/lib/context.svelte.ts @@ -4,8 +4,8 @@ import type { Library } from "./library.js"; // ─── Action config ─── export interface ActionConfig { - type?: string; - params?: Record; + type?: string; + params?: Record; } // ─── OpenUI context ─── @@ -17,42 +17,42 @@ export interface ActionConfig { * This avoids the stale-closure problem and matches Svelte's snippet model. */ export interface OpenUIContextValue { - /** The active component library (schema + renderers). */ - library: Library; - - /** - * Trigger an action. Components call this to fire structured ActionEvents. - * - * @param userMessage Human-readable label ("Submit Application") - * @param formName Optional form name — if provided, form state for this form is included - * @param action Optional custom action config { type, params } - */ - triggerAction: (userMessage: string, formName?: string, action?: ActionConfig) => void; - - /** Whether the LLM is currently streaming content. */ - isStreaming: boolean; - - /** Get a form field value. Returns undefined if not set. */ - getFieldValue: (formName: string | undefined, name: string) => any; - - /** - * Set a form field value. - * - * @param formName The form's name prop - * @param componentType The component type (e.g. "Input", "Select", "RadioGroup") - * @param name The field's name prop - * @param value The new value - * @param shouldTriggerSaveCallback When true, persists the updated state via updateMessage. - * Text inputs should pass `false` on change and `true` on blur. - * Discrete inputs (Select, RadioGroup, etc.) should always pass `true`. - */ - setFieldValue: ( - formName: string | undefined, - componentType: string | undefined, - name: string, - value: any, - shouldTriggerSaveCallback?: boolean, - ) => void; + /** The active component library (schema + renderers). */ + library: Library; + + /** + * Trigger an action. Components call this to fire structured ActionEvents. + * + * @param userMessage Human-readable label ("Submit Application") + * @param formName Optional form name — if provided, form state for this form is included + * @param action Optional custom action config { type, params } + */ + triggerAction: (userMessage: string, formName?: string, action?: ActionConfig) => void; + + /** Whether the LLM is currently streaming content. */ + isStreaming: boolean; + + /** Get a form field value. Returns undefined if not set. */ + getFieldValue: (formName: string | undefined, name: string) => any; + + /** + * Set a form field value. + * + * @param formName The form's name prop + * @param componentType The component type (e.g. "Input", "Select", "RadioGroup") + * @param name The field's name prop + * @param value The new value + * @param shouldTriggerSaveCallback When true, persists the updated state via updateMessage. + * Text inputs should pass `false` on change and `true` on blur. + * Discrete inputs (Select, RadioGroup, etc.) should always pass `true`. + */ + setFieldValue: ( + formName: string | undefined, + componentType: string | undefined, + name: string, + value: any, + shouldTriggerSaveCallback?: boolean, + ) => void; } const OPENUI_CONTEXT_KEY = Symbol("openui-context"); @@ -61,11 +61,11 @@ const FORM_NAME_CONTEXT_KEY = Symbol("openui-form-name"); // ─── Context setters ─── export function setOpenUIContext(value: OpenUIContextValue): void { - setContext(OPENUI_CONTEXT_KEY, value); + setContext(OPENUI_CONTEXT_KEY, value); } export function setFormNameContext(formName: string | undefined): void { - setContext(FORM_NAME_CONTEXT_KEY, formName); + setContext(FORM_NAME_CONTEXT_KEY, formName); } // ─── Context getters ─── @@ -74,18 +74,18 @@ export function setFormNameContext(formName: string | undefined): void { * Access the full OpenUI context. Throws if used outside a . */ export function getOpenUIContext(): OpenUIContextValue { - const ctx = getContext(OPENUI_CONTEXT_KEY); - if (!ctx) { - throw new Error("getOpenUIContext must be used within a component."); - } - return ctx; + const ctx = getContext(OPENUI_CONTEXT_KEY); + if (!ctx) { + throw new Error("getOpenUIContext must be used within a component."); + } + return ctx; } /** * Get the triggerAction function for firing structured action events. */ export function getTriggerAction() { - return getOpenUIContext().triggerAction; + return getOpenUIContext().triggerAction; } /** @@ -93,21 +93,21 @@ export function getTriggerAction() { * Returns a getter — use as `getIsStreaming()` for reactive reads. */ export function getIsStreaming(): boolean { - return getOpenUIContext().isStreaming; + return getOpenUIContext().isStreaming; } /** * Get a form field value from the form state context. */ export function getGetFieldValue() { - return getOpenUIContext().getFieldValue; + return getOpenUIContext().getFieldValue; } /** * Get the setFieldValue function for updating form field values. */ export function getSetFieldValue() { - return getOpenUIContext().setFieldValue; + return getOpenUIContext().setFieldValue; } /** @@ -115,7 +115,7 @@ export function getSetFieldValue() { * Returns undefined if not inside a Form. */ export function getFormName(): string | undefined { - return getContext(FORM_NAME_CONTEXT_KEY); + return getContext(FORM_NAME_CONTEXT_KEY); } // ─── Default value helper ─── @@ -125,26 +125,26 @@ export function getFormName(): string | undefined { * finishes — but only if the user hasn't already set a value. */ export function useSetDefaultValue({ - formName, - componentType, - name, - existingValue, - defaultValue, - shouldTriggerSaveCallback = false, + formName, + componentType, + name, + existingValue, + defaultValue, + shouldTriggerSaveCallback = false, }: { - formName?: string; - componentType: string; - name: string; - existingValue: any; - defaultValue: any; - shouldTriggerSaveCallback?: boolean; + formName?: string; + componentType: string; + name: string; + existingValue: any; + defaultValue: any; + shouldTriggerSaveCallback?: boolean; }): void { - const setFieldValue = getSetFieldValue(); - const ctx = getOpenUIContext(); - - $effect(() => { - if (!ctx.isStreaming && existingValue === undefined && defaultValue !== undefined) { - setFieldValue(formName, componentType, name, defaultValue, shouldTriggerSaveCallback); - } - }); + const setFieldValue = getSetFieldValue(); + const ctx = getOpenUIContext(); + + $effect(() => { + if (!ctx.isStreaming && existingValue === undefined && defaultValue !== undefined) { + setFieldValue(formName, componentType, name, defaultValue, shouldTriggerSaveCallback); + } + }); } diff --git a/packages/svelte-lang/src/lib/index.ts b/packages/svelte-lang/src/lib/index.ts index 1f9be9f19..c413e6a91 100644 --- a/packages/svelte-lang/src/lib/index.ts +++ b/packages/svelte-lang/src/lib/index.ts @@ -2,63 +2,63 @@ export { createLibrary, defineComponent } from "./library.js"; export type { - ComponentGroup, - ComponentRenderProps, - ComponentRenderer, - DefinedComponent, - Library, - LibraryDefinition, - PromptOptions, - SubComponentOf, + ComponentGroup, + ComponentRenderer, + ComponentRenderProps, + DefinedComponent, + Library, + LibraryDefinition, + PromptOptions, + SubComponentOf, } from "./library.js"; // ─── Renderer ─── -import type { Library } from "./library.js"; import type { ActionEvent, ParseResult } from "@openuidev/lang-core"; +import type { Library } from "./library.js"; export { default as Renderer } from "./Renderer.svelte"; /** Props accepted by the Renderer component. */ export interface RendererProps { - response: string | null; - library: Library; - isStreaming?: boolean; - onAction?: (event: ActionEvent) => void; - onStateUpdate?: (state: Record) => void; - initialState?: Record; - onParseResult?: (result: ParseResult | null) => void; + response: string | null; + library: Library; + isStreaming?: boolean; + onAction?: (event: ActionEvent) => void; + onStateUpdate?: (state: Record) => void; + initialState?: Record; + onParseResult?: (result: ParseResult | null) => void; } // ─── Context (for use inside component renderers) ─── export { - getFormName, - getGetFieldValue, - getIsStreaming, - getOpenUIContext, - getSetFieldValue, - getTriggerAction, - setFormNameContext, - setOpenUIContext, - useSetDefaultValue, + getFormName, + getGetFieldValue, + getIsStreaming, + getOpenUIContext, + getSetFieldValue, + getTriggerAction, + setFormNameContext, + setOpenUIContext, + useSetDefaultValue, } from "./context.svelte.js"; export type { ActionConfig, OpenUIContextValue } from "./context.svelte.js"; // ─── Form validation ─── export { - createFormValidation, - getFormValidation, - setFormValidationContext, + createFormValidation, + getFormValidation, + setFormValidationContext, } from "./validation.svelte.js"; export type { FormValidationContextValue } from "./validation.svelte.js"; export { - builtInValidators, - parseRules, - parseStructuredRules, - validate, + builtInValidators, + parseRules, + parseStructuredRules, + validate, } from "./validation.svelte.js"; export type { ParsedRule, ValidatorFn } from "./validation.svelte.js"; diff --git a/packages/svelte-lang/src/lib/validation.svelte.ts b/packages/svelte-lang/src/lib/validation.svelte.ts index a86c0ab2c..a05153ddc 100644 --- a/packages/svelte-lang/src/lib/validation.svelte.ts +++ b/packages/svelte-lang/src/lib/validation.svelte.ts @@ -1,12 +1,12 @@ -import { getContext, setContext } from "svelte"; import { - builtInValidators, - parseRules, - parseStructuredRules, - validate, - type ParsedRule, - type ValidatorFn, + builtInValidators, + parseRules, + parseStructuredRules, + validate, + type ParsedRule, + type ValidatorFn, } from "@openuidev/lang-core"; +import { getContext, setContext } from "svelte"; // ─── Re-exports from lang-core ─── @@ -16,12 +16,12 @@ export type { ParsedRule, ValidatorFn }; // ─── Form validation context ─── export interface FormValidationContextValue { - errors: Record; - validateField: (name: string, value: unknown, rules: ParsedRule[]) => boolean; - registerField: (name: string, rules: ParsedRule[], getValue: () => unknown) => void; - unregisterField: (name: string) => void; - validateForm: () => boolean; - clearFieldError: (name: string) => void; + errors: Record; + validateField: (name: string, value: unknown, rules: ParsedRule[]) => boolean; + registerField: (name: string, rules: ParsedRule[], getValue: () => unknown) => void; + unregisterField: (name: string) => void; + validateForm: () => boolean; + clearFieldError: (name: string) => void; } const FORM_VALIDATION_CONTEXT_KEY = Symbol("openui-form-validation"); @@ -33,56 +33,56 @@ const FORM_VALIDATION_CONTEXT_KEY = Symbol("openui-form-validation"); * provide the result via `setFormValidationContext()`. */ export function createFormValidation(): FormValidationContextValue { - let errors = $state>({}); - const fields: Record unknown }> = {}; - - function validateField(name: string, value: unknown, rules: ParsedRule[]): boolean { - const error = validate(value, rules); - if (errors[name] !== error) { - errors[name] = error; - } - return !error; - } - - function registerField(name: string, rules: ParsedRule[], getValue: () => unknown): void { - fields[name] = { rules, getValue }; - } - - function unregisterField(name: string): void { - delete fields[name]; - } - - function validateForm(): boolean { - let allValid = true; - const newErrors: Record = {}; - - for (const [name, reg] of Object.entries(fields)) { - const value = reg.getValue(); - const error = validate(value, reg.rules); - newErrors[name] = error; - if (error) allValid = false; - } - - errors = newErrors; - return allValid; - } - - function clearFieldError(name: string): void { - if (errors[name] !== undefined) { - errors[name] = undefined; - } - } - - return { - get errors() { - return errors; - }, - validateField, - registerField, - unregisterField, - validateForm, - clearFieldError, - }; + let errors = $state>({}); + const fields: Record unknown }> = {}; + + function validateField(name: string, value: unknown, rules: ParsedRule[]): boolean { + const error = validate(value, rules); + if (errors[name] !== error) { + errors[name] = error; + } + return !error; + } + + function registerField(name: string, rules: ParsedRule[], getValue: () => unknown): void { + fields[name] = { rules, getValue }; + } + + function unregisterField(name: string): void { + delete fields[name]; + } + + function validateForm(): boolean { + let allValid = true; + const newErrors: Record = {}; + + for (const [name, reg] of Object.entries(fields)) { + const value = reg.getValue(); + const error = validate(value, reg.rules); + newErrors[name] = error; + if (error) allValid = false; + } + + errors = newErrors; + return allValid; + } + + function clearFieldError(name: string): void { + if (errors[name] !== undefined) { + errors[name] = undefined; + } + } + + return { + get errors() { + return errors; + }, + validateField, + registerField, + unregisterField, + validateForm, + clearFieldError, + }; } /** @@ -90,12 +90,12 @@ export function createFormValidation(): FormValidationContextValue { * Returns null if not inside a Form with validation. */ export function getFormValidation(): FormValidationContextValue | null { - return getContext(FORM_VALIDATION_CONTEXT_KEY) ?? null; + return getContext(FORM_VALIDATION_CONTEXT_KEY) ?? null; } /** * Provide a FormValidationContextValue to child components. */ export function setFormValidationContext(value: FormValidationContextValue): void { - setContext(FORM_VALIDATION_CONTEXT_KEY, value); + setContext(FORM_VALIDATION_CONTEXT_KEY, value); } From a7f2cc5238e9642aa6c6e7f3b6429dea9363c665 Mon Sep 17 00:00:00 2001 From: abhithesys Date: Tue, 24 Mar 2026 22:59:30 +0530 Subject: [PATCH 08/12] fix(svelte-lang): improve error boundary recovery, reactive context, and useSetDefaultValue - RenderNode: auto-retry rendering when props change after an error (captures reset fn from svelte:boundary and calls it on prop change, with untrack to prevent infinite loops) - getIsStreaming: return () => boolean getter instead of a plain boolean snapshot so consumers get reactive updates - useSetDefaultValue: read existing value from form state inside instead of accepting a stale snapshot parameter, fixing reactivity - README: add children rendering, parser errors, context reactivity note, streaming parser docs, types, and React comparison table --- packages/svelte-lang/README.md | 147 +++++++++++++----- .../svelte-lang/src/lib/RenderNode.svelte | 104 ++++++++----- .../svelte-lang/src/lib/context.svelte.ts | 21 +-- 3 files changed, 181 insertions(+), 91 deletions(-) diff --git a/packages/svelte-lang/README.md b/packages/svelte-lang/README.md index 4627ba62d..8fbee62e5 100644 --- a/packages/svelte-lang/README.md +++ b/packages/svelte-lang/README.md @@ -31,7 +31,7 @@ pnpm add @openuidev/svelte-lang

@@ -72,7 +72,7 @@ const library = createLibrary({ const systemPrompt = library.prompt({ preamble: "You are a helpful assistant.", additionalRules: ["Always greet the user by name."], - examples: [""], + examples: ['User: Hi\n\nroot = Greeting("Alice", "happy")'], }); ``` @@ -81,6 +81,7 @@ const systemPrompt = library.prompt({ ```svelte @@ -97,76 +98,135 @@ const systemPrompt = library.prompt({ ### Component Definition -| Export | Description | -| :--- | :--- | -| `defineComponent(config)` | Define a single component with a name, Zod props schema, description, and Svelte component | -| `createLibrary(definition)` | Create a library from an array of defined components | +| Export | Description | +| :-------------------------- | :----------------------------------------------------------------------------------------- | +| `defineComponent(config)` | Define a single component with a name, Zod props schema, description, and Svelte component | +| `createLibrary(definition)` | Create a library from an array of defined components | ### Rendering -| Export | Description | -| :--- | :--- | +| Export | Description | +| :--------- | :---------------------------------------------------------- | | `Renderer` | Svelte component that parses and renders OpenUI Lang output | **`RendererProps`:** -| Prop | Type | Description | -| :--- | :--- | :--- | -| `response` | `string \| null` | Raw OpenUI Lang text from the model | -| `library` | `Library` | Component library from `createLibrary()` | -| `isStreaming` | `boolean` | Whether the model is still streaming (disables form interactions) | -| `onAction` | `(event: ActionEvent) => void` | Callback when a component triggers an action | -| `onStateUpdate` | `(state: Record) => void` | Callback when form field values change | -| `initialState` | `Record` | Initial form state for hydration | -| `onParseResult` | `(result: ParseResult \| null) => void` | Callback when the parse result changes | +| Prop | Type | Description | +| :-------------- | :-------------------------------------- | :---------------------------------------------------------------- | +| `response` | `string \| null` | Raw OpenUI Lang text from the model | +| `library` | `Library` | Component library from `createLibrary()` | +| `isStreaming` | `boolean` | Whether the model is still streaming (disables form interactions) | +| `onAction` | `(event: ActionEvent) => void` | Callback when a component triggers an action | +| `onStateUpdate` | `(state: Record) => void` | Callback when form field values change | +| `initialState` | `Record` | Initial form state for hydration | +| `onParseResult` | `(result: ParseResult \| null) => void` | Callback when the parse result changes | + +### Children Rendering + +Svelte components receive `renderNode` as a **snippet prop** (not via context). Use it to render child element nodes: + +```svelte + + +
+ {#if props.children} + {@render renderNode(props.children)} + {/if} +
+``` ### Parser (Server-Side) -| Export | Description | -| :--- | :--- | -| `createParser(library)` | Create a one-shot parser for complete OpenUI Lang text | -| `createStreamingParser(library)` | Create an incremental parser for streaming input | +| Export | Description | +| :------------------------------ | :----------------------------------------------------- | +| `createParser(schema)` | Create a one-shot parser for complete OpenUI Lang text | +| `createStreamingParser(schema)` | Create an incremental parser for streaming input | + +The streaming parser exposes two methods: + +| Method | Description | +| :------------ | :---------------------------------------------------- | +| `push(chunk)` | Feed the next chunk; returns the latest `ParseResult` | +| `getResult()` | Get the latest result without consuming new data | + +After the stream ends, check `meta.unresolved` for any identifiers that were referenced but never defined. During streaming these are expected (forward refs) and are not treated as errors. + +#### Errors + +`ParseResult.meta.errors` contains structured `OpenUIError` objects. Each error has a `type` discriminant (currently always `"validation"`) and a `code` for consumer-side filtering: + +| Code | Meaning | +| :------------------ | :-------------------------------------------------- | +| `missing-required` | Required prop absent with no default | +| `null-required` | Required prop explicitly null with no default | +| `unknown-component` | Component name not found in the library schema | +| `excess-args` | More positional args passed than the schema defines | + +Errors do not affect rendering — the parser stays permissive and renders what it can: + +```ts +const result = parser.parse(output); +const critical = result.meta.errors.filter((e) => e.code === "unknown-component"); +``` ### Context Getters Use these inside component renderers to interact with the rendering context: -| Function | Description | -| :--- | :--- | -| `getIsStreaming()` | Whether the model is still streaming | -| `getTriggerAction()` | Trigger an action event | -| `getGetFieldValue()` | Get a form field's current value | -| `getSetFieldValue()` | Set a form field's value | -| `useSetDefaultValue()` | Set a field's default value | -| `getFormName()` | Get the current form's name | - -> **Note:** Svelte components receive `renderNode` as a snippet prop instead of via context. This avoids stale-closure issues and is idiomatic Svelte 5. +| Function | Returns | Description | +| :------------------------- | :-------------------- | :----------------------------------------------------------------------------- | +| `getOpenUIContext()` | `OpenUIContextValue` | Access the full context object (library, streaming state, field accessors) | +| `getIsStreaming()` | `() => boolean` | Returns a getter for the streaming state — call it reactively: `isStreaming()` | +| `getTriggerAction()` | `Function` | Trigger an action event | +| `getGetFieldValue()` | `Function` | Get a form field's current value | +| `getSetFieldValue()` | `Function` | Set a form field's value | +| `getFormName()` | `string \| undefined` | Get the current form's name | +| `useSetDefaultValue(opts)` | `void` | Set a field's default value once streaming completes | ### Form Validation -| Export | Description | -| :--- | :--- | -| `getFormValidation()` | Access form validation state | -| `createFormValidation()` | Create a form validation context | -| `validate(value, rules)` | Run validation rules against a value | -| `builtInValidators` | Built-in validators (required, email, min, max, etc.) | +| Export | Description | +| :--------------------------- | :---------------------------------------------------- | +| `getFormValidation()` | Access form validation state | +| `createFormValidation()` | Create a form validation context | +| `setFormValidationContext()` | Provide validation context to child components | +| `validate(value, rules)` | Run validation rules against a value | +| `builtInValidators` | Built-in validators (required, email, min, max, etc.) | +| `parseRules(rules)` | Parse a rules config object into `ParsedRule[]` | ### Types ```ts import type { + // Component definition Library, LibraryDefinition, DefinedComponent, ComponentRenderer, ComponentRenderProps, ComponentGroup, + SubComponentOf, PromptOptions, + + // Rendering RendererProps, + OpenUIContextValue, + ActionConfig, + + // Parser & core ActionEvent, ElementNode, ParseResult, LibraryJSONSchema, + + // Validation + FormValidationContextValue, + ParsedRule, + ValidatorFn, } from "@openuidev/svelte-lang"; ``` @@ -176,10 +236,19 @@ Libraries can also produce a JSON Schema representation of their components: ```ts const schema = library.toJSONSchema(); -// schema["$defs"]["Card"] → { properties: {...}, required: [...] } -// schema["$defs"]["Greeting"] → { properties: {...}, required: [...] } +// schema.$defs["Card"] → { properties: {...}, required: [...] } +// schema.$defs["Greeting"] → { properties: {...}, required: [...] } ``` +## Differences from React + +| Concern | `react-lang` | `svelte-lang` | +| :----------------- | :----------------------------------------------- | :----------------------------------------------- | +| Children rendering | `renderNode` function prop returning `ReactNode` | `renderNode` **snippet** (`Snippet<[unknown]>`) | +| Context access | Hooks (`useIsStreaming()`, etc.) | Getters (`getIsStreaming()`, etc.) | +| Error boundaries | Class-based, preserves last valid render | `svelte:boundary` with auto-retry on prop change | +| Reactivity | Hooks re-run on every render | Runes (`$state`, `$derived`, `$effect`) | + ## Documentation Full documentation, guides, and the language specification are available at **[openui.com](https://openui.com)**. diff --git a/packages/svelte-lang/src/lib/RenderNode.svelte b/packages/svelte-lang/src/lib/RenderNode.svelte index 18dcceab7..5517c2026 100644 --- a/packages/svelte-lang/src/lib/RenderNode.svelte +++ b/packages/svelte-lang/src/lib/RenderNode.svelte @@ -1,48 +1,66 @@ {#if node && Comp} - - - {#snippet failed()} - - {/snippet} - + + + + {#snippet failed()}{/snippet} + {/if} diff --git a/packages/svelte-lang/src/lib/context.svelte.ts b/packages/svelte-lang/src/lib/context.svelte.ts index a69e41d1e..858b7a4f9 100644 --- a/packages/svelte-lang/src/lib/context.svelte.ts +++ b/packages/svelte-lang/src/lib/context.svelte.ts @@ -89,11 +89,12 @@ export function getTriggerAction() { } /** - * Whether the LLM is currently streaming content. - * Returns a getter — use as `getIsStreaming()` for reactive reads. + * Returns a getter for the streaming state. + * Use as: `const isStreaming = getIsStreaming(); ... disabled={isStreaming()}` */ -export function getIsStreaming(): boolean { - return getOpenUIContext().isStreaming; +export function getIsStreaming(): () => boolean { + const ctx = getOpenUIContext(); + return () => ctx.isStreaming; } /** @@ -123,28 +124,30 @@ export function getFormName(): string | undefined { /** * Persists a component's default/initial value into form state once streaming * finishes — but only if the user hasn't already set a value. + * + * Reads the current field value directly from form state inside the effect + * so that Svelte tracks it as a reactive dependency (unlike a snapshot + * parameter that would be captured once at call time). */ export function useSetDefaultValue({ formName, componentType, name, - existingValue, defaultValue, shouldTriggerSaveCallback = false, }: { formName?: string; componentType: string; name: string; - existingValue: any; defaultValue: any; shouldTriggerSaveCallback?: boolean; }): void { - const setFieldValue = getSetFieldValue(); const ctx = getOpenUIContext(); $effect(() => { - if (!ctx.isStreaming && existingValue === undefined && defaultValue !== undefined) { - setFieldValue(formName, componentType, name, defaultValue, shouldTriggerSaveCallback); + const existing = ctx.getFieldValue(formName, name); + if (!ctx.isStreaming && existing === undefined && defaultValue !== undefined) { + ctx.setFieldValue(formName, componentType, name, defaultValue, shouldTriggerSaveCallback); } }); } From ce800489dbe67a383c9f7a1cbf159df0880d093b Mon Sep 17 00:00:00 2001 From: abhithesys Date: Tue, 24 Mar 2026 23:01:31 +0530 Subject: [PATCH 09/12] create svelte-chat example using openai and ai-sdk --- examples/svelte-chat/package.json | 1 + .../src/lib/components/Chart.svelte | 128 ++++++++ examples/svelte-chat/src/lib/library.ts | 69 ++++- examples/svelte-chat/src/routes/+page.svelte | 281 ++++-------------- .../src/routes/AssistantMessage.svelte | 71 +++++ .../svelte-chat/src/routes/ChatHeader.svelte | 11 + .../svelte-chat/src/routes/ChatInput.svelte | 55 ++++ .../src/routes/LoadingIndicator.svelte | 19 ++ .../svelte-chat/src/routes/UserMessage.svelte | 19 ++ .../src/routes/WelcomeScreen.svelte | 29 ++ .../src/routes/api/chat/+server.ts | 7 +- examples/svelte-chat/vite.config.ts | 5 +- pnpm-lock.yaml | 16 + 13 files changed, 485 insertions(+), 226 deletions(-) create mode 100644 examples/svelte-chat/src/lib/components/Chart.svelte create mode 100644 examples/svelte-chat/src/routes/AssistantMessage.svelte create mode 100644 examples/svelte-chat/src/routes/ChatHeader.svelte create mode 100644 examples/svelte-chat/src/routes/ChatInput.svelte create mode 100644 examples/svelte-chat/src/routes/LoadingIndicator.svelte create mode 100644 examples/svelte-chat/src/routes/UserMessage.svelte create mode 100644 examples/svelte-chat/src/routes/WelcomeScreen.svelte diff --git a/examples/svelte-chat/package.json b/examples/svelte-chat/package.json index b2a5d92fa..36266ef87 100644 --- a/examples/svelte-chat/package.json +++ b/examples/svelte-chat/package.json @@ -12,6 +12,7 @@ "@ai-sdk/svelte": "^3.0.0", "@openuidev/svelte-lang": "workspace:*", "ai": "^6.0.116", + "chart.js": "^4.5.1", "zod": "^4.3.6" }, "devDependencies": { diff --git a/examples/svelte-chat/src/lib/components/Chart.svelte b/examples/svelte-chat/src/lib/components/Chart.svelte new file mode 100644 index 000000000..302b27dc5 --- /dev/null +++ b/examples/svelte-chat/src/lib/components/Chart.svelte @@ -0,0 +1,128 @@ + + +
+ {#if props.title} +

{props.title}

+ {/if} +
+ +
+
diff --git a/examples/svelte-chat/src/lib/library.ts b/examples/svelte-chat/src/lib/library.ts index 173e4a18f..8732f1010 100644 --- a/examples/svelte-chat/src/lib/library.ts +++ b/examples/svelte-chat/src/lib/library.ts @@ -1,7 +1,8 @@ -import { createLibrary, defineComponent } from "@openuidev/svelte-lang"; +import { createLibrary, defineComponent, type PromptOptions } from "@openuidev/svelte-lang"; import { z } from "zod"; import Button from "./components/Button.svelte"; import Card from "./components/Card.svelte"; +import Chart from "./components/Chart.svelte"; import Stack from "./components/Stack.svelte"; import TextContent from "./components/TextContent.svelte"; @@ -18,15 +19,30 @@ const ButtonDef = defineComponent({ label: z.string(), action: z.string().optional(), }), - description: "A clickable button that triggers an action", + description: + "A clickable button. The label is shown to the user and used as the follow-up message.", component: Button, }); +const ChartDef = defineComponent({ + name: "Chart", + props: z.object({ + title: z.string(), + type: z.enum(["bar", "line", "pie", "doughnut"]), + labels: z.array(z.string()), + values: z.array(z.number()), + datasetLabel: z.string().optional(), + }), + description: + "Renders a chart. Use bar for comparisons, line for trends, pie/doughnut for proportions.", + component: Chart, +}); + const CardDef = defineComponent({ name: "Card", props: z.object({ title: z.string(), - children: z.array(z.union([TextContentDef.ref, ButtonDef.ref])), + children: z.array(z.union([TextContentDef.ref, ButtonDef.ref, ChartDef.ref])), }), description: "A card container with a title and child components", component: Card, @@ -35,13 +51,54 @@ const CardDef = defineComponent({ const StackDef = defineComponent({ name: "Stack", props: z.object({ - children: z.array(z.union([CardDef.ref, TextContentDef.ref, ButtonDef.ref])), + children: z.array(z.union([CardDef.ref, TextContentDef.ref, ButtonDef.ref, ChartDef.ref])), }), - description: "Vertical layout container", + description: "Vertical layout container. Use as the root.", component: Stack, }); export const library = createLibrary({ - components: [TextContentDef, ButtonDef, CardDef, StackDef], + components: [TextContentDef, ButtonDef, ChartDef, CardDef, StackDef], root: "Stack", }); + +export const promptOptions: PromptOptions = { + additionalRules: [ + "Always use Stack as the root component.", + "Group related content in Card components with descriptive titles.", + "Use TextContent for all text output. You can use markdown within the text string.", + "Use Button for suggested follow-up actions the user might want to take.", + "For multi-section responses, use multiple Card components inside the root Stack.", + "Prefer using references for readability and better streaming performance.", + "Keep TextContent strings focused — use multiple TextContent components for different paragraphs or points.", + "Never nest Stack inside Stack directly.", + "Use Chart for data visualization. Choose bar for comparisons, line for trends, pie/doughnut for proportions.", + "Chart labels and values arrays must have the same length.", + ], + examples: [ + `User: What is Svelte? + +t1 = TextContent("Svelte is a modern JavaScript framework that shifts work from the browser to a compile step. Unlike React or Vue, Svelte compiles your components into efficient imperative code that directly manipulates the DOM.") +t2 = TextContent("**No virtual DOM** — Svelte updates the DOM surgically when state changes, resulting in excellent runtime performance.") +t3 = TextContent("**Less boilerplate** — Svelte's syntax is concise and intuitive, letting you write less code to achieve the same results.") +t4 = TextContent("**Built-in reactivity** — Simple variable assignments trigger UI updates. No hooks or special APIs needed.") +intro = Card("What is Svelte?", [t1]) +features = Card("Key Features", [t2, t3, t4]) +cta = Button("Tell me about Svelte 5") +root = Stack([intro, features, cta])`, + `User: What's the weather like? + +t1 = TextContent("I can look up the current weather for any city. Just tell me which location you're interested in!") +card = Card("Weather Lookup", [t1]) +b1 = Button("Weather in New York") +b2 = Button("Weather in Tokyo") +root = Stack([card, b1, b2])`, + `User: Show me the top 5 programming languages by popularity + +root = Stack([card, cta]) +chart = Chart("Programming Language Popularity", "bar", ["Python", "JavaScript", "Java", "C++", "TypeScript"], [30, 25, 18, 12, 10], "% Market Share") +t1 = TextContent("Python leads with 30% market share, driven by AI/ML adoption. JavaScript remains dominant for web development at 25%.") +card = Card("Language Trends", [chart, t1]) +cta = Button("Compare Python vs JavaScript")`, + ], +}; diff --git a/examples/svelte-chat/src/routes/+page.svelte b/examples/svelte-chat/src/routes/+page.svelte index 245a048a9..a8af7d126 100644 --- a/examples/svelte-chat/src/routes/+page.svelte +++ b/examples/svelte-chat/src/routes/+page.svelte @@ -1,220 +1,71 @@
-
-

OpenUI Svelte Chat

-

- Powered by @openuidev/svelte-lang & Vercel AI SDK -

-
- -
- {#if chat.messages.length === 0} -
-
-

- Welcome to OpenUI Chat -

-

- Ask anything — responses are rendered as structured UI components. -

-
-
- {#each starters as starter} - - {/each} -
-
- {:else} -
- {#each chat.messages as message, i} - {#if message.role === "user"} -
-
- {#each message.parts as part} - {#if part.type === "text"} -

{part.text}

- {/if} - {/each} -
-
- {:else if message.role === "assistant"} - {@const textContent = getTextContent(message.parts)} - {@const toolParts = message.parts.filter(isToolPart)} - {@const isLast = i === chat.messages.length - 1} -
-
- AI -
-
- {#each toolParts as tp} - {@const state = (tp as any).state} - {@const done = state === "output-available"} -
- {#if done} - - - - {:else} -
- {/if} - {getToolName(tp)} -
- {/each} - {#if textContent} - - {/if} -
-
- {/if} - {/each} - - {#if isLoading && (chat.messages.length === 0 || chat.messages[chat.messages.length - 1]?.role === "user")} -
-
- AI -
-
-
-
-
-
-
- {/if} - -
-
- {/if} -
- -
-
- - {#if isLoading} - - {:else} - - {/if} -
-
+ + +
+ {#if chat.messages.length === 0} + + {:else} +
+ {#each chat.messages as message, i} + {#if message.role === "user"} + + {:else if message.role === "assistant"} + + {/if} + {/each} + + {#if isLoading && (chat.messages.length === 0 || chat.messages[chat.messages.length - 1]?.role === "user")} + + {/if} + +
+
+ {/if} +
+ + chat.stop()} />
diff --git a/examples/svelte-chat/src/routes/AssistantMessage.svelte b/examples/svelte-chat/src/routes/AssistantMessage.svelte new file mode 100644 index 000000000..57a8b068f --- /dev/null +++ b/examples/svelte-chat/src/routes/AssistantMessage.svelte @@ -0,0 +1,71 @@ + + +
+
+ AI +
+
+ {#each toolParts as tp} + {@const state = (tp as any).state} + {@const done = state === "output-available"} +
+ {#if done} + + + + {:else} +
+ {/if} + {getToolName(tp)} +
+ {/each} + {#if textContent} + + {/if} +
+
diff --git a/examples/svelte-chat/src/routes/ChatHeader.svelte b/examples/svelte-chat/src/routes/ChatHeader.svelte new file mode 100644 index 000000000..86707b508 --- /dev/null +++ b/examples/svelte-chat/src/routes/ChatHeader.svelte @@ -0,0 +1,11 @@ + + +
+

OpenUI Svelte Chat

+

+ Powered by @openuidev/svelte-lang & Vercel AI SDK +

+
diff --git a/examples/svelte-chat/src/routes/ChatInput.svelte b/examples/svelte-chat/src/routes/ChatInput.svelte new file mode 100644 index 000000000..2e93f3b4b --- /dev/null +++ b/examples/svelte-chat/src/routes/ChatInput.svelte @@ -0,0 +1,55 @@ + + +
+
+ + {#if isLoading} + + {:else} + + {/if} +
+
diff --git a/examples/svelte-chat/src/routes/LoadingIndicator.svelte b/examples/svelte-chat/src/routes/LoadingIndicator.svelte new file mode 100644 index 000000000..07d1ab66c --- /dev/null +++ b/examples/svelte-chat/src/routes/LoadingIndicator.svelte @@ -0,0 +1,19 @@ + + +
+
+ AI +
+
+
+
+
+
+
diff --git a/examples/svelte-chat/src/routes/UserMessage.svelte b/examples/svelte-chat/src/routes/UserMessage.svelte new file mode 100644 index 000000000..bb999345b --- /dev/null +++ b/examples/svelte-chat/src/routes/UserMessage.svelte @@ -0,0 +1,19 @@ + + +
+
+ {#each parts as part} + {#if part.type === "text"} +

{part.text}

+ {/if} + {/each} +
+
diff --git a/examples/svelte-chat/src/routes/WelcomeScreen.svelte b/examples/svelte-chat/src/routes/WelcomeScreen.svelte new file mode 100644 index 000000000..c3bb760a6 --- /dev/null +++ b/examples/svelte-chat/src/routes/WelcomeScreen.svelte @@ -0,0 +1,29 @@ + + +
+
+

+ Welcome to OpenUI Chat +

+

+ Ask anything — responses are rendered as structured UI components. +

+
+
+ {#each starters as starter} + + {/each} +
+
diff --git a/examples/svelte-chat/src/routes/api/chat/+server.ts b/examples/svelte-chat/src/routes/api/chat/+server.ts index 3a89fb2bb..da3dd64f9 100644 --- a/examples/svelte-chat/src/routes/api/chat/+server.ts +++ b/examples/svelte-chat/src/routes/api/chat/+server.ts @@ -1,19 +1,18 @@ import { OPENAI_API_KEY } from "$env/static/private"; +import { library, promptOptions } from "$lib/library"; import { tools } from "$lib/tools"; import { createOpenAI } from "@ai-sdk/openai"; import { convertToModelMessages, stepCountIs, streamText } from "ai"; -import { readFileSync } from "fs"; -import { join } from "path"; const openai = createOpenAI({ apiKey: OPENAI_API_KEY }); -const systemPrompt = readFileSync(join(process.cwd(), "src/generated/system-prompt.txt"), "utf-8"); +const systemPrompt = library.prompt(promptOptions); export async function POST({ request }: { request: Request }) { const { messages } = await request.json(); const result = streamText({ - model: openai("gpt-4o"), + model: openai("gpt-5.4"), system: systemPrompt, messages: await convertToModelMessages(messages), tools, diff --git a/examples/svelte-chat/vite.config.ts b/examples/svelte-chat/vite.config.ts index b0741a1f2..a23082f45 100644 --- a/examples/svelte-chat/vite.config.ts +++ b/examples/svelte-chat/vite.config.ts @@ -3,5 +3,8 @@ import tailwindcss from "@tailwindcss/vite"; import { defineConfig } from "vite"; export default defineConfig({ - plugins: [tailwindcss(), sveltekit()], + plugins: [tailwindcss(), sveltekit()], + ssr: { + noExternal: ["@openuidev/svelte-lang"], + }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a74aae76c..f5ec633c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -435,6 +435,9 @@ importers: ai: specifier: ^6.0.116 version: 6.0.116(zod@4.3.6) + chart.js: + specifier: ^4.5.1 + version: 4.5.1 zod: specifier: ^4.3.6 version: 4.3.6 @@ -2597,6 +2600,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@kurkle/color@0.3.4': + resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} + '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} @@ -5597,6 +5603,10 @@ packages: chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + chart.js@4.5.1: + resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==} + engines: {pnpm: '>=8'} + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -12141,6 +12151,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@kurkle/color@0.3.4': {} + '@mdx-js/mdx@3.1.1': dependencies: '@types/estree': 1.0.8 @@ -15962,6 +15974,10 @@ snapshots: chardet@2.1.1: {} + chart.js@4.5.1: + dependencies: + '@kurkle/color': 0.3.4 + check-error@2.1.1: {} chokidar@3.6.0: From f2f2141929640ccf633218f238b30ef07783c5b7 Mon Sep 17 00:00:00 2001 From: abhithesys Date: Tue, 24 Mar 2026 23:07:00 +0530 Subject: [PATCH 10/12] remove handwritten system prompt --- .../src/generated/system-prompt.txt | 54 ------------------- 1 file changed, 54 deletions(-) delete mode 100644 examples/svelte-chat/src/generated/system-prompt.txt diff --git a/examples/svelte-chat/src/generated/system-prompt.txt b/examples/svelte-chat/src/generated/system-prompt.txt deleted file mode 100644 index 87db17ce5..000000000 --- a/examples/svelte-chat/src/generated/system-prompt.txt +++ /dev/null @@ -1,54 +0,0 @@ -You are an AI assistant that responds using openui-lang, a declarative UI language. Your ENTIRE response must be valid openui-lang code — no markdown, no explanations, just openui-lang. - -## Syntax Rules - -1. Each statement is on its own line: `identifier = Expression` -2. `root` is the entry point — every program must define `root = Stack(...)` -3. Expressions are: strings ("..."), numbers, booleans (true/false), arrays ([...]), objects ({...}), or component calls TypeName(arg1, arg2, ...) -4. Use references for readability: define `name = ...` on one line, then use `name` later -5. EVERY variable (except root) MUST be referenced by at least one other variable. Unreferenced variables are silently dropped and will NOT render. Always include defined variables in their parent's children/items array. -6. Arguments are POSITIONAL (order matters, not names) -7. Optional arguments can be omitted from the end -8. No operators, no logic, no variables — only declarations -9. Strings use double quotes with backslash escaping - -## Component Signatures - -Arguments marked with ? are optional. - -Stack(children: array) — Vertical layout container. Use as the root. -Card(title: string, children: array) — A card container with a title and child components. -TextContent(text: string) — Displays a block of text. Supports markdown formatting within the string. -Button(label: string, action?: string) — A clickable button. The label is shown to the user and used as the follow-up message. - -## Rules - -- Always use Stack as the root component. -- Group related content in Card components with descriptive titles. -- Use TextContent for all text output. You can use markdown within the text string. -- Use Button for suggested follow-up actions the user might want to take. -- For multi-section responses, use multiple Card components inside the root Stack. -- Prefer using references for readability and better streaming performance. -- Keep TextContent strings focused — use multiple TextContent components for different paragraphs or points. -- Never nest Stack inside Stack directly. - -## Examples - -User: What is Svelte? - -t1 = TextContent("Svelte is a modern JavaScript framework that shifts work from the browser to a compile step. Unlike React or Vue, Svelte compiles your components into efficient imperative code that directly manipulates the DOM.") -t2 = TextContent("**No virtual DOM** — Svelte updates the DOM surgically when state changes, resulting in excellent runtime performance.") -t3 = TextContent("**Less boilerplate** — Svelte's syntax is concise and intuitive, letting you write less code to achieve the same results.") -t4 = TextContent("**Built-in reactivity** — Simple variable assignments trigger UI updates. No hooks or special APIs needed.") -intro = Card("What is Svelte?", [t1]) -features = Card("Key Features", [t2, t3, t4]) -cta = Button("Tell me about Svelte 5") -root = Stack([intro, features, cta]) - -User: What's the weather like? - -t1 = TextContent("I can look up the current weather for any city. Just tell me which location you're interested in!") -card = Card("Weather Lookup", [t1]) -b1 = Button("Weather in New York") -b2 = Button("Weather in Tokyo") -root = Stack([card, b1, b2]) From ea42d4edfe950dc372b06adcde60c0e317ee73f8 Mon Sep 17 00:00:00 2001 From: abhithesys Date: Wed, 25 Mar 2026 13:22:36 +0530 Subject: [PATCH 11/12] fmt and update env var in svelte-chat --- examples/svelte-chat/src/routes/api/chat/+server.ts | 4 ++-- packages/react-lang/src/Renderer.tsx | 2 +- packages/react-lang/src/hooks/useFormValidation.ts | 2 +- packages/react-lang/src/hooks/useOpenUIState.ts | 7 ++++++- packages/react-lang/src/index.ts | 7 ++++++- packages/react-lang/src/library.ts | 9 +++++---- 6 files changed, 21 insertions(+), 10 deletions(-) diff --git a/examples/svelte-chat/src/routes/api/chat/+server.ts b/examples/svelte-chat/src/routes/api/chat/+server.ts index da3dd64f9..6c0dc1620 100644 --- a/examples/svelte-chat/src/routes/api/chat/+server.ts +++ b/examples/svelte-chat/src/routes/api/chat/+server.ts @@ -1,10 +1,10 @@ -import { OPENAI_API_KEY } from "$env/static/private"; +import { env } from "$env/dynamic/private"; import { library, promptOptions } from "$lib/library"; import { tools } from "$lib/tools"; import { createOpenAI } from "@ai-sdk/openai"; import { convertToModelMessages, stepCountIs, streamText } from "ai"; -const openai = createOpenAI({ apiKey: OPENAI_API_KEY }); +const openai = createOpenAI({ apiKey: env.OPENAI_API_KEY ?? "" }); const systemPrompt = library.prompt(promptOptions); diff --git a/packages/react-lang/src/Renderer.tsx b/packages/react-lang/src/Renderer.tsx index 4be5a2b52..fdd8c6eb9 100644 --- a/packages/react-lang/src/Renderer.tsx +++ b/packages/react-lang/src/Renderer.tsx @@ -1,5 +1,5 @@ -import React, { Component, Fragment, useEffect } from "react"; import type { ActionEvent, ElementNode, ParseResult } from "@openuidev/lang-core"; +import React, { Component, Fragment, useEffect } from "react"; import { OpenUIContext, useOpenUI, useRenderNode } from "./context"; import { useOpenUIState } from "./hooks/useOpenUIState"; import type { ComponentRenderer, Library } from "./library"; diff --git a/packages/react-lang/src/hooks/useFormValidation.ts b/packages/react-lang/src/hooks/useFormValidation.ts index adcad6bd3..073448b36 100644 --- a/packages/react-lang/src/hooks/useFormValidation.ts +++ b/packages/react-lang/src/hooks/useFormValidation.ts @@ -1,5 +1,5 @@ -import { createContext, useCallback, useContext, useMemo, useRef, useState } from "react"; import { validate, type ParsedRule } from "@openuidev/lang-core"; +import { createContext, useCallback, useContext, useMemo, useRef, useState } from "react"; export interface FormValidationContextValue { errors: Record; diff --git a/packages/react-lang/src/hooks/useOpenUIState.ts b/packages/react-lang/src/hooks/useOpenUIState.ts index 09802a440..f10875088 100644 --- a/packages/react-lang/src/hooks/useOpenUIState.ts +++ b/packages/react-lang/src/hooks/useOpenUIState.ts @@ -1,6 +1,11 @@ +import { + BuiltinActionType, + createParser, + type ActionEvent, + type ParseResult, +} from "@openuidev/lang-core"; import type React from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { BuiltinActionType, createParser, type ActionEvent, type ParseResult } from "@openuidev/lang-core"; import type { OpenUIContextValue } from "../context"; import type { Library } from "../library"; diff --git a/packages/react-lang/src/index.ts b/packages/react-lang/src/index.ts index 978e81a2c..9ef8bdce5 100644 --- a/packages/react-lang/src/index.ts +++ b/packages/react-lang/src/index.ts @@ -42,5 +42,10 @@ export { } from "./hooks/useFormValidation"; export type { FormValidationContextValue } from "./hooks/useFormValidation"; -export { builtInValidators, parseRules, parseStructuredRules, validate } from "@openuidev/lang-core"; +export { + builtInValidators, + parseRules, + parseStructuredRules, + validate, +} from "@openuidev/lang-core"; export type { ParsedRule, ValidatorFn } from "@openuidev/lang-core"; diff --git a/packages/react-lang/src/library.ts b/packages/react-lang/src/library.ts index bedda9ff2..e0cdc8109 100644 --- a/packages/react-lang/src/library.ts +++ b/packages/react-lang/src/library.ts @@ -1,20 +1,21 @@ -import type { ReactNode } from "react"; -import { z } from "zod"; import { createLibrary as coreCreateLibrary, defineComponent as coreDefineComponent, - type ComponentRenderProps as CoreRenderProps, type DefinedComponent as CoreDefinedComponent, type Library as CoreLibrary, type LibraryDefinition as CoreLibraryDefinition, + type ComponentRenderProps as CoreRenderProps, } from "@openuidev/lang-core"; +import type { ReactNode } from "react"; +import { z } from "zod"; // Re-export framework-agnostic types unchanged export type { ComponentGroup, PromptOptions, SubComponentOf } from "@openuidev/lang-core"; // ─── React-specific types ─────────────────────────────────────────────────── -export interface ComponentRenderProps

> extends CoreRenderProps {} +export interface ComponentRenderProps

> + extends CoreRenderProps {} export type ComponentRenderer

> = React.FC>; From 1bf40496467077c698bf48a5d8bc4fbeb563b45f Mon Sep 17 00:00:00 2001 From: abhithesys Date: Wed, 25 Mar 2026 13:58:48 +0530 Subject: [PATCH 12/12] lint fix --- packages/svelte-lang/tsconfig.test.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 packages/svelte-lang/tsconfig.test.json diff --git a/packages/svelte-lang/tsconfig.test.json b/packages/svelte-lang/tsconfig.test.json new file mode 100644 index 000000000..3567b8565 --- /dev/null +++ b/packages/svelte-lang/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules"] +}