@adia-ai/adia-ui-forge 0.1.2

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 (136) hide show
  1. package/.claude-plugin/plugin.json +23 -0
  2. package/CHANGELOG.md +26 -0
  3. package/README.md +54 -0
  4. package/bin/forge-lint +263 -0
  5. package/bin/lib/audit-axes.mjs +555 -0
  6. package/bin/lib/dry-run-irreversible.mjs +236 -0
  7. package/bin/lib/run-skill-evals.mjs +487 -0
  8. package/bin/lib/teach-router.mjs +250 -0
  9. package/commands/adia-forge-a2ui.md +10 -0
  10. package/commands/adia-forge-author.md +10 -0
  11. package/commands/adia-forge-dogfood.md +8 -0
  12. package/commands/adia-forge-llm.md +8 -0
  13. package/commands/adia-forge-orient.md +10 -0
  14. package/commands/adia-forge-release.md +8 -0
  15. package/commands/adia-forge-review.md +10 -0
  16. package/hooks/hooks.json +15 -0
  17. package/package.json +41 -0
  18. package/references/shared/content-trust.md +76 -0
  19. package/references/shared/pev-rationale.md +64 -0
  20. package/references/shared/skill-conventions.md +133 -0
  21. package/skills/adia-ui-a2ui/CHANGELOG.md +32 -0
  22. package/skills/adia-ui-a2ui/SKILL.md +243 -0
  23. package/skills/adia-ui-a2ui/evals/adversarial-corpus.json +75 -0
  24. package/skills/adia-ui-a2ui/evals/routing-corpus.json +31 -0
  25. package/skills/adia-ui-a2ui/evals/teach-routing-cases.json +100 -0
  26. package/skills/adia-ui-a2ui/references/anti-patterns.md +24 -0
  27. package/skills/adia-ui-a2ui/references/chunk-authoring.md +88 -0
  28. package/skills/adia-ui-a2ui/references/corpus-discipline.md +56 -0
  29. package/skills/adia-ui-a2ui/references/eval-diagnostics.md +127 -0
  30. package/skills/adia-ui-a2ui/references/fragment-graph.md +91 -0
  31. package/skills/adia-ui-a2ui/references/mcp-pipeline-ops.md +106 -0
  32. package/skills/adia-ui-a2ui/references/mcp-tool-reference.md +398 -0
  33. package/skills/adia-ui-a2ui/references/pipeline-overview.md +175 -0
  34. package/skills/adia-ui-a2ui/references/semantic-fail-lifting.md +120 -0
  35. package/skills/adia-ui-a2ui/references/strategy-engines.md +111 -0
  36. package/skills/adia-ui-a2ui/references/teach-protocol.md +220 -0
  37. package/skills/adia-ui-a2ui/references/zettel-calibration.md +93 -0
  38. package/skills/adia-ui-a2ui/scripts/audit-a2ui-roster.mjs +96 -0
  39. package/skills/adia-ui-a2ui/scripts/teach-route.mjs +157 -0
  40. package/skills/adia-ui-a2ui/skill.json +38 -0
  41. package/skills/adia-ui-authoring/CHANGELOG.md +32 -0
  42. package/skills/adia-ui-authoring/SKILL.md +256 -0
  43. package/skills/adia-ui-authoring/assets/case-studies/admin-shell-decomposition.md +101 -0
  44. package/skills/adia-ui-authoring/assets/case-studies/maxtokens-32768-discovery.md +109 -0
  45. package/skills/adia-ui-authoring/assets/case-studies/theme-panel-promotion.md +113 -0
  46. package/skills/adia-ui-authoring/evals/adversarial-design-plan-gates.json +138 -0
  47. package/skills/adia-ui-authoring/evals/routing-corpus.json +245 -0
  48. package/skills/adia-ui-authoring/references/anti-patterns.md +606 -0
  49. package/skills/adia-ui-authoring/references/api-contract.md +205 -0
  50. package/skills/adia-ui-authoring/references/authoring-cycle.md +211 -0
  51. package/skills/adia-ui-authoring/references/canonical-pattern-index.md +179 -0
  52. package/skills/adia-ui-authoring/references/code-style.md +329 -0
  53. package/skills/adia-ui-authoring/references/common-gotchas.md +93 -0
  54. package/skills/adia-ui-authoring/references/composite-demo-protocol.md +1084 -0
  55. package/skills/adia-ui-authoring/references/css-patterns.md +364 -0
  56. package/skills/adia-ui-authoring/references/lifecycle-patterns.md +302 -0
  57. package/skills/adia-ui-authoring/references/llm-bridge.md +254 -0
  58. package/skills/adia-ui-authoring/references/module-promotion.md +289 -0
  59. package/skills/adia-ui-authoring/references/primitive-audit.md +123 -0
  60. package/skills/adia-ui-authoring/references/shell-patterns.md +561 -0
  61. package/skills/adia-ui-authoring/references/teach-protocol.md +428 -0
  62. package/skills/adia-ui-authoring/references/token-contract.md +120 -0
  63. package/skills/adia-ui-authoring/references/worked-example.md +351 -0
  64. package/skills/adia-ui-authoring/references/yaml-contract.md +224 -0
  65. package/skills/adia-ui-authoring/scripts/audit-authoring-roster.mjs +148 -0
  66. package/skills/adia-ui-authoring/scripts/build-canonical-pattern-index.mjs +199 -0
  67. package/skills/adia-ui-authoring/skill.json +45 -0
  68. package/skills/adia-ui-dogfood/CHANGELOG.md +17 -0
  69. package/skills/adia-ui-dogfood/README.md +62 -0
  70. package/skills/adia-ui-dogfood/SKILL.md +866 -0
  71. package/skills/adia-ui-dogfood/scripts/analyze.mjs +603 -0
  72. package/skills/adia-ui-dogfood/skill.json +40 -0
  73. package/skills/adia-ui-forge/SKILL.md +88 -0
  74. package/skills/adia-ui-forge/evals/routing-corpus.json +30 -0
  75. package/skills/adia-ui-gen-review/CHANGELOG.md +108 -0
  76. package/skills/adia-ui-gen-review/SKILL.md +266 -0
  77. package/skills/adia-ui-gen-review/references/loop-protocol.md +712 -0
  78. package/skills/adia-ui-gen-review/references/rubric-cosmetic.md +144 -0
  79. package/skills/adia-ui-gen-review/references/rubric-decompose.md +117 -0
  80. package/skills/adia-ui-gen-review/references/rubric-score.md +249 -0
  81. package/skills/adia-ui-gen-review/references/scores.schema.json +125 -0
  82. package/skills/adia-ui-gen-review/references/teach-protocol.md +214 -0
  83. package/skills/adia-ui-gen-review/scripts/gen-review-coverage-audit.mjs +127 -0
  84. package/skills/adia-ui-gen-review/scripts/gen-review-decompose.mjs +569 -0
  85. package/skills/adia-ui-gen-review/scripts/gen-review-status.mjs +176 -0
  86. package/skills/adia-ui-gen-review/scripts/validate-cycle-scores.mjs +198 -0
  87. package/skills/adia-ui-gen-review/skill.json +22 -0
  88. package/skills/adia-ui-llm/CHANGELOG.md +25 -0
  89. package/skills/adia-ui-llm/SKILL.md +165 -0
  90. package/skills/adia-ui-llm/evals/adversarial-corpus.json +75 -0
  91. package/skills/adia-ui-llm/evals/routing-corpus.json +30 -0
  92. package/skills/adia-ui-llm/evals/teach-routing-cases.json +73 -0
  93. package/skills/adia-ui-llm/references/adapter-contract.md +99 -0
  94. package/skills/adia-ui-llm/references/add-a-provider.md +90 -0
  95. package/skills/adia-ui-llm/references/bridge-facade.md +94 -0
  96. package/skills/adia-ui-llm/references/browser-proxy-boundary.md +89 -0
  97. package/skills/adia-ui-llm/references/model-registry.md +75 -0
  98. package/skills/adia-ui-llm/references/streaming-sse.md +95 -0
  99. package/skills/adia-ui-llm/references/teach-protocol.md +78 -0
  100. package/skills/adia-ui-llm/scripts/audit-llm-roster.mjs +93 -0
  101. package/skills/adia-ui-llm/scripts/teach-route.mjs +119 -0
  102. package/skills/adia-ui-llm/skill.json +33 -0
  103. package/skills/adia-ui-release/CHANGELOG.md +23 -0
  104. package/skills/adia-ui-release/SKILL.md +295 -0
  105. package/skills/adia-ui-release/assets/case-studies/2026-05-20-batch-push-v0.6.14-v0.6.15.md +144 -0
  106. package/skills/adia-ui-release/assets/case-studies/2026-05-20-corpus-drift-remediation-v0.6.15.md +155 -0
  107. package/skills/adia-ui-release/assets/case-studies/2026-05-20-version-skip-correction-v0.6.12.md +114 -0
  108. package/skills/adia-ui-release/assets/case-studies/2026-05-21-author-from-scratch-v0.6.18.md +139 -0
  109. package/skills/adia-ui-release/assets/case-studies/2026-05-21-feedback37-retraction-v0.6.21.md +124 -0
  110. package/skills/adia-ui-release/assets/case-studies/2026-05-21-fn1-enrichment-pass-v0.6.19.md +125 -0
  111. package/skills/adia-ui-release/assets/case-studies/2026-05-21-stale-test-detection-v0.6.20.md +142 -0
  112. package/skills/adia-ui-release/assets/case-studies/2026-05-23-freshness-gate-recovery-v0.6.32.md +97 -0
  113. package/skills/adia-ui-release/assets/case-studies/2026-05-26-catalog-drift-recurring-v0.6.40.md +147 -0
  114. package/skills/adia-ui-release/assets/templates/stub-changelog.template.md +22 -0
  115. package/skills/adia-ui-release/evals/evals.json +164 -0
  116. package/skills/adia-ui-release/references/changelog-discipline.md +250 -0
  117. package/skills/adia-ui-release/references/cycle-happy-path.md +520 -0
  118. package/skills/adia-ui-release/references/exe-deploy.md +149 -0
  119. package/skills/adia-ui-release/references/gates-catalog.md +778 -0
  120. package/skills/adia-ui-release/references/ledger-discipline.md +232 -0
  121. package/skills/adia-ui-release/references/migration-guide-authoring.md +174 -0
  122. package/skills/adia-ui-release/references/multi-agent-baseline.md +207 -0
  123. package/skills/adia-ui-release/references/notes-authoring.md +212 -0
  124. package/skills/adia-ui-release/references/recovery-paths.md +215 -0
  125. package/skills/adia-ui-release/references/rollup-notes.md +208 -0
  126. package/skills/adia-ui-release/references/teach-protocol.md +468 -0
  127. package/skills/adia-ui-release/scripts/assert-monorepo-root.mjs +49 -0
  128. package/skills/adia-ui-release/scripts/audit-gate-roster.mjs +196 -0
  129. package/skills/adia-ui-release/scripts/bump.mjs +118 -0
  130. package/skills/adia-ui-release/scripts/dispatch-publish.mjs +151 -0
  131. package/skills/adia-ui-release/scripts/insert-stub.mjs +127 -0
  132. package/skills/adia-ui-release/scripts/make-ledger.mjs +179 -0
  133. package/skills/adia-ui-release/scripts/promote-unreleased.mjs +88 -0
  134. package/skills/adia-ui-release/scripts/release-pack.mjs +350 -0
  135. package/skills/adia-ui-release/scripts/tag-lockstep.mjs +110 -0
  136. package/skills/adia-ui-release/skill.json +75 -0
@@ -0,0 +1,351 @@
1
+ # Worked Examples
2
+
3
+ Two annotated examples: a light presentation component (`<badge-ui>`) and a form-associated component (`<counter-ui>`). Copy the structure; adapt the content.
4
+
5
+ ## Example 1 — Light presentation component: `<badge-ui>`
6
+
7
+ A small inline container for status labels. Supports cosmetic variants (accent/success/danger) and an optional remove button that fires a `remove` event.
8
+
9
+ ### File layout
10
+
11
+ ```text
12
+ packages/web-components/components/badge/
13
+ badge.js
14
+ badge.css
15
+ ```
16
+
17
+ ### `badge.js`
18
+
19
+ ```javascript
20
+ import { AdiaElement } from '../../core/element.js';
21
+
22
+ /**
23
+ * <badge-ui> — Compact inline status label.
24
+ *
25
+ * Props:
26
+ * variant — 'neutral' | 'accent' | 'success' | 'danger' (default 'neutral')
27
+ * removable — show an inline × button that fires 'remove' on click
28
+ *
29
+ * Events:
30
+ * remove — user clicked the × button (bubbles)
31
+ */
32
+ class AdiaBadge extends AdiaElement {
33
+ static properties = {
34
+ variant: { type: String, default: 'neutral', reflect: true },
35
+ removable: { type: Boolean, default: false, reflect: true },
36
+ };
37
+
38
+ static template = () => null;
39
+
40
+ #removeBtn = null;
41
+
42
+ #onRemove = (e) => {
43
+ e.stopPropagation();
44
+ this.dispatchEvent(new CustomEvent('remove', { bubbles: true }));
45
+ };
46
+
47
+ connected() {
48
+ // Lazy-append the remove button on first render when the attribute is present.
49
+ // If authors want to control the markup, they can pre-slot their own button
50
+ // and we skip the injection.
51
+ if (this.removable && !this.querySelector('[data-badge-remove]')) {
52
+ this.#removeBtn = document.createElement('button');
53
+ this.#removeBtn.type = 'button';
54
+ this.#removeBtn.setAttribute('data-badge-remove', '');
55
+ this.#removeBtn.setAttribute('aria-label', 'Remove');
56
+ this.#removeBtn.textContent = '×';
57
+ this.appendChild(this.#removeBtn);
58
+ this.#removeBtn.addEventListener('click', this.#onRemove);
59
+ }
60
+ }
61
+
62
+ disconnected() {
63
+ this.#removeBtn?.removeEventListener('click', this.#onRemove);
64
+ this.#removeBtn = null;
65
+ }
66
+ }
67
+
68
+ customElements.define('badge-ui', AdiaBadge);
69
+ export { AdiaBadge };
70
+ ```
71
+
72
+ **Annotations:**
73
+
74
+ - **Class name `AdiaBadge`, tag `badge-ui`** — three-way naming.
75
+ - **`variant` default `'neutral'`** — string enum, default is the absent- attribute state. No special behavior to "opt into."
76
+ - **`removable: default false`** — Boolean false (rule 1). Reflects so CSS can match `:scope[removable]`.
77
+ - **`#removeBtn` private field** — cached DOM ref, nulled on teardown.
78
+ - **`#onRemove` stable arrow** — reference equality works for `removeEventListener`.
79
+ - **Symmetric connected/disconnected** — single listener, single removal.
80
+ - **Idempotent `connected()`** — checks `querySelector('[data-badge-remove]')` before injecting, so an author who pre-slots their own button isn't overridden.
81
+
82
+ ### `badge.css`
83
+
84
+ ```css
85
+ @scope (badge-ui) {
86
+ :where(:scope) {
87
+ /* ── Tokens ── */
88
+ --badge-bg: var(--a-bg-muted);
89
+ --badge-fg: var(--a-fg);
90
+ --badge-border: 1px solid var(--a-border-subtle);
91
+ --badge-radius: var(--a-radius-sm);
92
+ --badge-padding: var(--a-space-1) var(--a-space-2);
93
+ --badge-gap: var(--a-space-1);
94
+ --badge-font-size: var(--a-caption-size);
95
+ }
96
+
97
+ :scope {
98
+ /* ── Base styles — consume component tokens only ── */
99
+ display: inline-flex;
100
+ align-items: center;
101
+ gap: var(--badge-gap);
102
+ padding: var(--badge-padding);
103
+ background: var(--badge-bg);
104
+ color: var(--badge-fg);
105
+ border: var(--badge-border);
106
+ border-radius: var(--badge-radius);
107
+ font-size: var(--badge-font-size);
108
+ line-height: 1;
109
+ white-space: nowrap;
110
+ }
111
+
112
+ /* ── Variants — token-only overrides ── */
113
+ :scope[variant="accent"] {
114
+ --badge-bg: var(--a-accent-bg);
115
+ --badge-fg: var(--a-accent-fg);
116
+ --badge-border: 1px solid transparent;
117
+ }
118
+ :scope[variant="success"] {
119
+ --badge-bg: var(--a-success-bg);
120
+ --badge-fg: var(--a-success-fg);
121
+ --badge-border: 1px solid transparent;
122
+ }
123
+ :scope[variant="danger"] {
124
+ --badge-bg: var(--a-danger-bg);
125
+ --badge-fg: var(--a-danger-fg);
126
+ --badge-border: 1px solid transparent;
127
+ }
128
+
129
+ /* ── Removable state ── */
130
+ :scope[removable] > [data-badge-remove] {
131
+ all: unset;
132
+ cursor: pointer;
133
+ padding: 0 var(--a-space-1);
134
+ margin-inline-start: var(--a-space-1);
135
+ color: inherit;
136
+ opacity: 0.7;
137
+ border-radius: var(--a-radius-sm);
138
+ }
139
+ :scope[removable] > [data-badge-remove]:hover {
140
+ opacity: 1;
141
+ background: color-mix(in oklab, currentColor 15%, transparent);
142
+ }
143
+ :scope[removable] > [data-badge-remove]:focus-visible {
144
+ outline: 2px solid var(--a-focus-ring);
145
+ outline-offset: 1px;
146
+ }
147
+ }
148
+ ```
149
+
150
+ **Annotations:**
151
+
152
+ - **Two blocks:** `:where(:scope)` for tokens, `:scope` for base. Variants and state third.
153
+ - **Variants override tokens only.** No `display`, `padding`, etc. in the variant bodies — they just reassign the component's token values.
154
+ - **Zero raw colors.** Every color is a token.
155
+ - **Zero raw px** — spacing via `--a-space-*`, radius via `--a-radius-*`. The only numeric literal is `1px` for the border (allowed carve-out).
156
+ - **Stem `--badge-*`** matches the scope tag `badge-ui`.
157
+ - **`color-mix()` on `currentColor`** is the idiomatic hover tint — stays theme-aware without introducing a new token.
158
+
159
+ ---
160
+
161
+ ## Example 2 — Form-associated component: `<counter-ui>`
162
+
163
+ A numeric input that shows value + increment/decrement buttons. Form- participating — submits a string value with the host form.
164
+
165
+ ### `counter.js`
166
+
167
+ ```javascript
168
+ import { AdiaFormElement } from '../../core/form.js';
169
+
170
+ /**
171
+ * <counter-ui> — Integer counter with +/− controls. Form-associated.
172
+ *
173
+ * Props:
174
+ * value — number, default 0 (syncs to form value as string)
175
+ * min — number, default null (no lower bound)
176
+ * max — number, default null (no upper bound)
177
+ * step — number, default 1
178
+ */
179
+ class AdiaCounter extends AdiaFormElement {
180
+ static properties = {
181
+ ...AdiaFormElement.properties,
182
+ value: { type: Number, default: 0, reflect: true },
183
+ min: { type: Number, default: null, reflect: true },
184
+ max: { type: Number, default: null, reflect: true },
185
+ step: { type: Number, default: 1, reflect: true },
186
+ };
187
+
188
+ static template = () => null;
189
+
190
+ #decBtn = null;
191
+ #incBtn = null;
192
+ #valueEl = null;
193
+
194
+ #onDecrement = () => this.#commit(this.value - this.step);
195
+ #onIncrement = () => this.#commit(this.value + this.step);
196
+
197
+ #commit(next) {
198
+ if (this.min != null) next = Math.max(this.min, next);
199
+ if (this.max != null) next = Math.min(this.max, next);
200
+ if (next === this.value) return;
201
+ this.value = next;
202
+ this.syncValue(String(next)); // inherited from AdiaFormElement
203
+ this.dispatchEvent(new Event('change', { bubbles: true }));
204
+ this.render();
205
+ }
206
+
207
+ connected() {
208
+ super.connected(); // registers ElementInternals — do not omit
209
+
210
+ if (!this.querySelector('[data-counter-dec]')) {
211
+ this.innerHTML = `
212
+ <button type="button" data-counter-dec aria-label="Decrement">−</button>
213
+ <span data-counter-value>${this.value}</span>
214
+ <button type="button" data-counter-inc aria-label="Increment">+</button>
215
+ `;
216
+ }
217
+
218
+ this.#decBtn = this.querySelector('[data-counter-dec]');
219
+ this.#incBtn = this.querySelector('[data-counter-inc]');
220
+ this.#valueEl = this.querySelector('[data-counter-value]');
221
+
222
+ this.#decBtn?.addEventListener('click', this.#onDecrement);
223
+ this.#incBtn?.addEventListener('click', this.#onIncrement);
224
+ }
225
+
226
+ disconnected() {
227
+ super.disconnected(); // unregisters form participation
228
+ this.#decBtn?.removeEventListener('click', this.#onDecrement);
229
+ this.#incBtn?.removeEventListener('click', this.#onIncrement);
230
+ this.#decBtn = null;
231
+ this.#incBtn = null;
232
+ this.#valueEl = null;
233
+ }
234
+
235
+ render() {
236
+ if (this.#valueEl) this.#valueEl.textContent = String(this.value);
237
+ }
238
+ }
239
+
240
+ customElements.define('counter-ui', AdiaCounter);
241
+ export { AdiaCounter };
242
+ ```
243
+
244
+ **Annotations:**
245
+
246
+ - **`AdiaFormElement` superclass** — inherits `name`, `disabled`, `required` etc. via the spread.
247
+ - **`super.connected()` / `super.disconnected()`** — both present. Without them, form association silently fails.
248
+ - **`min`/`max` default `null`** — no magic sentinels. `null` = "no constraint."
249
+ - **`#commit()` is the one-place-value-changes** — clamps, short-circuits on no-op, fires `change`, calls `syncValue`, triggers `render()`.
250
+ - **`syncValue(String(next))`** — inherited from `AdiaFormElement`, updates the form-submitted string representation.
251
+ - **Two stable handlers** — `#onDecrement`, `#onIncrement`. Both paired in `disconnected()`.
252
+ - **`render()` updates internal DOM** only when a reflected prop (`value`) changes and CSS selectors can't express the update (text content). CSS handles the disabled state via `:scope[disabled]`.
253
+
254
+ ### `counter.css`
255
+
256
+ ```css
257
+ @scope (counter-ui) {
258
+ :where(:scope) {
259
+ --counter-bg: var(--a-bg);
260
+ --counter-fg: var(--a-fg);
261
+ --counter-border: 1px solid var(--a-border);
262
+ --counter-radius: var(--a-radius);
263
+ --counter-gap: var(--a-space-2);
264
+ --counter-padding: var(--a-space-1) var(--a-space-2);
265
+ --counter-btn-size: var(--a-size-sm);
266
+ }
267
+
268
+ :scope {
269
+ display: inline-flex;
270
+ align-items: center;
271
+ gap: var(--counter-gap);
272
+ padding: var(--counter-padding);
273
+ background: var(--counter-bg);
274
+ color: var(--counter-fg);
275
+ border: var(--counter-border);
276
+ border-radius: var(--counter-radius);
277
+ }
278
+
279
+ :scope > [data-counter-dec],
280
+ :scope > [data-counter-inc] {
281
+ all: unset;
282
+ width: var(--counter-btn-size);
283
+ height: var(--counter-btn-size);
284
+ display: inline-flex;
285
+ align-items: center;
286
+ justify-content: center;
287
+ cursor: pointer;
288
+ border-radius: var(--a-radius-sm);
289
+ }
290
+
291
+ :scope > [data-counter-dec]:hover,
292
+ :scope > [data-counter-inc]:hover {
293
+ background: var(--a-bg-muted);
294
+ }
295
+
296
+ :scope > [data-counter-value] {
297
+ min-width: 3ch;
298
+ text-align: center;
299
+ font-variant-numeric: tabular-nums;
300
+ }
301
+
302
+ /* Disabled state — inherited from AdiaFormElement */
303
+ :scope[disabled] {
304
+ --counter-fg: var(--a-fg-muted);
305
+ --counter-border: 1px solid var(--a-border-subtle);
306
+ pointer-events: none;
307
+ opacity: 0.6;
308
+ }
309
+ }
310
+ ```
311
+
312
+ **Annotations:**
313
+
314
+ - **No variants** in this example — the component is functional, not cosmetic. All state communicated via tokens and the `[disabled]` selector.
315
+ - **`:scope > [data-*]`** for slotted-like children without `::slotted()`.
316
+ - **`pointer-events: none` + `opacity` in the disabled body** — this is allowed because `pointer-events` is an interaction property, not a layout property. Same for `cursor`.
317
+ - **`min-width: 3ch`** is a non-layout metric (character-based), not a px literal; no carve-out needed.
318
+
319
+ ---
320
+
321
+ ## Checklist — did this component pass?
322
+
323
+ Run the 30-second self-check from the main SKILL.md against each example:
324
+
325
+ **Attributes:**
326
+
327
+ - ✓ No Boolean `default: true`.
328
+ - ✓ No numeric sentinels (`min: null`, `max: null`).
329
+ - ✓ `attribute:` correct (or omitted where auto-conversion works).
330
+ - ✓ State-bearing Booleans reflect (`removable`, inherited `disabled`).
331
+ - ✓ No reserved-name anti-patterns.
332
+ - ✓ `-ui` tag + `Adia<Component>` class.
333
+
334
+ **CSS:**
335
+
336
+ - ✓ `@scope (component-ui)` with two blocks.
337
+ - ✓ Variants are token-only.
338
+ - ✓ Zero raw colors.
339
+ - ✓ Spacing/radius via tokens; `1px` border allowed.
340
+ - ✓ Stem matches scope tag.
341
+ - ✓ Variants consume L2 family tokens (the component-local values in `:where(:scope)` ARE the L3 aliases; overriding them in the variant body is the pattern). No direct L2 in state-change bodies.
342
+
343
+ **Lifecycle:**
344
+
345
+ - ✓ Every `addEventListener` paired.
346
+ - ✓ Stable `#field` arrows.
347
+ - ✓ `super.connected()` / `super.disconnected()` in the `AdiaFormElement` subclass.
348
+ - ✓ DOM refs nulled.
349
+ - ✓ `disconnected()` declared once.
350
+
351
+ Pass. Ship.
@@ -0,0 +1,224 @@
1
+ # YAML component contract — `<name>.yaml` schema
2
+
3
+ Authoritative source-of-truth fields for `packages/web-components/components/<name>/<name>.yaml` and `packages/web-modules/<cluster>/<name>/<name>.yaml`. The build pipeline (`scripts/build/components.mjs`) reads these yamls + emits sidecar JSON (`<name>.a2ui.json`) that feeds the docs site, the A2UI runtime registries, and consumer harnesses.
4
+
5
+ This is the authoritative schema reference for the authoring lane. The full validator + JSON Schema lives at `scripts/schemas/component.yaml.schema.json` (referenced by every yaml's `$schema:` key). This file covers the human-facing contract: what each field means, when to use which value, and the canonical shape of a complete yaml.
6
+
7
+ ---
8
+
9
+ ## Canonical fields (top-level)
10
+
11
+ ```yaml
12
+ # Edit this file; run `npm run build:components` to regenerate a2ui.json.
13
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
14
+ name: UIMyComponent # Class name (PascalCase, UI-prefixed)
15
+ tag: my-component-ui # Custom element tag (kebab-case, -ui-suffixed)
16
+ component: MyComponent # Short component name (no UI- prefix)
17
+ category: form # Category — form / display / layout / chrome / a2ui / shell
18
+ version: 1 # Schema version (always 1 for now)
19
+ status: stable # Stability tier — see §status field below
20
+ description: >-
21
+ Short one-paragraph description of what the component does and when
22
+ to use it. Used by the docs site, sidecar, and a2ui registry. Be
23
+ concrete about behavior + appearance, not generic ("a button").
24
+ props:
25
+ … # Prop schemas — see §props field below
26
+ events:
27
+ … # Event schemas — fired by the component
28
+ slots:
29
+ … # Named slot semantics
30
+ css-vars:
31
+ … # CSS custom properties the component reads
32
+ ```
33
+
34
+ ---
35
+
36
+ ## `status:` field — stability tier
37
+
38
+ **Required** for all new components. Existing components default to `stable` if unset, but new yamls MUST set this explicitly.
39
+
40
+ | Value | When to use |
41
+ | --- | --- |
42
+ | `stable` | Public API contract; safe for consumers to depend on. The vast majority of shipped components. No docs-site badge. |
43
+ | `beta` | Functional but API may change in MINOR releases. Docs site shows a `warning`-variant `<tag-ui>` badge labeled "beta". |
44
+ | `experimental` | Early prototype; expect breaking changes. Docs site shows a `ghost`-variant badge labeled "experimental". |
45
+ | `deprecated` | Has a replacement; check the component's `related:` section. Docs site shows a `danger`-variant badge labeled "deprecated". |
46
+ | `early-access` | Customer-preview tier; release notes gate. Docs site shows an `info`-variant badge labeled "early access". |
47
+
48
+ **Guidance**:
49
+
50
+ - Set `beta` or `experimental` at FIRST AUTHORING for any component that's not in the stable API contract yet. Don't default to `stable` and bump later — the badge is consumer-facing, and stable→beta is a downgrade signal.
51
+ - Only set `stable` after the component has shipped at least one MINOR cycle and gathered consumer feedback. The bar for `stable` is "no API changes anticipated in the next 3 MINOR releases."
52
+ - `deprecated` requires a `related:` entry pointing at the replacement. Without one, consumers can't recover.
53
+
54
+ **Sidecar emission**: `x-adiaui.status` field in `<name>.a2ui.json`. The docs site (`site/site.js`) reads this and injects the badge automatically — no HTML change needed in `<name>.examples.html`.
55
+
56
+ **Verification**: `grep -L '^status:' packages/web-components/components/*/*.yaml` should return empty (every yaml has a status). Run before opening any authoring PR that adds new yamls.
57
+
58
+ ---
59
+
60
+ ## `props:` field — prop schemas
61
+
62
+ Each prop is a top-level key inside `props:`. The full prop schema:
63
+
64
+ ```yaml
65
+ props:
66
+ label:
67
+ description: >-
68
+ Visible label for the field. Wires aria-labelledby on the
69
+ editable surface so screen readers announce it.
70
+ type: string # string | number | boolean | enum
71
+ default: "" # default value (omit for boolean true)
72
+ required: true # ← see §required field below
73
+ reflect: true # mirror attribute ↔ property
74
+ enum: [primary, ghost] # for type: enum
75
+ values: # alternative enum syntax (legacy)
76
+ - primary
77
+ - ghost
78
+ ```
79
+
80
+ ### `required: true` field
81
+
82
+ **When to use**: only for props where omitting them makes the component meaningless or inaccessible.
83
+
84
+ | Use `required: true` | Don't use `required: true` |
85
+ | --- | --- |
86
+ | `field-ui.label` — no visible/accessible label without it | `button-ui.variant` — has a sensible default |
87
+ | `icon-ui.name` — nothing renders without it | `select-ui.placeholder` — useful but optional |
88
+ | `nav-item-ui.text` — empty nav item | `card-ui.size` — affects styling, not function |
89
+ | `chart-ui.type` — can't render a chart of "nothing" | `stat-ui.change-indicator` — optional enhancement |
90
+ | `tabs-ui.value` — needs an initial selected tab | `tag-ui.variant` — has a default |
91
+
92
+ **Heuristic**: ask "if I author `<my-component-ui></my-component-ui>` with nothing else, is the component **broken** or just **default-styled**?" If broken → mark required. If default-styled → don't.
93
+
94
+ **Representative props marked required**:
95
+
96
+ - `field-ui.label`
97
+ - `icon-ui.name`
98
+ - `nav-item-ui.text`
99
+ - `chart-ui.type`
100
+ - `stat-ui.label`, `stat-ui.value`
101
+ - `check-ui.label`
102
+ - `badge-ui.text`
103
+ - `rating-ui.value`
104
+ - `tabs-ui.value`
105
+
106
+ **Sidecar emission**: `required: true` propagates to the JSON Schema `required` array in `<name>.a2ui.json`. The A2UI validator + MCP tools consume this array — correct marking improves validation quality on generated UI trees.
107
+
108
+ **Anti-pattern**: marking ALL props required because they all "have a useful value." That defeats the validation signal. `required` is a strict-failure constraint, not a "recommended" hint.
109
+
110
+ ---
111
+
112
+ ## Reserved `data-*` attribute names (admin-shell scope)
113
+
114
+ `admin-shell.helpers.css` (in `packages/web-modules/shell/admin-shell/css/`) reserves 5 layout-utility `data-*` attribute names with `admin-shell [data-X]` ancestor scoping:
115
+
116
+ | Attribute | Effect | Use within `<admin-shell>` |
117
+ | --- | --- | --- |
118
+ | `[data-col]` | `display: flex; flex-direction: column; gap: var(--page-grid-gap)` | column layout helper |
119
+ | `[data-row]` | `display: flex; align-items: center; gap: var(--page-grid-gap)` | row layout helper |
120
+ | `[data-grid]` | `display: grid; grid-template-columns: 1fr 1fr` (or `1fr 1fr 1fr` for `data-grid="3"`) | 2- or 3-col grid helper |
121
+ | `[data-actions]` | `display: flex; align-items: center; gap: var(--page-actions-gap)` | action button cluster |
122
+ | `[data-spacer]` | `flex: 1` | flex spacer for pushing content to edges |
123
+
124
+ **Authoring contract**:
125
+
126
+ 1. **DO NOT use these attribute names for any other purpose** (table column markers, sort-state, etc.) inside `<admin-shell>` descendants. The CSS rules will apply unintended layout. Use namespaced names instead (`data-page-col`, `data-sort-col`, `data-my-actions`).
127
+ 2. **`admin-shell` ancestor is required**. An earlier dist CSS shipped bare global selectors that applied to ANY element with these attributes on ANY page loading `admin-shell.min.css` — including `<table>` headers (silent layout breakage in Safari/WebKit). Source + dist now both prefix `admin-shell` ancestor. This is a hard constraint: the parent-tag selector is the only reliable CDN-safe scoping mechanism (LightningCSS strips `@scope` blocks).
128
+ 3. **Outside `<admin-shell>`, these names have NO EFFECT**. If you need `[data-col]` semantics on a non-admin-shell page, you must author your own CSS (the helpers do not apply globally).
129
+
130
+ **Documented for consumers** in the adia-ui-factory plugin's consumer composition skill (§CSSPolicy → "Reserved layout-helper attribute names (admin-shell scoping)").
131
+
132
+ ---
133
+
134
+ ## Build pipeline
135
+
136
+ ```bash
137
+ npm run build:components # regenerates all <name>.a2ui.json sidecars from yamls
138
+ npm run verify:components # verifies no drift (CI gate)
139
+ node scripts/build/components.mjs --verify # same as above, direct invocation
140
+ ```
141
+
142
+ The build:
143
+
144
+ 1. Reads every `<name>.yaml` under `packages/web-components/components/` and `packages/web-modules/<cluster>/`
145
+ 2. Validates against `scripts/schemas/component.yaml.schema.json`
146
+ 3. Emits `<name>.a2ui.json` (the sidecar) co-located with the yaml + js + css
147
+ 4. Emits the `traits/_catalog.json` aggregate
148
+ 5. `--verify` mode: re-runs steps 1-4 in-memory and fails if any sidecar drifts from disk content (CI hard-fail)
149
+
150
+ **Never hand-edit `<name>.a2ui.json`** — it's regenerated from the yaml. The yaml is the SoT.
151
+
152
+ ---
153
+
154
+ ## Component creation playbook (full lifecycle)
155
+
156
+ This is the canonical end-to-end procedure for creating a new component yaml + js + css + examples.html + sidecar. Run in order:
157
+
158
+ 1. **Author `<name>.yaml`** with all required fields:
159
+ - `name:`, `tag:`, `component:`, `category:`, `version: 1`
160
+ - `status:` — pick from the table above
161
+ - `description:` — concrete one-paragraph
162
+ - `props:` with per-prop `type:`, `default:`, optional `required: true`, optional `enum:` / `values:`
163
+ - `events:`, `slots:`, `css-vars:` as applicable
164
+ 2. **Author `<name>.js`** following the `AdiaElement` / `AdiaFormElement` patterns (see [code-style.md](code-style.md))
165
+ 3. **Author `<name>.css`** following the light-DOM cascade rules (see [css-patterns.md](css-patterns.md))
166
+ 4. **Build the sidecar**:
167
+
168
+ ```bash
169
+ npm run build:components
170
+ ```
171
+
172
+ This generates `<name>.a2ui.json`.
173
+
174
+ 5. **Author `<name>.examples.html`** with at minimum:
175
+ - A `<h1>` matching the component name
176
+ - One `<section data-section data-property="usage">` with the canonical worked example
177
+ - Per-prop demo sections matching `data-property="<prop-name>"` (one per prop)
178
+ 6. **Run the anatomy sweep**:
179
+
180
+ ```bash
181
+ node scripts/docs/anatomy-sweep.mjs
182
+ ```
183
+
184
+ This auto-generates the **canonical anatomy sections** (`slots`, `data-attrs`, `keyboard`, `css-vars`, `a2ui`, `related`) from the sidecar. The sweep is idempotent — skip-on-already-present, safe to re-run. Hand-author `accessibility` and any prop-demo sections; the sweep covers the schema-derivable sections only.
185
+
186
+ Canonical `data-property` vocabulary for `<section data-section data-property="X">`:
187
+ - `usage` — canonical worked example
188
+ - `props` — prop reference table
189
+ - `slots` — named-slot semantics
190
+ - `events` — fired events
191
+ - `data-attrs` — `data-*` attributes the component reads
192
+ - `css-vars` — CSS custom properties
193
+ - `keyboard` — keyboard interaction model
194
+ - `accessibility` — ARIA, screen-reader notes
195
+ - `a2ui` — A2UI runtime integration notes
196
+ - `related` — sibling/replacement components
197
+ - `<prop-name>` — visual demo for a specific prop
198
+
199
+ The sweep also **normalizes legacy section data-properties**: `Properties` → `props`, `Events` → `events`, `CSS Tokens` → `css-vars`, `Usage` → `usage`. Run with `--dry` first to preview changes:
200
+
201
+ ```bash
202
+ node scripts/docs/anatomy-sweep.mjs --dry
203
+ ```
204
+
205
+ 7. **Run the full verify gate**:
206
+
207
+ ```bash
208
+ node scripts/build/components.mjs --verify # sidecar drift
209
+ npm run verify:traits # 100% coverage
210
+ ```
211
+
212
+ After the playbook, the component is consumable by the docs site, the A2UI runtime, and any harness reading the sidecar.
213
+
214
+ ---
215
+
216
+ ## Cross-references
217
+
218
+ - `docs/specs/component-token-contract.md` — token/variant/mode contract
219
+ - `docs/specs/component-implementation-patterns.md` — implementation patterns
220
+ - [code-style.md](code-style.md) — JS code style rules
221
+ - [css-patterns.md](css-patterns.md) — light-DOM CSS cascade rules
222
+ - [api-contract.md](api-contract.md) — props/events/slots conventions
223
+ - [authoring-cycle.md](authoring-cycle.md) — the 5-step authoring procedure
224
+ - `scripts/schemas/component.yaml.schema.json` — JSON Schema (authoritative)