@adia-ai/adia-ui-forge 0.8.62 → 0.8.64
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/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +3 -3
- package/CHANGELOG.md +317 -275
- package/README.md +54 -18
- package/__init__.py +8 -8
- package/agents/a2ui-maintenance-agent.corpus.json +7 -7
- package/agents/a2ui-maintenance-agent.md +6 -6
- package/agents/a2ui-planner-agent.md +11 -11
- package/agents/demo-audit-agent.corpus.json +4 -4
- package/agents/demo-audit-agent.md +8 -9
- package/agents/framework-checker.md +10 -11
- package/agents/framework-planner-agent.md +9 -10
- package/agents/gen-ui-review-agent.corpus.json +4 -4
- package/agents/gen-ui-review-agent.md +8 -8
- package/agents/package-release-agent.corpus.json +4 -4
- package/agents/package-release-agent.md +9 -10
- package/agents/primitive-authoring-agent.corpus.json +1 -1
- package/agents/primitive-authoring-agent.md +5 -5
- package/agents/routing-corpus.json +46 -46
- package/codex.interface.json +1 -1
- package/commands/gen-ui-review.md +1 -1
- package/commands/package-release.md +2 -2
- package/package.json +1 -1
- package/plugin.yaml +2 -2
- package/prompts/gen-ui-review.md +1 -1
- package/prompts/package-release.md +2 -2
- package/references/contracts/a2ui-mcp-surface.md +7 -7
- package/references/contracts/migration-guide-format.md +6 -7
- package/references/shared/content-trust.md +3 -5
- package/skills/a2ui-maintenance/SKILL.md +13 -16
- package/skills/a2ui-maintenance/evals/evals.json +2 -2
- package/skills/a2ui-maintenance/evals/routing-corpus.json +1 -1
- package/skills/a2ui-maintenance/references/anti-patterns.md +10 -10
- package/skills/a2ui-maintenance/references/chunk-authoring.md +11 -12
- package/skills/a2ui-maintenance/references/corpus-discipline.md +13 -14
- package/skills/a2ui-maintenance/references/data-model-reactivity.md +22 -25
- package/skills/a2ui-maintenance/references/eval-diagnostics.md +21 -21
- package/skills/a2ui-maintenance/references/format-extension-decisions.md +12 -13
- package/skills/a2ui-maintenance/references/leverage-rules.md +6 -6
- package/skills/a2ui-maintenance/references/mcp-pipeline-ops.md +9 -10
- package/skills/a2ui-maintenance/references/mcp-tool-reference.md +11 -12
- package/skills/a2ui-maintenance/references/pipeline-overview.md +33 -38
- package/skills/a2ui-maintenance/references/semantic-fail-lifting.md +13 -14
- package/skills/a2ui-maintenance/references/strategy-engines.md +14 -16
- package/skills/a2ui-maintenance/references/surface-lifecycle.md +10 -11
- package/skills/a2ui-maintenance/references/zettel-calibration.md +14 -16
- package/skills/component-md-authoring/SKILL.md +22 -22
- package/skills/component-md-authoring/agents/openai.yaml +1 -1
- package/skills/cross-harness-authoring-standards/SKILL.md +19 -21
- package/skills/cross-harness-authoring-standards/agents/openai.yaml +1 -1
- package/skills/cross-harness-compatibility-standards/SKILL.md +35 -34
- package/skills/cross-harness-compatibility-standards/agents/openai.yaml +1 -1
- package/skills/demo-audit/SKILL.md +38 -38
- package/skills/demo-audit/evals/routing-corpus.json +1 -1
- package/skills/demo-audit/references/admin-shell-anatomy.md +21 -25
- package/skills/demo-audit/references/app-shell-pitfalls.md +11 -13
- package/skills/demo-audit/references/auto-fix-allowlist.md +3 -3
- package/skills/demo-audit/references/card-anatomy-sweep.md +10 -11
- package/skills/demo-audit/references/chat-shell-anatomy.md +25 -26
- package/skills/demo-audit/references/editor-shell-anatomy.md +25 -28
- package/skills/demo-audit/references/html-attr-sweep.md +12 -13
- package/skills/demo-audit/references/mode7-status-battery.md +4 -4
- package/skills/demo-audit/references/native-leak-annotations.md +8 -8
- package/skills/demo-audit/references/output-contract-worked-example.md +8 -8
- package/skills/demo-audit/references/probe-discipline-and-escalation.md +2 -2
- package/skills/demo-audit/references/visual-probe-triage.md +21 -23
- package/skills/gen-ui-review/SKILL.md +28 -28
- package/skills/gen-ui-review/agents/openai.yaml +1 -1
- package/skills/gen-ui-review/evals/evals.json +1 -1
- package/skills/gen-ui-review/evals/routing-corpus.json +1 -1
- package/skills/gen-ui-review/references/corpus-html-patterns.md +28 -29
- package/skills/gen-ui-review/references/exit-gate-mechanics.md +5 -5
- package/skills/gen-ui-review/references/lookup-maintenance.md +2 -2
- package/skills/gen-ui-review/references/loop-protocol.md +25 -25
- package/skills/gen-ui-review/references/rubric-cosmetic.md +10 -10
- package/skills/gen-ui-review/references/rubric-decompose.md +15 -16
- package/skills/gen-ui-review/references/rubric-score.md +23 -24
- package/skills/gen-ui-review/references/scorecard-worked-examples.md +12 -12
- package/skills/gen-ui-review/references/scores.schema.json +2 -2
- package/skills/llm-client-maintenance/SKILL.md +12 -12
- package/skills/llm-client-maintenance/evals/routing-corpus.json +1 -1
- package/skills/llm-client-maintenance/references/adapter-contract.md +27 -27
- package/skills/llm-client-maintenance/references/add-a-provider.md +25 -25
- package/skills/llm-client-maintenance/references/bridge-facade.md +31 -31
- package/skills/llm-client-maintenance/references/browser-proxy-boundary.md +17 -17
- package/skills/llm-client-maintenance/references/model-registry.md +14 -14
- package/skills/llm-client-maintenance/references/streaming-sse.md +21 -21
- package/skills/package-release/SKILL.md +21 -23
- package/skills/package-release/evals/evals.json +2 -2
- package/skills/package-release/evals/routing-corpus.json +2 -2
- package/skills/package-release/references/authorization-model.md +6 -8
- package/skills/package-release/references/changelog-discipline.md +25 -27
- package/skills/package-release/references/cut-procedure.md +129 -136
- package/skills/package-release/references/gates-catalog.md +42 -43
- package/skills/package-release/references/independent-package-release.md +14 -15
- package/skills/package-release/references/invariants-detail.md +9 -9
- package/skills/package-release/references/mechanization.md +11 -12
- package/skills/package-release/references/migration-guide-authoring.md +20 -20
- package/skills/package-release/references/notes-authoring.md +20 -20
- package/skills/package-release/references/recovery-paths.md +28 -28
- package/skills/primitive-authoring/SKILL.md +19 -19
- package/skills/primitive-authoring/agents/openai.yaml +1 -1
- package/skills/primitive-authoring/evals/evals.json +2 -2
- package/skills/primitive-authoring/evals/routing-corpus.json +2 -2
- package/skills/primitive-authoring/references/INDEX.md +22 -22
- package/skills/primitive-authoring/references/anti-patterns.md +76 -77
- package/skills/primitive-authoring/references/api-contract.md +63 -65
- package/skills/primitive-authoring/references/authoring-cycle.md +47 -50
- package/skills/primitive-authoring/references/canonical-pattern-index.md +118 -118
- package/skills/primitive-authoring/references/code-style.md +50 -52
- package/skills/primitive-authoring/references/common-gotchas.md +23 -23
- package/skills/primitive-authoring/references/composite-demo-protocol.md +42 -45
- package/skills/primitive-authoring/references/css-patterns.md +97 -97
- package/skills/primitive-authoring/references/for-attribute-event-contract.md +12 -12
- package/skills/primitive-authoring/references/form-control-sizing.md +16 -18
- package/skills/primitive-authoring/references/lifecycle-patterns.md +42 -42
- package/skills/primitive-authoring/references/llm-bridge.md +21 -21
- package/skills/primitive-authoring/references/module-promotion.md +51 -51
- package/skills/primitive-authoring/references/primitive-audit.md +16 -16
- package/skills/primitive-authoring/references/shell-patterns.md +81 -81
- package/skills/primitive-authoring/references/svg-authoring.md +62 -66
- package/skills/primitive-authoring/references/token-contract.md +45 -49
- package/skills/primitive-authoring/references/trait-pages.md +8 -8
- package/skills/primitive-authoring/references/worked-example.md +39 -39
- package/skills/primitive-authoring/references/yaml-contract.md +139 -143
- package/skills/site-deployment/SKILL.md +25 -26
- package/skills/site-deployment/agents/openai.yaml +1 -1
- package/skills/site-deployment/evals/routing-corpus.json +1 -1
- package/skills/site-deployment/references/deploy-playbooks.md +51 -53
- package/skills/site-docs-authoring/SKILL.md +14 -14
- package/skills/site-docs-authoring/agents/openai.yaml +1 -1
- package/skills/site-docs-authoring/evals/evals.json +1 -1
- package/skills/site-docs-authoring/evals/routing-corpus.json +1 -1
- package/skills/site-docs-authoring/intent.md +18 -18
- package/skills/ssr-compatibility/SKILL.md +24 -25
- package/skills/ssr-compatibility/agents/openai.yaml +1 -1
- package/skills/ssr-compatibility/evals/evals.json +1 -1
- package/skills/ssr-compatibility/evals/routing-corpus.json +1 -1
- package/skills/ssr-compatibility/references/consumer-workarounds.md +22 -23
- package/skills/ssr-compatibility/references/failure-shapes.md +84 -91
- package/skills/ssr-compatibility/references/guard-patterns.md +54 -58
- package/skills/ssr-compatibility/references/status-ledger.md +17 -17
- package/skills/ssr-compatibility/references/test-without-linkedom.md +26 -27
|
@@ -15,7 +15,7 @@ Organized by axis.
|
|
|
15
15
|
closable: { type: Boolean, default: true, reflect: true }
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
**What went wrong:** the component did something by default, and the prop named the default behavior. Users couldn't opt out by just setting the attribute
|
|
18
|
+
**What went wrong:** the component did something by default, and the prop named the default behavior. Users couldn't opt out by just setting the attribute, they had to pass `closable="false"` which doesn't even work for Boolean attributes (presence = true; absent = false is the HTML model).
|
|
19
19
|
|
|
20
20
|
**Fix:** flip the name. Default `true` behavior = name the prop for the opt-out.
|
|
21
21
|
|
|
@@ -28,7 +28,7 @@ closable: { type: Boolean, default: true, reflect: true }
|
|
|
28
28
|
| `toggle-group-ui[multiple]` | `toggle-group-ui[single]` |
|
|
29
29
|
| `swiper-ui[pause-on-hover]` | `swiper-ui[noPauseOnHover]` |
|
|
30
30
|
|
|
31
|
-
**Recognition:** if you find yourself writing `default: true` in a `static properties` block, stop
|
|
31
|
+
**Recognition:** if you find yourself writing `default: true` in a `static properties` block, stop: the name is wrong.
|
|
32
32
|
|
|
33
33
|
### AP-02 · Magic-value sentinel instead of `null`
|
|
34
34
|
|
|
@@ -50,11 +50,11 @@ value: { type: Number, default: null, reflect: true }
|
|
|
50
50
|
### AP-03 · `attr:` silent typo
|
|
51
51
|
|
|
52
52
|
```javascript
|
|
53
|
-
// WRONG
|
|
53
|
+
// WRONG, silently ignored
|
|
54
54
|
allowHalf: { type: Boolean, default: false, reflect: true, attr: 'allow-half' }
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
**What went wrong:** the mapper looks for `attribute:`, not `attr:`. The typo is silently dropped. The kebab-case HTML attribute _happens_ to auto- convert to `allow-half` so the bug hides
|
|
57
|
+
**What went wrong:** the mapper looks for `attribute:`, not `attr:`. The typo is silently dropped. The kebab-case HTML attribute _happens_ to auto- convert to `allow-half` so the bug hides, until someone renames `allowHalf` and the attribute never wires.
|
|
58
58
|
|
|
59
59
|
**Fix:** `attribute:` always.
|
|
60
60
|
|
|
@@ -83,7 +83,7 @@ allowHalf: { type: Boolean, default: false, reflect: true, attr: 'allow-half' }
|
|
|
83
83
|
|
|
84
84
|
- Parent holds a current index → `step` (e.g. `timeline-ui[step="3"]`)
|
|
85
85
|
- Parent holds a selection → `value` (e.g. `inspector-ui[value="item-42"]`)
|
|
86
|
-
- Child Boolean state stays as `active
|
|
86
|
+
- Child Boolean state stays as `active`, that's fine.
|
|
87
87
|
|
|
88
88
|
### AP-06 · `error` as a variant
|
|
89
89
|
|
|
@@ -103,7 +103,7 @@ allowHalf: { type: Boolean, default: false, reflect: true, attr: 'allow-half' }
|
|
|
103
103
|
|
|
104
104
|
**What went wrong:** `disabled` has specific form semantics (removed from tab order, blocks submission). On a non-form component, the implicit meaning is "read-only interaction."
|
|
105
105
|
|
|
106
|
-
**Fix:** `readonly
|
|
106
|
+
**Fix:** `readonly`, matches the actual intent.
|
|
107
107
|
|
|
108
108
|
### AP-08 · `multiple` with exclusion semantics
|
|
109
109
|
|
|
@@ -123,7 +123,7 @@ allowHalf: { type: Boolean, default: false, reflect: true, attr: 'allow-half' }
|
|
|
123
123
|
### AP-09 · Native `<input>` / `<select>` / `<textarea>` stamped inside a primitive
|
|
124
124
|
|
|
125
125
|
```javascript
|
|
126
|
-
// WRONG
|
|
126
|
+
// WRONG, command.class.js before gh#1302 (a9d282461^); select.class.js's
|
|
127
127
|
// searchable branch and drilldown.class.js carried the same shape
|
|
128
128
|
this.innerHTML = `
|
|
129
129
|
<header>
|
|
@@ -132,20 +132,20 @@ this.innerHTML = `
|
|
|
132
132
|
</header>…`;
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
**What went wrong:** AdiaUI interactive primitives own their affordance
|
|
135
|
+
**What went wrong:** AdiaUI interactive primitives own their affordance: the host IS the interactive surface: `contenteditable="plaintext-only"` (or a custom non-editable surface), form participation via `UIFormElement` + `ElementInternals`, explicit ARIA on the host, a JS keyboard model per the WAI-ARIA APG, and `--a-*`-token chrome (ADR-0025, 2026-05-11). A native form control under the hood leaks browser chrome the token contract cannot uniformly restyle, imports per-browser keyboard quirks, and splits the value contract. Three primitives stamped a raw `<input>` anyway with no recorded exception, a search field is exactly the case the rule exists for (ADR-0055, gh#1302).
|
|
136
136
|
|
|
137
|
-
**Fix:** compose an in-house surface already in the tree
|
|
137
|
+
**Fix:** compose an in-house surface already in the tree, pick per component:
|
|
138
138
|
|
|
139
|
-
- the `combobox-ui` shape
|
|
140
|
-
- composition of `<input-ui
|
|
139
|
+
- the `combobox-ui` shape, `contenteditable="plaintext-only"` + `role="combobox"` + list filtering (`combobox.class.js:22-23`); now used by `command-ui`'s palette field (`command.class.js:108`) and `select-ui`'s searchable trigger (`select.class.js:442`);
|
|
140
|
+
- composition of `<input-ui>`, the `search-ui` precedent (`search.class.js:19,44`); now used by `drilldown-ui`'s filter field (`drilldown.class.js:184-201`).
|
|
141
141
|
|
|
142
|
-
Behavior parity is part of done: focus order, IME/paste, `inputmode`, and APG-combobox ARIA, verified by a browser probe on the surface
|
|
142
|
+
Behavior parity is part of done: focus order, IME/paste, `inputmode`, and APG-combobox ARIA, verified by a browser probe on the surface, not by tests alone.
|
|
143
143
|
|
|
144
|
-
**The exception list is singular and closed:** `<input-ui type="password">` wraps a native `<input type="password">` (`input.class.js:116,213`) because `-webkit-text-security: disc` masking only paints on a real input
|
|
144
|
+
**The exception list is singular and closed:** `<input-ui type="password">` wraps a native `<input type="password">` (`input.class.js:116,213`) because `-webkit-text-security: disc` masking only paints on a real input, a mechanical justification, not an aesthetic one. Nothing else qualifies by convenience; a new exception is its own ADR, not a comment in the class. **Out of scope:** disclosure widgets (`<details>/<summary>`, e.g. `agent-trace.js:170`'s row expand), the rule covers surfaces that gather user input, participate in `<form>`, or carry a value; a `<details>` toggle does none of these. Widening the rule to all native interactive chrome would be a new decision with its own blast radius.
|
|
145
145
|
|
|
146
|
-
**Gate:** `npm run audit:stamped-native-buttons` (`scripts/dev/audit-stamped-native-buttons.mjs`; runs in PR CI via `.github/workflows/ci.yml`, NOT in the `npm run check` aggregate) scans `packages/web-components/components/**` + `packages/web-modules/**` `.js` for all four control classes ADR-0055 §Consequences named
|
|
146
|
+
**Gate:** `npm run audit:stamped-native-buttons` (`scripts/dev/audit-stamped-native-buttons.mjs`; runs in PR CI via `.github/workflows/ci.yml`, NOT in the `npm run check` aggregate) scans `packages/web-components/components/**` + `packages/web-modules/**` `.js` for all four control classes ADR-0055 §Consequences named, `<button` / `createElement('button')` (gh#276, `SANCTIONED_BUTTON` map), `<input` (ADR-0055 / gh#1302, `SANCTIONED_INPUT`), and since gh#1495 `<select` (`SANCTIONED_SELECT`) and `<textarea` (`SANCTIONED_TEXTAREA`), and fails any hit outside the matching map. The two stamps that widening surfaced are both dispositioned in the maps: `table.class.js` (`createElement('select')`, the numeric-filter operator picker, a value-gathering surface squarely inside the rule, PENDING the same gh#276 filter-popover migration blocker as this file's button/input entries) and `color-picker.class.js` (`createElement('textarea')`, DECIDED gh#1495, keep: an off-screen `execCommand('copy')` clipboard-transport shim, appended and removed within one call, is out of ADR-0025's scope by ADR-0055 §Decision 4's own test, it gathers no user input, joins no `<form>`, carries no user-facing value; the primary path is `navigator.clipboard.writeText` and the shim only runs where the Clipboard API is absent). Entries carry per-file reasons: password is the ADR-0025 exception; `type="file"` pickers (chat-input, upload) and the otp-input / table stamps are listed PENDING their own decision, sanctioned-with-reason is not the same as ratified. A sanctioned entry whose stamp disappears is reported stale so the list only shrinks, and the script's `selftest` proves each element class's positive + negative controls on scratch fixtures (also run in PR CI).
|
|
147
147
|
|
|
148
|
-
Source: ADR-0025 (the rule) · ADR-0055 (conformance amendment, ratified 2026-08-15, gh#1302
|
|
148
|
+
Source: ADR-0025 (the rule) · ADR-0055 (conformance amendment, ratified 2026-08-15, gh#1302, appends to ADR-0025's record; the ADR-0025 text itself is not edited).
|
|
149
149
|
|
|
150
150
|
---
|
|
151
151
|
|
|
@@ -154,14 +154,14 @@ Source: ADR-0025 (the rule) · ADR-0055 (conformance amendment, ratified 2026-08
|
|
|
154
154
|
### AP-T1 · Variant body consumes L2 directly
|
|
155
155
|
|
|
156
156
|
```css
|
|
157
|
-
/* WRONG
|
|
157
|
+
/* WRONG, button.css */
|
|
158
158
|
:scope[variant="danger"]:not([disabled]):hover {
|
|
159
159
|
--button-fg: var(--a-danger); /* L2 */
|
|
160
160
|
--button-border: var(--a-danger); /* L2 */
|
|
161
161
|
}
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
-
**What went wrong:** the variant bypassed the L3 state matrix. When a user enables high-contrast mode, the contrast-mode overrides on `--a-danger-fg-hover` don't reach this component
|
|
164
|
+
**What went wrong:** the variant bypassed the L3 state matrix. When a user enables high-contrast mode, the contrast-mode overrides on `--a-danger-fg-hover` don't reach this component, it's reading the flat L2 token instead of the stateful L3 one.
|
|
165
165
|
|
|
166
166
|
**Fix:** alias from the L3 matrix:
|
|
167
167
|
|
|
@@ -198,7 +198,7 @@ Or, if the component already defines state-aware tokens in `:where(:scope)`, reu
|
|
|
198
198
|
### AP-T3 · Raw px ≥ 3 without justification
|
|
199
199
|
|
|
200
200
|
```css
|
|
201
|
-
/* WRONG
|
|
201
|
+
/* WRONG, 12px is neither a stroke nor intrinsic */
|
|
202
202
|
:scope { padding: 12px; }
|
|
203
203
|
```
|
|
204
204
|
|
|
@@ -220,7 +220,7 @@ Or, if the component already defines state-aware tokens in `:where(:scope)`, reu
|
|
|
220
220
|
### AP-T4 · Component token name mismatches scope
|
|
221
221
|
|
|
222
222
|
```css
|
|
223
|
-
/* WRONG
|
|
223
|
+
/* WRONG, button.css */
|
|
224
224
|
@scope (button-ui) {
|
|
225
225
|
:where(:scope) {
|
|
226
226
|
--btn-bg: var(--a-primary-bg); /* ← stem mismatch */
|
|
@@ -245,7 +245,7 @@ Or, if the component already defines state-aware tokens in `:where(:scope)`, reu
|
|
|
245
245
|
### AP-S1 · Variant body sets layout properties
|
|
246
246
|
|
|
247
247
|
```css
|
|
248
|
-
/* WRONG
|
|
248
|
+
/* WRONG, pagination.css (pre-fix) */
|
|
249
249
|
:scope[variant="button"] [slot="nav"] button {
|
|
250
250
|
width: var(--pagination-button-size);
|
|
251
251
|
height: var(--pagination-button-size);
|
|
@@ -254,16 +254,16 @@ Or, if the component already defines state-aware tokens in `:where(:scope)`, reu
|
|
|
254
254
|
}
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
-
**What went wrong:** `variant="button"` changes the layout (square buttons with specific dimensions)
|
|
257
|
+
**What went wrong:** `variant="button"` changes the layout (square buttons with specific dimensions), that's a mode, not a variant.
|
|
258
258
|
|
|
259
259
|
**Fix:** add `pagination-ui[variant="button"]` to the Sanctioned Mode Attributes table in `.claude/docs/specs/component-token-contract.md`. Document the layout change as intentional.
|
|
260
260
|
|
|
261
|
-
Alternative: refactor to make the attribute cosmetic
|
|
261
|
+
Alternative: refactor to make the attribute cosmetic, if possible.
|
|
262
262
|
|
|
263
263
|
### AP-S2 · Single-block `:scope` (tokens and styles interleaved)
|
|
264
264
|
|
|
265
265
|
```css
|
|
266
|
-
/* WRONG
|
|
266
|
+
/* WRONG, missing the :where(:scope) token block */
|
|
267
267
|
@scope (button-ui) {
|
|
268
268
|
:scope {
|
|
269
269
|
--button-bg: var(--a-primary-bg);
|
|
@@ -280,7 +280,7 @@ Alternative: refactor to make the attribute cosmetic — if possible.
|
|
|
280
280
|
### AP-S3 · `::slotted()` in light-DOM component
|
|
281
281
|
|
|
282
282
|
```css
|
|
283
|
-
/* WRONG
|
|
283
|
+
/* WRONG, shadow DOM syntax */
|
|
284
284
|
::slotted([slot="icon"]) { margin-inline-end: var(--gap); }
|
|
285
285
|
```
|
|
286
286
|
|
|
@@ -297,7 +297,7 @@ Ever. If you need it, specificity is wrong somewhere. Fix the layering.
|
|
|
297
297
|
### AP-S5 · Inline `grid-template-columns` for asymmetric splits
|
|
298
298
|
|
|
299
299
|
```html
|
|
300
|
-
<!-- WRONG
|
|
300
|
+
<!-- WRONG, escape hatch that bypasses the grid-ui vocabulary -->
|
|
301
301
|
<grid-ui gap="4" style="grid-template-columns: 2fr 1fr">
|
|
302
302
|
<card-ui>Chart</card-ui>
|
|
303
303
|
<card-ui>Sidebar</card-ui>
|
|
@@ -309,61 +309,61 @@ Ever. If you need it, specificity is wrong somewhere. Fix the layering.
|
|
|
309
309
|
**Fix:** use `grid-ui[columns="N"]` plus `[span="M"]` on the children. The component ships column variants 1-6, auto-fill, auto-fit, and a per-child `span="2|3|4|5|6|full"` attribute. Compose a 2:1 split as `columns="3"` + the wide child gets `span="2"`; compose a 3:2 split as `columns="5"` + `span="3"` + `span="2"`.
|
|
310
310
|
|
|
311
311
|
```html
|
|
312
|
-
<!-- RIGHT
|
|
312
|
+
<!-- RIGHT, 2:1 via columns=3 and a span=2 child -->
|
|
313
313
|
<grid-ui columns="3" gap="4">
|
|
314
314
|
<card-ui span="2">Chart</card-ui>
|
|
315
315
|
<card-ui>Sidebar</card-ui>
|
|
316
316
|
</grid-ui>
|
|
317
317
|
|
|
318
|
-
<!-- RIGHT
|
|
318
|
+
<!-- RIGHT, 3:2 via columns=5 and span=3 + span=2 -->
|
|
319
319
|
<grid-ui columns="5" gap="4">
|
|
320
320
|
<card-ui span="3">Overview</card-ui>
|
|
321
321
|
<card-ui span="2">Recent Sales</card-ui>
|
|
322
322
|
</grid-ui>
|
|
323
323
|
```
|
|
324
324
|
|
|
325
|
-
**Recognition:** `style="grid-template-columns: …"` anywhere in a page is a signal that either (a) the author doesn't know the `span` attribute exists, or (b) the ratio truly can't be expressed in columns 1-6. The second case is rare
|
|
325
|
+
**Recognition:** `style="grid-template-columns: …"` anywhere in a page is a signal that either (a) the author doesn't know the `span` attribute exists, or (b) the ratio truly can't be expressed in columns 1-6. The second case is rare, most asymmetric splits approximate cleanly to `3:1`, `2:1`, `3:2`, or `4:1`. If the ratio genuinely can't be expressed, it probably should become a separate layout primitive with a named attribute rather than live as an inline style.
|
|
326
326
|
|
|
327
|
-
**Real fix:** admin-dashboard had `style="grid-template-columns: 7fr 5fr"` and `"2fr 1fr"`, analytics-dashboard had one `"2fr 1fr"`. Replaced with `columns="5" span="3"+"2"` and `columns="3" span="2"+default` respectively; the 7:5 ratio shifted to 3:2 (60:40 vs 58:42)
|
|
327
|
+
**Real fix:** admin-dashboard had `style="grid-template-columns: 7fr 5fr"` and `"2fr 1fr"`, analytics-dashboard had one `"2fr 1fr"`. Replaced with `columns="5" span="3"+"2"` and `columns="3" span="2"+default` respectively; the 7:5 ratio shifted to 3:2 (60:40 vs 58:42), imperceptible visually, a win structurally.
|
|
328
328
|
|
|
329
329
|
### AP-S6 · `:scope >` child combinator on a conditionally-rendered part
|
|
330
330
|
|
|
331
331
|
```css
|
|
332
|
-
/* WRONG
|
|
332
|
+
/* WRONG, [data-part="empty"] is rendered behind a `${isEmpty ? … : null}` branch */
|
|
333
333
|
:scope > [data-part="empty"] { display: grid; place-items: center; }
|
|
334
334
|
```
|
|
335
335
|
|
|
336
|
-
**What went wrong:** the template engine wraps every conditional render branch
|
|
336
|
+
**What went wrong:** the template engine wraps every conditional render branch, `${cond ? … : null}` (also `?` / `.map()`), in a `<span style="display:contents">`. That span is a real DOM child, so the conditionally-rendered element is a _grandchild_ of `:scope`, not a direct child. `display:contents` removes the span from layout but NOT from selector matching, so `:scope > [data-part="empty"]` matches nothing and the rule silently no-ops, no error, passes `components --verify`, looks fine in happy-dom. Only a live render shows the un-styled block.
|
|
337
337
|
|
|
338
338
|
**Fix:** use a **descendant** combinator for any conditionally-rendered part; keep `:scope >` only for parts that render unconditionally (those stay direct children).
|
|
339
339
|
|
|
340
340
|
```css
|
|
341
|
-
/* RIGHT
|
|
341
|
+
/* RIGHT, descendant combinator survives the display:contents wrapper */
|
|
342
342
|
:scope [data-part="empty"] { display: grid; place-items: center; }
|
|
343
|
-
:scope > [data-part="header"] { … } /* static part
|
|
343
|
+
:scope > [data-part="header"] { … } /* static part, child combinator OK */
|
|
344
344
|
```
|
|
345
345
|
|
|
346
|
-
**Recognition:** a `[data-part]` rule that "doesn't apply" though the markup looks right, on a part rendered inside a `${cond ? … : null}` / `?` / `.map()` branch. Inspect the live DOM
|
|
346
|
+
**Recognition:** a `[data-part]` rule that "doesn't apply" though the markup looks right, on a part rendered inside a `${cond ? … : null}` / `?` / `.map()` branch. Inspect the live DOM, a `<span style="display:contents">` wrapping the part confirms it. **Recurring class:** integrations-page empty-state, onboarding-checklist complete-CTA, and earlier sightings (bug-51, bug-53). Positive-guidance version: [css-patterns.md](css-patterns.md) §"Conditional-render parts defeat `:scope >`".
|
|
347
347
|
|
|
348
348
|
**Real fix:** integrations-page.css + onboarding-checklist.css had `:scope > [data-part]` on conditionally-rendered parts (empty-state / complete CTA); the layout grid and the separating margin silently dropped. Converted those rules to descendant combinators.
|
|
349
349
|
|
|
350
350
|
### AP-S7 · `align-self: stretch` to vertically center a fixed-height flex child
|
|
351
351
|
|
|
352
352
|
```css
|
|
353
|
-
/* WRONG
|
|
353
|
+
/* WRONG, stretch can't size a child that carries a definite height */
|
|
354
354
|
[slot="field"] > [slot="trailing"] { align-self: stretch; } /* a 20px <kbd-ui> pins to the TOP */
|
|
355
355
|
```
|
|
356
356
|
|
|
357
|
-
**What went wrong:** a flex item with `align-self: stretch` **and** a definite cross-axis size (`height` / `block-size`) does not stretch
|
|
357
|
+
**What went wrong:** a flex item with `align-self: stretch` **and** a definite cross-axis size (`height` / `block-size`) does not stretch, the explicit size wins and the item positions at **flex-start** (top), not center. A `<kbd-ui>` (`height: 1.25rem`) in a 30px chrome sat ~4px high. `stretch` only sizes auto-height items; for a fixed-height child it silently degrades to top-alignment. A sibling `<button-ui>` looked fine only because it had no blocking height and genuinely filled the chrome (center vs. stretch identical for it).
|
|
358
358
|
|
|
359
359
|
**Fix:** to vertically center a row of mixed-height affordances, use `align-self: center` and let each child keep its own token height.
|
|
360
360
|
|
|
361
361
|
```css
|
|
362
|
-
/* RIGHT
|
|
362
|
+
/* RIGHT, center keeps each child's intrinsic height on the vertical center */
|
|
363
363
|
[slot="field"] > [slot="trailing"] { align-self: center; }
|
|
364
364
|
```
|
|
365
365
|
|
|
366
|
-
**Recognition:** a fixed-height inline affordance (kbd / icon / badge) hugging the top of a taller flex container while a full-height sibling looks correct
|
|
366
|
+
**Recognition:** a fixed-height inline affordance (kbd / icon / badge) hugging the top of a taller flex container while a full-height sibling looks correct, suspect `align-self: stretch` on a mixed-height row.
|
|
367
367
|
|
|
368
368
|
**Real fix:** input-ui's leading/trailing affordance slots used `align-self: stretch`; the ⌘K `<kbd-ui>` hint sat 4px high. `center` fixed it and let `<button-ui>` children honor their own `--button-height` token (bug-60).
|
|
369
369
|
|
|
@@ -374,13 +374,13 @@ Ever. If you need it, specificity is wrong somewhere. Fix the layering.
|
|
|
374
374
|
### AP-L1 · Inline arrow passed to `addEventListener`
|
|
375
375
|
|
|
376
376
|
```javascript
|
|
377
|
-
// WRONG
|
|
377
|
+
// WRONG, cot.js (pre-fix)
|
|
378
378
|
this.#summaryEl.addEventListener('click', () => {
|
|
379
379
|
this.collapsed = !this.collapsed;
|
|
380
380
|
});
|
|
381
381
|
```
|
|
382
382
|
|
|
383
|
-
**What went wrong:** `removeEventListener(type, anInlineArrow)` can't match
|
|
383
|
+
**What went wrong:** `removeEventListener(type, anInlineArrow)` can't match, new function instance each time. The listener stays bound. If the component is re-attached, a SECOND listener binds too. Accumulates.
|
|
384
384
|
|
|
385
385
|
**Fix:** stable `#field` arrow.
|
|
386
386
|
|
|
@@ -402,7 +402,7 @@ disconnected() {
|
|
|
402
402
|
### AP-L2 · Duplicate `disconnected()` method
|
|
403
403
|
|
|
404
404
|
```javascript
|
|
405
|
-
// WRONG
|
|
405
|
+
// WRONG, chart.js (pre-fix)
|
|
406
406
|
class UIChart extends UIElement {
|
|
407
407
|
connected() { /* ... */ this.#ro = new ResizeObserver(...); }
|
|
408
408
|
|
|
@@ -435,7 +435,7 @@ disconnected() {
|
|
|
435
435
|
### AP-L3 · Missing `super.connected()` / `super.disconnected()`
|
|
436
436
|
|
|
437
437
|
```javascript
|
|
438
|
-
// WRONG
|
|
438
|
+
// WRONG, UIFormElement subclass missing super call
|
|
439
439
|
class UIInput extends UIFormElement {
|
|
440
440
|
connected() {
|
|
441
441
|
// super.connected() NOT called
|
|
@@ -444,7 +444,7 @@ class UIInput extends UIFormElement {
|
|
|
444
444
|
}
|
|
445
445
|
```
|
|
446
446
|
|
|
447
|
-
**What went wrong:** `ElementInternals` registration skipped. The component doesn't participate in forms
|
|
447
|
+
**What went wrong:** `ElementInternals` registration skipped. The component doesn't participate in forms, no value submission, no validation, no form-reset handling. Silent.
|
|
448
448
|
|
|
449
449
|
**Fix:** `super.connected()` first line of the method. `super.disconnected()` first line of its method too.
|
|
450
450
|
|
|
@@ -485,7 +485,7 @@ connected() {
|
|
|
485
485
|
this.#ro.observe(this);
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
-
// Later
|
|
488
|
+
// Later, new options, recreated
|
|
489
489
|
updateConfig(opts) {
|
|
490
490
|
this.#ro = new ResizeObserver(...); // ← old one leaks
|
|
491
491
|
this.#ro.observe(this);
|
|
@@ -505,7 +505,7 @@ updateConfig(opts) {
|
|
|
505
505
|
### AP-L7 · Empty template wipes authored light-DOM children
|
|
506
506
|
|
|
507
507
|
```javascript
|
|
508
|
-
// WRONG
|
|
508
|
+
// WRONG, drawer.js (pre-fix)
|
|
509
509
|
class UIDrawer extends UIElement {
|
|
510
510
|
static template = () => html``; // ← looks harmless. It isn't.
|
|
511
511
|
|
|
@@ -518,12 +518,12 @@ class UIDrawer extends UIElement {
|
|
|
518
518
|
|
|
519
519
|
**What went wrong:** `UIElement`'s base `connectedCallback` runs an effect that calls `stamp(template(this), this)`. `stamp` calls `mount`, which calls `container.replaceChildren(fragment)`. Even an "empty" `html\`\``produces a non-null result, so`replaceChildren`runs and clears every authored child (header/section/footer) out of the host element. The component's own`render()` then tries to migrate children that no longer exist.
|
|
520
520
|
|
|
521
|
-
Symptom: drawers render with a close button in an empty panel; all authored content is gone. Hard to debug because the children WERE in the DOM at `connected()` time
|
|
521
|
+
Symptom: drawers render with a close button in an empty panel; all authored content is gone. Hard to debug because the children WERE in the DOM at `connected()` time, they get wiped milliseconds later when the effect runs.
|
|
522
522
|
|
|
523
523
|
**Fix:** drop the override entirely. The base class already defines `static template = () => null;`, and `stamp()` is skipped when the result is null:
|
|
524
524
|
|
|
525
525
|
```javascript
|
|
526
|
-
// RIGHT
|
|
526
|
+
// RIGHT, no template override
|
|
527
527
|
class UIDrawer extends UIElement {
|
|
528
528
|
// base UIElement.template returns null → stamp() is skipped →
|
|
529
529
|
// authored light-DOM children survive through render().
|
|
@@ -533,17 +533,17 @@ class UIDrawer extends UIElement {
|
|
|
533
533
|
}
|
|
534
534
|
```
|
|
535
535
|
|
|
536
|
-
**Recognition:** light-DOM components that compose authored children (card-ui, drawer-ui, list-item-ui, menu-ui, anything with slots) must NOT declare any template
|
|
536
|
+
**Recognition:** light-DOM components that compose authored children (card-ui, drawer-ui, list-item-ui, menu-ui, anything with slots) must NOT declare any template, not even an empty one. If the component needs to stamp structural parts (dialog, panel, scrim), use `static parts = {...}` and `this.ensure('name')` instead; parts append once and don't wipe siblings.
|
|
537
537
|
|
|
538
538
|
### AP-L8 · `setAttribute('value', …)` on `input-ui` after first render
|
|
539
539
|
|
|
540
540
|
```javascript
|
|
541
|
-
// WRONG
|
|
541
|
+
// WRONG, expected to update the visible text
|
|
542
542
|
input.setAttribute('value', 'Liam Johnson');
|
|
543
543
|
// …user still sees an empty field
|
|
544
544
|
```
|
|
545
545
|
|
|
546
|
-
**What went wrong:** `input-ui.connected()` copies `this.value` to `#textEl.textContent` exactly once. `render()` does NOT re-sync when the `value` attribute changes externally
|
|
546
|
+
**What went wrong:** `input-ui.connected()` copies `this.value` to `#textEl.textContent` exactly once. `render()` does NOT re-sync when the `value` attribute changes externally: this is intentional, to prevent external re-renders from clobbering in-progress user input. But it means declarative value updates after mount have no visible effect.
|
|
547
547
|
|
|
548
548
|
**Fix:** set the attribute AND mirror the text slot when you need to update the displayed value from outside:
|
|
549
549
|
|
|
@@ -555,12 +555,12 @@ if (textSlot) textSlot.textContent = 'Liam Johnson';
|
|
|
555
555
|
|
|
556
556
|
**Real fix:** drawer-populating setup code (admin-dashboard transaction drawer, report drawer) hit this when filling input-ui fields from a clicked row. Worked around with an explicit text-slot mirror; a proper fix would be for `input-ui.render()` to re-sync `#textEl.textContent` when the `value` property changes while the field isn't focused.
|
|
557
557
|
|
|
558
|
-
**Recognition:** if you're setting an `input-ui` / `textarea-ui` `value` from JavaScript and the field looks empty, you've hit this. `<stat-ui>` and `<select-ui>` do sync on attribute change
|
|
558
|
+
**Recognition:** if you're setting an `input-ui` / `textarea-ui` `value` from JavaScript and the field looks empty, you've hit this. `<stat-ui>` and `<select-ui>` do sync on attribute change: the gap is only in the contenteditable-backed text field.
|
|
559
559
|
|
|
560
560
|
### AP-L9 · Radio cards without a `role="radiogroup"` parent
|
|
561
561
|
|
|
562
562
|
```html
|
|
563
|
-
<!-- WRONG
|
|
563
|
+
<!-- WRONG, radios never cross-deselect -->
|
|
564
564
|
<list-ui>
|
|
565
565
|
<list-item-ui>
|
|
566
566
|
<card-ui>
|
|
@@ -574,7 +574,7 @@ if (textSlot) textSlot.textContent = 'Liam Johnson';
|
|
|
574
574
|
</list-ui>
|
|
575
575
|
```
|
|
576
576
|
|
|
577
|
-
**What went wrong:** `radio-ui.#select` does a sibling lookup via `this.closest('fieldset, [role="radiogroup"]') || this.parentElement`. When each radio sits inside its own `<span slot="icon">`, the "parent" contains only that one radio
|
|
577
|
+
**What went wrong:** `radio-ui.#select` does a sibling lookup via `this.closest('fieldset, [role="radiogroup"]') || this.parentElement`. When each radio sits inside its own `<span slot="icon">`, the "parent" contains only that one radio, no siblings to deselect. Clicking one doesn't uncheck the others, and the initial `checked` state may or may not render visually depending on mount order.
|
|
578
578
|
|
|
579
579
|
**Fix:** wrap the whole group in an element `role="radiogroup"`:
|
|
580
580
|
|
|
@@ -648,9 +648,8 @@ Source: ADR-0027.
|
|
|
648
648
|
|
|
649
649
|
### AP-R2 · Folding two visually-converging widgets into a shared module (ADR-0101)
|
|
650
650
|
|
|
651
|
-
Two widgets can converge visually
|
|
652
|
-
band, same "looks like the same component" impression on the catalog page
|
|
653
|
-
— while carrying fundamentally different content models underneath. That
|
|
651
|
+
Two widgets can converge visually, same tab-strip chrome, same header
|
|
652
|
+
band, same "looks like the same component" impression on the catalog page, while carrying fundamentally different content models underneath. That
|
|
654
653
|
visual overlap alone is not sufficient reason to extract a shared module
|
|
655
654
|
or fold one into the other.
|
|
656
655
|
|
|
@@ -660,7 +659,7 @@ imperatively and repeatedly after connect via `update()`/`setHTML()`) and
|
|
|
660
659
|
shown two fixed ways) look like the same tabbed-view widget. They aren't:
|
|
661
660
|
folding one into the other would force the static-capture component to
|
|
662
661
|
grow a second, incompatible imperative-update mode it doesn't need for the
|
|
663
|
-
job it already does correctly everywhere it's used
|
|
662
|
+
job it already does correctly everywhere it's used, added surface, not
|
|
664
663
|
removed surface. Extracting a shared tab-view module was evaluated and
|
|
665
664
|
rejected for the same reason: no common substrate exists beneath the
|
|
666
665
|
visual layer that both widgets could share without also merging their
|
|
@@ -670,18 +669,18 @@ different content models (`tabs-ui`'s synthesized strip vs. a hand-rolled
|
|
|
670
669
|
**The resolution is a mirror, not a merge:** keep both components on their
|
|
671
670
|
own contracts; restyle the trailing one to match the leading one's chrome
|
|
672
671
|
via scoped CSS only (`@scope (inspector-ui)` overrides copying
|
|
673
|
-
`preview-ui`'s header-tab anatomy, tokens, and typography)
|
|
672
|
+
`preview-ui`'s header-tab anatomy, tokens, and typography), no shared
|
|
674
673
|
component, no deprecation. The tradeoff this accepts: because the
|
|
675
674
|
mirroring is duplicated CSS, not a shared module, a future visual change
|
|
676
675
|
to the pattern's source component doesn't automatically propagate: check
|
|
677
676
|
the mirrored block for parallel drift when you next touch the pattern
|
|
678
|
-
that's being mirrored
|
|
677
|
+
that's being mirrored, the same "primitives that compose primitives
|
|
679
678
|
don't auto-import" discipline ADR-0027 already requires (AP-R1, above),
|
|
680
679
|
now applied to a duplicated CSS block instead of a duplicated import.
|
|
681
680
|
|
|
682
681
|
Before extracting a shared module for two things that "look the same,"
|
|
683
682
|
verify they'd actually share the extracted contract (props, update
|
|
684
|
-
semantics, lifecycle)
|
|
683
|
+
semantics, lifecycle), not just its rendered chrome. If they wouldn't,
|
|
685
684
|
mirror the chrome and keep both components separate.
|
|
686
685
|
|
|
687
686
|
Source: ADR-0101.
|
|
@@ -694,33 +693,33 @@ One line each; every entry is a shipped bug or a CRITICAL gate. Same axes as abo
|
|
|
694
693
|
|
|
695
694
|
### API / silent-failure class
|
|
696
695
|
|
|
697
|
-
- **Components silently accept ANY made-up attribute and no-op** (`text-ui muted`, `card-ui hover-elevate`, `description-list-ui variant="columns"`)
|
|
698
|
-
- **`empty-state-ui` takes `[heading]`, not `[title]
|
|
699
|
-
- **A hardcoded `open` on a `showModal` overlay (`modal-ui` / `drawer-ui`) bricks the whole page
|
|
700
|
-
- **Set `select-ui.options = [...]` for dynamic options, never `appendChild(<option>)
|
|
701
|
-
- **`<thead>/<tbody>/<tr>/<th>/<td>` are foster-parented OUT of the DOM inside any non-`<table>` ancestor, including custom elements
|
|
702
|
-
- **The elements above still get foster-parented inside `table-ui` (no internal `<table>`, `table.class.js:18`)
|
|
696
|
+
- **Components silently accept ANY made-up attribute and no-op** (`text-ui muted`, `card-ui hover-elevate`, `description-list-ui variant="columns"`), check the component's yaml for the real prop list before authoring any attribute.
|
|
697
|
+
- **`empty-state-ui` takes `[heading]`, not `[title]`**, `title=` becomes the invisible native tooltip and the message never renders (the AP-04 collision in the wild).
|
|
698
|
+
- **A hardcoded `open` on a `showModal` overlay (`modal-ui` / `drawer-ui`) bricks the whole page**, no console error; only a live click / `elementFromPoint` probe catches it.
|
|
699
|
+
- **Set `select-ui.options = [...]` for dynamic options, never `appendChild(<option>)`**: the listbox popover is stamped around the INITIAL `<option>` children at connect; later-appended options land outside it as visible flow content.
|
|
700
|
+
- **`<thead>/<tbody>/<tr>/<th>/<td>` are foster-parented OUT of the DOM inside any non-`<table>` ancestor, including custom elements**, gone before JS runs; never author them inside a custom element.
|
|
701
|
+
- **The elements above still get foster-parented inside `table-ui` (no internal `<table>`, `table.class.js:18`), but their TEXT survives, and `table-ui` never clears it**, so a raw `<thead>/<tbody>` mistake renders its own "No data" empty-state stacked on a garbled text dump instead of erroring (gh#921/#923/#924, 3 separate demo files, fix by rewriting to the real `col-def`/`data` contract, never by touching table-ui).
|
|
703
702
|
|
|
704
703
|
### UIElement / reactivity class
|
|
705
704
|
|
|
706
|
-
- **Declaring `textContent` / `innerHTML` / `className` / `id` in `static properties` breaks the native accessor
|
|
707
|
-
- **`customElements.whenDefined(tag)` never rejects; an unimported tag never resolves
|
|
708
|
-
- **A MutationObserver that watches its own host AND mutates it in the callback hangs
|
|
709
|
-
- **A document-listening trait must NOT re-emit the same-named event on its host with `bubbles: true
|
|
710
|
-
- **Parent `render()` that filters children by an attribute the child sets in its OWN `connected()` races the upgrade
|
|
711
|
-
- **Sync icon-registry checks (`isIconName` / `hasIcon`) at connect race the async manifest on static (non-Vite) deploys
|
|
705
|
+
- **Declaring `textContent` / `innerHTML` / `className` / `id` in `static properties` breaks the native accessor**, `installProps` swaps it for a signal, so `el.textContent = ''` becomes a signal write, not a child-wipe.
|
|
706
|
+
- **`customElements.whenDefined(tag)` never rejects; an unimported tag never resolves**, a `Promise.all([...]).then(bootstrap)` gate hangs forever with no console error (chrome renders via tag-keyed CSS; the page is just "empty").
|
|
707
|
+
- **A MutationObserver that watches its own host AND mutates it in the callback hangs**, drain with `this.#observer.takeRecords()` after every absorb pass.
|
|
708
|
+
- **A document-listening trait must NOT re-emit the same-named event on its host with `bubbles: true`**, the bubble re-reaches document and re-fires the handler (infinite loop); use distinct incoming vs outgoing names.
|
|
709
|
+
- **Parent `render()` that filters children by an attribute the child sets in its OWN `connected()` races the upgrade**, match by tag name too.
|
|
710
|
+
- **Sync icon-registry checks (`isIconName` / `hasIcon`) at connect race the async manifest on static (non-Vite) deploys**, pair with `whenIconRegistryReady` deferred re-stamping.
|
|
712
711
|
|
|
713
712
|
### Registration / packaging class
|
|
714
713
|
|
|
715
|
-
- **Composites render internal `*-ui` tags the author never wrote** (`chat-input-ui` stamps `textarea-ui` + `select-ui`)
|
|
716
|
-
- **A NEW yaml prop with `reflect: true` is not load-bearing alone
|
|
717
|
-
- **Every `[slot="X"]` a component's CSS positions MUST be declared in the yaml `slots:` block** (including auto-stamped internal slots)
|
|
718
|
-
- **A new primitive's CSS must be registered in `packages/web-components/styles/components.css`** (the hand-maintained CSS aggregator)
|
|
714
|
+
- **Composites render internal `*-ui` tags the author never wrote** (`chat-input-ui` stamps `textarea-ui` + `select-ui`), a consumer importing only the composite must also import those primitives (or the cluster barrel `packages/web-modules/<cluster>/index.js`) or they stay unregistered and collapse to 0px.
|
|
715
|
+
- **A NEW yaml prop with `reflect: true` is not load-bearing alone**: the class's `static properties` block is hand-authored, not codegen'd from yaml; add both.
|
|
716
|
+
- **Every `[slot="X"]` a component's CSS positions MUST be declared in the yaml `slots:` block** (including auto-stamped internal slots), the slot-vocab-vs-CSS audit in `npm run check` fails CRITICAL otherwise.
|
|
717
|
+
- **A new primitive's CSS must be registered in `packages/web-components/styles/components.css`** (the hand-maintained CSS aggregator), separate from the `components/index.js` JS barrel; miss it and the element registers but renders unstyled.
|
|
719
718
|
- **A primitive that programmatically creates a form-bearing primitive needs a `COMPOSITION_PAIR` entry in `scripts/release/check-composes-passthrough.mjs`** or the passthrough gate flags it.
|
|
720
719
|
|
|
721
720
|
## Meta-pattern: the "just this once" trap
|
|
722
721
|
|
|
723
|
-
All of these bugs started as "just this once" exceptions
|
|
722
|
+
All of these bugs started as "just this once" exceptions, a component author thought "I'll fix it later," or "this component is special," or "the linter isn't catching it so it's fine." Each "just this once" became a template for the next author.
|
|
724
723
|
|
|
725
724
|
The audit found 100 findings in iteration 1. Fifteen were novel; eighty-five were different authors cargo-culting the same five shapes.
|
|
726
725
|
|