@lynx-js/genui 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/README.md +5 -9
  2. package/a2ui/AGENTS.md +167 -0
  3. package/a2ui/README.md +76 -780
  4. package/a2ui/README_zh.md +103 -0
  5. package/a2ui/dist/catalog/Button/catalog.json +2 -1
  6. package/a2ui/dist/catalog/Button/index.d.ts +5 -0
  7. package/a2ui/dist/catalog/Button/index.jsx +3 -0
  8. package/a2ui/dist/catalog/Button/index.jsx.map +1 -1
  9. package/a2ui/dist/catalog/Card/catalog.json +2 -1
  10. package/a2ui/dist/catalog/Card/index.d.ts +5 -0
  11. package/a2ui/dist/catalog/Card/index.jsx +3 -0
  12. package/a2ui/dist/catalog/Card/index.jsx.map +1 -1
  13. package/a2ui/dist/catalog/CheckBox/catalog.json +2 -1
  14. package/a2ui/dist/catalog/CheckBox/index.d.ts +5 -0
  15. package/a2ui/dist/catalog/CheckBox/index.jsx +3 -0
  16. package/a2ui/dist/catalog/CheckBox/index.jsx.map +1 -1
  17. package/a2ui/dist/catalog/ChoicePicker/catalog.json +2 -1
  18. package/a2ui/dist/catalog/ChoicePicker/index.d.ts +5 -0
  19. package/a2ui/dist/catalog/ChoicePicker/index.jsx +3 -0
  20. package/a2ui/dist/catalog/ChoicePicker/index.jsx.map +1 -1
  21. package/a2ui/dist/catalog/Column/catalog.json +2 -1
  22. package/a2ui/dist/catalog/Column/index.d.ts +5 -0
  23. package/a2ui/dist/catalog/Column/index.jsx +3 -0
  24. package/a2ui/dist/catalog/Column/index.jsx.map +1 -1
  25. package/a2ui/dist/catalog/DateTimeInput/catalog.json +2 -1
  26. package/a2ui/dist/catalog/DateTimeInput/index.d.ts +5 -0
  27. package/a2ui/dist/catalog/DateTimeInput/index.jsx +4 -10
  28. package/a2ui/dist/catalog/DateTimeInput/index.jsx.map +1 -1
  29. package/a2ui/dist/catalog/DateTimeInput/utils.d.ts +0 -1
  30. package/a2ui/dist/catalog/DateTimeInput/utils.js +0 -3
  31. package/a2ui/dist/catalog/DateTimeInput/utils.js.map +1 -1
  32. package/a2ui/dist/catalog/Divider/catalog.json +2 -1
  33. package/a2ui/dist/catalog/Divider/index.d.ts +5 -0
  34. package/a2ui/dist/catalog/Divider/index.jsx +3 -0
  35. package/a2ui/dist/catalog/Divider/index.jsx.map +1 -1
  36. package/a2ui/dist/catalog/Icon/catalog.json +2 -1
  37. package/a2ui/dist/catalog/Icon/index.d.ts +5 -0
  38. package/a2ui/dist/catalog/Icon/index.jsx +3 -0
  39. package/a2ui/dist/catalog/Icon/index.jsx.map +1 -1
  40. package/a2ui/dist/catalog/Image/catalog.json +2 -1
  41. package/a2ui/dist/catalog/Image/index.d.ts +5 -0
  42. package/a2ui/dist/catalog/Image/index.jsx +3 -0
  43. package/a2ui/dist/catalog/Image/index.jsx.map +1 -1
  44. package/a2ui/dist/catalog/LazyComponent/catalog.json +37 -0
  45. package/a2ui/dist/catalog/LazyComponent/index.d.ts +27 -0
  46. package/a2ui/dist/catalog/LazyComponent/index.jsx +39 -0
  47. package/a2ui/dist/catalog/LazyComponent/index.jsx.map +1 -0
  48. package/a2ui/dist/catalog/LineChart/catalog.json +2 -1
  49. package/a2ui/dist/catalog/LineChart/index.d.ts +8 -0
  50. package/a2ui/dist/catalog/LineChart/index.jsx +3 -0
  51. package/a2ui/dist/catalog/LineChart/index.jsx.map +1 -1
  52. package/a2ui/dist/catalog/List/catalog.json +2 -1
  53. package/a2ui/dist/catalog/List/index.d.ts +5 -0
  54. package/a2ui/dist/catalog/List/index.jsx +3 -0
  55. package/a2ui/dist/catalog/List/index.jsx.map +1 -1
  56. package/a2ui/dist/catalog/Loading/catalog.json +16 -0
  57. package/a2ui/dist/catalog/Loading/index.d.ts +15 -0
  58. package/a2ui/dist/catalog/Loading/index.jsx +14 -0
  59. package/a2ui/dist/catalog/Loading/index.jsx.map +1 -0
  60. package/a2ui/dist/catalog/Modal/catalog.json +2 -1
  61. package/a2ui/dist/catalog/Modal/index.d.ts +5 -0
  62. package/a2ui/dist/catalog/Modal/index.jsx +3 -0
  63. package/a2ui/dist/catalog/Modal/index.jsx.map +1 -1
  64. package/a2ui/dist/catalog/PieChart/catalog.json +2 -1
  65. package/a2ui/dist/catalog/PieChart/index.d.ts +8 -0
  66. package/a2ui/dist/catalog/PieChart/index.jsx +3 -0
  67. package/a2ui/dist/catalog/PieChart/index.jsx.map +1 -1
  68. package/a2ui/dist/catalog/RadioGroup/catalog.json +2 -1
  69. package/a2ui/dist/catalog/RadioGroup/index.d.ts +5 -0
  70. package/a2ui/dist/catalog/RadioGroup/index.jsx +3 -0
  71. package/a2ui/dist/catalog/RadioGroup/index.jsx.map +1 -1
  72. package/a2ui/dist/catalog/Row/catalog.json +2 -1
  73. package/a2ui/dist/catalog/Row/index.d.ts +5 -0
  74. package/a2ui/dist/catalog/Row/index.jsx +3 -0
  75. package/a2ui/dist/catalog/Row/index.jsx.map +1 -1
  76. package/a2ui/dist/catalog/Slider/catalog.json +2 -1
  77. package/a2ui/dist/catalog/Slider/index.d.ts +5 -0
  78. package/a2ui/dist/catalog/Slider/index.jsx +3 -0
  79. package/a2ui/dist/catalog/Slider/index.jsx.map +1 -1
  80. package/a2ui/dist/catalog/Tabs/catalog.json +2 -1
  81. package/a2ui/dist/catalog/Tabs/index.d.ts +5 -0
  82. package/a2ui/dist/catalog/Tabs/index.jsx +3 -0
  83. package/a2ui/dist/catalog/Tabs/index.jsx.map +1 -1
  84. package/a2ui/dist/catalog/Text/catalog.json +2 -1
  85. package/a2ui/dist/catalog/Text/index.d.ts +5 -0
  86. package/a2ui/dist/catalog/Text/index.jsx +3 -0
  87. package/a2ui/dist/catalog/Text/index.jsx.map +1 -1
  88. package/a2ui/dist/catalog/TextField/catalog.json +2 -1
  89. package/a2ui/dist/catalog/TextField/index.d.ts +5 -0
  90. package/a2ui/dist/catalog/TextField/index.jsx +3 -0
  91. package/a2ui/dist/catalog/TextField/index.jsx.map +1 -1
  92. package/a2ui/dist/catalog/defineCatalog.d.ts +4 -4
  93. package/a2ui/dist/catalog/defineCatalog.js.map +1 -1
  94. package/a2ui/dist/catalog/index.d.ts +2 -0
  95. package/a2ui/dist/catalog/index.js +92 -18
  96. package/a2ui/dist/catalog/index.js.map +1 -1
  97. package/a2ui/dist/catalog.json +2603 -0
  98. package/a2ui/dist/index.d.ts +1 -1
  99. package/a2ui/dist/index.js +2 -2
  100. package/a2ui/dist/index.js.map +1 -1
  101. package/a2ui/dist/react/A2UI.d.ts +3 -0
  102. package/a2ui/dist/react/A2UI.jsx.map +1 -1
  103. package/a2ui/dist/react/A2UIRenderer.d.ts +5 -18
  104. package/a2ui/dist/react/A2UIRenderer.jsx +3 -13
  105. package/a2ui/dist/react/A2UIRenderer.jsx.map +1 -1
  106. package/a2ui/dist/react/useAction.d.ts +7 -0
  107. package/a2ui/dist/react/useAction.js +4 -0
  108. package/a2ui/dist/react/useAction.js.map +1 -1
  109. package/a2ui/dist/react/useDataBinding.d.ts +7 -5
  110. package/a2ui/dist/react/useDataBinding.js +18 -0
  111. package/a2ui/dist/react/useDataBinding.js.map +1 -1
  112. package/a2ui/dist/store/FunctionRegistry.d.ts +7 -0
  113. package/a2ui/dist/store/FunctionRegistry.js +4 -0
  114. package/a2ui/dist/store/FunctionRegistry.js.map +1 -1
  115. package/a2ui/dist/store/MessageProcessor.d.ts +7 -0
  116. package/a2ui/dist/store/MessageProcessor.js +4 -0
  117. package/a2ui/dist/store/MessageProcessor.js.map +1 -1
  118. package/a2ui/dist/store/MessageStore.d.ts +6 -0
  119. package/a2ui/dist/store/MessageStore.js +3 -0
  120. package/a2ui/dist/store/MessageStore.js.map +1 -1
  121. package/a2ui/dist/store/Resource.d.ts +8 -0
  122. package/a2ui/dist/store/Resource.js +4 -0
  123. package/a2ui/dist/store/Resource.js.map +1 -1
  124. package/a2ui/dist/store/SignalStore.d.ts +3 -2
  125. package/a2ui/dist/store/SignalStore.js +9 -0
  126. package/a2ui/dist/store/SignalStore.js.map +1 -1
  127. package/a2ui/dist/store/resolveDynamic.d.ts +4 -3
  128. package/a2ui/dist/store/resolveDynamic.js +19 -0
  129. package/a2ui/dist/store/resolveDynamic.js.map +1 -1
  130. package/a2ui/dist/store/resolveFunctionCall.d.ts +7 -0
  131. package/a2ui/dist/store/resolveFunctionCall.js +4 -0
  132. package/a2ui/dist/store/resolveFunctionCall.js.map +1 -1
  133. package/a2ui/dist/store/types.d.ts +13 -0
  134. package/a2ui/dist/store/utils.d.ts +6 -4
  135. package/a2ui/dist/store/utils.js +26 -0
  136. package/a2ui/dist/store/utils.js.map +1 -1
  137. package/a2ui/dist/tsconfig.build.tsbuildinfo +1 -1
  138. package/a2ui/docs/catalog-guide.md +407 -0
  139. package/a2ui/docs/catalog-guide_zh.md +379 -0
  140. package/a2ui/docs/overview.md +312 -0
  141. package/a2ui/docs/overview_zh.md +289 -0
  142. package/a2ui/docs/system-prompts.md +187 -0
  143. package/a2ui/docs/system-prompts_zh.md +187 -0
  144. package/a2ui/src/catalog/README.md +12 -0
  145. package/a2ui/src/catalog/index.ts +52 -0
  146. package/a2ui/src/catalog/readme_zh.md +11 -0
  147. package/a2ui/src/index.ts +116 -0
  148. package/a2ui/styles/catalog/Button.css +5 -5
  149. package/a2ui/styles/catalog/DateTimeInput.css +22 -30
  150. package/a2ui/styles/catalog/Loading.css +61 -0
  151. package/a2ui-catalog-extractor/README.md +14 -7
  152. package/a2ui-catalog-extractor/dist/cli.d.ts +1 -0
  153. package/a2ui-catalog-extractor/dist/cli.js +15 -6
  154. package/a2ui-catalog-extractor/dist/cli.js.map +1 -1
  155. package/a2ui-catalog-extractor/dist/index.d.ts +97 -2
  156. package/a2ui-catalog-extractor/dist/index.js +91 -6
  157. package/a2ui-catalog-extractor/dist/index.js.map +1 -1
  158. package/a2ui-catalog-extractor/dist/tsconfig.build.tsbuildinfo +1 -1
  159. package/a2ui-catalog-extractor/skills/a2ui-catalog-extractor/SKILL.md +1 -1
  160. package/a2ui-prompt/README.md +3 -2
  161. package/a2ui-prompt/dist/index.d.ts +41 -0
  162. package/a2ui-prompt/dist/index.js +159 -84
  163. package/cli/README.md +26 -0
  164. package/cli/bin/cli.js +7 -265
  165. package/cli/dist/a2ui/create.d.ts +5 -0
  166. package/cli/dist/a2ui/create.js +178 -0
  167. package/cli/dist/a2ui/create.js.map +1 -0
  168. package/cli/dist/a2ui/index.d.ts +5 -0
  169. package/cli/dist/a2ui/index.js +170 -0
  170. package/cli/dist/a2ui/index.js.map +1 -0
  171. package/cli/dist/cli.d.ts +4 -0
  172. package/cli/dist/cli.js +40 -0
  173. package/cli/dist/cli.js.map +1 -0
  174. package/cli/dist/openui.d.ts +1 -0
  175. package/cli/dist/openui.js +21 -0
  176. package/cli/dist/openui.js.map +1 -0
  177. package/cli/dist/tsconfig.build.tsbuildinfo +1 -0
  178. package/cli/dist/utils.d.ts +2 -0
  179. package/cli/dist/utils.js +17 -0
  180. package/cli/dist/utils.js.map +1 -0
  181. package/cli/templates/default/lynx.config.ts +13 -0
  182. package/cli/templates/default/package.json +27 -0
  183. package/cli/templates/default/src/App.css +88 -0
  184. package/cli/templates/default/src/App.tsx +100 -0
  185. package/cli/templates/default/src/index.tsx +10 -0
  186. package/cli/templates/default/src/messages.ts +158 -0
  187. package/cli/templates/default/src/rspeedy-env.d.ts +14 -0
  188. package/cli/templates/default/src/tsconfig.json +17 -0
  189. package/cli/templates/default/tsconfig.json +15 -0
  190. package/cli/templates/default/tsconfig.node.json +16 -0
  191. package/dist/index.d.ts +2 -0
  192. package/dist/index.js +1 -0
  193. package/dist/index.js.map +1 -1
  194. package/dist/tsconfig.build.tsbuildinfo +1 -1
  195. package/index.ts +12 -0
  196. package/openui/README.md +50 -46
  197. package/openui/dist/catalog/Action/{index.js → index.jsx} +1 -1
  198. package/openui/dist/catalog/Action/index.jsx.map +1 -0
  199. package/openui/dist/catalog/Button/index.d.ts +8 -8
  200. package/openui/dist/catalog/Button/{index.js → index.jsx} +28 -14
  201. package/openui/dist/catalog/Button/index.jsx.map +1 -0
  202. package/openui/dist/catalog/Card/index.d.ts +1 -1
  203. package/openui/dist/catalog/Card/{index.js → index.jsx} +5 -4
  204. package/openui/dist/catalog/Card/{index.js.map → index.jsx.map} +1 -1
  205. package/openui/dist/catalog/CardHeader/index.d.ts +1 -1
  206. package/openui/dist/catalog/CardHeader/index.jsx +20 -0
  207. package/openui/dist/catalog/CardHeader/index.jsx.map +1 -0
  208. package/openui/dist/catalog/CheckBox/index.d.ts +16 -0
  209. package/openui/dist/catalog/CheckBox/index.jsx +82 -0
  210. package/openui/dist/catalog/CheckBox/index.jsx.map +1 -0
  211. package/openui/dist/catalog/Icon/index.d.ts +44 -0
  212. package/openui/dist/catalog/Icon/index.jsx +66 -0
  213. package/openui/dist/catalog/Icon/index.jsx.map +1 -0
  214. package/openui/dist/catalog/Image/index.d.ts +19 -0
  215. package/openui/dist/catalog/Image/index.jsx +40 -0
  216. package/openui/dist/catalog/Image/index.jsx.map +1 -0
  217. package/openui/dist/catalog/Loading/index.d.ts +7 -0
  218. package/openui/dist/catalog/Loading/index.jsx +25 -0
  219. package/openui/dist/catalog/Loading/index.jsx.map +1 -0
  220. package/openui/dist/catalog/RadioGroup/index.d.ts +21 -0
  221. package/openui/dist/catalog/RadioGroup/index.jsx +99 -0
  222. package/openui/dist/catalog/RadioGroup/index.jsx.map +1 -0
  223. package/openui/dist/catalog/Separator/index.d.ts +1 -1
  224. package/openui/dist/catalog/Separator/{index.js → index.jsx} +3 -4
  225. package/openui/dist/catalog/Separator/index.jsx.map +1 -0
  226. package/openui/dist/catalog/Slider/index.d.ts +19 -0
  227. package/openui/dist/catalog/Slider/index.jsx +139 -0
  228. package/openui/dist/catalog/Slider/index.jsx.map +1 -0
  229. package/openui/dist/catalog/Stack/index.d.ts +1 -1
  230. package/openui/dist/catalog/Stack/{index.js → index.jsx} +3 -4
  231. package/openui/dist/catalog/Stack/{index.js.map → index.jsx.map} +1 -1
  232. package/openui/dist/catalog/Tag/index.d.ts +1 -1
  233. package/openui/dist/catalog/Tag/{index.js → index.jsx} +5 -4
  234. package/openui/dist/catalog/Tag/index.jsx.map +1 -0
  235. package/openui/dist/catalog/TextContent/index.d.ts +1 -1
  236. package/openui/dist/catalog/TextContent/{index.js → index.jsx} +5 -4
  237. package/openui/dist/catalog/TextContent/{index.js.map → index.jsx.map} +1 -1
  238. package/openui/dist/catalog/TextField/index.d.ts +23 -0
  239. package/openui/dist/catalog/TextField/index.jsx +132 -0
  240. package/openui/dist/catalog/TextField/index.jsx.map +1 -0
  241. package/openui/dist/catalog/index.d.ts +14 -7
  242. package/openui/dist/catalog/index.js +14 -7
  243. package/openui/dist/catalog/index.js.map +1 -1
  244. package/openui/dist/core/context.d.ts +22 -8
  245. package/openui/dist/core/{context.js → context.jsx} +10 -3
  246. package/openui/dist/core/context.jsx.map +1 -0
  247. package/openui/dist/core/createLibrary.d.ts +8 -1
  248. package/openui/dist/core/{createLibrary.js → createLibrary.jsx} +18 -3
  249. package/openui/dist/core/createLibrary.jsx.map +1 -0
  250. package/openui/dist/core/hooks/index.d.ts +1 -0
  251. package/openui/dist/core/hooks/index.js +1 -0
  252. package/openui/dist/core/hooks/index.js.map +1 -1
  253. package/openui/dist/core/hooks/useFormValidation.d.ts +9 -0
  254. package/openui/dist/core/hooks/useFormValidation.js +6 -0
  255. package/openui/dist/core/hooks/useFormValidation.js.map +1 -1
  256. package/openui/dist/core/hooks/useOpenUIState.d.ts +8 -2
  257. package/openui/dist/core/hooks/useOpenUIState.js +3 -1
  258. package/openui/dist/core/hooks/useOpenUIState.js.map +1 -1
  259. package/openui/dist/core/hooks/useStateField.d.ts +3 -0
  260. package/openui/dist/core/hooks/useStateField.js +4 -1
  261. package/openui/dist/core/hooks/useStateField.js.map +1 -1
  262. package/openui/dist/core/index.d.ts +13 -7
  263. package/openui/dist/core/index.js +7 -4
  264. package/openui/dist/core/index.js.map +1 -1
  265. package/openui/dist/core/library.d.ts +6 -1
  266. package/openui/dist/core/{library.js → library.jsx} +9 -1
  267. package/openui/dist/core/library.jsx.map +1 -0
  268. package/openui/dist/core/renderer.css +527 -0
  269. package/openui/dist/core/renderer.d.ts +41 -4
  270. package/openui/dist/core/renderer.jsx +284 -0
  271. package/openui/dist/core/renderer.jsx.map +1 -0
  272. package/openui/dist/core/runtime/index.d.ts +1 -0
  273. package/openui/dist/core/runtime/index.js +5 -0
  274. package/openui/dist/core/runtime/index.js.map +1 -0
  275. package/openui/dist/core/runtime/reactive.d.ts +7 -0
  276. package/openui/dist/core/runtime/reactive.js +10 -0
  277. package/openui/dist/core/runtime/reactive.js.map +1 -0
  278. package/openui/dist/openui-prompt/index.d.ts +47 -0
  279. package/openui/dist/openui-prompt/index.js +321 -0
  280. package/openui/dist/openui-prompt/index.js.map +1 -0
  281. package/package.json +20 -8
  282. package/openui/dist/catalog/Action/index.js.map +0 -1
  283. package/openui/dist/catalog/Button/index.js.map +0 -1
  284. package/openui/dist/catalog/CardHeader/index.js +0 -18
  285. package/openui/dist/catalog/CardHeader/index.js.map +0 -1
  286. package/openui/dist/catalog/Separator/index.js.map +0 -1
  287. package/openui/dist/catalog/Tag/index.js.map +0 -1
  288. package/openui/dist/core/context.js.map +0 -1
  289. package/openui/dist/core/createLibrary.js.map +0 -1
  290. package/openui/dist/core/library.js.map +0 -1
  291. package/openui/dist/core/renderer.js +0 -139
  292. package/openui/dist/core/renderer.js.map +0 -1
package/a2ui/README.md CHANGED
@@ -2,816 +2,112 @@
2
2
 
3
3
  English | [简体中文](./README_zh.md)
4
4
 
5
- Lynx GenUI is the generated-UI stack for developers who already know React and
6
- want AI to assemble native Lynx interfaces from trusted components.
5
+ `@lynx-js/genui/a2ui` is the ReactLynx client runtime for A2UI v0.9. It
6
+ consumes validated A2UI server-to-client JSON messages and renders trusted
7
+ ReactLynx components in your app.
7
8
 
8
- If you have never heard of A2UI, think of it this way:
9
+ Use this package when you already have, or plan to build, an Agent service that
10
+ returns A2UI messages. The package does not host an Agent, call an LLM, own a
11
+ backend route, or provide a chat shell. Your app owns the transport layer and
12
+ pushes messages into the renderer.
9
13
 
10
- - In React, your code chooses components and passes props.
11
- - In GenUI, an agent chooses from a component catalog that you publish.
12
- - The client still renders real ReactLynx components. The model only sends
13
- data that says which approved component to render and what props to use.
14
-
15
- A2UI is the message protocol in the middle. It is not a replacement for React,
16
- and it is not a new styling system. It is a safe, JSON-based way for an agent to
17
- say: create a surface, render these components, update this data, and report
18
- this user action back to the agent.
19
-
20
- ## Why It Exists
14
+ If you have never used A2UI before, think of it this way:
21
15
 
22
- Generated UI becomes useful when it has product constraints:
23
-
24
- - The agent can only use components your app has registered.
25
- - Component props are described with TypeScript-derived schemas.
26
- - Model output is validated before the client renders it.
27
- - UI can stream in progressively instead of waiting for one giant response.
28
- - User actions are sent back as structured events, similar to React event
29
- handlers crossing a network boundary.
16
+ - In React, your code chooses components and passes props.
17
+ - In A2UI, an Agent chooses from a component catalog that your app publishes.
18
+ - The client still renders real ReactLynx components. The model only sends data
19
+ that says which approved component to render and what props to use.
30
20
 
31
21
  The result is not arbitrary generated code. It is a ReactLynx UI tree assembled
32
22
  from a trusted catalog.
33
23
 
34
- ## From React To GenUI
35
-
36
- Here is the React mental model:
37
-
38
- ```tsx
39
- function WeatherCard(props: WeatherCardProps) {
40
- return (
41
- <Card>
42
- <Text>{props.city}</Text>
43
- <Text>{props.temperature}</Text>
44
- <Button onClick={props.onRefresh}>Refresh</Button>
45
- </Card>
46
- );
47
- }
48
- ```
49
-
50
- Here is the GenUI mental model:
51
-
52
- 1. You publish `Card`, `Text`, `Button`, and any custom components into a
53
- catalog.
54
- 2. The agent receives the user's request and the catalog description.
55
- 3. The agent emits A2UI messages such as "render a Card with these children".
56
- 4. The client pushes those messages into a `MessageStore`.
57
- 5. `<A2UI>` renders the matching ReactLynx components.
58
- 6. When a user taps a generated button, `onAction` fires and your app sends the
59
- action back to the agent.
60
-
61
- The model never imports your code. It only names components that the renderer
62
- has already allowed.
63
-
64
- ## What You Use
65
-
66
- For a product app using A2UI, the important surfaces are:
67
-
68
- | Surface | Role |
69
- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
70
- | `@lynx-js/genui/a2ui` | ReactLynx renderer for A2UI v0.9. It provides `<A2UI>`, `MessageStore`, catalog APIs, built-in components, and protocol helpers. |
71
- | `genui a2ui` | Build-time command for generating catalog artifacts from TypeScript contracts and A2UI system prompts for your agent. |
72
- | Your agent service | A backend you own. It receives user prompts/actions, calls a model with the A2UI prompt and catalog, validates output, and returns A2UI. |
73
- | Your transport implementation | Client code that calls your agent service, handles REST or streaming responses, pushes messages into `MessageStore`, and forwards actions. |
74
-
75
- ## The Three Pieces
76
-
77
- ```text
78
- Catalog: what can be rendered
79
- -> Agent: what should be rendered
80
- -> Client: render it and send actions back
81
- ```
82
-
83
- ### Catalog
84
-
85
- For a React developer, the catalog is your public component API for AI. It is
86
- the generated-UI equivalent of exporting a component plus its prop types.
87
-
88
- The catalog tells the agent:
89
-
90
- - Component names, such as `Text`, `Column`, `ProductTile`.
91
- - Prop names and types.
92
- - Required fields.
93
- - Allowed enum values.
94
- - Optional functions for dynamic formatting and validation.
95
-
96
- The catalog tells the client:
24
+ ## Install
97
25
 
98
- - Which ReactLynx component to instantiate for each A2UI component name.
99
- - Which component names are safe to render.
100
-
101
- ### Agent
102
-
103
- The agent is a UI planner. It receives normal chat messages, reads the catalog,
104
- and returns A2UI JSON messages. Your backend should validate those messages
105
- before returning them to the client.
106
-
107
- The important product rule is: the agent designs within your catalog. If a
108
- component is not in the catalog, it should not appear in the generated UI.
109
-
110
- ### Client
111
-
112
- The client owns transport and rendering. It fetches messages from the agent,
113
- pushes them into `MessageStore`, renders `<A2UI>`, and forwards generated user
114
- actions back to your backend.
115
-
116
- If you know `useSyncExternalStore`, the `MessageStore` idea should feel
117
- familiar: it is an append-only external store of protocol messages. `<A2UI>`
118
- subscribes to it and updates the rendered surface as messages arrive.
119
-
120
- ## Quickstart
121
-
122
- In your ReactLynx app, install the GenUI package. The CLI requires Node.js 22
123
- or newer and is exposed by the same package.
26
+ Install the published GenUI package in a ReactLynx app:
124
27
 
125
28
  ```sh
126
- pnpm add @lynx-js/genui
127
- genui a2ui --help
29
+ pnpm add @lynx-js/genui @lynx-js/react
128
30
  ```
129
31
 
130
- The rest of the flow is app-local: define catalog-facing component contracts,
131
- generate catalog artifacts, give the generated prompt to your agent service,
132
- and render validated A2UI messages in your ReactLynx client.
133
-
134
- ### 1. Catalog: Turn React Components Into Agent-Visible Components
32
+ Import the optional default theme tokens once if you want to use the built-in
33
+ light/dark CSS variables:
135
34
 
136
- Start with a component contract. This is the part React developers already do
137
- well: name the props and keep the component predictable.
138
-
139
- ```tsx
140
- /**
141
- * Product tile for commerce recommendations.
142
- *
143
- * @a2uiCatalog ProductTile
144
- */
145
- export interface ProductTileProps {
146
- /** Product name shown as the title. */
147
- title: string;
148
- /** Price text already localized by the caller. */
149
- price: string;
150
- /** Image search query or resolved URL. */
151
- imageUrl?: string;
152
- }
153
-
154
- export function ProductTile(props: ProductTileProps) {
155
- return (
156
- <view className='product-tile'>
157
- {props.imageUrl ? <image src={props.imageUrl} /> : null}
158
- <text>{props.title}</text>
159
- <text>{props.price}</text>
160
- </view>
161
- );
162
- }
163
-
164
- ProductTile.displayName = 'ProductTile';
165
- ```
166
-
167
- Generate a schema for the agent:
168
-
169
- ```sh
170
- genui a2ui generate catalog --catalog-dir src/catalog --out-dir dist/catalog
171
- ```
172
-
173
- Then pair the component with its manifest:
174
-
175
- ```tsx
176
- import {
177
- Button,
178
- Column,
179
- Text,
180
- createMessageStore,
181
- defineCatalog,
182
- serializeCatalog,
183
- } from '@lynx-js/genui/a2ui';
184
- import buttonManifest from '@lynx-js/genui/a2ui/catalog/Button/catalog.json'
185
- with { type: 'json' };
186
- import columnManifest from '@lynx-js/genui/a2ui/catalog/Column/catalog.json'
187
- with { type: 'json' };
188
- import textManifest from '@lynx-js/genui/a2ui/catalog/Text/catalog.json'
189
- with { type: 'json' };
190
- import productTileManifest from './dist/catalog/ProductTile/catalog.json'
191
- with { type: 'json' };
192
-
193
- export const uiCatalog = defineCatalog([
194
- [Text, textManifest],
195
- [Column, columnManifest],
196
- [Button, buttonManifest],
197
- [ProductTile, productTileManifest],
198
- ]);
199
-
200
- export const catalogHandshake = serializeCatalog(uiCatalog);
201
- export const store = createMessageStore();
202
- ```
203
-
204
- Use `catalogHandshake` when your own transport or agent consumes the client
205
- handshake format. If your agent uses a different internal catalog format to
206
- build prompts, add an explicit backend conversion step so the agent sees the
207
- same component names that the client has registered.
208
-
209
- There is intentionally no exported "all built-ins" constant. Importing every
210
- component makes bundle cost invisible and weakens tree-shaking. Import only the
211
- built-in components and catalog manifests your generated UI should be allowed to
212
- use.
213
-
214
- Production note: minifiers can rewrite function names. Set
215
- `ProductTile.displayName = 'ProductTile'` or pair custom components with their
216
- manifest so the protocol name stays stable.
217
-
218
- ### 2. CLI: Generate Catalogs And Prompts
219
-
220
- The CLI is the build-time bridge between React source code and the agent. Use
221
- it when you want repeatable artifacts instead of hand-maintained JSON:
222
-
223
- - `generate catalog` reads TypeScript catalog contracts and writes
224
- `dist/catalog/<Component>/catalog.json`.
225
- - `generate prompt` reads generated catalog artifacts and writes an A2UI system
226
- prompt for an agent.
227
-
228
- Run the published CLI package through `npx`:
229
-
230
- ```sh
231
- genui a2ui generate catalog \
232
- --catalog-dir src/catalog \
233
- --source src/functions \
234
- --out-dir dist/catalog
235
-
236
- genui a2ui generate prompt \
237
- --catalog-dir dist/catalog \
238
- --catalog-id https://example.com/catalogs/custom/v1/catalog.json \
239
- --out dist/a2ui-system-prompt.txt
240
- ```
241
-
242
- When your build already produces TypeDoc JSON, keep the same
243
- `genui a2ui` command prefix and pass that file to `generate catalog`:
244
-
245
- ```sh
246
- genui a2ui generate catalog \
247
- --typedoc-json typedoc.json \
248
- --out-dir dist/catalog
249
- ```
250
-
251
- Key options:
252
-
253
- | Option | Use |
254
- | ----------------------- | ---------------------------------------------------------------------------- |
255
- | `--catalog-dir <dir>` | Scan catalog component interfaces, or read generated artifacts for prompts. |
256
- | `--source <path>` | Add source files or directories, commonly for catalog functions. Repeatable. |
257
- | `--typedoc-json <file>` | Reuse an existing TypeDoc JSON project instead of running TypeDoc. |
258
- | `--out-dir <dir>` | Write generated catalog artifacts. Defaults to `dist/catalog`. |
259
- | `--catalog-id <id>` | Set the catalog id expected in generated `createSurface` messages. |
260
- | `--out <file>` | Write the generated prompt to a file instead of stdout. |
261
- | `--appendix <text>` | Add extra agent instructions to the generated prompt. |
262
-
263
- Catalog authoring details:
264
-
265
- - Put `@a2uiCatalog` on the props `interface`, not on the component function.
266
- You can pass the component name explicitly, such as `@a2uiCatalog ProductTile`.
267
- If the tag is empty, the generator infers the name by removing a trailing
268
- `Props` or `ComponentProps` from the interface name.
269
- - TypeDoc comments become schema metadata: summary text and `@remarks` become
270
- `description`, `@defaultValue` or `@default` become `default`,
271
- `@deprecated` becomes `deprecated: true`, and optional properties are omitted
272
- from `required`. For object or array defaults, put the JSON value inside a
273
- code span, such as `` @defaultValue `{}` ``.
274
- - Supported prop types include `string`, `number`, `boolean`, string literal
275
- enums, unions, arrays, inline object types, and `Record<string, T>`.
276
- - Avoid `any`, `unknown`, `null`, `undefined`, `never`, `void`, nullable
277
- unions, most imported aliases, referenced external interfaces, and
278
- non-string `Record` keys. Inline the agent-facing fields directly in the
279
- marked interface.
280
- - The scanner accepts `.ts`, `.tsx`, `.js`, `.jsx`, `.mts`, and `.cts` files.
281
- It ignores `.d.ts`, `node_modules`, `dist`, and `.turbo`.
282
-
283
- Operational notes:
284
-
285
- - Keep generated catalog artifacts in your package build output and commit API
286
- reports or generated manifests when the package contract expects them.
287
- - Regenerate catalog artifacts whenever a catalog-facing props interface or
288
- `@a2uiFunction` definition changes.
289
- - `generate prompt` uses the built-in A2UI basic catalog when `--catalog-dir`
290
- is omitted; pass `--catalog-dir` for custom generated catalogs.
291
- - The generated prompt and the client catalog must describe the same component
292
- names and props. A mismatch can pass server validation but render as
293
- unsupported on the client.
294
- - `functions` and `theme` are not inferred from component props. Add them
295
- explicitly through generated function definitions or prompt/catalog helpers.
296
-
297
- Keep the generated prompt with your backend code and the generated catalog
298
- artifacts with your app package so agent and client deployments stay in sync.
299
-
300
- ### 3. Agent: Ask For UI, Receive Validated Messages
301
-
302
- Your agent service is a backend route in your product, not browser code. It
303
- should:
304
-
305
- - Load the A2UI system prompt generated by `genui a2ui generate
306
- prompt`.
307
- - Add conversation history, user intent, and any product state the model needs.
308
- - Call your model provider from the server.
309
- - Validate or repair model output before returning A2UI messages to the client.
310
- - Keep provider credentials, base URLs, and model selection out of untrusted
311
- browser requests.
312
-
313
- A typical request shape looks like this:
314
-
315
- ```sh
316
- curl https://your-domain.example/api/a2ui/chat \
317
- -H 'Content-Type: application/json' \
318
- -d '{
319
- "messages": [
320
- {
321
- "role": "user",
322
- "content": "Create a compact weather card with a photo, temperature, humidity, and a Refresh button."
323
- }
324
- ]
325
- }'
326
- ```
327
-
328
- The response contains `messages`. Those are not React elements. They are data
329
- instructions that the client renderer can process.
330
-
331
- A tiny A2UI response looks like this:
332
-
333
- ```json
334
- [
335
- {
336
- "version": "v0.9",
337
- "createSurface": {
338
- "surfaceId": "main",
339
- "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json"
340
- }
341
- },
342
- {
343
- "version": "v0.9",
344
- "updateComponents": {
345
- "surfaceId": "main",
346
- "components": [
347
- {
348
- "id": "root",
349
- "component": "Column",
350
- "children": ["title"]
351
- },
352
- {
353
- "id": "title",
354
- "component": "Text",
355
- "text": "Hello from generated UI"
356
- }
357
- ]
358
- }
359
- }
360
- ]
35
+ ```ts
36
+ import '@lynx-js/genui/a2ui/styles/theme.css';
361
37
  ```
362
38
 
363
- You do not need to hand-write this JSON for normal app development. It is
364
- useful to recognize the structure when debugging.
365
-
366
- Important endpoints:
367
-
368
- | Endpoint | Use |
369
- | ------------------------------ | ----------------------------------------------------------------------------------- |
370
- | `GET /api/a2ui/health` | Optional health/configuration check for your backend. |
371
- | `POST /api/a2ui/chat` | Return one validated JSON response. |
372
- | `POST /api/a2ui/stream` | Stream model deltas as SSE, then emit validated messages in the final `done` event. |
373
- | `POST /api/a2ui/action` | Convert a client action into the next validated A2UI response. |
374
- | `POST /api/a2ui/action/stream` | Stream an action response and final validation payload. |
375
-
376
- Common server-side configuration:
377
-
378
- | Variable | Purpose |
379
- | ------------------------- | ------------------------------------------------------------------------ |
380
- | `OPENAI_API_KEY` | Model credential kept on the server. |
381
- | `OPENAI_MODEL` | Model id chosen by your backend. |
382
- | `OPENAI_BASE_URL` | Optional OpenAI-compatible endpoint. |
383
- | `OPENAI_API_STYLE` | `responses` or `chat`, depending on your provider integration. |
384
- | `IMAGE_PROVIDER_API_KEY` | Optional image provider credential if your agent resolves image queries. |
385
- | `A2UI_CORS_ORIGINS` | Comma-separated browser origins allowed by your server. |
386
- | `A2UI_RATE_LIMIT_PER_MIN` | Per-client request limit for your server. |
387
-
388
- ### 4. Client: Render Messages Like React State
39
+ ## Quick Start
389
40
 
390
- The client fetches agent output and pushes each message into the store.
391
- `<A2UI>` does the protocol processing and renders the matching ReactLynx
392
- components.
41
+ Create a `MessageStore`, register the components that generated UI is allowed
42
+ to render, and forward generated actions back to your Agent service.
393
43
 
394
44
  ```tsx
395
45
  import {
396
46
  A2UI,
397
47
  Button,
398
- Column,
399
48
  Text,
49
+ basicFunctions,
400
50
  createMessageStore,
51
+ normalizePayloadToMessages,
401
52
  } from '@lynx-js/genui/a2ui';
402
- import type { UserActionPayload } from '@lynx-js/genui/a2ui';
403
53
 
404
54
  const store = createMessageStore();
405
- const catalogs = [Text, Column, Button];
55
+ const catalogs = [Text, Button, ...basicFunctions];
406
56
 
407
- async function sendPrompt(content: string) {
408
- const response = await fetch('/api/a2ui/chat', {
57
+ async function sendPrompt(input: string) {
58
+ const res = await fetch('/a2ui/chat', {
409
59
  method: 'POST',
410
60
  headers: { 'Content-Type': 'application/json' },
411
- body: JSON.stringify({
412
- messages: [{ role: 'user', content }],
413
- }),
61
+ body: JSON.stringify({ messages: [{ role: 'user', content: input }] }),
414
62
  });
415
- const body = await response.json();
416
- for (const message of body.messages ?? []) {
417
- store.push(message);
418
- }
419
- }
420
-
421
- async function sendAction(action: UserActionPayload) {
422
- const response = await fetch('/api/a2ui/action', {
423
- method: 'POST',
424
- headers: { 'Content-Type': 'application/json' },
425
- body: JSON.stringify({
426
- surfaceId: action.surfaceId,
427
- action,
428
- }),
429
- });
430
- const body = await response.json();
431
- for (const message of body.messages ?? []) {
432
- store.push(message);
433
- }
434
- }
435
-
436
- export function GeneratedUIScreen(): import('@lynx-js/react').ReactNode {
437
- return (
438
- <A2UI
439
- messageStore={store}
440
- catalogs={catalogs}
441
- onAction={(action) => {
442
- void sendAction(action);
443
- }}
444
- wrapSurface={(children) => <view className='a2ui-light'>{children}</view>}
445
- />
446
- );
447
- }
448
- ```
449
-
450
- Map this back to React:
451
-
452
- - `MessageStore` is the external state source.
453
- - `store.push(message)` is like receiving the next state update from the
454
- server.
455
- - `catalogs` is the allowlist of components the generated tree may use.
456
- - `onAction` is like an event handler, except the event is serialized and sent
457
- back to the agent.
458
- - Passing a new React `key` to `<A2UI>` starts a fresh renderer session.
459
-
460
- ## Transport Layer
461
-
462
- GenUI does not prescribe one transport. The protocol messages can travel over
463
- REST, SSE, WebSocket, A2A, AG UI, MCP, or an in-process mock. In a React app,
464
- the transport layer is the adapter between your product state and
465
- `MessageStore`.
466
-
467
- It owns:
468
-
469
- - Calling the agent endpoint.
470
- - Passing conversation history and data-model snapshots.
471
- - Parsing JSON or streaming SSE responses.
472
- - Pushing validated A2UI messages into the store in order.
473
- - Forwarding `onAction` payloads back to the agent.
474
- - Cancelling stale requests and surfacing errors.
475
-
476
- It should not own:
477
-
478
- - Rendering A2UI components directly.
479
- - Mutating the generated component tree by hand.
480
- - Trusting arbitrary prose from the model as UI.
481
- - Letting browser clients override provider credentials in production.
482
-
483
- ### Interface Best Practices
484
-
485
- Keep the transport small and explicit:
486
-
487
- ```ts
488
- import type { MessageStore, UserActionPayload } from '@lynx-js/genui/a2ui';
489
-
490
- interface ConversationContext {
491
- history: Array<{ role: 'user' | 'assistant' | 'system'; content: string }>;
492
- dataModel: Record<string, unknown>;
493
- }
494
-
495
- interface A2UITransport {
496
- generate(input: {
497
- prompt: string;
498
- conversation?: ConversationContext;
499
- signal?: AbortSignal;
500
- }): Promise<unknown[]>;
501
- respondToAction(input: {
502
- surfaceId: string;
503
- action: UserActionPayload;
504
- conversation?: ConversationContext;
505
- signal?: AbortSignal;
506
- }): Promise<unknown[]>;
507
- }
508
-
509
- async function applyMessages(
510
- store: MessageStore,
511
- messages: unknown[],
512
- ): Promise<void> {
513
- for (const message of messages) {
514
- store.push(message);
515
- }
516
- }
517
- ```
518
-
519
- This keeps generated UI as data until the last step. The renderer remains the
520
- only place that interprets A2UI messages.
521
-
522
- ### REST Baseline
523
-
524
- Use routes such as `/api/a2ui/chat` and `/api/a2ui/action` when you want a
525
- simple request/response implementation:
526
-
527
- ```ts
528
- function extractMessages(payload: unknown): unknown[] {
529
- if (Array.isArray(payload)) return payload;
530
- if (typeof payload === 'string') {
531
- try {
532
- return extractMessages(JSON.parse(payload));
533
- } catch {
534
- return [];
535
- }
536
- }
537
- if (!payload || typeof payload !== 'object') return [];
538
-
539
- const record = payload as {
540
- messages?: unknown;
541
- validation?: { messages?: unknown };
542
- text?: unknown;
543
- };
544
- if (Array.isArray(record.messages)) return record.messages;
545
- if (Array.isArray(record.validation?.messages)) {
546
- return record.validation.messages;
547
- }
548
- if (typeof record.text === 'string') return extractMessages(record.text);
549
- return [];
550
- }
551
-
552
- async function postA2UI(
553
- url: string,
554
- body: unknown,
555
- signal?: AbortSignal,
556
- ): Promise<unknown[]> {
557
- const response = await fetch(url, {
558
- method: 'POST',
559
- headers: { 'Content-Type': 'application/json' },
560
- body: JSON.stringify(body),
561
- signal,
562
- });
563
-
564
- const payload = await response.json().catch(() => ({}));
565
- if (!response.ok) {
566
- throw new Error(`A2UI request failed: ${response.status}`);
567
- }
568
-
569
- const messages = extractMessages(payload);
570
- if (messages.length === 0) {
571
- throw new Error('A2UI response did not include renderable messages');
572
- }
573
- return messages;
574
- }
575
- ```
576
-
577
- Then wire it to the renderer:
578
-
579
- ```ts
580
- async function generate(prompt: string, signal?: AbortSignal) {
581
- const messages = await postA2UI(
582
- '/api/a2ui/chat',
583
- { messages: [{ role: 'user', content: prompt }] },
584
- signal,
585
- );
586
- await applyMessages(store, messages);
587
- }
588
-
589
- async function respondToAction(
590
- action: UserActionPayload,
591
- signal?: AbortSignal,
592
- ) {
593
- const messages = await postA2UI(
594
- '/api/a2ui/action',
595
- { surfaceId: action.surfaceId, action },
596
- signal,
597
- );
598
- await applyMessages(store, messages);
599
- }
600
- ```
601
-
602
- ### SSE Streaming
603
-
604
- Use routes such as `/api/a2ui/stream` and `/api/a2ui/action/stream` when you
605
- want to show generation progress. The server emits:
606
-
607
- - `delta`: raw model text, useful for an inspector or loading state.
608
- - `repair`: optional metadata when the server had to repair invalid model
609
- output.
610
- - `done`: the final validated payload. Use the messages from this event for
611
- rendering.
612
- - `error`: structured failure payload.
613
-
614
- ```ts
615
- interface SseFrame {
616
- event: string;
617
- data: unknown;
618
- }
619
-
620
- function parseSseFrame(frame: string): SseFrame | null {
621
- const lines = frame.split(/\r?\n/u);
622
- let event = 'message';
623
- const dataLines: string[] = [];
624
-
625
- for (const line of lines) {
626
- if (line.startsWith('event:')) {
627
- event = line.slice('event:'.length).trim();
628
- } else if (line.startsWith('data:')) {
629
- dataLines.push(line.slice('data:'.length).trimStart());
630
- }
631
- }
632
-
633
- if (dataLines.length === 0) return null;
634
- const raw = dataLines.join('\n');
635
- try {
636
- return { event, data: JSON.parse(raw) };
637
- } catch {
638
- return { event, data: raw };
639
- }
640
- }
641
-
642
- async function readA2UISse(
643
- response: Response,
644
- onDelta?: (text: string) => void,
645
- ): Promise<unknown[]> {
646
- const reader = response.body?.getReader();
647
- if (!reader) return [];
648
-
649
- const decoder = new TextDecoder();
650
- let buffer = '';
651
- let generatedText = '';
652
-
653
- while (true) {
654
- const { done, value } = await reader.read();
655
- buffer += decoder.decode(value, { stream: !done });
656
-
657
- const frames = buffer.split(/\r?\n\r?\n/u);
658
- buffer = frames.pop() ?? '';
659
-
660
- for (const frame of frames) {
661
- const parsed = parseSseFrame(frame);
662
- if (!parsed) continue;
663
-
664
- if (parsed.event === 'delta') {
665
- const text = (parsed.data as { text?: unknown }).text;
666
- if (typeof text === 'string') {
667
- generatedText += text;
668
- onDelta?.(generatedText);
669
- }
670
- continue;
671
- }
672
-
673
- if (parsed.event === 'done') {
674
- const messages = extractMessages(parsed.data);
675
- if (messages.length === 0) {
676
- throw new Error('A2UI stream finished without renderable messages');
677
- }
678
- return messages;
679
- }
680
-
681
- if (parsed.event === 'error') {
682
- throw new Error(JSON.stringify(parsed.data));
683
- }
684
- }
685
-
686
- if (done) break;
687
- }
688
63
 
689
- return extractMessages(generatedText);
690
- }
64
+ store.push(normalizePayloadToMessages(await res.json()));
65
+ }
66
+
67
+ <A2UI
68
+ messageStore={store}
69
+ catalogs={catalogs}
70
+ wrapSurface={(children) => <view className='a2ui-light'>{children}</view>}
71
+ onAction={(action) => {
72
+ void fetch('/a2ui/action', {
73
+ method: 'POST',
74
+ headers: { 'Content-Type': 'application/json' },
75
+ body: JSON.stringify(action),
76
+ })
77
+ .then((res) => res.json())
78
+ .then((payload) => store.push(normalizePayloadToMessages(payload)));
79
+ }}
80
+ />;
691
81
  ```
692
82
 
693
- Avoid rendering every `delta` as A2UI. During streaming, the model text may be
694
- an incomplete JSON array. Render from the final `done` event by default. If you
695
- choose partial rendering, only publish complete parsed message objects and
696
- replace them with the final validated messages when `done` arrives.
697
-
698
- ## Operational Best Practices
699
-
700
- - Keep one active generation per conversation surface. Abort or ignore older
701
- requests when a new prompt starts.
702
- - Use a separate `AbortController` for user actions. An old action response
703
- should not update the UI after a newer action has started.
704
- - Render from `done.validation.messages` or `messages`. Treat `delta` as
705
- progress text for inspectors and loading states.
706
- - Push messages into `MessageStore` in server order. Do not sort, merge, or
707
- deduplicate them unless you understand the protocol consequences.
708
- - Keep conversation history and the current data-model snapshot outside
709
- `MessageStore`; include them in the next agent request when you need coherent
710
- multi-turn updates.
711
- - Send action requests with both `surfaceId` and the full `action` payload.
712
- Action responses normally update the existing surface rather than creating a
713
- new one.
714
- - Normalize all supported response formats: direct arrays, `{ messages }`,
715
- `{ validation: { messages } }`, and stringified JSON.
716
- - Check `content-type`. Your endpoints may return JSON or `text/event-stream`
717
- depending on the route.
718
- - Parse non-2xx responses as structured JSON when possible, then fall back to a
719
- status-based error.
720
- - Keep endpoint allowlists strict. The hosted Playground should only talk to
721
- trusted GenUI endpoints.
722
- - Do not pass model API keys, base URLs, or model ids from a browser in
723
- production. Keep provider selection and credentials on the server.
724
- - Configure CORS and rate limits on the server before exposing the agent to
725
- browsers.
726
- - Version your catalog contract. The agent catalog and client catalog must
727
- agree on component names and props, or validated output may still render as
728
- unsupported on the client.
729
- - Use deterministic mocks for tests. A transport can be an in-process async
730
- generator that pushes known A2UI messages into the store.
731
-
732
- Common mistakes:
733
-
734
- - Rendering raw model prose instead of validated A2UI messages.
735
- - Reusing one `MessageStore` for unrelated conversations without remounting
736
- `<A2UI key={...}>`.
737
- - Dropping `conversation.dataModel`, which makes follow-up actions lose state.
738
- - Retrying non-idempotent actions automatically, which can apply the same user
739
- intent twice.
740
- - Allowing generated image URLs, remote endpoints, or provider overrides from
741
- untrusted browser input.
742
-
743
- ## Try The Playground
744
-
745
- The hosted playground is the fastest way to see the whole loop before
746
- integrating it into an app:
747
-
748
- [https://lynx-stack.dev/a2ui/](https://lynx-stack.dev/a2ui/)
749
-
750
- Use the hosted page to try the demos, inspect generated A2UI JSON, browse the
751
- catalog, and preview Lynx surfaces.
752
-
753
- Use the playground to:
754
-
755
- - Describe UI in natural language and inspect the generated A2UI JSON.
756
- - Browse the component catalog like a React component library.
757
- - Preview the generated Lynx surface.
758
- - Test action flows such as submit, refresh, and selection.
759
- - Generate preview URLs and QR codes for native Lynx testing.
760
-
761
- ## Glossary For React Developers
762
-
763
- | GenUI term | React-friendly meaning |
764
- | ------------------ | -------------------------------------------------------------------------------------- |
765
- | A2UI | JSON messages that describe UI changes. Similar to a serialized, constrained UI tree. |
766
- | Surface | A generated UI root, similar to a mounted app region. |
767
- | Catalog | The approved component library and prop schema exposed to the agent. |
768
- | `MessageStore` | Append-only external store that receives protocol messages. |
769
- | `updateComponents` | "Render these component instances with these props." |
770
- | `updateDataModel` | "Patch the data used by bound props." Similar to remote state updates. |
771
- | Action | A generated UI event, similar to `onClick`, sent back to the agent as structured data. |
772
-
773
- ## Protocol Notes
774
-
775
- The current A2UI path targets A2UI v0.9.
776
-
777
- - The model must output a raw JSON array, not Markdown.
778
- - A fresh response starts with `createSurface`, followed by
779
- `updateComponents` containing a `root` component.
780
- - Components form a flat graph. Children are referenced by id rather than
781
- inlined.
782
- - Data bindings use JSON Pointer paths and must be populated by
783
- `updateDataModel`.
784
- - Interactive components emit action payloads. The client posts those actions
785
- to the agent, and the agent returns update messages for the existing surface.
786
-
787
- ## Testing And Quality
788
-
789
- Use your app's normal test runner. The high-value checks are:
790
-
791
- - Unit-test catalog registration so every component name in generated messages
792
- maps to the ReactLynx component you expect.
793
- - Unit-test transport parsing with deterministic JSON and SSE fixtures,
794
- including malformed responses and aborts.
795
- - Replay saved A2UI message arrays through `<A2UI>` so renderer regressions are
796
- visible without calling a model.
797
- - E2E-test one prompt flow and one action flow with mocked agent responses
798
- before adding model-backed tests.
799
-
800
- ## Product Direction
801
-
802
- GenUI is designed around a few commitments:
803
-
804
- - React remains the implementation layer. The agent chooses from components you
805
- own.
806
- - The catalog is the product contract. It keeps generated UI aligned with your
807
- design system and platform constraints.
808
- - Progressive rendering should make the UI useful before a turn fully
809
- completes.
810
- - Transports are replaceable. REST, SSE, WebSocket, A2A, AG UI, or MCP can all
811
- carry the same A2UI messages.
812
- - Generated UI should be inspectable, replayable, and judgeable in automated
813
- workflows.
814
-
815
- Start with the hosted Playground, then generate a small catalog in your app and
816
- wire one prompt route plus one action route before expanding to richer
817
- components.
83
+ `MessageStore` stores raw protocol messages in arrival order. `<A2UI>`
84
+ subscribes to it, processes new messages, renders the active surface, and emits
85
+ generated UI actions through `onAction`.
86
+
87
+ ## What You Own
88
+
89
+ | Part | Owner | Role |
90
+ | ------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------ |
91
+ | `@lynx-js/genui/a2ui` | This package | ReactLynx renderer, `MessageStore`, catalog APIs, built-in components, protocol helpers, and client function entries. |
92
+ | `npx @lynx-js/genui a2ui` | GenUI CLI | Build-time commands for generating custom catalog artifacts and A2UI system prompts. |
93
+ | Your Agent service | Your application | Receives user prompts/actions, calls a model with the A2UI prompt and catalog, validates output, and returns messages. |
94
+ | Your transport adapter | Your application | Calls the Agent service, handles REST or streaming responses, writes messages into `MessageStore`, and forwards actions. |
95
+
96
+ ## First Things To Know
97
+
98
+ - Pass only the components you want generated UI to use through `catalogs`.
99
+ - Include `...basicFunctions` when messages may use A2UI function calls such as
100
+ `formatString`, `formatDate`, `required`, `email`, or `and`.
101
+ - Pair components with their `catalog.json` manifests when you need
102
+ `serializeCatalog(...)` to send JSON schemas to an Agent.
103
+ - There is intentionally no `@lynx-js/genui/a2ui/catalog/all` export. Compose
104
+ the exact catalog you want at the integration site so bundle cost is visible.
105
+ - For a new turn or session, mount `<A2UI>` with a different `key`; the
106
+ component owns its `MessageProcessor` for the lifetime of the mount.
107
+
108
+ ## More Docs
109
+
110
+ - [Overview and architecture](./docs/overview.md)
111
+ - [Catalogs, built-ins, and custom components](./docs/catalog-guide.md)
112
+ - [System prompts](./docs/system-prompts.md)
113
+ - [Open the A2UI playground](https://lynxjs.org/a2ui)