@adia-ai/web-components 0.8.22 → 0.8.24

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 (106) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/MIGRATION.md +9 -0
  3. package/components/agent-questions/agent-questions.yaml +0 -2
  4. package/components/agent-trace/agent-trace.js +4 -1
  5. package/components/alert/alert.a2ui.json +1 -1
  6. package/components/alert/alert.class.js +5 -0
  7. package/components/alert/alert.css +44 -5
  8. package/components/alert/alert.yaml +10 -4
  9. package/components/calendar-grid/calendar-grid.yaml +3 -0
  10. package/components/canvas/canvas.js +4 -1
  11. package/components/card/card.css +12 -0
  12. package/components/chat-thread/chat-input.js +4 -1
  13. package/components/code/code.css +29 -26
  14. package/components/combobox/combobox.class.js +1 -1
  15. package/components/combobox/combobox.yaml +0 -1
  16. package/components/date-range-picker/date-range-picker.class.js +1 -1
  17. package/components/date-range-picker/date-range-picker.yaml +0 -1
  18. package/components/datetime-picker/datetime-picker.class.js +1 -1
  19. package/components/datetime-picker/datetime-picker.yaml +0 -1
  20. package/components/description-list/description-list.css +9 -0
  21. package/components/drawer/drawer.class.js +5 -0
  22. package/components/drawer/drawer.css +16 -0
  23. package/components/drawer/drawer.yaml +2 -0
  24. package/components/feed/feed.class.js +5 -0
  25. package/components/feed/feed.yaml +2 -0
  26. package/components/field/field.css +4 -1
  27. package/components/input/input.class.js +5 -0
  28. package/components/input/input.yaml +3 -0
  29. package/components/list/list.css +25 -5
  30. package/components/modal/modal.class.js +5 -0
  31. package/components/modal/modal.yaml +2 -0
  32. package/components/nav-group/nav-group.class.js +50 -2
  33. package/components/nav-group/nav-group.css +11 -2
  34. package/components/nav-group/nav-group.yaml +2 -0
  35. package/components/pagination/pagination.yaml +3 -0
  36. package/components/progress-row/progress-row.class.js +10 -2
  37. package/components/richtext/richtext.css +7 -0
  38. package/components/select/select.a2ui.json +5 -0
  39. package/components/select/select.class.js +27 -8
  40. package/components/select/select.yaml +13 -1
  41. package/components/stat/stat.js +4 -1
  42. package/components/stepper/stepper.css +25 -20
  43. package/components/swiper/swiper.class.js +5 -0
  44. package/components/swiper/swiper.yaml +3 -0
  45. package/components/table/table.a2ui.json +15 -1
  46. package/components/table/table.class.js +14 -1
  47. package/components/table/table.css +68 -3
  48. package/components/table/table.d.ts +5 -1
  49. package/components/table/table.yaml +34 -1
  50. package/components/tabs/tab.js +4 -1
  51. package/components/tag/tag.css +15 -9
  52. package/components/toolbar/toolbar.class.js +20 -0
  53. package/components/toolbar/toolbar.yaml +2 -0
  54. package/core/provider.js +5 -1
  55. package/dist/host.min.css +1 -1
  56. package/dist/host.sheet.js +1 -1
  57. package/dist/scale.min.css +1 -1
  58. package/dist/scale.sheet.js +1 -1
  59. package/dist/theme-provider.min.js +2 -2
  60. package/dist/web-components.min.css +1 -1
  61. package/dist/web-components.min.js +93 -93
  62. package/dist/web-components.sheet.js +1 -1
  63. package/package.json +1 -1
  64. package/patterns/access-requests/access-requests.examples.html +12 -3
  65. package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +23 -8
  66. package/patterns/agent-cost/agent-cost.examples.html +12 -3
  67. package/patterns/agent-memory/agent-memory.examples.html +12 -3
  68. package/patterns/agent-prompt-library/agent-prompt-library.examples.html +12 -3
  69. package/patterns/agent-tool-call/agent-tool-call.examples.html +16 -4
  70. package/patterns/alert-dialog/alert-dialog.examples.html +12 -3
  71. package/patterns/app-nav/app-nav.examples.html +36 -9
  72. package/patterns/approvals/approvals.examples.html +14 -10
  73. package/patterns/audit-log/audit-log.examples.html +5 -2
  74. package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.html +210 -32
  75. package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.js +83 -4
  76. package/patterns/bulk-action-toolbar/bulk-action-toolbar.html +68 -0
  77. package/patterns/changelog-feed/changelog-feed.examples.html +7 -4
  78. package/patterns/chart-in-card/chart-in-card.examples.html +28 -7
  79. package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +24 -9
  80. package/patterns/conversion-funnel/conversion-funnel.examples.html +24 -15
  81. package/patterns/data-tables-inline-edit-and-tree/data-tables-inline-edit-and-tree.examples.html +17 -8
  82. package/patterns/diff-review/diff-review.examples.html +12 -3
  83. package/patterns/export-flow/export-flow.examples.html +12 -3
  84. package/patterns/filter-bar/filter-bar.examples.html +13 -4
  85. package/patterns/form-drawer/form-drawer.examples.html +15 -6
  86. package/patterns/form-system/form-system.examples.html +4 -1
  87. package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.examples.html +12 -3
  88. package/patterns/inline-dialog/inline-dialog.examples.html +12 -3
  89. package/patterns/kanban-board/kanban-board.examples.html +4 -1
  90. package/patterns/marketing-engagement/marketing-engagement.examples.html +32 -14
  91. package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +13 -4
  92. package/patterns/permissions-matrix/permissions-matrix.examples.html +9 -3
  93. package/patterns/permissions-role-picker/permissions-role-picker.examples.html +8 -2
  94. package/patterns/permissions-sharing/permissions-sharing.examples.html +12 -3
  95. package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +18 -6
  96. package/patterns/record-detail-drawer/record-detail-drawer.examples.html +10 -4
  97. package/patterns/retention-cohort/retention-cohort.examples.html +15 -6
  98. package/patterns/search-discovery/search-discovery.examples.html +16 -4
  99. package/patterns/system-banners/system-banners.examples.html +54 -48
  100. package/patterns/timezone-selector/timezone-selector.examples.html +8 -2
  101. package/patterns/timezone-selector/timezone-selector.examples.js +3 -1
  102. package/patterns/wizard-drawer/wizard-drawer.examples.html +25 -23
  103. package/patterns/wizard-drawer/wizard-drawer.examples.js +12 -1
  104. package/styles/foundation/radius.css +2 -2
  105. package/styles/scale.css +2 -2
  106. package/styles/type/scale.css +6 -6
@@ -13,10 +13,26 @@
13
13
  --table-fg-hover: var(--a-fg-strong);
14
14
  --table-fg-secondary: var(--a-fg-muted);
15
15
  --table-border: var(--md-sys-color-neutral-outline-variant);
16
+ /* Perimeter split off the divider token (gh#511): --table-border feeds
17
+ BOTH the outer frame and the row dividers (TKT-0026), so consumers
18
+ could never drop the frame without erasing dividers. Inside card-ui
19
+ set --table-perimeter: transparent (or use [frameless]) — the card
20
+ owns the frame; standalone tables keep their chrome by default. */
21
+ --table-perimeter: var(--table-border);
16
22
  --table-border-strong: var(--md-sys-color-neutral-outline);
17
23
  --table-py: var(--a-space-3);
18
24
  --table-px: var(--a-space-4);
19
25
 
26
+ /* Body-cell vertical alignment mode (gh#505). 'center' is the default,
27
+ unconditional behavior every consumer already ships against — this
28
+ token flips to flex-start when [valign="top"] (below). Uniform
29
+ padding (--table-py) IS the first-line alignment story: every body
30
+ cell's content box starts flush at the same y-offset, so a chip in
31
+ one column and a wrapped title's first line in another share one
32
+ scan anchor, rather than each cell floating at its own vertical
33
+ center on a tall row. */
34
+ --table-valign: center;
35
+
20
36
  --table-row-bg: transparent;
21
37
  /* container-low, not the generic --a-bg-hover state-layer token
22
38
  (TKT-0020) — --a-bg-hover / --md-sys-color-neutral-hover is tuned
@@ -130,7 +146,15 @@
130
146
  inside the content box and doesn't shrink clientWidth — a real
131
147
  `border: 1px` made grid content overflow by exactly 2 px on every
132
148
  table (scrollbar appeared with nothing to scroll). */
133
- box-shadow: inset 0 0 0 1px var(--table-border);
149
+ /* Three-sided perimeter no TOP edge (gh#537, operator ruling
150
+ 2026-07-30): the header row's own border-bottom is the table's top
151
+ structure; a line above the header reads as a stray divider under
152
+ a card edge or empty-state. Sides + bottom keep the standalone
153
+ silhouette; [frameless] still blanks all of it. */
154
+ box-shadow:
155
+ inset 1px 0 0 var(--table-perimeter),
156
+ inset -1px 0 0 var(--table-perimeter),
157
+ inset 0 -1px 0 var(--table-perimeter);
134
158
  border-radius: var(--table-radius);
135
159
  position: relative;
136
160
  /* Own stacking context — sticky headers, pinned columns, and filter
@@ -176,6 +200,9 @@
176
200
 
177
201
  /* ═══════ Column headers ═══════ */
178
202
 
203
+ /* align-items:center is hardcoded, NOT --table-valign (gh#505) — header
204
+ cells are single-line by contract regardless of [valign] on the host
205
+ or any column. Do not swap this to the var. */
179
206
  [role="columnheader"] {
180
207
  box-sizing: border-box;
181
208
  text-align: left;
@@ -222,7 +249,9 @@
222
249
  color: var(--table-sort-fg);
223
250
  margin-inline-start: var(--table-sort-icon-gap);
224
251
  vertical-align: middle;
225
- --a-icon-size: 0.75em;
252
+ /* 0.95em ≈ +2px over the former 0.75em at the heading's --a-ui-xs size
253
+ (gh#496) — em-based so the caret keeps scaling with the register. */
254
+ --a-icon-size: 0.95em;
226
255
  }
227
256
 
228
257
  [aria-sort] [data-sort-icon] {
@@ -247,7 +276,7 @@
247
276
  [data-body] [role="gridcell"] {
248
277
  box-sizing: border-box;
249
278
  display: flex;
250
- align-items: center;
279
+ align-items: var(--table-valign);
251
280
  align-self: stretch;
252
281
  padding: var(--table-py) var(--table-px);
253
282
  min-width: 0;
@@ -331,6 +360,42 @@
331
360
  justify-content: center;
332
361
  }
333
362
 
363
+ /* ═══════ Vertical alignment (gh#505) ═══════ */
364
+
365
+ :scope[valign="top"] {
366
+ --table-valign: flex-start;
367
+ }
368
+
369
+ /* ═══════ Frameless (gh#511) ═══════
370
+ Drops ONLY the perimeter faux-border; row dividers keep reading
371
+ --table-border. For card-composed tables, where the card owns the
372
+ frame and the table's square perimeter otherwise double-borders. */
373
+ :scope[frameless] {
374
+ --table-perimeter: transparent;
375
+ }
376
+
377
+ /* Per-column override — beats the host var regardless of [valign], in
378
+ either direction (top column in a center table, or vice versa).
379
+ Higher specificity than the base `[data-body] [role="gridcell"]`
380
+ rule above (:249) so it wins without relying on source order. */
381
+ [data-body] [role="gridcell"][data-valign="top"] {
382
+ align-items: flex-start;
383
+ }
384
+
385
+ [data-body] [role="gridcell"][data-valign="center"] {
386
+ align-items: center;
387
+ }
388
+
389
+ /* Structural columns (checkbox / expand-toggle) are never part of
390
+ columns[] so authors can't set [data-valign] on them — they stay
391
+ centered under [valign="top"] by default (gh#505's checkbox example),
392
+ matching the icon/control's own fixed small size regardless of row
393
+ height. */
394
+ [data-body] [role="gridcell"][data-check-col],
395
+ [data-body] [role="gridcell"][data-expand-col] {
396
+ align-items: center;
397
+ }
398
+
334
399
  /* ═══════ Row states ═══════ */
335
400
 
336
401
  [data-body] > [role="row"] {
@@ -86,7 +86,7 @@ export interface TableSortEventDetail {
86
86
  export type TableSortEvent = CustomEvent<TableSortEventDetail>;
87
87
 
88
88
  export class UITable extends UIElement {
89
- /** Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children. */
89
+ /** Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign="top">). */
90
90
  columns: string;
91
91
  /** JS property (set programmatically — `el.data = [...]`) — row records, an array of plain objects keyed to columns[].key. May also be supplied declaratively as a JSON-array `data="[…]"` attribute, hydrated once at connect (gh#288) — the same pattern chart-ui's `data` attribute uses. Custom accessor on the element class, not a reflected attribute. */
92
92
  data: string;
@@ -94,6 +94,8 @@ export class UITable extends UIElement {
94
94
  density: 'compact' | 'standard' | 'comfortable';
95
95
  /** Enable row expansion */
96
96
  expandable: boolean;
97
+ /** Drop the table's outer 1px perimeter and nothing else (gh#511) — row dividers are untouched (the perimeter is split onto its own --table-perimeter token, defaulting to --table-border). For card-composed tables: the card owns the frame, so the table's square perimeter otherwise renders a doubled edge (flush bleed) or a stray inner box (with content above). Standalone tables keep their chrome by default. Theming path without the attribute: set --table-perimeter to transparent per instance. */
98
+ frameless: boolean;
97
99
  /** Renders N ghost skeleton rows in place of the body data (count derived from `paginate` if set, else 5). Header + columns stay intact so the table layout is preserved while data fetches. Sets aria-busy="true" on the host. Data updates are deferred until loading is set back to false. */
98
100
  loading: boolean;
99
101
  /** Rows per page. 0 = show all rows without pagination. When > 0, renders a pagination bar below the table. */
@@ -108,6 +110,8 @@ export class UITable extends UIElement {
108
110
  sortable: boolean;
109
111
  /** Alternate row background colors for visual scanning. */
110
112
  striped: boolean;
113
+ /** Vertical alignment of body-cell content. 'center' (default) keeps today's behavior — every cell centers on the row's cross-axis, which reads well for single-line rows. 'top' aligns each cell's content to the top of its padding box instead, so tall/wrapped rows (a [wrap] title column, a stacked identity cell) share one first-line scan anchor across the row rather than each cell floating at a different vertical center (gh#505). Per-column `valign` in the columns[] array (or <col-def valign="…">) overrides the host per-column — e.g. keep a checkbox column centered while data cells top-align. Header cells always stay centered regardless of this prop (single-line by contract) — the checkbox/expand structural columns also default to centered under [valign="top"] since they're not part of columns[]. */
114
+ valign: 'top' | 'center';
111
115
  /** Allow body-cell content to wrap onto multiple lines. Default is single-line with ellipsis truncation (matches the row convention shared by <select-ui> + <nav-item-ui>); long unbreakable strings clip gracefully rather than rewrapping the row. With [wrap], row height auto-grows to fit wrapped content. For surgical opt-in on a single column, set [data-wrap] on the cell / col-def instead. */
112
116
  wrap: boolean;
113
117
 
@@ -20,7 +20,7 @@ composes:
20
20
  - badge-ui
21
21
  props:
22
22
  columns:
23
- description: Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children.
23
+ description: Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign="top">).
24
24
  type: array
25
25
  default: []
26
26
  dynamic: true # JS-set collection prop with a custom setter — deliberately not in static properties
@@ -98,6 +98,39 @@ props:
98
98
  description: Alternate row background colors for visual scanning.
99
99
  type: boolean
100
100
  default: false
101
+ frameless:
102
+ description: >-
103
+ Drop the table's outer 1px perimeter and nothing else (gh#511) —
104
+ row dividers are untouched (the perimeter is split onto its own
105
+ --table-perimeter token, defaulting to --table-border). For
106
+ card-composed tables: the card owns the frame, so the table's
107
+ square perimeter otherwise renders a doubled edge (flush bleed) or
108
+ a stray inner box (with content above). Standalone tables keep
109
+ their chrome by default. Theming path without the attribute:
110
+ set --table-perimeter to transparent per instance.
111
+ type: boolean
112
+ default: false
113
+ reflect: true
114
+ valign:
115
+ description: >-
116
+ Vertical alignment of body-cell content. 'center' (default) keeps
117
+ today's behavior — every cell centers on the row's cross-axis, which
118
+ reads well for single-line rows. 'top' aligns each cell's content to
119
+ the top of its padding box instead, so tall/wrapped rows (a [wrap]
120
+ title column, a stacked identity cell) share one first-line scan
121
+ anchor across the row rather than each cell floating at a different
122
+ vertical center (gh#505). Per-column `valign` in the columns[] array
123
+ (or <col-def valign="…">) overrides the host per-column — e.g. keep a
124
+ checkbox column centered while data cells top-align. Header cells
125
+ always stay centered regardless of this prop (single-line by
126
+ contract) — the checkbox/expand structural columns also default to
127
+ centered under [valign="top"] since they're not part of columns[].
128
+ type: string
129
+ default: center
130
+ enum:
131
+ - top
132
+ - center
133
+ reflect: true
101
134
  wrap:
102
135
  description: >-
103
136
  Allow body-cell content to wrap onto multiple lines. Default is
@@ -7,6 +7,7 @@
7
7
  * The tab button is rendered by the parent tabs-ui, not by tab-ui itself.
8
8
  */
9
9
 
10
+ import { defineIfFree } from '../../core/register.js';
10
11
  import { UIElement } from '../../core/element.js';
11
12
 
12
13
  class UITab extends UIElement {
@@ -29,6 +30,8 @@ class UITab extends UIElement {
29
30
  else this.removeAttribute('aria-disabled');
30
31
  }
31
32
  }
32
- customElements.define('tab-ui', UITab);
33
+ // defineIfFree (gh#534): the prod bundle + module graph load in parallel;
34
+ // second registration must no-op, not throw.
35
+ defineIfFree('tab-ui', UITab);
33
36
 
34
37
  export { UITab };
@@ -51,15 +51,21 @@ tag-ui[removable]:not([disabled]):hover {
51
51
  box-sizing: border-box;
52
52
  display: inline-flex;
53
53
  align-items: center;
54
- /* align-self: start — TKT-0021. A flex-column ancestor (col-ui
55
- defaults to align-items: stretch) otherwise stretches a bare
56
- tag-ui to the column's full width; a chip should size to its own
57
- content regardless of ancestor. justify-content: center is the
58
- matching defensive default badge-ui/button-ui already carry
59
- (badge.css/button.css) if the box is ever wider than its
60
- content for any other reason, the label still centers instead of
61
- reading left-aligned in an oversized pill. */
62
- align-self: start;
54
+ /* width: fit-contentthe TKT-0021 guard, re-cut for gh#503. The old
55
+ `align-self: start` defeated col-ui's stretch, but align-self acts on
56
+ whichever cross axis the PARENT has: in a flex-ROW parent (every
57
+ table-ui body cell, row-ui, toolbars) it governed the vertical axis
58
+ and top-pinned bare chips in tall rows (measured −34px off-center,
59
+ adia-v2 console). A definite inline size defeats stretch in a column
60
+ stack the same way (stretch applies only at auto cross size; the item
61
+ then sits at cross-start identical content-sized, left-aligned
62
+ result), while a row parent's align-items: center regains control of
63
+ vertical placement. justify-content: center is the matching defensive
64
+ default badge-ui/button-ui already carry (badge.css/button.css) — if
65
+ the box is ever wider than its content for any other reason, the
66
+ label still centers instead of reading left-aligned in an oversized
67
+ pill. */
68
+ width: fit-content;
63
69
  justify-content: center;
64
70
  gap: var(--tag-gap);
65
71
  padding: var(--tag-py) var(--tag-px);
@@ -90,6 +90,11 @@ function outerWidth(el) {
90
90
  }
91
91
 
92
92
  export class UIToolbar extends UIElement {
93
+ // Phosphor icons this primitive auto-stamps (without consumer markup).
94
+ // Aggregated by installIconLoadersForRegistered() across all defined
95
+ // elements. Audited by check-required-icons.mjs.
96
+ static requiredIcons = ['dots-three'];
97
+
93
98
  static properties = {
94
99
  gap: { type: String, default: 'sm', reflect: true },
95
100
  align: { type: String, default: 'start', reflect: true },
@@ -320,6 +325,12 @@ export class UIToolbar extends UIElement {
320
325
  trigger.hidden = true;
321
326
 
322
327
  // 2. Measure.
328
+ // Hidden-ancestor guard (gh#528): a display:none mount reads
329
+ // clientWidth 0 — committing a spill from that reading strands the
330
+ // bar collapsed. Step 1 already restored everything visible, so
331
+ // leaving now is the correct state; the ResizeObserver fires again
332
+ // with a real size when the ancestor becomes visible.
333
+ if (this.clientWidth === 0) return;
323
334
  const style = getComputedStyle(this);
324
335
  const padL = parseFloat(style.paddingLeft) || 0;
325
336
  const padR = parseFloat(style.paddingRight) || 0;
@@ -329,7 +340,16 @@ export class UIToolbar extends UIElement {
329
340
  // Include horizontal margins — components like divider-ui add their own
330
341
  // margin-inline which getBoundingClientRect() does NOT include but flex
331
342
  // layout DOES count toward the row width.
343
+ // Grow-item guard (gh#528): a flex-grow child (a spacer, a stretched
344
+ // group) measures at its EXPANDED width — the row then always reads
345
+ // "full" and the last real action spills unrecoverably (the pattern
346
+ // bug gh#494's build hit). Measure grow items at their natural size
347
+ // by zeroing flex-grow for the read, then restoring.
348
+ const grown = all.filter(el => parseFloat(getComputedStyle(el).flexGrow) > 0);
349
+ const savedGrow = grown.map(el => el.style.flexGrow);
350
+ for (const el of grown) el.style.flexGrow = '0';
332
351
  const widths = all.map(el => outerWidth(el));
352
+ grown.forEach((el, i) => { el.style.flexGrow = savedGrow[i]; });
333
353
  const total = widths.reduce((a, w) => a + w, 0) + Math.max(0, all.length - 1) * gap;
334
354
 
335
355
  // Reserve OVERFLOW_BUFFER worth of headroom — start spilling sooner
@@ -41,6 +41,8 @@ states:
41
41
  description: Default, ready for interaction.
42
42
  traits: []
43
43
  tokens: {}
44
+ requiredIcons:
45
+ - dots-three
44
46
  a2ui:
45
47
  rules:
46
48
  - rule: 'Horizontal action bar — hosts <button-ui>, <toolbar-group-ui>, and <divider-ui> children.'
package/core/provider.js CHANGED
@@ -23,6 +23,7 @@
23
23
  */
24
24
 
25
25
  import { UIElement } from './element.js';
26
+ import { defineIfFree } from './register.js';
26
27
  import { BaseController } from './controller.js';
27
28
 
28
29
  // ═══════════════════════════════════════════════════════════════
@@ -295,4 +296,7 @@ export class UIRouter extends UIProvider {
295
296
  }
296
297
  }
297
298
 
298
- customElements.define('router-ui', UIRouter);
299
+ // defineIfFree, not a bare define (gh#534): the site's prod bootstrap loads
300
+ // the prebuilt everything-bundle ALONGSIDE the module graph — whichever
301
+ // registers second must no-op, not throw.
302
+ defineIfFree('router-ui', UIRouter);