@adia-ai/web-components 0.8.48 → 0.8.50

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 (51) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/MIGRATION.md +136 -0
  3. package/components/chart/chart.class.js +41 -10
  4. package/components/input/input.a2ui.json +2 -2
  5. package/components/input/input.css +45 -16
  6. package/components/input/input.yaml +11 -9
  7. package/components/search/search.a2ui.json +1 -1
  8. package/components/search/search.class.js +43 -0
  9. package/components/search/search.css +28 -0
  10. package/components/search/search.yaml +4 -1
  11. package/components/select/select.class.js +4 -1
  12. package/components/select/select.css +13 -0
  13. package/components/stat/stat.a2ui.json +30 -3
  14. package/components/stat/stat.css +88 -0
  15. package/components/stat/stat.d.ts +2 -2
  16. package/components/stat/stat.yaml +99 -6
  17. package/components/table/table.a2ui.json +1 -1
  18. package/components/table/table.class.js +9 -9
  19. package/components/table/table.d.ts +1 -1
  20. package/components/table/table.yaml +7 -7
  21. package/components/table-footer/table-footer.class.js +6 -6
  22. package/components/table-toolbar/table-toolbar.a2ui.json +3 -0
  23. package/components/table-toolbar/table-toolbar.class.js +28 -6
  24. package/components/table-toolbar/table-toolbar.css +30 -0
  25. package/components/table-toolbar/table-toolbar.yaml +57 -17
  26. package/components/theme-provider/theme-provider.a2ui.json +3 -5
  27. package/components/theme-provider/theme-provider.class.js +9 -30
  28. package/components/theme-provider/theme-provider.d.ts +3 -5
  29. package/components/theme-provider/theme-provider.yaml +4 -8
  30. package/core/element.js +16 -3
  31. package/custom-elements.json +10 -6
  32. package/dist/theme-provider.min.js +3 -3
  33. package/dist/web-components.min.css +1 -1
  34. package/dist/web-components.min.js +58 -57
  35. package/dist/web-components.sheet.js +1 -1
  36. package/index.css +1 -2
  37. package/package.json +1 -1
  38. package/styles/README.md +1 -1
  39. package/styles/api/sizing.css +1 -1
  40. package/styles/host.css +1 -4
  41. package/styles/index.css +2 -2
  42. package/styles/scale.css +19 -26
  43. package/styles/themes.css +2 -2
  44. package/styles/type/roles.css +3 -3
  45. package/styles/typography.css +6 -5
  46. package/dist/prose.min.css +0 -1
  47. package/dist/prose.sheet.js +0 -11
  48. package/dist/verse.min.css +0 -1
  49. package/dist/verse.sheet.js +0 -11
  50. package/styles/prose.css +0 -211
  51. package/styles/verse.css +0 -151
@@ -19,6 +19,19 @@
19
19
 
20
20
  /* ── Chart-band mode ── */
21
21
  --stat-chart-band-height: 3rem;
22
+
23
+ /* ── Tile mode (band + bleed, ADR-0083) — change-chip overlay ──
24
+ Stat-scoped aliases falling back to the exact rung chart.css
25
+ resolves the corresponding --chart-chip-* token to, so an
26
+ ancestor override re-themes both chart axis chips and this delta
27
+ chip in one stroke (ADR-0083 decision 3). */
28
+ --stat-change-inset: var(--a-space-2);
29
+ --stat-change-chip-bg: var(--chart-chip-bg, var(--md-sys-color-neutral-container-high));
30
+ --stat-change-chip-fg: var(--chart-chip-fg, var(--md-sys-color-neutral-on-surface));
31
+ --stat-change-chip-radius: var(--chart-chip-radius, var(--a-radius-sm));
32
+ --stat-change-chip-pad-x: var(--chart-chip-pad-x, var(--a-space-1-5));
33
+ --stat-change-chip-pad-y: var(--chart-chip-pad-y, var(--a-space-1));
34
+ --stat-change-chip-font-size: var(--chart-chip-font-size, var(--a-ui-tiny));
22
35
  }
23
36
 
24
37
  :scope {
@@ -166,6 +179,81 @@
166
179
  aspect-ratio: auto;
167
180
  }
168
181
 
182
+ /* ── Tile layout: band + bleed together (ADR-0083) ──
183
+ The chart-band from `[band]` above, generalized to bleed three edges
184
+ (inline-start, inline-end, block-end) the way `[bleed]` already
185
+ bleeds horizontally, with the `change` delta overlaid as a chip
186
+ inside the band instead of stacking below it. Declared AFTER the
187
+ shipped `[band]` block above so it wins the equal-specificity tie
188
+ the undefined `band bleed` combination used to lose — `[band]`'s
189
+ grid-template-areas is replaced wholesale (the `change` row is
190
+ removed; the chip lives in the chart grid area instead). Host
191
+ precondition (author-controlled, undetected — the same posture
192
+ gh#1801 ratified for `section[bleed]`): this stat-ui must be the
193
+ last content before the card's block-end edge, no trailing
194
+ footer/section — a following region would collide with the
195
+ negative block-end margin below. Compose inside a card section:
196
+ <stat-ui band bleed value=… label=… change=… trend=…>
197
+ <chart-ui slot="chart" type="area" …></chart-ui>
198
+ </stat-ui> */
199
+ :scope[band][bleed]:has([slot="chart"]) {
200
+ grid-template-columns: minmax(0, 1fr) auto;
201
+ grid-template-areas:
202
+ "label icon"
203
+ "value value"
204
+ "chart chart";
205
+ align-items: baseline;
206
+ }
207
+ :scope[band][bleed] [slot="chart"] {
208
+ grid-area: chart;
209
+ align-self: stretch;
210
+ height: var(--stat-chart-band-height);
211
+ aspect-ratio: auto;
212
+ /* Three-edge bleed via the --card-inset negative-extent technique
213
+ [bleed] already uses horizontally (see the horizontal-bleed block
214
+ above for the width/margin-block rationale — chart-ui pins its own
215
+ width:100%, so the box is explicitly sized rather than stretched). */
216
+ margin-inline: calc(-1 * var(--card-inset, 0px));
217
+ margin-block-end: calc(-1 * var(--card-inset, 0px));
218
+ width: calc(100% + 2 * var(--card-inset, 0px));
219
+ min-width: 0;
220
+ }
221
+ /* Change chip: overlaid in the chart's own grid area, above it in
222
+ paint order, anchored block-start / inline-end of the band (logical
223
+ properties — RTL mirrors for free). Corner clearance from the
224
+ card's rounded corners is structural: the band's rounded corners are
225
+ block-end only (its block-start edge is mid-card, square), so a
226
+ block-start-anchored chip can never meet one. pointer-events: none —
227
+ it's text, not a control, so it never blocks the chart's own
228
+ hover/tooltip hit-testing beneath it. */
229
+ :scope[band][bleed] [slot="change"] {
230
+ grid-area: chart;
231
+ justify-self: end;
232
+ align-self: start;
233
+ z-index: 1;
234
+ inset-block-start: var(--stat-change-inset);
235
+ inset-inline-end: var(--stat-change-inset);
236
+ position: relative;
237
+ pointer-events: none;
238
+ background: var(--stat-change-chip-bg);
239
+ border-radius: var(--stat-change-chip-radius);
240
+ padding: var(--stat-change-chip-pad-y) var(--stat-change-chip-pad-x);
241
+ font-size: var(--stat-change-chip-font-size);
242
+ }
243
+ /* Chip text fallback — deliberately LOW specificity via :where() (0,1,0,
244
+ same as the generic [slot="change"] rule below it) so the
245
+ [trend="up"/"down"] rules further down this file (0,2,0 — they win
246
+ regardless of source order) keep painting the chip's text + arrow
247
+ whenever a canonical trend is set. --stat-change-chip-fg applies only
248
+ when no trend is set — the fallback, not an override. Splitting this
249
+ one declaration out of the block above (which stays 0,3,0 for its
250
+ geometry/surface properties, none of which trend re-paints) is the
251
+ only way to give color a lower specificity than the rest of that
252
+ block while keeping this selector, not a shape-widening rewrite. */
253
+ :where(:scope[band][bleed]) [slot="change"] {
254
+ color: var(--stat-change-chip-fg);
255
+ }
256
+
169
257
  /* ── Label (eyebrow) ── */
170
258
  [slot="label"] {
171
259
  grid-area: label;
@@ -13,9 +13,9 @@
13
13
  import { UIElement } from '../../core/element.js';
14
14
 
15
15
  export class UIStat extends UIElement {
16
- /** Chart-band KPI layout. With a `slot="chart"` child, label / value / change stack full width top to bottom and the chart forms a short full-width band between the value and the change row (the Gmail/Vercel/shadcn stat-card shape). Composes with `icon` exactly as the base layout does. No effect without a chart slot. */
16
+ /** Chart-band KPI layout. With a `slot="chart"` child, label / value / change stack full width top to bottom and the chart forms a short full-width band between the value and the change row (the Gmail/Vercel/shadcn stat-card shape). Composes with `icon` exactly as the base layout does. Set together with `bleed` (`band bleed`) for the KPI tile: the band bleeds to the card's inline-start, inline-end, and block-end edges, and the `change` delta overlays it as a chip instead of stacking below it (ADR-0083) — the tile's `stat-ui` must be the card's last content (no trailing footer/section). No effect without a chart slot. */
17
17
  band: boolean;
18
- /** Horizontal-bleed KPI layout. With a `slot="chart"` child, the value / label / change stack on the left while the chart fills the right column at full height and bleeds to the card's top / right / bottom edges (the horizontal counterpart to a chart in a card `<section bleed>`). No effect without a chart slot. */
18
+ /** The chart reaches the card's edges — which edges depends on `band`: a column bleeding top/right/bottom without it, a three-edge (inline-start, inline-end, block-end) bottom band with it. With `slot="chart"` alone (no `band`), the value / label / change stack on the left while the chart fills the right column at full height and bleeds to the card's top / right / bottom edges (the horizontal counterpart to a chart in a card `<section bleed>`). Set together with `band` for the full-bleed KPI tile — see `band`. No effect without a chart slot. */
19
19
  bleed: boolean;
20
20
  /** Change indicator text (e.g. '+12%', '-3%') */
21
21
  change: string;
@@ -51,11 +51,14 @@ props:
51
51
  default: ""
52
52
  bleed:
53
53
  description: >-
54
- Horizontal-bleed KPI layout. With a `slot="chart"` child, the value /
55
- label / change stack on the left while the chart fills the right column
56
- at full height and bleeds to the card's top / right / bottom edges (the
57
- horizontal counterpart to a chart in a card `<section bleed>`). No effect
58
- without a chart slot.
54
+ The chart reaches the card's edges which edges depends on `band`: a
55
+ column bleeding top/right/bottom without it, a three-edge (inline-start,
56
+ inline-end, block-end) bottom band with it. With `slot="chart"` alone
57
+ (no `band`), the value / label / change stack on the left while the
58
+ chart fills the right column at full height and bleeds to the card's
59
+ top / right / bottom edges (the horizontal counterpart to a chart in a
60
+ card `<section bleed>`). Set together with `band` for the full-bleed KPI
61
+ tile — see `band`. No effect without a chart slot.
59
62
  type: boolean
60
63
  default: false
61
64
  band:
@@ -64,7 +67,12 @@ props:
64
67
  change stack full width top to bottom and the chart forms a short
65
68
  full-width band between the value and the change row (the
66
69
  Gmail/Vercel/shadcn stat-card shape). Composes with `icon` exactly as
67
- the base layout does. No effect without a chart slot.
70
+ the base layout does. Set together with `bleed` (`band bleed`) for the
71
+ KPI tile: the band bleeds to the card's inline-start, inline-end, and
72
+ block-end edges, and the `change` delta overlays it as a chip instead
73
+ of stacking below it (ADR-0083) — the tile's `stat-ui` must be the
74
+ card's last content (no trailing footer/section). No effect without a
75
+ chart slot.
68
76
  type: boolean
69
77
  default: false
70
78
  events: {}
@@ -112,6 +120,37 @@ tokens:
112
120
  description: Font size for the primary value
113
121
  --stat-value-weight:
114
122
  description: Font weight for the primary value
123
+ --stat-change-inset:
124
+ description: >-
125
+ Clearance of the `band bleed` tile's change chip from the chart band's
126
+ edges. Aliases the same ladder rung as `chart-ui`'s
127
+ `--chart-chrome-inset`.
128
+ --stat-change-chip-bg:
129
+ description: >-
130
+ Background of the `band bleed` tile's change chip. Falls back to
131
+ `chart-ui`'s `--chart-chip-bg`, so an ancestor override re-themes chart
132
+ axis chips and this delta chip together.
133
+ --stat-change-chip-fg:
134
+ description: >-
135
+ Text color of the `band bleed` tile's change chip when no canonical
136
+ `trend` is set (a set `trend` keeps painting via `--stat-up-fg` /
137
+ `--stat-down-fg`). Falls back to `chart-ui`'s `--chart-chip-fg`.
138
+ --stat-change-chip-radius:
139
+ description: >-
140
+ Corner radius of the `band bleed` tile's change chip. Falls back to
141
+ `chart-ui`'s `--chart-chip-radius`.
142
+ --stat-change-chip-pad-x:
143
+ description: >-
144
+ Inline padding of the `band bleed` tile's change chip. Falls back to
145
+ `chart-ui`'s `--chart-chip-pad-x`.
146
+ --stat-change-chip-pad-y:
147
+ description: >-
148
+ Block padding of the `band bleed` tile's change chip. Falls back to
149
+ `chart-ui`'s `--chart-chip-pad-y`.
150
+ --stat-change-chip-font-size:
151
+ description: >-
152
+ Font size of the `band bleed` tile's change chip. Falls back to
153
+ `chart-ui`'s `--chart-chip-font-size`.
115
154
  requiredIcons:
116
155
  - arrow-up
117
156
  - arrow-down
@@ -123,6 +162,8 @@ a2ui:
123
162
  reason: 'Different visual + semantic role.'
124
163
  - rule: 'Delta indicator uses positive/negative semantic tokens; pass change= attribute with sign.'
125
164
  reason: 'Built-in trend coloring; no manual styling needed.'
165
+ - rule: 'For the KPI tile — big value over a full-bleed sparkline/area band with the delta as a chip — set `band bleed` together on the stat-ui; the tile must be the card''s last content.'
166
+ reason: 'band bleed composes the shipped chart-band and horizontal-bleed layouts into the full-bleed tile with the change delta overlaid inside the band as a chip (ADR-0083); a trailing footer/section after it collides with the band''s negative block-end margin.'
126
167
  anti_patterns: []
127
168
  examples:
128
169
  - name: basic-stat
@@ -150,6 +191,57 @@ examples:
150
191
  "value": ""
151
192
  }
152
193
  ]
194
+ - name: tile
195
+ description: KPI tile — band bleed with an overlaid delta chip (ADR-0083)
196
+ a2ui: >-
197
+ [
198
+ {
199
+ "id": "root",
200
+ "component": "Card",
201
+ "children": [
202
+ "sec"
203
+ ]
204
+ },
205
+ {
206
+ "id": "sec",
207
+ "component": "Section",
208
+ "children": [
209
+ "comp"
210
+ ]
211
+ },
212
+ {
213
+ "id": "comp",
214
+ "component": "Stat",
215
+ "band": true,
216
+ "bleed": true,
217
+ "label": "Sessions",
218
+ "value": "84,232",
219
+ "change": "+57%",
220
+ "trend": "up",
221
+ "children": [
222
+ "chart"
223
+ ]
224
+ },
225
+ {
226
+ "id": "chart",
227
+ "component": "Chart",
228
+ "slot": "chart",
229
+ "type": "area",
230
+ "x": "d",
231
+ "y": "v",
232
+ "noGrid": true,
233
+ "noValues": true,
234
+ "data": [
235
+ { "d": "Mon", "v": 12 },
236
+ { "d": "Tue", "v": 18 },
237
+ { "d": "Wed", "v": 15 },
238
+ { "d": "Thu", "v": 27 },
239
+ { "d": "Fri", "v": 22 },
240
+ { "d": "Sat", "v": 31 },
241
+ { "d": "Sun", "v": 29 }
242
+ ]
243
+ }
244
+ ]
153
245
  keywords:
154
246
  - stat
155
247
  - stats
@@ -169,6 +261,7 @@ keywords:
169
261
  - observability
170
262
  - performance
171
263
  - scoreboard
264
+ - tile
172
265
  synonyms:
173
266
  analytics:
174
267
  - chart
@@ -60,7 +60,7 @@
60
60
  "default": 0
61
61
  },
62
62
  "range-total": {
63
- "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.",
63
+ "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: this prop is Number-typed and reflected; \"?\" parses to NaN (`+\"?\"`), and the coerced numeric PROPERTY is what carries the open/unproven signal (non-finite) — every reader checks the property, never `getAttribute(...) === '?'`. The DOM attribute itself keeps the authored \"?\" (gh#1895 the framework's attribute-reflection leaves a non-finite Number value unwritten rather than stringifying it back as the literal \"NaN\").",
64
64
  "type": [
65
65
  "number",
66
66
  "string"
@@ -339,15 +339,15 @@ export class UITable extends UIElement {
339
339
  // gh#1877/ADR-0082 Amendment — the open/unproven-total state. NOT keyed
340
340
  // on the raw attribute string: `rangeTotal` is a Number-typed reflected
341
341
  // prop (installProps in core/element.js), so setting range-total="?" is
342
- // parsed via `+"?"` → NaN and immediately reflected straight back onto
343
- // the DOM attribute as the literal string "NaN" (reflect() in
344
- // element.js stringifies any non-null coerced value) — by the time
345
- // #draw()/render() runs, `getAttribute('range-total')` already reads
346
- // "NaN", never the authored "?". The coerced PROPERTY still holds NaN
347
- // stably, though (Object.is(NaN, NaN) is true, so the reflect's own
348
- // re-parse never re-writes it) — that's the one signal that survives,
349
- // and it's what #pageCount/showPagination below need to tell "no total
350
- // yet" apart from "confirmed empty" (range-total="0", a finite 0).
342
+ // parsed via `+"?"` → NaN. gh#1895 reflect() (core/element.js) leaves
343
+ // a non-finite Number value unwritten instead of stringifying it back
344
+ // onto the attribute, so by the time #draw()/render() runs,
345
+ // `getAttribute('range-total')` still reads the authored "?". The
346
+ // coerced PROPERTY still holds NaN stably, though (Object.is(NaN, NaN)
347
+ // is true, so re-parsing the unchanged attribute never re-triggers a
348
+ // write either) — that's what #pageCount/showPagination below need to
349
+ // tell "no total yet" apart from "confirmed empty" (range-total="0", a
350
+ // finite 0).
351
351
  get #rangeOpen() { return this.#serverMode && !Number.isFinite(Number(this.rangeTotal)); }
352
352
 
353
353
  // gh#1754/ADR-0082 REQ-D-003 (CodeRabbit, PR #1828) — a raw
@@ -104,7 +104,7 @@ export class UITable extends UIElement {
104
104
  noPager: boolean;
105
105
  /** Rows per page. 0 = show all rows without pagination. When > 0, renders the internal pagination bar below the table — UNLESS [no-pager] is also set (gh#1807, ADR-0080), which keeps [paginate]'s slicing/page-state/`page`-event/`footer-page`-listener behavior but suppresses only the bar itself (the anti-doubled-pager path for a table-footer-ui composition). When [range-total] is also set (gh#1754, ADR-0082), [paginate] becomes purely presentational — it supplies only the page-size for pager math; no local slicing runs. */
106
106
  paginate: number;
107
- /** 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. */
107
+ /** 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: this prop is Number-typed and reflected; "?" parses to NaN (`+"?"`), and the coerced numeric PROPERTY is what carries the open/unproven signal (non-finite) — every reader checks the property, never `getAttribute(...) === '?'`. The DOM attribute itself keeps the authored "?" (gh#1895 the framework's attribute-reflection leaves a non-finite Number value unwritten rather than stringifying it back as the literal "NaN"). */
108
108
  rangeTotal: number | string;
109
109
  /** Visual-only passthrough — applies `<table-ui>`'s chrome reset (background / border / border-radius all transparent) AND short-circuits the data lifecycle entirely. The consumer owns the body shape: no header injection, no row reconciliation from `.data`, no empty-state / loading overlays, no aggregation or pagination footers. Use raw when embedding `<table-ui>` inside surfaces that supply their own chrome (e.g. `<card-ui><section bleed>`), or when wrapping a consumer-authored native `<table>` for design-token styling without the framework's data semantics. Note: a `<table-ui>` placed directly inside `<card-ui><section bleed>` drops the table's visual chrome automatically (card.css, gh#796) even without `raw` — `raw` is still required to also short-circuit the data lifecycle. Pre-v0.6.33 (FB-53 §2) `raw` was visual-only and the data lifecycle still ran — wrapping a `.data`-unset native table produced a phantom "No data" overlay. v0.6.33+ matches the documented contract. */
110
110
  raw: boolean;
@@ -107,13 +107,13 @@ props:
107
107
  way a confirmed `range-total="0"` would. Resolves to the normal
108
108
  finite server-mode behavior the instant a real number replaces the
109
109
  "?" — every reader downstream (table-toolbar-ui, table-footer-ui)
110
- shares this same distinction. Note: since this prop is Number-typed
111
- and reflected, the framework's own attribute-reflection immediately
112
- rewrites the DOM attribute to the literal string "NaN" once "?" is
113
- parsed (`+"?"` NaN, then stringified back onto the attribute) —
114
- the coerced numeric PROPERTY is what carries the open/unproven
115
- signal (non-finite), never a literal "?" surviving as the live DOM
116
- attribute value.
110
+ shares this same distinction. Note: this prop is Number-typed and
111
+ reflected; "?" parses to NaN (`+"?"`), and the coerced numeric
112
+ PROPERTY is what carries the open/unproven signal (non-finite)
113
+ every reader checks the property, never `getAttribute(...) === '?'`.
114
+ The DOM attribute itself keeps the authored "?" (gh#1895 — the
115
+ framework's attribute-reflection leaves a non-finite Number value
116
+ unwritten rather than stringifying it back as the literal "NaN").
117
117
  type: [number, string]
118
118
  default: 0
119
119
  reflect: true
@@ -340,13 +340,13 @@ export class UITableFooter extends UIElement {
340
340
  // resolved source of range-total (local attribute if set, else the
341
341
  // server-mode target's own) coerces to a non-finite number. NOT keyed
342
342
  // on the raw attribute string — rangeTotal is a Number-typed reflected
343
- // prop, so range-total="?" parses via `+"?"` → NaN and the property
344
- // setter's own reflect() immediately rewrites the DOM attribute to the
345
- // literal string "NaN" (core/element.js) before this ever runs;
346
- // getAttribute('range-total') would read "NaN", never "?". The
343
+ // prop, so range-total="?" parses via `+"?"` → NaN. gh#1895 reflect()
344
+ // (core/element.js) now leaves a non-finite Number value unwritten
345
+ // instead of stomping the sentinel with the literal string "NaN", so
346
+ // getAttribute('range-total') keeps reading "?" as authored. The
347
347
  // coerced PROPERTY still holds NaN stably (Object.is(NaN, NaN) is
348
- // true, so the reflect's re-parse never re-writes it) — that's the
349
- // one signal that survives, and what tells "unknown yet" apart from
348
+ // true, so re-parsing the unchanged attribute never re-triggers a
349
+ // write either) that's what tells "unknown yet" apart from
350
350
  // "confirmed zero" (a finite 0) once safeRangeTotal has clamped both
351
351
  // to 0.
352
352
  const rangeOpen = hasRangeTotal
@@ -239,6 +239,9 @@
239
239
  },
240
240
  "scope": {
241
241
  "description": "Leading region rendered BEFORE the [text]/[count] title cluster — e.g. a scope/view-switcher menu (an org's teams, a saved view). Positioning is CSS by DOM order, mirroring how [slot=\"actions\"] is a real, author-fillable insertion point rather than template-stamped content."
242
+ },
243
+ "summary": {
244
+ "description": "gh#1883 — trailing, text-tier cluster for an aggregate datum that belongs with the table but has no home in the range summary or the actions area (e.g. batch-detail's line-items Subtotal: `<span slot=\"summary\">Subtotal <strong>$1,240.00</strong></span>`). Rendered in the same flex row, after [slot=\"actions-leading\"] and before the page-size select / [slot=\"actions\"] — closer to the trailing edge than [slot=\"scope\"]/[range-*], since it reads as a result/total rather than pagination or view-switching context. Text sized via [--table-toolbar-summary-size] (shared with the \"Showing X–Y of N\" range summary — both are the same text tier). Plain author-supplied markup, same real-insertion-point pattern as every other table-toolbar slot — no forced typography beyond the font-size token. Stays visible at every ADR-0076 compaction stage: it is DATA (an aggregate figure the consumer chose to show), not chrome, so it is never dropped or compacted the way the range summary/ controls are — a consumer needing narrower-container behavior owns that via its own slotted content, not a component-side compaction rule. Empty (the default) has zero footprint on existing consumers."
242
245
  }
243
246
  },
244
247
  "states": [
@@ -683,6 +683,26 @@ export class UITableToolbar extends UIElement {
683
683
  }
684
684
  }
685
685
 
686
+ // Summary slot passthrough (gh#1883) — a real insertion point (ADR-0067),
687
+ // same pattern as [slot="scope"]/[slot="actions-leading"]/[slot="actions"]
688
+ // above, for a trailing aggregate/total datum (e.g. line-items Subtotal)
689
+ // that belongs with the table but isn't pagination context. Rendered
690
+ // AFTER [slot="actions-leading"] and BEFORE the page-size select /
691
+ // [slot="actions"] — closer to the trailing edge than the range summary,
692
+ // controls, or search, since it reads as a result rather than view
693
+ // chrome. Deliberately NOT named [data-summary] — that marker already
694
+ // belongs to the "Showing X–Y of N" range-summary cluster stamped above;
695
+ // reusing it here would collide with every existing #updateSummary()/CSS
696
+ // selector keyed on that attribute.
697
+ const summarySlot = document.createElement('div');
698
+ summarySlot.setAttribute('data-summary-slot', '');
699
+ for (const node of [...this.children]) {
700
+ if (node === root) continue;
701
+ if (node.getAttribute?.('slot') === 'summary') {
702
+ summarySlot.appendChild(node);
703
+ }
704
+ }
705
+
686
706
  // Actions slot passthrough — we move any pre-existing [slot="actions"] children here
687
707
  const actionsSlot = document.createElement('div');
688
708
  actionsSlot.setAttribute('data-actions', '');
@@ -699,6 +719,7 @@ export class UITableToolbar extends UIElement {
699
719
  root.appendChild(controls);
700
720
  root.appendChild(search);
701
721
  root.appendChild(actionsLeadingSlot);
722
+ root.appendChild(summarySlot);
702
723
  root.appendChild(pageSize);
703
724
  root.appendChild(actionsSlot);
704
725
 
@@ -865,12 +886,13 @@ export class UITableToolbar extends UIElement {
865
886
  // gh#1877/ADR-0082 Amendment — same open/unproven-total discipline as
866
887
  // table-footer-ui's own #draw(): NOT keyed on the raw attribute
867
888
  // string. rangeTotal is a Number-typed reflected prop, so
868
- // range-total="?" parses via `+"?"` → NaN and the property setter's
869
- // own reflect() immediately rewrites the DOM attribute to the literal
870
- // string "NaN" (core/element.js) — getAttribute('range-total') would
871
- // read "NaN", never "?". The coerced PROPERTY still holds NaN stably
872
- // (Object.is(NaN, NaN) is true, so the reflect's re-parse never
873
- // re-writes it), and that's what tells "unknown yet" apart from a
889
+ // range-total="?" parses via `+"?"` → NaN. gh#1895 reflect()
890
+ // (core/element.js) leaves a non-finite Number value unwritten instead
891
+ // of stomping the sentinel with the literal string "NaN", so
892
+ // getAttribute('range-total') keeps reading "?" as authored. The
893
+ // coerced PROPERTY still holds NaN stably (Object.is(NaN, NaN) is
894
+ // true, so re-parsing the unchanged attribute never re-triggers a
895
+ // write either), and that's what tells "unknown yet" apart from a
874
896
  // confirmed range-total="0" (a finite 0).
875
897
  const rangeOpen = !Number.isFinite(Number(this.rangeTotal));
876
898
  // gh#1877/ADR-0082 Amendment — additive suffixes onto the normal
@@ -526,6 +526,36 @@
526
526
 
527
527
  [data-actions-leading]:empty { display: none; }
528
528
 
529
+ /* ═══════ Summary slot ═══════ (gh#1883 — trailing aggregate/total datum,
530
+ e.g. line-items Subtotal, rendered AFTER [slot="actions-leading"] and
531
+ BEFORE the page-size select / [slot="actions"]. Text-tier sized via
532
+ --table-toolbar-summary-size — the SAME token the range summary
533
+ ([data-summary-text]/[data-summary-compact]) already uses, since both
534
+ read as the same "small figure next to the toolbar chrome" tier;
535
+ color is NOT forced (unlike the range summary, which paints its own
536
+ generated text) — this slot holds plain author-supplied markup that
537
+ may want its own emphasis, e.g. a bold `<strong>` total.
538
+ Deliberately carries no margin-inline-start:auto of its own: it sits
539
+ between [data-actions-leading] and [data-page-size], both of which
540
+ already push the trailing group to the far edge, so a third auto
541
+ margin here would only ever be a no-op (the first unclaimed-space
542
+ sibling already claims it) — see [data-page-size]'s own comment below.
543
+ No compaction-stage hide/compact rule at any breakpoint or [stage]
544
+ pin, unlike [data-summary] above — this is DATA the consumer chose to
545
+ show, not chrome; a consumer needing narrower-container behavior owns
546
+ that via its own slotted content. */
547
+
548
+ [data-summary-slot] {
549
+ display: inline-flex;
550
+ align-items: center;
551
+ gap: var(--table-toolbar-cluster-gap);
552
+ flex: 0 0 auto;
553
+ font-size: var(--table-toolbar-summary-size);
554
+ line-height: var(--a-font-leading-snug);
555
+ }
556
+
557
+ [data-summary-slot]:empty { display: none; }
558
+
529
559
  /* ═══════ Page-size select ═══════ (gh#1615)
530
560
  Carries the SAME margin-inline-start:auto as [data-search] so the
531
561
  trailing group (controls/search/page-size/actions) still pushes to the
@@ -317,6 +317,26 @@ slots:
317
317
  stamp. Empty (the default) has zero footprint on existing consumers.
318
318
  actions:
319
319
  description: Trailing action area — primary buttons (e.g. "New row") rendered after the search input, [slot="actions-leading"], and the page-size select.
320
+ summary:
321
+ description: >-
322
+ gh#1883 — trailing, text-tier cluster for an aggregate datum that
323
+ belongs with the table but has no home in the range summary or the
324
+ actions area (e.g. batch-detail's line-items Subtotal:
325
+ `<span slot="summary">Subtotal <strong>$1,240.00</strong></span>`).
326
+ Rendered in the same flex row, after [slot="actions-leading"] and
327
+ before the page-size select / [slot="actions"] — closer to the
328
+ trailing edge than [slot="scope"]/[range-*], since it reads as a
329
+ result/total rather than pagination or view-switching context. Text
330
+ sized via [--table-toolbar-summary-size] (shared with the
331
+ "Showing X–Y of N" range summary — both are the same text tier).
332
+ Plain author-supplied markup, same real-insertion-point pattern as
333
+ every other table-toolbar slot — no forced typography beyond the
334
+ font-size token. Stays visible at every ADR-0076 compaction stage: it
335
+ is DATA (an aggregate figure the consumer chose to show), not chrome,
336
+ so it is never dropped or compacted the way the range summary/
337
+ controls are — a consumer needing narrower-container behavior owns
338
+ that via its own slotted content, not a component-side compaction
339
+ rule. Empty (the default) has zero footprint on existing consumers.
320
340
  states:
321
341
  - name: idle
322
342
  description: Default, ready for interaction.
@@ -354,29 +374,40 @@ a2ui:
354
374
  - >-
355
375
  Pair <table-toolbar-ui> with <table-ui> via [for="<table-id>"]
356
376
  (or rely on first-sibling fallback when both are inside the
357
- same parent). One toolbar per table. The doubled-chrome hazard is
358
- a TITLE COLLISION, not a blanket card-header ban (gh#1807, SPEC
359
- REQ-C-004): a toolbar carrying [text] must not share a card with
360
- a heading-slotted <card-ui> <header> that pairing doubles the
361
- title row. A title-less toolbar (no [text]) inside a card whose
362
- <header> owns the title is the canonical table-in-card
363
- composition (see the table-in-card pattern) and is NOT doubled
364
- chrome the toolbar renders only its count/search/filter/sort/
365
- columns cluster, with the title living in the card header alone.
377
+ same parent). One toolbar per table. <table-toolbar-ui> MAY sit
378
+ as <card-ui>'s <header>'s sole child verified in a rendered
379
+ browser to lay out full-width identically to the
380
+ sibling-above-card pattern (gh#1807/#1884, ADR-0080) but do
381
+ NOT also give that same <header> a slot="heading"/slot="action"
382
+ child: card.css's grid mode then shrinks the toolbar to its own
383
+ content width and collapses its flex-row layout. The
384
+ doubled-chrome hazard is a TITLE COLLISION, not a blanket
385
+ card-header ban (SPEC REQ-C-004): a toolbar carrying [text] must
386
+ not share a card with a separately heading-slotted <card-ui>
387
+ <header> — that pairing doubles the title row. As the header's
388
+ sole child, the toolbar's own [text]/[count] cluster IS the
389
+ card's title; there is no separate heading to collide with.
366
390
  - >-
367
391
  All four affordances (search, filter, sort, columns) default ON.
368
392
  Opt out individually via [no-search] / [no-filter] / [no-sort]
369
393
  / [no-columns]. The previous [searchable] / [filterable]
370
394
  attributes are deprecated — do NOT emit them.
371
395
  - >-
372
- Three legitimate placements: ABOVE the <card-ui> containing the
373
- table-ui; standing alone outside a card-ui parent with
374
- [variant="card"] (wraps the toolbar in card-style chrome); or
375
- INSIDE the card, in its own plain (non-bleed) <section> between
376
- the card header and the bleed table section the canonical
377
- table-in-card shape (gh#1807) — title-less (no [text]) since the
378
- card header already owns the title (see the title-collision rule
379
- above).
396
+ Two legitimate in-card placements, chosen by whether the card
397
+ header needs content beyond the toolbar: ABOVE the <card-ui>
398
+ (sibling, typically wrapped with the table in a <col-ui>) when
399
+ the header carries its own slot="heading"/slot="action" content
400
+ alongside the toolbar; or AS <card-ui>'s <header> sole child
401
+ the canonical in-card shape (gh#1807/#1884, ADR-0080) — when the
402
+ toolbar IS the header and no separate title/action row is
403
+ needed. A toolbar can also stand alone outside any card-ui
404
+ parent with [variant="card"], which wraps it in card-style
405
+ chrome. The previous plain (non-bleed) <section> placement
406
+ between the card header and the bleed table section — a
407
+ title-less toolbar leaving the title to a separately-slotted
408
+ header — is retired: no live demo or pattern documents it, and
409
+ the header-child shape supersedes it as the canonical in-card
410
+ composition.
380
411
  - >-
381
412
  Use slot="action" (or [slot="actions"]) for trailing primary
382
413
  buttons (Invite, Export, +New). Use [text] / [count] props for
@@ -433,6 +464,15 @@ a2ui:
433
464
  and [range-noun] to stop hand-painting the per-slice noun ("of 80
434
465
  users"). Same three attributes, same names, on table-footer-ui
435
466
  (ADR-0082 §"same name on both companions").
467
+ - >-
468
+ Use [slot="summary"] (gh#1883) for a trailing aggregate/total datum
469
+ that belongs with the table but isn't pagination context — e.g.
470
+ batch-detail's line-items Subtotal:
471
+ `<span slot="summary">Subtotal <strong>$1,240.00</strong></span>`.
472
+ Renders after [slot="actions-leading"] and before the page-size
473
+ select / [slot="actions"]. Always visible — it is data, never
474
+ collapsed or hidden by the compaction stages that reduce
475
+ Filter/Sort/Columns/search or the range summary.
436
476
  - >-
437
477
  A plain <button-ui> slotted into [slot="actions-leading"] (e.g. the
438
478
  app-owned Filter/Columns triggers above) already collapses to
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://adiaui.dev/a2ui/v0_9/components/ThemeProvider.json",
4
4
  "title": "ThemeProvider",
5
- "description": "Foundation-providing wrapper — adopts the AdiaUI foundation (design tokens +\nresets + page-frame + every primitive's CSS) into the document from anywhere\nin the DOM, so a surface renders fully-styled WITHOUT a hand-wired\n<link rel=\"stylesheet\"> in <head>. Layout-transparent (display: contents): the\nelement owns no box; children lay out as if it weren't there.\n\nMechanism: it imports the constructable-stylesheet twin of web-components.min.css\n(byte-identical to the CDN bundle, emitted from the same build buffer) and\nadopts it once into document.adoptedStyleSheets, deduped — adoption fires at\nmodule load, before paint. Coexists with the render-blocking <link> path; both\ndeliver the same bytes. Use a <link> (-> the CDN web-components.min.css) for\nmulti-page / top-level surfaces (cacheable across navigations, zero flash); use\n<theme-provider> for SPA roots, embedded apps, and dynamic mounts where you do\nnot control <head> (e.g. <embed-shell>, an A2UI surface, a micro-frontend).\n\nTheming: the base foundation is OS light/dark via light-dark() tokens. Two opt-in\nattributes adopt their layer on demand — theme=\"ocean|forest|slate|…\" applies a\nnamed preset (adopts the themes layer; matches the [theme] hook) and\nscale=\"ui-sm|ui-md|ui-lg|content-sm|content-md|content-lg\" sets the sizing\nregister (adopts the matching register layer). Deprecated aliases \"verse\"\n(→ ui-sm) and \"prose\" (→ content-md) still map onto the legacy\n[verse]/[prose] attributes; removed at v1.0. Each layer is fetched only\nwhen its attribute is set, so a bare provider stays lean.\nOpt-in infra: NOT in the all-in-one @adia-ai/web-components barrel (it carries\nthe whole foundation); import @adia-ai/web-components/components/theme-provider\nexplicitly. Distinct from <frame-ui> (a layout skeleton, owns no CSS delivery)\nand the page shells (chrome over the same foundation).\n",
5
+ "description": "Foundation-providing wrapper — adopts the AdiaUI foundation (design tokens +\nresets + page-frame + every primitive's CSS) into the document from anywhere\nin the DOM, so a surface renders fully-styled WITHOUT a hand-wired\n<link rel=\"stylesheet\"> in <head>. Layout-transparent (display: contents): the\nelement owns no box; children lay out as if it weren't there.\n\nMechanism: it imports the constructable-stylesheet twin of web-components.min.css\n(byte-identical to the CDN bundle, emitted from the same build buffer) and\nadopts it once into document.adoptedStyleSheets, deduped — adoption fires at\nmodule load, before paint. Coexists with the render-blocking <link> path; both\ndeliver the same bytes. Use a <link> (-> the CDN web-components.min.css) for\nmulti-page / top-level surfaces (cacheable across navigations, zero flash); use\n<theme-provider> for SPA roots, embedded apps, and dynamic mounts where you do\nnot control <head> (e.g. <embed-shell>, an A2UI surface, a micro-frontend).\n\nTheming: the base foundation is OS light/dark via light-dark() tokens. Two opt-in\nattributes adopt their layer on demand — theme=\"ocean|forest|slate|…\" applies a\nnamed preset (adopts the themes layer; matches the [theme] hook) and\nscale=\"ui-sm|ui-md|ui-lg|content-sm|content-md|content-lg\" sets the sizing\nregister (adopts the matching register layer). Each layer is fetched only\nwhen its attribute is set, so a bare provider stays lean.\nOpt-in infra: NOT in the all-in-one @adia-ai/web-components barrel (it carries\nthe whole foundation); import @adia-ai/web-components/components/theme-provider\nexplicitly. Distinct from <frame-ui> (a layout skeleton, owns no CSS delivery)\nand the page shells (chrome over the same foundation).\n",
6
6
  "type": "object",
7
7
  "allOf": [
8
8
  {
@@ -22,7 +22,7 @@
22
22
  "const": "ThemeProvider"
23
23
  },
24
24
  "scale": {
25
- "description": "Sizing register for the subtree — one of the six [scale] tiers: ui-sm (dense product UI), ui-md (base), ui-lg (touch/presentation), content-sm (compact article), content-md (long-form), content-lg (hero/marketing). Adopts the scale layer on demand; the reflected attribute is the [scale=\"…\"] CSS hook. Deprecated aliases \"verse\" (→ ui-sm) and \"prose\" (→ content-md) still map onto the legacy [verse]/[prose] attributes; removed at v1.0.",
25
+ "description": "Sizing register for the subtree — one of the six [scale] tiers: ui-sm (dense product UI), ui-md (base), ui-lg (touch/presentation), content-sm (compact article), content-md (long-form), content-lg (hero/marketing). Adopts the scale layer on demand; the reflected attribute is the [scale=\"…\"] CSS hook.",
26
26
  "type": "string",
27
27
  "enum": [
28
28
  "ui-sm",
@@ -30,9 +30,7 @@
30
30
  "ui-lg",
31
31
  "content-sm",
32
32
  "content-md",
33
- "content-lg",
34
- "verse",
35
- "prose"
33
+ "content-lg"
36
34
  ],
37
35
  "default": ""
38
36
  }