From bd19dbee6bec72c4291bc11eda77c788ec600e21 Mon Sep 17 00:00:00 2001 From: Pavan Date: Fri, 20 Mar 2026 20:11:27 +0530 Subject: [PATCH 1/2] fix: prevent recharts crash during streaming by enforcing schema + runtime guard --- .../genui-lib/Charts/AreaChartCondensed.ts | 11 ++++- .../src/genui-lib/Charts/BarChartCondensed.ts | 11 ++++- .../genui-lib/Charts/HorizontalBarChart.ts | 11 ++++- .../genui-lib/Charts/LineChartCondensed.ts | 12 +++++- .../react-ui/src/genui-lib/Charts/PieChart.ts | 12 +++++- .../src/genui-lib/Charts/RadarChart.ts | 12 +++++- .../src/genui-lib/Charts/RadialChart.ts | 12 +++++- .../src/genui-lib/Charts/ScatterChart.ts | 18 ++++++-- .../genui-lib/Charts/SingleStackedBarChart.ts | 12 +++++- .../src/genui-lib/Charts/dataSchemas.ts | 41 +++++++++++++------ 10 files changed, 120 insertions(+), 32 deletions(-) diff --git a/packages/react-ui/src/genui-lib/Charts/AreaChartCondensed.ts b/packages/react-ui/src/genui-lib/Charts/AreaChartCondensed.ts index 394c23b99..f14f8e48a 100644 --- a/packages/react-ui/src/genui-lib/Charts/AreaChartCondensed.ts +++ b/packages/react-ui/src/genui-lib/Charts/AreaChartCondensed.ts @@ -21,8 +21,15 @@ export const AreaChartCondensed = defineComponent({ description: "Filled area under lines; use for cumulative totals or volume trends over time", component: ({ props }) => { if (!hasAllProps(props as Record, "labels", "series")) return null; + + // FIX: guard against partial streaming data + if (!Array.isArray(props.labels) || !Array.isArray(props.series)) return null; + const data = buildChartData(props.labels, props.series); - if (!data.length) return null; + + // FIX: ensure valid data before rendering (prevents recharts crash) + if (!Array.isArray(data) || data.length === 0) return null; + return React.createElement(AreaChartCondensedComponent, { data, categoryKey: "category", @@ -32,4 +39,4 @@ export const AreaChartCondensed = defineComponent({ isAnimationActive: false, }); }, -}); +}); \ No newline at end of file diff --git a/packages/react-ui/src/genui-lib/Charts/BarChartCondensed.ts b/packages/react-ui/src/genui-lib/Charts/BarChartCondensed.ts index c9b4db761..11ec81d6a 100644 --- a/packages/react-ui/src/genui-lib/Charts/BarChartCondensed.ts +++ b/packages/react-ui/src/genui-lib/Charts/BarChartCondensed.ts @@ -21,8 +21,15 @@ export const BarChartCondensed = defineComponent({ description: "Vertical bars; use for comparing values across categories with one or more series", component: ({ props }) => { if (!hasAllProps(props as Record, "labels", "series")) return null; + + // FIX: guard against partial streaming data + if (!Array.isArray(props.labels) || !Array.isArray(props.series)) return null; + const data = buildChartData(props.labels, props.series); - if (!data.length) return null; + + // FIX: ensure valid data before rendering (prevents recharts crash) + if (!Array.isArray(data) || data.length === 0) return null; + return React.createElement(BarChartCondensedComponent, { data, categoryKey: "category", @@ -32,4 +39,4 @@ export const BarChartCondensed = defineComponent({ isAnimationActive: false, }); }, -}); +}); \ No newline at end of file diff --git a/packages/react-ui/src/genui-lib/Charts/HorizontalBarChart.ts b/packages/react-ui/src/genui-lib/Charts/HorizontalBarChart.ts index df45564a6..78714bfd2 100644 --- a/packages/react-ui/src/genui-lib/Charts/HorizontalBarChart.ts +++ b/packages/react-ui/src/genui-lib/Charts/HorizontalBarChart.ts @@ -21,8 +21,15 @@ export const HorizontalBarChart = defineComponent({ description: "Horizontal bars; prefer when category labels are long or for ranked lists", component: ({ props }) => { if (!hasAllProps(props as Record, "labels", "series")) return null; + + // FIX: guard against partial streaming data + if (!Array.isArray(props.labels) || !Array.isArray(props.series)) return null; + const data = buildChartData(props.labels, props.series); - if (!data.length) return null; + + // FIX: ensure valid data before rendering (prevents recharts crash) + if (!Array.isArray(data) || data.length === 0) return null; + return React.createElement(HorizontalBarChartComponent, { data, categoryKey: "category", @@ -32,4 +39,4 @@ export const HorizontalBarChart = defineComponent({ isAnimationActive: false, }); }, -}); +}); \ No newline at end of file diff --git a/packages/react-ui/src/genui-lib/Charts/LineChartCondensed.ts b/packages/react-ui/src/genui-lib/Charts/LineChartCondensed.ts index 431ba3d34..a8c6b3e82 100644 --- a/packages/react-ui/src/genui-lib/Charts/LineChartCondensed.ts +++ b/packages/react-ui/src/genui-lib/Charts/LineChartCondensed.ts @@ -1,3 +1,4 @@ + "use client"; import { defineComponent } from "@openuidev/react-lang"; @@ -21,8 +22,15 @@ export const LineChartCondensed = defineComponent({ description: "Lines over categories; use for trends and continuous data over time", component: ({ props }) => { if (!hasAllProps(props as Record, "labels", "series")) return null; + + // FIX: guard against partial streaming data + if (!Array.isArray(props.labels) || !Array.isArray(props.series)) return null; + const data = buildChartData(props.labels, props.series); - if (!data.length) return null; + + // FIX: ensure valid data before rendering (prevents recharts crash) + if (!Array.isArray(data) || data.length === 0) return null; + return React.createElement(LineChartCondensedComponent, { data, categoryKey: "category", @@ -32,4 +40,4 @@ export const LineChartCondensed = defineComponent({ isAnimationActive: false, }); }, -}); +}); \ No newline at end of file diff --git a/packages/react-ui/src/genui-lib/Charts/PieChart.ts b/packages/react-ui/src/genui-lib/Charts/PieChart.ts index 71feb636d..e7ef87600 100644 --- a/packages/react-ui/src/genui-lib/Charts/PieChart.ts +++ b/packages/react-ui/src/genui-lib/Charts/PieChart.ts @@ -1,3 +1,4 @@ + "use client"; import { defineComponent } from "@openuidev/react-lang"; @@ -18,8 +19,15 @@ export const PieChart = defineComponent({ description: "Circular slices showing part-to-whole proportions; supports pie and donut variants", component: ({ props }) => { if (!hasAllProps(props as Record, "slices")) return null; + + // FIX: guard against partial streaming data + if (!Array.isArray(props.slices)) return null; + const data = buildSliceData(props.slices); - if (!data.length) return null; + + // FIX: ensure valid data before rendering (prevents recharts crash) + if (!Array.isArray(data) || data.length === 0) return null; + return React.createElement(PieChartComponent, { data, categoryKey: "category", @@ -28,4 +36,4 @@ export const PieChart = defineComponent({ isAnimationActive: false, }); }, -}); +}); \ No newline at end of file diff --git a/packages/react-ui/src/genui-lib/Charts/RadarChart.ts b/packages/react-ui/src/genui-lib/Charts/RadarChart.ts index 1f4c1a0f4..9c3abc372 100644 --- a/packages/react-ui/src/genui-lib/Charts/RadarChart.ts +++ b/packages/react-ui/src/genui-lib/Charts/RadarChart.ts @@ -1,3 +1,4 @@ + "use client"; import { defineComponent } from "@openuidev/react-lang"; @@ -18,12 +19,19 @@ export const RadarChart = defineComponent({ description: "Spider/web chart; use for comparing multiple variables across one or more entities", component: ({ props }) => { if (!hasAllProps(props as Record, "labels", "series")) return null; + + // FIX: guard against partial streaming data + if (!Array.isArray(props.labels) || !Array.isArray(props.series)) return null; + const data = buildChartData(props.labels, props.series); - if (!data.length) return null; + + // FIX: ensure valid data before rendering (prevents recharts crash) + if (!Array.isArray(data) || data.length === 0) return null; + return React.createElement(RadarChartComponent, { data, categoryKey: "category", isAnimationActive: false, }); }, -}); +}); \ No newline at end of file diff --git a/packages/react-ui/src/genui-lib/Charts/RadialChart.ts b/packages/react-ui/src/genui-lib/Charts/RadialChart.ts index 31f0da5fb..a254dc013 100644 --- a/packages/react-ui/src/genui-lib/Charts/RadialChart.ts +++ b/packages/react-ui/src/genui-lib/Charts/RadialChart.ts @@ -1,3 +1,4 @@ + "use client"; import { defineComponent } from "@openuidev/react-lang"; @@ -17,8 +18,15 @@ export const RadialChart = defineComponent({ description: "Radial bars showing proportional distribution across named segments", component: ({ props }) => { if (!hasAllProps(props as Record, "slices")) return null; + + // FIX: guard against partial streaming data + if (!Array.isArray((props as any).slices)) return null; + const data = buildSliceData((props as any).slices); - if (!data.length) return null; + + // FIX: ensure valid data before rendering (prevents recharts crash) + if (!Array.isArray(data) || data.length === 0) return null; + return React.createElement(RadialChartComponent, { data, categoryKey: "category", @@ -26,4 +34,4 @@ export const RadialChart = defineComponent({ isAnimationActive: false, }); }, -}); +}); \ No newline at end of file diff --git a/packages/react-ui/src/genui-lib/Charts/ScatterChart.ts b/packages/react-ui/src/genui-lib/Charts/ScatterChart.ts index e6fefa5a0..8d2909284 100644 --- a/packages/react-ui/src/genui-lib/Charts/ScatterChart.ts +++ b/packages/react-ui/src/genui-lib/Charts/ScatterChart.ts @@ -1,3 +1,4 @@ + "use client"; import { defineComponent } from "@openuidev/react-lang"; @@ -21,10 +22,18 @@ export const ScatterChart = defineComponent({ description: "X/Y scatter plot; use for correlations, distributions, and clustering", component: ({ props }) => { if (!hasAllProps(props as Record, "datasets")) return null; + + // FIX: guard against partial streaming data + if (!Array.isArray((props as any).datasets)) return null; + const rawDatasets = asArray((props as any).datasets); + const data = rawDatasets.map((ds: any) => { const dsProps = unwrap(ds); - const rawPoints = asArray(dsProps?.points); + + // FIX: ensure points is always array + const rawPoints = Array.isArray(dsProps?.points) ? dsProps.points : []; + return { name: (dsProps?.name ?? "") as string, data: rawPoints.map((pt: any) => { @@ -37,7 +46,10 @@ export const ScatterChart = defineComponent({ }), }; }); - if (!data.length) return null; + + // FIX: ensure valid data before rendering + if (!Array.isArray(data) || data.length === 0) return null; + return React.createElement(ScatterChartComponent, { data, xAxisDataKey: "x", @@ -45,4 +57,4 @@ export const ScatterChart = defineComponent({ isAnimationActive: false, }); }, -}); +}); \ No newline at end of file diff --git a/packages/react-ui/src/genui-lib/Charts/SingleStackedBarChart.ts b/packages/react-ui/src/genui-lib/Charts/SingleStackedBarChart.ts index a1a049b91..a99221de0 100644 --- a/packages/react-ui/src/genui-lib/Charts/SingleStackedBarChart.ts +++ b/packages/react-ui/src/genui-lib/Charts/SingleStackedBarChart.ts @@ -1,3 +1,4 @@ + "use client"; import { defineComponent } from "@openuidev/react-lang"; @@ -18,12 +19,19 @@ export const SingleStackedBarChart = defineComponent({ "Single horizontal stacked bar; use for showing part-to-whole proportions in one row", component: ({ props }) => { if (!hasAllProps(props as Record, "slices")) return null; + + // FIX: guard against partial streaming data + if (!Array.isArray((props as any).slices)) return null; + const data = buildSliceData((props as any).slices); - if (!data.length) return null; + + // FIX: ensure valid data before rendering (prevents recharts crash) + if (!Array.isArray(data) || data.length === 0) return null; + return React.createElement(SingleStackedBarChartComponent, { data, categoryKey: "category", dataKey: "value", }); }, -}); +}); \ No newline at end of file diff --git a/packages/react-ui/src/genui-lib/Charts/dataSchemas.ts b/packages/react-ui/src/genui-lib/Charts/dataSchemas.ts index 8161818af..c1f72c955 100644 --- a/packages/react-ui/src/genui-lib/Charts/dataSchemas.ts +++ b/packages/react-ui/src/genui-lib/Charts/dataSchemas.ts @@ -16,19 +16,25 @@ export const chart2DDataSchema = z.object({ values: z.array(z.number()), }), ) + // FIX: allow missing/undefined and fallback to [] + .optional() .default([]), }); export const scatterDataSchema = z.array( z.object({ name: z.string(), - series: z.array( - z.object({ - x: z.number(), - y: z.number(), - z: z.number().optional(), - }), - ), + series: z + .array( + z.object({ + x: z.number(), + y: z.number(), + z: z.number().optional(), + }), + ) + // FIX: ensure safe default for streaming cases + .optional() + .default([]), }), ); @@ -40,10 +46,16 @@ export type ScatterData = z.infer; export type MiniChartData = z.infer; export function transform2DData(data: Chart2DData): Array> { - return data.labels.map((label, i) => { + // FIX: ensure labels is always array + const labels = Array.isArray(data.labels) ? data.labels : []; + + // FIX: ensure series is always array + const series = Array.isArray(data.series) ? data.series : []; + + return labels.map((label, i) => { const row: Record = { category: label }; - for (const s of data.series) { - row[s.category] = s.values[i] ?? 0; + for (const s of series) { + row[s.category] = s.values?.[i] ?? 0; } return row; }); @@ -56,8 +68,11 @@ export function transform1DData(data: Chart1DData): Array }> { - return data.map((dataset) => ({ + // FIX: ensure safe iteration + const safeData = Array.isArray(data) ? data : []; + + return safeData.map((dataset) => ({ name: dataset.name, - data: dataset.series, + data: Array.isArray(dataset.series) ? dataset.series : [], })); -} +} \ No newline at end of file From 8d07091a63f4c1634fec9d57bbccc978c58e43fa Mon Sep 17 00:00:00 2001 From: Pavan Date: Fri, 20 Mar 2026 20:50:55 +0530 Subject: [PATCH 2/2] fix: apply prettier formatting --- .cursor/environment.json | 2 +- .cursor/worktrees.json | 4 +- .github/ISSUE_TEMPLATE/bug_report.md | 24 +- .github/ISSUE_TEMPLATE/feature_request.md | 7 +- .github/pull_request_template.md | 2 +- .github/workflows/build-js.yml | 6 +- .prettierrc.json | 2 +- CODE_OF_CONDUCT.md | 21 +- README.md | 22 +- benchmarks/README.md | 2 - .../CompatibilitySection.tsx | 28 +- .../FadeInSection/FadeInSection.tsx | 6 +- .../FeaturesSection/FeaturesSection.tsx | 16 +- docs/app/(home)/components/Footer/Footer.tsx | 14 +- .../HeroSection/HeroSection.module.css | 2 +- .../components/HeroSection/HeroSection.tsx | 32 +- docs/app/(home)/components/Navbar/Navbar.tsx | 11 +- .../PossibilitiesSection.tsx | 8 +- .../components/ShiroMascot/ShiroMascot.tsx | 6 +- .../components/StepsSection/StepsSection.tsx | 26 +- .../UILibrariesSection/UILibrariesSection.tsx | 11 +- docs/app/(home)/components/shared/shared.tsx | 3 +- docs/app/blog/[slug]/page.tsx | 14 +- docs/components/docs-route-layout.tsx | 13 +- docs/content/blog/rust-wasm-parser.mdx | 55 +- .../openui-lang/examples/react-native.mdx | 10 +- .../docs/openui-lang/examples/shadcn-chat.mdx | 1 - .../openui-lang/examples/vercel-ai-chat.mdx | 2 +- examples/openui-chat/eslint.config.mjs | 2 +- .../openui-chat/src/app/api/chat/route.ts | 158 +- examples/openui-chat/src/app/globals.css | 1 - examples/openui-chat/src/app/layout.tsx | 2 +- examples/openui-chat/src/library.ts | 5 +- examples/shadcn-chat/README.md | 112 +- examples/shadcn-chat/src/app/layout.tsx | 2 +- examples/vercel-ai-chat/README.md | 69 +- examples/vercel-ai-chat/eslint.config.mjs | 9 +- examples/vercel-ai-chat/src/app/layout.tsx | 2 +- examples/vercel-ai-chat/src/app/page.tsx | 19 +- .../src/components/chat-header.tsx | 8 +- .../vercel-ai-chat/src/components/sidebar.tsx | 9 +- .../src/hooks/use-system-theme.tsx | 2 +- .../vercel-ai-chat/src/hooks/use-threads.ts | 10 +- examples/vercel-ai-chat/src/library.ts | 5 +- .../templates/openui-chat/eslint.config.mjs | 2 +- .../src/templates/openui-chat/src/library.ts | 5 +- packages/react-headless/README.md | 79 +- packages/react-lang/README.md | 72 +- packages/react-ui/.storybook/manager.ts | 1 - packages/react-ui/.storybook/preflight.css | 16 +- packages/react-ui/.storybook/preview.tsx | 16 +- packages/react-ui/README.md | 80 +- .../genui-lib/Charts/AreaChartCondensed.ts | 2 +- .../src/genui-lib/Charts/BarChartCondensed.ts | 2 +- .../genui-lib/Charts/HorizontalBarChart.ts | 2 +- .../genui-lib/Charts/LineChartCondensed.ts | 3 +- .../react-ui/src/genui-lib/Charts/PieChart.ts | 3 +- .../src/genui-lib/Charts/RadarChart.ts | 3 +- .../src/genui-lib/Charts/RadialChart.ts | 3 +- .../src/genui-lib/Charts/ScatterChart.ts | 3 +- .../genui-lib/Charts/SingleStackedBarChart.ts | 3 +- .../src/genui-lib/Charts/dataSchemas.ts | 2 +- packages/react-ui/src/openui-defaults.scss | 12 +- pnpm-lock.yaml | 21597 ++++++++++------ 64 files changed, 14066 insertions(+), 8605 deletions(-) diff --git a/.cursor/environment.json b/.cursor/environment.json index 06198657e..faca89c27 100644 --- a/.cursor/environment.json +++ b/.cursor/environment.json @@ -2,4 +2,4 @@ "agentCanUpdateSnapshot": true, "install": "pnpm install", "start": "pnpm dev:start" -} \ No newline at end of file +} diff --git a/.cursor/worktrees.json b/.cursor/worktrees.json index e57556bc0..bf9e730d2 100644 --- a/.cursor/worktrees.json +++ b/.cursor/worktrees.json @@ -1,5 +1,3 @@ { - "setup-worktree": [ - "pnpm install" - ] + "setup-worktree": ["pnpm install"] } diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea782..9b77ea713 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,10 +1,9 @@ --- name: Bug report about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- **Describe the bug** @@ -12,6 +11,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] **Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] + +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Browser [e.g. stock browser, safari] +- Version [e.g. 22] **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7d6..2bc5d5f71 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,10 +1,9 @@ --- name: Feature request about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f42fcbe8a..d1fbd05f0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,7 +4,7 @@ Describe the change and why it is needed. ## Changes -- +- ## Test Plan diff --git a/.github/workflows/build-js.yml b/.github/workflows/build-js.yml index ba5065df4..8a909bb31 100644 --- a/.github/workflows/build-js.yml +++ b/.github/workflows/build-js.yml @@ -2,9 +2,9 @@ name: Build JS on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: build: @@ -20,7 +20,7 @@ jobs: with: node-version: 20 cache: pnpm - cache-dependency-path: 'pnpm-lock.yaml' + cache-dependency-path: "pnpm-lock.yaml" - name: Install dependencies run: pnpm install --frozen-lockfile diff --git a/.prettierrc.json b/.prettierrc.json index f94891b0a..27bc9781b 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -5,4 +5,4 @@ "printWidth": 100, "tabWidth": 2, "plugins": ["prettier-plugin-organize-imports"] -} \ No newline at end of file +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 20bcc4f64..a545f08a3 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,3 @@ - # Contributor Covenant Code of Conduct ## Our Pledge @@ -18,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall +- Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or advances of +- The use of sexualized language or imagery, and sexual attention or advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities diff --git a/README.md b/README.md index f16e5b052..b11272a6b 100644 --- a/README.md +++ b/README.md @@ -12,18 +12,12 @@ - OpenUI is a full-stack Generative UI framework — a compact streaming-first language, a React runtime with built-in component libraries, and ready-to-use chat interfaces — that is up to 67% more token-efficient than JSON. - - --- - - [Docs](https://openui.com) · [Playground](https://www.openui.com/playground) · [Sample Chat App](./examples/openui-chat) · [Discord](https://discord.com/invite/Pbv5PsqUSv) · [Contributing](./CONTRIBUTING.md) · [Code of Conduct](./CODE_OF_CONDUCT.md) · [Security](./SECURITY.md) · [License](./LICENSE) - --- ## What is OpenUI @@ -43,7 +37,6 @@ At the center of OpenUI is **OpenUI Lang**: a compact, streaming-first language - **Streaming renderer** — Parse and render model output progressively in React as tokens arrive. - **Chat and app surfaces** - Use the same foundation for assistants, copilots, and broader interactive product flows. - ## Quick Start ```bash @@ -62,8 +55,6 @@ What this gives you: - **Streaming support** - Update the UI progressively as output arrives. - **Working app foundation** - Start from a ready-to-run example instead of wiring everything manually. - - ## How it works Your components define what the model can generate. @@ -87,12 +78,12 @@ Try it yourself in the [Playground](https://www.openui.com/playground) — gener ## Packages -| Package | Description | -| :--- | :--- | -| [`@openuidev/react-lang`](./packages/react-lang) | Core runtime — component definitions, parser, renderer, prompt generation | -| [`@openuidev/react-headless`](./packages/react-headless) | Headless chat state, streaming adapters, message format converters | -| [`@openuidev/react-ui`](./packages/react-ui) | Prebuilt chat layouts and two built-in component libraries | -| [`@openuidev/cli`](./packages/openui-cli) | CLI for scaffolding apps and generating system prompts | +| Package | Description | +| :------------------------------------------------------- | :------------------------------------------------------------------------ | +| [`@openuidev/react-lang`](./packages/react-lang) | Core runtime — component definitions, parser, renderer, prompt generation | +| [`@openuidev/react-headless`](./packages/react-headless) | Headless chat state, streaming adapters, message format converters | +| [`@openuidev/react-ui`](./packages/react-ui) | Prebuilt chat layouts and two built-in component libraries | +| [`@openuidev/cli`](./packages/openui-cli) | CLI for scaffolding apps and generating system prompts | ```bash npm install @openuidev/react-lang @openuidev/react-ui @@ -154,7 +145,6 @@ Good places to start: - [Discord](https://discord.com/invite/Pbv5PsqUSv) — Ask questions, share what you're building - [GitHub Issues](https://github.com/thesysdev/openui/issues) — Report bugs or request features - ## Contributing Contributions are welcome. See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for contribution guidelines and ways to get involved. diff --git a/benchmarks/README.md b/benchmarks/README.md index 19f4aaf63..f0c4cc0cc 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -44,7 +44,6 @@ Measured with `tiktoken` (`gpt-5` model encoder). Generated by GPT-5.2 at temper | e-commerce-product | 2145 | 2449 | 2381 | 1166 | -45.6% | -52.4% | -51.0% | | **TOTAL** | **9122** | **10180** | **9948** | **4800** | **-47.4%** | **-52.8%** | **-51.7%** | - ## Running ### Prerequisites @@ -55,7 +54,6 @@ Export `OPENAI_API_KEY` in your shell: export OPENAI_API_KEY=sk-... ``` - ### 1. Generate samples (calls OpenAI) ```bash diff --git a/docs/app/(home)/components/CompatibilitySection/CompatibilitySection.tsx b/docs/app/(home)/components/CompatibilitySection/CompatibilitySection.tsx index b2717d257..661608d29 100644 --- a/docs/app/(home)/components/CompatibilitySection/CompatibilitySection.tsx +++ b/docs/app/(home)/components/CompatibilitySection/CompatibilitySection.tsx @@ -54,17 +54,9 @@ function Chip({ item }: { item: CompatibilityItem }) { style={{ backgroundColor: item.badgeBg }} > {/* eslint-disable-next-line @next/next/no-img-element */} - {item.name} + {item.name} - - {item.name} - + {item.name} ); } @@ -80,16 +72,12 @@ export function CompatibilitySection() {
{/* LLMs row */}
- - Any LLM - + Any LLM
{LLMS.map((item) => ( ))} - - + more - + + more
@@ -98,16 +86,12 @@ export function CompatibilitySection() { {/* Frameworks row */}
- - Any Framework - + Any Framework
{FRAMEWORKS.map((item) => ( ))} - - + more - + + more
diff --git a/docs/app/(home)/components/FadeInSection/FadeInSection.tsx b/docs/app/(home)/components/FadeInSection/FadeInSection.tsx index adf65cea7..5942cb7d4 100644 --- a/docs/app/(home)/components/FadeInSection/FadeInSection.tsx +++ b/docs/app/(home)/components/FadeInSection/FadeInSection.tsx @@ -9,9 +9,5 @@ interface FadeInSectionProps { } export function FadeInSection({ children, className = "" }: FadeInSectionProps) { - return ( -
- {children} -
- ); + return
{children}
; } diff --git a/docs/app/(home)/components/FeaturesSection/FeaturesSection.tsx b/docs/app/(home)/components/FeaturesSection/FeaturesSection.tsx index c64d53872..8453a2a58 100644 --- a/docs/app/(home)/components/FeaturesSection/FeaturesSection.tsx +++ b/docs/app/(home)/components/FeaturesSection/FeaturesSection.tsx @@ -88,13 +88,9 @@ function DesktopFeatureRow({ feature, index }: { feature: Feature; index: number
- - {feature.title} - + {feature.title} - - {feature.description} - + {feature.description} ); } @@ -103,12 +99,8 @@ function MobileFeatureRow({ feature, index }: { feature: Feature; index: number return (
- - {feature.title} - - - {feature.description} - + {feature.title} + {feature.description}
diff --git a/docs/app/(home)/components/Footer/Footer.tsx b/docs/app/(home)/components/Footer/Footer.tsx index af87b02c9..daa9464c6 100644 --- a/docs/app/(home)/components/Footer/Footer.tsx +++ b/docs/app/(home)/components/Footer/Footer.tsx @@ -1,8 +1,8 @@ "use client"; -import type { CSSProperties } from "react"; import svgPaths from "@/imports/svg-urruvoh2be"; import mascotSvgPaths from "@/imports/svg-xeurqn3j1r"; +import type { CSSProperties } from "react"; import styles from "./Footer.module.css"; // --------------------------------------------------------------------------- @@ -158,9 +158,7 @@ export function Footer() {
-

- Handcrafted with a lot of love. -

+

Handcrafted with a lot of love.

@@ -179,9 +177,7 @@ export function Footer() { {/* Bottom bar */}
-

- 355 Bryant St, San Francisco, CA 94107 -

+

355 Bryant St, San Francisco, CA 94107

© {new Date().getFullYear()} Thesys Inc. All Rights Reserved @@ -194,9 +190,7 @@ export function Footer() {

© {new Date().getFullYear()} Thesys Inc. All Rights Reserved

-

- 355 Bryant St, San Francisco, CA 94107 -

+

355 Bryant St, San Francisco, CA 94107

diff --git a/docs/app/(home)/components/HeroSection/HeroSection.module.css b/docs/app/(home)/components/HeroSection/HeroSection.module.css index 346663af9..7a42c2afa 100644 --- a/docs/app/(home)/components/HeroSection/HeroSection.module.css +++ b/docs/app/(home)/components/HeroSection/HeroSection.module.css @@ -429,4 +429,4 @@ .taglineBreak { display: inline; } -} \ No newline at end of file +} diff --git a/docs/app/(home)/components/HeroSection/HeroSection.tsx b/docs/app/(home)/components/HeroSection/HeroSection.tsx index 4a7ef8423..e4e982e4e 100644 --- a/docs/app/(home)/components/HeroSection/HeroSection.tsx +++ b/docs/app/(home)/components/HeroSection/HeroSection.tsx @@ -10,7 +10,8 @@ import styles from "./HeroSection.module.css"; const LazyMobileActionFigure = lazy(() => import("@/imports/MobileActionFigure")); -const HERO_BUTTON_SHADOW = "0 1.5px 5px 0 rgba(22, 34, 51, 0.06), 0 12px 24px 0 rgba(22, 34, 51, 0.04)"; +const HERO_BUTTON_SHADOW = + "0 1.5px 5px 0 rgba(22, 34, 51, 0.06), 0 12px 24px 0 rgba(22, 34, 51, 0.04)"; const HERO_BUTTON_STYLE = { "--hero-button-shadow": HERO_BUTTON_SHADOW, } as CSSProperties; @@ -57,18 +58,11 @@ function NpmButton({ className = "" }: { className?: string }) { style={HERO_BUTTON_STYLE} onClick={onCopy} > - - {primaryCTA} - + {primaryCTA} - - {primaryCTA} - - @@ -102,10 +96,7 @@ function NpmButton({ className = "" }: { className?: string }) { function DesktopPlaygroundButton({ className = "" }: { className?: string }) { return ( - + {secondaryCTA} @@ -143,11 +134,7 @@ const MASCOT_FILLED_PATHS = [ function MascotSvg() { return ( - + {MASCOT_STROKED_PATHS.map((d) => ( ))} @@ -232,10 +219,7 @@ function DesktopHero() {
{/* CTA buttons */} -
+
diff --git a/docs/app/(home)/components/Navbar/Navbar.tsx b/docs/app/(home)/components/Navbar/Navbar.tsx index 5da6d286a..2478079c9 100644 --- a/docs/app/(home)/components/Navbar/Navbar.tsx +++ b/docs/app/(home)/components/Navbar/Navbar.tsx @@ -37,11 +37,7 @@ function DesktopNavTabs() { return (
{NAV_TABS.map((tab) => ( - + {tab} ))} @@ -103,10 +99,7 @@ function MobileMenu({ starCount, onClose }: { starCount: number; onClose: () => {NAV_TABS.map((tab, index) => (
{index > 0 && diff --git a/docs/app/(home)/components/PossibilitiesSection/PossibilitiesSection.tsx b/docs/app/(home)/components/PossibilitiesSection/PossibilitiesSection.tsx index 9d3a1cffd..772f1b31c 100644 --- a/docs/app/(home)/components/PossibilitiesSection/PossibilitiesSection.tsx +++ b/docs/app/(home)/components/PossibilitiesSection/PossibilitiesSection.tsx @@ -45,9 +45,7 @@ function Card({ title }: { title: string }) {
)}
-

- {title} -

+

{title}

@@ -147,9 +145,7 @@ export function PossibilitiesSection() { {/* Header */}
-

- Endless possibilities. Built in realtime. -

+

Endless possibilities. Built in realtime.

diff --git a/docs/app/(home)/components/ShiroMascot/ShiroMascot.tsx b/docs/app/(home)/components/ShiroMascot/ShiroMascot.tsx index 9e44a08e4..8c2e83d5e 100644 --- a/docs/app/(home)/components/ShiroMascot/ShiroMascot.tsx +++ b/docs/app/(home)/components/ShiroMascot/ShiroMascot.tsx @@ -5,11 +5,7 @@ export function ShiroMascot() { return (
- + diff --git a/docs/app/(home)/components/StepsSection/StepsSection.tsx b/docs/app/(home)/components/StepsSection/StepsSection.tsx index c21a70d9c..a4ff37369 100644 --- a/docs/app/(home)/components/StepsSection/StepsSection.tsx +++ b/docs/app/(home)/components/StepsSection/StepsSection.tsx @@ -88,9 +88,7 @@ function StepBadge({ num, isActive }: { num: number; isActive: boolean }) {
- - {num} - + {num}
); } @@ -102,9 +100,7 @@ function Divider() { function StepDetails({ step, hideDetails }: { step: Step; hideDetails?: boolean }) { return (
-

- {step.description} -

+

{step.description}

{!hideDetails && step.details.length > 0 && (
{step.detailsTitle &&

{step.detailsTitle}

} @@ -141,10 +137,7 @@ function StepIllustration({ stepNumber, mobile }: { stepNumber: number; mobile?: if (mobile) { return ( -
+
-

- {step.title} -

+

{step.title}

{isActive && ( @@ -248,9 +239,7 @@ function MobileStep({
@@ -288,10 +277,7 @@ export function StepsSection() { return (
-
+
{/* Desktop */}
{STEPS.map((step, i) => ( diff --git a/docs/app/(home)/components/UILibrariesSection/UILibrariesSection.tsx b/docs/app/(home)/components/UILibrariesSection/UILibrariesSection.tsx index 9cb5cdaee..6da0c5dff 100644 --- a/docs/app/(home)/components/UILibrariesSection/UILibrariesSection.tsx +++ b/docs/app/(home)/components/UILibrariesSection/UILibrariesSection.tsx @@ -1,6 +1,6 @@ -import type { CSSProperties } from "react"; import mascotSvgPaths from "@/imports/svg-10waxq0xyc"; import svgPaths from "@/imports/svg-urruvoh2be"; +import type { CSSProperties } from "react"; import styles from "./UILibrariesSection.module.css"; // --------------------------------------------------------------------------- @@ -105,10 +105,7 @@ function LibraryCard({ lib }: { lib: UILibrary }) {
{/* Icon */} -
+
{lib.isMascot ? (
@@ -164,9 +161,7 @@ function LibraryCard({ lib }: { lib: UILibrary }) {
{/* Name */} - - {displayName} - + {displayName}
); diff --git a/docs/app/(home)/components/shared/shared.tsx b/docs/app/(home)/components/shared/shared.tsx index d7078a5db..712e1572d 100644 --- a/docs/app/(home)/components/shared/shared.tsx +++ b/docs/app/(home)/components/shared/shared.tsx @@ -5,7 +5,8 @@ import styles from "./shared.module.css"; // Design tokens shared across multiple sections // --------------------------------------------------------------------------- -export const BUTTON_SHADOW = "0px 1px 3px 0px rgba(22,34,51,0.08), 0px 12px 24px 0px rgba(22,34,51,0.04)"; +export const BUTTON_SHADOW = + "0px 1px 3px 0px rgba(22,34,51,0.08), 0px 12px 24px 0px rgba(22,34,51,0.04)"; // --------------------------------------------------------------------------- // Shared components diff --git a/docs/app/blog/[slug]/page.tsx b/docs/app/blog/[slug]/page.tsx index e0122576f..9040a0000 100644 --- a/docs/app/blog/[slug]/page.tsx +++ b/docs/app/blog/[slug]/page.tsx @@ -1,13 +1,11 @@ import { blog } from "@/lib/source"; import { getMDXComponents } from "@/mdx-components"; -import { TOCProvider, TOCScrollArea } from "fumadocs-ui/components/toc/index"; import { TOCItems } from "fumadocs-ui/components/toc/default"; +import { TOCProvider, TOCScrollArea } from "fumadocs-ui/components/toc/index"; import type { Metadata } from "next"; import { notFound } from "next/navigation"; -export default async function BlogPostPage(props: { - params: Promise<{ slug: string }>; -}) { +export default async function BlogPostPage(props: { params: Promise<{ slug: string }> }) { const params = await props.params; const page = blog.getPage([params.slug]); @@ -19,9 +17,7 @@ export default async function BlogPostPage(props: {