@adia-ai/web-components 0.8.32 → 0.8.34

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 (100) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/MIGRATION.md +136 -0
  3. package/components/accordion/accordion-item.a2ui.json +8 -0
  4. package/components/accordion/accordion-item.yaml +6 -0
  5. package/components/accordion/accordion.css +3 -1
  6. package/components/agent-reasoning/agent-reasoning.a2ui.json +1 -1
  7. package/components/agent-reasoning/agent-reasoning.yaml +3 -3
  8. package/components/alert/alert.a2ui.json +3 -5
  9. package/components/alert/alert.class.js +3 -3
  10. package/components/alert/alert.css +5 -12
  11. package/components/alert/alert.d.ts +2 -2
  12. package/components/alert/alert.yaml +6 -5
  13. package/components/badge/badge.a2ui.json +6 -9
  14. package/components/badge/badge.class.js +4 -4
  15. package/components/badge/badge.css +111 -77
  16. package/components/badge/badge.d.ts +34 -12
  17. package/components/badge/badge.examples.md +1 -1
  18. package/components/badge/badge.yaml +37 -16
  19. package/components/button/button.a2ui.json +1 -1
  20. package/components/button/button.css +16 -2
  21. package/components/button/button.d.ts +2 -2
  22. package/components/button/button.yaml +1 -1
  23. package/components/card/card.yaml +1 -1
  24. package/components/chart/chart.class.js +8 -4
  25. package/components/combobox/combobox.a2ui.json +1 -1
  26. package/components/combobox/combobox.yaml +3 -0
  27. package/components/command/command.a2ui.json +4 -0
  28. package/components/command/command.class.js +54 -4
  29. package/components/command/command.d.ts +2 -0
  30. package/components/command/command.yaml +25 -3
  31. package/components/date-range-picker/date-range-picker.a2ui.json +1 -1
  32. package/components/date-range-picker/date-range-picker.class.js +42 -43
  33. package/components/date-range-picker/date-range-picker.css +11 -0
  34. package/components/date-range-picker/date-range-picker.yaml +6 -3
  35. package/components/description-list/description-list.css +16 -4
  36. package/components/integration-card/integration-card.class.js +11 -2
  37. package/components/mark/mark.a2ui.json +6 -7
  38. package/components/mark/mark.class.js +1 -1
  39. package/components/mark/mark.css +7 -4
  40. package/components/mark/mark.d.ts +2 -2
  41. package/components/mark/mark.examples.md +0 -4
  42. package/components/mark/mark.yaml +16 -6
  43. package/components/modal/modal.class.js +43 -2
  44. package/components/modal/modal.css +23 -0
  45. package/components/nav-group/nav-group.css +15 -4
  46. package/components/nav-item/nav-item.css +11 -3
  47. package/components/option-card/option-card.examples.md +1 -1
  48. package/components/popover/popover.a2ui.json +5 -0
  49. package/components/popover/popover.class.js +9 -4
  50. package/components/popover/popover.css +5 -2
  51. package/components/popover/popover.yaml +8 -0
  52. package/components/preview/preview.examples.md +1 -1
  53. package/components/richtext/richtext.css +5 -1
  54. package/components/segment/segment.css +10 -0
  55. package/components/step-progress/step-progress.css +16 -3
  56. package/components/table/cell-types.js +20 -1
  57. package/components/table/table.a2ui.json +1 -1
  58. package/components/table/table.class.js +11 -0
  59. package/components/table/table.css +9 -0
  60. package/components/table/table.d.ts +1 -1
  61. package/components/table/table.yaml +1 -1
  62. package/components/table-toolbar/table-toolbar.class.js +1 -1
  63. package/components/tabs/tabs.css +3 -1
  64. package/components/tag/tag.a2ui.json +5 -5
  65. package/components/tag/tag.class.js +1 -1
  66. package/components/tag/tag.css +93 -77
  67. package/components/tag/tag.d.ts +17 -11
  68. package/components/tag/tag.yaml +30 -19
  69. package/components/toast/toast.a2ui.json +2 -4
  70. package/components/toast/toast.css +0 -12
  71. package/components/toast/toast.yaml +3 -4
  72. package/core/element.js +31 -0
  73. package/core/signals.js +3 -1
  74. package/dist/theme-provider.min.js +2 -2
  75. package/dist/web-components.min.css +1 -1
  76. package/dist/web-components.min.js +67 -67
  77. package/dist/web-components.sheet.js +1 -1
  78. package/package.json +1 -1
  79. package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +1 -1
  80. package/patterns/agent-memory/agent-memory.examples.html +5 -5
  81. package/patterns/agent-prompt-library/agent-prompt-library.examples.html +2 -2
  82. package/patterns/audit-log/audit-log.examples.js +12 -5
  83. package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.js +4 -1
  84. package/patterns/changelog-feed/changelog-feed.examples.html +1 -1
  85. package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +1 -1
  86. package/patterns/conversion-funnel/conversion-funnel.examples.html +1 -1
  87. package/patterns/diff-review/diff-review.examples.html +9 -9
  88. package/patterns/filter-bar/filter-bar.examples.html +12 -8
  89. package/patterns/filter-bar/filter-bar.examples.js +4 -1
  90. package/patterns/marketing-engagement/marketing-engagement.examples.html +1 -1
  91. package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +1 -1
  92. package/patterns/permissions-matrix/permissions-matrix.examples.html +1 -1
  93. package/patterns/permissions-sharing/permissions-sharing.examples.html +1 -1
  94. package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +2 -2
  95. package/patterns/record-detail-drawer/record-detail-drawer.examples.html +1 -1
  96. package/patterns/responsive-shell-sidebar/responsive-shell-sidebar.examples.html +135 -147
  97. package/patterns/retention-cohort/retention-cohort.examples.html +1 -1
  98. package/patterns/retention-cohort/retention-cohort.examples.js +4 -1
  99. package/patterns/system-banners/system-banners.examples.html +3 -3
  100. package/styles/colors/semantics/core.css +22 -2
@@ -14,19 +14,26 @@ tag-ui[removable]:not([disabled]):hover {
14
14
  default at construction time. A bare `<tag-ui text="…">` therefore
15
15
  never gets a physical `variant="default"` attribute; these
16
16
  `:where(:scope)` zero-specificity tokens are what it actually
17
- renders. Solid neutral inverse (was `--a-bg-muted` quiet chrome
18
- pre-#794) — same pair the family variants and the redundant
17
+ renders. Solid neutral (was `--a-bg-muted` quiet chrome pre-#794)
18
+ — same pair the family variants and the redundant
19
19
  `:scope[variant="default"]` rule below carry, for the rare case
20
20
  `variant="default"` IS reflected explicitly (SSR/a2ui-producer
21
- markup). Opt out via `[tone="muted"]`. */
22
- --tag-bg: var(--md-sys-color-neutral-on-surface);
23
- --tag-fg: var(--md-sys-color-neutral-background);
21
+ markup). Opt out via `[tone="muted"]`. Uses the real
22
+ `--md-sys-color-neutral`/`-on-neutral` pair (operator ruling,
23
+ 2026-08-10) — the true parallel to `-primary`/`-on-primary` etc.,
24
+ not the `-on-surface`/`-background` ink/page tokens this rule
25
+ borrowed pre-#794's cross-check. */
26
+ --tag-bg: var(--md-sys-color-neutral);
27
+ --tag-fg: var(--md-sys-color-neutral-on-neutral);
24
28
  --tag-border: transparent;
25
29
  --tag-radius: var(--a-radius-full);
26
30
  --tag-px: var(--a-space-1-5);
27
31
  --tag-py: var(--a-space-0-5);
28
32
  --tag-gap: var(--a-space-1);
29
- --tag-font: var(--a-ui-sm);
33
+ /* One rung down the --a-ui-* scale (gh#965): tags read as metadata
34
+ chrome, not body copy. Whole tier ladder shifts with it (sm=2xs,
35
+ lg=sm) so the tiers stay one rung apart. */
36
+ --tag-font: var(--a-ui-xs);
30
37
 
31
38
  /* ── Size tiers (gh#779) — md (above) is the pixel-identical default;
32
39
  [size="sm"|"lg"] below re-point the active tokens. Component-owned
@@ -41,8 +48,8 @@ tag-ui[removable]:not([disabled]):hover {
41
48
  --tag-px-lg: var(--a-space-2);
42
49
  --tag-py-sm: var(--a-space-px);
43
50
  --tag-py-lg: var(--a-space-1);
44
- --tag-font-sm: var(--a-ui-xs);
45
- --tag-font-lg: var(--a-ui-md);
51
+ --tag-font-sm: var(--a-ui-2xs);
52
+ --tag-font-lg: var(--a-ui-sm);
46
53
  --tag-font-family: var(--a-font-family-ui);
47
54
  --tag-dismiss-size: 1rem;
48
55
  --tag-bg-hover: var(--a-bg-hover);
@@ -122,85 +129,94 @@ tag-ui[removable]:not([disabled]):hover {
122
129
  }
123
130
 
124
131
  /* ── Variants ──
125
- Default tone is `solid` for the four family variants:
126
- `--a-{family}-bg` (saturated surface) + `--a-{family}-fg` (on-strong
127
- text, near-white in both schemes by design). Reads as a status pill
128
- where the chip IS the state. Opt out per-tag via [tone="muted"] for
129
- metadata-chip surfaces in dense lists. */
130
- /* `accent` is brand-primary emphasis for non-semantic labelsmirrors
131
- <badge-ui>'s `accent` variant token-for-token (--a-primary-bg /
132
- on-primary for the solid fill), applied to tag-ui's own solid-default
133
- tone shape. Not a status color no success/warning/danger implication. */
134
- :scope[variant="accent"] {
135
- --tag-bg: var(--a-primary-bg);
136
- --tag-fg: var(--md-sys-color-primary-on-primary);
132
+ Default tone is `muted` for the family variants (gh#864): the
133
+ tinted-container pair `--md-sys-color-{family}-container` (tinted
134
+ surface) + `-on-surface` (scheme-flipping text). Tag and badge had
135
+ been implementing each OTHER's reported default formula (gh#794
136
+ flipped tag's bare default to solid a day before gh#845 ratified
137
+ badge's register, with no cross-check between the two changes)this
138
+ restores tag's own. Opt into the saturated stamp per-tag via
139
+ [tone="solid"] below. This does NOT touch the family-less `default`
140
+ variant below, which stays solid unconditionally (gh#794, unrelated
141
+ to the gh#864 swap). */
142
+ /* `primary` is brand emphasis for non-semantic labels — mirrors
143
+ <badge-ui>'s `primary` variant token-for-token (--md-sys-color-primary-
144
+ container / -on-surface for the tinted fill; `accent` retired, gh#874).
145
+ Not a status color — no success/warning/danger implication. */
146
+ :scope[variant="primary"] {
147
+ --tag-bg: var(--md-sys-color-primary-container);
148
+ --tag-fg: var(--md-sys-color-primary-on-surface);
137
149
  }
138
150
 
139
151
  :scope[variant="info"] {
140
- --tag-bg: var(--a-info-bg);
141
- --tag-fg: var(--md-sys-color-info-on-info);
152
+ --tag-bg: var(--md-sys-color-info-container);
153
+ --tag-fg: var(--md-sys-color-info-on-surface);
142
154
  }
143
155
 
144
156
  :scope[variant="success"] {
145
- --tag-bg: var(--a-success-bg);
146
- --tag-fg: var(--md-sys-color-success-on-success);
157
+ --tag-bg: var(--md-sys-color-success-container);
158
+ --tag-fg: var(--md-sys-color-success-on-surface);
147
159
  }
148
160
 
149
- /* Same shape as the sibling families since the Material static-token
150
- migration: amber fill + the palette's own fixed-light on-color
151
- (`on-warning`, white in both schemes per Material's on-color law).
152
- The pre-migration dark-brown-text override is gone — its
153
- lower-than-siblings contrast is a known, operator-accepted
154
- consequence (TKT-0018, wontfix pending palette adjustment). */
155
161
  :scope[variant="warning"] {
156
- --tag-bg: var(--a-warning-bg);
157
- --tag-fg: var(--md-sys-color-warning-on-warning);
162
+ --tag-bg: var(--md-sys-color-warning-container);
163
+ --tag-fg: var(--md-sys-color-warning-on-surface);
158
164
  }
159
165
 
160
166
  :scope[variant="danger"] {
161
- --tag-bg: var(--a-danger-bg);
162
- --tag-fg: var(--md-sys-color-danger-on-danger);
167
+ --tag-bg: var(--md-sys-color-danger-container);
168
+ --tag-fg: var(--md-sys-color-danger-on-surface);
163
169
  }
164
170
 
165
171
  /* `default` (no family) is solid by default (gh#794) — the same
166
- high-contrast neutral inverse the family variants already carry.
167
- A bare `<tag-ui text="…">` is now a stamped pill, not quiet chrome.
168
- Opt OUT to the pre-#794 quiet-chrome look via `[tone="muted"]`
169
- (below) on metadata/citation chips in dense lists or tables where
170
- the saturated look competes for attention (gh#778's oversized-pill
171
- complaint) same escape hatch the family variants already use. */
172
+ high-contrast neutral inverse it has always carried. A bare
173
+ `<tag-ui text="…">` is a stamped pill, not quiet chrome. Unaffected by
174
+ the gh#864 family-variant swap above. Opt OUT to the pre-#794 quiet-
175
+ chrome look via `[tone="muted"]` (below) on metadata/citation chips
176
+ in dense lists or tables where the saturated look competes for
177
+ attention (gh#778's oversized-pill complaint). */
172
178
  :scope[variant="default"] {
173
- --tag-bg: var(--md-sys-color-neutral-on-surface);
174
- --tag-fg: var(--md-sys-color-neutral-background);
179
+ --tag-bg: var(--md-sys-color-neutral);
180
+ --tag-fg: var(--md-sys-color-neutral-on-neutral);
175
181
  }
176
182
 
177
183
  /* ── Tone modifier — orthogonal to [variant] ──
178
- `[tone="muted"]` opts each family variant OUT of the solid default
179
- into the canonical muted pair: `--a-{family}-muted` (tinted surface)
180
- + `--a-{family}-text` (scheme-flipping text). Same shape <badge-ui>
181
- uses as its default. Use on metadata chips in dense lists where the
182
- saturated default would compete for attention. */
183
- :scope[tone="muted"][variant="info"] {
184
- --tag-bg: var(--md-sys-color-info-container);
185
- --tag-fg: var(--md-sys-color-info-on-surface);
184
+ `[tone="solid"]` opts each family variant INTO the saturated-fill pair
185
+ (gh#864 this used to be the family variants' unconditional default;
186
+ see the Variants comment above): `--a-{family}-bg` (saturated surface)
187
+ + Material's on-color (`-on-{family}`, near-white by the fixed-light-
188
+ on-color law). Same shape <tag-ui>'s family-less `default` variant
189
+ still carries unconditionally. Use for a single hero tag that should
190
+ stamp its state.
191
+
192
+ Warning uses --a-warning-bg (semantics.css L3 redirect to the bright-
193
+ amber -20-tint) rather than the raw family token — see also
194
+ <badge-ui>'s identical workaround. */
195
+ :scope[tone="solid"][variant="info"] {
196
+ --tag-bg: var(--a-info-bg);
197
+ --tag-fg: var(--md-sys-color-info-on-info);
186
198
  }
187
- :scope[tone="muted"][variant="success"] {
188
- --tag-bg: var(--md-sys-color-success-container);
189
- --tag-fg: var(--md-sys-color-success-on-surface);
199
+ :scope[tone="solid"][variant="success"] {
200
+ --tag-bg: var(--a-success-bg);
201
+ --tag-fg: var(--md-sys-color-success-on-success);
190
202
  }
191
- :scope[tone="muted"][variant="warning"] {
192
- --tag-bg: var(--md-sys-color-warning-container);
193
- --tag-fg: var(--md-sys-color-warning-on-surface);
203
+ /* Same shape as the sibling families since the Material static-token
204
+ migration: amber fill + the palette's own fixed-light on-color
205
+ (`on-warning`, white in both schemes per Material's on-color law).
206
+ The pre-migration dark-brown-text override is gone — its
207
+ lower-than-siblings contrast is a known, operator-accepted
208
+ consequence (TKT-0018, wontfix pending palette adjustment). */
209
+ :scope[tone="solid"][variant="warning"] {
210
+ --tag-bg: var(--a-warning-bg);
211
+ --tag-fg: var(--md-sys-color-warning-on-warning);
194
212
  }
195
- :scope[tone="muted"][variant="danger"] {
196
- --tag-bg: var(--md-sys-color-danger-container);
197
- --tag-fg: var(--md-sys-color-danger-on-surface);
213
+ :scope[tone="solid"][variant="danger"] {
214
+ --tag-bg: var(--a-danger-bg);
215
+ --tag-fg: var(--md-sys-color-danger-on-danger);
198
216
  }
199
- /* Mirrors <badge-ui>'s default (muted) accent pair —
200
- --md-sys-color-primary-container / -on-surface. */
201
- :scope[tone="muted"][variant="accent"] {
202
- --tag-bg: var(--md-sys-color-primary-container);
203
- --tag-fg: var(--md-sys-color-primary-on-surface);
217
+ :scope[tone="solid"][variant="primary"] {
218
+ --tag-bg: var(--a-primary-bg);
219
+ --tag-fg: var(--md-sys-color-primary-on-primary);
204
220
  }
205
221
  /* gh#794: the pre-#794 quiet-chrome look for the variant-less default —
206
222
  same values the `:where(:scope)` base tokens carried before the
@@ -213,7 +229,7 @@ tag-ui[removable]:not([disabled]):hover {
213
229
  `installProps()` reflect() path only fires on a property SET) —
214
230
  the same reason the `[tone="solid"]` fallback below uses this same
215
231
  `:not()` shape instead of `[variant="default"]`. */
216
- :scope[tone="muted"]:not([variant="info"]):not([variant="success"]):not([variant="warning"]):not([variant="danger"]):not([variant="accent"]) {
232
+ :scope[tone="muted"]:not([variant="info"]):not([variant="success"]):not([variant="warning"]):not([variant="danger"]):not([variant="primary"]) {
217
233
  --tag-bg: var(--a-bg-muted);
218
234
  --tag-fg: var(--md-sys-color-neutral-on-surface);
219
235
  }
@@ -222,13 +238,13 @@ tag-ui[removable]:not([disabled]):hover {
222
238
  since gh#794 (the variant-less default is solid unconditionally
223
239
  above) but kept so `tone="solid"` stays a valid, harmless, explicit
224
240
  statement of intent for consumers who set it directly.
225
- [variant="accent"] is excluded — it already has an explicit solid
226
- pair above (the plain [variant="accent"] rule) that this higher-
227
- specificity fallback would otherwise clobber when tone="solid" is
228
- set explicitly alongside it. */
229
- :scope[tone="solid"]:not([variant="info"]):not([variant="success"]):not([variant="warning"]):not([variant="danger"]):not([variant="accent"]) {
230
- --tag-bg: var(--md-sys-color-neutral-on-surface);
231
- --tag-fg: var(--md-sys-color-neutral-background);
241
+ [variant="primary"] is excluded — its solid pair is the explicit
242
+ `[tone="solid"][variant="primary"]` rule above (gh#864: the bare
243
+ `[variant="primary"]` rule is container-tinted now, not solid) — this
244
+ `:not()` keeps the neutral-only fallback from ever matching primary. */
245
+ :scope[tone="solid"]:not([variant="info"]):not([variant="success"]):not([variant="warning"]):not([variant="danger"]):not([variant="primary"]) {
246
+ --tag-bg: var(--md-sys-color-neutral);
247
+ --tag-fg: var(--md-sys-color-neutral-on-neutral);
232
248
  }
233
249
 
234
250
  /* ── `[tone="outline"]` — transparent bg + family-colored border + text ──
@@ -255,15 +271,15 @@ tag-ui[removable]:not([disabled]):hover {
255
271
  --tag-fg: var(--md-sys-color-danger-on-surface);
256
272
  --tag-border: var(--md-sys-color-danger-outline);
257
273
  }
258
- /* Mirrors <badge-ui>'s outline accent pair —
274
+ /* Mirrors <badge-ui>'s outline primary pair —
259
275
  --md-sys-color-primary-on-surface / -primary-outline. */
260
- :scope[tone="outline"][variant="accent"] {
276
+ :scope[tone="outline"][variant="primary"] {
261
277
  --tag-fg: var(--md-sys-color-primary-on-surface);
262
278
  --tag-border: var(--md-sys-color-primary-outline);
263
279
  }
264
280
  /* Outline on neutral (no family) — fg-muted text + subtle border.
265
- [variant="accent"] excluded — see the [tone="solid"] fallback note above. */
266
- :scope[tone="outline"]:not([variant="info"]):not([variant="success"]):not([variant="warning"]):not([variant="danger"]):not([variant="accent"]) {
281
+ [variant="primary"] excluded — see the [tone="solid"] fallback note above. */
282
+ :scope[tone="outline"]:not([variant="info"]):not([variant="success"]):not([variant="warning"]):not([variant="danger"]):not([variant="primary"]) {
267
283
  --tag-fg: var(--a-fg-muted);
268
284
  --tag-border: var(--md-sys-color-neutral-outline);
269
285
  }
@@ -279,7 +295,7 @@ tag-ui[removable]:not([disabled]):hover {
279
295
  :scope[size="md"] {
280
296
  --tag-px: var(--a-space-1-5);
281
297
  --tag-py: var(--a-space-0-5);
282
- --tag-font: var(--a-ui-sm);
298
+ --tag-font: var(--a-ui-xs);
283
299
  }
284
300
  :scope[size="lg"] {
285
301
  --tag-px: var(--tag-px-lg);
@@ -41,15 +41,21 @@ export class UITag extends UIElement {
41
41
  /** Tag label. Renderer routes this to the `text` attribute, rendered via CSS attr(text) on ::after. */
42
42
  textContent: string;
43
43
  /** Fill style — orthogonal to [variant]. Three values:
44
- - `solid` (default for EVERY variant, including the family-less
45
- `default`, since gh#794) saturated bg + on-strong (near-white)
46
- text. The chip IS the state. A bare `<tag-ui text="…">` renders
47
- this look with no attributes at all.
48
- - `muted` tinted bg with scheme-paired text. Matches <badge-ui>'s
49
- default look, and is the pre-gh#794 look for the family-less
50
- `default` variant. Use on metadata / citation chips in dense
51
- lists or tables where the saturated default would compete for
52
- attention (gh#778's oversized-pill concern).
44
+ - `muted` (default for the family variants info/success/warning/
45
+ danger/primary gh#864: tag and badge were swapped relative to
46
+ their reported default formulas; this restores tag's own)
47
+ tinted bg with scheme-paired text. Matches <badge-ui>'s
48
+ `[tone="muted"]` look. Use on metadata / citation chips in dense
49
+ lists or tables where a saturated fill would compete for
50
+ attention (gh#778's oversized-pill concern). Does NOT apply to
51
+ the family-less `default` variant, which stays solid
52
+ unconditionally (gh#794, unaffected by gh#864) — a bare
53
+ `<tag-ui text="…">` still renders that solid look with no
54
+ attributes at all.
55
+ - `solid` — saturated bg + on-strong (near-white) text. The chip
56
+ IS the state. Opt in explicitly for a family variant (gh#864),
57
+ or it's already the unconditional look for the family-less
58
+ `default` variant (gh#794).
53
59
  - `outline` — transparent bg + family-colored border + family-colored
54
60
  text. The lightest visual weight; good in dense data tables or
55
61
  faceted filter rows where multiple chips would otherwise compete.
@@ -58,8 +64,8 @@ prop — see tag.class.js) so its schema `default` here is documentation
58
64
  of the rendered-by-default look, not a reflected attribute value.
59
65
  */
60
66
  tone: 'solid' | 'muted' | 'outline';
61
- /** Semantic variant — `default | accent | info | success | warning | danger`. `accent` is brand-primary emphasis for non-semantic labels — a filter chip or taxonomy tag that shouldn't imply a success/warning/danger state. Mirrors <badge-ui>'s `accent` variant token-for-token. */
62
- variant: 'default' | 'accent' | 'info' | 'success' | 'warning' | 'danger';
67
+ /** Semantic variant — `default | info | success | warning | danger | primary`. `primary` is brand emphasis for non-semantic labels — a filter chip or taxonomy tag that shouldn't imply a success/warning/danger state. Mirrors <badge-ui>'s `primary` variant token-for-token (`accent` retired, gh#874 — the capability survives renamed, sibling parity with badge preserved). */
68
+ variant: 'default' | 'info' | 'success' | 'warning' | 'danger' | 'primary';
63
69
 
64
70
  addEventListener(type: 'remove', listener: (ev: TagRemoveEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
65
71
  addEventListener<K extends keyof HTMLElementEventMap>(
@@ -46,31 +46,38 @@ props:
46
46
  type: string
47
47
  dynamic: true
48
48
  variant:
49
- description: Semantic variant — `default | accent | info | success | warning | danger`.
50
- `accent` is brand-primary emphasis for non-semantic labels — a filter chip
49
+ description: Semantic variant — `default | info | success | warning | danger | primary`.
50
+ `primary` is brand emphasis for non-semantic labels — a filter chip
51
51
  or taxonomy tag that shouldn't imply a success/warning/danger state. Mirrors
52
- <badge-ui>'s `accent` variant token-for-token.
52
+ <badge-ui>'s `primary` variant token-for-token (`accent` retired, gh#874 —
53
+ the capability survives renamed, sibling parity with badge preserved).
53
54
  type: string
54
55
  default: default
55
56
  enum:
56
57
  - default
57
- - accent
58
58
  - info
59
59
  - success
60
60
  - warning
61
61
  - danger
62
+ - primary
62
63
  tone:
63
64
  description: |
64
65
  Fill style — orthogonal to [variant]. Three values:
65
- - `solid` (default for EVERY variant, including the family-less
66
- `default`, since gh#794) saturated bg + on-strong (near-white)
67
- text. The chip IS the state. A bare `<tag-ui text="…">` renders
68
- this look with no attributes at all.
69
- - `muted` tinted bg with scheme-paired text. Matches <badge-ui>'s
70
- default look, and is the pre-gh#794 look for the family-less
71
- `default` variant. Use on metadata / citation chips in dense
72
- lists or tables where the saturated default would compete for
73
- attention (gh#778's oversized-pill concern).
66
+ - `muted` (default for the family variants info/success/warning/
67
+ danger/primary gh#864: tag and badge were swapped relative to
68
+ their reported default formulas; this restores tag's own)
69
+ tinted bg with scheme-paired text. Matches <badge-ui>'s
70
+ `[tone="muted"]` look. Use on metadata / citation chips in dense
71
+ lists or tables where a saturated fill would compete for
72
+ attention (gh#778's oversized-pill concern). Does NOT apply to
73
+ the family-less `default` variant, which stays solid
74
+ unconditionally (gh#794, unaffected by gh#864) — a bare
75
+ `<tag-ui text="…">` still renders that solid look with no
76
+ attributes at all.
77
+ - `solid` — saturated bg + on-strong (near-white) text. The chip
78
+ IS the state. Opt in explicitly for a family variant (gh#864),
79
+ or it's already the unconditional look for the family-less
80
+ `default` variant (gh#794).
74
81
  - `outline` — transparent bg + family-colored border + family-colored
75
82
  text. The lightest visual weight; good in dense data tables or
76
83
  faceted filter rows where multiple chips would otherwise compete.
@@ -78,7 +85,7 @@ props:
78
85
  prop — see tag.class.js) so its schema `default` here is documentation
79
86
  of the rendered-by-default look, not a reflected attribute value.
80
87
  type: string
81
- default: solid
88
+ default: muted
82
89
  enum:
83
90
  - solid
84
91
  - muted
@@ -132,12 +139,16 @@ a2ui:
132
139
  vertically; vertical lists of dismissable items are an
133
140
  <action-list-ui> use case, not <tag-ui>.
134
141
  - >-
135
- Bare <tag-ui text="…"> (no [tone]) renders the high-contrast
136
- solid pill by default (gh#794). In dense tables, tight metadata
137
- rows, or citation/footnote strips where many tags sit close
138
- together, set [tone="muted"] to keep the quieter tinted look
139
- instead — a wall of solid pills reads as visual noise there
142
+ A family-less <tag-ui text="…"> (no [variant], no [tone]) renders
143
+ the high-contrast solid pill by default (gh#794). In dense tables,
144
+ tight metadata rows, or citation/footnote strips where many tags
145
+ sit close together, set [tone="muted"] to keep the quieter tinted
146
+ look instead — a wall of solid pills reads as visual noise there
140
147
  (gh#778).
148
+ - >-
149
+ A family [variant] (info/success/warning/danger/primary) with no
150
+ [tone] renders the tinted-container look by default (gh#864). Set
151
+ [tone="solid"] for a saturated status stamp instead.
141
152
  anti_patterns: []
142
153
  examples:
143
154
  - name: basic-tag
@@ -40,7 +40,7 @@
40
40
  "default": ""
41
41
  },
42
42
  "variant": {
43
- "description": "Semantic variant — `default | info | success | warning | danger`. `primary` and `muted` are style hints; canonical \"neutral but interesting\" tone is `info`.",
43
+ "description": "Semantic variant — `default | info | success | warning | danger | primary`. `primary` is the brand-emphasis fill (mirrors badge-ui/button-ui's `primary`); canonical \"neutral but interesting\" tone is `info`.",
44
44
  "type": "string",
45
45
  "enum": [
46
46
  "default",
@@ -48,9 +48,7 @@
48
48
  "success",
49
49
  "warning",
50
50
  "danger",
51
- "primary",
52
- "muted",
53
- "neutral"
51
+ "primary"
54
52
  ],
55
53
  "default": "info"
56
54
  }
@@ -148,18 +148,6 @@ toast-ui[data-closing][position="top-left"] {
148
148
  --toast-border: var(--md-sys-color-primary-outline-variant);
149
149
  }
150
150
 
151
- /* `muted` and `neutral` are semantic aliases of the base canvas
152
- surface — same tokens as the unstyled `:scope`, declared explicitly
153
- so the yaml enum and the CSS contract agree. Use cases: quiet
154
- status nudges that shouldn't carry tonal weight (e.g. "Draft
155
- saved" without success-green). Mirrors alert-ui's pattern. */
156
- :where(:scope[variant="muted"]),
157
- :where(:scope[variant="neutral"]) {
158
- --toast-bg: var(--a-bg-subtle);
159
- --toast-fg: var(--md-sys-color-neutral-on-surface);
160
- --toast-border: var(--md-sys-color-neutral-outline-variant);
161
- }
162
-
163
151
  /* ── Message slot ── */
164
152
 
165
153
  [slot="message"] {
@@ -37,8 +37,9 @@ props:
37
37
  default: ""
38
38
  variant:
39
39
  description: >-
40
- Semantic variant — `default | info | success | warning | danger`.
41
- `primary` and `muted` are style hints; canonical "neutral but
40
+ Semantic variant — `default | info | success | warning | danger |
41
+ primary`. `primary` is the brand-emphasis fill (mirrors
42
+ badge-ui/button-ui's `primary`); canonical "neutral but
42
43
  interesting" tone is `info`.
43
44
  type: string
44
45
  default: info
@@ -49,8 +50,6 @@ props:
49
50
  - warning
50
51
  - danger
51
52
  - primary
52
- - muted
53
- - neutral
54
53
  events: {}
55
54
  usage: |-
56
55
  §224 (v0.5.9): `UIToast.show()` routes through `UIFeed.post()` — the
package/core/element.js CHANGED
@@ -176,12 +176,43 @@ export class UIElement extends HTMLElement {
176
176
  // compliant environments: the value already matches (the property
177
177
  // setter's Object.is check short-circuits), so this never causes an
178
178
  // extra render pass on browsers/environments that already got it right.
179
+ // The whole pre-sync loop runs UNTRACKED: connectedCallback commonly
180
+ // fires synchronously inside a PARENT's render effect (template-created
181
+ // children connect during stamp()), and the gh#961 branch below READS
182
+ // `this[key]` — a tracked read there subscribes the parent's effect to
183
+ // this child's signals, so any later child-prop write (a `.checked`
184
+ // template apply) re-dirties the parent, which re-stamps fresh
185
+ // children, whose own mount-reads re-subscribe: the unbounded
186
+ // parent↔child oscillation the signals drain-loop guard traps (the
187
+ // admin-settings/switch-ui/button-ui loop on gh#961). Same rationale
188
+ // as the untracked(connected()) wrapper below.
189
+ untracked(() => {
179
190
  for (const [key, cfg] of Object.entries(ctor.properties)) {
180
191
  const attr = cfg.attribute ?? key.toLowerCase();
181
192
  if (this.hasAttribute(attr)) {
182
193
  this[key] = parseAttr(this.getAttribute(attr), cfg.type ?? String);
194
+ } else if (cfg.reflect && cfg.type === Boolean && this[key] === true) {
195
+ // gh#961 — stamp the initial attribute for a TRUE-default reflected
196
+ // boolean. installProps seeds the signal with the default but can't
197
+ // touch attributes (constructor restriction), and the property
198
+ // setter's Object.is short-circuit means a later same-value set
199
+ // (e.g. chat-thread's #syncEmptyFromChildren recomputing `empty:
200
+ // true` on first connect) never reflects either — so CSS gates like
201
+ // `chat-thread:not([empty]) > chat-empty` and
202
+ // `editor-canvas:not([empty]) > editor-canvas-empty` read the
203
+ // wrong state forever. Deliberately scoped to Boolean-true ONLY:
204
+ // reflecting string/number defaults here stamps `text=""`-shaped
205
+ // attributes framework-wide, flipping `[attr]` CSS gates and
206
+ // cascading re-renders into the signals drain-loop guard (the
207
+ // full-reflect attempt documented on gh#961). A false default is
208
+ // already a DOM no-op (removeAttribute on an absent attribute).
209
+ // The setAttribute replays attributeChangedCallback → parseAttr →
210
+ // same value → Object.is short-circuit: no signal write, no
211
+ // render pass.
212
+ this.setAttribute(attr, '');
183
213
  }
184
214
  }
215
+ });
185
216
  // connected() commonly reads this element's reactive properties via
186
217
  // template-literal interpolation (`${this.label}` etc.) to stamp its
187
218
  // inner DOM. If the outer call path is inside another effect (e.g.
package/core/signals.js CHANGED
@@ -32,7 +32,9 @@ function flushSigs() {
32
32
  while ((q = pending)) {
33
33
  if (++loops > 100) {
34
34
  pending = null;
35
- console.error(`signals: drain loop exceeded 100 iterations`);
35
+ // Name the still-dirty effect hosts — a bare count is undiagnosable
36
+ // (gh#961's oscillation took host attribution to even locate).
37
+ console.error(`signals: drain loop exceeded 100 iterations`, JSON.stringify([...q].map(s => s.host || '?')));
36
38
  break;
37
39
  }
38
40
  pending = null;