@adia-ai/web-components 0.5.10 → 0.5.11

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/USAGE.md CHANGED
@@ -1057,6 +1057,35 @@ const tpl = html`
1057
1057
 
1058
1058
  Same rule applies to nested `<style>` and `<script>` blocks inside `html\`...\`` — anything that contains a literal backtick must be escaped (`\\\``) or rephrased.
1059
1059
 
1060
+ ### §250 — Template parser — invariants + unsupported syntaxes (FEEDBACK-27)
1061
+
1062
+ `html\`\`` is intentionally smaller than `lit-html`. The parser recognizes exactly four placeholder shapes; anything else fails (some loudly, some silently — §250 graduates every documented failure to load-time `console.warn`).
1063
+
1064
+ | Syntax | Behavior | Notes |
1065
+ |---|---|---|
1066
+ | `attr=${val}` (full attribute interpolation) | ✅ Supported. Writes the stringified value to the attribute. | The whole attribute value must be the placeholder — `attr="prefix-${val}"` is **NOT** supported (silent garbage pre-v0.5.3; load-time warn v0.5.3 §152+). |
1067
+ | `.prop=${val}` (property binding) | ✅ Supported. Assigns to the JS property, NOT the DOM attribute. | This is the canonical pattern for boolean state + object values. Custom elements reflect declared `static properties` to DOM attributes automatically — set `.disabled = true` and the host reflects `[disabled]`. |
1068
+ | `@event=${handler}` (event listener) | ✅ Supported. Calls `addEventListener(event, handler)`. | `handler` can be a function or a signal-wrapped function. |
1069
+ | `?attr=${bool}` (Lit-style boolean attribute) | ❌ **NOT supported (load-time `console.warn` v0.5.11 §250).** | Pre-§250: silently registered a literal `?attr` attribute (browser ignored it). v0.5.11 §250 emits a `console.warn` at scan time + degrades to no-op. Use `.attr=${bool}` instead. |
1070
+
1071
+ **Migration for `?attr=${bool}` patterns:**
1072
+
1073
+ ```js
1074
+ // ❌ Lit-style boolean attribute — silently inert pre-v0.5.11; warns + no-ops post-§250
1075
+ html`<button-ui ?disabled=${isLoading}>Save</button-ui>`;
1076
+
1077
+ // ✅ Property binding — the primitive reflects the property to [disabled] for you
1078
+ html`<button-ui .disabled=${isLoading}>Save</button-ui>`;
1079
+ ```
1080
+
1081
+ Why AdiaUI doesn't implement `?attr=`: custom elements declare their reflective property/attribute mapping via `static properties = { disabled: { type: Boolean, reflect: true } }`. The runtime handles the property-to-attribute reflection automatically. Adding a `?attr=` shape would create two valid syntaxes for the same effect — discoverability tax without behavioral benefit.
1082
+
1083
+ **Other documented parser invariants** (closed in earlier cycles):
1084
+
1085
+ - HTML comments containing apostrophes (`<!-- foo's bar -->`) — fixed at parser level in v0.5.3 §155. Native; no consumer action needed.
1086
+ - HTML comments containing quoted attributes (`<!-- attr="value" -->`) — same fix (v0.5.3 §155).
1087
+ - Backticks inside HTML comments — see §221i above (JS-spec footgun; not a parser bug).
1088
+
1060
1089
  ### §221j — Typography token cheatsheet
1061
1090
 
1062
1091
  Quick-reference for component-CSS authoring. Cross-reference [`styles/typography.css`](./styles/typography.css):
package/core/template.js CHANGED
@@ -175,6 +175,25 @@ function scan(fragment, count) {
175
175
  } else if (name[0] === '.') {
176
176
  n.removeAttribute(name);
177
177
  parts[i] = { t: 'p', n, name: name.slice(1), c: undefined, _fx: null };
178
+ } else if (name[0] === '?') {
179
+ // §250 (v0.5.11, FEEDBACK-27): Lit-style boolean attribute syntax
180
+ // (`?attr=${bool}`) is NOT supported. Without this branch, the
181
+ // attribute name registers verbatim including the `?` — silent
182
+ // inert binding. Strip the bogus attribute, warn loudly + degrade
183
+ // to a no-op text-node part so nothing reaches the DOM.
184
+ // Consumers should use `.attr=${bool}` (property binding); AdiaUI
185
+ // primitives reflect properties to DOM attributes automatically.
186
+ // Parallel structural fix to v0.5.3 §152 partial-interpolation warn.
187
+ n.removeAttribute(name);
188
+ // eslint-disable-next-line no-console
189
+ console.warn(
190
+ `[template] Lit-style boolean attribute "${name}=" is not supported.\n` +
191
+ ` Element: <${n.tagName.toLowerCase()}>\n` +
192
+ ` Use .${name.slice(1)}=\${value} (property binding) instead — ` +
193
+ `the primitive reflects the property to the DOM attribute for you.\n` +
194
+ ` See USAGE.md § Template parser — invariants + unsupported syntaxes.`
195
+ );
196
+ parts[i] = { t: 'n', n: document.createTextNode(''), c: undefined, _fx: null };
178
197
  } else {
179
198
  parts[i] = { t: 'a', n, name, c: undefined, _fx: null };
180
199
  }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * core/template.js — focused tests for the §250 (v0.5.11) `?attr=${bool}`
3
+ * silent-failure-trap close. Surfaced by FEEDBACK-27 against v0.5.10 source.
4
+ *
5
+ * Pre-§250: `?disabled=${true}` registered a literal `?disabled` DOM
6
+ * attribute with no console warning. Consumer ergonomics gap — Lit-familiar
7
+ * developers reach for the most ergonomic syntax + ship inert bindings.
8
+ *
9
+ * Post-§250: scan-time `console.warn` fires + the bogus part is degraded to
10
+ * a no-op text-node, so nothing reaches the DOM. Migration path is
11
+ * `.attr=${bool}` (property binding) — primitives reflect properties to DOM
12
+ * attributes automatically.
13
+ */
14
+
15
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
16
+ import { html, stamp } from './template.js';
17
+
18
+ describe('html template — §250 (v0.5.11) ?attr=${bool} silent-failure trap', () => {
19
+ let container;
20
+ let warnSpy;
21
+
22
+ beforeEach(() => {
23
+ container = document.createElement('div');
24
+ document.body.appendChild(container);
25
+ warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
26
+ });
27
+
28
+ afterEach(() => {
29
+ container.remove();
30
+ warnSpy.mockRestore();
31
+ });
32
+
33
+ it('fires console.warn at scan time when ?attr=${bool} is encountered', () => {
34
+ const tpl = html`<div ?disabled=${true}></div>`;
35
+ stamp(tpl, container);
36
+
37
+ expect(warnSpy).toHaveBeenCalledTimes(1);
38
+ const msg = warnSpy.mock.calls[0][0];
39
+ expect(msg).toMatch(/Lit-style boolean attribute/);
40
+ expect(msg).toMatch(/\?disabled=/);
41
+ expect(msg).toMatch(/\.disabled=/); // migration path mentioned
42
+ });
43
+
44
+ it('does NOT register a literal "?attr" attribute on the DOM node', () => {
45
+ const tpl = html`<div ?disabled=${true} data-test="probe"></div>`;
46
+ stamp(tpl, container);
47
+
48
+ const node = container.querySelector('[data-test="probe"]');
49
+ expect(node).not.toBeNull();
50
+ expect(node.getAttribute('?disabled')).toBeNull();
51
+ expect(node.hasAttribute('?disabled')).toBe(false);
52
+ });
53
+
54
+ it('does NOT set the matching DOM attribute either (no Lit-attr semantics)', () => {
55
+ // AdiaUI intentionally does not implement `?attr=${bool}` semantics.
56
+ // The warn + degrade-to-no-op path means truthy `?disabled` does NOT
57
+ // add `[disabled]` to the DOM. Consumer must use `.disabled=` for that.
58
+ const tpl = html`<div ?disabled=${true} data-test="probe"></div>`;
59
+ stamp(tpl, container);
60
+
61
+ const node = container.querySelector('[data-test="probe"]');
62
+ expect(node.hasAttribute('disabled')).toBe(false);
63
+ });
64
+
65
+ it('fires the warn even when the bound value is falsy', () => {
66
+ // Lit's `?attr=${false}` removes the attribute. AdiaUI's contract is
67
+ // "this syntax is not supported, period" — warn fires regardless of
68
+ // truthiness to maximize visibility of the authoring error.
69
+ const tpl = html`<div ?disabled=${false}></div>`;
70
+ stamp(tpl, container);
71
+
72
+ expect(warnSpy).toHaveBeenCalledTimes(1);
73
+ });
74
+
75
+ it('does not warn on canonical .prop=${val} (no false positive)', () => {
76
+ // Make sure the new `?`-prefix branch doesn't accidentally fire on
77
+ // adjacent canonical syntaxes.
78
+ const tpl = html`<div .className=${'x'}></div>`;
79
+ stamp(tpl, container);
80
+
81
+ expect(warnSpy).not.toHaveBeenCalled();
82
+ });
83
+
84
+ it('does not warn on canonical @event=${handler} (no false positive)', () => {
85
+ const tpl = html`<div @click=${() => {}}></div>`;
86
+ stamp(tpl, container);
87
+
88
+ expect(warnSpy).not.toHaveBeenCalled();
89
+ });
90
+
91
+ it('does not warn on canonical attr=${value} (no false positive)', () => {
92
+ const tpl = html`<div data-id=${'42'}></div>`;
93
+ stamp(tpl, container);
94
+
95
+ expect(warnSpy).not.toHaveBeenCalled();
96
+ });
97
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/web-components",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
4
4
  "description": "AdiaUI web components — 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",