@adia-ai/web-components 0.6.23 → 0.6.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog — @adia-ai/web-components
2
2
 
3
+ ## [0.6.24] — 2026-05-22
4
+
5
+ ### Fixed — `slider-ui` label and suffix template snapshot trap (FB-45)
6
+
7
+ - **`[slot="label"]` and `[slot="suffix"]` now re-sync in `render()`**, resolving the snapshot trap where `label=${expr}` via the template engine rendered the literal `{{p:N}}` placeholder instead of the resolved value. Both spans are updated on every reactive cycle; a missing label span is also created if absent at connection time. File: `components/slider/class.js`.
8
+
9
+ ### Fixed — `toast-ui` demo shell missing `feed.css` import
10
+
11
+ - **`toast.html` now imports `feed/feed.css` + `feed/feed.js`.** `<toast-ui>` renders via `<feed-ui>` — the demo shell was not loading feed's stylesheet, so feed items rendered without background, typography, or spacing. Files: `components/toast/toast.html`.
12
+
3
13
  ## [0.6.23] — 2026-05-22
4
14
 
5
15
  ### Added — `row-ui` `wrap-at="bp"` responsive wrap (`components/row/`)
package/README.md CHANGED
@@ -215,7 +215,7 @@ class MyPanel extends UIElement {
215
215
  The `el` parameter is the element instance — every signal-backed property is reactively read.
216
216
 
217
217
  Full authoring contract: [`docs/specs/component-token-contract.md`](../../docs/specs/component-token-contract.md).
218
- The `adia-ui-author` skill encodes the 20 non-negotiable rules.
218
+ The `adia-ui-authoring` skill encodes the 20 non-negotiable rules.
219
219
 
220
220
  ## Card-n / drawer-ui composition parity
221
221
 
@@ -149,6 +149,21 @@ export class UISlider extends UIFormElement {
149
149
  const valueEl = this.querySelector('[slot="value"]');
150
150
  if (valueEl) valueEl.textContent = this.#format(this.value);
151
151
 
152
+ // §FB-45: label and suffix are read in connected() but may be set later
153
+ // by the template engine (which first writes {{p:N}} then resolves).
154
+ // Re-sync both slots on every render so reactive bindings work.
155
+ const labelEl = this.querySelector('[slot="label"]');
156
+ if (labelEl) {
157
+ labelEl.textContent = this.label;
158
+ } else if (this.label) {
159
+ const header = this.querySelector('[slot="header"]');
160
+ if (header) { const s = document.createElement('span'); s.slot = 'label'; s.textContent = this.label; header.prepend(s); }
161
+ }
162
+ if (this.label) this.setAttribute('aria-label', this.label);
163
+
164
+ const suffixEl = this.querySelector('[slot="suffix"]');
165
+ if (suffixEl) suffixEl.textContent = this.suffix;
166
+
152
167
  this.setAttribute('aria-valuenow', this.value);
153
168
  this.setAttribute('aria-valuetext', `${this.#format(this.value)}${this.suffix ? ' ' + this.suffix : ''}`);
154
169
  this.syncValue(String(this.value));
@@ -105,4 +105,43 @@ describe('slider-ui', () => {
105
105
  await tick();
106
106
  expect(s.getAttribute('aria-valuenow')).toBe('80');
107
107
  });
108
+
109
+ // §FB-45 (color-app, 2026-05-22) — template engine snapshot trap.
110
+ // connected() stamped label/suffix once; render() didn't re-sync them.
111
+ // Template engine writes {{p:N}} first, then resolves to the real value —
112
+ // simulated here by setAttribute('label', placeholder) then setting the
113
+ // real value. Both spans must reflect the final value, not the placeholder.
114
+ it('re-syncs [slot="label"] when label attribute changes after connection', async () => {
115
+ const s = mount('<slider-ui value="50" min="0" max="100"></slider-ui>');
116
+ await tick();
117
+ // No label initially — span absent
118
+ expect(s.querySelector('[slot="label"]')).toBeNull();
119
+
120
+ // Simulate template engine writing placeholder then real value
121
+ s.setAttribute('label', '{{p:7}}');
122
+ await tick();
123
+ s.setAttribute('label', 'Volume');
124
+ await tick();
125
+
126
+ const labelEl = s.querySelector('[slot="label"]');
127
+ expect(labelEl).not.toBeNull();
128
+ expect(labelEl.textContent).toBe('Volume');
129
+ expect(s.getAttribute('aria-label')).toBe('Volume');
130
+ });
131
+
132
+ it('re-syncs [slot="suffix"] when suffix attribute changes after connection', async () => {
133
+ const s = mount('<slider-ui value="50" min="0" max="100" label="Volume" suffix="%"></slider-ui>');
134
+ await tick();
135
+ const suffixEl = s.querySelector('[slot="suffix"]');
136
+ expect(suffixEl).not.toBeNull();
137
+ expect(suffixEl.textContent).toBe('%');
138
+
139
+ // Simulate placeholder → resolved suffix
140
+ s.setAttribute('suffix', '{{p:8}}');
141
+ await tick();
142
+ s.setAttribute('suffix', 'rem');
143
+ await tick();
144
+
145
+ expect(s.querySelector('[slot="suffix"]').textContent).toBe('rem');
146
+ });
108
147
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/web-components",
3
- "version": "0.6.23",
3
+ "version": "0.6.24",
4
4
  "description": "AdiaUI web components \u2014 vanilla custom elements. A2UI runtime (renderer, registry, streams, wiring) lives in @adia-ai/a2ui-runtime.",
5
5
  "type": "module",
6
6
  "types": "./index.d.ts",