@exponential_app/eui 0.0.0-stage → 0.2.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 (272) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +54 -0
  3. package/README.md +377 -2
  4. package/catalog/a11y.json +705 -0
  5. package/catalog/basic-map.json +104 -0
  6. package/catalog/code.json +58 -0
  7. package/catalog/core.catalog.json +5190 -0
  8. package/catalog/core.schema.json +24542 -0
  9. package/catalog/host.json +95 -0
  10. package/catalog/layout.json +20 -0
  11. package/catalog/limits.json +9 -0
  12. package/catalog/locale.json +48 -0
  13. package/catalog/macros.json +4152 -0
  14. package/catalog/recipes.json +64 -0
  15. package/catalog/strings.json +84 -0
  16. package/catalog/style.json +180 -0
  17. package/catalog/theme.schema.json +47754 -0
  18. package/catalog/tokens.json +47 -0
  19. package/conformance/README.md +218 -0
  20. package/conformance/baseline.ts +101 -0
  21. package/conformance/browser-page.ts +146 -0
  22. package/conformance/browser-suites.ts +699 -0
  23. package/conformance/compare.ts +201 -0
  24. package/conformance/dom.ts +97 -0
  25. package/conformance/dump.ts +163 -0
  26. package/conformance/fonts.json +157 -0
  27. package/conformance/manifest.json +233 -0
  28. package/conformance/parity-compare.ts +191 -0
  29. package/conformance/parity-report.schema.json +73 -0
  30. package/conformance/parity.ts +412 -0
  31. package/conformance/pixel.ts +177 -0
  32. package/conformance/primitives-page.ts +49 -0
  33. package/conformance/report.schema.json +66 -0
  34. package/conformance/run.ts +118 -0
  35. package/conformance/voice-wave-child.ts +102 -0
  36. package/dist/chunks/index-5fmaa0xq.js +8384 -0
  37. package/dist/chunks/index-5fmaa0xq.js.map +10 -0
  38. package/dist/chunks/index-62yzr94z.js +2784 -0
  39. package/dist/chunks/index-62yzr94z.js.map +10 -0
  40. package/dist/chunks/index-bg2jsgjq.js +8541 -0
  41. package/dist/chunks/index-bg2jsgjq.js.map +15 -0
  42. package/dist/chunks/index-fy7y2nz8.js +186 -0
  43. package/dist/chunks/index-fy7y2nz8.js.map +9 -0
  44. package/dist/chunks/index-gtk2gwsb.js +4555 -0
  45. package/dist/chunks/index-gtk2gwsb.js.map +31 -0
  46. package/dist/chunks/index-t6qz1770.js +95 -0
  47. package/dist/chunks/index-t6qz1770.js.map +11 -0
  48. package/dist/conformance.js +230462 -0
  49. package/dist/conformance.js.map +10 -0
  50. package/dist/entries/prompt.js +251 -0
  51. package/dist/entries/prompt.js.map +11 -0
  52. package/dist/entries/schema.js +1725 -0
  53. package/dist/entries/schema.js.map +11 -0
  54. package/dist/host/index.js +194 -0
  55. package/dist/host/index.js.map +9 -0
  56. package/dist/index.js +3840 -0
  57. package/dist/index.js.map +29 -0
  58. package/dist/types/src/a11y.d.ts +21 -0
  59. package/dist/types/src/animation.d.ts +76 -0
  60. package/dist/types/src/basic-map.d.ts +38 -0
  61. package/dist/types/src/builder.d.ts +50 -0
  62. package/dist/types/src/catalog.d.ts +54 -0
  63. package/dist/types/src/catalog.described.d.ts +33 -0
  64. package/dist/types/src/catalog.generated.d.ts +904 -0
  65. package/dist/types/src/chart.d.ts +46 -0
  66. package/dist/types/src/code.d.ts +44 -0
  67. package/dist/types/src/color.d.ts +20 -0
  68. package/dist/types/src/conformance.d.ts +39 -0
  69. package/dist/types/src/connector/app.d.ts +14 -0
  70. package/dist/types/src/connector/connector.d.ts +77 -0
  71. package/dist/types/src/connector/index.d.ts +4 -0
  72. package/dist/types/src/css.d.ts +27 -0
  73. package/dist/types/src/define.d.ts +66 -0
  74. package/dist/types/src/direction.d.ts +16 -0
  75. package/dist/types/src/dynamic.d.ts +161 -0
  76. package/dist/types/src/entries/prompt.d.ts +11 -0
  77. package/dist/types/src/entries/schema.d.ts +8 -0
  78. package/dist/types/src/expr.d.ts +71 -0
  79. package/dist/types/src/extension.d.ts +4 -0
  80. package/dist/types/src/format.d.ts +112 -0
  81. package/dist/types/src/geometry.d.ts +40 -0
  82. package/dist/types/src/host/contract.d.ts +225 -0
  83. package/dist/types/src/host/decoders.d.ts +61 -0
  84. package/dist/types/src/host/index.d.ts +15 -0
  85. package/dist/types/src/host/package.d.ts +102 -0
  86. package/dist/types/src/host/policy.d.ts +107 -0
  87. package/dist/types/src/host/record-store.d.ts +103 -0
  88. package/dist/types/src/host/records.d.ts +45 -0
  89. package/dist/types/src/host/router.d.ts +38 -0
  90. package/dist/types/src/host/runtime.d.ts +213 -0
  91. package/dist/types/src/host/signature.d.ts +52 -0
  92. package/dist/types/src/host/sources.d.ts +22 -0
  93. package/dist/types/src/host/tools.d.ts +42 -0
  94. package/dist/types/src/host/transports.d.ts +109 -0
  95. package/dist/types/src/index.d.ts +69 -0
  96. package/dist/types/src/layout.d.ts +20 -0
  97. package/dist/types/src/limits.d.ts +21 -0
  98. package/dist/types/src/list.d.ts +133 -0
  99. package/dist/types/src/locale.d.ts +22 -0
  100. package/dist/types/src/macros.d.ts +29 -0
  101. package/dist/types/src/overlay.d.ts +27 -0
  102. package/dist/types/src/prompt.d.ts +44 -0
  103. package/dist/types/src/qr-code.d.ts +30 -0
  104. package/dist/types/src/recipes.d.ts +17 -0
  105. package/dist/types/src/reducer.d.ts +41 -0
  106. package/dist/types/src/resizable.d.ts +38 -0
  107. package/dist/types/src/schema.d.ts +7 -0
  108. package/dist/types/src/strings.d.ts +13 -0
  109. package/dist/types/src/style.d.ts +201 -0
  110. package/dist/types/src/test-preload.d.ts +1 -0
  111. package/dist/types/src/text-shimmer.d.ts +24 -0
  112. package/dist/types/src/theme-types.d.ts +162 -0
  113. package/dist/types/src/theme.d.ts +75 -0
  114. package/dist/types/src/themes.d.ts +22 -0
  115. package/dist/types/src/tree-guides.d.ts +17 -0
  116. package/dist/types/src/types.d.ts +320 -0
  117. package/dist/types/src/validate.d.ts +31 -0
  118. package/dist/types/src/voice-wave.d.ts +181 -0
  119. package/dist/types/src/web/base-css.d.ts +1 -0
  120. package/dist/types/src/web/box-css.d.ts +63 -0
  121. package/dist/types/src/web/coerce.d.ts +32 -0
  122. package/dist/types/src/web/custom-icons.generated.d.ts +4 -0
  123. package/dist/types/src/web/data.d.ts +107 -0
  124. package/dist/types/src/web/dates.d.ts +12 -0
  125. package/dist/types/src/web/form.d.ts +44 -0
  126. package/dist/types/src/web/host-types.d.ts +126 -0
  127. package/dist/types/src/web/index.d.ts +35 -0
  128. package/dist/types/src/web/inputs.d.ts +55 -0
  129. package/dist/types/src/web/list.d.ts +16 -0
  130. package/dist/types/src/web/listbox.d.ts +14 -0
  131. package/dist/types/src/web/markdown.d.ts +96 -0
  132. package/dist/types/src/web/media.d.ts +63 -0
  133. package/dist/types/src/web/parts.d.ts +4 -0
  134. package/dist/types/src/web/primitives/alert-dialog.d.ts +10 -0
  135. package/dist/types/src/web/primitives/alert.d.ts +9 -0
  136. package/dist/types/src/web/primitives/avatar-color.d.ts +2 -0
  137. package/dist/types/src/web/primitives/avatar.d.ts +13 -0
  138. package/dist/types/src/web/primitives/badge.d.ts +10 -0
  139. package/dist/types/src/web/primitives/breadcrumb.d.ts +10 -0
  140. package/dist/types/src/web/primitives/button.d.ts +6 -0
  141. package/dist/types/src/web/primitives/calendar.d.ts +43 -0
  142. package/dist/types/src/web/primitives/card.d.ts +9 -0
  143. package/dist/types/src/web/primitives/checkbox.d.ts +5 -0
  144. package/dist/types/src/web/primitives/choice-tiles.d.ts +32 -0
  145. package/dist/types/src/web/primitives/cn.d.ts +3 -0
  146. package/dist/types/src/web/primitives/code-block.d.ts +24 -0
  147. package/dist/types/src/web/primitives/command.d.ts +134 -0
  148. package/dist/types/src/web/primitives/context-ring.d.ts +24 -0
  149. package/dist/types/src/web/primitives/dialog-arms.d.ts +6 -0
  150. package/dist/types/src/web/primitives/dialog.d.ts +24 -0
  151. package/dist/types/src/web/primitives/disclosure-header.d.ts +5 -0
  152. package/dist/types/src/web/primitives/dock.d.ts +42 -0
  153. package/dist/types/src/web/primitives/dropdown-menu.d.ts +14 -0
  154. package/dist/types/src/web/primitives/empty-state.d.ts +15 -0
  155. package/dist/types/src/web/primitives/glass-card.d.ts +4 -0
  156. package/dist/types/src/web/primitives/hover-card.d.ts +7 -0
  157. package/dist/types/src/web/primitives/icon-disc.d.ts +12 -0
  158. package/dist/types/src/web/primitives/index.d.ts +50 -0
  159. package/dist/types/src/web/primitives/input.d.ts +5 -0
  160. package/dist/types/src/web/primitives/kbd.d.ts +4 -0
  161. package/dist/types/src/web/primitives/label.d.ts +3 -0
  162. package/dist/types/src/web/primitives/measure.d.ts +16 -0
  163. package/dist/types/src/web/primitives/menu-surface.d.ts +17 -0
  164. package/dist/types/src/web/primitives/meter.d.ts +21 -0
  165. package/dist/types/src/web/primitives/popover.d.ts +3 -0
  166. package/dist/types/src/web/primitives/progress-ring.d.ts +5 -0
  167. package/dist/types/src/web/primitives/progress.d.ts +8 -0
  168. package/dist/types/src/web/primitives/scroll-area.d.ts +49 -0
  169. package/dist/types/src/web/primitives/segmented-control.d.ts +5 -0
  170. package/dist/types/src/web/primitives/select.d.ts +47 -0
  171. package/dist/types/src/web/primitives/separator.d.ts +3 -0
  172. package/dist/types/src/web/primitives/sheet-chrome.d.ts +4 -0
  173. package/dist/types/src/web/primitives/sheet.d.ts +20 -0
  174. package/dist/types/src/web/primitives/skeleton.d.ts +3 -0
  175. package/dist/types/src/web/primitives/spinner.d.ts +3 -0
  176. package/dist/types/src/web/primitives/switch.d.ts +4 -0
  177. package/dist/types/src/web/primitives/table.d.ts +11 -0
  178. package/dist/types/src/web/primitives/tabs.d.ts +13 -0
  179. package/dist/types/src/web/primitives/textarea.d.ts +3 -0
  180. package/dist/types/src/web/primitives/toast.d.ts +163 -0
  181. package/dist/types/src/web/primitives/tooltip.d.ts +3 -0
  182. package/dist/types/src/web/primitives/truncate.d.ts +7 -0
  183. package/dist/types/src/web/primitives/typeahead-portal.d.ts +12 -0
  184. package/dist/types/src/web/primitives/voice-wave.d.ts +50 -0
  185. package/dist/types/src/web/round5.d.ts +107 -0
  186. package/dist/types/src/web/share.d.ts +6 -0
  187. package/dist/types/src/web/table.d.ts +20 -0
  188. package/dist/types/src/web/theme-css.d.ts +69 -0
  189. package/dist/types/src/web/urls.d.ts +21 -0
  190. package/dist/web/index.js +2565 -0
  191. package/dist/web/index.js.map +28 -0
  192. package/dist/web/primitives/index.js +1476 -0
  193. package/dist/web/primitives/index.js.map +57 -0
  194. package/docs/components.generated.json +6783 -0
  195. package/docs/package-format.md +182 -0
  196. package/docs/round-1-contract.md +679 -0
  197. package/docs/round-2-contract.md +476 -0
  198. package/docs/round-3-contract.md +174 -0
  199. package/docs/round-5-contract.md +268 -0
  200. package/docs/themes.generated.json +3647 -0
  201. package/fixtures/animations.json +2890 -0
  202. package/fixtures/bench-list.json +58 -0
  203. package/fixtures/bind-time.json +54734 -0
  204. package/fixtures/catalog-basic-map.json +1692 -0
  205. package/fixtures/catalog-components.json +10293 -0
  206. package/fixtures/catalog-extension.json +620 -0
  207. package/fixtures/catalog-macros.json +51805 -0
  208. package/fixtures/code-tokens.json +1993 -0
  209. package/fixtures/conformance-baseline.json +59016 -0
  210. package/fixtures/conformance-cases.json +49 -0
  211. package/fixtures/conformance-known.json +2111 -0
  212. package/fixtures/control-geometry.json +5129 -0
  213. package/fixtures/dashboard.data.json +74 -0
  214. package/fixtures/dashboard.json +781 -0
  215. package/fixtures/demo-surface.json +47 -0
  216. package/fixtures/format.json +870 -0
  217. package/fixtures/host-package.json +1262 -0
  218. package/fixtures/host-policy.json +1209 -0
  219. package/fixtures/host-router.json +2034 -0
  220. package/fixtures/host-transport.json +590 -0
  221. package/fixtures/interactions-round1.json +1058 -0
  222. package/fixtures/kitchen-sink.expanded.json +11972 -0
  223. package/fixtures/kitchen-sink.json +3140 -0
  224. package/fixtures/layout-geometry-round1.json +7278 -0
  225. package/fixtures/layout-geometry.json +2072 -0
  226. package/fixtures/overlay-geometry.json +272 -0
  227. package/fixtures/pointer.json +89 -0
  228. package/fixtures/primitive-specimens.json +59 -0
  229. package/fixtures/prompt-budget.json +33 -0
  230. package/fixtures/qr-code.json +480 -0
  231. package/fixtures/resizable.json +714 -0
  232. package/fixtures/round4-contract.json +2985 -0
  233. package/fixtures/specimens.json +11168 -0
  234. package/fixtures/style-conditions.json +336 -0
  235. package/fixtures/template-items.json +2215 -0
  236. package/fixtures/text-direction.json +309 -0
  237. package/fixtures/text-shimmer.json +96 -0
  238. package/fixtures/theme-extends.json +573 -0
  239. package/fixtures/theme-invalid.json +1300 -0
  240. package/fixtures/theme-recipes.json +61762 -0
  241. package/fixtures/theme-resolved.json +25969 -0
  242. package/fixtures/toast-stack.json +215 -0
  243. package/fixtures/tree-guides.json +224 -0
  244. package/fixtures/virtual-list.json +733 -0
  245. package/fixtures/voice-wave.json +19236 -0
  246. package/generated/ExponentialUICatalog.generated.kt +67 -0
  247. package/generated/ExponentialUICatalog.generated.swift +67 -0
  248. package/generated/ExponentialUIThemes.generated.kt +23 -0
  249. package/generated/ExponentialUIThemes.generated.swift +23 -0
  250. package/generated/catalog.descriptions.generated.rs +6 -0
  251. package/generated/catalog.generated.rs +80 -0
  252. package/generated/catalog.themes.generated.rs +34 -0
  253. package/generated/core.descriptions.json +2726 -0
  254. package/generated/core.runtime.json +3944 -0
  255. package/package.json +87 -4
  256. package/themes/exponential.recipes.json +49 -0
  257. package/themes/exponential.theme.json +768 -0
  258. package/themes/neutral.theme.json +6178 -0
  259. package/themes/orange.theme.json +43 -0
  260. package/themes/playful.theme.json +219 -0
  261. package/vendor/a2ui/LICENSE.txt +203 -0
  262. package/vendor/a2ui/README.md +27 -0
  263. package/vendor/a2ui/v0_9/basic_catalog.json +1387 -0
  264. package/vendor/a2ui/v0_9/basic_catalog_rules.txt +5 -0
  265. package/vendor/a2ui/v0_9/client_capabilities.json +97 -0
  266. package/vendor/a2ui/v0_9/client_to_server.json +104 -0
  267. package/vendor/a2ui/v0_9/common_types.json +315 -0
  268. package/vendor/a2ui/v0_9/server_to_client.json +132 -0
  269. package/vendor/json-render/LICENSE.txt +201 -0
  270. package/vendor/json-render/README.md +13 -0
  271. package/vendor/nayuki-qr/LICENSE.txt +20 -0
  272. package/vendor/nayuki-qr/README.md +10 -0
@@ -0,0 +1,1209 @@
1
+ {
2
+ "$comment": "VAPP-91: the pure host rules (catalog/host.json functions, urls, media, sources, negotiation). Inputs hand-written; `expected` filled by the generator from src/host/{policy,sources,package}.ts. VAPP-122: `sourceParams` = sourceWithParams(source, params) → the source URI the `bind` action follows.",
3
+ "functions": [
4
+ {
5
+ "name": "a built-in always runs",
6
+ "policy": {
7
+ "deny": [
8
+ "*"
9
+ ]
10
+ },
11
+ "fn": "formatDate",
12
+ "registered": false,
13
+ "expected": "allow"
14
+ },
15
+ {
16
+ "name": "openUrl is a built-in",
17
+ "fn": "openUrl",
18
+ "registered": false,
19
+ "expected": "allow"
20
+ },
21
+ {
22
+ "name": "an unregistered name",
23
+ "fn": "harness.mcp",
24
+ "registered": false,
25
+ "expected": "not_found"
26
+ },
27
+ {
28
+ "name": "registered, no policy = allow",
29
+ "fn": "harness.toast",
30
+ "registered": true,
31
+ "expected": "allow"
32
+ },
33
+ {
34
+ "name": "a prefix ask",
35
+ "policy": {
36
+ "ask": [
37
+ "harness.*"
38
+ ]
39
+ },
40
+ "fn": "harness.mcp",
41
+ "registered": true,
42
+ "expected": "ask"
43
+ },
44
+ {
45
+ "name": "allow beats ask",
46
+ "policy": {
47
+ "allow": [
48
+ "harness.toast"
49
+ ],
50
+ "ask": [
51
+ "harness.*"
52
+ ]
53
+ },
54
+ "fn": "harness.toast",
55
+ "registered": true,
56
+ "expected": "allow"
57
+ },
58
+ {
59
+ "name": "deny beats allow",
60
+ "policy": {
61
+ "allow": [
62
+ "harness.*"
63
+ ],
64
+ "deny": [
65
+ "harness.mcp"
66
+ ]
67
+ },
68
+ "fn": "harness.mcp",
69
+ "registered": true,
70
+ "expected": "deny"
71
+ },
72
+ {
73
+ "name": "the default when nothing matches",
74
+ "policy": {
75
+ "allow": [
76
+ "harness.toast"
77
+ ],
78
+ "default": "deny"
79
+ },
80
+ "fn": "harness.openIssue",
81
+ "registered": true,
82
+ "expected": "deny"
83
+ },
84
+ {
85
+ "name": "a package allowlist",
86
+ "policy": {
87
+ "allow": [
88
+ "harness.toast",
89
+ "acme.*"
90
+ ],
91
+ "default": "deny"
92
+ },
93
+ "fn": "acme.refresh",
94
+ "registered": true,
95
+ "expected": "allow"
96
+ },
97
+ {
98
+ "name": "a star alone matches everything",
99
+ "policy": {
100
+ "ask": [
101
+ "*"
102
+ ]
103
+ },
104
+ "fn": "x",
105
+ "registered": true,
106
+ "expected": "ask"
107
+ }
108
+ ],
109
+ "combine": [
110
+ {
111
+ "a": "allow",
112
+ "b": "allow",
113
+ "expected": "allow"
114
+ },
115
+ {
116
+ "a": "allow",
117
+ "b": "ask",
118
+ "expected": "ask"
119
+ },
120
+ {
121
+ "a": "allow",
122
+ "b": "deny",
123
+ "expected": "deny"
124
+ },
125
+ {
126
+ "a": "allow",
127
+ "b": "not_found",
128
+ "expected": "not_found"
129
+ },
130
+ {
131
+ "a": "ask",
132
+ "b": "allow",
133
+ "expected": "ask"
134
+ },
135
+ {
136
+ "a": "ask",
137
+ "b": "ask",
138
+ "expected": "ask"
139
+ },
140
+ {
141
+ "a": "ask",
142
+ "b": "deny",
143
+ "expected": "deny"
144
+ },
145
+ {
146
+ "a": "ask",
147
+ "b": "not_found",
148
+ "expected": "not_found"
149
+ },
150
+ {
151
+ "a": "deny",
152
+ "b": "allow",
153
+ "expected": "deny"
154
+ },
155
+ {
156
+ "a": "deny",
157
+ "b": "ask",
158
+ "expected": "deny"
159
+ },
160
+ {
161
+ "a": "deny",
162
+ "b": "deny",
163
+ "expected": "deny"
164
+ },
165
+ {
166
+ "a": "deny",
167
+ "b": "not_found",
168
+ "expected": "not_found"
169
+ },
170
+ {
171
+ "a": "not_found",
172
+ "b": "allow",
173
+ "expected": "not_found"
174
+ },
175
+ {
176
+ "a": "not_found",
177
+ "b": "ask",
178
+ "expected": "not_found"
179
+ },
180
+ {
181
+ "a": "not_found",
182
+ "b": "deny",
183
+ "expected": "not_found"
184
+ },
185
+ {
186
+ "a": "not_found",
187
+ "b": "not_found",
188
+ "expected": "not_found"
189
+ }
190
+ ],
191
+ "urls": [
192
+ {
193
+ "name": "https",
194
+ "url": "https://exponential.at/docs",
195
+ "expected": {
196
+ "allowed": true,
197
+ "url": "https://exponential.at/docs"
198
+ }
199
+ },
200
+ {
201
+ "name": "mailto",
202
+ "url": "mailto:hi@exponential.at",
203
+ "expected": {
204
+ "allowed": true,
205
+ "url": "mailto:hi@exponential.at"
206
+ }
207
+ },
208
+ {
209
+ "name": "javascript is never a default scheme",
210
+ "url": "javascript:alert(1)",
211
+ "expected": {
212
+ "allowed": false,
213
+ "url": "javascript:alert(1)",
214
+ "reason": "scheme"
215
+ }
216
+ },
217
+ {
218
+ "name": "data is not a default scheme",
219
+ "url": "data:text/html,hi",
220
+ "expected": {
221
+ "allowed": false,
222
+ "url": "data:text/html,hi",
223
+ "reason": "scheme"
224
+ }
225
+ },
226
+ {
227
+ "name": "file is not a default scheme",
228
+ "url": "file:///etc/passwd",
229
+ "expected": {
230
+ "allowed": false,
231
+ "url": "file:///etc/passwd",
232
+ "reason": "scheme"
233
+ }
234
+ },
235
+ {
236
+ "name": "upper-case scheme",
237
+ "url": "HTTPS://Example.com/A",
238
+ "expected": {
239
+ "allowed": true,
240
+ "url": "https://example.com/A"
241
+ }
242
+ },
243
+ {
244
+ "name": "relative without a base",
245
+ "url": "/api/attachments/a1",
246
+ "expected": {
247
+ "allowed": false,
248
+ "reason": "invalid"
249
+ }
250
+ },
251
+ {
252
+ "name": "relative against the base",
253
+ "policy": {
254
+ "baseUrl": "https://app.exponential.at"
255
+ },
256
+ "url": "/api/attachments/a1",
257
+ "expected": {
258
+ "allowed": true,
259
+ "url": "https://app.exponential.at/api/attachments/a1"
260
+ }
261
+ },
262
+ {
263
+ "name": "a host allowlist",
264
+ "policy": {
265
+ "hosts": [
266
+ "exponential.at",
267
+ "*.exponential.at"
268
+ ]
269
+ },
270
+ "url": "https://ui.exponential.at/catalogs",
271
+ "expected": {
272
+ "allowed": true,
273
+ "url": "https://ui.exponential.at/catalogs"
274
+ }
275
+ },
276
+ {
277
+ "name": "outside the host allowlist",
278
+ "policy": {
279
+ "hosts": [
280
+ "exponential.at"
281
+ ]
282
+ },
283
+ "url": "https://evil.example/",
284
+ "expected": {
285
+ "allowed": false,
286
+ "url": "https://evil.example/",
287
+ "reason": "host"
288
+ }
289
+ },
290
+ {
291
+ "name": "a wildcard host does not match its apex",
292
+ "policy": {
293
+ "hosts": [
294
+ "*.exponential.at"
295
+ ]
296
+ },
297
+ "url": "https://exponential.at/",
298
+ "expected": {
299
+ "allowed": false,
300
+ "url": "https://exponential.at/",
301
+ "reason": "host"
302
+ }
303
+ },
304
+ {
305
+ "name": "hosts do not gate mailto",
306
+ "policy": {
307
+ "hosts": [
308
+ "exponential.at"
309
+ ]
310
+ },
311
+ "url": "mailto:a@b.c",
312
+ "expected": {
313
+ "allowed": true,
314
+ "url": "mailto:a@b.c"
315
+ }
316
+ },
317
+ {
318
+ "name": "a custom scheme list",
319
+ "policy": {
320
+ "schemes": [
321
+ "https",
322
+ "exponential"
323
+ ]
324
+ },
325
+ "url": "exponential://issue/EXP-1",
326
+ "expected": {
327
+ "allowed": true,
328
+ "url": "exponential://issue/EXP-1"
329
+ }
330
+ },
331
+ {
332
+ "name": "garbage",
333
+ "url": "::not a url",
334
+ "expected": {
335
+ "allowed": false,
336
+ "reason": "invalid"
337
+ }
338
+ },
339
+ {
340
+ "name": "a data url is not a link scheme",
341
+ "url": "data:text/html,<script>alert(1)</script>",
342
+ "expected": {
343
+ "allowed": false,
344
+ "url": "data:text/html,<script>alert(1)</script>",
345
+ "reason": "scheme"
346
+ }
347
+ },
348
+ {
349
+ "name": "a host allow-list holds for a relative href",
350
+ "policy": {
351
+ "baseUrl": "https://evil.example",
352
+ "hosts": [
353
+ "exponential.at"
354
+ ]
355
+ },
356
+ "url": "/x",
357
+ "expected": {
358
+ "allowed": false,
359
+ "url": "https://evil.example/x",
360
+ "reason": "host"
361
+ }
362
+ },
363
+ {
364
+ "name": "a url with balanced parentheses",
365
+ "url": "https://en.wikipedia.org/wiki/A_(b)",
366
+ "expected": {
367
+ "allowed": true,
368
+ "url": "https://en.wikipedia.org/wiki/A_(b)"
369
+ }
370
+ },
371
+ {
372
+ "name": "VAPP-107: loopback is private by default",
373
+ "url": "http://127.0.0.1:3000/api/attachments/a1",
374
+ "expected": {
375
+ "allowed": false,
376
+ "url": "http://127.0.0.1:3000/api/attachments/a1",
377
+ "reason": "private"
378
+ }
379
+ },
380
+ {
381
+ "name": "localhost is private",
382
+ "url": "http://localhost:5173/",
383
+ "expected": {
384
+ "allowed": false,
385
+ "url": "http://localhost:5173/",
386
+ "reason": "private"
387
+ }
388
+ },
389
+ {
390
+ "name": "a localhost subdomain is private",
391
+ "url": "https://app.localhost/",
392
+ "expected": {
393
+ "allowed": false,
394
+ "url": "https://app.localhost/",
395
+ "reason": "private"
396
+ }
397
+ },
398
+ {
399
+ "name": "an IPv6 loopback is private",
400
+ "url": "http://[::1]:8080/",
401
+ "expected": {
402
+ "allowed": false,
403
+ "url": "http://[::1]:8080/",
404
+ "reason": "private"
405
+ }
406
+ },
407
+ {
408
+ "name": "the unspecified addresses are private",
409
+ "url": "http://0.0.0.0/",
410
+ "expected": {
411
+ "allowed": false,
412
+ "url": "http://0.0.0.0/",
413
+ "reason": "private"
414
+ }
415
+ },
416
+ {
417
+ "name": "the 10/8 block is private",
418
+ "url": "http://10.0.0.5/x.png",
419
+ "expected": {
420
+ "allowed": false,
421
+ "url": "http://10.0.0.5/x.png",
422
+ "reason": "private"
423
+ }
424
+ },
425
+ {
426
+ "name": "the 172.16/12 block is private",
427
+ "url": "http://172.31.255.254/",
428
+ "expected": {
429
+ "allowed": false,
430
+ "url": "http://172.31.255.254/",
431
+ "reason": "private"
432
+ }
433
+ },
434
+ {
435
+ "name": "172.32 is public",
436
+ "url": "http://172.32.0.1/",
437
+ "expected": {
438
+ "allowed": true,
439
+ "url": "http://172.32.0.1/"
440
+ }
441
+ },
442
+ {
443
+ "name": "the 192.168/16 block is private",
444
+ "url": "https://192.168.1.10/cam.jpg",
445
+ "expected": {
446
+ "allowed": false,
447
+ "url": "https://192.168.1.10/cam.jpg",
448
+ "reason": "private"
449
+ }
450
+ },
451
+ {
452
+ "name": "link-local is private (the cloud metadata endpoint)",
453
+ "url": "http://169.254.169.254/latest/meta-data/",
454
+ "expected": {
455
+ "allowed": false,
456
+ "url": "http://169.254.169.254/latest/meta-data/",
457
+ "reason": "private"
458
+ }
459
+ },
460
+ {
461
+ "name": "IPv6 link-local is private",
462
+ "url": "http://[fe80::1]/",
463
+ "expected": {
464
+ "allowed": false,
465
+ "url": "http://[fe80::1]/",
466
+ "reason": "private"
467
+ }
468
+ },
469
+ {
470
+ "name": "IPv6 unique-local is private",
471
+ "url": "http://[fd12:3456::1]/",
472
+ "expected": {
473
+ "allowed": false,
474
+ "url": "http://[fd12:3456::1]/",
475
+ "reason": "private"
476
+ }
477
+ },
478
+ {
479
+ "name": "an IPv4-mapped IPv6 address of a private IPv4 is private",
480
+ "url": "http://[::ffff:127.0.0.1]/",
481
+ "expected": {
482
+ "allowed": false,
483
+ "url": "http://[::ffff:7f00:1]/",
484
+ "reason": "private"
485
+ }
486
+ },
487
+ {
488
+ "name": "a decimal IPv4 form normalizes before the check",
489
+ "url": "http://2130706433/",
490
+ "expected": {
491
+ "allowed": false,
492
+ "url": "http://127.0.0.1/",
493
+ "reason": "private"
494
+ }
495
+ },
496
+ {
497
+ "name": "a hex IPv4 form normalizes before the check",
498
+ "url": "http://0xc0.0xa8.1.1/",
499
+ "expected": {
500
+ "allowed": false,
501
+ "url": "http://192.168.1.1/",
502
+ "reason": "private"
503
+ }
504
+ },
505
+ {
506
+ "name": "a .local name is private",
507
+ "url": "http://printer.local/status.png",
508
+ "expected": {
509
+ "allowed": false,
510
+ "url": "http://printer.local/status.png",
511
+ "reason": "private"
512
+ }
513
+ },
514
+ {
515
+ "name": "release train 2026-10-23: the CGNAT block is private",
516
+ "url": "http://100.64.0.1/",
517
+ "expected": {
518
+ "allowed": false,
519
+ "url": "http://100.64.0.1/",
520
+ "reason": "private"
521
+ }
522
+ },
523
+ {
524
+ "name": "the CGNAT block's top edge is private",
525
+ "url": "http://100.127.255.255/",
526
+ "expected": {
527
+ "allowed": false,
528
+ "url": "http://100.127.255.255/",
529
+ "reason": "private"
530
+ }
531
+ },
532
+ {
533
+ "name": "just below CGNAT is public",
534
+ "url": "http://100.63.255.255/",
535
+ "expected": {
536
+ "allowed": true,
537
+ "url": "http://100.63.255.255/"
538
+ }
539
+ },
540
+ {
541
+ "name": "just above CGNAT is public",
542
+ "url": "http://100.128.0.1/",
543
+ "expected": {
544
+ "allowed": true,
545
+ "url": "http://100.128.0.1/"
546
+ }
547
+ },
548
+ {
549
+ "name": "an IPv4-compatible IPv6 address of a private IPv4 is private",
550
+ "url": "http://[::7f00:1]/",
551
+ "expected": {
552
+ "allowed": false,
553
+ "url": "http://[::7f00:1]/",
554
+ "reason": "private"
555
+ }
556
+ },
557
+ {
558
+ "name": "a NAT64 address of a private IPv4 is private",
559
+ "url": "http://[64:ff9b::127.0.0.1]/",
560
+ "expected": {
561
+ "allowed": false,
562
+ "url": "http://[64:ff9b::7f00:1]/",
563
+ "reason": "private"
564
+ }
565
+ },
566
+ {
567
+ "name": "a 6to4 address of a private IPv4 is private",
568
+ "url": "http://[2002:7f00:1::]/",
569
+ "expected": {
570
+ "allowed": false,
571
+ "url": "http://[2002:7f00:1::]/",
572
+ "reason": "private"
573
+ }
574
+ },
575
+ {
576
+ "name": "a 6to4 address of a public IPv4 is public",
577
+ "url": "http://[2002:808:808::1]/",
578
+ "expected": {
579
+ "allowed": true,
580
+ "url": "http://[2002:808:808::1]/"
581
+ }
582
+ },
583
+ {
584
+ "name": "a NAT64 address of a public IPv4 is public",
585
+ "url": "http://[64:ff9b::808:808]/",
586
+ "expected": {
587
+ "allowed": true,
588
+ "url": "http://[64:ff9b::808:808]/"
589
+ }
590
+ },
591
+ {
592
+ "name": "a public address is not private",
593
+ "url": "http://8.8.8.8/",
594
+ "expected": {
595
+ "allowed": true,
596
+ "url": "http://8.8.8.8/"
597
+ }
598
+ },
599
+ {
600
+ "name": "a public name is not resolved",
601
+ "url": "https://intranet.corp.example/",
602
+ "expected": {
603
+ "allowed": true,
604
+ "url": "https://intranet.corp.example/"
605
+ }
606
+ },
607
+ {
608
+ "name": "a relative url against a private base is private",
609
+ "policy": {
610
+ "baseUrl": "http://localhost:3000"
611
+ },
612
+ "url": "/api/attachments/a1",
613
+ "expected": {
614
+ "allowed": false,
615
+ "url": "http://localhost:3000/api/attachments/a1",
616
+ "reason": "private"
617
+ }
618
+ },
619
+ {
620
+ "name": "the host opts localhost back in",
621
+ "policy": {
622
+ "allowPrivate": [
623
+ "localhost"
624
+ ]
625
+ },
626
+ "url": "http://localhost:3000/api/attachments/a1",
627
+ "expected": {
628
+ "allowed": true,
629
+ "url": "http://localhost:3000/api/attachments/a1"
630
+ }
631
+ },
632
+ {
633
+ "name": "an opt-in for one host does not cover another",
634
+ "policy": {
635
+ "allowPrivate": [
636
+ "localhost"
637
+ ]
638
+ },
639
+ "url": "http://127.0.0.1:3000/",
640
+ "expected": {
641
+ "allowed": false,
642
+ "url": "http://127.0.0.1:3000/",
643
+ "reason": "private"
644
+ }
645
+ },
646
+ {
647
+ "name": "an opt-in by suffix",
648
+ "policy": {
649
+ "allowPrivate": [
650
+ "*.local"
651
+ ]
652
+ },
653
+ "url": "http://printer.local/",
654
+ "expected": {
655
+ "allowed": true,
656
+ "url": "http://printer.local/"
657
+ }
658
+ },
659
+ {
660
+ "name": "a star opts every private address in",
661
+ "policy": {
662
+ "allowPrivate": [
663
+ "*"
664
+ ]
665
+ },
666
+ "url": "http://10.0.0.5/",
667
+ "expected": {
668
+ "allowed": true,
669
+ "url": "http://10.0.0.5/"
670
+ }
671
+ },
672
+ {
673
+ "name": "a hosts allowlist does not opt a private host in",
674
+ "policy": {
675
+ "hosts": [
676
+ "localhost"
677
+ ]
678
+ },
679
+ "url": "http://localhost/",
680
+ "expected": {
681
+ "allowed": false,
682
+ "url": "http://localhost/",
683
+ "reason": "private"
684
+ }
685
+ },
686
+ {
687
+ "name": "outside the allowlist reads host before private",
688
+ "policy": {
689
+ "hosts": [
690
+ "exponential.at"
691
+ ]
692
+ },
693
+ "url": "http://localhost/",
694
+ "expected": {
695
+ "allowed": false,
696
+ "url": "http://localhost/",
697
+ "reason": "host"
698
+ }
699
+ },
700
+ {
701
+ "name": "mailto is never private",
702
+ "url": "mailto:root@localhost",
703
+ "expected": {
704
+ "allowed": true,
705
+ "url": "mailto:root@localhost"
706
+ }
707
+ },
708
+ {
709
+ "name": "a custom scheme is never private",
710
+ "policy": {
711
+ "schemes": [
712
+ "exponential"
713
+ ]
714
+ },
715
+ "url": "exponential://localhost/issue/EXP-1",
716
+ "expected": {
717
+ "allowed": true,
718
+ "url": "exponential://localhost/issue/EXP-1"
719
+ }
720
+ },
721
+ {
722
+ "name": "https only",
723
+ "policy": {
724
+ "schemes": [
725
+ "https"
726
+ ]
727
+ },
728
+ "url": "http://exponential.at/",
729
+ "expected": {
730
+ "allowed": false,
731
+ "url": "http://exponential.at/",
732
+ "reason": "scheme"
733
+ }
734
+ },
735
+ {
736
+ "name": "https only keeps https",
737
+ "policy": {
738
+ "schemes": [
739
+ "https"
740
+ ]
741
+ },
742
+ "url": "https://exponential.at/",
743
+ "expected": {
744
+ "allowed": true,
745
+ "url": "https://exponential.at/"
746
+ }
747
+ },
748
+ {
749
+ "name": "https only drops mailto and tel",
750
+ "policy": {
751
+ "schemes": [
752
+ "https"
753
+ ]
754
+ },
755
+ "url": "tel:+4312345",
756
+ "expected": {
757
+ "allowed": false,
758
+ "url": "tel:+4312345",
759
+ "reason": "scheme"
760
+ }
761
+ }
762
+ ],
763
+ "media": [
764
+ {
765
+ "name": "relative attachment gets the bearer",
766
+ "options": {
767
+ "baseUrl": "https://app.exponential.at",
768
+ "rules": [
769
+ {
770
+ "prefix": "https://app.exponential.at/api/attachments/",
771
+ "headers": {
772
+ "authorization": "Bearer expu_test"
773
+ }
774
+ }
775
+ ]
776
+ },
777
+ "url": "/api/attachments/a1?poster=1",
778
+ "expected": {
779
+ "url": "https://app.exponential.at/api/attachments/a1?poster=1",
780
+ "headers": {
781
+ "authorization": "Bearer expu_test"
782
+ }
783
+ }
784
+ },
785
+ {
786
+ "name": "another origin gets nothing",
787
+ "options": {
788
+ "baseUrl": "https://app.exponential.at",
789
+ "rules": [
790
+ {
791
+ "prefix": "https://app.exponential.at/api/",
792
+ "headers": {
793
+ "authorization": "Bearer t"
794
+ }
795
+ }
796
+ ]
797
+ },
798
+ "url": "https://cdn.example/x.png",
799
+ "expected": {
800
+ "url": "https://cdn.example/x.png",
801
+ "headers": {}
802
+ }
803
+ },
804
+ {
805
+ "name": "later rules win per header",
806
+ "options": {
807
+ "rules": [
808
+ {
809
+ "prefix": "https://a.example/",
810
+ "headers": {
811
+ "x-a": "1",
812
+ "x-b": "1"
813
+ }
814
+ },
815
+ {
816
+ "prefix": "https://a.example/p/",
817
+ "headers": {
818
+ "x-b": "2"
819
+ }
820
+ }
821
+ ]
822
+ },
823
+ "url": "https://a.example/p/q.png",
824
+ "expected": {
825
+ "url": "https://a.example/p/q.png",
826
+ "headers": {
827
+ "x-a": "1",
828
+ "x-b": "2"
829
+ }
830
+ }
831
+ },
832
+ {
833
+ "name": "relative without a base",
834
+ "options": {},
835
+ "url": "x.png",
836
+ "expected": null
837
+ },
838
+ {
839
+ "name": "javascript is never a media scheme",
840
+ "options": {},
841
+ "url": "javascript:alert(1)",
842
+ "expected": null
843
+ },
844
+ {
845
+ "name": "file is denied by default",
846
+ "options": {},
847
+ "url": "file:///etc/passwd",
848
+ "expected": null
849
+ },
850
+ {
851
+ "name": "a relative path against a file base stays denied",
852
+ "options": {
853
+ "baseUrl": "file:///Users/me/"
854
+ },
855
+ "url": "/etc/passwd",
856
+ "expected": null
857
+ },
858
+ {
859
+ "name": "file only when the host lists it",
860
+ "options": {
861
+ "schemes": [
862
+ "file"
863
+ ]
864
+ },
865
+ "url": "file:///tmp/a.png",
866
+ "expected": {
867
+ "url": "file:///tmp/a.png",
868
+ "headers": {}
869
+ }
870
+ },
871
+ {
872
+ "name": "a data image",
873
+ "options": {},
874
+ "url": "data:image/png;base64,iVBORw0KGgo=",
875
+ "expected": {
876
+ "url": "data:image/png;base64,iVBORw0KGgo=",
877
+ "headers": {}
878
+ }
879
+ },
880
+ {
881
+ "name": "the media host allow-list",
882
+ "options": {
883
+ "hosts": [
884
+ "*.exponential.at"
885
+ ]
886
+ },
887
+ "url": "https://cdn.example/x.png",
888
+ "expected": null
889
+ },
890
+ {
891
+ "name": "an allowed media host",
892
+ "options": {
893
+ "hosts": [
894
+ "*.exponential.at"
895
+ ],
896
+ "rules": [
897
+ {
898
+ "prefix": "https://app.exponential.at/",
899
+ "headers": {
900
+ "authorization": "Bearer t"
901
+ }
902
+ }
903
+ ]
904
+ },
905
+ "url": "https://app.exponential.at/a.png",
906
+ "expected": {
907
+ "url": "https://app.exponential.at/a.png",
908
+ "headers": {
909
+ "authorization": "Bearer t"
910
+ }
911
+ }
912
+ },
913
+ {
914
+ "name": "VAPP-107: a camera on the LAN is denied",
915
+ "options": {},
916
+ "url": "http://192.168.1.10/cam.jpg",
917
+ "expected": null
918
+ },
919
+ {
920
+ "name": "a loopback poster is denied",
921
+ "options": {
922
+ "baseUrl": "http://127.0.0.1:3000"
923
+ },
924
+ "url": "/api/attachments/a1?poster=1",
925
+ "expected": null
926
+ },
927
+ {
928
+ "name": "the dev app opts its own origin in",
929
+ "options": {
930
+ "baseUrl": "http://localhost:3000",
931
+ "allowPrivate": [
932
+ "localhost"
933
+ ],
934
+ "rules": [
935
+ {
936
+ "prefix": "http://localhost:3000/api/attachments/",
937
+ "headers": {
938
+ "authorization": "Bearer expu_dev"
939
+ }
940
+ }
941
+ ]
942
+ },
943
+ "url": "/api/attachments/a1",
944
+ "expected": {
945
+ "url": "http://localhost:3000/api/attachments/a1",
946
+ "headers": {
947
+ "authorization": "Bearer expu_dev"
948
+ }
949
+ }
950
+ },
951
+ {
952
+ "name": "the opt-in does not widen to the LAN",
953
+ "options": {
954
+ "baseUrl": "http://localhost:3000",
955
+ "allowPrivate": [
956
+ "localhost"
957
+ ]
958
+ },
959
+ "url": "http://192.168.1.10/cam.jpg",
960
+ "expected": null
961
+ },
962
+ {
963
+ "name": "a data image is never private",
964
+ "options": {},
965
+ "url": "data:image/png;base64,iVBORw0KGgo=",
966
+ "expected": {
967
+ "url": "data:image/png;base64,iVBORw0KGgo=",
968
+ "headers": {}
969
+ }
970
+ },
971
+ {
972
+ "name": "https-only media",
973
+ "options": {
974
+ "schemes": [
975
+ "https"
976
+ ]
977
+ },
978
+ "url": "http://cdn.example/x.png",
979
+ "expected": null
980
+ },
981
+ {
982
+ "name": "a CGNAT poster is denied",
983
+ "options": {},
984
+ "url": "http://100.100.100.100/poster.jpg",
985
+ "expected": null
986
+ }
987
+ ],
988
+ "sources": [
989
+ {
990
+ "uri": "exp:issues?board=b1&limit=20",
991
+ "expected": {
992
+ "uri": "exp:issues?board=b1&limit=20",
993
+ "scheme": "exp",
994
+ "name": "issues",
995
+ "params": {
996
+ "board": "b1",
997
+ "limit": "20"
998
+ }
999
+ }
1000
+ },
1001
+ {
1002
+ "uri": "exp:devices",
1003
+ "expected": {
1004
+ "uri": "exp:devices",
1005
+ "scheme": "exp",
1006
+ "name": "devices",
1007
+ "params": {}
1008
+ }
1009
+ },
1010
+ {
1011
+ "uri": "EXP:Members?team=t%201",
1012
+ "expected": {
1013
+ "uri": "EXP:Members?team=t%201",
1014
+ "scheme": "exp",
1015
+ "name": "Members",
1016
+ "params": {
1017
+ "team": "t 1"
1018
+ }
1019
+ }
1020
+ },
1021
+ {
1022
+ "uri": "exp:issues?q=a+b&q=c",
1023
+ "expected": {
1024
+ "uri": "exp:issues?q=a+b&q=c",
1025
+ "scheme": "exp",
1026
+ "name": "issues",
1027
+ "params": {
1028
+ "q": "c"
1029
+ }
1030
+ }
1031
+ },
1032
+ {
1033
+ "uri": "exp:records?vapp=v1&kind=recipe&empty=&flag",
1034
+ "expected": {
1035
+ "uri": "exp:records?vapp=v1&kind=recipe&empty=&flag",
1036
+ "scheme": "exp",
1037
+ "name": "records",
1038
+ "params": {
1039
+ "empty": "",
1040
+ "flag": "",
1041
+ "kind": "recipe",
1042
+ "vapp": "v1"
1043
+ }
1044
+ }
1045
+ },
1046
+ {
1047
+ "uri": "acme.data:list/items?x=%E2%9C%93",
1048
+ "expected": {
1049
+ "uri": "acme.data:list/items?x=%E2%9C%93",
1050
+ "scheme": "acme.data",
1051
+ "name": "list/items",
1052
+ "params": {
1053
+ "x": "✓"
1054
+ }
1055
+ }
1056
+ },
1057
+ {
1058
+ "uri": "exp:",
1059
+ "expected": null
1060
+ },
1061
+ {
1062
+ "uri": "noscheme",
1063
+ "expected": null
1064
+ },
1065
+ {
1066
+ "uri": "1exp:issues",
1067
+ "expected": null
1068
+ },
1069
+ {
1070
+ "uri": "exp:issues?bad=%zz",
1071
+ "expected": {
1072
+ "uri": "exp:issues?bad=%zz",
1073
+ "scheme": "exp",
1074
+ "name": "issues",
1075
+ "params": {
1076
+ "bad": "%zz"
1077
+ }
1078
+ }
1079
+ }
1080
+ ],
1081
+ "negotiation": [
1082
+ {
1083
+ "extensionIds": [],
1084
+ "expected": {
1085
+ "supportedCatalogIds": [
1086
+ "https://ui.exponential.at/catalogs/core/v1",
1087
+ "https://ui.exponential.at/catalogs/core-lite/v1",
1088
+ "https://a2ui.org/specification/v0_9/basic_catalog.json"
1089
+ ],
1090
+ "clientCapabilities": {
1091
+ "v0.9": {
1092
+ "supportedCatalogIds": [
1093
+ "https://ui.exponential.at/catalogs/core/v1",
1094
+ "https://ui.exponential.at/catalogs/core-lite/v1",
1095
+ "https://a2ui.org/specification/v0_9/basic_catalog.json"
1096
+ ]
1097
+ }
1098
+ }
1099
+ }
1100
+ },
1101
+ {
1102
+ "extensionIds": [
1103
+ "https://ui.exponential.at/catalogs/exponential-app/v1"
1104
+ ],
1105
+ "expected": {
1106
+ "supportedCatalogIds": [
1107
+ "https://ui.exponential.at/catalogs/core/v1",
1108
+ "https://ui.exponential.at/catalogs/core-lite/v1",
1109
+ "https://a2ui.org/specification/v0_9/basic_catalog.json",
1110
+ "https://ui.exponential.at/catalogs/exponential-app/v1"
1111
+ ],
1112
+ "clientCapabilities": {
1113
+ "v0.9": {
1114
+ "supportedCatalogIds": [
1115
+ "https://ui.exponential.at/catalogs/core/v1",
1116
+ "https://ui.exponential.at/catalogs/core-lite/v1",
1117
+ "https://a2ui.org/specification/v0_9/basic_catalog.json",
1118
+ "https://ui.exponential.at/catalogs/exponential-app/v1"
1119
+ ]
1120
+ }
1121
+ }
1122
+ }
1123
+ },
1124
+ {
1125
+ "extensionIds": [
1126
+ "https://acme.example/catalog/v1",
1127
+ "https://acme.example/catalog/v1",
1128
+ "https://ui.exponential.at/catalogs/core/v1"
1129
+ ],
1130
+ "expected": {
1131
+ "supportedCatalogIds": [
1132
+ "https://ui.exponential.at/catalogs/core/v1",
1133
+ "https://ui.exponential.at/catalogs/core-lite/v1",
1134
+ "https://a2ui.org/specification/v0_9/basic_catalog.json",
1135
+ "https://acme.example/catalog/v1"
1136
+ ],
1137
+ "clientCapabilities": {
1138
+ "v0.9": {
1139
+ "supportedCatalogIds": [
1140
+ "https://ui.exponential.at/catalogs/core/v1",
1141
+ "https://ui.exponential.at/catalogs/core-lite/v1",
1142
+ "https://a2ui.org/specification/v0_9/basic_catalog.json",
1143
+ "https://acme.example/catalog/v1"
1144
+ ]
1145
+ }
1146
+ }
1147
+ }
1148
+ }
1149
+ ],
1150
+ "sourceParams": [
1151
+ {
1152
+ "source": "exp:records?kind=entry",
1153
+ "params": {
1154
+ "month": "2026-10"
1155
+ },
1156
+ "expected": "exp:records?kind=entry&month=2026-10"
1157
+ },
1158
+ {
1159
+ "source": "exp:records",
1160
+ "params": {
1161
+ "mine": true,
1162
+ "kind": "entry",
1163
+ "limit": 50
1164
+ },
1165
+ "expected": "exp:records?kind=entry&limit=50&mine=true"
1166
+ },
1167
+ {
1168
+ "source": "exp:issues?board=b1",
1169
+ "params": {
1170
+ "board": "b2"
1171
+ },
1172
+ "expected": "exp:issues?board=b1&board=b2"
1173
+ },
1174
+ {
1175
+ "source": "exp:x",
1176
+ "params": {
1177
+ "q": "a b&c=d/ü",
1178
+ "skip": null,
1179
+ "gone": null
1180
+ },
1181
+ "expected": "exp:x?q=a%20b%26c%3Dd%2F%C3%BC"
1182
+ },
1183
+ {
1184
+ "source": "exp:x",
1185
+ "params": {},
1186
+ "expected": "exp:x"
1187
+ },
1188
+ {
1189
+ "source": "exp:x",
1190
+ "params": {
1191
+ "tags": [
1192
+ "a",
1193
+ "b"
1194
+ ],
1195
+ "opts": {
1196
+ "k": 1
1197
+ }
1198
+ },
1199
+ "expected": "exp:x?opts=%7B%22k%22%3A1%7D&tags=%5B%22a%22%2C%22b%22%5D"
1200
+ },
1201
+ {
1202
+ "source": "exp:x?",
1203
+ "params": {
1204
+ "a": "1"
1205
+ },
1206
+ "expected": "exp:x?&a=1"
1207
+ }
1208
+ ]
1209
+ }