@lynx-js/genui 0.0.1 → 0.0.3

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 (236) 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/{index.js → index.jsx} +12 -8
  6. package/a2ui/dist/catalog/Button/index.jsx.map +1 -0
  7. package/a2ui/dist/catalog/Card/{index.js → index.jsx} +5 -4
  8. package/a2ui/dist/catalog/Card/index.jsx.map +1 -0
  9. package/a2ui/dist/catalog/CheckBox/{index.js → index.jsx} +11 -6
  10. package/a2ui/dist/catalog/CheckBox/index.jsx.map +1 -0
  11. package/a2ui/dist/catalog/ChoicePicker/index.jsx +98 -0
  12. package/a2ui/dist/catalog/ChoicePicker/index.jsx.map +1 -0
  13. package/a2ui/dist/catalog/Column/{index.js → index.jsx} +10 -7
  14. package/a2ui/dist/catalog/Column/{index.js.map → index.jsx.map} +1 -1
  15. package/a2ui/dist/catalog/DateTimeInput/index.jsx +249 -0
  16. package/a2ui/dist/catalog/DateTimeInput/index.jsx.map +1 -0
  17. package/a2ui/dist/catalog/DateTimeInput/utils.d.ts +0 -1
  18. package/a2ui/dist/catalog/DateTimeInput/utils.js +0 -3
  19. package/a2ui/dist/catalog/DateTimeInput/utils.js.map +1 -1
  20. package/a2ui/dist/catalog/Divider/index.jsx +7 -0
  21. package/a2ui/dist/catalog/Divider/index.jsx.map +1 -0
  22. package/a2ui/dist/catalog/Icon/{index.js → index.jsx} +4 -3
  23. package/a2ui/dist/catalog/Icon/{index.js.map → index.jsx.map} +1 -1
  24. package/a2ui/dist/catalog/Image/{index.js → index.jsx} +2 -3
  25. package/a2ui/dist/catalog/Image/{index.js.map → index.jsx.map} +1 -1
  26. package/a2ui/dist/catalog/LineChart/{index.js → index.jsx} +50 -13
  27. package/a2ui/dist/catalog/LineChart/{index.js.map → index.jsx.map} +1 -1
  28. package/a2ui/dist/catalog/List/{index.js → index.jsx} +9 -6
  29. package/a2ui/dist/catalog/List/{index.js.map → index.jsx.map} +1 -1
  30. package/a2ui/dist/catalog/Loading/catalog.json +15 -0
  31. package/a2ui/dist/catalog/Loading/index.d.ts +10 -0
  32. package/a2ui/dist/catalog/Loading/index.jsx +11 -0
  33. package/a2ui/dist/catalog/Loading/index.jsx.map +1 -0
  34. package/a2ui/dist/catalog/Modal/{index.js → index.jsx} +18 -9
  35. package/a2ui/dist/catalog/Modal/index.jsx.map +1 -0
  36. package/a2ui/dist/catalog/PieChart/{index.js → index.jsx} +41 -15
  37. package/a2ui/dist/catalog/PieChart/{index.js.map → index.jsx.map} +1 -1
  38. package/a2ui/dist/catalog/RadioGroup/{index.js → index.jsx} +18 -5
  39. package/a2ui/dist/catalog/RadioGroup/index.jsx.map +1 -0
  40. package/a2ui/dist/catalog/Row/{index.js → index.jsx} +10 -7
  41. package/a2ui/dist/catalog/Row/index.jsx.map +1 -0
  42. package/a2ui/dist/catalog/Slider/{index.js → index.jsx} +22 -7
  43. package/a2ui/dist/catalog/Slider/{index.js.map → index.jsx.map} +1 -1
  44. package/a2ui/dist/catalog/Tabs/index.jsx +40 -0
  45. package/a2ui/dist/catalog/Tabs/index.jsx.map +1 -0
  46. package/a2ui/dist/catalog/Text/catalog.json +6 -2
  47. package/a2ui/dist/catalog/Text/index.d.ts +1 -1
  48. package/a2ui/dist/catalog/Text/index.jsx +16 -0
  49. package/a2ui/dist/catalog/Text/index.jsx.map +1 -0
  50. package/a2ui/dist/catalog/TextField/{index.js → index.jsx} +8 -5
  51. package/a2ui/dist/catalog/TextField/{index.js.map → index.jsx.map} +1 -1
  52. package/a2ui/dist/catalog/defineCatalog.d.ts +0 -4
  53. package/a2ui/dist/catalog/defineCatalog.js.map +1 -1
  54. package/a2ui/dist/catalog/index.d.ts +20 -19
  55. package/a2ui/dist/catalog/index.js +41 -21
  56. package/a2ui/dist/catalog/index.js.map +1 -1
  57. package/a2ui/dist/catalog.json +2548 -0
  58. package/a2ui/dist/index.d.ts +1 -1
  59. package/a2ui/dist/index.js +2 -2
  60. package/a2ui/dist/index.js.map +1 -1
  61. package/a2ui/dist/react/A2UI.d.ts +1 -1
  62. package/a2ui/dist/react/{A2UI.js → A2UI.jsx} +6 -5
  63. package/a2ui/dist/react/{A2UI.js.map → A2UI.jsx.map} +1 -1
  64. package/a2ui/dist/react/{A2UIProvider.js → A2UIProvider.jsx} +2 -3
  65. package/a2ui/dist/react/{A2UIProvider.js.map → A2UIProvider.jsx.map} +1 -1
  66. package/a2ui/dist/react/{A2UIRenderer.js → A2UIRenderer.jsx} +32 -32
  67. package/a2ui/dist/react/A2UIRenderer.jsx.map +1 -0
  68. package/a2ui/dist/react/index.d.ts +3 -3
  69. package/a2ui/dist/react/index.js +2 -2
  70. package/a2ui/dist/react/index.js.map +1 -1
  71. package/a2ui/dist/react/useA2UIContext.d.ts +1 -1
  72. package/a2ui/dist/react/useA2UIContext.js +1 -1
  73. package/a2ui/dist/react/useA2UIContext.js.map +1 -1
  74. package/a2ui/dist/tsconfig.build.tsbuildinfo +1 -1
  75. package/a2ui/docs/catalog-guide.md +401 -0
  76. package/a2ui/docs/catalog-guide_zh.md +373 -0
  77. package/a2ui/docs/overview.md +312 -0
  78. package/a2ui/docs/overview_zh.md +289 -0
  79. package/a2ui/docs/system-prompts.md +187 -0
  80. package/a2ui/docs/system-prompts_zh.md +187 -0
  81. package/a2ui/src/catalog/README.md +12 -0
  82. package/a2ui/src/catalog/index.ts +51 -0
  83. package/a2ui/src/catalog/readme_zh.md +11 -0
  84. package/a2ui/src/index.ts +115 -0
  85. package/a2ui/styles/catalog/Button.css +5 -5
  86. package/a2ui/styles/catalog/DateTimeInput.css +22 -30
  87. package/a2ui/styles/catalog/Loading.css +61 -0
  88. package/a2ui/styles/catalog/Modal.css +1 -0
  89. package/a2ui/styles/catalog/Text.css +2 -6
  90. package/a2ui-catalog-extractor/README.md +14 -7
  91. package/a2ui-catalog-extractor/dist/cli.d.ts +1 -0
  92. package/a2ui-catalog-extractor/dist/cli.js +15 -6
  93. package/a2ui-catalog-extractor/dist/cli.js.map +1 -1
  94. package/a2ui-catalog-extractor/dist/index.d.ts +9 -2
  95. package/a2ui-catalog-extractor/dist/index.js +52 -6
  96. package/a2ui-catalog-extractor/dist/index.js.map +1 -1
  97. package/a2ui-catalog-extractor/dist/tsconfig.build.tsbuildinfo +1 -1
  98. package/a2ui-catalog-extractor/skills/a2ui-catalog-extractor/SKILL.md +1 -1
  99. package/a2ui-prompt/README.md +3 -2
  100. package/a2ui-prompt/dist/index.d.ts +2 -0
  101. package/a2ui-prompt/dist/index.js +259 -184
  102. package/cli/README.md +26 -0
  103. package/cli/bin/cli.js +7 -265
  104. package/cli/dist/a2ui/create.d.ts +5 -0
  105. package/cli/dist/a2ui/create.js +178 -0
  106. package/cli/dist/a2ui/create.js.map +1 -0
  107. package/cli/dist/a2ui/index.d.ts +5 -0
  108. package/cli/dist/a2ui/index.js +170 -0
  109. package/cli/dist/a2ui/index.js.map +1 -0
  110. package/cli/dist/cli.d.ts +4 -0
  111. package/cli/dist/cli.js +40 -0
  112. package/cli/dist/cli.js.map +1 -0
  113. package/cli/dist/openui.d.ts +1 -0
  114. package/cli/dist/openui.js +21 -0
  115. package/cli/dist/openui.js.map +1 -0
  116. package/cli/dist/tsconfig.build.tsbuildinfo +1 -0
  117. package/cli/dist/utils.d.ts +2 -0
  118. package/cli/dist/utils.js +17 -0
  119. package/cli/dist/utils.js.map +1 -0
  120. package/cli/templates/default/lynx.config.ts +13 -0
  121. package/cli/templates/default/package.json +27 -0
  122. package/cli/templates/default/src/App.css +88 -0
  123. package/cli/templates/default/src/App.tsx +100 -0
  124. package/cli/templates/default/src/index.tsx +10 -0
  125. package/cli/templates/default/src/messages.ts +158 -0
  126. package/cli/templates/default/src/rspeedy-env.d.ts +14 -0
  127. package/cli/templates/default/src/tsconfig.json +17 -0
  128. package/cli/templates/default/tsconfig.json +15 -0
  129. package/cli/templates/default/tsconfig.node.json +16 -0
  130. package/dist/tsconfig.build.tsbuildinfo +1 -1
  131. package/openui/README.md +50 -46
  132. package/openui/dist/catalog/Action/{index.js → index.jsx} +1 -1
  133. package/openui/dist/catalog/Action/index.jsx.map +1 -0
  134. package/openui/dist/catalog/Button/index.d.ts +8 -8
  135. package/openui/dist/catalog/Button/{index.js → index.jsx} +28 -14
  136. package/openui/dist/catalog/Button/index.jsx.map +1 -0
  137. package/openui/dist/catalog/Card/index.d.ts +1 -1
  138. package/openui/dist/catalog/Card/{index.js → index.jsx} +5 -4
  139. package/openui/dist/catalog/Card/{index.js.map → index.jsx.map} +1 -1
  140. package/openui/dist/catalog/CardHeader/index.d.ts +1 -1
  141. package/openui/dist/catalog/CardHeader/index.jsx +20 -0
  142. package/openui/dist/catalog/CardHeader/index.jsx.map +1 -0
  143. package/openui/dist/catalog/CheckBox/index.d.ts +16 -0
  144. package/openui/dist/catalog/CheckBox/index.jsx +82 -0
  145. package/openui/dist/catalog/CheckBox/index.jsx.map +1 -0
  146. package/openui/dist/catalog/Icon/index.d.ts +44 -0
  147. package/openui/dist/catalog/Icon/index.jsx +66 -0
  148. package/openui/dist/catalog/Icon/index.jsx.map +1 -0
  149. package/openui/dist/catalog/Image/index.d.ts +19 -0
  150. package/openui/dist/catalog/Image/index.jsx +40 -0
  151. package/openui/dist/catalog/Image/index.jsx.map +1 -0
  152. package/openui/dist/catalog/Loading/index.d.ts +7 -0
  153. package/openui/dist/catalog/Loading/index.jsx +25 -0
  154. package/openui/dist/catalog/Loading/index.jsx.map +1 -0
  155. package/openui/dist/catalog/RadioGroup/index.d.ts +21 -0
  156. package/openui/dist/catalog/RadioGroup/index.jsx +99 -0
  157. package/openui/dist/catalog/RadioGroup/index.jsx.map +1 -0
  158. package/openui/dist/catalog/Separator/index.d.ts +1 -1
  159. package/openui/dist/catalog/Separator/{index.js → index.jsx} +3 -4
  160. package/openui/dist/catalog/Separator/index.jsx.map +1 -0
  161. package/openui/dist/catalog/Slider/index.d.ts +19 -0
  162. package/openui/dist/catalog/Slider/index.jsx +139 -0
  163. package/openui/dist/catalog/Slider/index.jsx.map +1 -0
  164. package/openui/dist/catalog/Stack/index.d.ts +1 -1
  165. package/openui/dist/catalog/Stack/{index.js → index.jsx} +3 -4
  166. package/openui/dist/catalog/Stack/{index.js.map → index.jsx.map} +1 -1
  167. package/openui/dist/catalog/Tag/index.d.ts +1 -1
  168. package/openui/dist/catalog/Tag/{index.js → index.jsx} +5 -4
  169. package/openui/dist/catalog/Tag/index.jsx.map +1 -0
  170. package/openui/dist/catalog/TextContent/index.d.ts +1 -1
  171. package/openui/dist/catalog/TextContent/{index.js → index.jsx} +5 -4
  172. package/openui/dist/catalog/TextContent/{index.js.map → index.jsx.map} +1 -1
  173. package/openui/dist/catalog/TextField/index.d.ts +23 -0
  174. package/openui/dist/catalog/TextField/index.jsx +132 -0
  175. package/openui/dist/catalog/TextField/index.jsx.map +1 -0
  176. package/openui/dist/catalog/index.d.ts +14 -7
  177. package/openui/dist/catalog/index.js +14 -7
  178. package/openui/dist/catalog/index.js.map +1 -1
  179. package/openui/dist/core/context.d.ts +17 -7
  180. package/openui/dist/core/{context.js → context.jsx} +8 -2
  181. package/openui/dist/core/context.jsx.map +1 -0
  182. package/openui/dist/core/createLibrary.d.ts +1 -1
  183. package/openui/dist/core/{createLibrary.js → createLibrary.jsx} +14 -3
  184. package/openui/dist/core/createLibrary.jsx.map +1 -0
  185. package/openui/dist/core/hooks/index.d.ts +1 -0
  186. package/openui/dist/core/hooks/index.js +1 -0
  187. package/openui/dist/core/hooks/index.js.map +1 -1
  188. package/openui/dist/core/hooks/useOpenUIState.d.ts +2 -2
  189. package/openui/dist/core/hooks/useOpenUIState.js +3 -1
  190. package/openui/dist/core/hooks/useOpenUIState.js.map +1 -1
  191. package/openui/dist/core/hooks/useStateField.js +1 -1
  192. package/openui/dist/core/hooks/useStateField.js.map +1 -1
  193. package/openui/dist/core/index.d.ts +13 -7
  194. package/openui/dist/core/index.js +7 -4
  195. package/openui/dist/core/index.js.map +1 -1
  196. package/openui/dist/core/{library.js → library.jsx} +1 -1
  197. package/openui/dist/core/library.jsx.map +1 -0
  198. package/openui/dist/core/renderer.css +527 -0
  199. package/openui/dist/core/renderer.d.ts +31 -4
  200. package/openui/dist/core/renderer.jsx +281 -0
  201. package/openui/dist/core/renderer.jsx.map +1 -0
  202. package/openui/dist/core/runtime/index.d.ts +1 -0
  203. package/openui/dist/core/runtime/index.js +5 -0
  204. package/openui/dist/core/runtime/index.js.map +1 -0
  205. package/openui/dist/core/runtime/reactive.d.ts +7 -0
  206. package/openui/dist/core/runtime/reactive.js +10 -0
  207. package/openui/dist/core/runtime/reactive.js.map +1 -0
  208. package/package.json +18 -8
  209. package/a2ui/dist/catalog/Button/index.js.map +0 -1
  210. package/a2ui/dist/catalog/Card/index.js.map +0 -1
  211. package/a2ui/dist/catalog/CheckBox/index.js.map +0 -1
  212. package/a2ui/dist/catalog/ChoicePicker/index.js +0 -66
  213. package/a2ui/dist/catalog/ChoicePicker/index.js.map +0 -1
  214. package/a2ui/dist/catalog/DateTimeInput/index.js +0 -147
  215. package/a2ui/dist/catalog/DateTimeInput/index.js.map +0 -1
  216. package/a2ui/dist/catalog/Divider/index.js +0 -8
  217. package/a2ui/dist/catalog/Divider/index.js.map +0 -1
  218. package/a2ui/dist/catalog/Modal/index.js.map +0 -1
  219. package/a2ui/dist/catalog/RadioGroup/index.js.map +0 -1
  220. package/a2ui/dist/catalog/Row/index.js.map +0 -1
  221. package/a2ui/dist/catalog/Tabs/index.js +0 -32
  222. package/a2ui/dist/catalog/Tabs/index.js.map +0 -1
  223. package/a2ui/dist/catalog/Text/index.js +0 -27
  224. package/a2ui/dist/catalog/Text/index.js.map +0 -1
  225. package/a2ui/dist/react/A2UIRenderer.js.map +0 -1
  226. package/openui/dist/catalog/Action/index.js.map +0 -1
  227. package/openui/dist/catalog/Button/index.js.map +0 -1
  228. package/openui/dist/catalog/CardHeader/index.js +0 -18
  229. package/openui/dist/catalog/CardHeader/index.js.map +0 -1
  230. package/openui/dist/catalog/Separator/index.js.map +0 -1
  231. package/openui/dist/catalog/Tag/index.js.map +0 -1
  232. package/openui/dist/core/context.js.map +0 -1
  233. package/openui/dist/core/createLibrary.js.map +0 -1
  234. package/openui/dist/core/library.js.map +0 -1
  235. package/openui/dist/core/renderer.js +0 -139
  236. package/openui/dist/core/renderer.js.map +0 -1
@@ -0,0 +1,312 @@
1
+ # Overview and architecture
2
+
3
+ This page explains what `@lynx-js/genui/a2ui` is, the mental model behind
4
+ it, and how a server message becomes a rendered UI on the client. It
5
+ opens with a runnable [quick start](#quick-start), then works through
6
+ the architecture so you understand which part of the stack owns each
7
+ responsibility — and why the package is shaped the way it is.
8
+
9
+ ## What this package is
10
+
11
+ `@lynx-js/genui/a2ui` is the ReactLynx **client runtime** for the A2UI
12
+ v0.9 protocol. It consumes validated server-to-client JSON messages and
13
+ renders trusted ReactLynx components inside your app.
14
+
15
+ It is deliberately a renderer and nothing more. The package does **not**:
16
+
17
+ - host an Agent or call an LLM,
18
+ - own a backend route or chat shell,
19
+ - decide _what_ to render — that is the Agent's job.
20
+
21
+ Your app owns the transport layer and pushes messages into the renderer.
22
+ Use this package when you already have, or plan to build, an Agent service
23
+ that returns A2UI messages.
24
+
25
+ ## Quick start
26
+
27
+ Install the package in a ReactLynx app, then render a `MessageStore` with
28
+ `<A2UI>`. Your transport writes the Agent's messages into the store; the
29
+ renderer turns them into UI and hands user actions back through `onAction`.
30
+
31
+ ```sh
32
+ pnpm add @lynx-js/genui @lynx-js/react
33
+ ```
34
+
35
+ ```tsx
36
+ import {
37
+ A2UI,
38
+ basicFunctions,
39
+ Button,
40
+ createMessageStore,
41
+ normalizePayloadToMessages,
42
+ Text,
43
+ } from '@lynx-js/genui/a2ui';
44
+
45
+ // 1. A buffer your transport writes raw protocol messages into.
46
+ const store = createMessageStore();
47
+
48
+ // 2. The components and functions generated UI is allowed to use.
49
+ const catalogs = [Text, Button, ...basicFunctions];
50
+
51
+ // 3. Send a prompt and push the Agent's reply into the store.
52
+ async function sendPrompt(input: string) {
53
+ const res = await fetch('/a2ui/chat', {
54
+ method: 'POST',
55
+ headers: { 'Content-Type': 'application/json' },
56
+ body: JSON.stringify({ messages: [{ role: 'user', content: input }] }),
57
+ });
58
+ store.push(normalizePayloadToMessages(await res.json()));
59
+ }
60
+
61
+ // 4. Render. onAction round-trips user taps back to the Agent.
62
+ <A2UI
63
+ messageStore={store}
64
+ catalogs={catalogs}
65
+ onAction={(action) => {
66
+ void fetch('/a2ui/action', {
67
+ method: 'POST',
68
+ headers: { 'Content-Type': 'application/json' },
69
+ body: JSON.stringify(action),
70
+ })
71
+ .then((res) => res.json())
72
+ .then((payload) => store.push(normalizePayloadToMessages(payload)));
73
+ }}
74
+ />;
75
+ ```
76
+
77
+ That is the entire client loop: **push messages in, render, send actions
78
+ out.** The transport can be REST, SSE, WebSocket, or an in-process mock —
79
+ the renderer does not care. The rest of this page explains what happens
80
+ between `store.push(...)` and the rendered surface. For install details and
81
+ optional theme tokens, see the [README](../README.md).
82
+
83
+ ## The mental model
84
+
85
+ If you have written React, the shift is small but important:
86
+
87
+ - In **React**, your code chooses components and passes props.
88
+ - In **A2UI**, an _Agent_ chooses from a component catalog that _your app_
89
+ publishes, and sends data describing which approved component to render
90
+ and with what props.
91
+
92
+ The model never ships executable code. It selects a `component` name and a
93
+ prop bag from a contract you defined up front. The client looks that name up
94
+ in the catalog and renders the real ReactLynx component you registered.
95
+
96
+ ```text
97
+ Agent output (data, not code): Your catalog (code, trusted):
98
+ { component: "Card", Card -> <Card> (you wrote this)
99
+ child: "t1" } Text -> <Text> (you wrote this)
100
+ { component: "Text", id: "t1",
101
+ text: "Hello" } Result: <Card><Text>Hello</Text></Card>
102
+ ```
103
+
104
+ The result is not arbitrary generated markup. It is a ReactLynx UI tree
105
+ assembled from a trusted catalog — which is what makes generated UI safe to
106
+ mount in a production app. An Agent can only reach the components and
107
+ functions you put in the catalog; anything else it emits renders nothing.
108
+
109
+ ## The end-to-end picture
110
+
111
+ A2UI is a round trip between a server that decides and a client that
112
+ renders. This package is everything inside the **Client** box below.
113
+
114
+ ```text
115
+ ┌─────────────── Your application ───────────────┐
116
+ user │ │
117
+ input ─┼─► Transport ──prompt/action──► Agent service │
118
+ │ adapter (server) │
119
+ │ ▲ │ │
120
+ │ │ A2UI messages (JSON) │ │
121
+ │ └─────────────────────────────┘ │
122
+ │ │ │
123
+ │ ▼ │
124
+ │ MessageStore ──► <A2UI> ──renders──► surface │
125
+ │ (raw buffer) (this package) (UI tree) │
126
+ │ │ │
127
+ │ └─ onAction ─► back to ───┤
128
+ │ (user taps) transport │
129
+ └────────────────────────────────────────────────┘
130
+ ```
131
+
132
+ 1. The user prompts, or taps something in already-rendered UI.
133
+ 2. Your **transport adapter** sends that to your **Agent service**.
134
+ 3. The Agent calls a model with the A2UI system prompt and your catalog
135
+ contract, validates the output, and returns A2UI messages.
136
+ 4. Your adapter writes those messages into a `MessageStore`.
137
+ 5. `<A2UI>` consumes them, renders the active surface, and forwards any
138
+ user actions through `onAction` — which loops back to step 2.
139
+
140
+ Because the loop is just "push messages in, get actions out," the transport
141
+ can be REST, SSE, WebSocket, or an in-process mock. The renderer does not
142
+ care how messages arrive.
143
+
144
+ ## Who owns what
145
+
146
+ The package draws a hard line between what it provides and what your
147
+ application provides. Keeping that line crisp is the reason the runtime
148
+ stays transport-agnostic and the catalog stays explicit.
149
+
150
+ | Piece | Runs in | Owner | Responsibility |
151
+ | ------------------------- | ------------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
152
+ | Agent service | Server | Your application | Turns prompts and client actions into validated A2UI message arrays. Prompts the model with the same catalog contract the client renders. |
153
+ | Transport adapter | Client shell | Your application | Sends prompts/actions to the Agent over REST/SSE/WebSocket, then writes the returned messages into a `MessageStore`. |
154
+ | `MessageStore` | Client | This package | Stores raw A2UI messages in arrival order and notifies subscribers. It does not parse or interpret the protocol. |
155
+ | `<A2UI>` | Client | This package | Owns a `MessageProcessor` per mount, consumes new messages, renders the active surface, and forwards generated UI actions through `onAction`. |
156
+ | Catalog API | Client + Agent handshake | This package | Maps protocol component/function names to local implementations and optional JSON schemas. Compose it with `defineCatalog` and friends. |
157
+ | Built-ins | Client | This package | A2UI v0.9 basic-catalog component renderers, per-component JSON-Schema manifests, and client-side basic-catalog function implementations. |
158
+ | `npx @lynx-js/genui a2ui` | Build / setup time | GenUI CLI | Generates custom catalog artifacts and A2UI system prompts. Not required when both Agent and renderer use the built-in basic catalog. |
159
+
160
+ A useful way to remember it: **the server decides, the client renders, and
161
+ the catalog is the contract they agree on.** The catalog is the one piece
162
+ that lives on both sides of the wire — your client registers
163
+ implementations; your Agent receives the serialized schema during the
164
+ handshake.
165
+
166
+ ## Inside the client: how a message becomes UI
167
+
168
+ `<A2UI>` is an all-in-one front door, but underneath it the package is three
169
+ independently composable layers. Understanding the path a message takes
170
+ through them makes the renderer's behavior — and its lifecycle gotchas —
171
+ predictable.
172
+
173
+ ```text
174
+ store.push(msg)
175
+
176
+
177
+ MessageStore ──subscribe──► <A2UI> ──► MessageProcessor ──► Surface(s)
178
+ (raw buffer) (React) (state machine) │ │
179
+ Resource│ │SignalStore
180
+ (pending/ (data model,
181
+ success/ signal-backed)
182
+ error)
183
+
184
+
185
+ NodeRenderer walks the tree,
186
+ looks each component up in the
187
+ catalog, and renders it.
188
+ ```
189
+
190
+ - **Store layer** (`@lynx-js/genui/a2ui/store`) — pure data logic, no
191
+ React. The `MessageStore` is an append-only buffer with a
192
+ `useSyncExternalStore`-friendly `subscribe` / `getSnapshot` API. Your
193
+ transport calls `store.push(msg)`; the store stays intentionally dumb
194
+ about protocol semantics.
195
+ - **`MessageProcessor`** — the protocol brain. It owns every `Surface`,
196
+ applies `createSurface` / `updateComponents` / `updateDataModel` /
197
+ `deleteSurface` into surface state, and emits typed events
198
+ (`beginRendering`, `surfaceUpdate`, `deleteSurface`) for the React layer
199
+ to consume. `dispatch({ userAction })` fans actions out to listeners.
200
+ - **`Resource`** — a `pending → success → error` state machine, one per
201
+ surface root and per component instance. Its snapshot reference changes
202
+ on every transition so `useSyncExternalStore` never bails out of a
203
+ `pending → error` update.
204
+ - **`SignalStore`** — a `@preact/signals` wrapper used as the per-surface
205
+ data model, addressed with JSON-pointer-style paths.
206
+ - **React layer** (`@lynx-js/genui/a2ui/react`) — `<A2UI>` plus
207
+ `NodeRenderer` and the hooks (`useAction`, `useDataBinding`,
208
+ `useResolvedProps`, `useChecks`) that turn surface state into a ReactLynx
209
+ tree.
210
+
211
+ A few runtime behaviors worth knowing because they explain things you will
212
+ see while building:
213
+
214
+ - **Children by reference.** A component instance references children by id
215
+ (`child: "text-1"` or `children: ["a", "b"]`). Catalog components render
216
+ their child ids by delegating to `<NodeRenderer>` for the same surface.
217
+ - **Data binding.** A bound prop is `{ path: string }` resolved against the
218
+ surface's `SignalStore`. Relative paths resolve against the component's
219
+ `dataContextPath`, which is what makes templates and repeated lists work.
220
+ - **Template expansion.** When `updateComponents` carries a "templated
221
+ children" placeholder, the processor stores `__template` metadata. When a
222
+ later `updateDataModel` fills the bound path, it clones the template
223
+ subtree per item, rewrites child ids, and scopes each clone's
224
+ `dataContextPath`. This is why components can appear or disappear when
225
+ only the data model changes.
226
+ - **Actions loop back as messages.** A tap calls `sendAction`; `useAction`
227
+ resolves any dynamic values, builds a `UserActionPayload`, and dispatches
228
+ it. `<A2UI>` forwards it to your `onAction`. Responses, if any, return as
229
+ new protocol messages that you push into the same `MessageStore`.
230
+ - **Unknown components fail soft.** A `component` name not in the catalog
231
+ logs a warning once per tag and renders `null`, rather than throwing.
232
+
233
+ ## Package contents
234
+
235
+ The building blocks you compose against:
236
+
237
+ - **`<A2UI>`** — the all-in-one component. It owns a `MessageProcessor`,
238
+ subscribes to a developer-supplied `MessageStore`, and renders the most
239
+ recent surface.
240
+ - **`MessageStore`** — an append-only buffer of raw protocol messages you
241
+ push into from any transport: fetch, SSE, WebSocket, or an in-process
242
+ mock.
243
+ - **Catalog API** — `defineCatalog`, `mergeCatalogs`, `serializeCatalog`,
244
+ `resolveCatalog`, and `defineFunction`. There is no global component
245
+ registry; every consumer composes the component and function entries it
246
+ wants.
247
+ - **Built-in components** — 20 A2UI v0.9 basic-catalog renderers (`Text`,
248
+ `Image`, `Button`, `Row`, `Column`, `List`, `Loading`, `Card`, `Modal`,
249
+ `Divider`, `Icon`, `CheckBox`, `ChoicePicker`, `DateTimeInput`,
250
+ `LineChart`, `PieChart`, `RadioGroup`, `Slider`, `TextField`, and
251
+ `Tabs`). See the [catalog guide](./catalog-guide.md) for what each one
252
+ does.
253
+ - **Per-component manifests** — `catalog/<Name>/catalog.json`, the
254
+ JSON-Schema descriptions used during Agent handshakes.
255
+ - **`basicFunctions`** — A2UI v0.9 basic-catalog client function entries,
256
+ ready to spread into your `catalogs` array.
257
+
258
+ ## Exports
259
+
260
+ The package is split into subpaths so you import only what you use.
261
+
262
+ | Import | What you get |
263
+ | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
264
+ | `@lynx-js/genui/a2ui` | The main surface: `<A2UI>`, `createMessageStore`, the catalog API, every built-in component, `basicFunctions`, and protocol types. |
265
+ | `@lynx-js/genui/a2ui/catalog` | The catalog API and built-ins again, as a tree-shake-friendly subpath. |
266
+ | `@lynx-js/genui/a2ui/catalog/<Name>` | A single built-in component (e.g. `.../catalog/Text`). |
267
+ | `@lynx-js/genui/a2ui/catalog/<Name>/catalog.json` | That component's JSON-Schema manifest for the handshake. |
268
+ | `@lynx-js/genui/a2ui/store` | The pure data layer: `MessageStore`, `MessageProcessor`, `Resource`, `SignalStore`, and the payload normalizers. |
269
+ | `@lynx-js/genui/a2ui/react` | The custom-component contract: `NodeRenderer`, `useAction`, `useDataBinding`, `useResolvedProps`, and `useChecks`. |
270
+ | `@lynx-js/genui/a2ui/functions` | `basicFunctions` and the `registerBasicFunctions` escape hatch. |
271
+ | `@lynx-js/genui/a2ui/styles/theme.css` | Optional default CSS tokens for `.a2ui-light` and `.a2ui-dark`. |
272
+
273
+ Most apps only ever import from `@lynx-js/genui/a2ui`. Reach for `/store` and
274
+ `/react` when you build custom catalog components or your own renderer.
275
+
276
+ ## `<A2UI>` props and lifecycle
277
+
278
+ `<A2UI>` takes two required props and a set of optional render hooks.
279
+
280
+ | Prop | Type | Required | Purpose |
281
+ | ------------------- | ---------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------- |
282
+ | `messageStore` | `MessageStore` | yes | The raw-message buffer your transport pushes into. `<A2UI>` subscribes and processes new tail messages. |
283
+ | `catalogs` | `readonly CatalogInput[]` | yes | The components and function entries the renderer is allowed to instantiate. |
284
+ | `onAction` | `(action: UserActionPayload) => void` | no | Fired when a user action occurs in the tree. Forward to your Agent; push responses back into the store. |
285
+ | `className` | `string` | no | Applied to the surface root view (`surface-${surfaceId}`). Handy for surface-level theme classes. |
286
+ | `wrapSurface` | `(children, { surfaceId }) => ReactNode` | no | Wraps each surface so you can apply an outer theme shell or wrapper styles. |
287
+ | `renderEmpty` | `() => ReactNode` | no | Rendered before the first `beginRendering` arrives. Defaults to nothing. |
288
+ | `renderFallback` | `() => ReactNode` | no | Rendered while the active resource is pending. Defaults to the built-in `<Loading>`. |
289
+ | `renderError` | `(err: unknown) => ReactNode` | no | Rendered when the active resource fails. |
290
+ | `renderUnsupported` | `(info) => ReactNode` | no | Rendered for an unsupported component or data syntax. |
291
+
292
+ Lifecycle notes that save debugging time:
293
+
294
+ - **One processor per mount.** `<A2UI>` creates its `MessageProcessor`
295
+ (surfaces, signals, resources) once per mount. Passing a _different_
296
+ `messageStore` instance later does **not** reset internal state. To start
297
+ a fresh session or turn, mount with a different `key` derived from your
298
+ turn/session id: `<A2UI key={turnId} messageStore={turnStore} … />`.
299
+ - **`onAction` is fire-and-forget.** The renderer never awaits it. Your
300
+ Agent pushes follow-up messages back into the same `MessageStore` to
301
+ update the UI.
302
+ - **`className` vs `wrapSurface`.** Both can drive theme switching;
303
+ `className` styles the surface root, `wrapSurface` adds an outer wrapper.
304
+ Choose the layer that matches your styling strategy.
305
+
306
+ ## Where to go next
307
+
308
+ - [Catalogs, built-ins, and custom components](./catalog-guide.md) — compose
309
+ the contract, add manifests, and register your own components.
310
+ - [System prompts](./system-prompts.md) — generate the model instructions
311
+ that pair an Agent with your catalog.
312
+ - [Open the A2UI playground](https://lynxjs.org/a2ui) — try it live.
@@ -0,0 +1,289 @@
1
+ # 概览与架构
2
+
3
+ 这篇文档解释 `@lynx-js/genui/a2ui` 是什么、它背后的心智模型,以及一条
4
+ server message 如何在 client 上变成渲染出来的 UI。它以一个可运行的
5
+ [quick start](#quick-start) 开场,然后逐步讲解架构,帮你理解 stack 各部分的
6
+ 职责边界,以及这个包为什么设计成现在这样。
7
+
8
+ ## 这个包是什么
9
+
10
+ `@lynx-js/genui/a2ui` 是面向 A2UI v0.9 协议的 ReactLynx **客户端运行时**。
11
+ 它消费经过校验的 server-to-client JSON messages,并在你的应用中渲染可信的
12
+ ReactLynx 组件。
13
+
14
+ 它刻意只做一件事——渲染。这个包**不会**:
15
+
16
+ - 托管 Agent,也不调用 LLM;
17
+ - 拥有后端路由或 chat shell;
18
+ - 决定_渲染什么_——那是 Agent 的职责。
19
+
20
+ 你的应用负责传输层,并把 messages 写入 renderer。当你已经有、或准备构建一个
21
+ 返回 A2UI messages 的 Agent 服务时,使用这个包。
22
+
23
+ ## Quick start
24
+
25
+ 在 ReactLynx 应用里安装这个包,然后用 `<A2UI>` 渲染一个 `MessageStore`。你的
26
+ 传输层把 Agent 的 messages 写入 store;renderer 把它们变成 UI,并通过
27
+ `onAction` 把用户 action 交还给你。
28
+
29
+ ```sh
30
+ pnpm add @lynx-js/genui @lynx-js/react
31
+ ```
32
+
33
+ ```tsx
34
+ import {
35
+ A2UI,
36
+ basicFunctions,
37
+ Button,
38
+ createMessageStore,
39
+ normalizePayloadToMessages,
40
+ Text,
41
+ } from '@lynx-js/genui/a2ui';
42
+
43
+ // 1. 一个 buffer,你的传输层把原始 protocol messages 写进它。
44
+ const store = createMessageStore();
45
+
46
+ // 2. 允许 generated UI 使用的 component 和 function。
47
+ const catalogs = [Text, Button, ...basicFunctions];
48
+
49
+ // 3. 发送 prompt,并把 Agent 的回复推进 store。
50
+ async function sendPrompt(input: string) {
51
+ const res = await fetch('/a2ui/chat', {
52
+ method: 'POST',
53
+ headers: { 'Content-Type': 'application/json' },
54
+ body: JSON.stringify({ messages: [{ role: 'user', content: input }] }),
55
+ });
56
+ store.push(normalizePayloadToMessages(await res.json()));
57
+ }
58
+
59
+ // 4. 渲染。onAction 把用户点击回传给 Agent。
60
+ <A2UI
61
+ messageStore={store}
62
+ catalogs={catalogs}
63
+ onAction={(action) => {
64
+ void fetch('/a2ui/action', {
65
+ method: 'POST',
66
+ headers: { 'Content-Type': 'application/json' },
67
+ body: JSON.stringify(action),
68
+ })
69
+ .then((res) => res.json())
70
+ .then((payload) => store.push(normalizePayloadToMessages(payload)));
71
+ }}
72
+ />;
73
+ ```
74
+
75
+ 这就是 client 的全部循环:**把 messages 推进来、渲染、把 actions 抛出去。**
76
+ 传输层可以是 REST、SSE、WebSocket,或一个 in-process mock——renderer 并不关心。
77
+ 本页接下来会讲清楚在 `store.push(...)` 和渲染出的 surface 之间到底发生了什么。
78
+ 安装细节和可选的 theme tokens 见 [README](../README_zh.md)。
79
+
80
+ ## 心智模型
81
+
82
+ 如果你写过 React,这个转变很小但很关键:
83
+
84
+ - 在 **React** 里,是你的代码选择组件并传入 props。
85
+ - 在 **A2UI** 里,是 _Agent_ 从_你的应用_发布的组件 catalog 中选择组件,
86
+ 并发送数据,告诉渲染器用哪个已授权组件、传哪些 props。
87
+
88
+ 模型从不发送可执行代码。它只是从你预先定义好的 contract 中选出一个
89
+ `component` 名称和一组 props。Client 在 catalog 里查到这个名称,渲染你注册的
90
+ 真实 ReactLynx 组件。
91
+
92
+ ```text
93
+ Agent output (data, not code): Your catalog (code, trusted):
94
+ { component: "Card", Card -> <Card> (you wrote this)
95
+ child: "t1" } Text -> <Text> (you wrote this)
96
+ { component: "Text", id: "t1",
97
+ text: "Hello" } Result: <Card><Text>Hello</Text></Card>
98
+ ```
99
+
100
+ 最终产物不是任意生成的标记,而是由可信 catalog 组装出来的 ReactLynx UI 树——
101
+ 这正是 generated UI 能安全地挂载到生产应用里的原因。Agent 只能触及你放进
102
+ catalog 的 component 和 function;它发出的其他任何东西都渲染为空。
103
+
104
+ ## 端到端全貌
105
+
106
+ A2UI 是一次往返:server 负责决策,client 负责渲染。这个包就是下图 **Client**
107
+ 框里的全部内容。
108
+
109
+ ```text
110
+ ┌─────────────── Your application ───────────────┐
111
+ user │ │
112
+ input ─┼─► Transport ──prompt/action──► Agent service │
113
+ │ adapter (server) │
114
+ │ ▲ │ │
115
+ │ │ A2UI messages (JSON) │ │
116
+ │ └─────────────────────────────┘ │
117
+ │ │ │
118
+ │ ▼ │
119
+ │ MessageStore ──► <A2UI> ──renders──► surface │
120
+ │ (raw buffer) (this package) (UI tree) │
121
+ │ │ │
122
+ │ └─ onAction ─► back to ───┤
123
+ │ (user taps) transport │
124
+ └────────────────────────────────────────────────┘
125
+ ```
126
+
127
+ 1. 用户输入 prompt,或在已渲染的 UI 上点击某处。
128
+ 2. 你的 **transport adapter** 把它发给你的 **Agent 服务**。
129
+ 3. Agent 带着 A2UI system prompt 和你的 catalog contract 调用模型,校验输出,
130
+ 然后返回 A2UI messages。
131
+ 4. 你的 adapter 把这些 messages 写入 `MessageStore`。
132
+ 5. `<A2UI>` 消费它们、渲染 active surface,并通过 `onAction` 把用户 action
133
+ 抛出——再回到第 2 步。
134
+
135
+ 因为整个循环只是「把 messages 推进来、把 actions 抛出去」,传输层可以是 REST、
136
+ SSE、WebSocket,或一个 in-process mock。renderer 不关心 messages 是怎么来的。
137
+
138
+ ## 谁负责什么
139
+
140
+ 这个包在「它提供什么」和「你的应用提供什么」之间划了一条硬边界。保持这条边界
141
+ 清晰,正是运行时保持传输无关、catalog 保持显式的原因。
142
+
143
+ | 部分 | 运行位置 | 负责人 | 职责 |
144
+ | ------------------------- | ------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------- |
145
+ | Agent 服务 | Server | 你的应用 | 把 prompt 和 client action 转成经过校验的 A2UI message 数组。使用与 client 可渲染能力一致的 catalog contract 提示模型。 |
146
+ | Transport adapter | Client shell | 你的应用 | 通过 REST/SSE/WebSocket 把 prompt/action 发给 Agent,再把返回的 messages 写入 `MessageStore`。 |
147
+ | `MessageStore` | Client | 这个包 | 按到达顺序保存原始 A2UI messages 并通知订阅者。它不解析也不解释协议语义。 |
148
+ | `<A2UI>` | Client | 这个包 | 每次 mount 拥有一个 `MessageProcessor`,消费新 messages,渲染 active surface,并通过 `onAction` 转发 generated UI action。 |
149
+ | Catalog API | Client + Agent handshake | 这个包 | 把协议中的 component/function 名称映射到本地实现和可选 JSON schema。用 `defineCatalog` 等组合它。 |
150
+ | 内置能力 | Client | 这个包 | A2UI v0.9 basic-catalog 的组件 renderer、逐组件 JSON-Schema manifest,以及客户端 basic-catalog function 实现。 |
151
+ | `npx @lynx-js/genui a2ui` | 构建 / 接入阶段 | GenUI CLI | 生成自定义 catalog artifacts 和 A2UI system prompt。当 Agent 和 renderer 都用内置 basic catalog 时不需要它。 |
152
+
153
+ 一个好记的方式:**server 决策,client 渲染,catalog 是两边达成一致的
154
+ contract。** catalog 是唯一同时活在 wire 两侧的部分——你的 client 注册实现,
155
+ 你的 Agent 在 handshake 时收到序列化后的 schema。
156
+
157
+ ## client 内部:一条 message 如何变成 UI
158
+
159
+ `<A2UI>` 是一个 all-in-one 的入口,但它底下其实是三个可独立组合的 layer。理解
160
+ 一条 message 在它们之间走过的路径,能让 renderer 的行为——以及它的生命周期
161
+ 坑——变得可预测。
162
+
163
+ ```text
164
+ store.push(msg)
165
+
166
+
167
+ MessageStore ──subscribe──► <A2UI> ──► MessageProcessor ──► Surface(s)
168
+ (raw buffer) (React) (state machine) │ │
169
+ Resource│ │SignalStore
170
+ (pending/ (data model,
171
+ success/ signal-backed)
172
+ error)
173
+
174
+
175
+ NodeRenderer walks the tree,
176
+ looks each component up in the
177
+ catalog, and renders it.
178
+ ```
179
+
180
+ - **Store layer**(`@lynx-js/genui/a2ui/store`)——纯数据逻辑,没有 React。
181
+ `MessageStore` 是一个 append-only buffer,带有对 `useSyncExternalStore`
182
+ 友好的 `subscribe` / `getSnapshot` API。你的传输层调用 `store.push(msg)`;
183
+ store 刻意对协议语义保持「无知」。
184
+ - **`MessageProcessor`**——协议大脑。它拥有每一个 `Surface`,把
185
+ `createSurface` / `updateComponents` / `updateDataModel` / `deleteSurface`
186
+ 应用进 surface 状态,并发出带类型的事件(`beginRendering`、`surfaceUpdate`、
187
+ `deleteSurface`)供 React layer 消费。`dispatch({ userAction })` 把 action
188
+ 分发给监听者。
189
+ - **`Resource`**——一个 `pending → success → error` 状态机,每个 surface root
190
+ 和每个 component 实例各一个。它的 snapshot 引用在每次状态转换时都会改变,
191
+ 这样 `useSyncExternalStore` 永远不会在 `pending → error` 更新上「bail out」。
192
+ - **`SignalStore`**——一个 `@preact/signals` 封装,作为每个 surface 的 data
193
+ model,用 JSON-pointer 风格的 path 寻址。
194
+ - **React layer**(`@lynx-js/genui/a2ui/react`)——`<A2UI>` 加上
195
+ `NodeRenderer` 和那些把 surface 状态变成 ReactLynx 树的 hooks(`useAction`、
196
+ `useDataBinding`、`useResolvedProps`、`useChecks`)。
197
+
198
+ 有几个运行时行为值得了解,因为它们能解释你在开发中会看到的现象:
199
+
200
+ - **children 通过引用。** 一个 component 实例用 id 引用 children
201
+ (`child: "text-1"` 或 `children: ["a", "b"]`)。catalog 组件通过对同一个
202
+ surface 委托 `<NodeRenderer>` 来渲染它的 child id。
203
+ - **data binding。** 一个 bound prop 是 `{ path: string }`,它针对 surface 的
204
+ `SignalStore` 求值。相对 path 针对组件的 `dataContextPath` 求值——这正是
205
+ templates 和重复列表能工作的原因。
206
+ - **template 展开。** 当 `updateComponents` 带有「templated children」占位时,
207
+ processor 会存下 `__template` 元数据。当之后的 `updateDataModel` 填充被绑定
208
+ 的 path 时,它会按每个 item 克隆 template 子树、重写 child id,并 scope 每个
209
+ 克隆的 `dataContextPath`。这就是为什么只改 data model 也能让组件出现或消失。
210
+ - **action 以 message 形式回流。** 一次点击调用 `sendAction`;`useAction` 解析
211
+ 所有动态值,构建一个 `UserActionPayload` 并 dispatch。`<A2UI>` 把它转发给你
212
+ 的 `onAction`。响应(如果有)会作为新的 protocol messages 回来,你把它们推回
213
+ 同一个 `MessageStore`。
214
+ - **未知 component 软失败。** 不在 catalog 里的 `component` 名称会按 tag 打印
215
+ 一次警告并渲染 `null`,而不是抛错。
216
+
217
+ ## 包含内容
218
+
219
+ 你会用来组合的构建块:
220
+
221
+ - **`<A2UI>`**——all-in-one 组件。它拥有 `MessageProcessor`,订阅开发者传入的
222
+ `MessageStore`,并渲染最新的 surface。
223
+ - **`MessageStore`**——原始 protocol messages 的 append-only buffer。你可以从
224
+ fetch、SSE、WebSocket 或 in-process mock 等任意传输层写入。
225
+ - **Catalog API**——`defineCatalog`、`mergeCatalogs`、`serializeCatalog`、
226
+ `resolveCatalog` 和 `defineFunction`。这里没有全局 component registry;每个
227
+ 消费者都显式组合自己想开放的 component 和 function entries。
228
+ - **内置组件**——20 个 A2UI v0.9 basic-catalog renderer(`Text`、`Image`、
229
+ `Button`、`Row`、`Column`、`List`、`Loading`、`Card`、`Modal`、`Divider`、
230
+ `Icon`、`CheckBox`、`ChoicePicker`、`DateTimeInput`、`LineChart`、
231
+ `PieChart`、`RadioGroup`、`Slider`、`TextField` 和 `Tabs`)。每个的用途见
232
+ [catalog 指南](./catalog-guide_zh.md)。
233
+ - **逐组件 manifest**——`catalog/<Name>/catalog.json`,用于 Agent handshake 的
234
+ JSON-Schema 描述。
235
+ - **`basicFunctions`**——A2UI v0.9 basic-catalog 的客户端 function entries,
236
+ 可以直接展开进你的 `catalogs` 数组。
237
+
238
+ ## Exports
239
+
240
+ 这个包按 subpath 拆分,让你只导入用到的部分。
241
+
242
+ | 导入 | 你得到什么 |
243
+ | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
244
+ | `@lynx-js/genui/a2ui` | 主入口:`<A2UI>`、`createMessageStore`、catalog API、所有内置组件、`basicFunctions`,以及 protocol types。 |
245
+ | `@lynx-js/genui/a2ui/catalog` | catalog API 和内置组件的再导出,作为 tree-shake-friendly 的 subpath。 |
246
+ | `@lynx-js/genui/a2ui/catalog/<Name>` | 单个内置组件(例如 `.../catalog/Text`)。 |
247
+ | `@lynx-js/genui/a2ui/catalog/<Name>/catalog.json` | 该组件用于 handshake 的 JSON-Schema manifest。 |
248
+ | `@lynx-js/genui/a2ui/store` | 纯数据层:`MessageStore`、`MessageProcessor`、`Resource`、`SignalStore`,以及 payload normalizers。 |
249
+ | `@lynx-js/genui/a2ui/react` | 自定义组件的 contract:`NodeRenderer`、`useAction`、`useDataBinding`、`useResolvedProps` 和 `useChecks`。 |
250
+ | `@lynx-js/genui/a2ui/functions` | `basicFunctions` 和 `registerBasicFunctions` 这个 escape hatch。 |
251
+ | `@lynx-js/genui/a2ui/styles/theme.css` | 可选的默认 CSS tokens,提供 `.a2ui-light` 和 `.a2ui-dark`。 |
252
+
253
+ 大多数应用只会从 `@lynx-js/genui/a2ui` 导入。当你构建自定义 catalog 组件或自己
254
+ 的 renderer 时,才需要 `/store` 和 `/react`。
255
+
256
+ ## `<A2UI>` props 与生命周期
257
+
258
+ `<A2UI>` 接收两个必填 prop 和一组可选的 render hooks。
259
+
260
+ | Prop | 类型 | 必填 | 用途 |
261
+ | ------------------- | ---------------------------------------- | ---- | --------------------------------------------------------------------------------- |
262
+ | `messageStore` | `MessageStore` | 是 | 你的传输层写入的 raw-message buffer。`<A2UI>` 订阅它并处理新的 tail messages。 |
263
+ | `catalogs` | `readonly CatalogInput[]` | 是 | renderer 被允许实例化的 component 和 function entries。 |
264
+ | `onAction` | `(action: UserActionPayload) => void` | 否 | 树中发生用户 action 时触发。转发给你的 Agent;把响应推回 store。 |
265
+ | `className` | `string` | 否 | 加在 surface root view(`surface-${surfaceId}`)上。适合做 surface 级主题 class。 |
266
+ | `wrapSurface` | `(children, { surfaceId }) => ReactNode` | 否 | 包裹每个 surface,便于套一层外部主题壳或 wrapper 样式。 |
267
+ | `renderEmpty` | `() => ReactNode` | 否 | 在第一条 `beginRendering` 到达前渲染。默认什么都不渲染。 |
268
+ | `renderFallback` | `() => ReactNode` | 否 | 在 active resource 处于 pending 时渲染。默认是内置的 `<Loading>`。 |
269
+ | `renderError` | `(err: unknown) => ReactNode` | 否 | 在 active resource 失败时渲染。 |
270
+ | `renderUnsupported` | `(info) => ReactNode` | 否 | 在遇到不支持的 component 或数据语法时渲染。 |
271
+
272
+ 能省下调试时间的生命周期说明:
273
+
274
+ - **每次 mount 一个 processor。** `<A2UI>` 在每次 mount 时创建一次自己的
275
+ `MessageProcessor`(surfaces、signals、resources)。之后传入_另一个_
276
+ `messageStore` 实例**不会**重置内部状态。要开启新的 session 或 turn,请用一个
277
+ 由 turn/session id 派生的 `key` 来 mount:
278
+ `<A2UI key={turnId} messageStore={turnStore} … />`。
279
+ - **`onAction` 是 fire-and-forget。** renderer 从不 await 它。你的 Agent 把后续
280
+ messages 推回同一个 `MessageStore` 来更新 UI。
281
+ - **`className` vs `wrapSurface`。** 两者都能驱动主题切换;`className` 给 surface
282
+ root 加样式,`wrapSurface` 在外面加一层 wrapper。选择与你的样式策略匹配的那层。
283
+
284
+ ## 下一步
285
+
286
+ - [Catalogs、内置组件与自定义组件](./catalog-guide_zh.md)——组合 contract、加入
287
+ manifest、注册你自己的组件。
288
+ - [System Prompts](./system-prompts_zh.md)——生成让 Agent 与你的 catalog 配套的模型指令。
289
+ - [打开 A2UI playground](https://lynxjs.org/a2ui)——在线体验。