@adia-ai/adia-ui-forge 0.8.61 → 0.8.63
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 +309 -274
- 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 +20 -21
- 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 +34 -35
- 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 +27 -27
- package/skills/gen-ui-review/agents/openai.yaml +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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# API Contract
|
|
1
|
+
# API Contract, Props, Attributes, Reflection
|
|
2
2
|
|
|
3
3
|
Deep dive on declaring component APIs in AdiaUI. Read this when adding a new prop whose shape doesn't match any obvious pattern in the good-citizen references.
|
|
4
4
|
|
|
@@ -17,14 +17,14 @@ static properties = {
|
|
|
17
17
|
|
|
18
18
|
**Field rules:**
|
|
19
19
|
|
|
20
|
-
- `type
|
|
21
|
-
- `default
|
|
22
|
-
- `reflect
|
|
23
|
-
- `attribute
|
|
20
|
+
- `type`, one of `String`, `Number`, `Boolean`, `Object`, `Array`. The runtime uses this to coerce attribute strings into typed values.
|
|
21
|
+
- `default`: the value the prop takes when no attribute is present AND no JS value has been assigned. For `Boolean` props, default is `false` in the standard shape (see rule 1); `default: true` is a rare, ratified exception carried by connect-time attribute stamping (ADR-0075, gh#961), never the default shape to reach for. For numeric props, default is `0`, a real value, or `null` for indeterminate, NEVER a sentinel like `-1`.
|
|
22
|
+
- `reflect`, when `true`, JS property changes write back to the HTML attribute so CSS can match it. Required for every state-bearing Boolean. Usually safe to omit for large value props (long strings, big objects).
|
|
23
|
+
- `attribute`, explicit kebab-case mapping when the JS name doesn't auto-convert cleanly. `camelCase` → `camel-case` automatic; override via `attribute: 'max-length'` when you want non-default behavior.
|
|
24
24
|
|
|
25
25
|
[verified 2026-08-19] **Literal-string `"false"` parity (ADR-0075).** For a
|
|
26
26
|
`Boolean`-typed prop, `parseAttr` (`core/element.js:93-94`) special-cases the
|
|
27
|
-
literal attribute string `"false"` to parse as JS `false
|
|
27
|
+
literal attribute string `"false"` to parse as JS `false`, a deliberate
|
|
28
28
|
deviation from strict HTML boolean-attribute semantics (where any presence,
|
|
29
29
|
including `attr="false"`, means `true`). This matches the A2UI transpiler's
|
|
30
30
|
own prior `// (defensive)` special-case
|
|
@@ -34,10 +34,10 @@ the Decision: "for `t === Boolean`, the literal string `\"false\"` parses to
|
|
|
34
34
|
JS `false`; any other present value (including empty string) parses to
|
|
35
35
|
`true`; absence stays `false`." A component author or generation pipeline
|
|
36
36
|
that writes `interactive="false"` on a `default: true` Boolean prop gets the
|
|
37
|
-
intuitive result
|
|
37
|
+
intuitive result, do not assume strict HTML semantics here. (`default:
|
|
38
38
|
true` itself remains the rare, ratified exception this ADR's blast radius
|
|
39
|
-
runs against
|
|
40
|
-
default shape "Boolean prop naming
|
|
39
|
+
runs against, ADR-0063's stamped-attribute mechanism, gh#961, never the
|
|
40
|
+
default shape "Boolean prop naming, the flip rule" above documents.) Named
|
|
41
41
|
blast radius already shipped and relying on this: `password-strength-ui
|
|
42
42
|
show-label="false"`, `nav-group-ui collapsible="false"`
|
|
43
43
|
(`.examples.html` demos for both).
|
|
@@ -47,20 +47,20 @@ show-label="false"`, `nav-group-ui collapsible="false"`
|
|
|
47
47
|
A real bug caught in iteration 4:
|
|
48
48
|
|
|
49
49
|
```javascript
|
|
50
|
-
// BROKEN
|
|
50
|
+
// BROKEN, silent
|
|
51
51
|
allowHalf: { type: Boolean, default: false, reflect: true, attr: 'allow-half' },
|
|
52
52
|
|
|
53
53
|
// FIXED
|
|
54
54
|
allowHalf: { type: Boolean, default: false, reflect: true, attribute: 'allow-half' },
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
`attr:` is not a recognized key. The mapper ignores it. The default auto-conversion (`allowHalf` → `allow-half`) happens to produce the same result, so the bug hides
|
|
57
|
+
`attr:` is not a recognized key. The mapper ignores it. The default auto-conversion (`allowHalf` → `allow-half`) happens to produce the same result, so the bug hides, until someone changes the prop name and notices the attribute never wired.
|
|
58
58
|
|
|
59
59
|
Use `attribute:` verbatim. If you think you're writing `attr:`, stop and correct it before saving.
|
|
60
60
|
|
|
61
|
-
## Boolean prop naming
|
|
61
|
+
## Boolean prop naming, the flip rule
|
|
62
62
|
|
|
63
|
-
AdiaUI conventions require `default: false` on Boolean props in the standard shape, unless a ratified exception applies (ADR-0075's connect-time attribute stamping, gh#961
|
|
63
|
+
AdiaUI conventions require `default: false` on Boolean props in the standard shape, unless a ratified exception applies (ADR-0075's connect-time attribute stamping, gh#961, a rare exception, not a route open to new work). The naming follows:
|
|
64
64
|
|
|
65
65
|
| Intended default behavior | Wrong name (default:true) | Right name (default:false) |
|
|
66
66
|
| --- | --- | --- |
|
|
@@ -73,15 +73,15 @@ AdiaUI conventions require `default: false` on Boolean props in the standard sha
|
|
|
73
73
|
|
|
74
74
|
**Naming patterns:**
|
|
75
75
|
|
|
76
|
-
- `permanent` / `static` / `readonly
|
|
77
|
-
- `no*` / `hide*` / `disable
|
|
76
|
+
- `permanent` / `static` / `readonly`, describes the non-default state positively.
|
|
77
|
+
- `no*` / `hide*` / `disable*`, prefixes for "opt-out of a default."
|
|
78
78
|
|
|
79
79
|
**Don't write:**
|
|
80
80
|
|
|
81
|
-
- `enabled` (invert to `disabled`), `visible` (invert to `hidden`)
|
|
81
|
+
- `enabled` (invert to `disabled`), `visible` (invert to `hidden`), these clash with standard HTML attribute vocabulary.
|
|
82
82
|
- Double-negatives like `unhide` or `dontSkip`.
|
|
83
83
|
|
|
84
|
-
## Enum attribute + container-query auto-snap default
|
|
84
|
+
## Enum attribute + container-query auto-snap default, no interpolation
|
|
85
85
|
|
|
86
86
|
[verified 2026-08-19] ADR-0074 establishes the cross-component pattern for a
|
|
87
87
|
value-enum attribute whose sensible default is "pick automatically from live
|
|
@@ -90,55 +90,54 @@ layout, but let a consumer pin it explicitly": `chart-ui` /
|
|
|
90
90
|
values (`3:2`, `1:1`, `2:3`), unset by default. Quoting the Decision:
|
|
91
91
|
"Unset (the default, empty string) means auto-snap: a CSS container query on
|
|
92
92
|
the element's own box compares its live aspect ratio against two midpoint
|
|
93
|
-
boundaries … and renders the nearest named ratio's studied layout
|
|
93
|
+
boundaries … and renders the nearest named ratio's studied layout, never a
|
|
94
94
|
scaled/interpolated blend. Setting `ratio` explicitly pins that ratio's
|
|
95
95
|
rendering regardless of the container's actual aspect, overriding the
|
|
96
96
|
container query."
|
|
97
97
|
|
|
98
98
|
Shape to follow for a new attribute of this kind:
|
|
99
99
|
|
|
100
|
-
- A plain value-enum attribute, not a boolean
|
|
100
|
+
- A plain value-enum attribute, not a boolean, ADR-0063's `no-*`
|
|
101
101
|
negation-prefix grammar does not apply.
|
|
102
102
|
- Unset/default means container-query-driven auto-snap against studied
|
|
103
|
-
breakpoints
|
|
103
|
+
breakpoints, discrete snapping between named values, never continuous
|
|
104
104
|
interpolation (an in-between state was never individually studied, so it
|
|
105
105
|
can't carry a "deliberately designed" claim).
|
|
106
106
|
- An explicit attribute value always pins and overrides the auto-snap,
|
|
107
107
|
never blends with it.
|
|
108
|
-
- The resolved value reflects back for CSS/consumer introspection
|
|
109
|
-
`data-ratio-resolved` on `chart-ui` names which of the enum's snapped or
|
|
108
|
+
- The resolved value reflects back for CSS/consumer introspection, `data-ratio-resolved` on `chart-ui` names which of the enum's snapped or
|
|
110
109
|
pinned values is currently in effect, distinct from the (possibly unset)
|
|
111
110
|
`ratio` attribute itself.
|
|
112
111
|
- Don't fold the new axis into an existing enum attribute that covers a
|
|
113
112
|
different concern (`size` stayed `sm|md|lg`-only; `ratio` didn't grow a
|
|
114
|
-
compound value like `lg-2:3`)
|
|
113
|
+
compound value like `lg-2:3`), orthogonal axes get their own attribute.
|
|
115
114
|
|
|
116
115
|
`table-toolbar-ui[stage]` (ADR-0076) is the second precedent for this same
|
|
117
|
-
shape
|
|
116
|
+
shape, see the ADR-0063 conventions list in
|
|
118
117
|
[token-contract.md](token-contract.md).
|
|
119
118
|
|
|
120
119
|
Source: [ADR-0074](../../../../../../docs/ops/adr/adr-0074-chart-ratio-attribute-grammar.md).
|
|
121
120
|
|
|
122
|
-
**[verified 2026-08-23] Failure mode
|
|
121
|
+
**[verified 2026-08-23] Failure mode, DOM-ancestry auto-detection is riskier
|
|
123
122
|
than container-query auto-detection.** ADR-0081 Decision 1 (`chart-ui[labels]`,
|
|
124
123
|
a 3-value enum: `""`/`chip`/`outside`) initially mirrored this same
|
|
125
|
-
auto/explicit-override shape, but auto-detected via DOM ancestry
|
|
124
|
+
auto/explicit-override shape, but auto-detected via DOM ancestry, resolving
|
|
126
125
|
unset `labels=""` to `chip` whenever the element sat inside a `section[bleed]`
|
|
127
|
-
or `card-ui[padding="none"]` ancestor
|
|
126
|
+
or `card-ui[padding="none"]` ancestor, instead of a container query on the
|
|
128
127
|
element's own box. The 2026-08-21 amendment walked that back entirely after it
|
|
129
128
|
silently flipped 2 of 60 fixtures on the Charts visual-eval floor
|
|
130
129
|
(`comp-chart-in-card-n-*`, pre-existing full-bleed compositions), caught only
|
|
131
|
-
by the floor's pixel-diff gate
|
|
130
|
+
by the floor's pixel-diff gate, neither the unit-test suite nor code review
|
|
132
131
|
renders real CSS/layout. Amended decision: unset `labels=""` resolves to
|
|
133
132
|
`outside` unconditionally; `chip` mode activates only via the explicit
|
|
134
|
-
`labels="chip"` attribute
|
|
133
|
+
`labels="chip"` attribute, no ancestry detection at all. ADR-0081's Decision
|
|
135
134
|
2 (a `today` marker attribute) independently rejects clock-derived
|
|
136
135
|
auto-detection for the same reliability reason (cites IDR-0006). Lesson for a
|
|
137
136
|
new auto-snap attribute of this shape: auto-detection is safe when it reads
|
|
138
137
|
the element's *own* rendered geometry (a container query against its own box,
|
|
139
138
|
as `ratio` does); it is risky when it reads *ancestor* DOM state or wall-clock
|
|
140
139
|
time, because neither is guaranteed stable across every composition that
|
|
141
|
-
happens to nest the element
|
|
140
|
+
happens to nest the element, verify any such default against a real
|
|
142
141
|
pixel-diff/visual-eval gate, not unit tests or review alone. Source:
|
|
143
142
|
[ADR-0081](../../../../../../docs/ops/adr/adr-0081-chart-2-0-foundations-attribute-grammar.md)
|
|
144
143
|
Amendment (2026-08-21).
|
|
@@ -147,12 +146,11 @@ Amendment (2026-08-21).
|
|
|
147
146
|
|
|
148
147
|
[verified 2026-08-25, gh#1984] `core/responsive.js`'s `BREAKPOINTS` export is
|
|
149
148
|
the single canonical reference for any device-class threshold, whether the
|
|
150
|
-
query mechanism is `@media` (the five sanctioned viewport primitives
|
|
151
|
-
`grid-ui`, `col-ui`, `row-ui`, `block-ui`, `text-ui` — plus top-layer/popover
|
|
149
|
+
query mechanism is `@media` (the five sanctioned viewport primitives, `grid-ui`, `col-ui`, `row-ui`, `block-ui`, `text-ui`, plus top-layer/popover
|
|
152
150
|
positioning with no ancestor box to query) or `@container` (everything else,
|
|
153
|
-
per `adr-0088
|
|
151
|
+
per `adr-0088`, `spec-breakpoint-convention` REQ-004 forbids importing
|
|
154
152
|
`core/responsive.js` itself outside those cases). This table MUST match
|
|
155
|
-
`core/responsive.js`'s `BREAKPOINTS` export byte-for-byte
|
|
153
|
+
`core/responsive.js`'s `BREAKPOINTS` export byte-for-byte, a gap here was
|
|
156
154
|
exactly the drift `spec-breakpoint-convention` REQ-007 named and closed:
|
|
157
155
|
|
|
158
156
|
| Name | Min-width |
|
|
@@ -165,25 +163,25 @@ exactly the drift `spec-breakpoint-convention` REQ-007 named and closed:
|
|
|
165
163
|
|
|
166
164
|
A `@container`/`@media` rule can't read a CSS custom property at parse time
|
|
167
165
|
(forbidden-pattern #2 in [token-contract.md](token-contract.md)), so these
|
|
168
|
-
values are cited as raw literals in component CSS, never tokenized
|
|
166
|
+
values are cited as raw literals in component CSS, never tokenized, but
|
|
169
167
|
every such literal MUST carry a preceding comment naming the rung it
|
|
170
168
|
implements (device-class threshold) or the ergonomic reason for the number
|
|
171
|
-
(a component-intrinsic threshold, exempt from this table entirely
|
|
169
|
+
(a component-intrinsic threshold, exempt from this table entirely, `nav-ui`'s
|
|
172
170
|
96px icon-rail floor, `table-toolbar-ui`'s compaction stages, `chart-ui`'s
|
|
173
171
|
200px legend-hide are correctly exempt and are NOT migration candidates).
|
|
174
172
|
`packages/web-components/core/breakpoint-observer.js` is the shared
|
|
175
173
|
`ResizeObserver`-to-attribute helper for a component that needs
|
|
176
174
|
JS-observable state (relocating a node, switching a positioning strategy)
|
|
177
|
-
rather than a pure CSS layout switch
|
|
175
|
+
rather than a pure CSS layout switch, see its own module doc for the
|
|
178
176
|
boolean-vs-named-value reflection modes. Full requirements:
|
|
179
177
|
[spec-breakpoint-convention](../../../../../../docs/ops/spec/spec-breakpoint-convention.md).
|
|
180
178
|
|
|
181
|
-
## Numeric props
|
|
179
|
+
## Numeric props, `null` over sentinels
|
|
182
180
|
|
|
183
181
|
Indeterminate, unknown, or "not yet set" numeric state uses `null`, not `-1` or `Infinity`:
|
|
184
182
|
|
|
185
183
|
```javascript
|
|
186
|
-
// BROKEN
|
|
184
|
+
// BROKEN, `-1` is a magic sentinel
|
|
187
185
|
value: { type: Number, default: -1, reflect: true }
|
|
188
186
|
// Consumer: if (this.value !== -1) { ... }
|
|
189
187
|
|
|
@@ -192,7 +190,7 @@ value: { type: Number, default: null, reflect: true }
|
|
|
192
190
|
// Consumer: if (this.value != null) { ... }
|
|
193
191
|
```
|
|
194
192
|
|
|
195
|
-
The `progress-ui` component carried `-1` for "indeterminate" for a long time. Branching on a specific number is fragile
|
|
193
|
+
The `progress-ui` component carried `-1` for "indeterminate" for a long time. Branching on a specific number is fragile, someone assigns `-1` meaningfully later and the indeterminate check breaks silently. `null` is unambiguous.
|
|
196
194
|
|
|
197
195
|
**Back-compat coercion is allowed:**
|
|
198
196
|
|
|
@@ -215,9 +213,9 @@ HTML's global `title` attribute is the browser tooltip. Using `title` on a custo
|
|
|
215
213
|
|
|
216
214
|
Children can have per-item `active` (a Boolean describing that item's state). Parents use `value` (for a selection) or `step` (for an index into a series).
|
|
217
215
|
|
|
218
|
-
- `<timeline-item-ui active
|
|
219
|
-
- `<timeline-ui step="3"
|
|
220
|
-
- `<timeline-ui active="3"
|
|
216
|
+
- `<timeline-item-ui active>`, OK, per-item Boolean state.
|
|
217
|
+
- `<timeline-ui step="3">`, correct: parent holds the index.
|
|
218
|
+
- `<timeline-ui active="3">`, wrong: `active` shouldn't carry a non-boolean payload.
|
|
221
219
|
|
|
222
220
|
### `error` as a variant
|
|
223
221
|
|
|
@@ -236,10 +234,10 @@ Reserve `error` for validation state (`[error]` on form inputs matches ARIA patt
|
|
|
236
234
|
|
|
237
235
|
### `disabled` on a non-form component
|
|
238
236
|
|
|
239
|
-
`disabled` has form-participating semantics
|
|
237
|
+
`disabled` has form-participating semantics, it removes the element from the tab order, blocks submission, etc. On a non-form component (a diagram, a toolbar, a noodle editor), use `readonly`:
|
|
240
238
|
|
|
241
|
-
- `<input-ui disabled
|
|
242
|
-
- `<noodles-ui readonly
|
|
239
|
+
- `<input-ui disabled>`, correct: input is form-participating.
|
|
240
|
+
- `<noodles-ui readonly>`, correct: diagram is read-only, not form-disabled.
|
|
243
241
|
|
|
244
242
|
### `multiple` with exclusion semantics
|
|
245
243
|
|
|
@@ -255,34 +253,34 @@ Reserve `error` for validation state (`[error]` on form inputs matches ARIA patt
|
|
|
255
253
|
|
|
256
254
|
**[historical, 2026-08-31, ADR-0056 amendment]** `<toggle-group-ui>` /
|
|
257
255
|
`<toggle-option-ui>` were cut outright in `0.8.43` (gh#1617), before the
|
|
258
|
-
migration this ADR's Decision 2 anticipated ever ran
|
|
256
|
+
migration this ADR's Decision 2 anticipated ever ran, moot, not
|
|
259
257
|
falsified. `segmented-ui`/`segment-ui` absorbed the role
|
|
260
258
|
(`segmented.yaml`'s `multiple` prop description names the absorption
|
|
261
259
|
directly, gh#1369/#1363 C1). The example above stays as written because it
|
|
262
260
|
illustrates the Boolean-false naming rule against a real historical prop
|
|
263
|
-
name, not because `<toggle-group-ui>` still exists
|
|
261
|
+
name, not because `<toggle-group-ui>` still exists, `segmented-ui`
|
|
264
262
|
deliberately does NOT follow this same polarity (`[multiple]` is
|
|
265
263
|
positive-polarity by design, matching `select-ui[multiple]`; there is no
|
|
266
264
|
`segmented-ui[single]` opt-out), so it is not a drop-in replacement
|
|
267
265
|
example for this rule.
|
|
268
266
|
|
|
269
|
-
## Selection-item state
|
|
267
|
+
## Selection-item state, a declared, reflected `selected` prop, never a private `data-*` stamp
|
|
270
268
|
|
|
271
|
-
A **selection-item primitive** (one selectable option inside a selection-group parent
|
|
269
|
+
A **selection-item primitive** (one selectable option inside a selection-group parent, `segment-ui` in `segmented-ui`; historically also `toggle-option-ui` in `toggle-group-ui`, cut in `0.8.43`/gh#1617, see the dated note below) exposes its current state as a declared, reflected `selected: Boolean` prop, documented as parent-managed, and styled via `[selected]`:
|
|
272
270
|
|
|
273
271
|
```javascript
|
|
274
|
-
// child
|
|
272
|
+
// child, the declared API surface
|
|
275
273
|
static properties = {
|
|
276
274
|
selected: { type: Boolean, default: false, reflect: true },
|
|
277
275
|
};
|
|
278
276
|
```
|
|
279
277
|
|
|
280
278
|
```yaml
|
|
281
|
-
# child yaml
|
|
279
|
+
# child yaml, the SoT the catalog and A2UI grammar read
|
|
282
280
|
selected:
|
|
283
281
|
description: >-
|
|
284
282
|
Whether this option is currently selected. Managed by the parent
|
|
285
|
-
<group> container
|
|
283
|
+
<group> container, don't set directly; the group's `value` is the
|
|
286
284
|
single source of truth.
|
|
287
285
|
type: boolean
|
|
288
286
|
default: false
|
|
@@ -290,18 +288,18 @@ selected:
|
|
|
290
288
|
```
|
|
291
289
|
|
|
292
290
|
```css
|
|
293
|
-
/* child css
|
|
291
|
+
/* child css: the state hook is the reflected attribute */
|
|
294
292
|
segment-ui[selected] { … }
|
|
295
293
|
```
|
|
296
294
|
|
|
297
295
|
Rules:
|
|
298
296
|
|
|
299
297
|
- **The parent's `value` is the single source of truth**; the parent writes `selected` on its children (`segmented.class.js:146,149` sets/removes the attribute; `toggle-group.class.js:104-111` assigns `opt.selected`). Authors and generated markup drive the group's `value`, never `[selected]` on a child (`toggle-group.yaml`'s rules block says so in so many words).
|
|
300
|
-
- **No private `data-*` stamp for API-conceptual state.** `data-selected` hides the state from the yaml SoT, the catalog, and generative authoring
|
|
301
|
-
- **Parent-stamped ARIA stays as-is
|
|
302
|
-
- **Reference implementation:** `segment-ui` (`segment.yaml:32-36`, `segment.css:7`) is the current, live reference. **[historical, 2026-08-31, ADR-0056 amendment]** `toggle-option-ui`/`toggle-group-ui` were the converged second instance at ratification time, but both were cut outright in `0.8.43` (gh#1617), before any migration ran
|
|
298
|
+
- **No private `data-*` stamp for API-conceptual state.** `data-selected` hides the state from the yaml SoT, the catalog, and generative authoring, a consumer or the A2UI grammar cannot express "this option is selected" against a stamp that no schema declares. `toggle-group-ui` used to stamp `data-selected` + style `:scope[data-selected]`; it converged onto `segment-ui`'s mechanism (`toggle-option.yaml:38-46`, `toggle-group.css:14`, `toggle-group.test.js:64` asserts the stamp is gone).
|
|
299
|
+
- **Parent-stamped ARIA stays as-is, ARIA is wiring, not API.** `segment-ui` derives `aria-checked` from `selected` (`segment.class.js:42`); `toggle-option-ui` derives `aria-pressed` (`toggle-group.class.js:65`). The reflected prop is the API; the ARIA attribute follows it.
|
|
300
|
+
- **Reference implementation:** `segment-ui` (`segment.yaml:32-36`, `segment.css:7`) is the current, live reference. **[historical, 2026-08-31, ADR-0056 amendment]** `toggle-option-ui`/`toggle-group-ui` were the converged second instance at ratification time, but both were cut outright in `0.8.43` (gh#1617), before any migration ran, `segmented-ui`/`segment-ui` absorbed the role. A new selection-item primitive copies `segment-ui`'s shape directly; there is no second live instance to extend by analogy anymore (the divergence gh#1303 describes was between `segment-ui` and the now-deleted `toggle-option-ui`).
|
|
303
301
|
|
|
304
|
-
Migration note: removing `data-selected` was a breaking change for external CSS that targeted it, so it shipped in two halves
|
|
302
|
+
Migration note: removing `data-selected` was a breaking change for external CSS that targeted it, so it shipped in two halves, the additive `selected` prop in `0.8.39`, the stamp removal on the `0.8.40` breaking wave with a migration-guide entry (`packages/web-components/CHANGELOG.md`; the ADR text names the wave `0.9.0`, it shipped as `0.8.40`).
|
|
305
303
|
|
|
306
304
|
Source: ADR-0056 (ratified 2026-08-15, gh#1303).
|
|
307
305
|
|
|
@@ -329,7 +327,7 @@ class UIInput extends UIFormElement {
|
|
|
329
327
|
};
|
|
330
328
|
|
|
331
329
|
connected() {
|
|
332
|
-
super.connected(); // MUST call
|
|
330
|
+
super.connected(); // MUST call, registers ElementInternals
|
|
333
331
|
// ...
|
|
334
332
|
}
|
|
335
333
|
|
|
@@ -347,9 +345,9 @@ class UIInput extends UIFormElement {
|
|
|
347
345
|
|
|
348
346
|
Key details:
|
|
349
347
|
|
|
350
|
-
- **Always `super.connected()` and `super.disconnected()
|
|
351
|
-
- **`this.syncValue(str)
|
|
352
|
-
- **Inheriting properties
|
|
348
|
+
- **Always `super.connected()` and `super.disconnected()`**, without them, form-association doesn't register.
|
|
349
|
+
- **`this.syncValue(str)`**, call this whenever the value changes to update the form-submitted string. Accepts a string.
|
|
350
|
+
- **Inheriting properties**, spread `UIFormElement.properties` into your own `static properties` so you don't re-declare `name`, `disabled`, `required`.
|
|
353
351
|
|
|
354
352
|
## Event conventions
|
|
355
353
|
|
|
@@ -373,9 +371,9 @@ render() {
|
|
|
373
371
|
|
|
374
372
|
Rule of thumb: if CSS can do the work via an attribute selector (`:scope[disabled] { ... }`), prefer CSS. Reserve `render()` for propagating state into child inputs, recalculating positions, or reflecting data changes that attribute selectors can't express.
|
|
375
373
|
|
|
376
|
-
## Popover `placement` defaults
|
|
374
|
+
## Popover `placement` defaults, picked by popover-to-trigger width ratio
|
|
377
375
|
|
|
378
|
-
Every popover-bearing primitive exposes a consumer-overridable `placement` attribute (declare it in the yaml SoT, with the default documented in the prop description). The DEFAULT is selected by the popover's natural width relative to its trigger
|
|
376
|
+
Every popover-bearing primitive exposes a consumer-overridable `placement` attribute (declare it in the yaml SoT, with the default documented in the prop description). The DEFAULT is selected by the popover's natural width relative to its trigger, never by component identity:
|
|
379
377
|
|
|
380
378
|
| Case | Default |
|
|
381
379
|
|---|---|
|
|
@@ -384,6 +382,6 @@ Every popover-bearing primitive exposes a consumer-overridable `placement` attri
|
|
|
384
382
|
| Trigger sits at a container's right edge **by construction** (toolbar spillover, right-pinned "more") | `bottom-end` |
|
|
385
383
|
| Non-bottom-axis surfaces | component-specific: `tooltip-ui` → `top`, `nav-group-ui` collapsed flyout → `right` |
|
|
386
384
|
|
|
387
|
-
Rationale: `bottom-start` on a wide popover under a right-anchored trigger fires `anchor.js`'s right-edge overflow recovery, snapping the panel far past the trigger's left edge (the v0.6.35 date-range-picker incident
|
|
385
|
+
Rationale: `bottom-start` on a wide popover under a right-anchored trigger fires `anchor.js`'s right-edge overflow recovery, snapping the panel far past the trigger's left edge (the v0.6.35 date-range-picker incident, ~800px panel under a ~280px button). Centered `bottom` shifts at most half the overflow distance. Classification is done once at authoring time; borderline cases resolve by what the popover *wants*: if it `matchWidth`s the trigger it is trigger-width regardless of absolute size. "The consumer placed their button on the right of the layout" is a call-site `placement="bottom-end"` override, never a default. The overflow-recovery logic in `anchor.js` is the safety net and stays unchanged: the default's job is to make recovery rarely fire.
|
|
388
386
|
|
|
389
387
|
Source: ADR-0034.
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
# Authoring cycle
|
|
1
|
+
# Authoring cycle, author a NEW primitive or MODIFY an existing one
|
|
2
2
|
|
|
3
|
-
The 5-step procedure run AFTER [primitive-audit.md](primitive-audit.md) clears (new primitive)
|
|
3
|
+
The 5-step procedure run AFTER [primitive-audit.md](primitive-audit.md) clears (new primitive), or jumping straight to Step 2 when modifying an existing one.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## Step 1
|
|
7
|
+
## Step 1, Read the contract and the reference components
|
|
8
8
|
|
|
9
9
|
Before writing or editing, load these four files. They are the ground truth:
|
|
10
10
|
|
|
11
|
-
- `.claude/docs/specs/component-token-contract.md
|
|
12
|
-
- `packages/web-components/core/element.js
|
|
13
|
-
- `packages/web-components/core/form.js
|
|
11
|
+
- `.claude/docs/specs/component-token-contract.md`, the authoritative invariants. Skim the whole thing if you haven't read it this session; focus on "Variants vs Modes" and "Sanctioned Mode Attributes" if you're adding a new layout-affecting attribute.
|
|
12
|
+
- `packages/web-components/core/element.js`, `UIElement` base class. The `static properties` schema, `connected()`/`disconnected()`/`render()` lifecycle, and attribute-mapping conventions are all defined here.
|
|
13
|
+
- `packages/web-components/core/form.js`, `UIFormElement`. Only needed if the new component participates in forms (inputs, selects, checkboxes, etc.).
|
|
14
14
|
- At least one good-citizen reference that matches the shape of what you're building. Pick from: `button-ui`, `card-ui`, `input-ui`, `textarea-ui`, `check-ui`. Read both the `.js` and the `.css`.
|
|
15
15
|
|
|
16
|
-
## Step 2
|
|
16
|
+
## Step 2, Classify the work before you write
|
|
17
17
|
|
|
18
18
|
Before typing, answer:
|
|
19
19
|
|
|
20
|
-
1. **Is this cosmetic or structural?** If the change affects `display`, `flex-direction`, `grid-template`, `padding`, layout geometry
|
|
20
|
+
1. **Is this cosmetic or structural?** If the change affects `display`, `flex-direction`, `grid-template`, `padding`, layout geometry, it's a mode, not a variant. Modes require an entry in the Sanctioned Mode Attributes table (`.claude/docs/specs/component-token-contract.md` `Modes` section). Do not introduce undocumented layout-changing variants.
|
|
21
21
|
|
|
22
22
|
2. **Does the new prop fit the Boolean-default-false rule?** If the default behavior is "on," negate the prop name before writing (`closable` is wrong if closable is the default; `permanent` is right).
|
|
23
23
|
|
|
24
24
|
3. **Does the component hold state that CSS needs to read?** If yes, every state-bearing Boolean declares `{ type: Boolean, default: false, reflect: true }`.
|
|
25
25
|
|
|
26
|
-
4. **Does the component add listeners, timers, or observers?** If yes, plan the `disconnected()` method at the same time as `connected()
|
|
26
|
+
4. **Does the component add listeners, timers, or observers?** If yes, plan the `disconnected()` method at the same time as `connected()`, do not defer. The symmetric pair is one unit of work, never two.
|
|
27
27
|
|
|
28
|
-
## Step 3
|
|
28
|
+
## Step 3, Apply the non-negotiable rules
|
|
29
29
|
|
|
30
30
|
These rules are the distilled lessons from a 5-iteration audit. Each one corresponds to a real bug that was fixed in the codebase. Full rationale and the bug histories are in [anti-patterns.md](anti-patterns.md).
|
|
31
31
|
|
|
32
32
|
### API / Attributes
|
|
33
33
|
|
|
34
|
-
1. **Boolean defaults are `false`.** If the expected default is "on," rename: `closable` → `permanent`, `animate` → `static`, `cursor` → `noCursor`, `average` → `noAverage`, `pause-on-hover` → `noPauseOnHover`. `no-*` is the canonical negation prefix (ADR-0063 decision 1)
|
|
34
|
+
1. **Boolean defaults are `false`.** If the expected default is "on," rename: `closable` → `permanent`, `animate` → `static`, `cursor` → `noCursor`, `average` → `noAverage`, `pause-on-hover` → `noPauseOnHover`. `no-*` is the canonical negation prefix (ADR-0063 decision 1), a new rename lands on `no-*` directly, never `hide-*`.
|
|
35
35
|
|
|
36
36
|
2. **No magic-value sentinels in numeric props.** Indeterminate = `null`, not `-1`. Consumers branch on `value == null`, which is explicit.
|
|
37
37
|
|
|
38
|
-
3. **Use `attribute:` not `attr:`.** `attr:` is a silent typo
|
|
38
|
+
3. **Use `attribute:` not `attr:`.** `attr:` is a silent typo, the mapper ignores it and the kebab-case HTML attribute never wires. This cost a real bug in `rating-ui`.
|
|
39
39
|
|
|
40
40
|
4. **State-bearing Booleans reflect.** `{ type: Boolean, default: false, reflect: true }`. Without `reflect`, CSS can't match `:scope[disabled]`, hover/active/selected states break silently.
|
|
41
41
|
|
|
42
|
-
5. **Reserved-name anti-patterns.** Avoid: `title` (collides with HTML tooltip attribute), `active` on parent components (use `value` for a selection or `step` for an index
|
|
42
|
+
5. **Reserved-name anti-patterns.** Avoid: `title` (collides with HTML tooltip attribute), `active` on parent components (use `value` for a selection or `step` for an index, per-item `active` on children is fine), `error` in variant names (use `danger`; reserve `error` for validation state), `disabled` on non-form-participating components (use `readonly`), `multiple` with exclusion semantics (use a negated positive like `single`).
|
|
43
43
|
|
|
44
|
-
6. **Element tag ends in `-ui`; JS class is `UI<Component>`.** `<foo-ui>` ↔ `class UIFoo extends UIElement`. Three-way consistency: filename, class name, custom-element tag. The sanctioned `-n` carve-out is `cot-ui` (the chain-of-thought streaming component); `nav-ui` was deprecated in favor of the `-ui` replacements. New `-n` tags require a contract-doc update
|
|
44
|
+
6. **Element tag ends in `-ui`; JS class is `UI<Component>`.** `<foo-ui>` ↔ `class UIFoo extends UIElement`. Three-way consistency: filename, class name, custom-element tag. The sanctioned `-n` carve-out is `cot-ui` (the chain-of-thought streaming component); `nav-ui` was deprecated in favor of the `-ui` replacements. New `-n` tags require a contract-doc update, see `.claude/docs/specs/component-token-contract.md`.
|
|
45
45
|
|
|
46
46
|
### CSS
|
|
47
47
|
|
|
@@ -57,12 +57,12 @@ These rules are the distilled lessons from a 5-iteration audit. Each one corresp
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
:scope {
|
|
60
|
-
/* ── Base styles
|
|
60
|
+
/* ── Base styles, consume only component tokens ── */
|
|
61
61
|
background: var(--component-bg);
|
|
62
62
|
color: var(--component-fg);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
/* ── Variants / states third
|
|
65
|
+
/* ── Variants / states third, override TOKENS only ── */
|
|
66
66
|
:scope[variant="outlined"] {
|
|
67
67
|
--component-bg: transparent;
|
|
68
68
|
--component-border: var(--a-border);
|
|
@@ -84,7 +84,7 @@ These rules are the distilled lessons from a 5-iteration audit. Each one corresp
|
|
|
84
84
|
|
|
85
85
|
### JS Lifecycle
|
|
86
86
|
|
|
87
|
-
1. **Every `addEventListener` in `connected()` has a matching `removeEventListener` in `disconnected()`.** Handler must be a stable `#field` arrow (`#onClick = (e) => { ... }`), never an inline arrow passed to `addEventListener`. Inline arrows can't be removed
|
|
87
|
+
1. **Every `addEventListener` in `connected()` has a matching `removeEventListener` in `disconnected()`.** Handler must be a stable `#field` arrow (`#onClick = (e) => { ... }`), never an inline arrow passed to `addEventListener`. Inline arrows can't be removed, `removeEventListener` needs reference equality.
|
|
88
88
|
|
|
89
89
|
2. **`UIFormElement` subclasses call `super.connected()` and `super.disconnected()`.** `ElementInternals` registration depends on it. Omitting `super` strands the form-association.
|
|
90
90
|
|
|
@@ -92,7 +92,7 @@ These rules are the distilled lessons from a 5-iteration audit. Each one corresp
|
|
|
92
92
|
|
|
93
93
|
4. **Null cached DOM refs in `disconnected()`.** `this.#fooEl = null` after removing its listeners. Prevents stale-tree GC pinning when the component is re-attached.
|
|
94
94
|
|
|
95
|
-
5. **Never declare `disconnected()` twice in one class.** The second silently overrides the first
|
|
95
|
+
5. **Never declare `disconnected()` twice in one class.** The second silently overrides the first, this exact bug lost `ResizeObserver` cleanup in `chart.js` for several commits. If you find yourself needing a "second disconnected," merge it into the existing one.
|
|
96
96
|
|
|
97
97
|
6. **Popover/tooltip overlays created in `connected()` are removed in `disconnected()`.** Anything appended to `document.body` or `<body>` via the Popover API needs explicit cleanup; they don't GC with the host.
|
|
98
98
|
|
|
@@ -100,7 +100,7 @@ These rules are the distilled lessons from a 5-iteration audit. Each one corresp
|
|
|
100
100
|
|
|
101
101
|
### Field composition
|
|
102
102
|
|
|
103
|
-
1. **Do not add a `label` attribute to a new form-associated control.** `<field-ui label="…">` is the canonical labeled-field wrapper. It owns the real `<label for="…">` and binds to the slotted control's id for proper click-to-focus
|
|
103
|
+
1. **Do not add a `label` attribute to a new form-associated control.** `<field-ui label="…">` is the canonical labeled-field wrapper. It owns the real `<label for="…">` and binds to the slotted control's id for proper click-to-focus, a pattern the embedded per-control `label` attribute can't provide (no `[for]`, just a shadow slot). Existing controls (input-ui, select-ui, textarea-ui, switch-ui, check-ui, radio-ui, slider-ui, calendar-picker-ui, upload-ui, range-ui) still accept the legacy `label` attr but log a one-shot console.warn; **no new control should declare one**. Wrap instead:
|
|
104
104
|
|
|
105
105
|
```html
|
|
106
106
|
<!-- right -->
|
|
@@ -120,8 +120,7 @@ These rules are the distilled lessons from a 5-iteration audit. Each one corresp
|
|
|
120
120
|
|
|
121
121
|
```css
|
|
122
122
|
@scope (my-composite-ui) {
|
|
123
|
-
/* 1. Composite paints the ring via :focus-within
|
|
124
|
-
wraps both the control and any siblings inside the shell. */
|
|
123
|
+
/* 1. Composite paints the ring via :focus-within, wraps both the control and any siblings inside the shell. */
|
|
125
124
|
:scope:focus-within {
|
|
126
125
|
box-shadow: var(--my-composite-focus-ring);
|
|
127
126
|
}
|
|
@@ -131,7 +130,7 @@ These rules are the distilled lessons from a 5-iteration audit. Each one corresp
|
|
|
131
130
|
}
|
|
132
131
|
|
|
133
132
|
/* 2. Suppress the inner control's own focus affordance.
|
|
134
|
-
The @scope block's containment IS the signal
|
|
133
|
+
The @scope block's containment IS the signal, no data
|
|
135
134
|
attribute or explicit opt-in needed; selectors targeting
|
|
136
135
|
inner elements only apply when they're inside this host. */
|
|
137
136
|
textarea-ui [slot="text"]:focus {
|
|
@@ -148,12 +147,12 @@ These rules are the distilled lessons from a 5-iteration audit. Each one corresp
|
|
|
148
147
|
```
|
|
149
148
|
|
|
150
149
|
**When to use this (not field-ui):**
|
|
151
|
-
- **field-ui** is a _wrapper composite_
|
|
152
|
-
- **chat-input-ui** (and future equivalents) are _shell composites_
|
|
150
|
+
- **field-ui** is a _wrapper composite_, it adds chrome (label / hint / error / required) around a control, but the control is still the primary focus target. Control owns the ring.
|
|
151
|
+
- **chat-input-ui** (and future equivalents) are _shell composites_: the composite IS the control; the inner textarea is an implementation detail. Host owns the ring.
|
|
153
152
|
|
|
154
153
|
A user's mental model is the discriminator: do they think of the composite as a single control, or as a labeled/wrapped version of an inner control? The former is a shell; the latter is a wrapper.
|
|
155
154
|
|
|
156
|
-
## Step 4
|
|
155
|
+
## Step 4, Run the 30-second self-check
|
|
157
156
|
|
|
158
157
|
Before declaring the work done, run through this checklist. If anything fails, fix before committing.
|
|
159
158
|
|
|
@@ -184,63 +183,61 @@ Before declaring the work done, run through this checklist. If anything fails, f
|
|
|
184
183
|
- [ ] Cached DOM refs (`this.#fooEl`) are nulled in `disconnected()`.
|
|
185
184
|
- [ ] Class declares `disconnected()` exactly once.
|
|
186
185
|
|
|
187
|
-
## Step 4b
|
|
186
|
+
## Step 4b, Author the yaml SoT (+ regenerate the sidecar)
|
|
188
187
|
|
|
189
|
-
A component isn't just its `.js`/`.css
|
|
188
|
+
A component isn't just its `.js`/`.css`: the `<name>.yaml` is the SOURCE OF
|
|
190
189
|
TRUTH the whole pipeline reads (docs site, A2UI registries, consumer
|
|
191
190
|
harnesses, `.d.ts` codegen). Every new primitive ships one; every prop/slot/
|
|
192
191
|
event change updates it. Field-by-field contract:
|
|
193
|
-
[yaml-contract.md](yaml-contract.md). Then regenerate
|
|
192
|
+
[yaml-contract.md](yaml-contract.md). Then regenerate, never hand-edit the
|
|
194
193
|
sidecar (`sidecar-prewrite-guard` blocks it anyway):
|
|
195
194
|
|
|
196
195
|
```bash
|
|
197
196
|
npm run build:components # yaml → <name>.a2ui.json + catalog + .d.ts
|
|
198
|
-
node scripts/build/components.mjs --verify # "clean
|
|
197
|
+
node scripts/build/components.mjs --verify # "clean, N files up-to-date"
|
|
199
198
|
```
|
|
200
199
|
|
|
201
|
-
A primitive also needs BOTH barrel registrations
|
|
200
|
+
A primitive also needs BOTH barrel registrations, the CSS `@import` in
|
|
202
201
|
`styles/components.css` AND the JS `export` in `components/index.js`
|
|
203
202
|
(`scripts/audit/check-components-js-barrel.mjs` gates the second). Either
|
|
204
|
-
registration drifts the built `dist/` bundles
|
|
203
|
+
registration drifts the built `dist/` bundles, regenerate them in the same
|
|
205
204
|
change (`npm run build:bundle-css && npm run build:bundle-js`) and commit the
|
|
206
205
|
`dist/` updates, or CI's `check:{css,js}-bundles-fresh` gates fail on the PR
|
|
207
206
|
(gh#390's PR shipped without this and failed exactly there). Both commands,
|
|
208
|
-
not just one
|
|
207
|
+
not just one, `theme-provider.min.js` (a JS bundle) inlines the co-emitted
|
|
209
208
|
`dist/web-components.sheet.js` twin, so a CSS-only edit still drifts a JS
|
|
210
209
|
bundle if only `build:bundle-css` runs (bit the v0.8.13 theme-panel work
|
|
211
210
|
twice).
|
|
212
211
|
|
|
213
212
|
**Rebuild JS bundles from an `npm ci` scratch checkout, never a pnpm-bootstrapped worktree.**
|
|
214
213
|
`node scripts/dev/bootstrap-worktree.mjs`'s pnpm install produces `dist/*.min.js` output
|
|
215
|
-
~30% larger than CI's npm-ci install (a lockfile/hoisting difference, not a code difference)
|
|
216
|
-
— running `build:bundle-js` in such a worktree commits drift instead of fixing it, and a
|
|
214
|
+
~30% larger than CI's npm-ci install (a lockfile/hoisting difference, not a code difference), running `build:bundle-js` in such a worktree commits drift instead of fixing it, and a
|
|
217
215
|
local `check:js-bundles-fresh` run there will falsely pass against its own inflated bundle.
|
|
218
216
|
When a PR built in a pnpm worktree needs a JS-bundle rebuild: commit the CSS-side changes
|
|
219
|
-
first if any (`build:bundle-css` has no install-shape risk
|
|
220
|
-
deterministic from source
|
|
217
|
+
first if any (`build:bundle-css` has no install-shape risk, LightningCSS's bundling is
|
|
218
|
+
deterministic from source, so the branch's committed `dist/web-components.sheet.js` is
|
|
221
219
|
trustworthy going into the next step, satisfying the CSS-before-JS ordering above), then
|
|
222
220
|
`git worktree add <scratch-path> <branch>`, `npm ci` there (real npm-shaped install), run
|
|
223
221
|
`build:bundle-js`, copy only the changed `dist/*.min.js` file(s) back into the working
|
|
224
222
|
worktree, commit there with an explicit pathspec, then remove the scratch worktree. This
|
|
225
223
|
is the correction to gh#1825's own PR body, which deferred the rebuild to "CI's
|
|
226
|
-
derived-artifact pipeline"
|
|
224
|
+
derived-artifact pipeline", wrong per ADR-0069 (`dist/` is Class C, committed and
|
|
227
225
|
PR-blocking, never derive-resync'd); the scratch-checkout procedure above is what actually
|
|
228
226
|
landed the fix, 2026-08-21.
|
|
229
227
|
|
|
230
228
|
**Never `@import` a remote URL from any file `build:bundle-css` bundles.**
|
|
231
229
|
LightningCSS's `bundleAsync` (the engine behind it) resolves every `@import`
|
|
232
|
-
as a local filesystem path, remote `http(s)` URLs included
|
|
230
|
+
as a local filesystem path, remote `http(s)` URLs included, a CSS-level
|
|
233
231
|
`@import url('https://fonts.googleapis.com/...')` breaks the build with
|
|
234
232
|
`ResolverError: No such file or directory`, not a lint warning; there's no
|
|
235
233
|
`bundleAsync` option to skip or externalize one `@import`. Link web fonts
|
|
236
234
|
(or anything else external) as a real `<link>` in the consumer's `<head>`
|
|
237
|
-
instead
|
|
235
|
+
instead, see `styles/theme-fonts-url.txt` for the pattern this repo's 12
|
|
238
236
|
named themes use. A generated CSS file's own fix doesn't survive its next
|
|
239
|
-
regen unless the generator is fixed too, not just the committed output
|
|
240
|
-
the standing "generated artifacts are never hand-edited" invariant
|
|
237
|
+
regen unless the generator is fixed too, not just the committed output: the standing "generated artifacts are never hand-edited" invariant
|
|
241
238
|
(AGENTS.md) cuts both ways.
|
|
242
239
|
|
|
243
|
-
## Step 5
|
|
240
|
+
## Step 5, Run the project's verification gates
|
|
244
241
|
|
|
245
242
|
Before committing, run the project's verify scripts. These catch the drift-shaped bugs the checklist can miss:
|
|
246
243
|
|
|
@@ -256,11 +253,11 @@ If a gate fails, fix before declaring done.
|
|
|
256
253
|
|
|
257
254
|
## Cross-references
|
|
258
255
|
|
|
259
|
-
- [primitive-audit.md](primitive-audit.md)
|
|
260
|
-
- [api-contract.md](api-contract.md)
|
|
261
|
-
- [css-patterns.md](css-patterns.md)
|
|
262
|
-
- [lifecycle-patterns.md](lifecycle-patterns.md)
|
|
263
|
-
- [anti-patterns.md](anti-patterns.md)
|
|
264
|
-
- [worked-example.md](worked-example.md)
|
|
265
|
-
- [yaml-contract.md](yaml-contract.md)
|
|
266
|
-
- [token-contract.md](token-contract.md)
|
|
256
|
+
- [primitive-audit.md](primitive-audit.md), the §0 gate (run BEFORE this)
|
|
257
|
+
- [api-contract.md](api-contract.md), deep dive on prop naming, type choices, reflection policy
|
|
258
|
+
- [css-patterns.md](css-patterns.md), exhaustive CSS architecture (@scope, variants, modes)
|
|
259
|
+
- [lifecycle-patterns.md](lifecycle-patterns.md), timers, observers, popovers, listener patterns
|
|
260
|
+
- [anti-patterns.md](anti-patterns.md), full failure-mode catalogue, file:line refs
|
|
261
|
+
- [worked-example.md](worked-example.md), badge-ui + counter-ui walkthroughs
|
|
262
|
+
- [yaml-contract.md](yaml-contract.md), the `<name>.yaml` SoT schema (Step 4b)
|
|
263
|
+
- [token-contract.md](token-contract.md), token audit (post-implementation check)
|