Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions .github/genui-tsconfig.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
applyTo: "packages/genui/**/*"
---

When adding a new package under `packages/genui`, add a package-level `tsconfig.json` and wire it into `packages/genui/tsconfig.json` references in the same change. Root type-aware ESLint uses the TypeScript project service, so new GenUI source files that are not reachable from those references will fail pre-commit parsing before code linting even starts.
1 change: 1 addition & 0 deletions biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

"packages/tools/**",
"packages/genui/a2ui/tools/**",
"packages/genui/openui/**",

"packages/use-sync-external-store/test/**",

Expand Down
85 changes: 85 additions & 0 deletions packages/genui/a2ui-playground/lynx-src/openui/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2026 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
import {
OpenUiRenderer,
createOpenUiLibrary,
createStreamingParser,
} from '@lynx-js/openui-reactlynx';
import type { ActionEvent, ParseResult } from '@lynx-js/openui-reactlynx';
import { useCallback, useEffect, useMemo, useState } from '@lynx-js/react';

import { OPENUI_SCENARIOS } from './mockData.js';

const mockData = OPENUI_SCENARIOS[0].raw;

export function App() {
const openUiLibrary = useMemo(() => createOpenUiLibrary(), []);

const [parseResult, setParseResult] = useState<ParseResult | null>(null);
const [error, setError] = useState<string>('');
const [loading, setLoading] = useState<boolean>(false);

useEffect(() => {
let cancelled = false;
setLoading(true);
setError('');

try {
const streamParser = createStreamingParser(openUiLibrary.toJSONSchema());
const result = streamParser.push(mockData);
if (!cancelled) {
setParseResult(result);
}
} catch (e) {
if (!cancelled) {
setError(String(e));
}
} finally {
if (!cancelled) {
setLoading(false);
}
}

return () => {
cancelled = true;
};
}, [openUiLibrary]);

const onOpenUiAction = useCallback((_event: ActionEvent) => {
// noop for now
}, []);

return (
<view style={{ width: '100%', height: '100%', backgroundColor: '#fff' }}>
{error
? (
<view style={{ padding: '12px' }}>
<text style={{ color: '#c40000' }}>{error}</text>
</view>
)
: null}

{loading
? (
<view style={{ padding: '12px' }}>
<text>Loading...</text>
</view>
)
: null}

{parseResult?.root
? (
<scroll-view scroll-y style={{ height: '100%' }}>
<OpenUiRenderer
result={parseResult}
library={openUiLibrary}
onAction={onOpenUiAction}
isStreaming={false}
/>
</scroll-view>
)
: null}
</view>
);
}
4 changes: 4 additions & 0 deletions packages/genui/a2ui-playground/lynx-src/openui/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
page {
padding: 10px;
box-sizing: border-box;
}
9 changes: 9 additions & 0 deletions packages/genui/a2ui-playground/lynx-src/openui/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2026 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
import { root } from '@lynx-js/react';

import { App } from './App.jsx';
import './index.css';

root.render(<App />);
62 changes: 62 additions & 0 deletions packages/genui/a2ui-playground/lynx-src/openui/mockData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2026 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.

export interface OpenUIScenario {
id: string;
title: string;
raw: string;
}

export const OPENUI_SCENARIOS: OpenUIScenario[] = [
{
id: 'pricing-cards',
title: 'Pricing Cards',
raw: `root = Stack([header, cards], "column", "l", "center")
header = TextContent("Choose Your Plan", "large-heavy")
cards = Stack([freeCard, proCard, enterpriseCard], "row", "l", "stretch")

freeCard = Card([freeHeader, freePrice, freeSep, freeFeatures, freeBtn], "card", "column", "stretch", "between")
freeHeader = CardHeader("Free", "For individuals just getting started")
freePrice = Stack([freePriceAmt, freePricePer], "row", "none", "baseline")
freePriceAmt = TextContent("$0", "large-heavy")
freePricePer = TextContent(" / month", "small")
freeSep = Separator("horizontal", true)
freeFeatures = Stack([ff1, ff2, ff3, ff4], "column", "s")
ff1 = TextContent("✓ 1 user")
ff2 = TextContent("✓ 5 projects")
ff3 = TextContent("✓ 2 GB storage")
ff4 = TextContent("✗ Priority support")
freeBtn = Buttons([Button("Get Started", Action([@ToAssistant("Get started with Free plan")]), "secondary")])

proCard = Card([proHeader, proPrice, proBadge, proSep, proFeatures, proBtn], "card", "column", "stretch", "between")
proHeader = CardHeader("Pro", "For growing teams and professionals")
proPrice = Stack([proPriceAmt, proPricePer], "row", "none", "baseline")
proPriceAmt = TextContent("$29", "large-heavy")
proPricePer = TextContent(" / month", "small")
proBadge = Tag("Most Popular", null, "sm", "info")
proSep = Separator("horizontal", true)
proFeatures = Stack([pf1, pf2, pf3, pf4, pf5], "column", "s")
pf1 = TextContent("✓ Up to 10 users")
pf2 = TextContent("✓ Unlimited projects")
pf3 = TextContent("✓ 50 GB storage")
pf4 = TextContent("✓ Priority support")
pf5 = TextContent("✓ Advanced analytics")
proBtn = Buttons([Button("Start Free Trial", Action([@ToAssistant("Start free trial with Pro plan")]), "primary")])

enterpriseCard = Card([entHeader, entPrice, entSep, entFeatures, entBtn], "card", "column", "stretch", "between")
entHeader = CardHeader("Enterprise", "For large organizations at scale")
entPrice = Stack([entPriceAmt, entPricePer], "row", "none", "baseline")
entPriceAmt = TextContent("$99", "large-heavy")
entPricePer = TextContent(" / month", "small")
entSep = Separator("horizontal", true)
entFeatures = Stack([ef1, ef2, ef3, ef4, ef5, ef6], "column", "s")
ef1 = TextContent("✓ Unlimited users")
ef2 = TextContent("✓ Unlimited projects")
ef3 = TextContent("✓ 1 TB storage")
ef4 = TextContent("✓ 24/7 dedicated support")
ef5 = TextContent("✓ Advanced analytics")
ef6 = TextContent("✓ Custom integrations")
entBtn = Buttons([Button("Contact Sales", Action([@ToAssistant("Contact sales for Enterprise plan")]), "secondary")])`,
},
];
1 change: 1 addition & 0 deletions packages/genui/a2ui-playground/lynx.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default defineConfig({
source: {
entry: {
a2ui: './lynx-src/a2ui/index.tsx',
openui: './lynx-src/openui/index.tsx',
},
},
environments: {
Expand Down
1 change: 1 addition & 0 deletions packages/genui/a2ui-playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@codemirror/lang-json": "^6.0.2",
"@lynx-js/a2ui-reactlynx": "workspace:*",
"@lynx-js/lynx-core": "0.1.3",
"@lynx-js/openui-reactlynx": "workspace:*",
"@lynx-js/react": "workspace:*",
"@lynx-js/web-core": "workspace:*",
"@lynx-js/web-elements": "workspace:*",
Expand Down
Loading
Loading