From e25ea31d2c2780bf0b0b51837876fcb16bfde76c Mon Sep 17 00:00:00 2001 From: Omkar Bhor Date: Fri, 5 Jun 2026 16:10:14 +0530 Subject: [PATCH 1/3] Add unit tests for the React OpenUI Lang renderer Adds a Vitest test suite for the React Renderer component, mirroring the existing tests in the Vue and Svelte packages. Covers null/empty response handling, onParseResult callbacks, parsing correctness, and isStreaming prop defaults. Closes #549 --- packages/react-lang/package.json | 2 + .../src/__tests__/Renderer.test.tsx | 85 +++++ packages/react-lang/vitest.config.ts | 8 + pnpm-lock.yaml | 325 +++++++++++++++++- 4 files changed, 402 insertions(+), 18 deletions(-) create mode 100644 packages/react-lang/src/__tests__/Renderer.test.tsx create mode 100644 packages/react-lang/vitest.config.ts diff --git a/packages/react-lang/package.json b/packages/react-lang/package.json index 72b6693a3..dd20f8177 100644 --- a/packages/react-lang/package.json +++ b/packages/react-lang/package.json @@ -80,7 +80,9 @@ }, "devDependencies": { "@modelcontextprotocol/sdk": "^1.27.1", + "@testing-library/react": "^16.3.2", "@types/react": "^19.0.0", + "jsdom": "^29.1.1", "vitest": "^4.0.18" } } diff --git a/packages/react-lang/src/__tests__/Renderer.test.tsx b/packages/react-lang/src/__tests__/Renderer.test.tsx new file mode 100644 index 000000000..98045c0ed --- /dev/null +++ b/packages/react-lang/src/__tests__/Renderer.test.tsx @@ -0,0 +1,85 @@ +import { render } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { z } from "zod/v4"; +import { createLibrary, defineComponent } from "../library"; +import { Renderer } from "../Renderer"; + +// 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(); + + expect(container).toBeDefined(); + }); + + it("renders without errors when response is empty string", () => { + const { container } = render(); + + expect(container).toBeDefined(); + }); + + it("calls onParseResult with null when response is null", () => { + const onParseResult = vi.fn(); + + render(); + + expect(onParseResult).toHaveBeenCalledWith(null); + }); + + it("calls onParseResult with a ParseResult when given valid openui-lang", async () => { + const onParseResult = vi.fn(); + + render( + , + ); + + 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( + , + ); + + 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(); + expect(container).toBeDefined(); + }); + + it("accepts isStreaming prop without errors", () => { + const { container } = render( + , + ); + expect(container).toBeDefined(); + }); +}); diff --git a/packages/react-lang/vitest.config.ts b/packages/react-lang/vitest.config.ts new file mode 100644 index 000000000..64153fc54 --- /dev/null +++ b/packages/react-lang/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["src/**/*.test.ts", "src/**/*.test.tsx"], + environment: "jsdom", + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 759229d5a..2dad07dbd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -320,7 +320,7 @@ importers: version: 0.0.45 '@ag-ui/mastra': specifier: ^1.0.1 - version: 1.0.1(mo2d2k6urt4bfbrhfsfscm4ram) + version: 1.0.1(f00fa45f575c251283cb17e81c0eba82) '@mastra/core': specifier: 1.15.0 version: 1.15.0(@cfworker/json-schema@4.1.1)(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@1.0.0)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@1.0.0)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@4.3.6))(@types/json-schema@7.0.15)(openapi-types@12.1.3)(zod@4.3.6) @@ -1165,7 +1165,7 @@ importers: version: 1.29.0(@cfworker/json-schema@4.1.1)(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@26.1.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.3) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@29.1.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.3) packages/openui-cli: dependencies: @@ -1231,7 +1231,7 @@ importers: version: 6.22.0(ws@8.20.0)(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@26.1.0)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.3) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@29.1.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.3) packages/react-lang: dependencies: @@ -1248,12 +1248,18 @@ importers: '@modelcontextprotocol/sdk': specifier: ^1.27.1 version: 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@types/react': specifier: ^19.0.0 version: 19.2.14 + jsdom: + specifier: ^29.1.1 + version: 29.1.1 vitest: specifier: ^4.0.18 - version: 4.0.18(@opentelemetry/api@1.9.0)(@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.3) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@29.1.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.3) packages/react-ui: dependencies: @@ -1479,7 +1485,7 @@ importers: version: 5.4.19(@types/node@22.15.32)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0) vitest: specifier: ^4.0.18 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@22.15.32)(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.3) + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@22.15.32)(jiti@2.6.1)(jsdom@29.1.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.3) packages/svelte-lang: dependencies: @@ -1774,6 +1780,21 @@ packages: '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@7.1.1': + resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@babel/code-frame@7.10.4': resolution: {integrity: sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==} @@ -2338,6 +2359,10 @@ packages: '@braintree/sanitize-url@7.1.2': resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + '@bufbuild/protobuf@2.11.0': resolution: {integrity: sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==} @@ -2438,6 +2463,10 @@ packages: 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'} @@ -2445,6 +2474,13 @@ packages: '@csstools/css-parser-algorithms': ^3.0.5 '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@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'} @@ -2452,16 +2488,41 @@ packages: '@csstools/css-parser-algorithms': ^3.0.5 '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-color-parser@4.1.1': + resolution: {integrity: sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@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'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.5': + resolution: {integrity: sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + '@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'} + '@date-fns/tz@1.2.0': resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==} @@ -3295,6 +3356,15 @@ packages: resolution: {integrity: sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@expo/cli@54.0.23': resolution: {integrity: sha512-km0h72SFfQCmVycH/JtPFTVy69w6Lx1cHNDmfLfQqgKFYeeHTjx7LVDP4POHCtNxFP2UeRazrygJhlh4zz498g==} hasBin: true @@ -6883,6 +6953,7 @@ packages: '@rolldown/binding-darwin-arm64@1.0.0-rc.16': resolution: {integrity: sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==} engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] os: [darwin] '@rolldown/binding-darwin-x64@1.0.0-rc.12': @@ -7939,6 +8010,21 @@ packages: resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@testing-library/svelte-core@1.0.0': resolution: {integrity: sha512-VkUePoLV6oOYwSUvX6ShA8KLnJqZiYMIbP2JW2t0GLWLkJxKGvuH5qrrZBV/X7cXFnLGuFQEC7RheYiZOW68KQ==} engines: {node: '>=16'} @@ -8324,6 +8410,7 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@unhead/vue@2.1.13': resolution: {integrity: sha512-HYy0shaHRnLNW9r85gppO8IiGz0ONWVV3zGdlT8CQ0tbTwixznJCIiyqV4BSV1aIF1jJIye0pd1p/k6Eab8Z/A==} @@ -9149,6 +9236,9 @@ packages: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} engines: {node: '>=12.0.0'} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + big-integer@1.6.52: resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} engines: {node: '>=0.6'} @@ -9916,6 +10006,10 @@ packages: 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} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -10251,6 +10345,10 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + env-editor@0.4.2: resolution: {integrity: sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA==} engines: {node: '>=8'} @@ -11365,6 +11463,10 @@ packages: 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} + html-to-text@9.0.5: resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} engines: {node: '>=14'} @@ -11889,6 +11991,15 @@ packages: canvas: optional: true + jsdom@29.1.1: + resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -12267,6 +12378,10 @@ packages: resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} engines: {node: 20 || >=22} + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -13306,6 +13421,9 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + parseley@0.12.1: resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} @@ -14104,6 +14222,7 @@ packages: recharts@2.15.4: resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==} engines: {node: '>=14'} + deprecated: 1.x and 2.x branches are no longer active. Bump to Recharts v3 to receive latest features and bugfixes. See https://github.com/recharts/recharts/wiki/3.0-migration-guide peerDependencies: react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -15050,10 +15169,17 @@ packages: tldts-core@6.1.86: resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + tldts-core@7.4.2: + resolution: {integrity: sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==} + tldts@6.1.86: resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} hasBin: true + tldts@7.4.2: + resolution: {integrity: sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==} + hasBin: true + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -15080,6 +15206,10 @@ packages: resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} engines: {node: '>=16'} + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -15087,6 +15217,10 @@ packages: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -15270,6 +15404,10 @@ packages: resolution: {integrity: sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==} engines: {node: '>=18.17'} + undici@7.27.1: + resolution: {integrity: sha512-UDdpiex+mzigiyrXrGbiUaF4HzTNhKbh2vRNFaTMzcqmLIPrZxaCtwo/1TMSuWoM1Xz3WiTo9KdgI3kRqYzJGg==} + engines: {node: '>=20.18.1'} + unenv@2.0.0-rc.24: resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} @@ -15920,6 +16058,10 @@ packages: 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'} + webpack-sources@3.3.2: resolution: {integrity: sha512-ykKKus8lqlgXX/1WjudpIEjqsafjOTcOJqxnAbMLAu/KCsDCJ6GBtvscewvTkrn24HsnvFwrSCbenFrhtcCsAA==} engines: {node: '>=10.13.0'} @@ -15949,6 +16091,10 @@ packages: 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'} + whatwg-url-without-unicode@8.0.0-3: resolution: {integrity: sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==} engines: {node: '>=10'} @@ -15957,6 +16103,10 @@ packages: 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} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -16294,7 +16444,7 @@ snapshots: - react - react-dom - '@ag-ui/mastra@1.0.1(mo2d2k6urt4bfbrhfsfscm4ram)': + '@ag-ui/mastra@1.0.1(f00fa45f575c251283cb17e81c0eba82)': dependencies: '@ag-ui/client': 0.0.49 '@ag-ui/core': 0.0.45 @@ -16529,6 +16679,26 @@ snapshots: '@csstools/css-tokenizer': 3.0.4 lru-cache: 10.4.3 + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@7.1.1': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + '@babel/code-frame@7.10.4': dependencies: '@babel/highlight': 7.25.9 @@ -17203,6 +17373,10 @@ snapshots: '@braintree/sanitize-url@7.1.2': {} + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + '@bufbuild/protobuf@2.11.0': {} '@cfworker/json-schema@4.1.1': {} @@ -17341,11 +17515,18 @@ snapshots: '@csstools/color-helpers@5.1.0': {} + '@csstools/color-helpers@6.0.2': {} + '@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.2.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 + '@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 @@ -17353,12 +17534,29 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-color-parser@4.1.1(@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 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + '@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 + + '@csstools/css-syntax-patches-for-csstree@1.1.5(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + '@csstools/css-tokenizer@3.0.4': {} + '@csstools/css-tokenizer@4.0.0': {} + '@date-fns/tz@1.2.0': {} '@date-fns/tz@1.4.1': {} @@ -17835,6 +18033,8 @@ snapshots: '@eslint/core': 0.15.0 levn: 0.4.1 + '@exodus/bytes@1.15.1': {} + '@expo/cli@54.0.23(expo@54.0.33(@babel/core@7.29.0)(graphql@16.13.2)(react-native@0.83.2(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(graphql@16.13.2)(react-native@0.83.2(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))': dependencies: '@0no-co/graphql.web': 1.2.0(graphql@16.13.2) @@ -19393,7 +19593,7 @@ snapshots: rc9: 3.0.0 std-env: 3.10.0 - '@nuxt/vite-builder@3.21.2(thirvmyz7u7sotpvl5josuvsem)': + '@nuxt/vite-builder@3.21.2(cda632d3acfa8c47554ee5be1e146aef)': dependencies: '@nuxt/kit': 3.21.2(magicast@0.5.2) '@rollup/plugin-replace': 6.0.3(rollup@4.60.1) @@ -24380,6 +24580,16 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 + '@testing-library/react@16.3.2(@testing-library/dom@10.4.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.27.6 + '@testing-library/dom': 10.4.0 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@testing-library/svelte-core@1.0.0(svelte@5.55.1)': dependencies: svelte: 5.55.1 @@ -25853,6 +26063,10 @@ snapshots: dependencies: open: 8.4.2 + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + big-integer@1.6.52: {} bignumber.js@9.3.1: {} @@ -26693,6 +26907,13 @@ snapshots: whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -26939,6 +27160,8 @@ snapshots: entities@7.0.1: {} + entities@8.0.0: {} + env-editor@0.4.2: {} environment@1.1.0: {} @@ -27235,7 +27458,7 @@ snapshots: '@next/eslint-plugin-next': 16.1.6 eslint: 9.29.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.29.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@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)))(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.29.0(jiti@2.6.1)) @@ -27255,7 +27478,7 @@ snapshots: '@next/eslint-plugin-next': 16.2.3 eslint: 9.29.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.29.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@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)))(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.29.0(jiti@2.6.1)) @@ -27282,7 +27505,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.29.0(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@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)))(eslint@9.29.0(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -27297,14 +27520,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@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)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: '@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) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.29.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@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)))(eslint@9.29.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -27319,7 +27542,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.29.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@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)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -28544,6 +28767,12 @@ snapshots: dependencies: whatwg-encoding: 3.1.1 + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.1 + transitivePeerDependencies: + - '@noble/hashes' + html-to-text@9.0.5: dependencies: '@selderee/plugin-htmlparser2': 0.11.0 @@ -29102,6 +29331,32 @@ snapshots: - supports-color - utf-8-validate + jsdom@29.1.1: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@asamuzakjp/dom-selector': 7.1.1 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.5(css-tree@3.2.1) + '@exodus/bytes': 1.15.1 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.1 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.27.1 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -29413,6 +29668,8 @@ snapshots: lru-cache@11.2.7: {} + lru-cache@11.5.1: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -30785,7 +31042,7 @@ snapshots: '@nuxt/nitro-server': 3.21.2(db0@0.3.4)(ioredis@5.10.1)(magicast@0.5.2)(nuxt@3.21.2(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1)(@parcel/watcher@2.5.1)(@types/node@25.3.2)(@vue/compiler-sfc@3.5.31)(cac@6.7.14)(db0@0.3.4)(eslint@9.29.0(jiti@2.6.1))(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(optionator@0.9.4)(rolldown@1.0.0-rc.12(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-rc.12(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1))(rollup@4.60.1))(rollup@4.60.1)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(typescript@5.9.3)(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.3))(vue-tsc@2.2.12(typescript@5.9.3))(yaml@2.8.3))(rolldown@1.0.0-rc.12(@emnapi/core@1.8.1)(@emnapi/runtime@1.8.1))(typescript@5.9.3) '@nuxt/schema': 3.21.2 '@nuxt/telemetry': 2.7.0(@nuxt/kit@3.21.2(magicast@0.5.2)) - '@nuxt/vite-builder': 3.21.2(thirvmyz7u7sotpvl5josuvsem) + '@nuxt/vite-builder': 3.21.2(cda632d3acfa8c47554ee5be1e146aef) '@unhead/vue': 2.1.13(vue@3.5.31(typescript@5.9.3)) '@vue/shared': 3.5.31 c12: 3.3.3(magicast@0.5.2) @@ -31271,6 +31528,10 @@ snapshots: dependencies: entities: 6.0.1 + parse5@8.0.1: + dependencies: + entities: 8.0.0 + parseley@0.12.1: dependencies: leac: 0.6.0 @@ -33640,10 +33901,16 @@ snapshots: tldts-core@6.1.86: {} + tldts-core@7.4.2: {} + tldts@6.1.86: dependencies: tldts-core: 6.1.86 + tldts@7.4.2: + dependencies: + tldts-core: 7.4.2 + tmpl@1.0.5: {} to-regex-range@5.0.1: @@ -33662,12 +33929,20 @@ snapshots: dependencies: tldts: 6.1.86 + tough-cookie@6.0.1: + dependencies: + tldts: 7.4.2 + tr46@0.0.3: {} tr46@5.1.1: dependencies: punycode: 2.3.1 + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} trim-lines@3.0.1: {} @@ -33862,6 +34137,8 @@ snapshots: undici@6.24.1: {} + undici@7.27.1: {} + unenv@2.0.0-rc.24: dependencies: pathe: 2.0.3 @@ -34422,7 +34699,7 @@ snapshots: - tsx - yaml - vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@22.15.32)(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.3): + vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@22.15.32)(jiti@2.6.1)(jsdom@29.1.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.3): dependencies: '@vitest/expect': 4.0.18 '@vitest/mocker': 4.0.18(vite@6.4.1(@types/node@22.15.32)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.3)) @@ -34447,7 +34724,7 @@ snapshots: optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 22.15.32 - jsdom: 26.1.0 + jsdom: 29.1.1 transitivePeerDependencies: - jiti - less @@ -34461,7 +34738,7 @@ snapshots: - tsx - yaml - vitest@4.0.18(@opentelemetry/api@1.9.0)(@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.3): + vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(jsdom@29.1.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.3): 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.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.3)) @@ -34486,7 +34763,7 @@ snapshots: optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 25.3.2 - jsdom: 26.1.0 + jsdom: 29.1.1 transitivePeerDependencies: - jiti - less @@ -34579,6 +34856,8 @@ snapshots: webidl-conversions@7.0.0: {} + webidl-conversions@8.0.1: {} + webpack-sources@3.3.2: {} webpack-virtual-modules@0.6.2: {} @@ -34622,6 +34901,8 @@ snapshots: whatwg-mimetype@4.0.0: {} + whatwg-mimetype@5.0.0: {} + whatwg-url-without-unicode@8.0.0-3: dependencies: buffer: 5.7.1 @@ -34633,6 +34914,14 @@ snapshots: tr46: 5.1.1 webidl-conversions: 7.0.0 + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 From b3db0defb54c45516d5554c6fb6721bf160b8437 Mon Sep 17 00:00:00 2001 From: Omkar Bhor Date: Fri, 5 Jun 2026 16:17:37 +0530 Subject: [PATCH 2/3] Implement stricter parsing with strict mode Adds a `createStrictParser()` that reports unexpected text as parse-failed errors instead of silently skipping it. Useful for debugging when LLMs generate preamble text alongside valid openui-lang statements. Changes: - Add `tokenText()` utility to reconstruct text from tokens - `split()` now collects skipped line text when a collector is provided - Add `createStrictParser()` and `strict` parameter to `parse()` - Add "parse-failed" to ValidationErrorCode - Add test coverage for strict mode Closes #582 --- .../src/parser/__tests__/parser.test.ts | 58 +++++++++++++++++++ packages/lang-core/src/parser/index.ts | 2 +- packages/lang-core/src/parser/parser.ts | 36 +++++++++++- packages/lang-core/src/parser/statements.ts | 18 ++++-- packages/lang-core/src/parser/tokens.ts | 38 ++++++++++++ packages/lang-core/src/parser/types.ts | 3 +- 6 files changed, 146 insertions(+), 9 deletions(-) diff --git a/packages/lang-core/src/parser/__tests__/parser.test.ts b/packages/lang-core/src/parser/__tests__/parser.test.ts index 2bd339aa3..355261fb0 100644 --- a/packages/lang-core/src/parser/__tests__/parser.test.ts +++ b/packages/lang-core/src/parser/__tests__/parser.test.ts @@ -235,3 +235,61 @@ describe("orphaned statements", () => { expect(result.meta.orphaned).toHaveLength(0); }); }); + +// ── strict mode ──────────────────────────────────────────────────────────────── + +describe("strict mode", () => { + it("ignores non-statement lines by default (non-strict)", () => { + const result = parse( + 'Here is the response:\nroot = Stack([Title("hi")])', + schema, + ); + expect(result.meta.errors).toHaveLength(0); + expect(result.root).not.toBeNull(); + }); + + it("reports non-statement lines as errors in strict mode", () => { + const result = parse( + 'Here is the response:\nroot = Stack([Title("hi")])', + schema, + undefined, + true, + ); + expect(result.meta.errors.length).toBeGreaterThan(0); + expect(result.meta.errors[0].code).toBe("parse-failed"); + expect(result.meta.errors[0].message).toMatch(/unexpected text/i); + }); + + it("reports invalid identifier lines in strict mode", () => { + const result = parse( + 'foo bar\nroot = Stack([Title("hi")])', + schema, + undefined, + true, + ); + expect(result.meta.errors.length).toBeGreaterThan(0); + expect(result.meta.errors[0].code).toBe("parse-failed"); + }); + + it("still parses valid statements correctly in strict mode", () => { + const result = parse( + 'root = Stack([Title("hi")])', + schema, + undefined, + true, + ); + expect(result.meta.errors).toHaveLength(0); + expect(result.root).not.toBeNull(); + }); + + it("reports multiple invalid lines", () => { + const result = parse( + 'some text\nmore noise\nroot = Stack([Title("hi")])', + schema, + undefined, + true, + ); + expect(result.meta.errors).toHaveLength(2); + expect(result.meta.errors.every((e: { code: string }) => e.code === "parse-failed")).toBe(true); + }); +}); diff --git a/packages/lang-core/src/parser/index.ts b/packages/lang-core/src/parser/index.ts index 13c2138bb..4aa97073c 100644 --- a/packages/lang-core/src/parser/index.ts +++ b/packages/lang-core/src/parser/index.ts @@ -13,7 +13,7 @@ export type { ValidationErrorCode, } from "./types"; -export { createParser, createStreamingParser, parse } from "./parser"; +export { createParser, createStrictParser, createStreamingParser, parse } from "./parser"; export type { Parser, StreamParser } from "./parser"; export { enrichErrors } from "./enrich-errors"; diff --git a/packages/lang-core/src/parser/parser.ts b/packages/lang-core/src/parser/parser.ts index b76b6240f..1037a9449 100644 --- a/packages/lang-core/src/parser/parser.ts +++ b/packages/lang-core/src/parser/parser.ts @@ -375,14 +375,16 @@ function preprocess(input: string): string { * * @param input - Full openui-lang source text (may be partial/streaming) * @param cat - Param map for positional-arg → named-prop mapping + * @param strict - When true, report lines that are not valid openui-lang as errors * @returns ParseResult with root ElementNode (or null) and metadata */ -export function parse(input: string, cat: ParamMap, rootName?: string): ParseResult { +export function parse(input: string, cat: ParamMap, rootName?: string, strict?: boolean): ParseResult { const trimmed = preprocess(input); if (!trimmed) return emptyResult(); const { text, wasIncomplete } = autoClose(trimmed); - const stmts = split(tokenize(text)); + const skipped: string[] = []; + const stmts = split(tokenize(text), strict ? skipped : undefined); if (!stmts.length) return emptyResult(wasIncomplete); const stmtMap = new Map(); @@ -396,7 +398,21 @@ export function parse(input: string, cat: ParamMap, rootName?: string): ParseRes // Derive from map to deduplicate — Map.set overwrites duplicates const typedStmts = [...stmtMap.values()]; - return buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtMap.size, cat, rootName); + const result = buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtMap.size, cat, rootName); + + // In strict mode, add parse-level errors for skipped lines + if (strict && skipped.length > 0) { + for (const line of skipped) { + result.meta.errors.push({ + code: "parse-failed", + component: "", + path: "", + message: `Unexpected text: "${line}" — expected a valid openui-lang statement (identifier = expression)`, + }); + } + } + + return result; } export interface StreamParser { @@ -632,6 +648,20 @@ export function createParser(schema: LibraryJSONSchema, rootName?: string): Pars }; } +/** + * Create a parser from a library JSON Schema document with strict mode. + * When strict is true, lines that don't parse as valid openui-lang statements + * are reported as errors instead of being silently skipped. + */ +export function createStrictParser(schema: LibraryJSONSchema, rootName?: string): Parser { + const paramMap = compileSchema(schema); + return { + parse(input: string): ParseResult { + return parse(input, paramMap, rootName, true); + }, + }; +} + /** * Create a streaming parser from a library JSON Schema document. * Pass `library.toJSONSchema()` to get the schema. diff --git a/packages/lang-core/src/parser/statements.ts b/packages/lang-core/src/parser/statements.ts index 9cc3d6b32..41de2f571 100644 --- a/packages/lang-core/src/parser/statements.ts +++ b/packages/lang-core/src/parser/statements.ts @@ -2,7 +2,7 @@ // Statement splitter for openui-lang // ───────────────────────────────────────────────────────────────────────────── -import { T, type Token } from "./tokens"; +import { T, type Token, tokenText } from "./tokens"; export interface RawStmt { id: string; @@ -69,7 +69,7 @@ export function autoClose(input: string): { text: string; wasIncomplete: boolean * * Invalid lines (no `=`, or no identifier) are silently skipped. */ -export function split(tokens: Token[]): RawStmt[] { +export function split(tokens: Token[], skipped?: string[]): RawStmt[] { const stmts: RawStmt[] = []; let pos = 0; @@ -81,7 +81,12 @@ export function split(tokens: Token[]): RawStmt[] { // Expect: Ident|Type|StateVar = expression const tok = tokens[pos]; if (tok.t !== T.Ident && tok.t !== T.Type && tok.t !== T.StateVar) { - while (pos < tokens.length && tokens[pos].t !== T.Newline && tokens[pos].t !== T.EOF) pos++; + let text = ""; + while (pos < tokens.length && tokens[pos].t !== T.Newline && tokens[pos].t !== T.EOF) { + text += tokenText(tokens[pos]); + pos++; + } + if (skipped && text.trim()) skipped.push(text.trim()); continue; } const id = tok.v as string; @@ -90,7 +95,12 @@ export function split(tokens: Token[]): RawStmt[] { // 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++; + let text = id; + while (pos < tokens.length && tokens[pos].t !== T.Newline && tokens[pos].t !== T.EOF) { + text += tokenText(tokens[pos]); + pos++; + } + if (skipped && text.trim()) skipped.push(text.trim()); continue; } pos++; diff --git a/packages/lang-core/src/parser/tokens.ts b/packages/lang-core/src/parser/tokens.ts index 4202a955b..fee9604b2 100644 --- a/packages/lang-core/src/parser/tokens.ts +++ b/packages/lang-core/src/parser/tokens.ts @@ -50,3 +50,41 @@ export type Token = { t: T; v?: string | number; }; + +/** Reconstruct the text representation of a token. */ +export function tokenText(tok: Token): string { + if (tok.v !== undefined) return String(tok.v); + switch (tok.t) { + case T.Newline: return "\n"; + case T.LParen: return "("; + case T.RParen: return ")"; + case T.LBrack: return "["; + case T.RBrack: return "]"; + case T.LBrace: return "{"; + case T.RBrace: return "}"; + case T.Comma: return ","; + case T.Colon: return ":"; + case T.Equals: return "="; + case T.True: return "true"; + case T.False: return "false"; + case T.Null: return "null"; + case T.Dot: return "."; + case T.Plus: return "+"; + case T.Minus: return "-"; + case T.Star: return "*"; + case T.Slash: return "/"; + case T.Percent: return "%"; + case T.EqEq: return "=="; + case T.NotEq: return "!="; + case T.Greater: return ">"; + case T.Less: return "<"; + case T.GreaterEq: return ">="; + case T.LessEq: return "<="; + case T.And: return "&&"; + case T.Or: return "||"; + case T.Not: return "!"; + case T.Question: return "?"; + case T.EOF: return ""; + default: return ""; + } +} diff --git a/packages/lang-core/src/parser/types.ts b/packages/lang-core/src/parser/types.ts index dbe32229c..b6272c7dd 100644 --- a/packages/lang-core/src/parser/types.ts +++ b/packages/lang-core/src/parser/types.ts @@ -73,7 +73,8 @@ export type ValidationErrorCode = | "null-required" | "unknown-component" | "inline-reserved" - | "excess-args"; + | "excess-args" + | "parse-failed"; /** * A prop validation error. Components with missing required props are From 3604ef0c8a7099e49ffc52226f975a6b98709e4f Mon Sep 17 00:00:00 2001 From: Omkar Bhor Date: Mon, 8 Jun 2026 18:40:18 +0530 Subject: [PATCH 3/3] fix: apply Prettier formatting to Renderer.test.tsx --- packages/react-lang/src/__tests__/Renderer.test.tsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/react-lang/src/__tests__/Renderer.test.tsx b/packages/react-lang/src/__tests__/Renderer.test.tsx index 98045c0ed..f86fad684 100644 --- a/packages/react-lang/src/__tests__/Renderer.test.tsx +++ b/packages/react-lang/src/__tests__/Renderer.test.tsx @@ -48,9 +48,7 @@ describe("Renderer", () => { it("calls onParseResult with a ParseResult when given valid openui-lang", async () => { const onParseResult = vi.fn(); - render( - , - ); + render(); expect(onParseResult).toHaveBeenCalled(); const result = onParseResult.mock.calls[onParseResult.mock.calls.length - 1]![0]; @@ -62,9 +60,7 @@ describe("Renderer", () => { it("parse result contains the correct component typeName", async () => { const onParseResult = vi.fn(); - render( - , - ); + render(); const result = onParseResult.mock.calls[onParseResult.mock.calls.length - 1]![0]; expect(result?.root?.typeName).toBe("TextContent"); @@ -77,9 +73,7 @@ describe("Renderer", () => { }); it("accepts isStreaming prop without errors", () => { - const { container } = render( - , - ); + const { container } = render(); expect(container).toBeDefined(); }); });