@adia-ai/web-modules 0.8.40 → 0.8.41

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 +27 -0
  2. package/README.md +6 -6
  3. package/agent-admin/admin-roster/admin-roster.a2ui.json +1 -0
  4. package/agent-admin/admin-roster/admin-roster.css +10 -2
  5. package/agent-admin/admin-roster/admin-roster.js +70 -22
  6. package/agent-admin/admin-settings/admin-settings.a2ui.json +1 -0
  7. package/agent-admin/agent-admin/agent-admin.a2ui.json +1 -0
  8. package/billing/billing-overview/billing-overview.a2ui.json +10 -1
  9. package/billing/billing-overview/billing-overview.class.js +142 -73
  10. package/billing/billing-overview/billing-overview.css +70 -57
  11. package/billing/billing-overview/billing-overview.yaml +5 -0
  12. package/billing/invoice-detail/invoice-detail.a2ui.json +9 -4
  13. package/billing/invoice-detail/invoice-detail.class.js +6 -5
  14. package/billing/invoice-detail/invoice-detail.yaml +9 -4
  15. package/billing/invoice-history/invoice-history.a2ui.json +7 -4
  16. package/billing/invoice-history/invoice-history.class.js +29 -18
  17. package/billing/invoice-history/invoice-history.css +18 -15
  18. package/billing/invoice-history/invoice-history.yaml +10 -4
  19. package/billing/payment-method-form/payment-method-form.a2ui.json +1 -0
  20. package/billing/payment-method-form/payment-method-form.class.js +3 -3
  21. package/billing/payment-method-form/payment-method-form.css +4 -4
  22. package/billing/payment-method-list/payment-method-list.a2ui.json +4 -0
  23. package/billing/payment-method-list/payment-method-list.class.js +31 -11
  24. package/billing/payment-method-list/payment-method-list.css +13 -10
  25. package/billing/payment-method-list/payment-method-list.yaml +3 -0
  26. package/billing/plan-picker/plan-picker.a2ui.json +6 -0
  27. package/billing/plan-picker/plan-picker.class.js +70 -22
  28. package/billing/plan-picker/plan-picker.css +21 -38
  29. package/billing/plan-picker/plan-picker.yaml +5 -0
  30. package/chat/chat-composer/chat-composer.a2ui.json +1 -0
  31. package/chat/chat-empty/chat-empty.a2ui.json +4 -0
  32. package/chat/chat-empty/chat-empty.yaml +2 -0
  33. package/chat/chat-header/chat-header.a2ui.json +5 -0
  34. package/chat/chat-header/chat-header.yaml +3 -0
  35. package/chat/chat-shell/chat-shell.a2ui.json +4 -0
  36. package/chat/chat-shell/chat-shell.yaml +2 -0
  37. package/chat/chat-sidebar/chat-sidebar.a2ui.json +4 -0
  38. package/chat/chat-sidebar/chat-sidebar.yaml +2 -0
  39. package/chat/chat-status/chat-status.a2ui.json +4 -0
  40. package/chat/chat-status/chat-status.yaml +2 -0
  41. package/chat/chat-thread/chat-thread.a2ui.json +4 -0
  42. package/chat/chat-thread/chat-thread.yaml +2 -0
  43. package/dashboard/dashboard-layout/dashboard-layout.a2ui.json +8 -8
  44. package/dashboard/dashboard-layout/dashboard-layout.css +4 -4
  45. package/dashboard/dashboard-layout/dashboard-layout.js +10 -9
  46. package/dashboard/dashboard-layout/dashboard-layout.yaml +19 -7
  47. package/dashboard/date-range-selector/date-range-selector.a2ui.json +4 -3
  48. package/dashboard/date-range-selector/date-range-selector.yaml +6 -4
  49. package/dist/chat/chat-shell.min.js +23 -23
  50. package/dist/editor/editor-shell.min.js +3 -3
  51. package/dist/everything.min.js +119 -132
  52. package/dist/shell/admin-shell.min.css +1 -1
  53. package/dist/shell/admin-shell.min.js +6 -6
  54. package/dist/simple/simple-shell.min.js +5 -5
  55. package/dist/web-modules.min.css +1 -1
  56. package/dist/web-modules.sheet.js +1 -1
  57. package/editor/editor-canvas/editor-canvas.a2ui.json +4 -0
  58. package/editor/editor-canvas/editor-canvas.yaml +2 -0
  59. package/editor/editor-canvas-empty/editor-canvas-empty.a2ui.json +4 -0
  60. package/editor/editor-canvas-empty/editor-canvas-empty.yaml +2 -0
  61. package/editor/editor-canvas-toolbar/editor-canvas-toolbar.a2ui.json +4 -0
  62. package/editor/editor-canvas-toolbar/editor-canvas-toolbar.yaml +2 -0
  63. package/editor/editor-shell/editor-shell.a2ui.json +5 -0
  64. package/editor/editor-shell/editor-shell.yaml +3 -0
  65. package/editor/editor-sidebar/editor-sidebar.a2ui.json +4 -0
  66. package/editor/editor-sidebar/editor-sidebar.yaml +2 -0
  67. package/editor/editor-statusbar/editor-statusbar.a2ui.json +4 -0
  68. package/editor/editor-statusbar/editor-statusbar.yaml +2 -0
  69. package/editor/editor-toolbar/editor-toolbar.a2ui.json +4 -0
  70. package/editor/editor-toolbar/editor-toolbar.yaml +2 -0
  71. package/feedback/confirm-dialog/confirm-dialog.a2ui.json +1 -0
  72. package/form/form-popover/form-popover.a2ui.json +1 -0
  73. package/onboarding/onboarding-checklist/onboarding-checklist.a2ui.json +2 -1
  74. package/onboarding/onboarding-checklist/onboarding-checklist.js +2 -2
  75. package/onboarding/onboarding-checklist/onboarding-checklist.yaml +1 -1
  76. package/package.json +6 -6
  77. package/runtime/a2ui-root/a2ui-root.a2ui.json +6 -5
  78. package/runtime/a2ui-root/a2ui-root.css +75 -0
  79. package/runtime/a2ui-root/a2ui-root.js +118 -10
  80. package/runtime/a2ui-root/a2ui-root.yaml +7 -2
  81. package/runtime/gen-root/gen-root.a2ui.json +6 -5
  82. package/runtime/gen-root/gen-root.yaml +10 -2
  83. package/settings/integrations-page/integrations-page.a2ui.json +3 -2
  84. package/settings/integrations-page/integrations-page.js +8 -8
  85. package/settings/integrations-page/integrations-page.yaml +2 -2
  86. package/settings/notification-preferences/notification-preferences.a2ui.json +1 -0
  87. package/shell/admin-command/admin-command.a2ui.json +5 -1
  88. package/shell/admin-command/admin-command.yaml +3 -1
  89. package/shell/admin-content/admin-content.a2ui.json +4 -0
  90. package/shell/admin-content/admin-content.yaml +2 -0
  91. package/shell/admin-entity-item/admin-entity-item.a2ui.json +1 -0
  92. package/shell/admin-page/admin-page.a2ui.json +5 -0
  93. package/shell/admin-page/admin-page.yaml +3 -0
  94. package/shell/admin-page-body/admin-page-body.a2ui.json +4 -0
  95. package/shell/admin-page-body/admin-page-body.yaml +2 -0
  96. package/shell/admin-page-footer/admin-page-footer.a2ui.json +4 -0
  97. package/shell/admin-page-footer/admin-page-footer.yaml +2 -0
  98. package/shell/admin-page-header/admin-page-header.a2ui.json +4 -0
  99. package/shell/admin-page-header/admin-page-header.yaml +2 -0
  100. package/shell/admin-scroll/admin-scroll.a2ui.json +4 -0
  101. package/shell/admin-scroll/admin-scroll.yaml +2 -0
  102. package/shell/admin-shell/admin-shell.a2ui.json +7 -3
  103. package/shell/admin-shell/admin-shell.yaml +3 -1
  104. package/shell/admin-shell/css/admin-shell.sidebar.css +2 -2
  105. package/shell/admin-sidebar/admin-sidebar.a2ui.json +4 -0
  106. package/shell/admin-sidebar/admin-sidebar.yaml +2 -0
  107. package/shell/admin-statusbar/admin-statusbar.a2ui.json +6 -0
  108. package/shell/admin-statusbar/admin-statusbar.yaml +4 -0
  109. package/shell/admin-topbar/admin-topbar.a2ui.json +6 -0
  110. package/shell/admin-topbar/admin-topbar.yaml +4 -0
  111. package/shell/embed-shell/embed-shell.a2ui.json +1 -0
  112. package/simple/simple-content/simple-content.a2ui.json +4 -0
  113. package/simple/simple-content/simple-content.yaml +2 -0
  114. package/simple/simple-hero/simple-hero.a2ui.json +4 -0
  115. package/simple/simple-hero/simple-hero.yaml +2 -0
  116. package/simple/simple-shell/simple-shell.a2ui.json +4 -0
  117. package/simple/simple-shell/simple-shell.yaml +2 -0
  118. package/theme/theme-panel/theme-panel.a2ui.json +1 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog — @adia-ai/web-modules
2
2
 
3
+ ## [0.8.41] — 2026-08-17
4
+
5
+ ### Added
6
+ - **`a2ui-root` — surface lifecycle integration (gh#1364 + follow-up gh#1433; `spec-a2ui-surface-lifecycle.md`, ADR-0061).** The `doc` setter is now an atomic `replaceDoc()`: per surfaceId, an EXISTING surface regenerates through a begin → apply → commit bracket so old content stays visible until the new answer commits (no blank frame — "the blank flash is the bug", OD-2), and surfaces the new doc no longer declares are torn down. New reflected rolled-up `[pending]` attribute (true while any surface has an in-flight generation), driven by the renderer's bubbling `surface-pending`/`surface-committed`/`surface-aborted` events; default pending/stale styling ships in `runtime/a2ui-root/a2ui-root.css`. `runtime/a2ui-root/a2ui-root.js` + `a2ui-root.test.js`.
7
+
8
+ ### Changed
9
+ - **BREAKING, no deprecation aliases — composite-side renames from the global-attribute-shadowing sweep (gh#1335).** `invoice-detail-ui`/`invoice-history-ui`/`dashboard-layout-ui[density]` converge onto the global grammar (`compact`/`spacious`/unset — the component-local enums are gone); `dashboard-layout-ui[gap]` → `[band-gap]`. See the migration guide's [§ Shipped in `0.8.41` — global-attribute-shadowing collision sweep](../../.claude/docs/MIGRATION%20GUIDE.md).
10
+ - **BREAKING, no deprecation alias — `data-*` states converge to bare attributes (gh#1332, Category A).** `integrations-page-ui`'s `empty-search`/`empty-data` states (was `[data-empty-search]`/`[data-empty-data]`) and `onboarding-checklist-ui`'s `complete` state (was `[data-complete]`) now match their own sibling states' bare-attribute convention (`loading`, `collapsed`). Any CSS/script targeting the old forms moves to `[empty-search]`/`[empty-data]`/`[complete]`. The same sweep (PR #1438) stamped declared API-state attributes across the module tier per the ADR-0060 anatomy-marker convention.
11
+ - **BREAKING for raw A2UI documents only — `admin-shell`'s catalog identity is now `AdminShell` (gh#1427).** `admin-shell.yaml` and `embed-shell.yaml` both declared `component: AppShell`, so the catalog build silently dropped `admin-shell`'s entry (embed-shell's fragment won); `embed-shell` keeps `AppShell`. A raw a2ui tree with `{"component": "AppShell"}` meaning the stable admin shell updates to `AdminShell`; the `<admin-shell>` tag and JS API are unaffected.
12
+ - **`billing-overview` family recomposed from layout primitives (gh#1519).** The billing overview page family now composes the shared layout primitives instead of bespoke wrappers — `billing/billing-overview/`.
13
+ - **`dashboard-layout` — chart band adopts the chart-in-card bleed/legend convention (#1399).** Charts in the band render with the card-bleed + legend placement the dashboard pattern library established.
14
+ - **`admin-roster` — persona-import composes the `upload-ui` primitive (#1398)** instead of a hand-rolled native file picker.
15
+
16
+ ### Fixed
17
+ - **`table-toolbar` hides the title cluster when it has nothing to show (ADIA2-8897).** An empty title/description cluster no longer reserves its grid column, so toolbars with only actions/filters align flush.
18
+
19
+ ### Maintenance
20
+ - **`dist/` bundles rebuilt** in this cut's window — carried by the entries above plus the web-components SSR shape-1 sweep (gh#1430/gh#1436) and yaml/sidecar regenerations (ADR-0067 `parts:`, ADR-0069 derived-resync); regenerated artifacts, not independent edits.
21
+ - **`agent-admin/` touched in this release window** (6 file(s), e.g. `admin-roster/admin-roster.a2ui.json`) — carried by the entries above.
22
+ - **`chat/` touched in this release window** (14 file(s), e.g. `chat-composer/chat-composer.a2ui.json`) — carried by the entries above.
23
+ - **`editor/` touched in this release window** (14 file(s), e.g. `editor-canvas-empty/editor-canvas-empty.a2ui.json`) — carried by the entries above.
24
+ - **`feedback/` touched in this release window** (1 file(s), e.g. `confirm-dialog/confirm-dialog.a2ui.json`) — carried by the entries above.
25
+ - **`form/` touched in this release window** (1 file(s), e.g. `form-popover/form-popover.a2ui.json`) — carried by the entries above.
26
+ - **`settings/` touched in this release window** (6 file(s), e.g. `integrations-page/integrations-page.a2ui.json`) — carried by the entries above.
27
+ - **`simple/` touched in this release window** (6 file(s), e.g. `simple-content/simple-content.a2ui.json`) — carried by the entries above.
28
+ - **`theme/` touched in this release window** (1 file(s), e.g. `theme-panel/theme-panel.a2ui.json`) — carried by the entries above.
29
+
3
30
  ## [0.8.40] — 2026-08-15
4
31
 
5
32
  ### Fixed
package/README.md CHANGED
@@ -22,7 +22,7 @@ Since **v0.6.30**, both this package and `@adia-ai/web-components` ship pre-flat
22
22
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@adia-ai/web-components@0.8/dist/web-components.min.css">
23
23
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@adia-ai/web-modules@0.8/dist/shell/admin-shell.min.css">
24
24
 
25
- <!-- JS: everything in one bundle (primitives + all 4 shells + a2ui-runtime; ~190 KB gzipped) -->
25
+ <!-- JS: everything in one bundle (primitives + all 4 shells + the @adia-ai/a2ui runtime; ~424 KB gzipped) -->
26
26
  <script type="module" src="https://cdn.jsdelivr.net/npm/@adia-ai/web-modules@0.8/dist/everything.min.js"></script>
27
27
 
28
28
  <admin-shell mode="rounded borderless">
@@ -87,7 +87,7 @@ Since **v0.6.30**, both this package and `@adia-ai/web-components` ship pre-flat
87
87
 
88
88
  | Bundle | Path | Gzipped | Contents | When to use |
89
89
  |---|---|---|---|---|
90
- | **Everything** | `/dist/everything.min.js` | **~250 KB** | All primitives + a2ui-runtime + all 4 shells | **CodePen / one-tag demos** |
90
+ | **Everything** | `/dist/everything.min.js` | **~424 KB** | All primitives + the @adia-ai/a2ui runtime + all 4 shells | **CodePen / one-tag demos** |
91
91
  | Admin shell | `/dist/shell/admin-shell.min.js` | ~6 KB | Shell registration; needs `web-components.min.js` first | Admin app, no chat/editor |
92
92
  | Chat shell | `/dist/chat/chat-shell.min.js` | ~230 KB | Includes chat-thread/composer | Chat surface |
93
93
  | Editor shell | `/dist/editor/editor-shell.min.js` | ~6 KB | Shell registration; needs primitives first | Editor surface |
@@ -119,7 +119,7 @@ When composing UI with an AI coding agent (Claude Code / Cursor / Windsurf / Cla
119
119
  | Peer dependency | Required when you use |
120
120
  |---|---|
121
121
  | `@adia-ai/web-components` (always) | Any cluster |
122
- | `@adia-ai/a2ui-runtime` | `runtime` cluster (`<gen-root>`, `<a2ui-root>`) — A2UI message → DOM renderer |
122
+ | `@adia-ai/a2ui` | `runtime` cluster (`<gen-root>`, `<a2ui-root>`) — A2UI message → DOM renderer |
123
123
  | `@adia-ai/llm` | `chat` cluster (`<chat-shell>`) — LLM proxy + streaming |
124
124
 
125
125
  If you only use `shell`, `editor`, `simple`, or `theme` clusters, those two peers can be omitted (or kept as `--legacy-peer-deps` warnings under npm). pnpm with auto-install-peers will silently install them.
@@ -250,7 +250,7 @@ import '@adia-ai/web-components'; // primitives + nav family
250
250
  import '@adia-ai/web-modules/shell'; // admin-shell + bespoke children
251
251
  import '@adia-ai/web-modules/chat'; // chat-shell + bespoke children (needs @adia-ai/llm)
252
252
  import '@adia-ai/web-modules/editor'; // editor-shell + bespoke children
253
- import '@adia-ai/web-modules/runtime'; // gen-root, a2ui-root (needs @adia-ai/a2ui-runtime)
253
+ import '@adia-ai/web-modules/runtime'; // gen-root, a2ui-root (needs @adia-ai/a2ui)
254
254
  import '@adia-ai/web-modules/theme'; // theme-panel (appearance prefs)
255
255
 
256
256
  // CSS — one stylesheet per cluster you use (REQUIRED — see callout above)
@@ -343,13 +343,13 @@ web-modules/
343
343
  │ └── simple-{content,hero}/ CSS-only
344
344
  ├── runtime/ 2 render roots
345
345
  │ ├── gen-root/ <gen-root> turns gen-UI intents into DOM
346
- │ └── a2ui-root/ <a2ui-root> A2UI protocol → DOM (uses @adia-ai/a2ui-runtime)
346
+ │ └── a2ui-root/ <a2ui-root> A2UI protocol → DOM (uses @adia-ai/a2ui)
347
347
  ├── theme/ 1 element
348
348
  │ └── theme-panel/ <theme-panel> appearance-preferences control surface
349
349
  └── index.js barrel re-export of every cluster
350
350
  ```
351
351
 
352
- Each element directory carries its `.js`, `.css`, `.yaml`, and `.a2ui.json` files — same shape as `web-components/components/<x>/`. The yaml + a2ui.json contracts feed the gen-UI catalog at `packages/gen-ui/a2ui/corpus/catalog-a2ui_0_9.json`; the build script at `scripts/build/components.mjs` scans both `web-components/components/` and `web-modules/<cluster>/`.
352
+ Each element directory carries its `.js`, `.css`, `.yaml`, and `.a2ui.json` files — same shape as `web-components/components/<x>/`. The yaml + a2ui.json contracts feed the gen-UI catalog at `packages/gen-ui/a2ui/catalog/catalog-a2ui_0_9.json`; the build script at `scripts/build/components.mjs` scans both `web-components/components/` and `web-modules/<cluster>/`.
353
353
 
354
354
  ## Behavior contract
355
355
 
@@ -94,6 +94,7 @@
94
94
  "menu"
95
95
  ],
96
96
  "name": "UIAdminRoster",
97
+ "parts": {},
97
98
  "related": [
98
99
  "AdminSettings",
99
100
  "AgentAdmin",
@@ -64,7 +64,7 @@
64
64
  box-sizing: border-box;
65
65
  }
66
66
 
67
- :scope [data-part="row"][data-active="true"] {
67
+ :scope [data-part="row"][aria-checked="true"] {
68
68
  --menu-item-bg: var(--admin-roster-active-bg);
69
69
  --menu-item-fg: var(--admin-roster-active-fg);
70
70
  --menu-item-fg-hover: var(--admin-roster-active-fg);
@@ -76,7 +76,7 @@
76
76
  visibility: hidden;
77
77
  }
78
78
 
79
- :scope [data-part="row"][data-active="true"] [data-part="row-check"] {
79
+ :scope [data-part="row"][aria-checked="true"] [data-part="row-check"] {
80
80
  visibility: visible;
81
81
  }
82
82
 
@@ -90,4 +90,12 @@
90
90
  display: flex;
91
91
  gap: var(--a-space-2);
92
92
  }
93
+
94
+ /* Reuses <upload-ui> as the picker engine behind the visible "Import"
95
+ button (gh#1360) — display:none, not visually-hidden-ui: this node
96
+ is never meant to reach AT (aria-hidden + out of tab order above),
97
+ unlike a sr-only label that stays AT-visible on purpose. */
98
+ :scope [data-part="import-file"] {
99
+ display: none;
100
+ }
93
101
  }
@@ -20,20 +20,50 @@
20
20
  *
21
21
  * Import/export rides `@adia-ai/persona`'s persona-file functions
22
22
  * verbatim — this composite never re-implements the file format. Export
23
- * downloads a `.json` via a throwaway object URL; import reads a chosen
24
- * file through `input[type=file]`, calls `importPersona` (which always
25
- * MINTS a new persona never overwrites an existing one, even on an id
26
- * collision) and re-emits it upward as `persona-import` so a host can
27
- * append it to its own persona list and persist it.
23
+ * downloads a `.json` via a throwaway object URL; import reuses the
24
+ * `<upload-ui>` primitive (packages/web-components/components/upload/) as
25
+ * its file-picking enginea `display:none` `<upload-ui data-part=
26
+ * "import-file">` sits in the template (`accept="application/json"`
27
+ * shapes its PICKER only upload-ui's drop path forwards dropped files
28
+ * unfiltered, so this module re-checks "exactly one JSON-named/typed
29
+ * file" itself before importing), and the toolbar's visible "Import" icon button
30
+ * (gh#1360, replacing a hand-rolled `input[type=file]`) delegates the
31
+ * click to upload-ui's own dropzone part (`[data-dropzone]` — this
32
+ * framework's light-DOM "part" hook, styled by upload.css and driven by
33
+ * upload.class.js the same way; there is no public `.open()`/`.pick()`
34
+ * method on the primitive, so this is the narrowest supported trigger
35
+ * point short of a primitive change — see the ticket's Findings for the
36
+ * gap this surfaced). upload-ui's own `showOpenFilePicker` (+
37
+ * `input[type=file]` fallback) does the picking; its documented `change`
38
+ * event (`{ value, files }`, upload.d.ts) is read here, then handed to
39
+ * `importPersona` (which always MINTS a new persona — never overwrites an
40
+ * existing one, even on an id collision) and re-emitted upward as
41
+ * `persona-import` so a host can append it to its own persona list and
42
+ * persist it.
28
43
  *
29
44
  * Per ADR-0027 — composites that programmatically create primitives in
30
45
  * render() do NOT auto-import those primitives; a consumer imports
31
- * <button-ui> + <icon-ui> + <menu-item-ui> (menu.js) alongside this module.
46
+ * <button-ui> + <icon-ui> + <menu-item-ui> (menu.js) + <upload-ui>
47
+ * alongside this module.
32
48
  */
33
49
 
34
50
  import { UIElement, html } from '@adia-ai/web-components/core/element';
35
51
  import { exportPersona, importPersona } from '@adia-ai/persona';
36
52
 
53
+ // The template's <upload-ui accept="application/json"> — one value, both
54
+ // the picker filter and the drop-side check below derive from it.
55
+ const IMPORT_ACCEPT = 'application/json';
56
+
57
+ // A file "indicates JSON" when either its MIME type is the accept value or
58
+ // its name carries a .json suffix — OS drops routinely leave `type` empty
59
+ // for .json, and a picker-chosen file may be typed but oddly named, so
60
+ // either signal suffices; neither alone is required.
61
+ function isJsonFile(file) {
62
+ if (!file) return false;
63
+ if (file.type === IMPORT_ACCEPT) return true;
64
+ return /\.json$/i.test(String(file.name || ''));
65
+ }
66
+
37
67
  class UIAdminRoster extends UIElement {
38
68
  static properties = {
39
69
  personas: { type: Array, default: [] },
@@ -43,7 +73,6 @@ class UIAdminRoster extends UIElement {
43
73
  static template = (host) => host._renderTemplate();
44
74
 
45
75
  #headerId = `admin-roster-h-${Math.random().toString(36).slice(2, 8)}`;
46
- #fileInput = null;
47
76
 
48
77
  attributeChangedCallback(name, oldVal, newVal) {
49
78
  if (name === 'personas') {
@@ -94,6 +123,7 @@ class UIAdminRoster extends UIElement {
94
123
  <button-ui data-action="export" text="Export" icon="download-simple" variant="ghost" size="sm"
95
124
  .disabled=${!activeId}></button-ui>
96
125
  <button-ui data-action="import" text="Import" icon="upload-simple" variant="ghost" size="sm"></button-ui>
126
+ <upload-ui data-part="import-file" accept=${IMPORT_ACCEPT} aria-hidden="true" tabindex="-1"></upload-ui>
97
127
  </div>
98
128
  `;
99
129
  }
@@ -109,7 +139,6 @@ class UIAdminRoster extends UIElement {
109
139
  role="menuitemradio"
110
140
  tabindex="0"
111
141
  aria-checked=${String(isActive)}
112
- data-active=${isActive}
113
142
  text=${persona.label ?? persona.id}
114
143
  subtitle=${persona.category ?? ''}
115
144
  >
@@ -175,24 +204,43 @@ class UIAdminRoster extends UIElement {
175
204
  URL.revokeObjectURL(url);
176
205
  }
177
206
 
207
+ // <upload-ui> exposes no public open()/pick() method (upload.d.ts) — the
208
+ // narrowest supported trigger is clicking its own `[data-dropzone]` part
209
+ // (the same node upload.class.js binds its click/keydown/drag listeners
210
+ // to, and upload.css styles by), so showOpenFilePicker (+ its
211
+ // input[type=file] fallback) runs exactly as it would for a directly
212
+ // clicked <upload-ui>.
178
213
  #triggerImport() {
179
- if (!this.#fileInput) {
180
- this.#fileInput = this.ownerDocument.createElement('input');
181
- this.#fileInput.type = 'file';
182
- this.#fileInput.accept = 'application/json';
183
- this.#fileInput.setAttribute('data-part', 'import-file');
184
- this.#fileInput.style.display = 'none';
185
- this.appendChild(this.#fileInput);
186
- }
187
- this.#fileInput.value = '';
188
- this.#fileInput.click();
214
+ const upload = this.querySelector('[data-part="import-file"]');
215
+ upload?.querySelector('[data-dropzone]')?.click();
189
216
  }
190
217
 
218
+ // Fires on the bubbling `change` <upload-ui> dispatches on itself
219
+ // (upload.d.ts's documented UploadChangeEvent — { value, files }).
220
+ //
221
+ // upload-ui's `accept` / `multiple` only shape its PICKER; its drop path
222
+ // (upload.class.js #onDrop) forwards `dataTransfer.files` unfiltered. So
223
+ // the single-JSON-file contract is enforced here: exactly one file whose
224
+ // name or MIME type says JSON — anything else is rejected through the
225
+ // same `persona-import-error` path a malformed file takes.
191
226
  #onImportFileChange = async (e) => {
192
- const input = e.target;
193
- if (!input || input !== this.#fileInput) return;
194
- const file = input.files?.[0];
195
- if (!file) return;
227
+ const target = e.target;
228
+ if (!target || target.getAttribute?.('data-part') !== 'import-file') return;
229
+ const files = Array.isArray(e.detail?.files) ? e.detail.files : [];
230
+ if (files.length === 0) return;
231
+ const reject = (message) => this.dispatchEvent(new CustomEvent('persona-import-error', {
232
+ bubbles: true,
233
+ detail: { error: message },
234
+ }));
235
+ if (files.length !== 1) {
236
+ reject(`Import expects exactly one .json file (got ${files.length})`);
237
+ return;
238
+ }
239
+ const [file] = files;
240
+ if (!isJsonFile(file)) {
241
+ reject(`Import expects a .json file (got "${file?.name || 'unnamed file'}")`);
242
+ return;
243
+ }
196
244
  try {
197
245
  const text = await file.text();
198
246
  const existingIds = (Array.isArray(this.personas) ? this.personas : []).map((p) => p.id);
@@ -80,6 +80,7 @@
80
80
  "agent"
81
81
  ],
82
82
  "name": "UIAdminSettings",
83
+ "parts": {},
83
84
  "related": [
84
85
  "AdminRoster",
85
86
  "AgentAdmin"
@@ -91,6 +91,7 @@
91
91
  "store-swap"
92
92
  ],
93
93
  "name": "UIAgentAdmin",
94
+ "parts": {},
94
95
  "related": [
95
96
  "AdminRoster",
96
97
  "AdminSettings",
@@ -15,7 +15,10 @@
15
15
  "properties": {
16
16
  "account": {
17
17
  "description": "Aggregate billing snapshot — source of truth for the whole\ndashboard. Shape:\n {plan: {id, name, price, cycle, currency, status},\n status: \"active\" | \"trialing\" | \"past_due\" | \"canceled\" | \"paused\",\n billingCycle: \"month\" | \"year\",\n currentSpend: number,\n seats: {used: number, total: number},\n nextInvoiceDate: ISO 8601 date string,\n paymentMethods: [...], // forwarded to payment-method-list\n invoices: [...], // forwarded to invoice-history\n plans?: [...], // forwarded to plan-picker; omitted when planSection is \"hidden\"\n dunning?: {amount, currency, dueAt, cardLast4?, reason?}}\nNull + no `data-stream-src` renders the empty state.\n",
18
- "type": "object",
18
+ "type": [
19
+ "object",
20
+ "null"
21
+ ],
19
22
  "default": null
20
23
  },
21
24
  "component": {
@@ -86,6 +89,11 @@
86
89
  "tag-ui",
87
90
  "text-ui",
88
91
  "alert-ui",
92
+ "avatar-ui",
93
+ "col-ui",
94
+ "row-ui",
95
+ "grid-ui",
96
+ "progress-ui",
89
97
  "plan-picker-ui",
90
98
  "payment-method-list-ui",
91
99
  "invoice-history-ui",
@@ -155,6 +163,7 @@
155
163
  "past-due"
156
164
  ],
157
165
  "name": "UIBillingOverview",
166
+ "parts": {},
158
167
  "related": [
159
168
  "PlanPicker",
160
169
  "PaymentMethodList",