@assistant-ui/react-generative-ui 0.0.24 → 0.1.0

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 (338) hide show
  1. package/README.md +2 -0
  2. package/dist/a2ui.d.ts +1 -4
  3. package/dist/a2ui.d.ts.map +1 -1
  4. package/dist/a2ui.js +1 -4
  5. package/dist/index.d.ts +1 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -10
  8. package/dist/index.server.d.ts +1 -2
  9. package/dist/index.server.d.ts.map +1 -1
  10. package/dist/index.server.js +1 -10
  11. package/dist/ir.d.ts +1 -111
  12. package/dist/ir.d.ts.map +1 -1
  13. package/dist/ir.js +1 -175
  14. package/dist/slack.d.ts +1 -4
  15. package/dist/slack.d.ts.map +1 -1
  16. package/dist/slack.js +1 -4
  17. package/dist/teams.d.ts +1 -4
  18. package/dist/teams.d.ts.map +1 -1
  19. package/dist/teams.js +1 -4
  20. package/package.json +4 -5
  21. package/src/a2ui.ts +1 -22
  22. package/src/index.server.ts +1 -2
  23. package/src/index.test.ts +75 -0
  24. package/src/index.ts +1 -2
  25. package/src/ir.ts +1 -279
  26. package/src/slack.ts +1 -39
  27. package/src/teams.ts +1 -35
  28. package/dist/JSONGenerativeUI.client.d.ts +0 -28
  29. package/dist/JSONGenerativeUI.client.d.ts.map +0 -1
  30. package/dist/JSONGenerativeUI.client.js +0 -101
  31. package/dist/JSONGenerativeUI.client.js.map +0 -1
  32. package/dist/JSONGenerativeUI.server.d.ts +0 -21
  33. package/dist/JSONGenerativeUI.server.d.ts.map +0 -1
  34. package/dist/JSONGenerativeUI.server.js +0 -32
  35. package/dist/JSONGenerativeUI.server.js.map +0 -1
  36. package/dist/JSONGenerativeUI.shared.d.ts +0 -63
  37. package/dist/JSONGenerativeUI.shared.d.ts.map +0 -1
  38. package/dist/JSONGenerativeUI.shared.js +0 -31
  39. package/dist/JSONGenerativeUI.shared.js.map +0 -1
  40. package/dist/RadioGroupScope.d.ts +0 -6
  41. package/dist/RadioGroupScope.d.ts.map +0 -1
  42. package/dist/RadioGroupScope.js +0 -25
  43. package/dist/RadioGroupScope.js.map +0 -1
  44. package/dist/a2ui/BindingContext.d.ts +0 -11
  45. package/dist/a2ui/BindingContext.d.ts.map +0 -1
  46. package/dist/a2ui/BindingContext.js +0 -12
  47. package/dist/a2ui/BindingContext.js.map +0 -1
  48. package/dist/a2ui/PresentRenderer.d.ts +0 -11
  49. package/dist/a2ui/PresentRenderer.d.ts.map +0 -1
  50. package/dist/a2ui/PresentRenderer.js +0 -39
  51. package/dist/a2ui/PresentRenderer.js.map +0 -1
  52. package/dist/a2ui/convert.d.ts +0 -15
  53. package/dist/a2ui/convert.d.ts.map +0 -1
  54. package/dist/a2ui/convert.js +0 -728
  55. package/dist/a2ui/convert.js.map +0 -1
  56. package/dist/a2ui/dataModel.d.ts +0 -9
  57. package/dist/a2ui/dataModel.d.ts.map +0 -1
  58. package/dist/a2ui/dataModel.js +0 -44
  59. package/dist/a2ui/dataModel.js.map +0 -1
  60. package/dist/a2ui/reducer.d.ts +0 -8
  61. package/dist/a2ui/reducer.d.ts.map +0 -1
  62. package/dist/a2ui/reducer.js +0 -242
  63. package/dist/a2ui/reducer.js.map +0 -1
  64. package/dist/a2ui/snapshot.d.ts +0 -3
  65. package/dist/a2ui/snapshot.d.ts.map +0 -1
  66. package/dist/a2ui/snapshot.js +0 -33
  67. package/dist/a2ui/snapshot.js.map +0 -1
  68. package/dist/a2ui/types.d.ts +0 -88
  69. package/dist/a2ui/types.d.ts.map +0 -1
  70. package/dist/a2ui/types.js +0 -6
  71. package/dist/a2ui/types.js.map +0 -1
  72. package/dist/a2ui/valueFunctions.d.ts +0 -16
  73. package/dist/a2ui/valueFunctions.d.ts.map +0 -1
  74. package/dist/a2ui/valueFunctions.js +0 -332
  75. package/dist/a2ui/valueFunctions.js.map +0 -1
  76. package/dist/actionRegistry.d.ts +0 -29
  77. package/dist/actionRegistry.d.ts.map +0 -1
  78. package/dist/actionRegistry.js +0 -41
  79. package/dist/actionRegistry.js.map +0 -1
  80. package/dist/answeredValues.d.ts +0 -16
  81. package/dist/answeredValues.d.ts.map +0 -1
  82. package/dist/answeredValues.js +0 -37
  83. package/dist/answeredValues.js.map +0 -1
  84. package/dist/buildPresentParameters.d.ts +0 -21
  85. package/dist/buildPresentParameters.d.ts.map +0 -1
  86. package/dist/buildPresentParameters.js +0 -95
  87. package/dist/buildPresentParameters.js.map +0 -1
  88. package/dist/constants.d.ts +0 -15
  89. package/dist/constants.d.ts.map +0 -1
  90. package/dist/constants.js +0 -19
  91. package/dist/constants.js.map +0 -1
  92. package/dist/convert/boundSpec.d.ts +0 -48
  93. package/dist/convert/boundSpec.d.ts.map +0 -1
  94. package/dist/convert/boundSpec.js +0 -108
  95. package/dist/convert/boundSpec.js.map +0 -1
  96. package/dist/convert/copyBounded.d.ts +0 -25
  97. package/dist/convert/copyBounded.d.ts.map +0 -1
  98. package/dist/convert/copyBounded.js +0 -38
  99. package/dist/convert/copyBounded.js.map +0 -1
  100. package/dist/convert/isElement.d.ts +0 -3
  101. package/dist/convert/isElement.d.ts.map +0 -1
  102. package/dist/convert/isElement.js +0 -6
  103. package/dist/convert/isElement.js.map +0 -1
  104. package/dist/convert/takeRun.d.ts +0 -6
  105. package/dist/convert/takeRun.d.ts.map +0 -1
  106. package/dist/convert/takeRun.js +0 -20
  107. package/dist/convert/takeRun.js.map +0 -1
  108. package/dist/defineGenerativeComponents.d.ts +0 -29
  109. package/dist/defineGenerativeComponents.d.ts.map +0 -1
  110. package/dist/defineGenerativeComponents.js +0 -34
  111. package/dist/defineGenerativeComponents.js.map +0 -1
  112. package/dist/fieldReferences.d.ts +0 -9
  113. package/dist/fieldReferences.d.ts.map +0 -1
  114. package/dist/fieldReferences.js +0 -53
  115. package/dist/fieldReferences.js.map +0 -1
  116. package/dist/generativeUIToJSX.d.ts +0 -18
  117. package/dist/generativeUIToJSX.d.ts.map +0 -1
  118. package/dist/generativeUIToJSX.js +0 -76
  119. package/dist/generativeUIToJSX.js.map +0 -1
  120. package/dist/index.shared.d.ts +0 -15
  121. package/dist/index.shared.d.ts.map +0 -1
  122. package/dist/index.shared.js +0 -9
  123. package/dist/ir.js.map +0 -1
  124. package/dist/renderGenerativeUI.d.ts +0 -14
  125. package/dist/renderGenerativeUI.d.ts.map +0 -1
  126. package/dist/renderGenerativeUI.js +0 -83
  127. package/dist/renderGenerativeUI.js.map +0 -1
  128. package/dist/scopeSchema.d.ts +0 -6
  129. package/dist/scopeSchema.d.ts.map +0 -1
  130. package/dist/scopeSchema.js +0 -56
  131. package/dist/scopeSchema.js.map +0 -1
  132. package/dist/slack/constants.d.ts +0 -93
  133. package/dist/slack/constants.d.ts.map +0 -1
  134. package/dist/slack/constants.js +0 -146
  135. package/dist/slack/constants.js.map +0 -1
  136. package/dist/slack/decodeBlockAction.d.ts +0 -10
  137. package/dist/slack/decodeBlockAction.d.ts.map +0 -1
  138. package/dist/slack/decodeBlockAction.js +0 -129
  139. package/dist/slack/decodeBlockAction.js.map +0 -1
  140. package/dist/slack/fromSlackBlocks.d.ts +0 -27
  141. package/dist/slack/fromSlackBlocks.d.ts.map +0 -1
  142. package/dist/slack/fromSlackBlocks.js +0 -483
  143. package/dist/slack/fromSlackBlocks.js.map +0 -1
  144. package/dist/slack/toSlackBlocks.d.ts +0 -4
  145. package/dist/slack/toSlackBlocks.d.ts.map +0 -1
  146. package/dist/slack/toSlackBlocks.js +0 -901
  147. package/dist/slack/toSlackBlocks.js.map +0 -1
  148. package/dist/slack/types.d.ts +0 -204
  149. package/dist/slack/types.d.ts.map +0 -1
  150. package/dist/slack/types.js +0 -0
  151. package/dist/teams/constants.d.ts +0 -34
  152. package/dist/teams/constants.d.ts.map +0 -1
  153. package/dist/teams/constants.js +0 -68
  154. package/dist/teams/constants.js.map +0 -1
  155. package/dist/teams/decodeSubmitData.d.ts +0 -26
  156. package/dist/teams/decodeSubmitData.d.ts.map +0 -1
  157. package/dist/teams/decodeSubmitData.js +0 -70
  158. package/dist/teams/decodeSubmitData.js.map +0 -1
  159. package/dist/teams/temporalId.d.ts +0 -5
  160. package/dist/teams/temporalId.d.ts.map +0 -1
  161. package/dist/teams/temporalId.js +0 -28
  162. package/dist/teams/temporalId.js.map +0 -1
  163. package/dist/teams/toAdaptiveCard.d.ts +0 -45
  164. package/dist/teams/toAdaptiveCard.d.ts.map +0 -1
  165. package/dist/teams/toAdaptiveCard.js +0 -642
  166. package/dist/teams/toAdaptiveCard.js.map +0 -1
  167. package/dist/teams/toTeamsAttachments.d.ts +0 -10
  168. package/dist/teams/toTeamsAttachments.d.ts.map +0 -1
  169. package/dist/teams/toTeamsAttachments.js +0 -99
  170. package/dist/teams/toTeamsAttachments.js.map +0 -1
  171. package/dist/teams/types.d.ts +0 -224
  172. package/dist/teams/types.d.ts.map +0 -1
  173. package/dist/teams/types.js +0 -0
  174. package/dist/temporal.d.ts +0 -33
  175. package/dist/temporal.d.ts.map +0 -1
  176. package/dist/temporal.js +0 -153
  177. package/dist/temporal.js.map +0 -1
  178. package/dist/types.d.ts +0 -99
  179. package/dist/types.d.ts.map +0 -1
  180. package/dist/types.js +0 -0
  181. package/dist/vocabulary/alert.d.ts +0 -32
  182. package/dist/vocabulary/alert.d.ts.map +0 -1
  183. package/dist/vocabulary/alert.js +0 -160
  184. package/dist/vocabulary/alert.js.map +0 -1
  185. package/dist/vocabulary/collectFormValues.d.ts +0 -23
  186. package/dist/vocabulary/collectFormValues.d.ts.map +0 -1
  187. package/dist/vocabulary/collectFormValues.js +0 -44
  188. package/dist/vocabulary/collectFormValues.js.map +0 -1
  189. package/dist/vocabulary/data.d.ts +0 -66
  190. package/dist/vocabulary/data.d.ts.map +0 -1
  191. package/dist/vocabulary/data.js +0 -503
  192. package/dist/vocabulary/data.js.map +0 -1
  193. package/dist/vocabulary/dispatch.d.ts +0 -9
  194. package/dist/vocabulary/dispatch.d.ts.map +0 -1
  195. package/dist/vocabulary/dispatch.js +0 -50
  196. package/dist/vocabulary/dispatch.js.map +0 -1
  197. package/dist/vocabulary/fact.d.ts +0 -19
  198. package/dist/vocabulary/fact.d.ts.map +0 -1
  199. package/dist/vocabulary/fact.js +0 -51
  200. package/dist/vocabulary/fact.js.map +0 -1
  201. package/dist/vocabulary/form.d.ts +0 -11
  202. package/dist/vocabulary/form.d.ts.map +0 -1
  203. package/dist/vocabulary/form.js +0 -23
  204. package/dist/vocabulary/form.js.map +0 -1
  205. package/dist/vocabulary/formatValue.d.ts +0 -7
  206. package/dist/vocabulary/formatValue.d.ts.map +0 -1
  207. package/dist/vocabulary/formatValue.js +0 -76
  208. package/dist/vocabulary/formatValue.js.map +0 -1
  209. package/dist/vocabulary/icon.d.ts +0 -41
  210. package/dist/vocabulary/icon.d.ts.map +0 -1
  211. package/dist/vocabulary/icon.js +0 -272
  212. package/dist/vocabulary/icon.js.map +0 -1
  213. package/dist/vocabulary/index.d.ts +0 -12
  214. package/dist/vocabulary/index.d.ts.map +0 -1
  215. package/dist/vocabulary/index.js +0 -36
  216. package/dist/vocabulary/index.js.map +0 -1
  217. package/dist/vocabulary/interactive.d.ts +0 -230
  218. package/dist/vocabulary/interactive.d.ts.map +0 -1
  219. package/dist/vocabulary/interactive.js +0 -584
  220. package/dist/vocabulary/interactive.js.map +0 -1
  221. package/dist/vocabulary/layout.d.ts +0 -79
  222. package/dist/vocabulary/layout.d.ts.map +0 -1
  223. package/dist/vocabulary/layout.js +0 -139
  224. package/dist/vocabulary/layout.js.map +0 -1
  225. package/dist/vocabulary/list.d.ts +0 -14
  226. package/dist/vocabulary/list.d.ts.map +0 -1
  227. package/dist/vocabulary/list.js +0 -50
  228. package/dist/vocabulary/list.js.map +0 -1
  229. package/dist/vocabulary/media.d.ts +0 -25
  230. package/dist/vocabulary/media.d.ts.map +0 -1
  231. package/dist/vocabulary/media.js +0 -42
  232. package/dist/vocabulary/media.js.map +0 -1
  233. package/dist/vocabulary/text.d.ts +0 -57
  234. package/dist/vocabulary/text.d.ts.map +0 -1
  235. package/dist/vocabulary/text.js +0 -49
  236. package/dist/vocabulary/text.js.map +0 -1
  237. package/dist/vocabulary/toTextContent.d.ts +0 -2
  238. package/dist/vocabulary/toTextContent.d.ts.map +0 -1
  239. package/dist/vocabulary/toTextContent.js +0 -6
  240. package/dist/vocabulary/toTextContent.js.map +0 -1
  241. package/src/JSONGenerativeUI.client.tsx +0 -168
  242. package/src/JSONGenerativeUI.server.tsx +0 -39
  243. package/src/JSONGenerativeUI.shared.ts +0 -90
  244. package/src/JSONGenerativeUI.test.tsx +0 -1298
  245. package/src/RadioGroupScope.server.test.tsx +0 -24
  246. package/src/RadioGroupScope.tsx +0 -34
  247. package/src/a2ui/BindingContext.ts +0 -26
  248. package/src/a2ui/PresentRenderer.tsx +0 -83
  249. package/src/a2ui/convert.test.ts +0 -2923
  250. package/src/a2ui/convert.ts +0 -1207
  251. package/src/a2ui/dataModel.ts +0 -69
  252. package/src/a2ui/reducer.test.ts +0 -422
  253. package/src/a2ui/reducer.ts +0 -363
  254. package/src/a2ui/snapshot.test.ts +0 -52
  255. package/src/a2ui/snapshot.ts +0 -52
  256. package/src/a2ui/types.ts +0 -114
  257. package/src/a2ui/valueFunctions.test.ts +0 -271
  258. package/src/a2ui/valueFunctions.ts +0 -523
  259. package/src/actionRegistry.test.ts +0 -90
  260. package/src/actionRegistry.ts +0 -88
  261. package/src/answeredValues.test.ts +0 -33
  262. package/src/answeredValues.tsx +0 -70
  263. package/src/buildPresentParameters.test.ts +0 -246
  264. package/src/buildPresentParameters.ts +0 -130
  265. package/src/constants.ts +0 -17
  266. package/src/convert/boundSpec.test.ts +0 -192
  267. package/src/convert/boundSpec.ts +0 -134
  268. package/src/convert/copyBounded.test.ts +0 -131
  269. package/src/convert/copyBounded.ts +0 -38
  270. package/src/convert/isElement.ts +0 -6
  271. package/src/convert/takeRun.test.ts +0 -64
  272. package/src/convert/takeRun.ts +0 -19
  273. package/src/defineGenerativeComponents.ts +0 -38
  274. package/src/fieldReferences.test.ts +0 -66
  275. package/src/fieldReferences.ts +0 -83
  276. package/src/generativeUIToJSX.test.ts +0 -268
  277. package/src/generativeUIToJSX.ts +0 -152
  278. package/src/index.server.test.ts +0 -106
  279. package/src/index.shared.ts +0 -61
  280. package/src/ir.test.ts +0 -203
  281. package/src/renderGenerativeUI.test.tsx +0 -373
  282. package/src/renderGenerativeUI.tsx +0 -165
  283. package/src/scopeSchema.test.ts +0 -72
  284. package/src/scopeSchema.ts +0 -71
  285. package/src/slack/a2uiSlack.test.ts +0 -198
  286. package/src/slack/constants.ts +0 -194
  287. package/src/slack/datePicker.test.ts +0 -612
  288. package/src/slack/decodeBlockAction.test.ts +0 -233
  289. package/src/slack/decodeBlockAction.ts +0 -232
  290. package/src/slack/fromSlackBlocks.test.ts +0 -1465
  291. package/src/slack/fromSlackBlocks.ts +0 -789
  292. package/src/slack/toSlackBlocks.test.ts +0 -3065
  293. package/src/slack/toSlackBlocks.ts +0 -1818
  294. package/src/slack/types.ts +0 -264
  295. package/src/teams/constants.ts +0 -93
  296. package/src/teams/decodeSubmitData.test.ts +0 -397
  297. package/src/teams/decodeSubmitData.ts +0 -110
  298. package/src/teams/temporal.test.ts +0 -766
  299. package/src/teams/temporalId.ts +0 -56
  300. package/src/teams/toAdaptiveCard.test.ts +0 -1897
  301. package/src/teams/toAdaptiveCard.ts +0 -1057
  302. package/src/teams/toTeamsAttachments.test.ts +0 -74
  303. package/src/teams/toTeamsAttachments.ts +0 -133
  304. package/src/teams/types.ts +0 -266
  305. package/src/temporal.test.ts +0 -337
  306. package/src/temporal.ts +0 -263
  307. package/src/types.ts +0 -119
  308. package/src/vocabulary/alert.test.tsx +0 -427
  309. package/src/vocabulary/alert.tsx +0 -217
  310. package/src/vocabulary/collectFormValues.test.ts +0 -389
  311. package/src/vocabulary/collectFormValues.ts +0 -116
  312. package/src/vocabulary/data.test.tsx +0 -858
  313. package/src/vocabulary/data.tsx +0 -819
  314. package/src/vocabulary/datepicker.dom.test.tsx +0 -1457
  315. package/src/vocabulary/dispatch.ts +0 -83
  316. package/src/vocabulary/fact.test.tsx +0 -94
  317. package/src/vocabulary/fact.tsx +0 -54
  318. package/src/vocabulary/form.test.tsx +0 -113
  319. package/src/vocabulary/form.tsx +0 -39
  320. package/src/vocabulary/formatValue.test.ts +0 -72
  321. package/src/vocabulary/formatValue.ts +0 -127
  322. package/src/vocabulary/icon.test.tsx +0 -64
  323. package/src/vocabulary/icon.tsx +0 -174
  324. package/src/vocabulary/index.test.ts +0 -65
  325. package/src/vocabulary/index.ts +0 -33
  326. package/src/vocabulary/interactive.dispatch.test.tsx +0 -454
  327. package/src/vocabulary/interactive.dom.test.tsx +0 -1747
  328. package/src/vocabulary/interactive.test.tsx +0 -377
  329. package/src/vocabulary/interactive.tsx +0 -1109
  330. package/src/vocabulary/layout.test.tsx +0 -323
  331. package/src/vocabulary/layout.tsx +0 -247
  332. package/src/vocabulary/list.test.tsx +0 -222
  333. package/src/vocabulary/list.tsx +0 -50
  334. package/src/vocabulary/media.test.tsx +0 -85
  335. package/src/vocabulary/media.tsx +0 -56
  336. package/src/vocabulary/text.test.tsx +0 -102
  337. package/src/vocabulary/text.tsx +0 -60
  338. package/src/vocabulary/toTextContent.ts +0 -2
package/src/ir.test.ts DELETED
@@ -1,203 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- normalizeSpec,
4
- normalizeUINode,
5
- type NormalizedUIElement,
6
- type NormalizedUINode,
7
- } from "./ir";
8
-
9
- /** Narrows a normalized node to its element form for property assertions. */
10
- const asElement = (node: NormalizedUINode): NormalizedUIElement => {
11
- if (node === null || typeof node === "string" || typeof node === "number") {
12
- throw new Error(`expected an element, got ${node as unknown as string}`);
13
- }
14
- if (Array.isArray(node)) {
15
- throw new Error("expected an element, got an array");
16
- }
17
- return node as NormalizedUIElement;
18
- };
19
-
20
- describe("normalizeUINode", () => {
21
- describe("text leaf", () => {
22
- it("passes a string through", () => {
23
- expect(normalizeUINode("hello")).toBe("hello");
24
- });
25
- });
26
-
27
- describe("legacy component shape", () => {
28
- it("maps component + nested props to the canonical element", () => {
29
- const node = asElement(
30
- normalizeUINode({
31
- component: "Card",
32
- props: { padding: 4 },
33
- children: ["inside"],
34
- key: "c1",
35
- }),
36
- );
37
-
38
- expect(node).toEqual({
39
- type: "Card",
40
- props: { padding: 4 },
41
- children: ["inside"],
42
- key: "c1",
43
- action: undefined,
44
- });
45
- });
46
-
47
- it("defaults missing props to an empty bag and carries no action", () => {
48
- const node = asElement(normalizeUINode({ component: "Divider" }));
49
- expect(node.props).toEqual({});
50
- expect(node.children).toBeUndefined();
51
- expect(node.action).toBeUndefined();
52
- });
53
- });
54
-
55
- describe("flat $type shape", () => {
56
- it("keeps inline props and strips reserved keys", () => {
57
- const node = asElement(
58
- normalizeUINode({
59
- $type: "Text",
60
- value: "hi",
61
- size: "md",
62
- weight: "semibold",
63
- }),
64
- );
65
-
66
- expect(node.type).toBe("Text");
67
- expect(node.props).toEqual({
68
- value: "hi",
69
- size: "md",
70
- weight: "semibold",
71
- });
72
- expect(node.children).toBeUndefined();
73
- });
74
-
75
- it("lets a component use `type` as an ordinary prop (no collision)", () => {
76
- const node = asElement(
77
- normalizeUINode({
78
- $type: "Button",
79
- type: "submit",
80
- label: "Go",
81
- }),
82
- );
83
-
84
- expect(node.type).toBe("Button");
85
- expect(node.props).toEqual({ type: "submit", label: "Go" });
86
- });
87
-
88
- it("routes a flat `$type` node with a `component` prop to the flat branch, not the legacy one", () => {
89
- const node = asElement(
90
- normalizeUINode({
91
- $type: "Schema",
92
- component: "display-name",
93
- label: "Go",
94
- }),
95
- );
96
-
97
- expect(node.type).toBe("Schema");
98
- expect(node.props).toEqual({ component: "display-name", label: "Go" });
99
- });
100
-
101
- it("threads $action and $key through as action/key", () => {
102
- const node = asElement(
103
- normalizeUINode({
104
- $type: "Button",
105
- $key: "buy",
106
- label: "Purchase",
107
- $action: { type: "purchase", itemId: "sku-1" },
108
- }),
109
- );
110
-
111
- expect(node.key).toBe("buy");
112
- expect(node.action).toEqual({ type: "purchase", itemId: "sku-1" });
113
- expect(node.props).toEqual({ label: "Purchase" });
114
- });
115
-
116
- it("strips the whole $-prefixed namespace from props (not just the named keys)", () => {
117
- const node = asElement(
118
- normalizeUINode({
119
- $type: "Card",
120
- $status: "done",
121
- $custom: 1,
122
- title: "hi",
123
- }),
124
- );
125
-
126
- expect(node.props).toEqual({ title: "hi" });
127
- expect("$status" in node.props).toBe(false);
128
- expect("$custom" in node.props).toBe(false);
129
- });
130
- });
131
-
132
- describe("nesting", () => {
133
- it("recurses through children of both shapes", () => {
134
- const node = asElement(
135
- normalizeUINode({
136
- $type: "Col",
137
- children: [
138
- { component: "Text", props: { value: "a" } },
139
- { $type: "Text", value: "b" },
140
- "leaf",
141
- ],
142
- }),
143
- );
144
-
145
- expect(node.children).toEqual([
146
- {
147
- type: "Text",
148
- props: { value: "a" },
149
- children: undefined,
150
- key: undefined,
151
- action: undefined,
152
- },
153
- {
154
- type: "Text",
155
- props: { value: "b" },
156
- children: undefined,
157
- key: undefined,
158
- action: undefined,
159
- },
160
- "leaf",
161
- ]);
162
- });
163
- });
164
-
165
- describe("malformed input", () => {
166
- it("resolves a node without $type or component to null", () => {
167
- expect(normalizeUINode({ foo: "bar" } as unknown as never)).toBeNull();
168
- });
169
-
170
- it("resolves non-record input: null and boolean to null, number to a text leaf", () => {
171
- expect(normalizeUINode(null as unknown as never)).toBeNull();
172
- expect(normalizeUINode(true as unknown as never)).toBeNull();
173
- expect(normalizeUINode(42 as unknown as never)).toBe(42);
174
- });
175
- });
176
- });
177
-
178
- describe("normalizeSpec", () => {
179
- it("normalizes a single root", () => {
180
- expect(normalizeSpec({ $type: "Text", value: "x" }).root).toEqual({
181
- type: "Text",
182
- props: { value: "x" },
183
- children: undefined,
184
- key: undefined,
185
- action: undefined,
186
- });
187
- });
188
-
189
- it("normalizes a list root", () => {
190
- const { root } = normalizeSpec([{ $type: "Text", value: "a" }, "b"]);
191
- expect(Array.isArray(root)).toBe(true);
192
- expect(root).toEqual([
193
- {
194
- type: "Text",
195
- props: { value: "a" },
196
- children: undefined,
197
- key: undefined,
198
- action: undefined,
199
- },
200
- "b",
201
- ]);
202
- });
203
- });
@@ -1,373 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { renderToStaticMarkup } from "react-dom/server";
3
- import { isValidElement, type ReactElement } from "react";
4
- import { parsePartialJsonObject } from "assistant-stream/utils";
5
- import { z } from "zod";
6
- import { renderGenerativeUI } from "./renderGenerativeUI";
7
- import { buildPresentParameters } from "./buildPresentParameters";
8
- import type { GenerativeUILibrary } from "./types";
9
-
10
- const library: GenerativeUILibrary = {
11
- Card: {
12
- description: "A card container.",
13
- properties: z.object({ title: z.string() }),
14
- render: ({ title, children }: any) => (
15
- <section data-title={title}>{children}</section>
16
- ),
17
- },
18
- Text: {
19
- description: "A run of text.",
20
- properties: z.object({ tone: z.enum(["muted", "normal"]).optional() }),
21
- render: ({ tone, children }: any) => <p data-tone={tone}>{children}</p>,
22
- },
23
- Button: {
24
- description: "A button with its own `type` prop.",
25
- properties: z.object({ type: z.enum(["button", "submit"]) }),
26
- render: ({ type, children }: any) => (
27
- <button type={type}>{children}</button>
28
- ),
29
- },
30
- Live: {
31
- description: "Renders from partial props while streaming.",
32
- properties: z.object({ label: z.string() }),
33
- streamProperties: true,
34
- render: (props) => (
35
- <span data-status={props.$status}>{props.label ?? "…"}</span>
36
- ),
37
- },
38
- };
39
-
40
- describe("renderGenerativeUI", () => {
41
- it("renders a component and passes its props", () => {
42
- const html = renderToStaticMarkup(
43
- <>{renderGenerativeUI({ $type: "Text", tone: "muted" }, library)}</>,
44
- );
45
- expect(html).toBe('<p data-tone="muted"></p>');
46
- });
47
-
48
- it("renders children recursively", () => {
49
- const html = renderToStaticMarkup(
50
- <>
51
- {renderGenerativeUI(
52
- {
53
- $type: "Card",
54
- title: "Hello",
55
- children: [
56
- { $type: "Text", children: "first" },
57
- { $type: "Text", tone: "muted", children: "second" },
58
- ],
59
- },
60
- library,
61
- )}
62
- </>,
63
- );
64
- expect(html).toBe(
65
- '<section data-title="Hello"><p>first</p><p data-tone="muted">second</p></section>',
66
- );
67
- });
68
-
69
- it("uses stable $key for array items and keeps the positional fallback", () => {
70
- const out = renderGenerativeUI(
71
- [
72
- { $type: "Text", $key: "1:Text", children: "first" },
73
- { $type: "Text", children: "second" },
74
- "plain",
75
- { $type: "Text", $key: { id: "bad" }, children: "bad key" },
76
- ],
77
- library,
78
- );
79
-
80
- expect(isValidElement(out)).toBe(true);
81
- const elements = (out as ReactElement<{ children: ReactElement[] }>).props
82
- .children;
83
- expect(Array.isArray(elements)).toBe(true);
84
- expect(elements.every(isValidElement)).toBe(true);
85
- expect(elements.map((element) => element.key)).toEqual([
86
- "model:1:Text",
87
- "1:Text",
88
- "2:#text",
89
- "3:Text",
90
- ]);
91
- });
92
-
93
- it("passes a component's own `type` prop through without collision", () => {
94
- const html = renderToStaticMarkup(
95
- <>
96
- {renderGenerativeUI(
97
- { $type: "Button", type: "submit", children: "Go" },
98
- library,
99
- )}
100
- </>,
101
- );
102
- expect(html).toBe('<button type="submit">Go</button>');
103
- });
104
-
105
- it("renders a string child directly", () => {
106
- const html = renderToStaticMarkup(
107
- <>{renderGenerativeUI({ $type: "Card", children: "plain" }, library)}</>,
108
- );
109
- expect(html).toBe("<section><p></p></section>".replace("<p></p>", "plain"));
110
- });
111
-
112
- it("renders nothing for an unknown component", () => {
113
- const rendered = renderGenerativeUI({ $type: "Missing" }, library);
114
- expect(rendered).toBeNull();
115
- const html = renderToStaticMarkup(<>{rendered}</>);
116
- expect(html).toBe("");
117
- });
118
-
119
- it.each(["plain", 42, null])(
120
- "preserves a primitive root result: %j",
121
- (value) => {
122
- expect(renderGenerativeUI(value, library)).toBe(value);
123
- },
124
- );
125
-
126
- it.each(["toString", "constructor"])(
127
- "treats inherited %s as an unknown component",
128
- (type) => {
129
- const html = renderToStaticMarkup(
130
- <>{renderGenerativeUI({ $type: type }, library)}</>,
131
- );
132
- expect(html).toBe("");
133
- },
134
- );
135
-
136
- it("renders an explicitly registered prototype-named component", () => {
137
- const prototypeNamedLibrary: GenerativeUILibrary = {
138
- ...library,
139
- toString: {
140
- description: "An explicitly registered component.",
141
- properties: z.object({}),
142
- render: () => <span>registered</span>,
143
- },
144
- };
145
-
146
- const html = renderToStaticMarkup(
147
- <>{renderGenerativeUI({ $type: "toString" }, prototypeNamedLibrary)}</>,
148
- );
149
- expect(html).toBe("<span>registered</span>");
150
- });
151
-
152
- it("holds back a node whose `$type` is still streaming", () => {
153
- const error = vi.spyOn(console, "error").mockImplementation(() => {});
154
- try {
155
- for (const argsText of ['{"$type": "', '{"$type": "Tex']) {
156
- const html = renderToStaticMarkup(
157
- <>
158
- {renderGenerativeUI(parsePartialJsonObject(argsText), library, {
159
- status: "streaming",
160
- })}
161
- </>,
162
- );
163
- expect(html).toBe("");
164
- }
165
- expect(error).not.toHaveBeenCalled();
166
- } finally {
167
- error.mockRestore();
168
- }
169
- });
170
-
171
- it("renders completed siblings while a nested `$type` streams", () => {
172
- const error = vi.spyOn(console, "error").mockImplementation(() => {});
173
- try {
174
- const args = parsePartialJsonObject(
175
- '{"$type": "Live", "label": "hi", "children": [{"$type": "Liv',
176
- );
177
- const html = renderToStaticMarkup(
178
- <>{renderGenerativeUI(args, library, { status: "streaming" })}</>,
179
- );
180
- expect(html).toBe('<span data-status="streaming">hi</span>');
181
- expect(error).not.toHaveBeenCalled();
182
- } finally {
183
- error.mockRestore();
184
- }
185
- });
186
-
187
- it("renders nothing for a node without a resolved type", () => {
188
- const html = renderToStaticMarkup(<>{renderGenerativeUI({}, library)}</>);
189
- expect(html).toBe("");
190
- });
191
-
192
- it("bounds deeply nested trees instead of overflowing the stack", () => {
193
- let node: any = { $type: "Text", children: "deep" };
194
- for (let i = 0; i < 5000; i++) node = { $type: "Card", children: node };
195
- expect(() =>
196
- renderToStaticMarkup(<>{renderGenerativeUI(node, library)}</>),
197
- ).not.toThrow();
198
- });
199
-
200
- it("passes the status to render and tolerates partial props while streaming", () => {
201
- const html = renderToStaticMarkup(
202
- <>
203
- {renderGenerativeUI({ $type: "Live" }, library, {
204
- status: "streaming",
205
- })}
206
- </>,
207
- );
208
- expect(html).toBe('<span data-status="streaming">…</span>');
209
- });
210
-
211
- it("gates opt-out components until their props are complete", () => {
212
- const streaming = renderToStaticMarkup(
213
- <>
214
- {renderGenerativeUI({ $type: "Card", title: "x" }, library, {
215
- status: "streaming",
216
- })}
217
- </>,
218
- );
219
- expect(streaming).toBe("");
220
-
221
- const done = renderToStaticMarkup(
222
- <>
223
- {renderGenerativeUI({ $type: "Card", title: "x" }, library, {
224
- status: "done",
225
- })}
226
- </>,
227
- );
228
- expect(done).toBe('<section data-title="x"></section>');
229
- });
230
- });
231
-
232
- describe("buildPresentParameters", () => {
233
- it("produces a flat object schema with no top-level oneOf/anyOf", () => {
234
- const schema = buildPresentParameters(library) as any;
235
-
236
- expect(schema.type).toBe("object");
237
- expect(schema.required).toEqual(["$type"]);
238
- // Tool/function-call schemas reject these at the top level.
239
- expect(schema.oneOf).toBeUndefined();
240
- expect(schema.anyOf).toBeUndefined();
241
-
242
- expect(schema.properties.$type.enum).toEqual([
243
- "Card",
244
- "Text",
245
- "Button",
246
- "Live",
247
- ]);
248
- // each component's description rides along on the $type enum.
249
- expect(schema.properties.$type.description).toContain("Card");
250
- expect(schema.properties.$key).toEqual({
251
- description:
252
- "Stable identity for this UI node. Use it for list items that may reorder.",
253
- anyOf: [{ type: "string" }, { type: "number" }],
254
- });
255
- expect(schema.properties.children.$ref).toBe("#/$defs/children");
256
-
257
- // every component's props are merged into the one flat property bag.
258
- expect(schema.properties.title).toBeDefined(); // Card
259
- expect(schema.properties.tone).toBeDefined(); // Text
260
- expect(schema.properties.type).toBeDefined(); // Button's own `type` prop
261
- expect(schema.properties.label).toBeDefined(); // Live
262
-
263
- // children recurses back into a node.
264
- expect(schema.$defs.children.anyOf).toContainEqual({
265
- $ref: "#/$defs/node",
266
- });
267
- expect(schema.$defs.node.type).toBe("object");
268
- expect(schema.$defs.node.oneOf).toBeUndefined();
269
- });
270
-
271
- it("drops author-declared `$`-prefixed and `children` props, keeping framework fields", () => {
272
- const schema = buildPresentParameters({
273
- Reserved: {
274
- description: "Declares reserved keys that must not leak through.",
275
- properties: z.object({
276
- $type: z.number(),
277
- $key: z.boolean(),
278
- $action: z.string(),
279
- children: z.number(),
280
- label: z.string(),
281
- }),
282
- render: () => null,
283
- },
284
- }) as any;
285
-
286
- // The discriminator is the framework enum, not the author's `$type`; the
287
- // author's `$`-prefixed props and `children` are dropped in favor of the
288
- // framework fields.
289
- expect(schema.properties.$type.enum).toEqual(["Reserved"]);
290
- expect(schema.properties.$key).toEqual({
291
- description:
292
- "Stable identity for this UI node. Use it for list items that may reorder.",
293
- anyOf: [{ type: "string" }, { type: "number" }],
294
- });
295
- expect(schema.properties.$action).toBeUndefined();
296
- expect(schema.properties.children.$ref).toBe("#/$defs/children");
297
- expect(schema.properties.label).toBeDefined();
298
- expect(schema.required).toEqual(["$type"]);
299
- });
300
-
301
- it("keeps props whose names are inherited from Object.prototype", () => {
302
- const schema = buildPresentParameters({
303
- PrototypeProps: {
304
- description: "Declares props that shadow Object.prototype members.",
305
- properties: z.object({
306
- toString: z.string(),
307
- valueOf: z.number(),
308
- constructor: z.boolean(),
309
- }),
310
- render: () => null,
311
- },
312
- }) as any;
313
-
314
- expect(schema.properties.toString.type).toBe("string");
315
- expect(schema.properties.valueOf.type).toBe("number");
316
- expect(schema.properties.constructor.type).toBe("boolean");
317
- });
318
-
319
- it("omits __proto__, which the tool-argument decoder rejects", () => {
320
- const schema = buildPresentParameters({
321
- PrototypeProps: {
322
- description: "Declares an undeliverable prop name.",
323
- properties: z.object({ ["__proto__"]: z.number(), label: z.string() }),
324
- render: () => null,
325
- },
326
- }) as any;
327
-
328
- expect(Object.hasOwn(schema.properties, "__proto__")).toBe(false);
329
- expect(schema.properties.label.type).toBe("string");
330
- });
331
-
332
- it("names every component that declares the same prop in the dev warning", () => {
333
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
334
- try {
335
- buildPresentParameters({
336
- Select: {
337
- description: "Selects an option.",
338
- properties: z.object({ value: z.string() }),
339
- render: () => null,
340
- },
341
- DatePicker: {
342
- description: "Picks a date.",
343
- properties: z.object({ value: z.string() }),
344
- render: () => null,
345
- },
346
- Combobox: {
347
- description: "Chooses from filtered options.",
348
- properties: z.object({ value: z.string() }),
349
- render: () => null,
350
- },
351
- });
352
-
353
- expect(warn).toHaveBeenCalledTimes(1);
354
- expect(warn).toHaveBeenCalledWith(
355
- '[@assistant-ui/react-generative-ui] Prop "value" is declared by "Select", "DatePicker", and "Combobox"; keeping "Select"\'s schema. Rename or align the prop type to avoid an ambiguous schema.',
356
- );
357
- } finally {
358
- warn.mockRestore();
359
- }
360
- });
361
-
362
- it("throws when a component's properties is not an object schema", () => {
363
- expect(() =>
364
- buildPresentParameters({
365
- Bad: {
366
- description: "Non-object props.",
367
- properties: z.string() as never,
368
- render: () => null,
369
- },
370
- }),
371
- ).toThrow(/must be an object schema/);
372
- });
373
- });