@adia-ai/web-components 0.7.6 → 0.7.7

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog — @adia-ai/web-components
2
2
 
3
+ ## [0.7.7] — 2026-06-02
4
+
5
+ ### Fixed
6
+
7
+ - **`[verse]` register now scales `--a-toggle-size` (check / radio / switch) down with its type + controls.** Companion to the 0.7.6 `[verse]` icon/caret fix (`91460aae2`). `--a-toggle-size` (the check / radio / switch control size) is declared at `:root` (`styles/foundation/size.css` = `--a-size-sm − --a-space-1` = 20px @md) and shifted by the global `[size]` rules (`styles/api/sizing.css` = 16/24 at sm/lg), but was **absent from `styles/verse.css`** — so in a verse context check / radio / switch chrome stayed the `:root`-computed 20px against verse's smaller (18/20/24) controls and 11/12/13px type. Added `--a-toggle-size` to all three verse sites (base `[verse]` + `[verse][size="lg"]` + `[verse][size="sm"]`) on clean `--a-space` rungs, one tier down: verse toggle is now **12 / 16 / 20px** at sm/md/lg (was a flat 20px); the regular register is unchanged (16/20/24). The explicit `[size]` tier rules are **required** — verse's `context` layer shadows the lower-layer global `[size]` `--a-toggle-size` rules, the same reason `--a-size` and the icon/caret tokens need them. `--a-space` rungs (not literal px like icon/caret) because the toggle targets land exactly on rungs, keeping the values density-aware and consistent with verse's inset/gap. Verified by `scripts/qa/register-scale-probe.mjs` (extended with a toggle axis): verse 12/16/20, regular 16/20/24, all other registers/axes unchanged. Showcased on the `check` / `radio` / `switch` demos (a "Typography registers" section, mirroring the card / button / text / field / badge / stat pages). File: `styles/verse.css`.
8
+ - **`<agent-artifact-ui>` no longer strands `.map()`-rendered action buttons in the body.** `#build()` grabbed the author's `slot="primary"` / `slot="secondary"` buttons with a `:scope > [slot]` direct-child query, then `this.innerHTML = ''`. Buttons supplied via interpolation (`${actions.map(…)}`) arrive nested in the template engine's `display:contents` / `role="presentation"` wrapper spans, so the direct-child grab missed them and the wipe left them rendered in the artifact *body* instead of the header actions cluster. `#build()` now partitions children with a wrapper-piercing logical walk (the FB-92/96/98 pattern, mirroring `select-ui` `#logicalOptionChildren`), so interpolated AND static action buttons both reach the actions cluster. +2 regression tests. Surfaced by `audit-wrapper-trap` arm 2. File: `components/agent-artifact/agent-artifact.class.js`.
9
+
10
+ ### Changed
11
+
12
+ - **`<drawer-ui>` body inset is now register / size-aware (`--drawer-inset` → `var(--a-inset)`).** `--drawer-inset` hardcoded `var(--a-space-4)` (a literal 16px rung); it now reads `var(--a-inset)`, matching its sibling `card-ui` (`--card-inset: var(--a-inset)`). **Behavior-neutral at the default tier** (`--a-inset-md` == `--a-space-4` == 16px); the change is that the drawer body / header / footer inset (header & footer padding derive from `--drawer-inset`) now scales with `[size]` (sm 14 / lg 18), `[verse]` (12), and `[prose]` (40). drawer's `[padding]` / `[bleed]` are layout *modes* (canvas-bg / no-pad), not a value scale — unchanged. Part of the 0.7.7 inset-alignment audit, which left `page` / `canvas` / `table` filter / `richtext` `pre` as intentional fixed rungs (their literals aren't 16px, so a swap would change behavior). Files: `components/drawer/drawer.css`, `components/drawer/drawer.examples.html`.
13
+ - **CDN bundle rebuilt** — `dist/web-components.min.css` regenerated so the `[verse]` `--a-toggle-size` fix + the drawer inset reach `@adia-ai/web-components@0.7` CDN consumers.
14
+
3
15
  ## [0.7.6] — 2026-06-02
4
16
 
5
17
  ### Added
@@ -139,19 +139,35 @@ export class UIAgentArtifact extends UIElement {
139
139
  // To keep the Light-DOM approach consistent, we wrap existing children
140
140
  // into a body container if one isn't already present.
141
141
 
142
- // Capture any existing non-slotted children to put into the body wrapper
143
- const existingBodyNodes = Array.from(this.childNodes).filter((n) => {
144
- if (n.nodeType === 1) {
145
- const slot = /** @type {Element} */ (n).getAttribute?.('slot') || '';
146
- return slot !== 'primary' && slot !== 'secondary';
142
+ // Partition the author's children by LOGICAL slot, piercing the template
143
+ // engine's display:contents / role="presentation" wrapper spans. A consumer
144
+ // that interpolates the action buttons — `${actions.map(a => html`<button-ui
145
+ // slot="primary">…`)}` gets them nested in wrapper spans (core/template.js
146
+ // wrap()), so the old `:scope > [slot="primary"]` direct-child grab returned
147
+ // nothing and `innerHTML = ''` stranded them in the body. This is the
148
+ // wrapper-trap class (FB-92/96/98); walk + pierce so wrapped action buttons
149
+ // reach the header actions cluster. Wrappers are flattened (display:contents
150
+ // is layout-transparent); everything non-action becomes body.
151
+ const primaryBtns = [];
152
+ const secondaryBtns = [];
153
+ const bodyNodes = [];
154
+ const isWrapper = (el) =>
155
+ el.getAttribute('role') === 'presentation' || el.style?.display === 'contents';
156
+ const partition = (nodes) => {
157
+ for (const n of nodes) {
158
+ if (n.nodeType === 1) {
159
+ const el = /** @type {Element} */ (n);
160
+ if (isWrapper(el)) { partition(el.childNodes); continue; } // pierce; drop the wrapper
161
+ const slot = el.getAttribute('slot') || '';
162
+ if (slot === 'primary') { primaryBtns.push(el); continue; }
163
+ if (slot === 'secondary') { secondaryBtns.push(el); continue; }
164
+ }
165
+ bodyNodes.push(n);
147
166
  }
148
- return true;
149
- });
150
-
151
- // Clear — we'll rebuild, preserving slotted action buttons
152
- const primaryBtns = this.querySelectorAll(':scope > [slot="primary"]');
153
- const secondaryBtns = this.querySelectorAll(':scope > [slot="secondary"]');
167
+ };
168
+ partition(Array.from(this.childNodes));
154
169
 
170
+ // Clear — we'll rebuild around the captured (now-detached) children.
155
171
  this.innerHTML = '';
156
172
 
157
173
  // Header — keyboard-focusable button-style row that toggles collapsed.
@@ -197,7 +213,7 @@ export class UIAgentArtifact extends UIElement {
197
213
  // Body
198
214
  this.#bodyEl = document.createElement('div');
199
215
  this.#bodyEl.setAttribute('data-artifact-body', '');
200
- for (const n of existingBodyNodes) this.#bodyEl.appendChild(n);
216
+ for (const n of bodyNodes) this.#bodyEl.appendChild(n);
201
217
  if (this.collapsed) this.#bodyEl.hidden = true;
202
218
 
203
219
  this.append(this.#headerEl, this.#bodyEl);
@@ -0,0 +1,61 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import '../../core/element.js';
3
+ import './agent-artifact.js';
4
+
5
+ const tick = () => new Promise((r) => queueMicrotask(r));
6
+
7
+ function mount(html) {
8
+ const wrap = document.createElement('div');
9
+ wrap.innerHTML = html;
10
+ document.body.appendChild(wrap);
11
+ return wrap.firstElementChild;
12
+ }
13
+
14
+ describe('agent-artifact-ui', () => {
15
+ beforeEach(() => { document.body.innerHTML = ''; });
16
+
17
+ // Baseline — static slot="primary"/"secondary" buttons route into the header
18
+ // actions cluster, and the default-slot content lands in the body.
19
+ it('routes static action buttons into the header actions cluster (not the body)', async () => {
20
+ const a = mount(`
21
+ <agent-artifact-ui title="Build">
22
+ <button-ui slot="primary">Run</button-ui>
23
+ <button-ui slot="secondary">Cancel</button-ui>
24
+ <p>Body content</p>
25
+ </agent-artifact-ui>
26
+ `);
27
+ await tick();
28
+ const actions = a.querySelector('[data-artifact-actions]');
29
+ const body = a.querySelector('[data-artifact-body]');
30
+ expect(actions).toBeTruthy();
31
+ expect(actions.querySelectorAll('[slot="primary"], [slot="secondary"]').length).toBe(2);
32
+ expect(body.querySelectorAll('[slot="primary"], [slot="secondary"]').length).toBe(0);
33
+ expect(body.textContent).toContain('Body content');
34
+ });
35
+
36
+ // Wrapper-trap regression (audit-wrapper-trap arm 2): action buttons rendered
37
+ // via `.map()`/`repeat()` arrive nested in the template engine's
38
+ // display:contents / role="presentation" wrapper spans. The old `:scope >
39
+ // [slot="primary"]` direct-child grab missed them, so `innerHTML = ''`
40
+ // stranded them in the body. They must reach the actions cluster.
41
+ it('routes WRAPPED (interpolated) action buttons into the actions cluster, not the body', async () => {
42
+ const a = mount(`
43
+ <agent-artifact-ui title="Build">
44
+ <span style="display:contents" role="presentation">
45
+ <span style="display:contents" role="presentation"><button-ui slot="primary">Run</button-ui></span>
46
+ <span style="display:contents" role="presentation"><button-ui slot="secondary">Cancel</button-ui></span>
47
+ </span>
48
+ <p>Body content</p>
49
+ </agent-artifact-ui>
50
+ `);
51
+ await tick();
52
+ const actions = a.querySelector('[data-artifact-actions]');
53
+ const body = a.querySelector('[data-artifact-body]');
54
+ expect(actions.querySelectorAll('[slot="primary"], [slot="secondary"]').length).toBe(2);
55
+ // The regression: before the wrapper-pierce fix these landed in the body.
56
+ expect(body.querySelectorAll('[slot="primary"], [slot="secondary"]').length).toBe(0);
57
+ expect(body.textContent).toContain('Body content');
58
+ // No empty wrapper spans left dangling — they're flattened, not re-appended.
59
+ expect(a.querySelectorAll('[role="presentation"]').length).toBe(0);
60
+ });
61
+ });
@@ -19,7 +19,7 @@
19
19
  /* ── Geometry ── */
20
20
  --drawer-width: 24rem;
21
21
  --drawer-height: 24rem;
22
- --drawer-inset: var(--a-space-4);
22
+ --drawer-inset: var(--a-inset);
23
23
  --drawer-header-pad: var(--drawer-inset);
24
24
  --drawer-footer-pad: var(--drawer-inset);
25
25
  --drawer-header-gap: var(--a-space-2);