@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,174 @@
1
+ # Exponential UI — round 3 contract (VAPP-102)
2
+
3
+ Round 3 folds the catalog onto the vocabulary the Exponential app settled on
4
+ in its UI cleanup (EXP-1248, EXP-1249, EXP-1251): ONE row, ONE section band,
5
+ ONE chip, ONE segmented control, ONE menu. The catalog had split each of
6
+ these several ways; the app has one of each. This file is the CONTRACT half:
7
+ what changed in `@exponential_app/eui` and what each renderer implements to
8
+ match. Everything here is locked by a fixture or a test.
9
+
10
+ Read §0, the sections you own, then §6 (your checklist).
11
+
12
+ ## 0. What moved
13
+
14
+ | area | change | source of truth | locked by |
15
+ |---|---|---|---|
16
+ | `Row` | NEW macro: leading/trailing slots, identifier, title/subtitle/meta/value(+placeholder), chevron, selected, density, surface flat\|card, pressable, depth | `core.catalog.json`, `macros.json` | `catalog-macros.json` |
17
+ | `Section` | NEW macro: a band header (title, caption, count OFF by default, glyph, leading/trailing slots, collapsible/open, tint) over hairline-divided rows (a `List` native, the windowed backend); `tree` = the rows nest by depth | same | `catalog-macros.json`, `kitchen-sink.expanded.json` |
18
+ | tree guides | computed by the CORE from consecutive rows' `depth` (elbow, tee, pass-through); `TreeGuides` = a hidden Row part, never authored; column 14 px (the line's left edge at i·14 + 7), rounded 3 px elbow, 1 px bridge | `src/tree-guides.ts`, `layout.json` | `fixtures/tree-guides.json`, the `Row/tree:*` macro cases |
19
+ | `Chip` | NEW macro: shape pill\|rect (the old Pill + EntityChip), selected, tone, dot, icon, image, detail, removable, pressable, a `leading` slot | `macros.json` | `catalog-macros.json` |
20
+ | `Segmented` | native (ToggleGroup renamed): variant segmented (default) \| toggles \| outline \| bar; TabBar + ButtonGroup fold in | `core.catalog.json`, `recipes.json` | `catalog-components.json`, `control-geometry.json` |
21
+ | `Menu` | native (DropdownMenu + ContextMenu): `openOn press\|contextmenu`, the ONE child = the trigger or the target region, `menuItem.items` bindable (a source-fed submenu) | `core.catalog.json` | `catalog-components.json`, the React + Rust bound-submenu tests |
22
+ | `Badge` | counts and short status labels only | `core.catalog.json` | — |
23
+ | sheets | `Drawer side: bottom` (what the app calls a sheet; the Sheet name is gone) | `core.catalog.json` | `catalog-components.json` |
24
+ | removed names | round 4 (VAPP-103) REMOVED the 16 folded names (§4): no aliases, an old name is an `unknown component` | `core.catalog.json` `$comment` | `catalog.test.ts` |
25
+ | lite | Pagination, Accordion, Radio, Slider, Spinner, Table, Toggle leave the lite subset (with Carousel, the overlays, media, Chart) | `core.catalog.json` | `prompt-budget.json` |
26
+ | a11y | roles gain `tree`, `treeitem` | `a11y.json` | `round1-fixes.test.ts` |
27
+ | extension | the app extension splits RunRow into RunStatusRow + SessionRow, adds PrRow/StackRail/DiffCounts/DiffFileRow/GuideSection; IssueRow, IssueGroupBand and IssueChip are macros over Row/Section/Chip | `packages/ui/exponential-ui/extension.json` | `packages/ui/src/exponential-ui-extension.test.ts` |
28
+
29
+ Counts (after round 4): 72 components in the catalog, 70 offered (44
30
+ natives, 26 macros; 50 lite), TreeGuides and Unknown hidden.
31
+
32
+ ## 1. Row and Section
33
+
34
+ ```json
35
+ { "id": "runs", "component": "Section", "props": { "title": "Running", "tree": true, "collapsible": true },
36
+ "children": ["r1", "r2", "r3"] }
37
+ { "id": "r1", "component": "Row", "props": { "identifier": "EXP-12", "title": "Release 0.18", "meta": "4 runs", "pressable": true } }
38
+ { "id": "r2", "component": "Row", "props": { "title": "Notarize", "depth": 1, "icon": "status-in-progress" } }
39
+ { "id": "r3", "component": "Row", "props": { "title": "Upload", "depth": 1, "value": "queued", "chevron": "right", "pressable": true } }
40
+ ```
41
+
42
+ **Row** parts, in order: `root` (a pressable Box: flex row, gap sm, min
43
+ height `$control.row` / `$control.rowCompact` by `density`, padding
44
+ horizontal md and NO vertical padding (the `body` carries xs (flat) / md
45
+ (card) so the `guides` part spans the row's full height and the lines of
46
+ consecutive rows meet across the divider), radius md (flat) / lg (card))
47
+ → `guides` (a `TreeGuides` native, only when `depth > 0`, FILLED BY THE
48
+ CORE) → the `leading` slot → `icon` (sm, when no slot is given) →
49
+ `identifier` (Text `code`) → `body` (`title` Text body, `subtitle` Text
50
+ muted) → `meta` (Text muted) → `value` (Text body, or muted while it shows
51
+ `placeholder`, aligned end) → the `trailing` slot → `chevron` (`right` =
52
+ `ui-chevron-right`, `selector` = `ui-selector`). Recipe props: `selected`,
53
+ `density`, `surface`, `depth`. A11y: role `button` + `selected` when
54
+ `pressable`, otherwise `none`. `on.press` implies `pressable`.
55
+
56
+ **Section** parts: `root` (column, gap xs) → `header` (only with a `title`;
57
+ flex row, height `$control.row`, padding horizontal md, radius md; a heading
58
+ (level 3), or a `button` with `expanded` when `collapsible`, whose press
59
+ routes to `change {open}` and writes a bound `open` back) → `chevron`
60
+ (collapsible only, `ui-chevron-right`, rotates when open) → the `leading`
61
+ slot → `icon` → `title` (Text label) → `caption` (Text muted) → `count`
62
+ (Text muted, only when given) → `spacer` → the `trailing` slot; then `body`
63
+ = a `List` native (`divided: true`, `gap: none`, role `list`, or `tree`
64
+ when `tree`) holding the author's children (a data template rides through),
65
+ shown while `open` (default true). Recipe props: `tint`, `tree`, `open`,
66
+ `collapsible`. The old `Band` = a Section without rows; the old `RowList` =
67
+ a Section without a title.
68
+
69
+ ## 2. The tree guides the core computes
70
+
71
+ `treeGuides(depths)` (`src/tree-guides.ts`; Rust `tree_guides.rs`) is the
72
+ app's ×4 rule verbatim: a row at depth `d ≥ 1` gets its elbow in gutter
73
+ level `d − 1`; the elbow TEES on when another row at depth `d` follows
74
+ before the walk leaves the subtree (a row at depth `≤ d − 1`); every
75
+ ancestor level `L < d − 1` whose subtree continues after the row passes
76
+ straight through. `fixtures/tree-guides.json` locks the vectors.
77
+
78
+ `applyTreeGuides(root)` runs ONCE at the end of `expandMacros`
79
+ (`expand_macros`), when every node is native: over every node's children in
80
+ order, a Row root (`recipe.macro` Row, part root) contributes its numeric
81
+ `recipe.props.depth` (floored; anything else, including a non-Row sibling,
82
+ counts as 0 and ends every subtree); the Row's `guides` part gets
83
+ `{depth, elbowAt?, tee, passThrough}` in that key order (`elbowAt` omitted
84
+ for a root). A lone root or slot value is a one-element list. Any OTHER
85
+ sibling with a numeric `depth` prop (an extension row such as SessionRow or
86
+ PrRow) takes part too and gets the result as its `guide` prop
87
+ (`{elbowAt?, tee, passThrough}`) for its painter to draw. A `depth` is
88
+ static (never bindable). The pass runs over the EXPANDED tree only: rows a
89
+ data template instantiates (a List over `{path}`) are not siblings at
90
+ expansion time, and a painter draws NO elbow without `elbowAt`; author
91
+ static rows for a connected tree. A macro that offers `pressable` (Row,
92
+ Chip) takes it from an `on.press` when unset.
93
+
94
+ **Geometry** (`layout.json`): `treeGuideColumn` = 14 (the `guides` part is
95
+ `depth × 14` wide, stretched to the row; column `i`'s line is at
96
+ `x = i·14 + 7`, the elbow's stub runs to `i·14 + 14` so it ends at the
97
+ child's own lead glyph), `treeGuideRadius` = 3 (the elbow's corner),
98
+ `treeGuideBridge` = 1 (every guide vertical starts that many px ABOVE the
99
+ part's top edge, paint only, so a Section's hairline divider never breaks the
100
+ line). Stroke width and colour = the `TreeGuides/line` recipe.
101
+
102
+ ## 3. Chip, Segmented, Menu
103
+
104
+ **Chip** `shape: pill` = radius full, height `$control.pill` (the old
105
+ Pill); `rect` = radius md, height `$control.chip` (the old EntityChip).
106
+ Parts: root → the `leading` slot → dot → image (16 px, round) → icon (when
107
+ no image) → label (caption) → detail (muted) → remove (a ghost icon Button,
108
+ `$on press → remove`). Recipe props `shape`, `selected`, `tone`.
109
+
110
+ **Segmented** keeps ToggleGroup's contract (`items`, `type`, bound `value`
111
+ comma-joined when multiple, `size`, `fill`) with `variant`: `segmented` (the
112
+ DEFAULT: the pill track), `toggles` (bare toggle buttons, ToggleGroup's old
113
+ default), `outline` (joined outline buttons, the old ButtonGroup), `bar` (the
114
+ old TabBar: full width, each item a column of icon over a caption label,
115
+ role `navigation` with `aria-current=page` on the selected item). Recipe
116
+ parts `root`, `item`, `icon`, `label`; `when` props `type`, `variant`, `size`,
117
+ `fill`. Content-sized (align-self start) unless `fill` or `bar`.
118
+
119
+ **Menu** keeps the `menuItem` contract (item | checkbox | separator | label |
120
+ submenu, one level) with `openOn`: `press` (the ONE child is the trigger; no
121
+ child = a default outline Button from `label`/`icon`, no chevron) or
122
+ `contextmenu` (the child is the target region: right-click, long-press,
123
+ Shift+F10 or the Menu key open it at the pointer). `menuItem.items` is
124
+ bindable: a `{path}` whose rows a host feeds (`bindDataModel` from an
125
+ `exp:` source) becomes the submenu. Parts `trigger`, `content`, `item`,
126
+ `separator`, `label`, `shortcut`, `check`, `submenuIndicator`; built-in
127
+ glyphs `Menu.check`, `Menu.submenuIndicator`. The DropdownMenu and
128
+ ContextMenu glyph rows are gone (round 4 removed those names).
129
+
130
+ ## 4. Removed names (round 4)
131
+
132
+ Round 3 shipped these as one-release aliases; round 4 (VAPP-103) REMOVED
133
+ them before any release: there is no `deprecated` key, no alias macro, no
134
+ example case and no generated `componentDeprecated` constant. An old name
135
+ reduces to the `Unknown` placeholder with an `unknown component <name>`
136
+ issue, and a theme naming one fails to load (`unknown component`).
137
+
138
+ | removed | write instead |
139
+ |---|---|
140
+ | ListRow, CardRow, PropertyRow, PickerRow, NavRow | `Row` (card = `surface: card`; property = a `value`; picker = `value` + `placeholder` + `chevron: selector`; nav = `icon` + `selected` + a `Badge` in the trailing slot) |
141
+ | Band, RowList | `Section` (no rows / no title; `tree` for nested rows) |
142
+ | Pill, EntityChip | `Chip` (`shape: pill` / `rect`) |
143
+ | ToggleGroup, TabBar, ButtonGroup | `Segmented` (`toggles`\|`outline`\|`segmented` / `bar` + fill / `outline`) |
144
+ | DropdownMenu, ContextMenu | `Menu` (`openOn: press` with the trigger as the child / `contextmenu`) |
145
+ | Sheet | `Drawer side: bottom` |
146
+ | HoverCard | `Popover openOn: hover` |
147
+
148
+ ## 5. Breaking changes
149
+
150
+ - Themes: recipes for the folded components fail to load (`unknown
151
+ component`); move them to `Row`, `Section`, `Chip`, `Segmented`, `Menu`.
152
+ - `layout.json` `treeGuideColumn` 16 → 14; `TreeGuides` is hidden.
153
+ - `builtinIcons`: `DropdownMenu.*` / `ContextMenu.*` → `Menu.*`.
154
+ - Enums `bandVariant`, `toggleVariant` are gone; `rowChevron`, `rowSurface`,
155
+ `chipShape`, `segmentedVariant`, `menuOpenOn` are new.
156
+ - The lite subset lost seven components; `prompt-budget.json` moved.
157
+
158
+ ## 6. Per-renderer checklist
159
+
160
+ Every renderer: replay the regenerated fixtures (`catalog-components.json`, `catalog-macros.json` with the `Row/tree:*` cases,
161
+ `tree-guides.json`, `kitchen-sink*.json`, `control-geometry.json`,
162
+ `theme-*.json`, `bind-time.json`); paint `Segmented` (+ `bar`) and `Menu`
163
+ (+ `contextmenu`) under the new names; draw `TreeGuides` at 14 / 3 / 1.
164
+
165
+ - **Rust core**: `tree_guides.rs` + the post-expansion pass; `Menu` and
166
+ `Segmented` in the layout tree, events and state.
167
+ - **gpui**: the menu and segmented painters renamed, the `bar` column item,
168
+ the rounded + bridged guides; the conformance ratchet re-recorded against
169
+ the new web baseline.
170
+ - **React**: `MenuNative`, `SegmentedNative`, the guides geometry, the
171
+ snapshot and the web baseline re-recorded.
172
+ - **SwiftUI / Compose**: the leaf switches renamed (`Menu`, `Segmented`),
173
+ the `bar` item, the guides geometry, the built-in glyph mirror, snapshots
174
+ re-recorded (Compose on ubuntu).
@@ -0,0 +1,268 @@
1
+ # Exponential UI — round 5 contract (VAPP-162)
2
+
3
+ Round 5 adds the low-level pieces the spezl drafts (a chat app with voice
4
+ calls, push-to-talk, computer-use plans and QR pairing) kept rebuilding: a
5
+ chat bubble, a call screen, a plan card, a QR code and a QR scanner, plus
6
+ five extensions of existing components. This file is the CONTRACT half:
7
+ what changed in `@exponential_app/eui` and what each renderer implements to
8
+ match. Everything here is locked by a fixture or a test.
9
+
10
+ Read §0, the sections you own, then §11 (your checklist).
11
+
12
+ ## 0. What moved
13
+
14
+ | area | change | source of truth | locked by |
15
+ |---|---|---|---|
16
+ | serif | `type.family.serif` (append-only), `ui-serif` in every built-in theme, `fonts["ui-serif"]` = system, fallback `Georgia, serif` | `tokens.json`, `themes/*.json`, `generate-themes.ts` | `theme-resolved.json`, `theme-invalid.json`, the `Text` specimen extra |
17
+ | `MessageBubble` | NEW macro (list, lite): text, side in\|out, time, status sending\|sent\|read, author, slot `attachment` | `core.catalog.json`, `macros.json` | `catalog-macros.json`, `bind-time.json` |
18
+ | `Segmented` | `orientation` horizontal\|vertical (vertical `bar` = THE navigation rail); `option.count` = a badge on a `bar` item; recipe part `badge`, `when` prop `orientation` | `core.catalog.json`, `recipes.json` | `catalog-components.json`, `control-geometry.json` (`orientation=*`, `variant=bar,orientation=vertical`) |
19
+ | `CallSurface` | NEW macro (media, not lite): name, avatar, state ringing\|connecting\|talking\|ended, activity, level, bands, status, caption, muted (two-way), speaker; events mute, speaker, hangUp | same | `catalog-macros.json` (every `state=*`, `muted=*`, `speaker=*`, `bound:*`) |
20
+ | `VoiceWave` | `variant` ring\|bars; `bars` = a horizontal level meter (no child); part `bar` | `core.catalog.json`, `style.json voiceWave.bars`, `src/voice-wave.ts voiceWaveBars` | `voice-wave.json` `bars`, `control-geometry.json` (`variant=bars,size=*`) |
21
+ | `QrCode` | NEW native (media, not lite): value, size, level L\|M\|Q\|H, label; THE MATRIX COMES FROM THE CORE | `src/qr-code.ts` (Nayuki port), `style.json qrCode` | `qr-code.json` |
22
+ | `QrScanner` | NEW native (input, not lite): hint, formats, label, disabled; event `scanned {value}`; the camera is HOST-OWNED | `core.catalog.json`, `style.json qrScanner` | `catalog-components.json` |
23
+ | `TraceItem` | `traceStatus` += `done`, `skipped` | `core.catalog.json`, theme recipes | `theme-recipes.json`, `catalog-macros.json` |
24
+ | `Plan` | NEW macro (list, lite): title, description, steps (`planStep`), state proposed\|running\|done\|declined, progress, approve/decline/stop labels; events approve, decline, stop | `core.catalog.json`, `macros.json` | `catalog-macros.json` |
25
+ | `Button` | `hold` + `holdingLabel`; events += holdStart, holdEnd, holdCancel | `core.catalog.json` | `interactions-round1.json` `buttonHold` |
26
+ | icons | concepts `call-incoming` (phone-incoming), `ui-qr-code` (qr-code), `ui-scan` (scan-line), `ui-read` (check-check), `ui-hold` (hand), `voice-level` (audio-lines); `mic-off`, `phone-off` were already concept targets (`call-unmute`, `call-end`) | `packages/icons/icons.json` | the icon generator ×4 |
27
+ | strings | `cameraUnavailable`, `endCall`, `mute`, `no`, `read`, `scanQrCode`, `sending`, `sent`, `speaker`, `speakerOff`, `unmute`, `yes` | `strings.json` | generated STRING tables |
28
+ | kitchen sink | + CallSurface, two MessageBubbles, Plan, QrCode, QrScanner (every visible component appears once) | `kitchen-sink.json` | `kitchen-sink.expanded.json` |
29
+
30
+ Counts: 87 offered components (49 natives, 38 macros; 61 lite), + the hidden
31
+ TreeGuides and Unknown. Prompt budget 7,410 → 7,900 tokens (the measured
32
+ total after trimming every new description, a named exception).
33
+
34
+ ## 1. The serif family
35
+
36
+ `$type.family.serif` is valid wherever `sans`/`mono` are: a recipe's
37
+ `fontFamily`, a Box/Text node style's `fontFamily`. Every built-in theme
38
+ names `ui-serif` (neutral and playful directly, exponential through the
39
+ generator, orange by inheritance); `fonts["ui-serif"]` = `{fallback:
40
+ "Georgia, serif", source: "system"}`. Painters map the generic name, like
41
+ `ui-monospace`:
42
+
43
+ | renderer | `ui-serif` |
44
+ |---|---|
45
+ | web (React, Solid, element) | `font-family: ui-serif, Georgia, serif` (the family, then the fallback list: `fontStack` already does this) |
46
+ | SwiftUI | `Font.system(size:weight:design: .serif)` (New York); TextKit measure with the same descriptor (`.withDesign(.serif)`) |
47
+ | Compose | `FontFamily.Serif`; the TextShaper's generic switch gains `ui-serif` / `serif` |
48
+ | gpui | a system serif: `New York` on macOS (fall back to `Georgia`, then `Times New Roman`); the measure's availability check treats `ui-serif` like `ui-monospace` |
49
+ | conformance | `conformance/fonts.json` maps `ui-serif` to the substitute `Inter` (no serif face is committed), so the harness compares like with like |
50
+
51
+ Specimen: `exponential-ui-text` case `fontFamily · serif` (a lead Text with
52
+ `style.fontFamily: "$type.family.serif"`).
53
+
54
+ ## 2. MessageBubble
55
+
56
+ Parts: `root` (column, full width, `alignItems` flex-start for `in`, flex-end
57
+ for `out`; role group labelled by `author`, else `time`) → `bubble` (column,
58
+ gap xxs, max width 80 %, padding md × sm, radius xl, the top-start corner 6 px
59
+ on `in` bubbles = the app's tail) → `author` (caption, one line; `in` only)
60
+ → the `attachment` slot → `text` (Markdown) → `footer` (row, aligned end,
61
+ gap xs; shown when `time` or `status`) → `time` (caption) → `status` (Icon
62
+ sm at 14 × 14; `out` only; `sending` = `ui-clock`, `sent` = `ui-check`,
63
+ `read` = `ui-read` (check-check), labelled `$string.sending|sent|read`).
64
+ Recipe props `side`, `status`. Neutral recipes: `in` = the card fill
65
+ (`$color.card`) with a hairline `$color.border` (the app's agent bubble on
66
+ THE card surface), `out` = `$color.secondary` with the same hairline (the
67
+ app's own bubble); author/time/status muted.
68
+
69
+ ## 3. Segmented `orientation` (the rail) and `option.count`
70
+
71
+ Round 3's ONE Segmented rule stays: no new component. `orientation:
72
+ vertical` lays the items out as a column: every variant stacks its segments
73
+ top to bottom (the `segmented` track becomes a vertical pill, `outline`
74
+ joins vertically: the shared border runs horizontally); a vertical `bar` is
75
+ THE navigation rail: content-sized in height, full height of its parent is
76
+ the author's style, each item still icon over caption label, the item width
77
+ = the rail width, role `navigation`, `aria-current=page` on the selected
78
+ item, ArrowUp/ArrowDown move focus. Geometry (neutral, every theme inherits):
79
+ root `{variant: bar, orientation: vertical}` = width 72, no top rule, a
80
+ hairline on the trailing (right) edge; item = width 72 × height
81
+ `$control.tabBar` (56). `control-geometry.json` `variant=bar,orientation=vertical`
82
+ locks the item box per theme. The core lays the rail out (Rust
83
+ `layout_tree`: a vertical bar is a column of fixed items, not a filling row;
84
+ TS has no layout reference for Segmented, the Rust lane owns it).
85
+
86
+ `option.count` (number, bindable): a `bar` item paints a count badge at its
87
+ icon's top-end corner (the badge's centre on the icon box's top-right
88
+ corner, mirrored under RTL), text = the number (`99+` above 99), hidden at 0
89
+ or when unset; part `badge` (neutral: min width 16, height 16, padding xxs,
90
+ radius full, primary fill, xs medium text). Every other component and
91
+ variant ignores `count`. A11y: the count reads after the label
92
+ ("Chats, 3").
93
+
94
+ ## 4. CallSurface
95
+
96
+ THE app call screen (`packages/ui` `VoiceCallSurface` + `CallControls`) in
97
+ catalog terms. Parts: `root` (column, centred, full width and height,
98
+ padding xl / xl3) → `head` (column, centred, max width 384) → `wave`
99
+ (VoiceWave `xl`, hidden from assistive tech; activity = `muted` when
100
+ `muted`, else ringing → `thinking`, connecting → `thinking`, ended → `idle`,
101
+ talking → the `activity` prop, default `speaking`) → `avatar` (Avatar lg,
102
+ the wave's child) → `name` (Text title, heading level 2, one line) → `status`
103
+ (muted, one line; the HOST ticks the timer, the core has no clock) →
104
+ `caption` (centred, at most 4 lines, live polite) → `spacer` (flex 1) →
105
+ `controls` (row, centred, 32 px apart): `muteTip` Tooltip → `mute` (icon
106
+ `call-mute`, `call-unmute` while muted; label `$string.mute` /
107
+ `$string.unmute`; `$set` writes a bound `muted` back; context `{muted}` =
108
+ the new value), `speakerTip` → `speaker` (only when the `speaker` prop is
109
+ given; `call-speaker` / `call-speaker-off`, `$string.speaker` /
110
+ `$string.speakerOff`, context `{speaker}`), `endTip` → `end` (`call-end`,
111
+ `$string.endCall`, event `hangUp`); while ringing ONLY `cancel` (column, gap
112
+ xs) → `end` (labelled `$string.cancel`) → `cancelLabel` (caption, the word
113
+ written under the button, hidden from assistive tech). Every round button
114
+ is a Button `size: icon` with node style 56 × 56, radius full, its Icon `lg`
115
+ (24) as the custom child; the tooltip text = its accessibility label.
116
+ Recipe props `state`, `muted`. Neutral: `mute`/`speaker` = secondary fill,
117
+ foreground icon; `mute` when `muted: true` = INVERTED (foreground fill,
118
+ background icon); `end` = THE red one (destructive fill at full strength,
119
+ destructiveForeground); name xl2 semibold.
120
+
121
+ ## 5. VoiceWave `variant: bars`
122
+
123
+ `variant: ring` is today's look. `bars` = a horizontal level meter of
124
+ `count` (20) rounded vertical bars, same `activity`/`level`/`bands`/phase
125
+ inputs, NO child (a child stays in the tree, unpainted, like `sm`). Sizes
126
+ (`style.json voiceWave.bars.sizes`): sm 16 tall, 2 px bars, 2 px gaps (78
127
+ wide); md 24/3/3 (117); lg 40/4/4 (156); xl 56/5/5 (195); the root recipe's
128
+ `{variant: bars, size}` rules carry the same boxes (`control-geometry.json`).
129
+ Bar i: drive d (listening | speaking: (2 + 6·level)/8; thinking: 2.5/8;
130
+ idle | muted: 0), band b (bands[i mod len] clamped, 1 when none or
131
+ thinking), envelope e = 1 − 0.5·|2i/19 − 1|, shape s = 0.5 + 0.5·sin(1.7i +
132
+ phase·(1 + (i mod 3)·0.5)), fraction f = 0.12 + 0.88·d·b·e·(0.35 + 0.65·s);
133
+ height = round2(H·f), centred vertically, x = i·(barWidth + gap), radius
134
+ barWidth/2, filled with the `bar` part colour (primary; muted foreground when
135
+ idle or muted) at the root's opacity. Phase = `voiceWavePhase` (0 when idle,
136
+ muted, reduced motion). `voiceWaveBars(input)` is the reference,
137
+ `voice-wave.json` `bars` (8 cases) locks every rect.
138
+
139
+ ## 6. QrCode
140
+
141
+ The core owns the matrix: `qrMatrix(value, level)` (`src/qr-code.ts`, a
142
+ port of Project Nayuki's QR Code generator, MIT) = byte mode over the UTF-8
143
+ bytes, the smallest version 1–40 that fits, the level EXACT (no boost), the
144
+ lowest-penalty mask → `{size, version, mask, rows}` ('1' dark, no quiet
145
+ zone); `null` for an empty value or one version 40 cannot hold at that level
146
+ (2,953 bytes at L … 1,273 at H). An unknown level is `M`. The Rust lane
147
+ ports the same algorithm (`qr_code.rs` + FFI `qr_matrix_json`), replaying
148
+ `qr-code.json` (13 cases) bit for bit; every SDK painter takes the matrix
149
+ from the core (web: the TS reference; natives: the FFI), never from a
150
+ platform QR library.
151
+
152
+ Paint (`style.json qrCode`): root = a centred column, gap 8 (`labelGap`);
153
+ the `plate` is `size` px square (default 160), radius = the plate recipe
154
+ (`$radius.md`); module = size / (n + 2·4) px (quiet zone 4 modules each
155
+ side), fractional, painters snap module EDGES to device pixels so the outer
156
+ size stays exact; DARK MODULES ON A LIGHT PLATE ALWAYS: of the resolved
157
+ `plate` and `module` colours the lighter (relative luminance) fills the
158
+ plate and the darker the modules, so a dark theme still scans. `null`
159
+ matrix = the empty plate. The `label` = a caption Text (muted, centred)
160
+ under the plate AND the img's accessibility label (no label = decorative).
161
+
162
+ ## 7. QrScanner
163
+
164
+ The component is the viewfinder CHROME; THE CAMERA IS HOST-OWNED (like
165
+ FileUpload's bytes). Each renderer defines a renderer-level host hook in its
166
+ own package API (NOT `catalog/host.json`): the host renders its live feed
167
+ into the `feed` region, decodes codes of the given `formats` (default
168
+ `["qr"]`) and hands the renderer the decoded string, which the renderer
169
+ dispatches as `scanned {value}` (once per distinct value until it changes
170
+ or the scanner remounts; `disabled` stops reporting and shows the chrome at
171
+ the root's disabled opacity). Geometry (`style.json qrScanner`): root =
172
+ centred column, gap 12; `viewfinder` = a square (aspect 1) as wide as the
173
+ root up to 280 px, radius `$radius.xl`, clipping; `feed` = the host's video,
174
+ or without one the `feed` colour (foreground) at alpha 0.06; four `corner`
175
+ brackets at the inside corners, two strokes 24 px long, 3 px wide, primary,
176
+ rounded caps; `scanLine` = a 2 px primary bar inset 16 px left and right,
177
+ sweeping top → bottom → top over 2,000 ms (ease standard, alternate) while a
178
+ feed shows; reduced motion or disabled = static at the vertical centre. No
179
+ host camera = no feed, no line: the `placeholderIcon` (`ui-scan` =
180
+ scan-line, 32 px, muted) centred and the caption `$string.cameraUnavailable`
181
+ under it. `hint` = a muted Text centred under the viewfinder. A11y: img
182
+ labelled `label` (default `$string.scanQrCode`); a decoded value is
183
+ announced politely.
184
+
185
+ Lane hooks (each lane names its own, documented in its README): React /
186
+ Solid / element `qrScanner` host option `{start(feedElement, formats,
187
+ onValue) → stop}`; SwiftUI an environment value (`\.exponentialQrScanner`)
188
+ returning a feed view + an async value stream; Compose a `CompositionLocal`
189
+ (`LocalQrScanner`); gpui a host trait method. Absent = the placeholder.
190
+
191
+ ## 8. TraceItem `done`/`skipped` and Plan
192
+
193
+ `traceStatus` = running | error | pending | done | skipped (appended).
194
+ `done` = the line at full strength, its icon tinted `$color.success`;
195
+ `skipped` = dimmed like pending (line opacity 0.6) and the title struck
196
+ through (`textDecoration: line-through`). Recipes only; the template is
197
+ unchanged (`shimmer` still only for running).
198
+
199
+ **Plan** parts: `root` (column; group labelled by `title`) → `card` (a Card)
200
+ → `header` (column, gap xxs) → `title` (Heading h3) → `description` (muted)
201
+ → `steps` (a Trace; `$each` step → `step` = TraceItem with icon, title,
202
+ description = `detail`, status) → `footer` (proposed only; row, end, gap sm:
203
+ `decline` outline Button `{declineLabel|$string.no}` → event `decline`,
204
+ `approve` default Button with `ui-check` `{approveLabel|$string.yes}` →
205
+ `approve`) → `running` (running only; row, gap md: `progress` = Progress
206
+ `value: progress`, `max: 1`, flex 1, then `stop` = destructive Button with
207
+ `ui-stop` `{stopLabel|$string.stop}` → `stop`). `done`/`declined` = no
208
+ footer. Recipe prop `state`. `steps` is static (an `$each` over a bound
209
+ array emits nothing; bind each step's status instead).
210
+
211
+ ## 9. Button `hold`
212
+
213
+ With `hold: true` a press NEVER fires `press`. Pointer down (or Space /
214
+ Enter key down; key repeat ignored) fires `holdStart` and paints the
215
+ `pressed` state and `holdingLabel` (when given); while held the pointer may
216
+ wander up to 24 px (THE slop) outside the box; release (pointer up within
217
+ the slop, or key up of the key that started it) fires `holdEnd`; the pointer
218
+ leaving the box by more than the slop (the slide away), a pointer cancel,
219
+ Escape or losing focus fire `holdCancel`; after a cancel nothing fires until
220
+ the next down. `disabled` fires nothing. Touch: the platform's long-press
221
+ and scroll gestures must not steal a hold (claim the pointer on down).
222
+ `interactions-round1.json` `buttonHold` (10 cases, step by step: the events
223
+ each step fires, `pressed`, the painted label) is replayed by every lane.
224
+
225
+ ## 10. Breaking changes
226
+
227
+ None for authors. Renderers: the new enums and components appear in the
228
+ generated constants; `traceStatus` gained two values; the kitchen sink grew
229
+ (replay baselines, bench node counts and allocation counts move).
230
+
231
+ ## 11. Per-renderer checklist
232
+
233
+ Every renderer: replay the regenerated `catalog-components.json` (579),
234
+ `catalog-macros.json` (312), `bind-time.json`, `theme-*.json`,
235
+ `control-geometry.json` (400), `kitchen-sink*.json`; paint the five new
236
+ specimens (`exponential-ui-message-bubble`, `-call-surface`, `-plan`,
237
+ `-qr-code`, `-qr-scanner`, view-catalog entries added) and the new extras
238
+ (TraceItem done/skipped, Segmented rail with counts, VoiceWave bars ×4,
239
+ Button hold, serif Text); re-record the shots ×4.
240
+
241
+ - **TS reference (done here)**: `qrMatrix`, `voiceWaveBars`, the fixtures.
242
+ Left for the React lane: re-record `fixtures/conformance-baseline.json`
243
+ (the web DOM dump of the grown kitchen sink and the new cases) and the
244
+ ratchets in `conformance-known.json`.
245
+ - **React / Solid / element**: QrCode (SVG of `rows`, the plate swap),
246
+ QrScanner (the `qrScanner` host option, the placeholder, the sweep via a
247
+ CSS animation honouring `prefers-reduced-motion`), VoiceWave bars, the
248
+ Segmented column + rail + badge, Button hold (pointer capture, slop,
249
+ Escape/blur, `interactions-round1.json buttonHold` in
250
+ `interactions-fixture.test.tsx`), `ui-serif` in `fontStack`; Solid and the
251
+ element stay parity-gated against React.
252
+ - **Rust core**: `qr_code.rs` (the port, `qr-code.json` bit for bit, FFI
253
+ `qr_matrix_json`), `voice_wave_bars` (`voice-wave.json bars`, FFI), the
254
+ vertical Segmented in `layout_tree` (a vertical bar = a column of fixed
255
+ 72 × 56 items, not content-sized in width), Button hold state + events
256
+ (`interactions_fixture.rs` replays `buttonHold`), the new enums/strings
257
+ from the generated catalog.
258
+ - **gpui**: paint QrCode (module rects from the FFI-free core call),
259
+ QrScanner (host trait, placeholder), VoiceWave bars, the rail + badge,
260
+ Button hold (mouse capture, 24 px slop), the serif mapping; re-record the
261
+ conformance ratchet against the new web baseline.
262
+ - **SwiftUI**: QrCode (Canvas of the core matrix, pixel-snapped), QrScanner
263
+ (environment hook, AVFoundation stays in the HOST, not the package),
264
+ VoiceWave bars, the rail + badge, Button hold (a `DragGesture(minimumDistance: 0)`
265
+ with the slop), `.serif`; snapshots re-recorded.
266
+ - **Compose**: the same five; QrScanner through `LocalQrScanner` (CameraX
267
+ stays in the host), Button hold via `awaitEachGesture` with the slop,
268
+ `FontFamily.Serif`; snapshots re-recorded on ubuntu.