Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/publish-npm-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on:
- react-headless
- react-lang
- react-email
- lang-core
- openui-cli
- svelte-lang
- vue-lang
Expand Down
1 change: 0 additions & 1 deletion docs/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const withMDX = createMDX();
/** @type {import('next').NextConfig} */
const config = {
serverExternalPackages: ["@takumi-rs/image-response"],
transpilePackages: ["@openuidev/react-ui", "@openuidev/react-lang", "@openuidev/react-headless"],
turbopack: {
root: dirname(dirname(__dirname)),
},
Expand Down
1 change: 1 addition & 0 deletions eslint.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ module.exports = [
"**/__tests__/**/*.{ts,tsx}",
"**/*.test.{ts,tsx}",
"**/*.spec.{ts,tsx}",
"*.config.ts",
],
languageOptions: {
parser: typescript,
Expand Down
1 change: 0 additions & 1 deletion examples/form-generator/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { NextConfig } from "next";

const nextConfig: NextConfig = {
turbopack: {},
transpilePackages: ["@openuidev/react-headless", "@openuidev/react-lang"],
};

export default nextConfig;
1 change: 0 additions & 1 deletion examples/mastra-chat/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { NextConfig } from "next";

const nextConfig: NextConfig = {
turbopack: {},
transpilePackages: ["@openuidev/react-ui", "@openuidev/react-headless", "@openuidev/react-lang"],
};

export default nextConfig;
1 change: 0 additions & 1 deletion examples/multi-agent-chat/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { NextConfig } from "next";

const nextConfig: NextConfig = {
turbopack: {},
transpilePackages: ["@openuidev/react-ui", "@openuidev/react-lang"],
};

export default nextConfig;
1 change: 0 additions & 1 deletion examples/openui-artifact-demo/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",
turbopack: {},
transpilePackages: ["@openuidev/react-ui", "@openuidev/react-headless", "@openuidev/react-lang"],
};

export default nextConfig;
1 change: 0 additions & 1 deletion examples/openui-chat/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",
turbopack: {},
transpilePackages: ["@openuidev/react-ui", "@openuidev/react-headless", "@openuidev/react-lang"],
};

export default nextConfig;
1 change: 0 additions & 1 deletion examples/openui-dashboard/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",
turbopack: {},
transpilePackages: ["@openuidev/react-ui", "@openuidev/react-headless", "@openuidev/react-lang"],
};

export default nextConfig;
8 changes: 1 addition & 7 deletions examples/react-email/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
transpilePackages: [
"@openuidev/react-headless",
"@openuidev/react-lang",
"@openuidev/react-email",
],
};
const nextConfig: NextConfig = {};

export default nextConfig;
52 changes: 2 additions & 50 deletions examples/react-email/src/generated/system-prompt.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ You are an AI assistant that responds using openui-lang, a declarative UI langua
## Component Signatures

Arguments marked with ? are optional. Sub-components can be inline or referenced; prefer references for better streaming.
Props typed `ActionExpression` accept an Action([@steps...]) expression. See the Action section for available steps (@ToAssistant, @OpenUrl).
Props marked `$binding<type>` accept a `$variable` reference for two-way binding.

### Email Structure
EmailTemplate(subject: string, previewText?: string, children: (EmailHeading | EmailText | EmailButton | EmailImage | EmailDivider | EmailLink | EmailCodeBlock | EmailCodeInline | EmailMarkdown | EmailArticle | EmailProductCard | EmailFeatureGrid | EmailFeatureList | EmailNumberedSteps | EmailCheckoutTable | EmailPricingCard | EmailTestimonial | EmailSurveyRating | EmailStats | EmailImageGrid | EmailAvatarGroup | EmailAvatarWithText | EmailList | EmailCustomerReview | EmailBentoGrid | EmailSection | EmailColumns | EmailColumn | EmailHeaderSideNav | EmailHeaderCenteredNav | EmailHeaderSocial | EmailFooterCentered | EmailFooterTwoColumn)[]) — Root email template. Renders a live email preview with Copy HTML export. Always provide a subject line.
Expand Down Expand Up @@ -135,50 +133,6 @@ EmailBentoGrid(heroTitle: string, heroDescription: string, heroLinkText?: string
- EmailBentoGrid: Bento-style layout with a dark hero section on top and product cards below. Pass EmailBentoItem children.
- Great for product showcase and marketing emails.

## Built-in Functions

Data functions prefixed with `@` to distinguish from components. These are the ONLY functions available — do NOT invent new ones.
Use @-prefixed built-in functions (@Count, @Sum, @Avg, @Min, @Max, @Round) on Query results — do NOT hardcode computed values.

@Count(array) → number — Returns array length
@First(array) → element — Returns first element of array
@Last(array) → element — Returns last element of array
@Sum(numbers[]) → number — Sum of numeric array
@Avg(numbers[]) → number — Average of numeric array
@Min(numbers[]) → number — Minimum value in array
@Max(numbers[]) → number — Maximum value in array
@Sort(array, field, direction?) → sorted array — Sort array by field. Direction: "asc" (default) or "desc"
@Filter(array, field, operator: "==" | "!=" | ">" | "<" | ">=" | "<=" | "contains", value) → filtered array — Filter array by field value
@Round(number, decimals?) → number — Round to N decimal places (default 0)
@Abs(number) → number — Absolute value
@Floor(number) → number — Round down to nearest integer
@Ceil(number) → number — Round up to nearest integer
@Each(array, varName, template) — Evaluate template for each element. varName is the loop variable — use it ONLY inside the template expression (inline). Do NOT create a separate statement for the template.

Builtins compose — output of one is input to the next:
`@Count(@Filter(data.rows, "field", "==", "val"))` for KPIs/chart values, `@Round(@Avg(data.rows.score), 1)`, `@Each(data.rows, "item", Comp(item.field))` for per-item rendering.
Array pluck: `data.rows.field` extracts a field from every row → use with @Sum, @Avg, charts, tables.

IMPORTANT @Each rule: The loop variable (e.g. "item") is ONLY available inside the @Each template expression. Always inline the template — do NOT extract it to a separate statement.
CORRECT: `Col("Actions", @Each(rows, "t", Button("Edit", Action([@Set($id, t.id)]))))`
WRONG: `myBtn = Button("Edit", Action([@Set($id, t.id)]))` then `Col("Actions", @Each(rows, "t", myBtn))` — t is undefined in myBtn.

## Action — Button Behavior

Action([@steps...]) wires button clicks to operations. Steps are @-prefixed built-in actions. Steps execute in order.
Buttons without an explicit Action prop automatically send their label to the assistant (equivalent to Action([@ToAssistant(label)])).

Available steps:
- @ToAssistant("message") — Send a message to the assistant (for conversational buttons like "Tell me more", "Explain this")
- @OpenUrl("https://...") — Navigate to a URL

Example — simple nav:
```
viewBtn = Button("View", Action([@OpenUrl("https://example.com")]))
```

- Action can be assigned to a variable or inlined: Button("Go", onSubmit) and Button("Go", Action([...])) both work

## 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.
Expand All @@ -187,10 +141,8 @@ During streaming, the output is re-parsed on every chunk. Undefined references a

**Recommended statement order for optimal streaming:**
1. `root = EmailTemplate(...)` — UI shell appears immediately
2. $variable declarations — state ready for bindings
3. Query statements — defaults resolve immediately so components render with data
4. Component definitions — fill in with data already available
5. Data values — leaf content last
2. Component definitions — fill in as they stream
3. Data values — leaf content last

Always write the root = EmailTemplate(...) statement first so the UI shell appears immediately, even before child data has streamed in.

Expand Down
1 change: 0 additions & 1 deletion examples/shadcn-chat/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { NextConfig } from "next";

const nextConfig: NextConfig = {
turbopack: {},
transpilePackages: ["@openuidev/react-ui", "@openuidev/react-headless", "@openuidev/react-lang"],
};

export default nextConfig;
1 change: 0 additions & 1 deletion examples/vercel-ai-chat/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { NextConfig } from "next";

const nextConfig: NextConfig = {
turbopack: {},
transpilePackages: ["@openuidev/react-ui", "@openuidev/react-lang"],
};

export default nextConfig;
3 changes: 0 additions & 3 deletions examples/vue-chat/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,4 @@ export default defineNuxtConfig({
"@tailwindcss/postcss": {},
},
},
build: {
transpile: ["@openuidev/vue-lang", "@openuidev/lang-core"],
},
});
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.2",
"@typescript-eslint/eslint-plugin": "^8.56.1",
"eslint": "^9.0.0",
"eslint-config-prettier": "^10.1.8",
Expand All @@ -17,6 +18,8 @@
"eslint-plugin-unused-imports": "^4.4.1",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
"publint": "^0.3.18",
"tsdown": "^0.21.7",
"typescript": "^5.9.3"
},
"keywords": [
Expand Down
1 change: 1 addition & 0 deletions packages/lang-core/eslint.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module.exports = [
"**/__tests__/**/*.{ts,tsx}",
"**/*.test.{ts,tsx}",
"**/*.spec.{ts,tsx}",
"*.config.ts",
],
languageOptions: {
parser: typescript,
Expand Down
33 changes: 21 additions & 12 deletions packages/lang-core/package.json
Original file line number Diff line number Diff line change
@@ -1,38 +1,47 @@
{
"name": "@openuidev/lang-core",
"version": "0.1.3",
"version": "0.2.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",
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.cts",
"sideEffects": false,
"files": [
"dist",
"README.md"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"scripts": {
"test": "vitest run",
"build": "tsc -p .",
"watch": "tsc -p . --watch",
"build": "tsdown",
"watch": "tsdown --watch",
"typecheck": "tsc --noEmit",
"lint:check": "eslint ./src",
"lint:fix": "eslint ./src --fix",
"format:fix": "prettier --write ./src",
"format:check": "prettier --check ./src",
"check:publint": "publint",
"check:attw": "attw --pack .",
"prepare": "pnpm run build",
"prepublishOnly": "pnpm run check:publint && pnpm run check:attw",
"ci": "pnpm run lint:check && pnpm run format:check"
},
"dependencies": {
"zod": "^4.0.0"
},
"peerDependencies": {
"@modelcontextprotocol/sdk": ">=1.0.0"
"@modelcontextprotocol/sdk": ">=1.0.0",
"zod": "^4.0.0"
},
"peerDependenciesMeta": {
"@modelcontextprotocol/sdk": {
Expand Down
2 changes: 1 addition & 1 deletion packages/lang-core/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"module": "ESNext",
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"noEmit": true,
"noPropertyAccessFromIndexSignature": false,
"noUncheckedIndexedAccess": false,
"noImplicitReturns": false,
Expand Down
14 changes: 14 additions & 0 deletions packages/lang-core/tsdown.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from "tsdown";

export default defineConfig({
entry: ["src/index.ts"],
format: ["esm", "cjs"],
dts: true,
sourcemap: true,
target: "es2022",
outDir: "dist",
clean: true,
deps: {
neverBundle: [/^[^./]/],
},
});
33 changes: 22 additions & 11 deletions packages/react-email/package.json
Original file line number Diff line number Diff line change
@@ -1,40 +1,51 @@
{
"name": "@openuidev/react-email",
"version": "0.1.3",
"version": "0.2.0",
"description": "React Email components for OpenUI — 44 email building blocks with defineComponent",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.cts",
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"files": [
"dist",
"README.md"
],
"scripts": {
"build": "tsc -p tsconfig.build.json",
"dev": "tsc -p tsconfig.build.json --watch",
"build": "tsdown",
"dev": "tsdown --watch",
"typecheck": "tsc --noEmit",
"lint": "eslint",
"lint:check": "eslint ./src",
"lint:fix": "eslint ./src --fix",
"format:fix": "prettier --write ./src",
"format:check": "prettier --check ./src",
"check:publint": "publint",
"check:attw": "attw --pack .",
"prepare": "pnpm run build",
"prepublishOnly": "pnpm run check:publint && pnpm run check:attw",
"ci": "pnpm run lint:check && pnpm run format:check"
},
"dependencies": {
"@react-email/components": "^0.0.41",
"zod": "^4.3.6"
"@react-email/components": "^0.0.41"
},
"peerDependencies": {
"@openuidev/react-lang": "workspace:*",
"react": ">=19.0.0",
"react-dom": ">=19.0.0"
"react-dom": ">=19.0.0",
"zod": "^4.3.6"
},
"devDependencies": {
"@types/react": "^19",
Expand Down
1 change: 0 additions & 1 deletion packages/react-email/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"lib": ["dom", "dom.iterable", "esnext"],
"module": "esnext",
"moduleResolution": "bundler",
"declaration": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
Expand Down
14 changes: 14 additions & 0 deletions packages/react-email/tsdown.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from "tsdown";

export default defineConfig({
entry: ["src/index.ts"],
format: ["esm", "cjs"],
dts: true,
sourcemap: true,
target: "es2022",
outDir: "dist",
clean: true,
deps: {
neverBundle: [/^[^./]/],
},
});
Loading
Loading