@adia-ai/web-modules 0.8.39 → 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 (120) hide show
  1. package/CHANGELOG.md +37 -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 +22 -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 +45 -12
  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-shell/css/chat-shell.tokens.css +1 -1
  38. package/chat/chat-sidebar/chat-sidebar.a2ui.json +4 -0
  39. package/chat/chat-sidebar/chat-sidebar.yaml +2 -0
  40. package/chat/chat-status/chat-status.a2ui.json +4 -0
  41. package/chat/chat-status/chat-status.yaml +2 -0
  42. package/chat/chat-thread/chat-thread.a2ui.json +4 -0
  43. package/chat/chat-thread/chat-thread.yaml +2 -0
  44. package/dashboard/dashboard-layout/dashboard-layout.a2ui.json +8 -8
  45. package/dashboard/dashboard-layout/dashboard-layout.css +4 -4
  46. package/dashboard/dashboard-layout/dashboard-layout.js +10 -9
  47. package/dashboard/dashboard-layout/dashboard-layout.yaml +19 -7
  48. package/dashboard/date-range-selector/date-range-selector.a2ui.json +4 -3
  49. package/dashboard/date-range-selector/date-range-selector.yaml +6 -4
  50. package/dist/chat/chat-shell.min.css +1 -1
  51. package/dist/chat/chat-shell.min.js +25 -25
  52. package/dist/editor/editor-shell.min.js +3 -3
  53. package/dist/everything.min.js +118 -131
  54. package/dist/shell/admin-shell.min.css +1 -1
  55. package/dist/shell/admin-shell.min.js +6 -6
  56. package/dist/simple/simple-shell.min.js +5 -5
  57. package/dist/web-modules.min.css +1 -1
  58. package/dist/web-modules.sheet.js +1 -1
  59. package/editor/editor-canvas/editor-canvas.a2ui.json +4 -0
  60. package/editor/editor-canvas/editor-canvas.yaml +2 -0
  61. package/editor/editor-canvas-empty/editor-canvas-empty.a2ui.json +4 -0
  62. package/editor/editor-canvas-empty/editor-canvas-empty.yaml +2 -0
  63. package/editor/editor-canvas-toolbar/editor-canvas-toolbar.a2ui.json +4 -0
  64. package/editor/editor-canvas-toolbar/editor-canvas-toolbar.yaml +2 -0
  65. package/editor/editor-shell/editor-shell.a2ui.json +5 -0
  66. package/editor/editor-shell/editor-shell.yaml +3 -0
  67. package/editor/editor-sidebar/editor-sidebar.a2ui.json +4 -0
  68. package/editor/editor-sidebar/editor-sidebar.yaml +2 -0
  69. package/editor/editor-statusbar/editor-statusbar.a2ui.json +4 -0
  70. package/editor/editor-statusbar/editor-statusbar.yaml +2 -0
  71. package/editor/editor-toolbar/editor-toolbar.a2ui.json +4 -0
  72. package/editor/editor-toolbar/editor-toolbar.yaml +2 -0
  73. package/feedback/confirm-dialog/confirm-dialog.a2ui.json +1 -0
  74. package/form/form-popover/form-popover.a2ui.json +1 -0
  75. package/onboarding/onboarding-checklist/onboarding-checklist.a2ui.json +2 -1
  76. package/onboarding/onboarding-checklist/onboarding-checklist.js +2 -2
  77. package/onboarding/onboarding-checklist/onboarding-checklist.yaml +1 -1
  78. package/package.json +6 -6
  79. package/runtime/a2ui-root/a2ui-root.a2ui.json +6 -5
  80. package/runtime/a2ui-root/a2ui-root.css +75 -0
  81. package/runtime/a2ui-root/a2ui-root.js +118 -10
  82. package/runtime/a2ui-root/a2ui-root.yaml +7 -2
  83. package/runtime/gen-root/gen-root.a2ui.json +6 -5
  84. package/runtime/gen-root/gen-root.yaml +10 -2
  85. package/settings/integrations-page/integrations-page.a2ui.json +3 -2
  86. package/settings/integrations-page/integrations-page.js +8 -8
  87. package/settings/integrations-page/integrations-page.yaml +2 -2
  88. package/settings/notification-preferences/notification-preferences.a2ui.json +1 -0
  89. package/shell/admin-command/admin-command.a2ui.json +5 -1
  90. package/shell/admin-command/admin-command.yaml +3 -1
  91. package/shell/admin-content/admin-content.a2ui.json +4 -0
  92. package/shell/admin-content/admin-content.yaml +2 -0
  93. package/shell/admin-entity-item/admin-entity-item.a2ui.json +1 -0
  94. package/shell/admin-page/admin-page.a2ui.json +5 -0
  95. package/shell/admin-page/admin-page.yaml +3 -0
  96. package/shell/admin-page-body/admin-page-body.a2ui.json +4 -0
  97. package/shell/admin-page-body/admin-page-body.yaml +2 -0
  98. package/shell/admin-page-footer/admin-page-footer.a2ui.json +4 -0
  99. package/shell/admin-page-footer/admin-page-footer.yaml +2 -0
  100. package/shell/admin-page-header/admin-page-header.a2ui.json +4 -0
  101. package/shell/admin-page-header/admin-page-header.yaml +2 -0
  102. package/shell/admin-scroll/admin-scroll.a2ui.json +4 -0
  103. package/shell/admin-scroll/admin-scroll.yaml +2 -0
  104. package/shell/admin-shell/admin-shell.a2ui.json +7 -3
  105. package/shell/admin-shell/admin-shell.yaml +3 -1
  106. package/shell/admin-shell/css/admin-shell.sidebar.css +2 -2
  107. package/shell/admin-sidebar/admin-sidebar.a2ui.json +4 -0
  108. package/shell/admin-sidebar/admin-sidebar.yaml +2 -0
  109. package/shell/admin-statusbar/admin-statusbar.a2ui.json +6 -0
  110. package/shell/admin-statusbar/admin-statusbar.yaml +4 -0
  111. package/shell/admin-topbar/admin-topbar.a2ui.json +6 -0
  112. package/shell/admin-topbar/admin-topbar.yaml +4 -0
  113. package/shell/embed-shell/embed-shell.a2ui.json +1 -0
  114. package/simple/simple-content/simple-content.a2ui.json +4 -0
  115. package/simple/simple-content/simple-content.yaml +2 -0
  116. package/simple/simple-hero/simple-hero.a2ui.json +4 -0
  117. package/simple/simple-hero/simple-hero.yaml +2 -0
  118. package/simple/simple-shell/simple-shell.a2ui.json +4 -0
  119. package/simple/simple-shell/simple-shell.yaml +2 -0
  120. package/theme/theme-panel/theme-panel.a2ui.json +1 -0
@@ -147,6 +147,7 @@
147
147
  "getting started"
148
148
  ],
149
149
  "name": "UIOnboardingChecklist",
150
+ "parts": {},
150
151
  "related": [
151
152
  "Check",
152
153
  "List",
@@ -181,7 +182,7 @@
181
182
  },
182
183
  {
183
184
  "description": "All items done. Behavior depends on dismiss-on-complete.",
184
- "attribute": "data-complete",
185
+ "attribute": "complete",
185
186
  "name": "complete"
186
187
  },
187
188
  {
@@ -308,8 +308,8 @@ class UIOnboardingChecklist extends UIElement {
308
308
  }
309
309
 
310
310
  #syncCompletionAttrs() {
311
- if (this.#isAllDone()) this.setAttribute('data-complete', '');
312
- else this.removeAttribute('data-complete');
311
+ if (this.#isAllDone()) this.setAttribute('complete', '');
312
+ else this.removeAttribute('complete');
313
313
  }
314
314
 
315
315
  #maybeEmitComplete() {
@@ -122,7 +122,7 @@ states:
122
122
  attribute: collapsed
123
123
  - name: complete
124
124
  description: All items done. Behavior depends on dismiss-on-complete.
125
- attribute: data-complete
125
+ attribute: complete
126
126
  - name: dismissed
127
127
  description: Widget removed from layout after collapse or auto-dismiss.
128
128
  attribute: hidden
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/web-modules",
3
- "version": "0.8.39",
3
+ "version": "0.8.41",
4
4
  "description": "AdiaUI composite custom elements — shell, chat, editor, runtime clusters built from @adia-ai/web-components primitives. Subpath exports per cluster.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -389,11 +389,11 @@
389
389
  "./simple/**/*.js"
390
390
  ],
391
391
  "peerDependencies": {
392
- "@adia-ai/web-components": "0.8.39",
393
- "@adia-ai/a2ui": "0.8.39",
394
- "@adia-ai/llm": "0.8.39",
395
- "@adia-ai/agent": "0.8.39",
396
- "@adia-ai/persona": "0.8.39"
392
+ "@adia-ai/web-components": "0.8.41",
393
+ "@adia-ai/a2ui": "0.8.41",
394
+ "@adia-ai/llm": "0.8.41",
395
+ "@adia-ai/agent": "0.8.41",
396
+ "@adia-ai/persona": "0.8.41"
397
397
  },
398
398
  "publishConfig": {
399
399
  "access": "public",
@@ -94,15 +94,16 @@
94
94
  "mcp"
95
95
  ],
96
96
  "name": "A2UIRoot",
97
+ "parts": {
98
+ "default": {
99
+ "description": "The rendered surface — template-owned anatomy, not a light-DOM insertion point (gh#1454). `connected()` constructs `new A2UIRenderer(this, registry, …)` targeting the host itself; the renderer owns and stamps all rendered children as A2UI messages are processed. No consumer content is captured or preserved."
100
+ }
101
+ },
97
102
  "related": [
98
103
  "canvas",
99
104
  "inspector"
100
105
  ],
101
- "slots": {
102
- "default": {
103
- "description": "The rendered surface. Children are stamped by the A2UI renderer."
104
- }
105
- },
106
+ "slots": {},
106
107
  "states": [
107
108
  {
108
109
  "description": "Default, ready to connect.",
@@ -0,0 +1,75 @@
1
+ /* <a2ui-root> — default surface lifecycle styling.
2
+ *
3
+ * Spec: docs/ops/spec/spec-a2ui-surface-lifecycle.md "DOM contract", ADR-0061,
4
+ * gh#1364. Follow-up: gh#1433 — the gh#1364 build shipped the
5
+ * `data-a2ui-lifecycle` attribute + `pending` reflection with no default
6
+ * CSS hook (named gap in its own Findings).
7
+ *
8
+ * DEPENDENCY NOTE (2026-08-17): gh#1364's own PR (#1431) — the runtime
9
+ * that actually STAMPS `data-a2ui-lifecycle` — has not merged yet. This
10
+ * file is written ahead of that landing: every selector below is
11
+ * structurally inert until #1431 ships (no element ever carries the
12
+ * attribute on this branch), and activates automatically once it does —
13
+ * no further CSS change is owed here.
14
+ *
15
+ * `data-a2ui-lifecycle` is stamped on the PER-SURFACE root div
16
+ * (`[data-a2ui-surface]`, renderer-owned) — a descendant of `<a2ui-root>`,
17
+ * never on `<a2ui-root>` itself — so every rule below is a descendant
18
+ * selector inside this scope, matching the spec's own DOM-contract sample.
19
+ *
20
+ * Error UI is host-owned (OD-4, spec ratification 2026-08-16) — no default
21
+ * CSS for `error-empty` (nothing has ever rendered; the host owns what
22
+ * shows there). `error-stale` gets the SAME dim treatment as
23
+ * `pending-stale` — both are "prior committed content, still visible"
24
+ * states (REQ-007), not error chrome.
25
+ */
26
+ @scope (a2ui-root) {
27
+ :where(:scope) {
28
+ /* ── Tokens ── */
29
+ --a2ui-root-stale-opacity: 0.6;
30
+ --a2ui-root-pending-bg: var(--a-bg-muted);
31
+ --a2ui-root-pending-highlight: var(--a-bg-subtle);
32
+ --a2ui-root-pending-min-block: 6rem;
33
+ --a2ui-root-pending-radius: var(--a-radius-md);
34
+ --a2ui-root-pending-duration: 1.5s;
35
+ --a2ui-root-pending-easing: var(--a-easing);
36
+ }
37
+
38
+ /* ── stale-while-regenerate (REQ-001/007): old content stays visible,
39
+ dimmed, never blanked ── */
40
+ [data-a2ui-lifecycle="pending-stale"],
41
+ [data-a2ui-lifecycle="error-stale"] {
42
+ opacity: var(--a2ui-root-stale-opacity);
43
+ }
44
+
45
+ /* ── first render only (REQ-006): the surface has no content yet — a
46
+ shimmer window standing in for the host's own skeleton-ui, never
47
+ shown again once the surface has committed once ── */
48
+ [data-a2ui-lifecycle="pending-first"] {
49
+ display: block;
50
+ position: relative;
51
+ overflow: hidden;
52
+ min-block-size: var(--a2ui-root-pending-min-block);
53
+ background: var(--a2ui-root-pending-bg);
54
+ border-radius: var(--a2ui-root-pending-radius);
55
+ }
56
+
57
+ [data-a2ui-lifecycle="pending-first"]::after {
58
+ content: '';
59
+ position: absolute;
60
+ inset: 0;
61
+ background: linear-gradient(
62
+ 90deg,
63
+ transparent 25%,
64
+ var(--a2ui-root-pending-highlight) 50%,
65
+ transparent 75%
66
+ );
67
+ background-size: 200% 100%;
68
+ animation: a2ui-root-pending-shimmer var(--a2ui-root-pending-duration) var(--a2ui-root-pending-easing) infinite;
69
+ }
70
+
71
+ @keyframes a2ui-root-pending-shimmer {
72
+ from { background-position: 200% 0; }
73
+ to { background-position: -200% 0; }
74
+ }
75
+ }
@@ -6,9 +6,12 @@
6
6
  * <a2ui-root src="ws://localhost:8080" transport="ws"></a2ui-root>
7
7
  *
8
8
  * Static / author-driven mode — set the `doc` property (array of A2UI messages)
9
- * and the renderer resets + replays them. Editors and previews can drive the
10
- * surface without opening a transport. Setting `doc` to a new array re-renders
11
- * from scratch (reset() + processAll()).
9
+ * and the renderer transactionally replaces the surfaces it declares (per
10
+ * surfaceId: an existing surface regenerates through the surface lifecycle
11
+ * bracket old content stays visible, no blank frame; spec-a2ui-surface-lifecycle.md,
12
+ * ADR-0061, gh#1364 — OD-2: "the blank flash is the bug"). Any surface the
13
+ * renderer is currently showing but the new doc no longer declares is torn
14
+ * down. Setting `doc` to a new array delegates to `replaceDoc()`.
12
15
  *
13
16
  * const root = document.querySelector('a2ui-root');
14
17
  * root.doc = [
@@ -22,7 +25,10 @@
22
25
  * a2ui-error — stream error (detail: { error })
23
26
  * a2ui-closed — stream ended
24
27
  * a2ui-action — user interaction (detail: { name, sourceComponentId, context })
25
- * doc-replaced — fired after a full doc reset + replay (author-driven mode)
28
+ * doc-replaced — fired after replaceDoc()/`doc =` settles (author-driven mode)
29
+ * surface-pending / surface-committed / surface-aborted — bubble from the
30
+ * renderer's surface lifecycle (REQ-010); `pending` below is their
31
+ * rolled-up reflection.
26
32
  */
27
33
 
28
34
  import { defineIfFree } from '@adia-ai/web-components/core/register';
@@ -43,6 +49,13 @@ class A2UIRoot extends UIElement {
43
49
  loading: { type: Boolean, default: false, reflect: true },
44
50
  active: { type: Boolean, default: false, reflect: true },
45
51
  batch: { type: Boolean, default: false, reflect: true },
52
+ // Surface lifecycle (spec-a2ui-surface-lifecycle.md, ADR-0061, gh#1364):
53
+ // true while ANY surface has an in-flight generation
54
+ // (renderer.pendingSurfaces.length > 0) — the "is something being
55
+ // regenerated" reflection, alongside the transport-level `loading`.
56
+ // NOT yet declared in a2ui-root.yaml's catalog contract — see this
57
+ // PR's Findings for the follow-up owed to the catalog seat.
58
+ pending: { type: Boolean, default: false, reflect: true },
46
59
  };
47
60
 
48
61
  static template = () => null;
@@ -66,12 +79,23 @@ class A2UIRoot extends UIElement {
66
79
  }));
67
80
  };
68
81
 
82
+ // Surface lifecycle (gh#1364) — recompute the rolled-up `pending`
83
+ // reflection whenever any surface's bracket opens or settles. Bubbles
84
+ // from surface roots (children of `this`, since `this` is the renderer's
85
+ // container) so one listener on `this` covers every surface.
86
+ #onLifecycleChange = () => {
87
+ this.pending = !!this.#renderer && this.#renderer.pendingSurfaces.length > 0;
88
+ };
89
+
69
90
  connected() {
70
91
  this.#renderer = new A2UIRenderer(this, registry, { batch: this.batch });
71
92
 
72
93
  if (!this.#bound) {
73
94
  this.#bound = true;
74
95
  this.addEventListener('click', this.#onClick);
96
+ this.addEventListener('surface-pending', this.#onLifecycleChange);
97
+ this.addEventListener('surface-committed', this.#onLifecycleChange);
98
+ this.addEventListener('surface-aborted', this.#onLifecycleChange);
75
99
  }
76
100
  }
77
101
 
@@ -156,16 +180,97 @@ class A2UIRoot extends UIElement {
156
180
  }
157
181
 
158
182
  get doc() { return this.#doc; }
159
- set doc(messages) {
160
- this.#doc = Array.isArray(messages) ? messages : [];
183
+ set doc(messages) { this.replaceDoc(messages); }
184
+
185
+ /**
186
+ * Transactionally replace the rendered doc (spec-a2ui-surface-lifecycle.md
187
+ * OD-2: "the doc setter becomes atomic replace"). Per surfaceId declared
188
+ * in `messages`:
189
+ * - an EXISTING surface regenerates through a begin → apply → commit
190
+ * bracket — old content stays visible until the new answer commits,
191
+ * no blank frame (REQ-001), and a replace-mode sweep removes
192
+ * whatever the new messages no longer declare (REQ-004);
193
+ * - a NEW surface is created, then immediately bracketed too, so first
194
+ * render also gets a `pending-first` window a host can skeleton
195
+ * (REQ-006) — see the events below.
196
+ * A surface the renderer currently holds that this doc no longer
197
+ * declares at all is torn down (deleteSurface) — same end state as the
198
+ * old reset()+replay, just without blanking surfaces that persist
199
+ * across calls (the common case: one long-lived `surfaceId`).
200
+ * `reset()` stays public for hosts that want a hard, unconditional clear.
201
+ *
202
+ * `async` (gh#1364 review finding): `commitSurfaceUpdate` itself is now
203
+ * async (it awaits any buffered `wireComponents` before sweeping), so
204
+ * this method awaits each commit before moving on. Every message this
205
+ * method applies directly goes through `renderer.applyTo(...)`, never
206
+ * `renderer.process(...)` — `process()` queues through the renderer's
207
+ * own rAF batch when `batch: true` (the `<a2ui-root batch>` config this
208
+ * element is constructed with), which would defer create/delete/commit
209
+ * ordering to the NEXT animation frame and break this method's
210
+ * transactional guarantee for exactly the hosts that opted into
211
+ * batching. `applyTo` always applies immediately regardless of the
212
+ * renderer's own batching flag (it's the same immediate path
213
+ * `beginSurfaceUpdate`'s bracket already uses for buffered replay).
214
+ */
215
+ async replaceDoc(messages) {
216
+ const docMessages = Array.isArray(messages) ? messages : [];
161
217
  if (!this.#renderer) {
162
- this.#renderer = new A2UIRenderer(this, registry);
218
+ this.#renderer = new A2UIRenderer(this, registry, { batch: this.batch });
219
+ }
220
+ const renderer = this.#renderer;
221
+
222
+ const declaredSurfaceIds = new Set();
223
+ for (const msg of docMessages) {
224
+ if (msg && typeof msg === 'object' && typeof msg.surfaceId === 'string') {
225
+ declaredSurfaceIds.add(msg.surfaceId);
226
+ }
227
+ }
228
+
229
+ // Open a bracket for every EXISTING surface this doc still declares —
230
+ // before touching any message, so every message that targets one
231
+ // buffers instead of applying blank-then-fill.
232
+ const generationIds = new Map();
233
+ for (const surfaceId of declaredSurfaceIds) {
234
+ if (!renderer.getSurface(surfaceId)) continue;
235
+ const genId = renderer.beginSurfaceUpdate(surfaceId, { reason: 'regenerate' });
236
+ if (genId) generationIds.set(surfaceId, genId);
237
+ }
238
+
239
+ for (const msg of docMessages) {
240
+ const kind = msg?.type || msg?.messageType;
241
+ const surfaceId = msg?.surfaceId;
242
+
243
+ if (kind === 'createSurface') {
244
+ if (renderer.getSurface(surfaceId)) continue; // already exists — re-targeting is the bracket above, never a second createSurface (REQ-002)
245
+ renderer.applyTo(surfaceId, msg); // immediate — never queued behind `batch: true` (see method doc)
246
+ const genId = renderer.beginSurfaceUpdate(surfaceId, { reason: 'regenerate' });
247
+ if (genId) generationIds.set(surfaceId, genId);
248
+ continue;
249
+ }
250
+
251
+ renderer.applyTo(surfaceId, msg); // buffers if `surfaceId` has an open bracket above; applies immediately otherwise
252
+ }
253
+
254
+ for (const [surfaceId, genId] of generationIds) {
255
+ await renderer.commitSurfaceUpdate(surfaceId, genId);
163
256
  }
164
- this.#renderer.reset();
165
- for (const msg of this.#doc) this.#renderer.process(msg);
257
+
258
+ // Tear down whatever the renderer still holds that this doc no longer
259
+ // declares at all — the doc setter's full-replace guarantee, minus the
260
+ // blank flash for surfaces that persisted across the call. Immediate
261
+ // (applyTo), same batching-bypass reasoning as above — a torn-down
262
+ // surface must actually be absent before `doc-replaced` fires, not
263
+ // merely queued to become absent next frame.
264
+ for (const surfaceId of renderer.surfaces) {
265
+ if (!declaredSurfaceIds.has(surfaceId)) {
266
+ renderer.applyTo(surfaceId, { type: 'deleteSurface', surfaceId });
267
+ }
268
+ }
269
+
270
+ this.#doc = docMessages;
166
271
  this.dispatchEvent(new CustomEvent('doc-replaced', {
167
272
  bubbles: true,
168
- detail: { count: this.#doc.length },
273
+ detail: { count: docMessages.length },
169
274
  }));
170
275
  }
171
276
 
@@ -181,6 +286,9 @@ class A2UIRoot extends UIElement {
181
286
  disconnected() {
182
287
  this.#abortCtrl?.abort();
183
288
  this.removeEventListener('click', this.#onClick);
289
+ this.removeEventListener('surface-pending', this.#onLifecycleChange);
290
+ this.removeEventListener('surface-committed', this.#onLifecycleChange);
291
+ this.removeEventListener('surface-aborted', this.#onLifecycleChange);
184
292
  this.#bound = false;
185
293
  this.active = false;
186
294
  }
@@ -58,9 +58,14 @@ events:
58
58
  description: Fired when the stream ends.
59
59
  doc-replaced:
60
60
  description: "Fired after a full doc reset + replay in author-driven mode. detail: { count }"
61
- slots:
61
+ parts:
62
62
  default:
63
- description: The rendered surface. Children are stamped by the A2UI renderer.
63
+ description: >-
64
+ The rendered surface — template-owned anatomy, not a light-DOM
65
+ insertion point (gh#1454). `connected()` constructs `new
66
+ A2UIRenderer(this, registry, …)` targeting the host itself; the
67
+ renderer owns and stamps all rendered children as A2UI messages are
68
+ processed. No consumer content is captured or preserved.
64
69
  states:
65
70
  - name: idle
66
71
  description: Default, ready to connect.
@@ -53,15 +53,16 @@
53
53
  "agent-shell"
54
54
  ],
55
55
  "name": "GenRoot",
56
+ "parts": {
57
+ "default": {
58
+ "description": "Template-owned anatomy, not a light-DOM insertion point (gh#1454). render() unconditionally creates its own `<chat-thread-ui>` / `<canvas-ui>` / (when `[inspector]`) `<inspector-ui>` children and appends them — it never checks for or adopts consumer-authored children of those tags. The previous description (\"Author supplies chat-ui, canvas-ui... children\") documented an authoring model the source has never implemented; corrected here per AGENTS.md's source-wins rule."
59
+ }
60
+ },
56
61
  "related": [
57
62
  "AppShell",
58
63
  "Chat"
59
64
  ],
60
- "slots": {
61
- "default": {
62
- "description": "Author supplies chat-ui, canvas-ui, and optional inspector-ui children."
63
- }
64
- },
65
+ "slots": {},
65
66
  "states": [
66
67
  {
67
68
  "description": "Default mode=chat.",
@@ -32,9 +32,17 @@ props:
32
32
 
33
33
  events: {}
34
34
 
35
- slots:
35
+ parts:
36
36
  default:
37
- description: Author supplies chat-ui, canvas-ui, and optional inspector-ui children.
37
+ description: >-
38
+ Template-owned anatomy, not a light-DOM insertion point (gh#1454).
39
+ render() unconditionally creates its own `<chat-thread-ui>` /
40
+ `<canvas-ui>` / (when `[inspector]`) `<inspector-ui>` children and
41
+ appends them — it never checks for or adopts consumer-authored
42
+ children of those tags. The previous description ("Author supplies
43
+ chat-ui, canvas-ui... children") documented an authoring model the
44
+ source has never implemented; corrected here per AGENTS.md's
45
+ source-wins rule.
38
46
 
39
47
  states:
40
48
  - name: idle
@@ -157,6 +157,7 @@
157
157
  "marketplace"
158
158
  ],
159
159
  "name": "UIIntegrationsPage",
160
+ "parts": {},
160
161
  "related": [
161
162
  "IntegrationCard",
162
163
  "Grid",
@@ -184,12 +185,12 @@
184
185
  },
185
186
  {
186
187
  "description": "Search query yields zero cards.",
187
- "attribute": "data-empty-search",
188
+ "attribute": "empty-search",
188
189
  "name": "empty-search"
189
190
  },
190
191
  {
191
192
  "description": "`integrations` array is empty.",
192
- "attribute": "data-empty-data",
193
+ "attribute": "empty-data",
193
194
  "name": "empty-data"
194
195
  }
195
196
  ],
@@ -337,19 +337,19 @@ class UIIntegrationsPage extends UIElement {
337
337
  const groups = this.#computeGroups();
338
338
  const totalVisible = groups.reduce((sum, g) => sum + g.items.length, 0);
339
339
  if (this.loading) {
340
- this.removeAttribute('data-empty-search');
341
- this.removeAttribute('data-empty-data');
340
+ this.removeAttribute('empty-search');
341
+ this.removeAttribute('empty-data');
342
342
  return;
343
343
  }
344
344
  if (totalVisible === 0 && all.length > 0) {
345
- this.setAttribute('data-empty-search', '');
346
- this.removeAttribute('data-empty-data');
345
+ this.setAttribute('empty-search', '');
346
+ this.removeAttribute('empty-data');
347
347
  } else if (all.length === 0) {
348
- this.setAttribute('data-empty-data', '');
349
- this.removeAttribute('data-empty-search');
348
+ this.setAttribute('empty-data', '');
349
+ this.removeAttribute('empty-search');
350
350
  } else {
351
- this.removeAttribute('data-empty-search');
352
- this.removeAttribute('data-empty-data');
351
+ this.removeAttribute('empty-search');
352
+ this.removeAttribute('empty-data');
353
353
  }
354
354
  }
355
355
 
@@ -146,10 +146,10 @@ states:
146
146
  attribute: loading
147
147
  - name: empty-search
148
148
  description: Search query yields zero cards.
149
- attribute: data-empty-search
149
+ attribute: empty-search
150
150
  - name: empty-data
151
151
  description: "`integrations` array is empty."
152
- attribute: data-empty-data
152
+ attribute: empty-data
153
153
 
154
154
  traits: []
155
155
 
@@ -135,6 +135,7 @@
135
135
  "notification-settings"
136
136
  ],
137
137
  "name": "UINotificationPreferences",
138
+ "parts": {},
138
139
  "related": [
139
140
  "Check",
140
141
  "Switch",
@@ -42,8 +42,11 @@
42
42
  ],
43
43
  "unevaluatedProperties": false,
44
44
  "x-adiaui": {
45
+ "allowedParents": [
46
+ "AdminShell"
47
+ ],
45
48
  "anti_patterns": [],
46
- "category": "interaction",
49
+ "category": "container",
47
50
  "composes": [],
48
51
  "events": {
49
52
  "command-select": {
@@ -63,6 +66,7 @@
63
66
  "quickaction"
64
67
  ],
65
68
  "name": "AdminCommand",
69
+ "parts": {},
66
70
  "related": [
67
71
  "AdminShell",
68
72
  "AdminSidebar",
@@ -4,7 +4,7 @@ name: AdminCommand
4
4
  tag: admin-command
5
5
  status: stable
6
6
  component: AdminCommand
7
- category: interaction
7
+ category: container
8
8
  version: 1
9
9
  description: |
10
10
  Module-tier command palette wrapper — wraps a native <dialog> and the
@@ -75,6 +75,8 @@ states:
75
75
  traits: []
76
76
 
77
77
  a2ui:
78
+ allowedParents:
79
+ - AdminShell
78
80
  rules:
79
81
  - >-
80
82
  admin-command wraps a native <dialog>; the inner <command-ui>
@@ -22,6 +22,9 @@
22
22
  ],
23
23
  "unevaluatedProperties": false,
24
24
  "x-adiaui": {
25
+ "allowedParents": [
26
+ "AdminShell"
27
+ ],
25
28
  "anti_patterns": [],
26
29
  "category": "layout",
27
30
  "composes": [],
@@ -34,6 +37,7 @@
34
37
  "workspace"
35
38
  ],
36
39
  "name": "AdminContent",
40
+ "parts": {},
37
41
  "related": [
38
42
  "AdminShell",
39
43
  "AdminSidebar",
@@ -40,6 +40,8 @@ states:
40
40
  traits: []
41
41
 
42
42
  a2ui:
43
+ allowedParents:
44
+ - AdminShell
43
45
  rules:
44
46
  - >-
45
47
  admin-content is the bespoke replacement for raw <main> inside
@@ -36,6 +36,7 @@
36
36
  "icon-label-badge"
37
37
  ],
38
38
  "name": "AdminEntityItem",
39
+ "parts": {},
39
40
  "related": [
40
41
  "AdminTopbar",
41
42
  "AdminStatusbar",
@@ -22,6 +22,10 @@
22
22
  ],
23
23
  "unevaluatedProperties": false,
24
24
  "x-adiaui": {
25
+ "allowedParents": [
26
+ "AdminContent",
27
+ "AdminScroll"
28
+ ],
25
29
  "anti_patterns": [],
26
30
  "category": "layout",
27
31
  "composes": [],
@@ -33,6 +37,7 @@
33
37
  "container-query-host"
34
38
  ],
35
39
  "name": "AdminPage",
40
+ "parts": {},
36
41
  "related": [
37
42
  "AdminShell",
38
43
  "AdminContent",
@@ -40,6 +40,9 @@ states:
40
40
  traits: []
41
41
 
42
42
  a2ui:
43
+ allowedParents:
44
+ - AdminContent
45
+ - AdminScroll
43
46
  rules:
44
47
  - >-
45
48
  admin-page is the bespoke replacement for <article data-content-root>.
@@ -22,6 +22,9 @@
22
22
  ],
23
23
  "unevaluatedProperties": false,
24
24
  "x-adiaui": {
25
+ "allowedParents": [
26
+ "AdminPage"
27
+ ],
25
28
  "anti_patterns": [],
26
29
  "category": "layout",
27
30
  "composes": [],
@@ -33,6 +36,7 @@
33
36
  "centered-body"
34
37
  ],
35
38
  "name": "AdminPageBody",
39
+ "parts": {},
36
40
  "related": [
37
41
  "AdminPage",
38
42
  "AdminPageHeader",
@@ -30,6 +30,8 @@ states:
30
30
  traits: []
31
31
 
32
32
  a2ui:
33
+ allowedParents:
34
+ - AdminPage
33
35
  rules:
34
36
  - >-
35
37
  admin-page-body is the centered body band of <admin-page>.
@@ -22,6 +22,9 @@
22
22
  ],
23
23
  "unevaluatedProperties": false,
24
24
  "x-adiaui": {
25
+ "allowedParents": [
26
+ "AdminPage"
27
+ ],
25
28
  "anti_patterns": [],
26
29
  "category": "layout",
27
30
  "composes": [],
@@ -35,6 +38,7 @@
35
38
  "action-bar"
36
39
  ],
37
40
  "name": "AdminPageFooter",
41
+ "parts": {},
38
42
  "related": [
39
43
  "AdminPage",
40
44
  "AdminPageHeader",
@@ -34,6 +34,8 @@ states:
34
34
  traits: []
35
35
 
36
36
  a2ui:
37
+ allowedParents:
38
+ - AdminPage
37
39
  rules:
38
40
  - >-
39
41
  admin-page-footer is the sticky bottom band of <admin-page>,
@@ -22,6 +22,9 @@
22
22
  ],
23
23
  "unevaluatedProperties": false,
24
24
  "x-adiaui": {
25
+ "allowedParents": [
26
+ "AdminPage"
27
+ ],
25
28
  "anti_patterns": [],
26
29
  "category": "layout",
27
30
  "composes": [],
@@ -33,6 +36,7 @@
33
36
  "page-band"
34
37
  ],
35
38
  "name": "AdminPageHeader",
39
+ "parts": {},
36
40
  "related": [
37
41
  "AdminPage",
38
42
  "AdminPageBody",