diff --git a/.gitignore b/.gitignore index d7452398e9..eef93622aa 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,7 @@ target/ # rslib .rslib + +# next +.next +.vercel diff --git a/eslint.config.js b/eslint.config.js index ea270aa317..2089ad4ebe 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -26,7 +26,9 @@ export default tseslint.config( // Outputs '**/.rslib/**', + '**/.next/**', '**/.turbo/**', + '**/.vercel/**', '**/coverage/**', 'output/**', 'target/**', @@ -74,6 +76,7 @@ export default tseslint.config( 'packages/react/transform/index.d.ts', 'packages/react/transform/index.cjs', 'packages/react/transform/**/index.d.ts', + 'packages/genui/server/next-env.d.ts', // REPL examples use Lynx platform globals and are not subject to lint rules 'packages/repl/src/examples/**', @@ -453,4 +456,13 @@ export default tseslint.config( 'headers/header-format': 'off', }, }, + { + files: [ + 'packages/genui/server/**/*.{ts,tsx}', + ], + rules: { + 'n/file-extension-in-import': 'off', + 'n/no-unsupported-features/node-builtins': 'off', + }, + }, ); diff --git a/packages/genui/a2ui-playground/lynx-src/a2ui/App.tsx b/packages/genui/a2ui-playground/lynx-src/a2ui/App.tsx index 5a4d6fd7a5..f968115c2a 100644 --- a/packages/genui/a2ui-playground/lynx-src/a2ui/App.tsx +++ b/packages/genui/a2ui-playground/lynx-src/a2ui/App.tsx @@ -15,9 +15,12 @@ import { Row, Text, createMessageStore, + normalizePayloadToMessages as normalizeProtocolMessages, } from '@lynx-js/a2ui-reactlynx'; import type { + CatalogComponent, CatalogInput, + CatalogManifest, MessageStore, ServerToClientMessage, UserActionPayload, @@ -57,18 +60,25 @@ const DEFAULT_STREAM_DELAY_MS = 800; // agent handshake. To include schemas, pair each component with its // `catalog.json` manifest โ€” see // `packages/genui/a2ui/src/catalog/README.md`. +function manifestEntry( + component: unknown, + manifest: CatalogManifest, +): readonly [CatalogComponent, CatalogManifest] { + return [component as CatalogComponent, manifest]; +} + const ALL_BUILTINS: readonly CatalogInput[] = [ - [Text, textManifest], - [Image, imageManifest], - [Row, rowManifest], - [Column, columnManifest], - [List, listManifest], - [Card, cardManifest], - [Button, buttonManifest], - [Divider, dividerManifest], - [Icon, iconManifest], - [CheckBox, checkBoxManifest], - [RadioGroup, radioGroupManifest], + manifestEntry(Text, textManifest), + manifestEntry(Image, imageManifest), + manifestEntry(Row, rowManifest), + manifestEntry(Column, columnManifest), + manifestEntry(List, listManifest), + manifestEntry(Card, cardManifest), + manifestEntry(Button, buttonManifest), + manifestEntry(Divider, dividerManifest), + manifestEntry(Icon, iconManifest), + manifestEntry(CheckBox, checkBoxManifest), + manifestEntry(RadioGroup, radioGroupManifest), ]; interface InitData { @@ -83,9 +93,6 @@ interface InitData { } type Theme = 'light' | 'dark'; - -type A2uiMessage = Record & { messageId?: string }; -type ResponseMessages = A2uiMessage[]; type ActionMocks = Record< string, | ServerToClientMessage[] @@ -184,40 +191,21 @@ function mergeInitDataPreferLeft(a: InitData, b: InitData): InitData { }; } -function normalizePayloadToMessages(payload: unknown): ResponseMessages { - if (payload === null || payload === undefined) return []; - if (Array.isArray(payload)) return payload as ResponseMessages; - if (typeof payload === 'string') { - try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const parsed = JSON.parse(payload); - return normalizePayloadToMessages(parsed); - } catch { - return []; - } - } - if ( - typeof payload === 'object' - && Array.isArray((payload as Record).messages) - ) { - return (payload as Record).messages as ResponseMessages; - } - return []; -} - -async function loadMessages(initData: InitData): Promise { +async function loadMessages( + initData: InitData, +): Promise { if (initData.messagesUrl) { // eslint-disable-next-line n/no-unsupported-features/node-builtins const res = await fetch(initData.messagesUrl, { cache: 'no-store' }); const text = await res.text(); try { - return normalizePayloadToMessages(JSON.parse(text)); + return normalizeProtocolMessages(JSON.parse(text)); } catch { - return normalizePayloadToMessages(text); + return normalizeProtocolMessages(text); } } if (initData.messages !== undefined) { - return normalizePayloadToMessages(initData.messages); + return normalizeProtocolMessages(initData.messages); } return []; } @@ -414,12 +402,10 @@ export function App() { loadActionMocks(streamConfig as InitData), ]); - const initialMessages = rawMessages as ServerToClientMessage[]; + const initialMessages = rawMessages; const actionMocks: ActionMocks = {}; for (const [name, value] of Object.entries(rawActionMocks)) { - actionMocks[name] = normalizePayloadToMessages( - value, - ) as ServerToClientMessage[]; + actionMocks[name] = normalizeProtocolMessages(value); } const next = createMessageStore(); diff --git a/packages/genui/a2ui-playground/src/components/MobilePreview.tsx b/packages/genui/a2ui-playground/src/components/MobilePreview.tsx index 4218d27d53..f22af51b0f 100644 --- a/packages/genui/a2ui-playground/src/components/MobilePreview.tsx +++ b/packages/genui/a2ui-playground/src/components/MobilePreview.tsx @@ -1,7 +1,15 @@ // 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 function MobilePreview(props: { src: string }) { +import type { ComponentPropsWithoutRef, Ref } from 'react'; + +interface MobilePreviewProps { + src: string; + iframeRef?: Ref; + onLoad?: ComponentPropsWithoutRef<'iframe'>['onLoad']; +} + +export function MobilePreview(props: MobilePreviewProps) { return (
@@ -10,7 +18,13 @@ export function MobilePreview(props: { src: string }) {
-