@adia-ai/web-components 0.8.33 → 0.8.35

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 (118) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/components/agent-questions/agent-questions.class.js +0 -1
  3. package/components/agent-reasoning/agent-reasoning.a2ui.json +1 -1
  4. package/components/agent-reasoning/agent-reasoning.yaml +3 -3
  5. package/components/badge/badge.css +33 -7
  6. package/components/badge/badge.examples.md +4 -4
  7. package/components/button/button.css +12 -0
  8. package/components/card/card.css +44 -1
  9. package/components/check/check.a2ui.json +25 -11
  10. package/components/check/check.class.js +5 -0
  11. package/components/check/check.css +21 -0
  12. package/components/check/check.d.ts +2 -0
  13. package/components/check/check.examples.md +4 -4
  14. package/components/check/check.yaml +26 -7
  15. package/components/combobox/combobox.a2ui.json +1 -1
  16. package/components/combobox/combobox.yaml +3 -0
  17. package/components/command/command.a2ui.json +4 -0
  18. package/components/command/command.class.js +54 -4
  19. package/components/command/command.d.ts +2 -0
  20. package/components/command/command.yaml +25 -3
  21. package/components/date-range-picker/date-range-picker.a2ui.json +1 -1
  22. package/components/date-range-picker/date-range-picker.class.js +42 -43
  23. package/components/date-range-picker/date-range-picker.css +11 -0
  24. package/components/date-range-picker/date-range-picker.yaml +6 -3
  25. package/components/demo-toggle/demo-toggle.css +4 -0
  26. package/components/demo-toggle/demo-toggle.examples.md +4 -4
  27. package/components/description-list/description-list.css +16 -4
  28. package/components/feed/feed-item.a2ui.json +3 -2
  29. package/components/feed/feed-item.yaml +7 -1
  30. package/components/feed/feed.css +28 -5
  31. package/components/field/field.examples.md +4 -4
  32. package/components/image/image.class.js +8 -0
  33. package/components/modal/modal.class.js +43 -2
  34. package/components/modal/modal.css +23 -0
  35. package/components/nav-group/nav-group.css +27 -5
  36. package/components/nav-item/nav-item.css +11 -3
  37. package/components/option-card/option-card.a2ui.json +10 -10
  38. package/components/option-card/option-card.css +32 -16
  39. package/components/option-card/option-card.yaml +19 -15
  40. package/components/radio/radio.examples.md +4 -4
  41. package/components/range/range.css +8 -4
  42. package/components/richtext/richtext.css +5 -1
  43. package/components/segment/segment.css +10 -0
  44. package/components/slider/slider.a2ui.json +6 -3
  45. package/components/slider/slider.css +28 -12
  46. package/components/slider/slider.yaml +11 -3
  47. package/components/stat/stat.examples.md +4 -4
  48. package/components/step-progress/step-progress.css +16 -3
  49. package/components/swatch/swatch.css +14 -6
  50. package/components/switch/switch.examples.md +4 -4
  51. package/components/table/cell-types.js +8 -0
  52. package/components/table/table.a2ui.json +1 -1
  53. package/components/table/table.class.js +11 -0
  54. package/components/table/table.css +9 -0
  55. package/components/table/table.d.ts +1 -1
  56. package/components/table/table.yaml +1 -1
  57. package/components/table-toolbar/table-toolbar.css +10 -1
  58. package/components/tabs/tabs.css +13 -5
  59. package/components/tag/tag.class.js +14 -1
  60. package/components/tag/tag.css +70 -9
  61. package/components/text/text.examples.md +4 -4
  62. package/components/timeline/timeline-item.a2ui.json +2 -2
  63. package/components/timeline/timeline-item.yaml +2 -2
  64. package/components/timeline/timeline.css +33 -5
  65. package/components/timeline/timeline.d.ts +2 -2
  66. package/components/toast/toast.css +28 -5
  67. package/core/element.js +31 -0
  68. package/core/signals.js +3 -1
  69. package/custom-elements.json +469 -46
  70. package/dist/theme-provider.min.js +2 -2
  71. package/dist/web-components.min.css +1 -1
  72. package/dist/web-components.min.js +41 -41
  73. package/dist/web-components.sheet.js +1 -1
  74. package/package.json +1 -1
  75. package/patterns/access-requests/access-requests.examples.html +3 -3
  76. package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +3 -3
  77. package/patterns/admin-shell/admin-shell.examples.html +3 -4
  78. package/patterns/agent-cost/agent-cost.examples.html +3 -3
  79. package/patterns/agent-memory/agent-memory.examples.html +3 -3
  80. package/patterns/agent-prompt-library/agent-prompt-library.examples.html +3 -3
  81. package/patterns/agent-tool-call/agent-tool-call.examples.html +3 -3
  82. package/patterns/alert-dialog/alert-dialog.examples.html +3 -3
  83. package/patterns/app-nav/app-nav.examples.html +4 -4
  84. package/patterns/approvals/approvals.examples.html +3 -3
  85. package/patterns/audit-log/audit-log.examples.html +3 -3
  86. package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.html +19 -9
  87. package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.js +2 -2
  88. package/patterns/changelog-feed/changelog-feed.examples.html +3 -3
  89. package/patterns/chart-in-card/chart-in-card.examples.html +37 -3
  90. package/patterns/chart-in-card/chart-in-card.examples.js +12 -0
  91. package/patterns/chart-in-card/chart-in-card.html +6 -0
  92. package/patterns/chat-shell/chat-shell.examples.html +3 -4
  93. package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +3 -3
  94. package/patterns/conversion-funnel/conversion-funnel.examples.html +3 -3
  95. package/patterns/data-tables-inline-edit-and-tree/data-tables-inline-edit-and-tree.examples.html +3 -3
  96. package/patterns/diff-review/diff-review.examples.html +3 -3
  97. package/patterns/editor-shell/editor-shell.examples.html +3 -4
  98. package/patterns/export-flow/export-flow.examples.html +3 -3
  99. package/patterns/filter-bar/filter-bar.examples.html +3 -3
  100. package/patterns/form-drawer/form-drawer.examples.html +3 -3
  101. package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.examples.html +3 -3
  102. package/patterns/inline-dialog/inline-dialog.examples.html +3 -3
  103. package/patterns/kanban-board/kanban-board.examples.html +3 -3
  104. package/patterns/layout/layout.examples.html +4 -7
  105. package/patterns/marketing-engagement/marketing-engagement.examples.html +3 -3
  106. package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +3 -3
  107. package/patterns/permissions-matrix/permissions-matrix.examples.html +3 -3
  108. package/patterns/permissions-role-picker/permissions-role-picker.examples.html +3 -3
  109. package/patterns/permissions-sharing/permissions-sharing.examples.html +3 -3
  110. package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +3 -3
  111. package/patterns/record-detail-drawer/record-detail-drawer.examples.html +3 -3
  112. package/patterns/responsive-shell-sidebar/responsive-shell-sidebar.examples.html +135 -147
  113. package/patterns/retention-cohort/retention-cohort.examples.html +3 -3
  114. package/patterns/search-discovery/search-discovery.examples.html +3 -3
  115. package/patterns/system-banners/system-banners.examples.html +3 -3
  116. package/patterns/timezone-selector/timezone-selector.examples.html +3 -3
  117. package/patterns/wizard-drawer/wizard-drawer.examples.html +3 -3
  118. package/styles/colors/semantics/core.css +22 -2
@@ -4,8 +4,13 @@
4
4
  --tabs-border: transparent;
5
5
  --tabs-bg: transparent;
6
6
  --tabs-radius: var(--a-radius-md);
7
- --tabs-button-height: var(--a-size);
8
- --tabs-font-size: var(--a-ui-md);
7
+ /* One quarter-rem taller than the shared control height (operator
8
+ ruling 2026-08-12) — tab labels sit at --a-ui-lg since gh#965 and
9
+ needed the extra breathing room. */
10
+ --tabs-button-height: calc(var(--a-size) + 0.25rem);
11
+ /* One rung UP the --a-ui-* scale (gh#965): tab labels are primary
12
+ navigation, not control chrome. */
13
+ --tabs-font-size: var(--a-ui-lg);
9
14
  --tabs-font-family: var(--a-font-family-ui);
10
15
  /* Strip owns the button-to-button spacing — 4px base gap + one
11
16
  padding unit (8px) folded in, since tab-button carries no
@@ -13,9 +18,12 @@
13
18
  --tabs-gap: var(--a-space-3);
14
19
  --tabs-indicator-color: var(--md-sys-color-primary);
15
20
  --tabs-indicator-height: 2px;
16
- --tabs-fg: var(--md-sys-color-neutral-on-surface-variant);
17
- --tabs-fg-hover: var(--md-sys-color-neutral-on-surface);
18
- --tabs-fg-active: var(--a-fg-selected);
21
+ /* State fg follows nav-item-ui's convention (gh#968 ruling, gh#1088):
22
+ idle neutral-high · hover/selected the shared --a-ui-text-* aliases —
23
+ one color language across horizontal (tabs) and vertical (nav) selection. */
24
+ --tabs-fg: var(--md-sys-color-neutral-high);
25
+ --tabs-fg-hover: var(--a-ui-text-hover);
26
+ --tabs-fg-active: var(--a-ui-text-selected);
19
27
  --tabs-fg-disabled: var(--a-ui-text-disabled);
20
28
 
21
29
  /* ── Spacing ── */
@@ -57,7 +57,20 @@ export class UITag extends UIElement {
57
57
  static parts = {
58
58
  // button-ui, not a native <button> (gh issue 276) — the icon slots in
59
59
  // explicitly to keep its bold weight (the icon= prop stamps regular).
60
- dismiss: '<button-ui slot="dismiss" variant="ghost" size="sm" aria-label="Remove"><icon-ui slot="icon" name="x" weight="bold"></icon-ui></button-ui>',
60
+ // gh#1084: the icon carries its own explicit free-form `size` (routes
61
+ // through icon.class.js's inline-style path, --icon-size) rather than
62
+ // relying on tag.css's `[slot="dismiss"] { --a-icon-size: … }` token
63
+ // override — that CSS-custom-property override is DEAD on this button:
64
+ // the button also carries `size="sm"`, and the universal
65
+ // `[size="sm"] { --a-icon-size: 0.875rem; … }` rule (styles/api/
66
+ // sizing.css) lives in the `utilities` layer, which the cascade-layer
67
+ // order (`@layer reset, tokens, elements, components, utilities, …` —
68
+ // styles/index.css) ranks ABOVE the `components` layer tag.css's own
69
+ // rule lives in — so the universal rule always wins on this element
70
+ // regardless of specificity, and any --a-icon-size value tag.css sets
71
+ // there is inert. An icon-level `size="…"` attribute sets an inline
72
+ // style directly (icon.class.js), which outranks every layer.
73
+ dismiss: '<button-ui slot="dismiss" variant="ghost" size="sm" aria-label="Remove"><icon-ui slot="icon" name="x" weight="bold" size="0.75rem"></icon-ui></button-ui>',
61
74
  };
62
75
 
63
76
  static template = () => null;
@@ -28,9 +28,29 @@ tag-ui[removable]:not([disabled]):hover {
28
28
  --tag-border: transparent;
29
29
  --tag-radius: var(--a-radius-full);
30
30
  --tag-px: var(--a-space-1-5);
31
- --tag-py: var(--a-space-0-5);
31
+ /* gh#1084: block padding flattened toward 1px per tier — the
32
+ --tag-min-h floor (below) now owns the chip's vertical size, so
33
+ py no longer has to carry it. Was --a-space-0-5 (2px); the extra
34
+ space min-height used to need from padding is now supplied by
35
+ align-items:center centering the content inside the floored box. */
36
+ --tag-py: var(--a-space-px);
32
37
  --tag-gap: var(--a-space-1);
33
- --tag-font: var(--a-ui-sm);
38
+ /* gh#1084 — min-height floor, decoupled from padding (mirrors
39
+ tags-input-ui's --tags-input-min-h). Component-owned fixed rem
40
+ constants (not density-scaled, matching --tag-dismiss-size's own
41
+ precedent below) calibrated to the CURRENT rendered height of a
42
+ plain (non-removable) tag at each tier — measured 14/17/22px
43
+ sm/md/lg before this change — so the dominant, non-removable case
44
+ has zero visual regression. Removable tags (previously inflated
45
+ by the oversized dismiss button, see --tag-dismiss-size) now
46
+ settle at or above this same floor instead of exceeding it. */
47
+ --tag-min-h: 1.0625rem; /* 17px @ d=1 — md */
48
+ --tag-min-h-sm: 0.875rem; /* 14px @ d=1 */
49
+ --tag-min-h-lg: 1.375rem; /* 22px @ d=1 */
50
+ /* One rung down the --a-ui-* scale (gh#965): tags read as metadata
51
+ chrome, not body copy. Whole tier ladder shifts with it (sm=2xs,
52
+ lg=sm) so the tiers stay one rung apart. */
53
+ --tag-font: var(--a-ui-xs);
34
54
 
35
55
  /* ── Size tiers (gh#779) — md (above) is the pixel-identical default;
36
56
  [size="sm"|"lg"] below re-point the active tokens. Component-owned
@@ -43,12 +63,34 @@ tag-ui[removable]:not([disabled]):hover {
43
63
  same rung spacing the space/type scales already use. */
44
64
  --tag-px-sm: var(--a-space-1);
45
65
  --tag-px-lg: var(--a-space-2);
66
+ /* gh#1084: py flattened toward 1px/tier now that --tag-min-h floors
67
+ the box — was --a-space-px/--a-space-1 (1px/4px), a 4x spread;
68
+ the height job moved to the floor, so the ladder can compress. */
46
69
  --tag-py-sm: var(--a-space-px);
47
- --tag-py-lg: var(--a-space-1);
48
- --tag-font-sm: var(--a-ui-xs);
49
- --tag-font-lg: var(--a-ui-md);
70
+ --tag-py-lg: var(--a-space-0-5);
71
+ --tag-font-sm: var(--a-ui-2xs);
72
+ --tag-font-lg: var(--a-ui-sm);
50
73
  --tag-font-family: var(--a-font-family-ui);
51
- --tag-dismiss-size: 1rem;
74
+ /* gh#1084: was 1rem (16px). NOTE: --button-height (button.css) is a
75
+ MIN-height on the dismiss <button-ui>, but an icon-only button
76
+ also gets an explicit fixed `width: var(--button-height)`
77
+ ([data-icon-only], button.css) — so shrinking this token alone
78
+ (leaving the glyph at its old 14px) would be a no-op on height
79
+ (border(2px, top+bottom) + icon(14px) = 16px content already
80
+ exceeded the old 16px floor exactly, so content — not the
81
+ floor — was driving height) and would CLIP the width (a smaller
82
+ fixed width vs. the still-16px icon+border content). The real
83
+ fix is shrinking the glyph itself — see tag.class.js's dismiss
84
+ template, which now gives the icon its own `size="0.75rem"`
85
+ (12px) instead of leaning on a `--a-icon-size` CSS override here
86
+ (that override is dead — see the [slot="dismiss"] rule below) —
87
+ and re-deriving this token to match: new icon (12px) + the
88
+ button's own 1px top/bottom border (2px) = 14px, so width and
89
+ the min-height floor both land exactly on the new content size —
90
+ no floor/content mismatch, no clipping. Net: the dismiss button
91
+ (and its glyph) really is ~2px smaller in both dimensions, at
92
+ every [size] tier (this token isn't tiered). */
93
+ --tag-dismiss-size: 0.875rem;
52
94
  --tag-bg-hover: var(--a-bg-hover);
53
95
  --tag-bg-disabled: var(--a-ui-bg-disabled);
54
96
  --tag-fg-disabled: var(--a-ui-text-disabled);
@@ -98,6 +140,11 @@ tag-ui[removable]:not([disabled]):hover {
98
140
  label still centers instead of reading left-aligned in an oversized
99
141
  pill. */
100
142
  width: fit-content;
143
+ /* gh#1084: floor, not a fixed size — mirrors tags-input-ui's own
144
+ min-height (tags-input.css). Decouples the chip's vertical size
145
+ from --tag-py so padding can stay minimal; content taller than
146
+ the floor (e.g. a slotted leading icon) still grows the box. */
147
+ min-height: var(--tag-min-h);
101
148
  justify-content: center;
102
149
  gap: var(--tag-gap);
103
150
  padding: var(--tag-py) var(--tag-px);
@@ -288,16 +335,19 @@ tag-ui[removable]:not([disabled]):hover {
288
335
  --tag-px: var(--tag-px-sm);
289
336
  --tag-py: var(--tag-py-sm);
290
337
  --tag-font: var(--tag-font-sm);
338
+ --tag-min-h: var(--tag-min-h-sm);
291
339
  }
292
340
  :scope[size="md"] {
293
341
  --tag-px: var(--a-space-1-5);
294
- --tag-py: var(--a-space-0-5);
295
- --tag-font: var(--a-ui-sm);
342
+ --tag-py: var(--a-space-px);
343
+ --tag-font: var(--a-ui-xs);
344
+ --tag-min-h: 1.0625rem; /* 17px @ d=1 — matches the :where(:scope) default */
296
345
  }
297
346
  :scope[size="lg"] {
298
347
  --tag-px: var(--tag-px-lg);
299
348
  --tag-py: var(--tag-py-lg);
300
349
  --tag-font: var(--tag-font-lg);
350
+ --tag-min-h: var(--tag-min-h-lg);
301
351
  }
302
352
 
303
353
  /* hover rule moved outside @scope — see Safari 17.x bug note at top. */
@@ -342,7 +392,18 @@ tag-ui[removable]:not([disabled]):hover {
342
392
  --button-fg: var(--tag-dismiss-fg);
343
393
  --button-bg-ghost-hover: var(--tag-dismiss-bg-hover);
344
394
  --button-fg-ghost-hover: var(--tag-dismiss-fg);
345
- --a-icon-size: 0.875rem;
395
+ /* gh#1084: NO `--a-icon-size` override here — it doesn't work. This
396
+ button also carries `size="sm"` (tag.class.js), and the universal
397
+ `[size="sm"] { --a-icon-size: 0.875rem; }` rule (styles/api/
398
+ sizing.css) lives in the `utilities` cascade layer, which
399
+ `@layer reset, tokens, elements, components, utilities, …`
400
+ (styles/index.css) ranks ABOVE `components` — so it always wins
401
+ over anything this file sets on the same element, regardless of
402
+ selector specificity. The icon's rendered size is set directly
403
+ on the icon itself instead (tag.class.js's dismiss template gives
404
+ `<icon-ui size="0.75rem">` — a free-form size, which icon.class.js
405
+ applies as an inline style, outranking every layer). See
406
+ --tag-dismiss-size above — it's kept in sync with that 0.75rem. */
346
407
 
347
408
  opacity: var(--tag-dismiss-opacity);
348
409
  order: 1; /* push dismiss to end so layout reads [text] [×] */
@@ -11,8 +11,8 @@
11
11
  ```html
12
12
  <row-ui gap="6" align="start" wrap>
13
13
  <col-ui gap="2">
14
- <text-ui variant="caption" color="muted">verse</text-ui>
15
- <div verse style="max-width:240px">
14
+ <text-ui variant="caption" color="muted">scale="ui-sm"</text-ui>
15
+ <div scale="ui-sm" style="max-width:240px">
16
16
  <col-ui gap="2">
17
17
  <text-ui variant="heading">Heading</text-ui>
18
18
  <text-ui variant="body">Body copy showing how this register sizes running text and the leading between lines.</text-ui>
@@ -31,8 +31,8 @@
31
31
  </div>
32
32
  </col-ui>
33
33
  <col-ui gap="2">
34
- <text-ui variant="caption" color="muted">prose</text-ui>
35
- <div prose style="max-width:240px">
34
+ <text-ui variant="caption" color="muted">scale="content-md"</text-ui>
35
+ <div scale="content-md" style="max-width:240px">
36
36
  <col-ui gap="2">
37
37
  <text-ui variant="heading">Heading</text-ui>
38
38
  <text-ui variant="body">Body copy showing how this register sizes running text and the leading between lines.</text-ui>
@@ -29,12 +29,12 @@
29
29
  "type": "string"
30
30
  },
31
31
  "spinner": {
32
- "description": "Show a spinner instead of the icon while status=pending.",
32
+ "description": "Show a spinner instead of the dot/icon while status=\"active\".",
33
33
  "type": "boolean",
34
34
  "default": false
35
35
  },
36
36
  "status": {
37
- "description": "Lifecycle state (idle | pending | done | failed).",
37
+ "description": "Lifecycle state (idle | active | completed | error). Pairs with `spinner` on \"active\" for an in-progress step.",
38
38
  "type": "string",
39
39
  "default": "idle"
40
40
  },
@@ -39,11 +39,11 @@ props:
39
39
  type: string
40
40
  default: 'default'
41
41
  status:
42
- description: Lifecycle state (idle | pending | done | failed).
42
+ description: Lifecycle state (idle | active | completed | error). Pairs with `spinner` on "active" for an in-progress step.
43
43
  type: string
44
44
  default: 'idle'
45
45
  spinner:
46
- description: Show a spinner instead of the icon while status=pending.
46
+ description: Show a spinner instead of the dot/icon while status="active".
47
47
  type: boolean
48
48
  default: false
49
49
 
@@ -176,6 +176,17 @@ agent-reasoning-ui timeline-ui:not([orientation="horizontal"]),
176
176
  --timeline-item-toggle-px: var(--a-space-0-5);
177
177
  --timeline-item-toggle-time-margin: 1.5rem;
178
178
  --timeline-item-outcome-indent: 0.75rem;
179
+
180
+ /* Row-1's rendered height — [slot="label"]'s own line-height (1.4,
181
+ set at var(--timeline-item-label-size), timeline.css below). Marker
182
+ centering (dot/line/icon/caret) must measure against THIS, not a
183
+ bare `1.4em`: `em` on the marker's own box resolves against
184
+ timeline-item-ui's ambient/inherited font-size, which is NOT
185
+ guaranteed to equal --timeline-item-label-size (it doesn't, by
186
+ default — nothing sets font-size on timeline-item-ui itself), so a
187
+ literal `1.4em` silently drifted off the label's real line-height
188
+ and threw every marker's vertical center off by that gap (gh#1090). */
189
+ --timeline-item-row-h: calc(var(--timeline-item-label-size) * 1.4);
179
190
  }
180
191
 
181
192
  :scope {
@@ -200,7 +211,7 @@ agent-reasoning-ui timeline-ui:not([orientation="horizontal"]),
200
211
  content: '';
201
212
  position: absolute;
202
213
  left: calc((var(--timeline-item-marker-w) - var(--timeline-item-dot-size)) / 2);
203
- top: calc((1.4em - var(--timeline-item-dot-size)) / 2 + 1px);
214
+ top: calc((var(--timeline-item-row-h) - var(--timeline-item-dot-size)) / 2 + 1px);
204
215
  width: var(--timeline-item-dot-size);
205
216
  height: var(--timeline-item-dot-size);
206
217
  border-radius: 50%;
@@ -235,7 +246,7 @@ agent-reasoning-ui timeline-ui:not([orientation="horizontal"]),
235
246
  content: '';
236
247
  position: absolute;
237
248
  left: calc((var(--timeline-item-marker-w) - var(--timeline-item-line-size)) / 2);
238
- top: calc((1.4em + var(--timeline-item-dot-size)) / 2 + 2px);
249
+ top: calc((var(--timeline-item-row-h) + var(--timeline-item-dot-size)) / 2 + 2px);
239
250
  bottom: calc(-1 * var(--timeline-item-gap-row));
240
251
  width: var(--timeline-item-line-size);
241
252
  background: var(--timeline-item-line-fg);
@@ -252,8 +263,17 @@ agent-reasoning-ui timeline-ui:not([orientation="horizontal"]),
252
263
  grid-column: marker;
253
264
  grid-row: 1;
254
265
  justify-self: center;
255
- align-self: baseline;
256
- margin-top: calc((1.4em - var(--timeline-item-icon-size)) / 2);
266
+ /* `baseline` anchors a baseline-less box (icon-ui has no text) by its
267
+ synthesized baseline — the bottom margin edge (CSS Box Alignment)
268
+ to the row's shared text baseline. That pins the icon's BOTTOM to
269
+ near the bottom of the label's glyphs and makes the margin-top
270
+ centering below a no-op (the alignment step repositions the box
271
+ after margin is applied, so no margin-top value moves the rendered
272
+ circle). `start` anchors the row's block-start instead, so
273
+ margin-top's `(row-h - icon-size)/2` genuinely centers the icon
274
+ against the label's line-height (gh#1090). */
275
+ align-self: start;
276
+ margin-top: calc((var(--timeline-item-row-h) - var(--timeline-item-icon-size)) / 2);
257
277
  width: var(--timeline-item-icon-size);
258
278
  height: var(--timeline-item-icon-size);
259
279
  border-radius: 50%;
@@ -278,7 +298,7 @@ agent-reasoning-ui timeline-ui:not([orientation="horizontal"]),
278
298
 
279
299
  /* Push connector line past the icon circle */
280
300
  :scope:has([slot="icon"])::after {
281
- top: calc((1.4em + var(--timeline-item-icon-size)) / 2 + 2px);
301
+ top: calc((var(--timeline-item-row-h) + var(--timeline-item-icon-size)) / 2 + 2px);
282
302
  }
283
303
 
284
304
  /* ═══════ Content slots ═══════ */
@@ -400,6 +420,14 @@ timeline-ui[size="sm"] > timeline-item-ui[status="active"][spinner]::before {
400
420
  timeline-ui[orientation="horizontal"] > timeline-item-ui {
401
421
  display: flex;
402
422
  flex-direction: column;
423
+ /* Reset the vertical-mode grid's `align-items: baseline` (timeline.css
424
+ above) — baseline is incompatible with cross-axis stretch, so without
425
+ this reset [slot="label"]/[slot="description"]/[slot="time"] (plain
426
+ <span>s) shrink-to-fit their own text instead of spanning the item's
427
+ full width, leaving `text-align: center` below with no extra width to
428
+ center within (labels rendered flush against the item's left edge —
429
+ gh#1090). */
430
+ align-items: stretch;
403
431
  flex: 1 1 0;
404
432
  min-width: 0;
405
433
  padding-top: calc(var(--timeline-dot-size) + var(--timeline-gap-col));
@@ -40,9 +40,9 @@ export class UITimelineItem extends UIElement {
40
40
  duration: string;
41
41
  /** Optional leading icon name (Phosphor). */
42
42
  icon: string;
43
- /** Show a spinner instead of the icon while status=pending. */
43
+ /** Show a spinner instead of the dot/icon while status="active". */
44
44
  spinner: boolean;
45
- /** Lifecycle state (idle | pending | done | failed). */
45
+ /** Lifecycle state (idle | active | completed | error). Pairs with `spinner` on "active" for an in-progress step. */
46
46
  status: string;
47
47
  /** Primary step label. */
48
48
  text: string;
@@ -2,6 +2,17 @@
2
2
  TOAST-N — Notification popup with auto-dismiss.
3
3
  ═══════════════════════════════════════════════════════════════ */
4
4
 
5
+ /* DEAD-CODE NOTICE (gh#1104, ties to Phase 4 of
6
+ .claude/docs/specs/feed-channel.md / SPEC-FEED-CHANNEL-001):
7
+ <toast-ui> is a self-removing facade — toast.class.js's
8
+ `connected()` posts into <feed-ui>/UIFeed then `queueMicrotask`s
9
+ `this.remove()`, so this element never survives to a paint. Every
10
+ rule below (including the `primary` variant) is unreachable at
11
+ runtime. The real paint site is feed.css's `@scope (feed-item-ui)`
12
+ block — edit THAT file to change what a posted toast looks like.
13
+ Kept here (not deleted) as the documented visual contract + the
14
+ reference the feed-item-ui `primary` variant was ported from. */
15
+
5
16
  /* Safari 17.x bug: `:scope[data-open]` and `:scope[data-closing]`
6
17
  (Flavor B — attribute-removal restyle) don't reliably restyle on
7
18
  attribute toggling inside `@scope`. Selectors moved out as plain
@@ -73,6 +84,10 @@ toast-ui[data-closing][position="top-left"] {
73
84
  box-shadow: var(--toast-shadow);
74
85
  opacity: 0;
75
86
  pointer-events: auto;
87
+ /* Elevated-surface separation over busy page content (gh#1091) —
88
+ mirrors loading-overlay.css's [variant="blur"] treatment. */
89
+ backdrop-filter: blur(4px);
90
+ -webkit-backdrop-filter: blur(4px);
76
91
  }
77
92
 
78
93
  /* ── Position variants ── */
@@ -112,26 +127,34 @@ toast-ui[data-closing][position="top-left"] {
112
127
  light-dark scheme-aware token), NOT `--a-{variant}-text-strong`
113
128
  (the latter is locked dark for warning + light for the others —
114
129
  designed for solid `--variant-strong` fills, where the bg never
115
- flips). Mirrors alert.css's variant wiring. */
130
+ flips). Mirrors alert.css's variant wiring.
131
+
132
+ `--toast-bg` rides `-container-high` (NOT the plain `-container`
133
+ alert.css uses) — a floating overlay over arbitrary page content
134
+ needs a stronger tonal step than an inline, already-boxed alert to
135
+ read as separated (gh#1091). `-container-high` is a real generated
136
+ role per family in colors/material-static.css (the same tier
137
+ `--a-{family}-subtle` aliases to per component-token-contract.md's
138
+ `-strong` semantics note) — not a hand-authored addition. */
116
139
 
117
140
  :where(:scope),
118
141
  :where(:scope[variant="info"]) {
119
- --toast-bg: var(--md-sys-color-info-container);
142
+ --toast-bg: var(--md-sys-color-info-container-high);
120
143
  --toast-fg: var(--md-sys-color-info-on-surface);
121
144
  --toast-border: var(--md-sys-color-info-outline-variant);
122
145
  }
123
146
  :where(:scope[variant="success"]) {
124
- --toast-bg: var(--md-sys-color-success-container);
147
+ --toast-bg: var(--md-sys-color-success-container-high);
125
148
  --toast-fg: var(--md-sys-color-success-on-surface);
126
149
  --toast-border: var(--md-sys-color-success-outline-variant);
127
150
  }
128
151
  :where(:scope[variant="warning"]) {
129
- --toast-bg: var(--md-sys-color-warning-container);
152
+ --toast-bg: var(--md-sys-color-warning-container-high);
130
153
  --toast-fg: var(--md-sys-color-warning-on-surface);
131
154
  --toast-border: var(--md-sys-color-warning-outline-variant);
132
155
  }
133
156
  :where(:scope[variant="danger"]) {
134
- --toast-bg: var(--md-sys-color-danger-container);
157
+ --toast-bg: var(--md-sys-color-danger-container-high);
135
158
  --toast-fg: var(--md-sys-color-danger-on-surface);
136
159
  --toast-border: var(--md-sys-color-danger-outline-variant);
137
160
  }
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;