@adia-ai/a2ui 0.8.46 → 0.8.48
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/CHANGELOG.md +20 -0
- package/catalog/catalog-a2ui_0_9.json +62 -8
- package/catalog/catalog-a2ui_0_9_rules.txt +3 -1
- package/catalog/common_types.json +1 -1
- package/catalog/tier-index.json +2 -2
- package/package.json +1 -1
- package/prop-apply.js +24 -0
- package/renderer.js +13 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog — @adia-ai/a2ui
|
|
2
2
|
|
|
3
|
+
## [0.8.48] — 2026-08-23
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Runtime stops stamping synthetic `.id` onto elements with no authored id.** `resolve-node.js`'s synthetic-id path fired for every unauthored element; now only elements that need an id for event wiring get one (#1872).
|
|
7
|
+
- **`prop-apply.js` reflects `JS_PROPS` as JSON attributes alongside property writes (gh#1858).** Attribute-based selectors and SSR snapshots were seeing a stale value because only the JS property was updated; both now update together (#1870).
|
|
8
|
+
|
|
9
|
+
### Note
|
|
10
|
+
- The headline `[Unreleased]` work above shipped in `@adia-ai/a2ui` itself (runtime fixes); `@adia-ai/web-components`' table range-grammar (gh#1877, ADR-0082 Amendment) and toolbar `title-always` (gh#1879) are the other substantive arcs feeding this window — see `packages/web-components/CHANGELOG.md#unreleased`. This package's own `catalog/` touch below is downstream regen, not an independent change.
|
|
11
|
+
|
|
12
|
+
### Maintenance
|
|
13
|
+
- **`catalog/` touched in this release window** — carried by the entries above.
|
|
14
|
+
|
|
15
|
+
## [0.8.47] — 2026-08-22
|
|
16
|
+
|
|
17
|
+
### Note
|
|
18
|
+
- The headline v0.8.47 work shipped in `@adia-ai/web-components` (table-toolbar `size` prop + cascade/min-width fixes, anchor reuse guard, gh#1852 token cleanup, nav polish). See `packages/web-components/CHANGELOG.md#0847--2026-08-22` for details; this package's own catalog touch below is downstream regen, not an independent change.
|
|
19
|
+
|
|
20
|
+
### Maintenance
|
|
21
|
+
- **`catalog/` touched in this release window** (2 file(s), e.g. `catalog/catalog-a2ui_0_9.json`) — carried by the entries above.
|
|
22
|
+
|
|
3
23
|
## [0.8.46] — 2026-08-22
|
|
4
24
|
|
|
5
25
|
|
|
@@ -5892,7 +5892,7 @@
|
|
|
5892
5892
|
"default": false
|
|
5893
5893
|
},
|
|
5894
5894
|
"ratio": {
|
|
5895
|
-
"description": "Chart 2.0 aspect-ratio bucket (ADR-0074). Unset (default) auto-snaps to the nearest of the three studied ratios by comparing the chart's own live box aspect against two midpoint boundaries (5:4 between 3:2/1:1, 4:5 between 1:1/2:3) — never interpolated; the resolved bucket reflects onto the host as `data-ratio-resolved`. Setting `ratio` explicitly pins that bucket regardless of the box's actual aspect, overriding the auto-snap. Orthogonal to `size` (overall scale) and `type`/`color` (ADR-0064's family axis) —
|
|
5895
|
+
"description": "Chart 2.0 aspect-ratio bucket (ADR-0074). Unset (default) auto-snaps to the nearest of the three studied ratios by comparing the chart's own live box aspect against two midpoint boundaries (5:4 between 3:2/1:1, 4:5 between 1:1/2:3) — never interpolated; the resolved bucket reflects onto the host as `data-ratio-resolved`. Setting `ratio` explicitly pins that bucket regardless of the box's actual aspect, overriding the auto-snap. Orthogonal to `size` (overall scale) and `type`/`color` (ADR-0064's family axis) — per-type studied renderings per bucket shipped across Waves A-C (gh#1624 plan steps 2-4: #1658 bar/line/area/pie/donut/sparkline, #1659 radar/scatter/ radial-bar/gauge/segments/stacked-bar/grouped-bar/multi-line, #1662 funnel/treemap/sankey/composed) — all 18 types have dedicated per-ratio render paths.",
|
|
5896
5896
|
"type": "string",
|
|
5897
5897
|
"enum": [
|
|
5898
5898
|
"3:2",
|
|
@@ -25044,8 +25044,11 @@
|
|
|
25044
25044
|
"default": 0
|
|
25045
25045
|
},
|
|
25046
25046
|
"range-total": {
|
|
25047
|
-
"description": "Total row count across ALL server pages (gh#1754, ADR-0082) — the table-authoritative server-mode data contract. Presence-gated: the attribute's PRESENCE, not its value, is the mode switch (checked via hasAttribute, never the coerced value alone — the same discipline table-footer-ui's own [range-total] ships). Absent (the default) means client mode: today's behavior exactly, [paginate] slices `.data` as always. Present with [paginate] > 0 means server mode: no local slicing (`.data` IS the current page and renders whole, after local search/sort/filter), the internal pager's page count becomes `max(1, ceil(range-total / paginate))` (one calc site, consolidated), and the internal page-reset sites (`data` set, `setFilter`, `clearFilters`) are suppressed so a fetch write-back never fights the pager back to page 0 — page state then moves only via pager interaction, the `footer-page` command, or `setState()`. The existing `page` event (0-based, unchanged) becomes the fetch trigger: the consumer listens for it, fetches that server page, and writes `.data` back. Explicit `range-total=\"0\"` is server-confirmed empty (pager hidden), never conflated with absent. With [paginate] absent/0, [range-total] is inert for this table's own rendering but stays readable by a bound table-footer-ui (REQ-W-006 branch 2) for its count-only label — see the footer-authoritative shape below, which remains lawful and is unaffected by this attribute. Named identically to table-toolbar-ui's and table-footer-ui's own [range-total] (rows, never `pagination-ui[total]`'s pages — the ADR-0063 B5 collision rule); a third instance of one name for one concept.",
|
|
25048
|
-
"type":
|
|
25047
|
+
"description": "Total row count across ALL server pages (gh#1754, ADR-0082) — the table-authoritative server-mode data contract. Presence-gated: the attribute's PRESENCE, not its value, is the mode switch (checked via hasAttribute, never the coerced value alone — the same discipline table-footer-ui's own [range-total] ships). Absent (the default) means client mode: today's behavior exactly, [paginate] slices `.data` as always. Present with [paginate] > 0 means server mode: no local slicing (`.data` IS the current page and renders whole, after local search/sort/filter), the internal pager's page count becomes `max(1, ceil(range-total / paginate))` (one calc site, consolidated), and the internal page-reset sites (`data` set, `setFilter`, `clearFilters`) are suppressed so a fetch write-back never fights the pager back to page 0 — page state then moves only via pager interaction, the `footer-page` command, or `setState()`. The existing `page` event (0-based, unchanged) becomes the fetch trigger: the consumer listens for it, fetches that server page, and writes `.data` back. Explicit `range-total=\"0\"` is server-confirmed empty (pager hidden), never conflated with absent. With [paginate] absent/0, [range-total] is inert for this table's own rendering but stays readable by a bound table-footer-ui (REQ-W-006 branch 2) for its count-only label — see the footer-authoritative shape below, which remains lawful and is unaffected by this attribute. Named identically to table-toolbar-ui's and table-footer-ui's own [range-total] (rows, never `pagination-ui[total]`'s pages — the ADR-0063 B5 collision rule); a third instance of one name for one concept. Explicit `range-total=\"?\"` (gh#1877, ADR-0082 Amendment) is the OPEN/unproven-total state — cursor/hasMore server paging with no known row count yet: still server mode (still presence-gated), but the internal pager's page count stays exactly one page ahead of wherever the pager currently sits (never a fixed ceil(...) total), keeping `next` enabled indefinitely instead of hiding the pager the way a confirmed `range-total=\"0\"` would. Resolves to the normal finite server-mode behavior the instant a real number replaces the \"?\" — every reader downstream (table-toolbar-ui, table-footer-ui) shares this same distinction. Note: since this prop is Number-typed and reflected, the framework's own attribute-reflection immediately rewrites the DOM attribute to the literal string \"NaN\" once \"?\" is parsed (`+\"?\"` → NaN, then stringified back onto the attribute) — the coerced numeric PROPERTY is what carries the open/unproven signal (non-finite), never a literal \"?\" surviving as the live DOM attribute value.",
|
|
25048
|
+
"type": [
|
|
25049
|
+
"number",
|
|
25050
|
+
"string"
|
|
25051
|
+
],
|
|
25049
25052
|
"default": 0
|
|
25050
25053
|
},
|
|
25051
25054
|
"raw": {
|
|
@@ -25625,11 +25628,24 @@
|
|
|
25625
25628
|
"type": "number",
|
|
25626
25629
|
"default": 0
|
|
25627
25630
|
},
|
|
25628
|
-
"range-
|
|
25629
|
-
"description": "
|
|
25631
|
+
"range-noun": {
|
|
25632
|
+
"description": "gh#1877, ADR-0082 Amendment — optional per-slice noun appended after the range total (\"of 80 users\") so consumers stop hand-painting the noun themselves. Applies in both the normal finite range text and the open/unproven-total text (\"Showing 51–100 users\" with no \"of N\"). Unset (the default) changes nothing. Named identically on table-toolbar-ui (ADR-0082 §\"same name on both companions\").",
|
|
25633
|
+
"type": "string",
|
|
25634
|
+
"default": ""
|
|
25635
|
+
},
|
|
25636
|
+
"range-of": {
|
|
25637
|
+
"description": "gh#1877, ADR-0082 Amendment — secondary whole-set total for the \"filtered-of-whole\" shape: a client-side facet narrows [range-total] (a filtered count) while range-of names the unfiltered set size, e.g. \"Showing 1–4 of 4 traversals (6 total)\". Appends \"(of <range-of> total)\" onto the normal finite range text; unset (the default) changes nothing. Never applies while [range-total] is absent (loading) or \"?\" (open/unproven) — there's no finite range text to append onto yet. Named identically on table-toolbar-ui (ADR-0082 §\"same name on both companions\").",
|
|
25630
25638
|
"type": "number",
|
|
25631
25639
|
"default": 0
|
|
25632
25640
|
},
|
|
25641
|
+
"range-total": {
|
|
25642
|
+
"description": "Total ROWS across all pages. Absent (the default) means \"not yet known\" (loading) — never \"zero\"; both the range label and the pager stay hidden until this is set. Explicit range-total=\"0\" is the confirmed-empty state (pager hidden; the `empty` slot renders in the range label's position when supplied). Named identically to table-toolbar-ui's [range-total] and table-ui's own [range-total] (gh#1754, ADR-0082) and deliberately NOT `total` (pagination-ui's `total` is total PAGES — the ADR-0063 B5 collision rule). Explicit range-total=\"?\" (gh#1877, ADR-0082 Amendment) is the OPEN/unproven- total state — cursor/hasMore server paging with no known row count yet: the range label renders \"Showing X–Y\" with no \"of N\", and the pager stays in has-more mode (next enabled) rather than hiding — distinct from both \"loading\" (attribute absent) and \"confirmed empty\" (range-total=\"0\"). Resolves to the normal finite behavior the moment a real number replaces the \"?\". When this attribute is unset, the footer derives it from the resolved target via a two-branch ladder (REQ-W-006, amended 0.2.0/ADR-0082): (1) the target's own [range-total] attribute, when present — the table-authoritative SERVER mode (gh#1754 Shape 1's second lawful composition; the target's own \"?\" carries through identically); the identity rule: the footer's range-total derives from the table's range-total, never the table's filteredCount, and this branch wins even when the target's [paginate] is also > 0; (2) otherwise, when the target has [paginate] > 0, CLIENT mode: the target's filtered row count (search + column filters applied, before pagination). Refreshes on the target's `page` / `filter-change` / `sort` events in either branch. Widened named staleness edge: a programmatic `.data` swap on the target — or a `range-total` update on the target without an accompanying page move — emits no event; either re-set this footer's own attributes after such a change, or accept staleness until the target's next event.",
|
|
25643
|
+
"type": [
|
|
25644
|
+
"number",
|
|
25645
|
+
"string"
|
|
25646
|
+
],
|
|
25647
|
+
"default": 0
|
|
25648
|
+
},
|
|
25633
25649
|
"siblings": {
|
|
25634
25650
|
"description": "Passed through to the composed pagination-ui's own [siblings] (page-number window size on each side of the current page).",
|
|
25635
25651
|
"type": "number",
|
|
@@ -25671,6 +25687,11 @@
|
|
|
25671
25687
|
"a2ui": "[\n {\"id\": \"root\", \"component\": \"Column\", \"gap\": \"0\", \"children\": [\"tests\", \"ftr\"]},\n {\"id\": \"tests\", \"component\": \"Table\", \"raw\": true},\n {\"id\": \"ftr\", \"component\": \"TableFooter\", \"for\": \"tests\", \"page\": 1, \"page-size\": 10, \"range-total\": 128}\n]",
|
|
25672
25688
|
"name": "table-footer-server-mode"
|
|
25673
25689
|
},
|
|
25690
|
+
{
|
|
25691
|
+
"description": "gh#1877, ADR-0082 Amendment — cursor/hasMore server mode with no known row count yet: [range-total=\"?\"] renders \"Showing 1–50 subscriptions\" (via [range-noun]) with `next` kept enabled until a real total resolves.",
|
|
25692
|
+
"a2ui": "[\n {\"id\": \"root\", \"component\": \"Column\", \"gap\": \"0\", \"children\": [\"subs\", \"ftr\"]},\n {\"id\": \"subs\", \"component\": \"Table\", \"raw\": true},\n {\"id\": \"ftr\", \"component\": \"TableFooter\", \"for\": \"subs\", \"page\": 1, \"page-size\": 50, \"range-total\": \"?\", \"range-noun\": \"subscriptions\"}\n]",
|
|
25693
|
+
"name": "table-footer-open-total"
|
|
25694
|
+
},
|
|
25674
25695
|
{
|
|
25675
25696
|
"description": "Client-mode footer bound to a [paginate]d table-ui with its own internal pager suppressed via [no-pager].",
|
|
25676
25697
|
"a2ui": "[\n {\"id\": \"root\", \"component\": \"Column\", \"gap\": \"0\", \"children\": [\"tests-c\", \"ftr\"]},\n {\"id\": \"tests-c\", \"component\": \"Table\", \"paginate\": 10, \"no-pager\": true, \"raw\": true},\n {\"id\": \"ftr\", \"component\": \"TableFooter\", \"for\": \"tests-c\"}\n]",
|
|
@@ -25970,16 +25991,39 @@
|
|
|
25970
25991
|
"type": "number",
|
|
25971
25992
|
"default": 0
|
|
25972
25993
|
},
|
|
25994
|
+
"rangeNoun": {
|
|
25995
|
+
"description": "gh#1877, ADR-0082 Amendment — optional per-slice noun appended after the range total (\"of 80 users\") so consumers stop hand-painting the noun themselves. Applies in both the normal finite range summary and the open/unproven-total summary (\"Showing 51–100 users\" with no \"of N\"). Unset (the default) changes nothing. Named identically to table-footer-ui's own [range-noun] (ADR-0082 §\"same name on both companions\").",
|
|
25996
|
+
"type": "string",
|
|
25997
|
+
"default": ""
|
|
25998
|
+
},
|
|
25999
|
+
"rangeOf": {
|
|
26000
|
+
"description": "gh#1877, ADR-0082 Amendment — secondary whole-set total for the \"filtered-of-whole\" shape: a client-side facet narrows [rangeTotal] (a filtered count) while rangeOf names the unfiltered set size, e.g. \"Showing 1–4 of 4 traversals (6 total)\". Appends \"(of <rangeOf> total)\" onto the normal finite range summary; unset (the default) changes nothing. Never applies while the summary is in its open (\"?\") or opted-out (all-zero) state. Named identically to table-footer-ui's own [range-of] (ADR-0082 §\"same name on both companions\").",
|
|
26001
|
+
"type": "number",
|
|
26002
|
+
"default": 0
|
|
26003
|
+
},
|
|
25973
26004
|
"rangeStart": {
|
|
25974
26005
|
"description": "1-indexed first row number of the current page, for the \"Showing X–Y of N\" range summary (e.g. `1` in \"Showing 1–25 of 320\"). Renders alongside — never in place of — [text]/[count]; the range summary is its own cluster, independent of the title cluster. All three of rangeStart/rangeEnd/rangeTotal must be set (> 0) for the summary to render; omit all three (the default) to opt out entirely.",
|
|
25975
26006
|
"type": "number",
|
|
25976
26007
|
"default": 0
|
|
25977
26008
|
},
|
|
25978
26009
|
"rangeTotal": {
|
|
25979
|
-
"description": "Total row count across all pages, for the range summary (e.g. `320` in \"Showing 1–25 of 320\"). Distinct from pagination-ui's `total` (total PAGES) — this is total ROWS; deliberately not spelled `total` to avoid that cross-sibling name collision (ADR-0063 B5). See rangeStart.",
|
|
25980
|
-
"type":
|
|
26010
|
+
"description": "Total row count across all pages, for the range summary (e.g. `320` in \"Showing 1–25 of 320\"). Distinct from pagination-ui's `total` (total PAGES) — this is total ROWS; deliberately not spelled `total` to avoid that cross-sibling name collision (ADR-0063 B5). See rangeStart. Explicit range-total=\"?\" (gh#1877, ADR-0082 Amendment) is the OPEN/unproven-total state — cursor/hasMore server paging with no known row count yet: the summary reads \"Showing X–Y\" with no \"of N\", requiring only [range-start]/[range-end] (> 0) to render, not rangeTotal too. Resolves to the normal finite summary the moment a real number replaces the \"?\". Named identically to table-footer-ui's own [range-total] (ADR-0082 §\"same name on both companions\").",
|
|
26011
|
+
"type": [
|
|
26012
|
+
"number",
|
|
26013
|
+
"string"
|
|
26014
|
+
],
|
|
25981
26015
|
"default": 0
|
|
25982
26016
|
},
|
|
26017
|
+
"size": {
|
|
26018
|
+
"description": "Toolbar scale, via sizing.css's universal [size=\"sm|md|lg\"] ambient tokens. Not table-toolbar-specific chrome — the same attribute that scales any component. Set it once on the toolbar instead of on each slotted scope/actions-leading/actions control individually; anything slotted with no [size] of its own inherits the ambient value. Default (empty) = md.",
|
|
26019
|
+
"type": "string",
|
|
26020
|
+
"enum": [
|
|
26021
|
+
"sm",
|
|
26022
|
+
"md",
|
|
26023
|
+
"lg"
|
|
26024
|
+
],
|
|
26025
|
+
"default": ""
|
|
26026
|
+
},
|
|
25983
26027
|
"stage": {
|
|
25984
26028
|
"description": "Explicit compaction-stage override (ADR-0076). Unset (the default, empty string) means auto-snap: the toolbar's own inline-size container query picks the nearest stage from its live width against the three --table-toolbar-bp-* breakpoints, snapping discretely, never interpolating. Setting stage explicitly pins that stage's rendering regardless of the container's actual width — overriding the container query, mirroring chart-ui's ratio override shape. Primary use: visual-eval fixtures that need a deterministic stage without resizing a real container, and a consumer embedding the toolbar inside a known-narrow panel who wants to skip auto-detection entirely. All four stages' CSS is now live (ADR-0076): search-tight steps the search field down (step 2); icon-only drops control labels and compacts/hides the range summary (steps 3+4); overflow collapses Filter/Sort/Columns into one \"More\" trigger routed through the same menu-ui/menu-item-ui pattern used elsewhere in the corpus (step 5). The resolved stage — the pin if set, otherwise the live width classification — also reflects onto the host as `data-stage-resolved` (step 7, informational only, mirrors chart-ui's `data-ratio-resolved` convention — never drives table-toolbar's own rendering, which stays 100% CSS-driven).",
|
|
25985
26029
|
"type": "string",
|
|
@@ -25997,6 +26041,11 @@
|
|
|
25997
26041
|
"type": "string",
|
|
25998
26042
|
"default": ""
|
|
25999
26043
|
},
|
|
26044
|
+
"titleAlways": {
|
|
26045
|
+
"description": "Opt out of the scope-reconciliation title hide: by default, a filled [slot=\"scope\"] region (a select/menu whose own displayed value already names the entity being listed) hides the separate [text]/[count] title cluster next to it, since together they'd say the same thing twice. Set [title-always] to keep the title cluster visible alongside a filled scope slot — e.g. a scope select PLUS an explicit text/count title on the same row (gh#1879).",
|
|
26046
|
+
"type": "boolean",
|
|
26047
|
+
"default": false
|
|
26048
|
+
},
|
|
26000
26049
|
"variant": {
|
|
26001
26050
|
"description": "Toolbar visual variant. `default` renders bare on parent surface; `card` adds the same chrome as a card-ui header.",
|
|
26002
26051
|
"type": "string",
|
|
@@ -26069,6 +26118,11 @@
|
|
|
26069
26118
|
"description": "The ratified adiav2 list-page toolbar row (ADIA2-9123, gh#1649): scope menu, range summary, app-owned Filter/Columns triggers (their own filter drawer, not the native popovers — [no-filter]/[no-columns]/ [no-sort]/[no-search] drop the native affordances), page-size select, and a primary CTA — [slot=\"actions-leading\"] carries the Filter/Columns triggers so they land BEFORE the page-size select, while [slot=\"actions\"] carries the CTA so it lands after.",
|
|
26070
26119
|
"a2ui": "[\n {\"id\": \"root\", \"component\": \"Column\", \"gap\": \"3\", \"children\": [\"bar\", \"card\"]},\n {\"id\": \"bar\", \"component\": \"TableToolbar\", \"for\": \"accounts\", \"no-filter\": true, \"no-sort\": true, \"no-columns\": true, \"no-search\": true, \"range-start\": 1, \"range-end\": 25, \"range-total\": 320, \"page-size\": 25, \"page-size-options\": [10, 25, 50, 100], \"children\": [\"btn-filter\", \"btn-columns\", \"btn-new\"]},\n {\"id\": \"btn-filter\", \"component\": \"Button\", \"text\": \"Filter\", \"variant\": \"ghost\", \"slot\": \"actions-leading\"},\n {\"id\": \"btn-columns\", \"component\": \"Button\", \"text\": \"Columns\", \"variant\": \"ghost\", \"slot\": \"actions-leading\"},\n {\"id\": \"btn-new\", \"component\": \"Button\", \"text\": \"New account\", \"variant\": \"primary\", \"slot\": \"actions\"},\n {\"id\": \"card\", \"component\": \"Card\", \"children\": [\"sec\"]},\n {\"id\": \"sec\", \"component\": \"Section\", \"bleed\": true, \"children\": [\"tbl\"]},\n {\"id\": \"tbl\", \"component\": \"Table\", \"id\": \"accounts\", \"sortable\": true, \"raw\": true}\n]",
|
|
26071
26120
|
"name": "adiav2-list-page-toolbar-row"
|
|
26121
|
+
},
|
|
26122
|
+
{
|
|
26123
|
+
"description": "gh#1877, ADR-0082 Amendment — cursor/hasMore server paging with no known row count yet: [range-total=\"?\"] renders \"Showing 1–50 subscriptions\" (via [range-noun]) with no \"of N\", resolving to the normal finite summary once a real total arrives.",
|
|
26124
|
+
"a2ui": "[\n {\"id\": \"root\", \"component\": \"Column\", \"gap\": \"3\", \"children\": [\"bar\", \"card\"]},\n {\"id\": \"bar\", \"component\": \"TableToolbar\", \"for\": \"subs\", \"range-start\": 1, \"range-end\": 50, \"range-total\": \"?\", \"range-noun\": \"subscriptions\"},\n {\"id\": \"card\", \"component\": \"Card\", \"children\": [\"sec\"]},\n {\"id\": \"sec\", \"component\": \"Section\", \"bleed\": true, \"children\": [\"tbl\"]},\n {\"id\": \"tbl\", \"component\": \"Table\", \"id\": \"subs\", \"raw\": true}\n]",
|
|
26125
|
+
"name": "table-toolbar-open-total"
|
|
26072
26126
|
}
|
|
26073
26127
|
],
|
|
26074
26128
|
"keywords": [
|
|
@@ -30229,7 +30283,7 @@
|
|
|
30229
30283
|
"$ref": "#/$defs/DynamicBoolean"
|
|
30230
30284
|
},
|
|
30231
30285
|
"prose": {
|
|
30232
|
-
"description": "
|
|
30286
|
+
"description": "DEPRECATED (removed at v1.0): content-optimized spacing/typography for long-form containers. Still works — maps onto the legacy [prose] HTML attribute (byte-identical to the ancestor scale=\"content-md\" tier) — but new generation should not emit this prop; no first-class scale-tier prop exists yet in this schema (gh#1822 follow-up).",
|
|
30233
30287
|
"$ref": "#/$defs/DynamicBoolean"
|
|
30234
30288
|
},
|
|
30235
30289
|
"size": {
|
|
@@ -708,6 +708,7 @@
|
|
|
708
708
|
- The toolbar's [count] badge and this footer's range label coexist by design — they answer different questions (the collection summary vs. the page-window position) and neither replaces the other. In a composition that includes a table-footer-ui, leave the toolbar's [range-start]/[range-end]/[range-total] unset — the range summary's home is the footer.
|
|
709
709
|
- Canonical composition: card-ui header (title + primary action) → a plain <section> wrapping a title-less <table-toolbar-ui for> → a <section bleed> wrapping the <table-ui id> → a <footer divider> wrapping this <table-footer-ui for> — see the table-in-card pattern.
|
|
710
710
|
- Use <span slot="empty"> (or any markup) inside a table-footer-ui that also sets [range-total="0"] to show a "no results" message in the range label's own position, without a layout shift versus the normal range text. Only fires on an EXPLICIT range-total="0" — omitting range-total entirely (loading) never shows it.
|
|
711
|
+
- gh#1877, ADR-0082 Amendment — cursor/hasMore server paging (no server-reported row count, only "more rows exist or not") sets [range-total="?"] instead of a real number: the range label reads "Showing 51–100" with no "of N", and the pager keeps `next` enabled until a later response supplies a real [range-total], at which point the footer renders the normal finite text automatically.
|
|
711
712
|
|
|
712
713
|
## TableToolbar
|
|
713
714
|
- Pair <table-toolbar-ui> with <table-ui> via [for="<table-id>"] (or rely on first-sibling fallback when both are inside the same parent). One toolbar per table. The doubled-chrome hazard is a TITLE COLLISION, not a blanket card-header ban (gh#1807, SPEC REQ-C-004): a toolbar carrying [text] must not share a card with a heading-slotted <card-ui> <header> — that pairing doubles the title row. A title-less toolbar (no [text]) inside a card whose <header> owns the title is the canonical table-in-card composition (see the table-in-card pattern) and is NOT doubled chrome — the toolbar renders only its count/search/filter/sort/ columns cluster, with the title living in the card header alone.
|
|
@@ -715,10 +716,11 @@
|
|
|
715
716
|
- Three legitimate placements: ABOVE the <card-ui> containing the table-ui; standing alone outside a card-ui parent with [variant="card"] (wraps the toolbar in card-style chrome); or INSIDE the card, in its own plain (non-bleed) <section> between the card header and the bleed table section — the canonical table-in-card shape (gh#1807) — title-less (no [text]) since the card header already owns the title (see the title-collision rule above).
|
|
716
717
|
- Use slot="action" (or [slot="actions"]) for trailing primary buttons (Invite, Export, +New). Use [text] / [count] props for the left cluster, or slotted [slot="title"] / [slot="count"] when content is markup (a <span> + <badge-ui>, etc.).
|
|
717
718
|
- Listen for toolbar events (`search`, `filter-change`, `sort-change`, `columns-change`, `page-size-change`) only to mirror state to URL / persistence / analytics. The toolbar already wires its changes into the bound table — you don't need to manually update the table.
|
|
718
|
-
- Use [slot="scope"] for a leading scope/view switcher (an org's teams, a saved view) rendered before [text]/[count]. Use [range-start] / [range-end] / [range-total] together for a "Showing X–Y of N" summary — it renders independently of, and alongside, [text]/[count], never in place of them. Use [page-size] + [page-size-options] for a rows-per-page select; it applies directly to the bound table's [paginate] prop the same way filter/sort/columns changes apply directly to the target.
|
|
719
|
+
- Use [slot="scope"] for a leading scope/view switcher (an org's teams, a saved view) rendered before [text]/[count]. By default, a filled scope slot hides the [text]/[count] title cluster (they'd otherwise repeat the same entity name) — set [title-always] to keep both visible together. Use [range-start] / [range-end] / [range-total] together for a "Showing X–Y of N" summary — it renders independently of, and alongside, [text]/[count], never in place of them. Use [page-size] + [page-size-options] for a rows-per-page select; it applies directly to the bound table's [paginate] prop the same way filter/sort/columns changes apply directly to the target.
|
|
719
720
|
- Use [slot="actions-leading"] (gh#1649) for app-owned trigger buttons that must land BEFORE the page-size select — e.g. custom Filter / Columns triggers that open the consumer's own drawer instead of the native filter/columns popovers (drop [no-filter] [no-columns] to hide the native ones when doing this). [slot="actions"] stays the trailing region AFTER the page-size select, for a primary CTA.
|
|
720
721
|
- Use [chrome-only] (ADR-0076, ADIA2-9123) instead of stacking [no-filter] [no-sort] [no-columns] [no-search] individually when an app owns ALL FOUR affordances itself (its own drawer/menu). Pure OR, absolute while set — clearing an individual no-* attribute does NOT re-enable that one control; drop [chrome-only] entirely and set the three you still want off explicitly instead. Combine with [slot="actions-leading"] for the app's own Filter/Columns triggers.
|
|
721
722
|
- Use <span slot="empty"> (or any markup) inside a table-toolbar-ui that also sets [range-total="0"] to show a "no results" message in the range-summary's own position, without a layout shift versus the normal "Showing X-Y of N" text (ADR-0076, REQ-E). Only fires on an EXPLICIT range-total="0" — omitting range-* entirely (loading) never shows it.
|
|
723
|
+
- gh#1877, ADR-0082 Amendment — cursor/hasMore server paging (no server-reported row count) sets [range-total="?"] instead of a real number: the summary reads "Showing 51–100" with no "of N" (needs only [range-start]/[range-end] > 0 to render), resolving to the normal finite summary once a real [range-total] arrives. Use [range-of] for the separate "filtered-of-whole" shape (a filtered range-total plus a secondary unfiltered-set total, e.g. "of 4 traversals (6 total)"), and [range-noun] to stop hand-painting the per-slice noun ("of 80 users"). Same three attributes, same names, on table-footer-ui (ADR-0082 §"same name on both companions").
|
|
722
724
|
- A plain <button-ui> slotted into [slot="actions-leading"] (e.g. the app-owned Filter/Columns triggers above) already collapses to icon-only at the SAME breakpoint as the native Filter/Sort/Columns buttons (gh#1748) — no consumer JS/CSS required. Read the host's [data-stage-resolved] attribute (ADR-0076 REQ-M-005, ADIA2-9123 S4-ii) only for compaction BEYOND plain icon-only reduction — e.g. hiding a slotted button entirely at the overflow stage, or reacting to the stage in a consumer's own drawer/menu — without re-deriving the same breakpoints yourself. Informational only — mirrors the CSS-decided compaction stage (full | search-tight | icon-only | overflow), never drives table-toolbar's own rendering. Reports the pinned [stage] value when set, the live width classification otherwise; updates on both a live resize and a [stage] attribute change.
|
|
723
725
|
|
|
724
726
|
## Table
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
"align": { "$ref": "#/$defs/DynamicString", "description": "Alignment role. Values depend on container (flex cross-axis for row-ui/col-ui, text for typography)." },
|
|
148
148
|
"justify": { "$ref": "#/$defs/DynamicString", "description": "Main-axis justification for flex/grid containers." },
|
|
149
149
|
"text": { "$ref": "#/$defs/DynamicString", "description": "Short text payload rendered via CSS attr(text) on ::after (Button, Badge, Tag, etc.)." },
|
|
150
|
-
"prose": { "$ref": "#/$defs/DynamicBoolean", "description": "
|
|
150
|
+
"prose": { "$ref": "#/$defs/DynamicBoolean", "description": "DEPRECATED (removed at v1.0): content-optimized spacing/typography for long-form containers. Still works — maps onto the legacy [prose] HTML attribute (byte-identical to the ancestor scale=\"content-md\" tier) — but new generation should not emit this prop; no first-class scale-tier prop exists yet in this schema (gh#1822 follow-up)." },
|
|
151
151
|
"icon": { "$ref": "#/$defs/DynamicString", "description": "Leading Phosphor icon name. Components that support leading icons render it; others ignore." },
|
|
152
152
|
"title": { "$ref": "#/$defs/DynamicString", "description": "Short heading/identifier. Used by EmptyState/Dialog/Modal etc. for heading slot shortcut." },
|
|
153
153
|
"label": { "$ref": "#/$defs/DynamicString", "description": "Accessible label or leading heading — aria-label or first-line text depending on component." },
|
package/catalog/tier-index.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"catalogId": "adia.tiers@0.8.
|
|
2
|
+
"catalogId": "adia.tiers@0.8.48",
|
|
3
3
|
"description": "Tier index of the AdiaUI catalog contract (ADR-0050). L0-L4 ladder; every tier-N entry is a declared composition of tier-(N-1) entries. L1 `composes` edges speak A2UI component TYPE names — the R-C7 catalogs’ `components` keys (genui-system SPEC R-C13). Derived — regenerate with `npm run build:catalog-tiers`, never hand-edit.",
|
|
4
4
|
"extends": {
|
|
5
5
|
"catalogId": "adia.base",
|
|
6
|
-
"version": "0.8.
|
|
6
|
+
"version": "0.8.48"
|
|
7
7
|
},
|
|
8
8
|
"tiers": {
|
|
9
9
|
"L0": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/a2ui",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.48",
|
|
4
4
|
"description": "The A2UI protocol — runtime (renderer, registry, streams, surface manifest, wiring primitives, dockable base classes) plus protocol-side validation. Framework-agnostic and dependency-free; pairs with any A2UI-conformant component set. Folded from @adia-ai/a2ui-runtime + the protocol half of @adia-ai/a2ui-validator (ADR-0048).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
package/prop-apply.js
CHANGED
|
@@ -19,6 +19,21 @@
|
|
|
19
19
|
* - A JSON-looking string ('[' / '{' prefixed) destined for a JS-property
|
|
20
20
|
* prop is parsed — corpus templates encode `data`/`columns` as JSON
|
|
21
21
|
* strings; components expect arrays.
|
|
22
|
+
* - gh#1858 — every JS-property prop except `textContent` is ALSO
|
|
23
|
+
* reflected as a JSON-string attribute alongside the property write
|
|
24
|
+
* (belt-and-suspenders, same idiom hand-authored HTML already uses for
|
|
25
|
+
* `pageSizeOptions='[…]'`/col-def's `meta='{…}'`). A consumer that
|
|
26
|
+
* re-serializes A2UI-rendered DOM via `outerHTML`/`innerHTML` (concretely:
|
|
27
|
+
* `preview-ui`'s connect-time code/render capture — packages/web-components/
|
|
28
|
+
* components/preview/preview.class.js) only ever sees attributes, never
|
|
29
|
+
* JS-property-only state; without this, that state silently vanishes on
|
|
30
|
+
* round-trip. A value JSON can't represent (a function like
|
|
31
|
+
* `itemRenderer`) never gets an attribute — `JSON.stringify` returns
|
|
32
|
+
* `undefined` for it, and the write is skipped rather than stamping the
|
|
33
|
+
* string `"undefined"`. This only reflects the attribute; whether a given
|
|
34
|
+
* component reads it back on reconnect is that component's own
|
|
35
|
+
* hydration contract (`data` and `pageSizeOptions` already do; `columns`/
|
|
36
|
+
* `options` do not yet — see gh#1858's own follow-on note).
|
|
22
37
|
* - boolean → attribute presence (`setAttribute(key, '')` / removeAttribute).
|
|
23
38
|
* - everything else → `setAttribute(toAttr(key), String(value))`.
|
|
24
39
|
*/
|
|
@@ -98,6 +113,15 @@ export function applyResolvedProp(el, key, value) {
|
|
|
98
113
|
try { v = JSON.parse(v); } catch { /* keep as string on parse failure */ }
|
|
99
114
|
}
|
|
100
115
|
el[key] = v;
|
|
116
|
+
// gh#1858 — reflect alongside the property write so a DOM
|
|
117
|
+
// re-serialization (outerHTML/innerHTML) doesn't lose this state.
|
|
118
|
+
// `textContent` has no attribute counterpart to reflect to (the
|
|
119
|
+
// container case is redirected to `text=` above and returns early;
|
|
120
|
+
// this branch only reaches here for a real text-bearing tag).
|
|
121
|
+
if (key !== 'textContent') {
|
|
122
|
+
const json = JSON.stringify(v);
|
|
123
|
+
if (json !== undefined) el.setAttribute(toAttr(key), json);
|
|
124
|
+
}
|
|
101
125
|
return;
|
|
102
126
|
}
|
|
103
127
|
|
package/renderer.js
CHANGED
|
@@ -261,7 +261,14 @@ export class A2UIRenderer {
|
|
|
261
261
|
if (!el) {
|
|
262
262
|
el = document.createElement('div');
|
|
263
263
|
el.setAttribute('data-a2ui-id', comp.id);
|
|
264
|
-
|
|
264
|
+
// gh#1859: `data-a2ui-id` always carries the graph node id (the
|
|
265
|
+
// internal lookup key every consumer above already uses); the
|
|
266
|
+
// native `.id` attribute only mirrors it when `comp.id` is a
|
|
267
|
+
// real AUTHORED id — a producer-marked `comp.syntheticId` (e.g.
|
|
268
|
+
// docs-transpiler's own unrecovered `<base>-i<k>-root` graph id)
|
|
269
|
+
// skips the native stamp so it never leaks into re-serialized
|
|
270
|
+
// DOM (docs demos' Copy/code text).
|
|
271
|
+
if (!comp.syntheticId) el.id = comp.id;
|
|
265
272
|
el.setAttribute('data-a2ui-unknown', comp.component);
|
|
266
273
|
el.textContent = `[unknown: ${comp.component}]`;
|
|
267
274
|
surface.elements.set(comp.id, el);
|
|
@@ -273,13 +280,13 @@ export class A2UIRenderer {
|
|
|
273
280
|
if (!el) {
|
|
274
281
|
el = document.createElement(tagName);
|
|
275
282
|
el.setAttribute('data-a2ui-id', comp.id);
|
|
276
|
-
el.id = comp.id;
|
|
283
|
+
if (!comp.syntheticId) el.id = comp.id; // gh#1859 — see comment above
|
|
277
284
|
surface.elements.set(comp.id, el);
|
|
278
285
|
this.#elements.set(comp.id, el);
|
|
279
286
|
} else if (el.localName !== tagName) {
|
|
280
287
|
const newEl = document.createElement(tagName);
|
|
281
288
|
newEl.setAttribute('data-a2ui-id', comp.id);
|
|
282
|
-
newEl.id = comp.id;
|
|
289
|
+
if (!comp.syntheticId) newEl.id = comp.id; // gh#1859 — see comment above
|
|
283
290
|
el.replaceWith(newEl);
|
|
284
291
|
el = newEl;
|
|
285
292
|
surface.elements.set(comp.id, el);
|
|
@@ -443,7 +450,9 @@ export class A2UIRenderer {
|
|
|
443
450
|
}
|
|
444
451
|
|
|
445
452
|
#applyProps(el, comp) {
|
|
446
|
-
|
|
453
|
+
// syntheticId (gh#1859): a producer-only marker consumed above to decide
|
|
454
|
+
// whether the native `.id` gets stamped — never a real prop to reflect.
|
|
455
|
+
const skip = new Set(['id', 'component', 'children', 'child', '_surfaceId', 'syntheticId']);
|
|
447
456
|
// Skip variant attr when the tag IS the variant (h1 for h1, p for body, small for caption)
|
|
448
457
|
if (comp.component === 'Text' && comp.variant && !comp.slot && A2UIRenderer.#TEXT_TAG_MAP[comp.variant]) {
|
|
449
458
|
skip.add('variant');
|