@exponential_app/eui 0.0.0-stage → 0.2.1
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.
- package/LICENSE +201 -0
- package/NOTICE +54 -0
- package/README.md +377 -2
- package/catalog/a11y.json +705 -0
- package/catalog/basic-map.json +104 -0
- package/catalog/code.json +58 -0
- package/catalog/core.catalog.json +5190 -0
- package/catalog/core.schema.json +24542 -0
- package/catalog/host.json +95 -0
- package/catalog/layout.json +20 -0
- package/catalog/limits.json +9 -0
- package/catalog/locale.json +48 -0
- package/catalog/macros.json +4152 -0
- package/catalog/recipes.json +64 -0
- package/catalog/strings.json +84 -0
- package/catalog/style.json +180 -0
- package/catalog/theme.schema.json +47754 -0
- package/catalog/tokens.json +47 -0
- package/conformance/README.md +218 -0
- package/conformance/baseline.ts +101 -0
- package/conformance/browser-page.ts +146 -0
- package/conformance/browser-suites.ts +699 -0
- package/conformance/compare.ts +201 -0
- package/conformance/dom.ts +97 -0
- package/conformance/dump.ts +163 -0
- package/conformance/fonts.json +157 -0
- package/conformance/manifest.json +233 -0
- package/conformance/parity-compare.ts +191 -0
- package/conformance/parity-report.schema.json +73 -0
- package/conformance/parity.ts +412 -0
- package/conformance/pixel.ts +177 -0
- package/conformance/primitives-page.ts +49 -0
- package/conformance/report.schema.json +66 -0
- package/conformance/run.ts +118 -0
- package/conformance/voice-wave-child.ts +102 -0
- package/dist/chunks/index-5fmaa0xq.js +8384 -0
- package/dist/chunks/index-5fmaa0xq.js.map +10 -0
- package/dist/chunks/index-62yzr94z.js +2784 -0
- package/dist/chunks/index-62yzr94z.js.map +10 -0
- package/dist/chunks/index-bg2jsgjq.js +8541 -0
- package/dist/chunks/index-bg2jsgjq.js.map +15 -0
- package/dist/chunks/index-fy7y2nz8.js +186 -0
- package/dist/chunks/index-fy7y2nz8.js.map +9 -0
- package/dist/chunks/index-gtk2gwsb.js +4555 -0
- package/dist/chunks/index-gtk2gwsb.js.map +31 -0
- package/dist/chunks/index-t6qz1770.js +95 -0
- package/dist/chunks/index-t6qz1770.js.map +11 -0
- package/dist/conformance.js +230462 -0
- package/dist/conformance.js.map +10 -0
- package/dist/entries/prompt.js +251 -0
- package/dist/entries/prompt.js.map +11 -0
- package/dist/entries/schema.js +1725 -0
- package/dist/entries/schema.js.map +11 -0
- package/dist/host/index.js +194 -0
- package/dist/host/index.js.map +9 -0
- package/dist/index.js +3851 -0
- package/dist/index.js.map +30 -0
- package/dist/types/src/a11y.d.ts +21 -0
- package/dist/types/src/animation.d.ts +76 -0
- package/dist/types/src/basic-map.d.ts +38 -0
- package/dist/types/src/builder.d.ts +50 -0
- package/dist/types/src/catalog.d.ts +54 -0
- package/dist/types/src/catalog.described.d.ts +33 -0
- package/dist/types/src/catalog.generated.d.ts +904 -0
- package/dist/types/src/chart.d.ts +46 -0
- package/dist/types/src/code.d.ts +44 -0
- package/dist/types/src/color.d.ts +20 -0
- package/dist/types/src/conformance.d.ts +39 -0
- package/dist/types/src/connector/app.d.ts +14 -0
- package/dist/types/src/connector/connector.d.ts +77 -0
- package/dist/types/src/connector/index.d.ts +4 -0
- package/dist/types/src/css.d.ts +27 -0
- package/dist/types/src/define.d.ts +66 -0
- package/dist/types/src/direction.d.ts +16 -0
- package/dist/types/src/dynamic.d.ts +161 -0
- package/dist/types/src/entries/prompt.d.ts +11 -0
- package/dist/types/src/entries/schema.d.ts +8 -0
- package/dist/types/src/expr.d.ts +71 -0
- package/dist/types/src/extension.d.ts +4 -0
- package/dist/types/src/format.d.ts +112 -0
- package/dist/types/src/geometry.d.ts +40 -0
- package/dist/types/src/host/contract.d.ts +225 -0
- package/dist/types/src/host/decoders.d.ts +61 -0
- package/dist/types/src/host/index.d.ts +15 -0
- package/dist/types/src/host/package.d.ts +102 -0
- package/dist/types/src/host/policy.d.ts +107 -0
- package/dist/types/src/host/record-store.d.ts +103 -0
- package/dist/types/src/host/records.d.ts +45 -0
- package/dist/types/src/host/router.d.ts +38 -0
- package/dist/types/src/host/runtime.d.ts +213 -0
- package/dist/types/src/host/signature.d.ts +52 -0
- package/dist/types/src/host/sources.d.ts +22 -0
- package/dist/types/src/host/tools.d.ts +42 -0
- package/dist/types/src/host/transports.d.ts +109 -0
- package/dist/types/src/index.d.ts +70 -0
- package/dist/types/src/layout.d.ts +20 -0
- package/dist/types/src/limits.d.ts +21 -0
- package/dist/types/src/list.d.ts +133 -0
- package/dist/types/src/locale.d.ts +22 -0
- package/dist/types/src/macros.d.ts +29 -0
- package/dist/types/src/overlay.d.ts +27 -0
- package/dist/types/src/prompt.d.ts +44 -0
- package/dist/types/src/qr-code.d.ts +30 -0
- package/dist/types/src/recipes.d.ts +17 -0
- package/dist/types/src/reducer.d.ts +41 -0
- package/dist/types/src/resizable.d.ts +38 -0
- package/dist/types/src/schema.d.ts +7 -0
- package/dist/types/src/strings.d.ts +13 -0
- package/dist/types/src/style.d.ts +201 -0
- package/dist/types/src/test-preload.d.ts +1 -0
- package/dist/types/src/text-clusters.d.ts +11 -0
- package/dist/types/src/text-shimmer.d.ts +24 -0
- package/dist/types/src/theme-types.d.ts +162 -0
- package/dist/types/src/theme.d.ts +75 -0
- package/dist/types/src/themes.d.ts +22 -0
- package/dist/types/src/tree-guides.d.ts +17 -0
- package/dist/types/src/types.d.ts +320 -0
- package/dist/types/src/validate.d.ts +31 -0
- package/dist/types/src/voice-wave.d.ts +181 -0
- package/dist/types/src/web/base-css.d.ts +1 -0
- package/dist/types/src/web/box-css.d.ts +63 -0
- package/dist/types/src/web/coerce.d.ts +32 -0
- package/dist/types/src/web/custom-icons.generated.d.ts +4 -0
- package/dist/types/src/web/data.d.ts +107 -0
- package/dist/types/src/web/dates.d.ts +12 -0
- package/dist/types/src/web/form.d.ts +44 -0
- package/dist/types/src/web/host-types.d.ts +126 -0
- package/dist/types/src/web/index.d.ts +35 -0
- package/dist/types/src/web/inputs.d.ts +55 -0
- package/dist/types/src/web/list.d.ts +16 -0
- package/dist/types/src/web/listbox.d.ts +14 -0
- package/dist/types/src/web/markdown.d.ts +96 -0
- package/dist/types/src/web/media.d.ts +63 -0
- package/dist/types/src/web/parts.d.ts +4 -0
- package/dist/types/src/web/primitives/alert-dialog.d.ts +10 -0
- package/dist/types/src/web/primitives/alert.d.ts +9 -0
- package/dist/types/src/web/primitives/avatar-color.d.ts +2 -0
- package/dist/types/src/web/primitives/avatar.d.ts +13 -0
- package/dist/types/src/web/primitives/badge.d.ts +10 -0
- package/dist/types/src/web/primitives/breadcrumb.d.ts +10 -0
- package/dist/types/src/web/primitives/button.d.ts +6 -0
- package/dist/types/src/web/primitives/calendar.d.ts +43 -0
- package/dist/types/src/web/primitives/card.d.ts +9 -0
- package/dist/types/src/web/primitives/checkbox.d.ts +5 -0
- package/dist/types/src/web/primitives/choice-tiles.d.ts +32 -0
- package/dist/types/src/web/primitives/cn.d.ts +3 -0
- package/dist/types/src/web/primitives/code-block.d.ts +24 -0
- package/dist/types/src/web/primitives/command.d.ts +134 -0
- package/dist/types/src/web/primitives/context-ring.d.ts +24 -0
- package/dist/types/src/web/primitives/dialog-arms.d.ts +6 -0
- package/dist/types/src/web/primitives/dialog.d.ts +24 -0
- package/dist/types/src/web/primitives/disclosure-header.d.ts +5 -0
- package/dist/types/src/web/primitives/dock.d.ts +42 -0
- package/dist/types/src/web/primitives/dropdown-menu.d.ts +14 -0
- package/dist/types/src/web/primitives/empty-state.d.ts +15 -0
- package/dist/types/src/web/primitives/glass-card.d.ts +4 -0
- package/dist/types/src/web/primitives/hover-card.d.ts +7 -0
- package/dist/types/src/web/primitives/icon-disc.d.ts +12 -0
- package/dist/types/src/web/primitives/index.d.ts +50 -0
- package/dist/types/src/web/primitives/input.d.ts +5 -0
- package/dist/types/src/web/primitives/kbd.d.ts +4 -0
- package/dist/types/src/web/primitives/label.d.ts +3 -0
- package/dist/types/src/web/primitives/measure.d.ts +16 -0
- package/dist/types/src/web/primitives/menu-surface.d.ts +17 -0
- package/dist/types/src/web/primitives/meter.d.ts +21 -0
- package/dist/types/src/web/primitives/popover.d.ts +3 -0
- package/dist/types/src/web/primitives/progress-ring.d.ts +5 -0
- package/dist/types/src/web/primitives/progress.d.ts +8 -0
- package/dist/types/src/web/primitives/scroll-area.d.ts +49 -0
- package/dist/types/src/web/primitives/segmented-control.d.ts +5 -0
- package/dist/types/src/web/primitives/select.d.ts +47 -0
- package/dist/types/src/web/primitives/separator.d.ts +3 -0
- package/dist/types/src/web/primitives/sheet-chrome.d.ts +4 -0
- package/dist/types/src/web/primitives/sheet.d.ts +20 -0
- package/dist/types/src/web/primitives/skeleton.d.ts +3 -0
- package/dist/types/src/web/primitives/spinner.d.ts +3 -0
- package/dist/types/src/web/primitives/switch.d.ts +4 -0
- package/dist/types/src/web/primitives/table.d.ts +11 -0
- package/dist/types/src/web/primitives/tabs.d.ts +13 -0
- package/dist/types/src/web/primitives/textarea.d.ts +3 -0
- package/dist/types/src/web/primitives/toast.d.ts +163 -0
- package/dist/types/src/web/primitives/tooltip.d.ts +3 -0
- package/dist/types/src/web/primitives/truncate.d.ts +7 -0
- package/dist/types/src/web/primitives/typeahead-portal.d.ts +12 -0
- package/dist/types/src/web/primitives/voice-wave.d.ts +50 -0
- package/dist/types/src/web/round5.d.ts +107 -0
- package/dist/types/src/web/share.d.ts +6 -0
- package/dist/types/src/web/table.d.ts +20 -0
- package/dist/types/src/web/theme-css.d.ts +69 -0
- package/dist/types/src/web/urls.d.ts +21 -0
- package/dist/web/index.js +2565 -0
- package/dist/web/index.js.map +28 -0
- package/dist/web/primitives/index.js +1476 -0
- package/dist/web/primitives/index.js.map +57 -0
- package/docs/components.generated.json +6783 -0
- package/docs/package-format.md +182 -0
- package/docs/round-1-contract.md +679 -0
- package/docs/round-2-contract.md +476 -0
- package/docs/round-3-contract.md +174 -0
- package/docs/round-5-contract.md +268 -0
- package/docs/themes.generated.json +3647 -0
- package/fixtures/animations.json +2890 -0
- package/fixtures/bench-list.json +58 -0
- package/fixtures/bind-time.json +54734 -0
- package/fixtures/catalog-basic-map.json +1692 -0
- package/fixtures/catalog-components.json +10293 -0
- package/fixtures/catalog-extension.json +620 -0
- package/fixtures/catalog-macros.json +51805 -0
- package/fixtures/code-tokens.json +1993 -0
- package/fixtures/conformance-baseline.json +59016 -0
- package/fixtures/conformance-cases.json +49 -0
- package/fixtures/conformance-known.json +2111 -0
- package/fixtures/control-geometry.json +5129 -0
- package/fixtures/dashboard.data.json +74 -0
- package/fixtures/dashboard.json +781 -0
- package/fixtures/demo-surface.json +47 -0
- package/fixtures/format.json +870 -0
- package/fixtures/host-package.json +1262 -0
- package/fixtures/host-policy.json +1209 -0
- package/fixtures/host-router.json +2034 -0
- package/fixtures/host-transport.json +590 -0
- package/fixtures/interactions-round1.json +1058 -0
- package/fixtures/kitchen-sink.expanded.json +11972 -0
- package/fixtures/kitchen-sink.json +3140 -0
- package/fixtures/layout-geometry-round1.json +7278 -0
- package/fixtures/layout-geometry.json +2072 -0
- package/fixtures/overlay-geometry.json +272 -0
- package/fixtures/pointer.json +89 -0
- package/fixtures/primitive-specimens.json +59 -0
- package/fixtures/prompt-budget.json +33 -0
- package/fixtures/qr-code.json +480 -0
- package/fixtures/resizable.json +714 -0
- package/fixtures/round4-contract.json +2985 -0
- package/fixtures/specimens.json +11168 -0
- package/fixtures/style-conditions.json +336 -0
- package/fixtures/template-items.json +2215 -0
- package/fixtures/text-clusters.json +65 -0
- package/fixtures/text-direction.json +309 -0
- package/fixtures/text-shimmer.json +96 -0
- package/fixtures/theme-extends.json +573 -0
- package/fixtures/theme-invalid.json +1300 -0
- package/fixtures/theme-recipes.json +61762 -0
- package/fixtures/theme-resolved.json +25969 -0
- package/fixtures/toast-stack.json +215 -0
- package/fixtures/tree-guides.json +224 -0
- package/fixtures/virtual-list.json +733 -0
- package/fixtures/voice-wave.json +19236 -0
- package/generated/ExponentialUICatalog.generated.kt +67 -0
- package/generated/ExponentialUICatalog.generated.swift +67 -0
- package/generated/ExponentialUIThemes.generated.kt +23 -0
- package/generated/ExponentialUIThemes.generated.swift +23 -0
- package/generated/catalog.descriptions.generated.rs +6 -0
- package/generated/catalog.generated.rs +80 -0
- package/generated/catalog.themes.generated.rs +34 -0
- package/generated/core.descriptions.json +2726 -0
- package/generated/core.runtime.json +3944 -0
- package/package.json +87 -4
- package/themes/exponential.recipes.json +49 -0
- package/themes/exponential.theme.json +768 -0
- package/themes/neutral.theme.json +6178 -0
- package/themes/orange.theme.json +43 -0
- package/themes/playful.theme.json +219 -0
- package/vendor/a2ui/LICENSE.txt +203 -0
- package/vendor/a2ui/README.md +27 -0
- package/vendor/a2ui/v0_9/basic_catalog.json +1387 -0
- package/vendor/a2ui/v0_9/basic_catalog_rules.txt +5 -0
- package/vendor/a2ui/v0_9/client_capabilities.json +97 -0
- package/vendor/a2ui/v0_9/client_to_server.json +104 -0
- package/vendor/a2ui/v0_9/common_types.json +315 -0
- package/vendor/a2ui/v0_9/server_to_client.json +132 -0
- package/vendor/json-render/LICENSE.txt +201 -0
- package/vendor/json-render/README.md +13 -0
- package/vendor/nayuki-qr/LICENSE.txt +20 -0
- 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.
|