@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.
- package/.claude-plugin/plugin.json +23 -0
- package/CHANGELOG.md +26 -0
- package/README.md +54 -0
- package/bin/forge-lint +263 -0
- package/bin/lib/audit-axes.mjs +555 -0
- package/bin/lib/dry-run-irreversible.mjs +236 -0
- package/bin/lib/run-skill-evals.mjs +487 -0
- package/bin/lib/teach-router.mjs +250 -0
- package/commands/adia-forge-a2ui.md +10 -0
- package/commands/adia-forge-author.md +10 -0
- package/commands/adia-forge-dogfood.md +8 -0
- package/commands/adia-forge-llm.md +8 -0
- package/commands/adia-forge-orient.md +10 -0
- package/commands/adia-forge-release.md +8 -0
- package/commands/adia-forge-review.md +10 -0
- package/hooks/hooks.json +15 -0
- package/package.json +41 -0
- package/references/shared/content-trust.md +76 -0
- package/references/shared/pev-rationale.md +64 -0
- package/references/shared/skill-conventions.md +133 -0
- package/skills/adia-ui-a2ui/CHANGELOG.md +32 -0
- package/skills/adia-ui-a2ui/SKILL.md +243 -0
- package/skills/adia-ui-a2ui/evals/adversarial-corpus.json +75 -0
- package/skills/adia-ui-a2ui/evals/routing-corpus.json +31 -0
- package/skills/adia-ui-a2ui/evals/teach-routing-cases.json +100 -0
- package/skills/adia-ui-a2ui/references/anti-patterns.md +24 -0
- package/skills/adia-ui-a2ui/references/chunk-authoring.md +88 -0
- package/skills/adia-ui-a2ui/references/corpus-discipline.md +56 -0
- package/skills/adia-ui-a2ui/references/eval-diagnostics.md +127 -0
- package/skills/adia-ui-a2ui/references/fragment-graph.md +91 -0
- package/skills/adia-ui-a2ui/references/mcp-pipeline-ops.md +106 -0
- package/skills/adia-ui-a2ui/references/mcp-tool-reference.md +398 -0
- package/skills/adia-ui-a2ui/references/pipeline-overview.md +175 -0
- package/skills/adia-ui-a2ui/references/semantic-fail-lifting.md +120 -0
- package/skills/adia-ui-a2ui/references/strategy-engines.md +111 -0
- package/skills/adia-ui-a2ui/references/teach-protocol.md +220 -0
- package/skills/adia-ui-a2ui/references/zettel-calibration.md +93 -0
- package/skills/adia-ui-a2ui/scripts/audit-a2ui-roster.mjs +96 -0
- package/skills/adia-ui-a2ui/scripts/teach-route.mjs +157 -0
- package/skills/adia-ui-a2ui/skill.json +38 -0
- package/skills/adia-ui-authoring/CHANGELOG.md +32 -0
- package/skills/adia-ui-authoring/SKILL.md +256 -0
- package/skills/adia-ui-authoring/assets/case-studies/admin-shell-decomposition.md +101 -0
- package/skills/adia-ui-authoring/assets/case-studies/maxtokens-32768-discovery.md +109 -0
- package/skills/adia-ui-authoring/assets/case-studies/theme-panel-promotion.md +113 -0
- package/skills/adia-ui-authoring/evals/adversarial-design-plan-gates.json +138 -0
- package/skills/adia-ui-authoring/evals/routing-corpus.json +245 -0
- package/skills/adia-ui-authoring/references/anti-patterns.md +606 -0
- package/skills/adia-ui-authoring/references/api-contract.md +205 -0
- package/skills/adia-ui-authoring/references/authoring-cycle.md +211 -0
- package/skills/adia-ui-authoring/references/canonical-pattern-index.md +179 -0
- package/skills/adia-ui-authoring/references/code-style.md +329 -0
- package/skills/adia-ui-authoring/references/common-gotchas.md +93 -0
- package/skills/adia-ui-authoring/references/composite-demo-protocol.md +1084 -0
- package/skills/adia-ui-authoring/references/css-patterns.md +364 -0
- package/skills/adia-ui-authoring/references/lifecycle-patterns.md +302 -0
- package/skills/adia-ui-authoring/references/llm-bridge.md +254 -0
- package/skills/adia-ui-authoring/references/module-promotion.md +289 -0
- package/skills/adia-ui-authoring/references/primitive-audit.md +123 -0
- package/skills/adia-ui-authoring/references/shell-patterns.md +561 -0
- package/skills/adia-ui-authoring/references/teach-protocol.md +428 -0
- package/skills/adia-ui-authoring/references/token-contract.md +120 -0
- package/skills/adia-ui-authoring/references/worked-example.md +351 -0
- package/skills/adia-ui-authoring/references/yaml-contract.md +224 -0
- package/skills/adia-ui-authoring/scripts/audit-authoring-roster.mjs +148 -0
- package/skills/adia-ui-authoring/scripts/build-canonical-pattern-index.mjs +199 -0
- package/skills/adia-ui-authoring/skill.json +45 -0
- package/skills/adia-ui-dogfood/CHANGELOG.md +17 -0
- package/skills/adia-ui-dogfood/README.md +62 -0
- package/skills/adia-ui-dogfood/SKILL.md +866 -0
- package/skills/adia-ui-dogfood/scripts/analyze.mjs +603 -0
- package/skills/adia-ui-dogfood/skill.json +40 -0
- package/skills/adia-ui-forge/SKILL.md +88 -0
- package/skills/adia-ui-forge/evals/routing-corpus.json +30 -0
- package/skills/adia-ui-gen-review/CHANGELOG.md +108 -0
- package/skills/adia-ui-gen-review/SKILL.md +266 -0
- package/skills/adia-ui-gen-review/references/loop-protocol.md +712 -0
- package/skills/adia-ui-gen-review/references/rubric-cosmetic.md +144 -0
- package/skills/adia-ui-gen-review/references/rubric-decompose.md +117 -0
- package/skills/adia-ui-gen-review/references/rubric-score.md +249 -0
- package/skills/adia-ui-gen-review/references/scores.schema.json +125 -0
- package/skills/adia-ui-gen-review/references/teach-protocol.md +214 -0
- package/skills/adia-ui-gen-review/scripts/gen-review-coverage-audit.mjs +127 -0
- package/skills/adia-ui-gen-review/scripts/gen-review-decompose.mjs +569 -0
- package/skills/adia-ui-gen-review/scripts/gen-review-status.mjs +176 -0
- package/skills/adia-ui-gen-review/scripts/validate-cycle-scores.mjs +198 -0
- package/skills/adia-ui-gen-review/skill.json +22 -0
- package/skills/adia-ui-llm/CHANGELOG.md +25 -0
- package/skills/adia-ui-llm/SKILL.md +165 -0
- package/skills/adia-ui-llm/evals/adversarial-corpus.json +75 -0
- package/skills/adia-ui-llm/evals/routing-corpus.json +30 -0
- package/skills/adia-ui-llm/evals/teach-routing-cases.json +73 -0
- package/skills/adia-ui-llm/references/adapter-contract.md +99 -0
- package/skills/adia-ui-llm/references/add-a-provider.md +90 -0
- package/skills/adia-ui-llm/references/bridge-facade.md +94 -0
- package/skills/adia-ui-llm/references/browser-proxy-boundary.md +89 -0
- package/skills/adia-ui-llm/references/model-registry.md +75 -0
- package/skills/adia-ui-llm/references/streaming-sse.md +95 -0
- package/skills/adia-ui-llm/references/teach-protocol.md +78 -0
- package/skills/adia-ui-llm/scripts/audit-llm-roster.mjs +93 -0
- package/skills/adia-ui-llm/scripts/teach-route.mjs +119 -0
- package/skills/adia-ui-llm/skill.json +33 -0
- package/skills/adia-ui-release/CHANGELOG.md +23 -0
- package/skills/adia-ui-release/SKILL.md +295 -0
- package/skills/adia-ui-release/assets/case-studies/2026-05-20-batch-push-v0.6.14-v0.6.15.md +144 -0
- package/skills/adia-ui-release/assets/case-studies/2026-05-20-corpus-drift-remediation-v0.6.15.md +155 -0
- package/skills/adia-ui-release/assets/case-studies/2026-05-20-version-skip-correction-v0.6.12.md +114 -0
- package/skills/adia-ui-release/assets/case-studies/2026-05-21-author-from-scratch-v0.6.18.md +139 -0
- package/skills/adia-ui-release/assets/case-studies/2026-05-21-feedback37-retraction-v0.6.21.md +124 -0
- package/skills/adia-ui-release/assets/case-studies/2026-05-21-fn1-enrichment-pass-v0.6.19.md +125 -0
- package/skills/adia-ui-release/assets/case-studies/2026-05-21-stale-test-detection-v0.6.20.md +142 -0
- package/skills/adia-ui-release/assets/case-studies/2026-05-23-freshness-gate-recovery-v0.6.32.md +97 -0
- package/skills/adia-ui-release/assets/case-studies/2026-05-26-catalog-drift-recurring-v0.6.40.md +147 -0
- package/skills/adia-ui-release/assets/templates/stub-changelog.template.md +22 -0
- package/skills/adia-ui-release/evals/evals.json +164 -0
- package/skills/adia-ui-release/references/changelog-discipline.md +250 -0
- package/skills/adia-ui-release/references/cycle-happy-path.md +520 -0
- package/skills/adia-ui-release/references/exe-deploy.md +149 -0
- package/skills/adia-ui-release/references/gates-catalog.md +778 -0
- package/skills/adia-ui-release/references/ledger-discipline.md +232 -0
- package/skills/adia-ui-release/references/migration-guide-authoring.md +174 -0
- package/skills/adia-ui-release/references/multi-agent-baseline.md +207 -0
- package/skills/adia-ui-release/references/notes-authoring.md +212 -0
- package/skills/adia-ui-release/references/recovery-paths.md +215 -0
- package/skills/adia-ui-release/references/rollup-notes.md +208 -0
- package/skills/adia-ui-release/references/teach-protocol.md +468 -0
- package/skills/adia-ui-release/scripts/assert-monorepo-root.mjs +49 -0
- package/skills/adia-ui-release/scripts/audit-gate-roster.mjs +196 -0
- package/skills/adia-ui-release/scripts/bump.mjs +118 -0
- package/skills/adia-ui-release/scripts/dispatch-publish.mjs +151 -0
- package/skills/adia-ui-release/scripts/insert-stub.mjs +127 -0
- package/skills/adia-ui-release/scripts/make-ledger.mjs +179 -0
- package/skills/adia-ui-release/scripts/promote-unreleased.mjs +88 -0
- package/skills/adia-ui-release/scripts/release-pack.mjs +350 -0
- package/skills/adia-ui-release/scripts/tag-lockstep.mjs +110 -0
- 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)
|