@adia-ai/web-components 0.8.47 → 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 (68) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/MIGRATION.md +136 -0
  3. package/components/card/card.css +25 -2
  4. package/components/chart/chart.a2ui.json +1 -1
  5. package/components/chart/chart.class.js +41 -10
  6. package/components/chart/chart.d.ts +1 -1
  7. package/components/chart/chart.yaml +6 -3
  8. package/components/input/input.a2ui.json +2 -2
  9. package/components/input/input.css +45 -16
  10. package/components/input/input.yaml +11 -9
  11. package/components/link/link.css +3 -2
  12. package/components/richtext/richtext.css +2 -2
  13. package/components/search/search.a2ui.json +1 -1
  14. package/components/search/search.class.js +43 -0
  15. package/components/search/search.css +28 -0
  16. package/components/search/search.yaml +4 -1
  17. package/components/select/select.class.js +4 -1
  18. package/components/select/select.css +13 -0
  19. package/components/stat/stat.a2ui.json +30 -3
  20. package/components/stat/stat.css +88 -0
  21. package/components/stat/stat.d.ts +2 -2
  22. package/components/stat/stat.yaml +99 -6
  23. package/components/table/table.a2ui.json +5 -2
  24. package/components/table/table.class.js +36 -1
  25. package/components/table/table.d.ts +2 -2
  26. package/components/table/table.yaml +17 -1
  27. package/components/table-footer/table-footer.a2ui.json +20 -2
  28. package/components/table-footer/table-footer.class.js +94 -8
  29. package/components/table-footer/table-footer.d.ts +6 -2
  30. package/components/table-footer/table-footer.yaml +57 -6
  31. package/components/table-toolbar/table-toolbar.a2ui.json +28 -2
  32. package/components/table-toolbar/table-toolbar.class.js +69 -2
  33. package/components/table-toolbar/table-toolbar.css +36 -2
  34. package/components/table-toolbar/table-toolbar.d.ts +8 -2
  35. package/components/table-toolbar/table-toolbar.examples.md +6 -4
  36. package/components/table-toolbar/table-toolbar.yaml +140 -26
  37. package/components/theme-provider/theme-provider.a2ui.json +3 -5
  38. package/components/theme-provider/theme-provider.class.js +9 -30
  39. package/components/theme-provider/theme-provider.d.ts +3 -5
  40. package/components/theme-provider/theme-provider.yaml +4 -8
  41. package/core/element.js +16 -3
  42. package/custom-elements.json +52 -10
  43. package/dist/host.min.css +1 -1
  44. package/dist/host.sheet.js +1 -1
  45. package/dist/theme-provider.min.js +8 -8
  46. package/dist/themes.min.css +1 -1
  47. package/dist/themes.sheet.js +1 -1
  48. package/dist/web-components.min.css +1 -1
  49. package/dist/web-components.min.js +70 -69
  50. package/dist/web-components.sheet.js +1 -1
  51. package/index.css +1 -2
  52. package/package.json +1 -1
  53. package/styles/README.md +1 -1
  54. package/styles/api/sizing.css +1 -1
  55. package/styles/colors/material-static.css +34 -12
  56. package/styles/host.css +1 -4
  57. package/styles/index.css +2 -2
  58. package/styles/scale.css +19 -26
  59. package/styles/themes.css +2678 -2
  60. package/styles/type/elements.css +1 -0
  61. package/styles/type/roles.css +3 -3
  62. package/styles/typography.css +6 -5
  63. package/dist/prose.min.css +0 -1
  64. package/dist/prose.sheet.js +0 -11
  65. package/dist/verse.min.css +0 -1
  66. package/dist/verse.sheet.js +0 -11
  67. package/styles/prose.css +0 -211
  68. package/styles/verse.css +0 -151
@@ -3,11 +3,13 @@
3
3
  ## Wiring
4
4
 
5
5
  ```html
6
- <table-toolbar-ui for="members" text="All employees" count="32"></table-toolbar-ui>
7
6
  <card-ui>
8
- <section-ui bleed>
9
- <table-ui id="members" sortable raw>…</table-ui>
10
- </section-ui>
7
+ <header>
8
+ <table-toolbar-ui for="members" text="All employees" count="32"></table-toolbar-ui>
9
+ </header>
10
+ <section bleed>
11
+ <table-ui id="members" sortable>…</table-ui>
12
+ </section>
11
13
  </card-ui>
12
14
  ```
13
15
 
@@ -88,11 +88,45 @@ props:
88
88
  in "Showing 1–25 of 320"). Distinct from pagination-ui's `total`
89
89
  (total PAGES) — this is total ROWS; deliberately not spelled `total`
90
90
  to avoid that cross-sibling name collision (ADR-0063 B5). See
91
- rangeStart.
92
- type: number
91
+ rangeStart. Explicit range-total="?" (gh#1877, ADR-0082 Amendment) is
92
+ the OPEN/unproven-total state — cursor/hasMore server paging with no
93
+ known row count yet: the summary reads "Showing X–Y" with no "of N",
94
+ requiring only [range-start]/[range-end] (> 0) to render, not
95
+ rangeTotal too. Resolves to the normal finite summary the moment a
96
+ real number replaces the "?". Named identically to table-footer-ui's
97
+ own [range-total] (ADR-0082 §"same name on both companions").
98
+ type: [number, string]
93
99
  default: 0
94
100
  reflect: true
95
101
  attribute: range-total
102
+ rangeOf:
103
+ description: >-
104
+ gh#1877, ADR-0082 Amendment — secondary whole-set total for the
105
+ "filtered-of-whole" shape: a client-side facet narrows [rangeTotal]
106
+ (a filtered count) while rangeOf names the unfiltered set size, e.g.
107
+ "Showing 1–4 of 4 traversals (6 total)". Appends "(of <rangeOf>
108
+ total)" onto the normal finite range summary; unset (the default)
109
+ changes nothing. Never applies while the summary is in its open
110
+ ("?") or opted-out (all-zero) state. Named identically to
111
+ table-footer-ui's own [range-of] (ADR-0082 §"same name on both
112
+ companions").
113
+ type: number
114
+ default: 0
115
+ reflect: true
116
+ attribute: range-of
117
+ rangeNoun:
118
+ description: >-
119
+ gh#1877, ADR-0082 Amendment — optional per-slice noun appended after
120
+ the range total ("of 80 users") so consumers stop hand-painting the
121
+ noun themselves. Applies in both the normal finite range summary and
122
+ the open/unproven-total summary ("Showing 51–100 users" with no "of
123
+ N"). Unset (the default) changes nothing. Named identically to
124
+ table-footer-ui's own [range-noun] (ADR-0082 §"same name on both
125
+ companions").
126
+ type: string
127
+ default: ""
128
+ reflect: true
129
+ attribute: range-noun
96
130
  pageSize:
97
131
  description: >-
98
132
  Current rows-per-page value, shown as the selected option in the
@@ -190,6 +224,19 @@ props:
190
224
  default: false
191
225
  reflect: true
192
226
  attribute: chrome-only
227
+ titleAlways:
228
+ description: >-
229
+ Opt out of the scope-reconciliation title hide: by default, a filled
230
+ [slot="scope"] region (a select/menu whose own displayed value already
231
+ names the entity being listed) hides the separate [text]/[count]
232
+ title cluster next to it, since together they'd say the same thing
233
+ twice. Set [title-always] to keep the title cluster visible alongside
234
+ a filled scope slot — e.g. a scope select PLUS an explicit
235
+ text/count title on the same row (gh#1879).
236
+ type: boolean
237
+ default: false
238
+ reflect: true
239
+ attribute: title-always
193
240
  placeholder:
194
241
  description: Placeholder text for the search input.
195
242
  type: string
@@ -270,6 +317,26 @@ slots:
270
317
  stamp. Empty (the default) has zero footprint on existing consumers.
271
318
  actions:
272
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.
273
340
  states:
274
341
  - name: idle
275
342
  description: Default, ready for interaction.
@@ -307,29 +374,40 @@ a2ui:
307
374
  - >-
308
375
  Pair <table-toolbar-ui> with <table-ui> via [for="<table-id>"]
309
376
  (or rely on first-sibling fallback when both are inside the
310
- same parent). One toolbar per table. The doubled-chrome hazard is
311
- a TITLE COLLISION, not a blanket card-header ban (gh#1807, SPEC
312
- REQ-C-004): a toolbar carrying [text] must not share a card with
313
- a heading-slotted <card-ui> <header> that pairing doubles the
314
- title row. A title-less toolbar (no [text]) inside a card whose
315
- <header> owns the title is the canonical table-in-card
316
- composition (see the table-in-card pattern) and is NOT doubled
317
- chrome the toolbar renders only its count/search/filter/sort/
318
- 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.
319
390
  - >-
320
391
  All four affordances (search, filter, sort, columns) default ON.
321
392
  Opt out individually via [no-search] / [no-filter] / [no-sort]
322
393
  / [no-columns]. The previous [searchable] / [filterable]
323
394
  attributes are deprecated — do NOT emit them.
324
395
  - >-
325
- Three legitimate placements: ABOVE the <card-ui> containing the
326
- table-ui; standing alone outside a card-ui parent with
327
- [variant="card"] (wraps the toolbar in card-style chrome); or
328
- INSIDE the card, in its own plain (non-bleed) <section> between
329
- the card header and the bleed table section the canonical
330
- table-in-card shape (gh#1807) — title-less (no [text]) since the
331
- card header already owns the title (see the title-collision rule
332
- 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.
333
411
  - >-
334
412
  Use slot="action" (or [slot="actions"]) for trailing primary
335
413
  buttons (Invite, Export, +New). Use [text] / [count] props for
@@ -343,13 +421,15 @@ a2ui:
343
421
  update the table.
344
422
  - >-
345
423
  Use [slot="scope"] for a leading scope/view switcher (an org's
346
- teams, a saved view) rendered before [text]/[count]. Use
347
- [range-start] / [range-end] / [range-total] together for a
348
- "Showing X–Y of N" summaryit renders independently of, and
349
- alongside, [text]/[count], never in place of them. Use [page-size]
350
- + [page-size-options] for a rows-per-page select; it applies
351
- directly to the bound table's [paginate] prop the same way
352
- filter/sort/columns changes apply directly to the target.
424
+ teams, a saved view) rendered before [text]/[count]. By default, a
425
+ filled scope slot hides the [text]/[count] title cluster (they'd
426
+ otherwise repeat the same entity name) set [title-always] to keep
427
+ both visible together. Use [range-start] / [range-end] /
428
+ [range-total] together for a "Showing X–Y of N" summary — it renders
429
+ independently of, and alongside, [text]/[count], never in place of
430
+ them. Use [page-size] + [page-size-options] for a rows-per-page
431
+ select; it applies directly to the bound table's [paginate] prop the
432
+ same way filter/sort/columns changes apply directly to the target.
353
433
  - >-
354
434
  Use [slot="actions-leading"] (gh#1649) for app-owned trigger
355
435
  buttons that must land BEFORE the page-size select — e.g. custom
@@ -373,6 +453,26 @@ a2ui:
373
453
  normal "Showing X-Y of N" text (ADR-0076, REQ-E). Only fires on an
374
454
  EXPLICIT range-total="0" — omitting range-* entirely (loading) never
375
455
  shows it.
456
+ - >-
457
+ gh#1877, ADR-0082 Amendment — cursor/hasMore server paging (no
458
+ server-reported row count) sets [range-total="?"] instead of a real
459
+ number: the summary reads "Showing 51–100" with no "of N" (needs only
460
+ [range-start]/[range-end] > 0 to render), resolving to the normal
461
+ finite summary once a real [range-total] arrives. Use [range-of] for
462
+ the separate "filtered-of-whole" shape (a filtered range-total plus a
463
+ secondary unfiltered-set total, e.g. "of 4 traversals (6 total)"),
464
+ and [range-noun] to stop hand-painting the per-slice noun ("of 80
465
+ users"). Same three attributes, same names, on table-footer-ui
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.
376
476
  - >-
377
477
  A plain <button-ui> slotted into [slot="actions-leading"] (e.g. the
378
478
  app-owned Filter/Columns triggers above) already collapses to
@@ -420,6 +520,20 @@ examples:
420
520
  {"id": "sec", "component": "Section", "bleed": true, "children": ["tbl"]},
421
521
  {"id": "tbl", "component": "Table", "id": "accounts", "sortable": true, "raw": true}
422
522
  ]
523
+ - name: table-toolbar-open-total
524
+ description: >-
525
+ gh#1877, ADR-0082 Amendment — cursor/hasMore server paging with no
526
+ known row count yet: [range-total="?"] renders "Showing 1–50
527
+ subscriptions" (via [range-noun]) with no "of N", resolving to the
528
+ normal finite summary once a real total arrives.
529
+ a2ui: >-
530
+ [
531
+ {"id": "root", "component": "Column", "gap": "3", "children": ["bar", "card"]},
532
+ {"id": "bar", "component": "TableToolbar", "for": "subs", "range-start": 1, "range-end": 50, "range-total": "?", "range-noun": "subscriptions"},
533
+ {"id": "card", "component": "Card", "children": ["sec"]},
534
+ {"id": "sec", "component": "Section", "bleed": true, "children": ["tbl"]},
535
+ {"id": "tbl", "component": "Table", "id": "subs", "raw": true}
536
+ ]
423
537
  keywords:
424
538
  - table-toolbar
425
539
  - data-grid
@@ -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
  }
@@ -28,12 +28,11 @@
28
28
  *
29
29
  * Scope (v1): the provider supplies the FOUNDATION only — design tokens, resets,
30
30
  * page-frame, and every primitive's CSS. OS light/dark resolves automatically
31
- * (the tokens are `light-dark()`-based). Named themes (`[theme]`) and the
32
- * `verse` / `prose` context registers live in OPT-IN layers (themes.css /
33
- * verse.css / prose.css) that are NOT in the foundation bundle, so setting
34
- * `theme` / `verse` on the wrapper does nothing until those layers are also
35
- * adopted a planned `theme=` / `verse` provider option (the token-root selector
36
- * is pre-wired for it; see .brain/notes/theme-provider-design-2026-06-04.md).
31
+ * (the tokens are `light-dark()`-based). Named themes (`[theme]`) live in an
32
+ * OPT-IN layer (themes.css) that is NOT in the foundation bundle, so setting
33
+ * `theme` on the wrapper does nothing until that layer is also adopted — a
34
+ * planned `theme=` provider option (the token-root selector is pre-wired for
35
+ * it; see .brain/notes/theme-provider-design-2026-06-04.md).
37
36
  *
38
37
  * Deliberately NOT in the all-in-one `@adia-ai/web-components` barrel — it's
39
38
  * opt-in infra (it carries the whole foundation). Import it explicitly:
@@ -58,19 +57,17 @@ function provideFoundation() {
58
57
  }
59
58
  }
60
59
 
61
- // Opt-in context registers — themes (named `[theme]` presets), and the `verse` /
62
- // `prose` `scale` registers. Each is its own constructable-sheet twin (built by
60
+ // Opt-in context registers — themes (named `[theme]` presets) and the `scale`
61
+ // register. Each is its own constructable-sheet twin (built by
63
62
  // scripts/build/bundle-css.mjs, byte-identical to its .min.css). Adopted on demand
64
63
  // the first time a provider needs one, deduped by sheet identity. Dynamic-imported
65
64
  // (static paths, so bundlers can code-split) so the element stays lean — a provider
66
65
  // that does no theming never pulls these in.
67
- const TWIN_LOADED = { themes: false, verse: false, prose: false, scale: false };
66
+ const TWIN_LOADED = { themes: false, scale: false };
68
67
  function loadTwin(name) {
69
68
  switch (name) {
70
69
  case 'themes': return import('../../dist/themes.sheet.js');
71
70
  case 'scale': return import('../../dist/scale.sheet.js');
72
- case 'verse': return import('../../dist/verse.sheet.js');
73
- case 'prose': return import('../../dist/prose.sheet.js');
74
71
  default: return null;
75
72
  }
76
73
  }
@@ -100,8 +97,6 @@ export class UIThemeProvider extends UIElement {
100
97
  // Sizing register — one of the six [scale] tiers (ui-sm | ui-md | ui-lg |
101
98
  // content-sm | content-md | content-lg). Reflected, so the attribute IS the
102
99
  // `[scale="…"]` CSS hook in scale.css (the `theme` prop's mechanism).
103
- // DEPRECATED aliases: "verse" (→ ui-sm) and "prose" (→ content-md) still
104
- // map onto the legacy [verse]/[prose] attributes; removed at v1.0.
105
100
  scale: { type: String, reflect: true },
106
101
  };
107
102
  static template = () => null;
@@ -128,15 +123,7 @@ export class UIThemeProvider extends UIElement {
128
123
  if (this.theme) ensureTwin('themes');
129
124
 
130
125
  const scale = this.scale;
131
- this.toggleAttribute('verse', scale === 'verse');
132
- this.toggleAttribute('prose', scale === 'prose');
133
- if (scale === 'verse' || scale === 'prose') {
134
- // Legacy register aliases — the reflected scale="verse|prose" attribute
135
- // matches no scale.css tier selector, so only the toggled [verse]/[prose]
136
- // attribute has CSS effect. Removed at v1.0.
137
- warnDeprecatedScaleAlias(scale);
138
- ensureTwin(scale);
139
- } else if (SCALE_TIERS.has(scale)) {
126
+ if (SCALE_TIERS.has(scale)) {
140
127
  // Tier names match [scale="…"] in scale.css directly via reflection.
141
128
  ensureTwin('scale');
142
129
  }
@@ -144,11 +131,3 @@ export class UIThemeProvider extends UIElement {
144
131
  }
145
132
 
146
133
  const SCALE_TIERS = new Set(['ui-sm', 'ui-md', 'ui-lg', 'content-sm', 'content-md', 'content-lg']);
147
-
148
- const warnedAliases = new Set();
149
- function warnDeprecatedScaleAlias(alias) {
150
- if (warnedAliases.has(alias)) return;
151
- warnedAliases.add(alias);
152
- const tier = alias === 'verse' ? 'ui-sm' : 'content-md';
153
- console.warn(`[theme-provider] scale="${alias}" is deprecated — use scale="${tier}" (removed at v1.0).`);
154
- }
@@ -18,9 +18,7 @@ Theming: the base foundation is OS light/dark via light-dark() tokens. Two opt-i
18
18
  attributes adopt their layer on demand — theme="ocean|forest|slate|…" applies a
19
19
  named preset (adopts the themes layer; matches the [theme] hook) and
20
20
  scale="ui-sm|ui-md|ui-lg|content-sm|content-md|content-lg" sets the sizing
21
- register (adopts the matching register layer). Deprecated aliases "verse"
22
- (→ ui-sm) and "prose" (→ content-md) still map onto the legacy
23
- [verse]/[prose] attributes; removed at v1.0. Each layer is fetched only
21
+ register (adopts the matching register layer). Each layer is fetched only
24
22
  when its attribute is set, so a bare provider stays lean.
25
23
  Opt-in infra: NOT in the all-in-one @adia-ai/web-components barrel (it carries
26
24
  the whole foundation); import @adia-ai/web-components/components/theme-provider
@@ -42,6 +40,6 @@ import { UIElement } from '../../core/element.js';
42
40
  export class UIThemeProvider extends UIElement {
43
41
  /** Named theme preset for the wrapped subtree (default, ocean, forest, sunset, lavender, rose, slate, midnight). Adopts the themes layer on demand + matches the [theme="…"] hook. */
44
42
  theme: string;
45
- /** 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. */
46
- scale: 'ui-sm' | 'ui-md' | 'ui-lg' | 'content-sm' | 'content-md' | 'content-lg' | 'verse' | 'prose';
43
+ /** 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. */
44
+ scale: 'ui-sm' | 'ui-md' | 'ui-lg' | 'content-sm' | 'content-md' | 'content-lg';
47
45
  }
@@ -26,9 +26,7 @@ description: |
26
26
  attributes adopt their layer on demand — theme="ocean|forest|slate|…" applies a
27
27
  named preset (adopts the themes layer; matches the [theme] hook) and
28
28
  scale="ui-sm|ui-md|ui-lg|content-sm|content-md|content-lg" sets the sizing
29
- register (adopts the matching register layer). Deprecated aliases "verse"
30
- (→ ui-sm) and "prose" (→ content-md) still map onto the legacy
31
- [verse]/[prose] attributes; removed at v1.0. Each layer is fetched only
29
+ register (adopts the matching register layer). Each layer is fetched only
32
30
  when its attribute is set, so a bare provider stays lean.
33
31
  Opt-in infra: NOT in the all-in-one @adia-ai/web-components barrel (it carries
34
32
  the whole foundation); import @adia-ai/web-components/components/theme-provider
@@ -41,7 +39,7 @@ props:
41
39
  default: ""
42
40
  reflect: true
43
41
  scale:
44
- 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.'
42
+ 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.'
45
43
  type: string
46
44
  default: ""
47
45
  enum:
@@ -51,8 +49,6 @@ props:
51
49
  - content-sm
52
50
  - content-md
53
51
  - content-lg
54
- - verse
55
- - prose
56
52
  reflect: true
57
53
  events: {}
58
54
  slots: {}
@@ -67,8 +63,8 @@ a2ui:
67
63
  reason: 'Adopts the foundation bundle into the document from anywhere in the tree (document.adoptedStyleSheets).'
68
64
  - rule: 'For multi-page / top-level pages, prefer a render-blocking <link rel="stylesheet" href=".../web-components.min.css"> in <head> instead — cacheable across navigations, styled on the first frame. <theme-provider> and the link deliver byte-identical CSS, so they coexist.'
69
65
  reason: 'Link for multi-page (no flash); provider for SPA/embedded (no head access).'
70
- - rule: 'Theme the wrapped subtree with theme="ocean" (a named preset) and/or scale="verse|prose" (compact / long-form register) — the provider adopts the matching opt-in layer on demand. The base foundation is OS light/dark automatically. Keep <theme-provider> at/near the mount root — display:contents means an ancestor "> " child selector will not reach its children.'
71
- reason: 'theme/scale each fetch + adopt their layer (themes.css / verse.css / prose.css) on demand; display:contents preserves custom-property inheritance but not child-combinator reach (Light DOM, ADR-0033).'
66
+ - rule: 'Theme the wrapped subtree with theme="ocean" (a named preset) and/or scale="content-md" (a sizing register tier) — the provider adopts the matching opt-in layer on demand. The base foundation is OS light/dark automatically. Keep <theme-provider> at/near the mount root — display:contents means an ancestor "> " child selector will not reach its children.'
67
+ reason: 'theme/scale each fetch + adopt their layer (themes.css / scale.css) on demand; display:contents preserves custom-property inheritance but not child-combinator reach (Light DOM, ADR-0033).'
72
68
  anti_patterns: []
73
69
  examples:
74
70
  - name: spa-root
package/core/element.js CHANGED
@@ -85,9 +85,22 @@ const NOOP_INTERNALS = Object.freeze({
85
85
  });
86
86
 
87
87
  function reflect(el, a, v, t) {
88
- t === Boolean
89
- ? v ? el.setAttribute(a, '') : el.removeAttribute(a)
90
- : v == null ? el.removeAttribute(a) : el.setAttribute(a, String(v));
88
+ if (t === Boolean) {
89
+ v ? el.setAttribute(a, '') : el.removeAttribute(a);
90
+ return;
91
+ }
92
+ // gh#1895 — a Number-typed reflected prop set from an unparseable sentinel
93
+ // attribute (e.g. `range-total="?"`, ADR-0082 Amendment's open/unproven-
94
+ // total grammar) coerces to NaN. Writing `String(NaN)` back would stomp
95
+ // the author's sentinel with the literal string "NaN" on the very next
96
+ // render, so a non-finite Number value is deliberately left unreflected —
97
+ // the attribute keeps whatever the author wrote (or stays absent, for a
98
+ // NaN assigned straight from JS with no attribute yet). This mirrors how
99
+ // `null` already skips straight to removeAttribute below instead of
100
+ // stringifying: NaN gets its own no-write branch rather than one more
101
+ // Number.isFinite check duplicated at every "?"-sentinel call site.
102
+ if (t === Number && v != null && !Number.isFinite(v)) return;
103
+ v == null ? el.removeAttribute(a) : el.setAttribute(a, String(v));
91
104
  }
92
105
 
93
106
  const parseAttr = (v, t) =>