@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,95 @@
1
+ {
2
+ "$comment": "VAPP-91: the host API contract, the same on all four platforms (TS src/host/, the Rust core's `host` module, which the Swift and Kotlin painters reach through exponential-ui-ffi). A surface needs exactly these from its host: transport, functions, bindings (optional), extensions, theme, catalog negotiation and policy hooks. Every pure rule here is locked by fixtures/host-*.json, replayed by every renderer's conformance runner (conformance/manifest.json).",
3
+ "version": 3,
4
+ "a2uiVersion": "v0.9",
5
+ "messages": {
6
+ "$comment": "Server → client. The four A2UI v0.9 messages plus two Exponential UI extensions: `applyTemplate` (a registered declarative package's template becomes create + components + data + binds) and `bindDataModel` (a data model path follows a host `source`). A message is ONE object with `version` (optional, must equal a2uiVersion when present) and exactly one kind key.",
7
+ "a2ui": ["createSurface", "updateComponents", "updateDataModel", "deleteSurface"],
8
+ "extensions": ["applyTemplate", "bindDataModel"]
9
+ },
10
+ "ops": {
11
+ "$comment": "What the router (`HostRouter.route`) turns a message into. A platform host performs them in order: create = a new EMPTY surface (replacing one with that id), components = updateComponents on it, data = updateDataModel (no `value` = remove the path), bind = subscribe the path to a source resolver, delete = drop the surface and every subscription, send = a client message back over the transport (errors).",
12
+ "kinds": ["create", "components", "data", "bind", "delete", "send"]
13
+ },
14
+ "clientMessages": {
15
+ "$comment": "Client → server, A2UI v0.9 client_to_server: `{version, action: {name, surfaceId, sourceComponentId, timestamp, context}}` (+ `payload` when the component adds one, e.g. a Select's value) or `{version, error: {code, surfaceId, message, path?}}`.",
16
+ "errorCodes": ["VALIDATION_FAILED", "INVALID_MESSAGE", "UNSUPPORTED_CATALOG", "SURFACE_NOT_FOUND", "TEMPLATE_NOT_FOUND", "FUNCTION_NOT_FOUND", "FUNCTION_DENIED", "SOURCE_DENIED", "RENDER_FAILED"]
17
+ },
18
+ "functions": {
19
+ "$comment": "An `on.<event>` action `{functionCall: {call, args}}` (A2UI's key, the only one) runs on the client. The catalog's functions are built in (the basic 14 + the 15 core ones: value functions, `set`; `openUrl` and `bind` are the two with an effect: `openUrl` goes through the URL policy; `bind {path, source, params?}` (VAPP-122) re-follows `path` from an action: `source` with `params` appended as its query (sourceWithParams: keys sorted, percent-encoded, null skipped, objects as JSON) goes through the router like a server's bindDataModel, so the surface's package `sources` allowlist applies (SOURCE_DENIED otherwise) and the host REPLACES its earlier subscription at that path). Any other name is a namespaced HOST function (`app.toast`; an un-namespaced unknown name is a validation issue) that must be REGISTERED by the host (an action function: side effects, may be async) and passes the policy gate first: decide(policy, name) → allow | ask | deny. `ask` = the host's onFunctionCall hook decides (the Exponential app's consent card). Patterns: an exact name or a prefix ending in `*` (`harness.*`). Order: deny wins, then allow, then ask, then `default`.",
20
+ "decisions": ["allow", "ask", "deny", "not_found"],
21
+ "defaultPolicy": { "allow": [], "ask": [], "deny": [], "default": "allow" }
22
+ },
23
+ "urls": {
24
+ "$comment": "EVERY href on every renderer goes through decideUrl(policy, url): openUrl, Link (external or not), markdown links, FileUpload file urls, a host's default opener. The scheme (lower-cased) must be in `schemes` (`[\"https\"]` = https only: drops http, mailto and tel) and, when `hosts` is set, an http(s) url's host must match one (exact or `*.example.com`). Relative urls resolve against `baseUrl` first; without one they are denied. A denied href is never navigated: the link paints as plain text. The defaults keep `mailto` and `tel`: they open the system mail or phone handler with the address visible, run nothing, and are not gated by `hosts`. VAPP-107 (decided by Danny): an http(s) url whose host is PRIVATE (`privateHosts`: localhost and *.localhost, *.local, the IPv4 blocks 0/8, 10/8, 100.64/10, 127/8, 169.254/16, 172.16/12, 192.168/16, IPv6 ::, ::1, fe80::/10, fc00::/7 and a private IPv4 inside an IPv4-mapped, IPv4-compatible, NAT64 or 6to4 address) is DENIED with reason `private` unless the host opts it back in with `allowPrivate` (exact names or addresses, `*.suffix`, or `*` for every private address); a `hosts` allowlist alone does not opt one in. LITERALS only: the policy never resolves a name (a public name that resolves to a private address is the host network's job: egress rules, a filtering resolver). The WHATWG parser normalizes an address before the check (`2130706433`, `0x7f.1`, `::ffff:127.0.0.1`). The Exponential app opts its own origin in only when that origin is private (the localhost dev setup); its cloud origin is public and needs nothing; the web renderers (React and Solid, through the shared web layer) do the same for the document's own host on top of the host's list (a dev server on localhost keeps its same-origin loads).",
25
+ "defaultSchemes": ["https", "http", "mailto", "tel"],
26
+ "privateHosts": ["localhost", "*.localhost", "*.local", "0.0.0.0/8", "10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.168.0.0/16", "100.64.0.0/10", "::", "::1", "fe80::/10", "fc00::/7", "::ffff:<private IPv4>", "::<private IPv4>", "64:ff9b::<private IPv4>", "2002:<private IPv4>::/48"],
27
+ "reasons": ["invalid", "scheme", "host", "private"]
28
+ },
29
+ "media": {
30
+ "$comment": "EVERY src on every renderer (Image, Avatar, Video + poster, Audio, markdown images) goes through mediaRequest(url, {baseUrl, rules, schemes, hosts}) → {url, headers} | null. A relative url resolves against baseUrl; the absolute url must pass decideUrl with `schemes` (default `defaultSchemes`: no `file`, so no local file is ever read unless the host lists it; `[\"https\"]` = https only), `hosts` and `allowPrivate` (VAPP-107: a private, loopback or link-local host is denied unless opted in, like every href; a redirect hop passes the same rules; web: a direct element load's redirects are the browser's); null = denied, nothing loads. Every rule whose `prefix` the url starts with adds its headers (later rules win per header): how the Exponential app sends auth for /api/attachments. Every image loader enforces `limits`: `maxBytes` (Content-Length up front and the body as it streams), `timeoutMs` (the whole request), `maxPixels` (width × height read from the image header BEFORE decoding); a load over a limit fails like a 404 (the fallback paints). Video/Audio stream through the platform player: the policy applies, the byte limits do not (a poster is an image: they do); a web player cannot send headers, so a request WITH headers goes to the host's `mediaStreamUrl(request)` (a signed or cookie-authed url, re-checked by the policy) and without that hook is fetched whole into a blob url under `limits.maxBytes` and `limits.timeoutMs` for the WHOLE download (VAPP-107: past either it fails like a 404; a long video needs `mediaStreamUrl`).",
31
+ "ruleKeys": ["prefix", "headers"],
32
+ "optionKeys": ["baseUrl", "rules", "schemes", "hosts", "allowPrivate"],
33
+ "defaultSchemes": ["https", "http", "data"],
34
+ "limits": { "maxBytes": 20971520, "timeoutMs": 30000, "maxPixels": 33554432 }
35
+ },
36
+ "sources": {
37
+ "$comment": "A binding source URI: `<scheme>:<name>[?k=v&…]`, scheme `[a-z][a-z0-9+.-]*` (lower-cased), name non-empty without `?`, params percent-decoded (`+` = space), the last value of a repeated key wins, keys sorted. The host registers one resolver per scheme (the Exponential app: `exp`); subscribe(source, emit) → cancel, every emit writes the value at the bound path.",
38
+ "pattern": "^([a-zA-Z][a-zA-Z0-9+.-]*):([^?]+)(?:\\?(.*))?$",
39
+ "paramsEncoding": "sourceWithParams: keys sorted; key and value percent-encoded (everything but A-Z a-z 0-9 - _ . ! ~ * ' ( )); null skipped; an object or array as JSON; after ? or &"
40
+ },
41
+ "transport": {
42
+ "$comment": "Adapters every SDK ships: in-memory, A2UI JSONL over a byte stream (one message per line; CRLF and blank lines tolerated; a bad line is an issue, never fatal), SSE (`data:` lines joined per event; an event's data is one message or JSONL; events named other than `message`/`a2ui` are ignored), WebSocket (one message or JSONL per frame), and the A2UI-over-MCP carrier (a tool result's `content[]` resources with one of `mcpMimeTypes`, text = JSONL / a JSON array / one message; or `structuredContent.a2ui` = an array). Actions out over MCP = tools/call `mcpActionTool` with `{message}`.",
43
+ "mcpMimeTypes": ["application/json+a2ui", "application/a2ui+json"],
44
+ "mcpActionTool": "a2ui_event",
45
+ "sseEvents": ["message", "a2ui"]
46
+ },
47
+ "negotiation": {
48
+ "$comment": "supportedCatalogIds = the core, the core lite, the A2UI basic catalog, then every registered extension id in registration order, de-duplicated. clientCapabilities = {\"v0.9\": {supportedCatalogIds}}. A createSurface whose catalogId is not in the list → UNSUPPORTED_CATALOG."
49
+ },
50
+ "package": {
51
+ "$comment": "A declarative app package (VAPP-82): data, never code. {id, name, version, catalogId, templates: {<id>: {components: [flat, one `root`], data?, bindings?: [{path, source}]}}, functions?: [allowed patterns], sources?: [allowed sources], theme?: a theme id or theme JSON, icon?}. Installing one registers its templates with the router (applyTemplate) and turns `functions` into the policy for its surfaces: listed = allow, anything else = deny. VAPP-107: `sources` is the same allowlist for binding sources (`exp:devices`, or a prefix `exp:*`, matched against the URI's scheme:name without its query): unset = any registered scheme; set, a template binding outside it is an install issue and a later bindDataModel on one of the package's surfaces answers SOURCE_DENIED (the data model never follows it). VAPP-161 (docs/package-format.md, the prose spec): format version `formatVersion` (a package's optional `formatVersion`, a positive integer, default 1; newer than this = an install issue; version 2 only ADDS optional keys, so every version-1 package stays valid): `records:` (the neutral binding source + write functions over a HOST-provided store), `tools` (MCP servers and scripts behind a consent card) and `extensions` (the extension catalogs a package needs, versioned). The package FILE (any JSON object holding the package under `package`, e.g. Exponential's export) may carry `author` + `signature` (`signature` below).",
52
+ "required": ["id", "name", "version", "catalogId", "templates"],
53
+ "formatVersion": 2,
54
+ "records": {
55
+ "$comment": "VAPP-122: an app's own data. `records: {<kind>: {schema, title?, description?}}`: a kind's name follows `kindPattern`; `schema` is a JSON Schema SUBSET: {type: \"object\", properties: {<field>: {type: string|number|integer|boolean|array, enum? (strings), items? (arrays: {type, enum?}), maxLength? (characters, or items), minimum?, maximum?, format? (strings: date = YYYY-MM-DD, date-time = ISO 8601, time = HH:MM[:SS]), description?}}, required?: [fields], additionalProperties?: false by default}. The `reserved` keys ride on every row the host reads back and may not be declared. validateRecordSchema(schema, at) checks a kind at install (an issue = PACKAGE_INVALID), validateRecord(schema, data) a record before it is stored: a missing or null optional field passes, a required one does not, an undeclared key is refused unless additionalProperties. VAPP-161: READ = the binding `<scheme>:<kind>?query` (scheme `records.scheme`) → {rows: [{…data, id, createdAt, updatedAt, createdBy, author}], count}; `query`: mine=1, sort (default sortDefault), order (default orderDefault), limit (default limitDefault, capped at limitMax), any other key = a data-field filter (`<field>=v` equals as text, `<field>.gt|gte|lt|lte=v` numbers when both parse, else text); `query.reserved` keys are a host's extensions (Exponential: sum, month view, another app), never a filter; ties sort by id. A template binding to `records:<kind>` must name a declared kind (install issue `records:<kind> names no declared kind`). WRITE = `records.functions`: upsert {kind, data, id?} → {id} (create, or shallow-patch with id; the host validates the merged data with validateRecord BEFORE it stores it) and delete {id}; listed in `functions` like any host function, never behind a consent card. The host provides the store (TS: the RecordStore interface, MemoryRecordStore + recordsHost as the reference); Exponential = its synced store, spelled `exp:records?kind=` / `harness.records.*` too.",
56
+ "kindPattern": "^[a-z][a-z0-9_]{0,31}$",
57
+ "reserved": ["id", "createdAt", "updatedAt", "createdBy", "author"],
58
+ "types": ["string", "number", "integer", "boolean", "array"],
59
+ "formats": ["date", "date-time", "time"],
60
+ "scheme": "records",
61
+ "functions": ["records.upsert", "records.delete"],
62
+ "query": { "sortDefault": "createdAt", "orderDefault": "desc", "limitDefault": 100, "limitMax": 500, "reserved": ["app", "by", "kind", "limit", "mine", "month", "order", "sort", "sum", "user"] }
63
+ },
64
+ "tools": {
65
+ "$comment": "VAPP-161: `tools: {<id>: {kind, title, description?, url | command, args?, env?}}`, ids in `idPattern`, checked in sorted order. kind `mcp` = an MCP server: `url` (absolute http(s): Streamable HTTP) XOR `command` (+ args, a local stdio process); kind `script` = `command` (+ args) run once per call with the args as JSON on stdin, its stdout JSON the result. `env` = variable NAMES the host passes through (never values). Functions: `tools.<id>.<tool>` calls an MCP server's tool, `tools.<id>` a script; listed in `functions` like any other. CONSENT (the host MUST): before a tool's FIRST call the host shows the toolConsent row {id, kind, transport: http|stdio|script, title, description, target: the url or the command line (an arg that is empty or holds whitespace, a quote or a backslash JSON-quoted), env} and asks allow / deny; deny = FUNCTION_DENIED; an allow is remembered under toolConsentKey = `<packageId>/<toolId>:` + the tool's canonical JSON, so ANY change asks again; policy alone never allows a tool call; a host that cannot run a kind refuses its calls with FUNCTION_NOT_FOUND and says so on the card.",
66
+ "idPattern": "^[a-z][a-z0-9_-]{0,31}$",
67
+ "kinds": ["mcp", "script"],
68
+ "transports": ["http", "stdio", "script"],
69
+ "functionPrefix": "tools."
70
+ },
71
+ "extensions": {
72
+ "$comment": "VAPP-161: `extensions: [{id, version, components?}]`: an extension catalog id the package needs beyond its catalogId, the minimum MAJOR.MINOR.PATCH (caret: the host's has the same major and is not lower; an unversioned registration satisfies any) and the components it uses. Install issues: an id not registered on the host (`extension <id> is not available on this host`), a version that does not satisfy (`extension <id> <have> does not satisfy ^<want>`), a component the registered extension does not define (`extension <id> has no component <name>`), a duplicate id. A host registers an extension's version as ExtensionDef.version.",
73
+ "versionPattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$"
74
+ },
75
+ "signature": {
76
+ "$comment": "VAPP-161: a package FILE's `author: {name (1..nameMax characters), key: keyPrefix + the 32-byte Ed25519 public key, unpadded base64url}` and `signature: {alg, value: the 64-byte Ed25519 (RFC 8032) signature, unpadded base64url}` over UTF-8 of `context` + the RFC 8785 canonical JSON (no whitespace, keys sorted by UTF-16 code units, ECMAScript strings and numbers) of the whole file WITHOUT `signature`. verifyPackageFile → unsigned (no signature; an author alone is an unverified claim) | valid {author} | invalid {issues}. A host refuses to import an invalid file. Fixtures sign with the RFC 8032 §7.1 TEST 1 keypair.",
77
+ "alg": "ed25519",
78
+ "context": "exponential-ui/package-file/v1\n",
79
+ "keyPrefix": "ed25519:",
80
+ "nameMax": 80,
81
+ "statuses": ["unsigned", "valid", "invalid"]
82
+ }
83
+ },
84
+ "paint": {
85
+ "$comment": "A component whose painter fails (React: a render error; native: a painter that throws or meets props it cannot paint) paints an empty box in its place, never takes the surface down, and calls the host's `onPaintError({surfaceId, componentId, message})` ONCE per surface + component + message. ExponentialHost.paintError forwards it to the agent as an A2UI error `{code: errorCode, surfaceId, message, path: /components/<componentId>}` and reports it as a host issue with the same code.",
86
+ "hook": "onPaintError",
87
+ "errorCode": "RENDER_FAILED"
88
+ },
89
+ "surface": {
90
+ "$comment": "Round 2 (docs/round-2-contract.md §3, §5): what a host hands each surface besides messages. `settings` = the SurfaceSettings keys (src/types.ts): `timeZone` = the IANA zone instants show in (default the platform's). `formatter` = the methods of the host-supplied Formatter that formats every number, currency, percent, date and relative time (TS: intlFormatter(locale, timeZone); Rust: the Formatter trait, default the English fallback at the host's UTC offset per instant; FFI: a foreign Formatter, else the core's). `commands` = what a host may ask of a live surface (catalog/a11y.json `commands`; `scrollToIndex` takes the DATA index). Round 4 (VAPP-103): an unusable theme (an unknown built-in id, a missing or other `$schema`, any theme issue) NEVER fails a host: it paints with the default theme and reports the issues (TS `themeOrDefault` + React `HostPlugin.onThemeIssues`, Rust `themes::theme_or_default`, Swift/Kotlin `ThemeHandle.loadOrDefault`); a server's unusable `createSurface.theme` answers VALIDATION_FAILED at `/createSurface/theme`.",
91
+ "settings": ["locale", "timeZone", "strings", "mode", "density", "contrast", "theme"],
92
+ "formatter": ["number", "currency", "percent", "date", "relativeTime", "plural"],
93
+ "commands": ["focus", "announce", "scrollIntoView", "scrollToIndex"]
94
+ }
95
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$comment": "Round 2 (docs/round-2-contract.md §7): the layout NUMBERS every painter shares where no theme token decides (px unless noted). `windowThreshold` = items past which List and Table window; `windowOverscan` = items rendered beyond each viewport edge; `resizeStep` = percentage points per arrow key on a Resizable handle; `panelMin` = a panel's min (percent) when unset; `resizeHandleHit` = a handle's hit area across its hairline; `fieldIntrinsicWidth` = the max-content width of a text field (Input, Textarea, NumberField, ChipInput: never the browser's `size` default) — picker triggers (Select, DatePicker, DateRangePicker, TimePicker) size to their content; `mediaIntrinsicWidth` = the max-content width of an Image/Video without a width (its min-content is 0); `mediaAspectRatio` = width/height of an Image without `aspectRatio` or a height (Video: its `aspectRatio` default); `treeGuideColumn` = one TreeGuides gutter column (the recipe's `line` width is the STROKE; round 3: 14, the ×4 app indent, the elbow lands on the parent's lead glyph); `treeGuideRadius` = the elbow's corner radius; `treeGuideBridge` = how far the guide verticals overshoot the row's TOP edge (paint only) so a Section's hairline divider never breaks the line. EXP-1259 (packages/domain-contract/fixtures/feedback-states.json + search-list.json): `pendingDelayMs` = how long a `loading` Button waits before it shows the spinner and its `pendingLabel` (a fast action never flashes; presses are refused at once); `skeletonPulseMs` / `skeletonOpacityLow` / `skeletonOpacityHigh` = the placeholder's ONE opacity pulse (low → high → low, ease-in-out), `skeletonReducedOpacity` = its static opacity under reduce motion; `searchListFade` = the bottom fade over a SearchList (or a searchable submenu) while more rows sit below. Generated into every target; src/layout.ts reads it.",
3
+ "windowThreshold": 50,
4
+ "windowOverscan": 5,
5
+ "resizeStep": 10,
6
+ "panelMin": 10,
7
+ "resizeHandleHit": 8,
8
+ "fieldIntrinsicWidth": 160,
9
+ "mediaIntrinsicWidth": 320,
10
+ "mediaAspectRatio": 1.7777778,
11
+ "treeGuideColumn": 14,
12
+ "treeGuideRadius": 3,
13
+ "treeGuideBridge": 1,
14
+ "pendingDelayMs": 150,
15
+ "skeletonPulseMs": 1600,
16
+ "skeletonOpacityLow": 0.45,
17
+ "skeletonOpacityHigh": 0.9,
18
+ "skeletonReducedOpacity": 0.6,
19
+ "searchListFade": 24
20
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "$comment": "VAPP-103: the input LIMITS every core enforces (the TS reference, the Rust core and through it Swift, Compose and gpui). An agent's input past a limit is refused with an issue, never worked on. `maxComponents` = nodes the reducer places in one surface's tree, template nodes included (the rest is dropped: `surface: more than N components; the rest is dropped`); a renderer's build holds at most `maxComponents` nodes too: the reduced tree's (after expansion, children and slots) plus, per template ITEM, its template subtree's (nested items charged when they are instantiated); the first item that does not fit and every later one are not rendered, with ONE issue on the template component (the same message). `maxDepth` = nesting levels of the REDUCED tree, after macro expansion (the root and every lifted template = 1, children and slots one level down; a Card, Section or Group macro adds a level; a deeper node is the Unknown placeholder, its subtree dropped: `nesting deeper than N levels`); 48 keeps every tree a core emits (2 JSON levels per node) inside serde_json's default recursion limit of 128, so reduced trees, fixtures and the FFI's JSON round-trip; the Rust core's recursive passes switch to a heap-allocated stack segment when the host thread runs low, so an iOS secondary thread's 512 KB never decides (a 48-level surface lays out on a 128 KB thread, debug build). `maxMessageBytes` = one server message as UTF-8 JSON (host routers refuse it: INVALID_MESSAGE). `maxTemplateItems` = template items one surface instantiates (beyond it the items are not rendered: `template: more than N items; the rest is not rendered`). `maxPointerBytes` / `maxPointerSegments` = a data pointer's length and token count (a longer write is refused). Generated into every target; src/limits.ts reads it.",
3
+ "maxComponents": 20000,
4
+ "maxDepth": 48,
5
+ "maxMessageBytes": 4194304,
6
+ "maxTemplateItems": 10000,
7
+ "maxPointerBytes": 1024,
8
+ "maxPointerSegments": 64
9
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "$comment": "Round 1 (docs/round-1-contract.md §4): the LOCALE contract. A surface carries one BCP 47 `locale` (SurfaceSettings.locale, default `en-US`); it drives the basic catalog's formatNumber/formatCurrency/formatDate/pluralize, NumberField's display, the calendar's month and weekday names and the default week start, and the text direction. Platforms format with their own ICU (Intl, Foundation, java.text, icu4x); what must not differ is decided HERE so every renderer agrees: `weekStart` = CLDR supplemental weekData `firstDay` by REGION (0 = Sunday … 6 = Saturday; a region not listed = `default`); a locale without a region takes `likelyRegion[language]` (CLDR likelySubtags: every rtl language and the most spoken ~90; else `default`); `languageAliases` = deprecated codes normalised FIRST (Java/Android still emit `iw`, `ji`, `in`); `rtl` = the languages whose text direction is right-to-left (the surface root gets `direction: rtl` unless the author set one); `rtlMirroredIcons` = the DIRECTIONAL glyphs (icons.json names, semantic and Lucide) a painter MIRRORS under rtl: scaleX(-1) as the OUTERMOST transform of the glyph, after the node's own `transform` (CSS `transform: scaleX(-1) <own>`), so a back arrow, a prev/next or breadcrumb chevron and a submenu indicator point the reading direction; media transport glyphs and everything else are never mirrored. Round 2: formatting goes through a host-supplied Formatter (SurfaceSettings.locale + timeZone; TS = Intl, Rust = a Formatter trait with the `englishFallback` below, FFI = a foreign Formatter). src/locale.ts + src/format.ts are the reference; generated into every target as constants. Append-only.",
3
+ "default": "en-US",
4
+ "weekStart": {
5
+ "default": 1,
6
+ "regions": {
7
+ "0": ["AG", "AS", "BD", "BR", "BS", "BT", "BW", "BZ", "CA", "CN", "CO", "DM", "DO", "ET", "GT", "GU", "HK", "HN", "ID", "IL", "IN", "JM", "JP", "KE", "KH", "KR", "LA", "MH", "MM", "MO", "MT", "MX", "MZ", "NI", "NP", "PA", "PE", "PH", "PK", "PR", "PT", "PY", "SA", "SG", "SV", "TH", "TT", "TW", "UM", "US", "VE", "VI", "WS", "YE", "ZA", "ZW"],
8
+ "5": ["MV"],
9
+ "6": ["AE", "AF", "BH", "DJ", "DZ", "EG", "IQ", "IR", "JO", "KW", "LY", "OM", "QA", "SD", "SY"]
10
+ }
11
+ },
12
+ "likelyRegion": {
13
+ "af": "ZA", "am": "ET", "ar": "EG", "az": "AZ", "be": "BY", "bg": "BG", "bn": "BD", "bs": "BA", "ca": "ES", "ckb": "IQ", "cs": "CZ", "cy": "GB",
14
+ "da": "DK", "de": "DE", "dv": "MV", "el": "GR", "en": "US", "es": "ES", "et": "EE", "eu": "ES", "fa": "IR", "fi": "FI", "fil": "PH", "fr": "FR",
15
+ "ga": "IE", "gl": "ES", "gu": "IN", "ha": "NG", "he": "IL", "hi": "IN", "hr": "HR", "hu": "HU", "hy": "AM", "id": "ID", "ig": "NG", "is": "IS",
16
+ "it": "IT", "ja": "JP", "jv": "ID", "ka": "GE", "kk": "KZ", "km": "KH", "kn": "IN", "ko": "KR", "ku": "TR", "ky": "KG", "lo": "LA", "lt": "LT",
17
+ "lv": "LV", "mk": "MK", "ml": "IN", "mn": "MN", "mr": "IN", "ms": "MY", "mt": "MT", "my": "MM", "nb": "NO", "ne": "NP", "nl": "NL", "nn": "NO",
18
+ "no": "NO", "or": "IN", "pa": "IN", "pl": "PL", "ps": "AF", "pt": "BR", "ro": "RO", "ru": "RU", "sd": "PK", "si": "LK", "sk": "SK", "sl": "SI",
19
+ "so": "SO", "sq": "AL", "sr": "RS", "sv": "SE", "sw": "TZ", "ta": "IN", "te": "IN", "th": "TH", "tl": "PH", "tr": "TR", "ug": "CN", "uk": "UA",
20
+ "ur": "PK", "uz": "UZ", "vi": "VN", "yi": "001", "yo": "NG", "zh": "CN", "zu": "ZA"
21
+ },
22
+ "languageAliases": { "in": "id", "iw": "he", "ji": "yi", "jw": "jv", "mo": "ro", "tl": "fil" },
23
+ "rtl": ["ar", "ckb", "dv", "fa", "he", "ps", "sd", "ug", "ur", "yi"],
24
+ "rtlMirroredIcons": ["ui-back", "ui-chevron-left", "ui-chevron-right", "ui-arrow-right", "ui-send", "ui-undo", "ui-external-link", "arrow-left", "arrow-right", "chevron-left", "chevron-right", "send", "undo-2", "external-link"],
25
+ "englishFallback": {
26
+ "$comment": "Round 2 (docs/round-2-contract.md §3): the data of the ENGLISH fallback Formatter (src/format.ts englishFormatter; the Rust core's fallback reads the same): what a renderer without a platform ICU prints, equal to Intl en-US (ICU 74+) for every case of fixtures/format.json. `currencyPrefix` = the en-US prefix per ISO 4217 code (a code not listed prints `unknownCurrency`: `<CODE>` + U+00A0); `currencyDigits` = the minor digits where they are not 2; `relative` = the numeric:auto phrases of a unit at -1/0/+1 and the unit names (singular, plural); `datePatterns` = the TR35 pattern per `style`, `timePattern` = what `time: true` adds, joined by `dateTimeJoin[style]`. Instants show in the surface zone: the fallback takes the zone as a UTC offset per instant from the host (fixtures: `offsetMinutes`).",
27
+ "months": ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
28
+ "monthsShort": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
29
+ "weekdays": ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
30
+ "weekdaysShort": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
31
+ "dayPeriods": ["AM", "PM"],
32
+ "currencyPrefix": {
33
+ "USD": "$", "EUR": "€", "GBP": "£", "JPY": "¥", "CNY": "CN¥", "INR": "₹", "KRW": "₩", "CAD": "CA$", "AUD": "A$", "MXN": "MX$",
34
+ "BRL": "R$", "ILS": "₪", "NZD": "NZ$", "HKD": "HK$", "TWD": "NT$", "VND": "₫", "PHP": "₱", "XAF": "FCFA\u00a0", "XOF": "F\u202fCFA\u00a0"
35
+ },
36
+ "currencyDigits": { "JPY": 0, "KRW": 0, "VND": 0, "CLP": 0, "ISK": 0, "UGX": 0, "PYG": 0, "XAF": 0, "XOF": 0, "BHD": 3, "IQD": 3, "JOD": 3, "KWD": 3, "OMR": 3, "TND": 3 },
37
+ "relative": {
38
+ "units": { "second": ["second", "seconds"], "minute": ["minute", "minutes"], "hour": ["hour", "hours"], "day": ["day", "days"], "week": ["week", "weeks"], "month": ["month", "months"], "year": ["year", "years"] },
39
+ "auto": { "second": { "0": "now" }, "day": { "-1": "yesterday", "1": "tomorrow" }, "week": { "-1": "last week", "1": "next week" }, "month": { "-1": "last month", "1": "next month" }, "year": { "-1": "last year", "1": "next year" } },
40
+ "past": "{value} {unit} ago",
41
+ "future": "in {value} {unit}"
42
+ },
43
+ "unknownCurrency": "{code}\u00a0",
44
+ "datePatterns": { "short": "M/d/yy", "medium": "MMM d, y", "long": "MMMM d, y", "full": "EEEE, MMMM d, y" },
45
+ "timePattern": "h:mm a",
46
+ "dateTimeJoin": { "short": "{date}, {time}", "medium": "{date}, {time}", "long": "{date} at {time}", "full": "{date} at {time}" }
47
+ }
48
+ }