@adia-ai/adia-ui-forge 0.8.59 → 0.8.60

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.
@@ -89,13 +89,26 @@ check) is unaffected either way — it only checks for a slot literally named
89
89
  ## `a2ui.allowedParents:` / `a2ui.allowedChildren:` — composition constraints (SPEC REQ-011, gh#1353)
90
90
 
91
91
  Optional keys inside the `a2ui:` block, alongside `rules:`. Each is a
92
- non-empty list of catalog `component:` names (NOT tags) naming the direct
93
- parents this component may sit under / the direct children it may contain.
94
- The reserved name `Surface` (the A2UI v1.0 implicit root container) is legal
92
+ non-empty list of catalog `component:` names (NOT tags) naming the parents
93
+ this component may sit under / the direct children it may contain. The
94
+ reserved name `Surface` (the A2UI v1.0 implicit root container) is legal
95
95
  only in `allowedParents` and means "may sit at the surface root". **Omitted
96
96
  means unconstrained** — never write an empty list (that would mean "allowed
97
97
  nowhere"; the build refuses it).
98
98
 
99
+ `allowedParents` matches the nearest custom-element ancestor, not the
100
+ immediate DOM parent (gh#3310): the generated lint rule's
101
+ `compositionFindings()` walks up past native (non-hyphenated) wrapper
102
+ elements — `section`, `div`, `td`, `tr`, `tbody`, and any other plain HTML
103
+ tag — until it finds a real catalog component tag or reaches the surface
104
+ root. This mirrors Light DOM's own composition reality (AGENTS.md: CSS
105
+ positions by tag + ancestor + DOM order) — a wrapper interposed for layout
106
+ or semantics (a `<section>` inside a card, a `<td>` in a table body) doesn't
107
+ change a component's logical host. `allowedChildren`, by contrast, still
108
+ matches DIRECT children only — a named-slot child that should be exempt
109
+ from the default-slot list is a separate, open gap (gh#3308), not addressed
110
+ by this semantics change.
111
+
99
112
  ```yaml
100
113
  a2ui:
101
114
  allowedParents:
@@ -112,6 +125,35 @@ children). A parent that adopts items through wrappers (menu.class.js's
112
125
  deliberate descendant query) must NOT constrain — a declared constraint
113
126
  stricter than the source is a defect, not documentation.
114
127
 
128
+ **`allowedChildren` is a default-slot-only check (gh#3308).** A direct
129
+ child carrying ANY `slot=` attribute is a sibling-level named slot, not a
130
+ default-slot member — the lint-side matcher (`compositionFindings`)
131
+ exempts it from `allowedChildren` entirely, regardless of the slot's name.
132
+ This is why menu-ui can declare `allowedChildren: [MenuItem, MenuDivider,
133
+ MenuLabel]` for its default slot while still accepting an arbitrary
134
+ focusable element on `slot="trigger"` ("typically button-ui, but any
135
+ focusable element works") without a false positive. When a named slot
136
+ *should* be constrained too (rare — most named slots exist precisely
137
+ because their content varies), add an `allowedChildrenBySlot:` map
138
+ alongside `allowedChildren:`, keyed by slot name, same catalog-name-list
139
+ shape:
140
+
141
+ ```yaml
142
+ a2ui:
143
+ allowedChildren:
144
+ - MenuItem
145
+ - MenuDivider
146
+ - MenuLabel
147
+ allowedChildrenBySlot:
148
+ trigger: # only if the trigger slot ITSELF needs constraining
149
+ - ButtonUI
150
+ ```
151
+
152
+ A slot with no entry in `allowedChildrenBySlot` (or the key omitted
153
+ entirely) stays unconstrained by design — this is the common case, and
154
+ matches "omitted means unconstrained" for `allowedChildren`/`allowedParents`
155
+ above.
156
+
115
157
  Pipeline: `components.mjs` validates the shape per-yaml, cross-checks every
116
158
  referenced name against the full catalog on a full build, and forwards the
117
159
  lists onto `x-adiaui` → `catalog-a2ui_1_0.json`.
@@ -122,16 +164,71 @@ per gh#2116) across the five opt-out-scoped catalogs (`adia.core.json`,
122
164
  `adia.shells.json`, gh#2211/ADR-0093), where the vendored `@genui/core`
123
165
  validator enforces them (`UNALLOWED_PARENT`/`UNALLOWED_CHILD`). Module-tier
124
166
  yamls (web-modules) now carry a v1.0 sidecar too and land in `adia.shells`.
125
-
126
- Once a component earns a `component.md` (both `screenReader:` and
127
- `behavioral:` authored, gh#2615), a non-empty `allowedParents`/
128
- `allowedChildren` also generates an enforced lint rule (LLD-0016 §C4,
129
- gh#2647): `scripts/build/gen-composition-rules.mjs` emits `scripts/lint/
167
+ `allowedChildrenBySlot` (gh#3308) is NOT part of this catalog/sidecar
168
+ pipeline `components.mjs` never reads it, it never lands in
169
+ `x-adiaui`/`catalog-a2ui_1_0.json`, and the A2UI v1.0 protocol validator
170
+ never enforces it. It exists purely for the lint-side check below.
171
+
172
+ A non-empty `allowedParents`/`allowedChildren`/`allowedChildrenBySlot`
173
+ generates an enforced lint rule (LLD-0016 §C4, gh#2647):
174
+ `scripts/build/gen-composition-rules.mjs` emits `scripts/lint/
130
175
  rules/generated/composition/<name>.mjs`, which flags a markup file where
131
- the tag nests under (or contains) a non-declared tag. Advisory (`warn`)
176
+ the tag nests under (or contains) a non-declared tag `allowedChildren`
177
+ checked against default-slot children only, `allowedChildrenBySlot`
178
+ against the matching named-slot children (see above). Advisory (`warn`)
132
179
  until a corpus-wide rollout promotes it (`npm run build:composition-rules`
133
180
  / `check:composition-rules-fresh`).
134
181
 
182
+ ### `a2ui.noCompositionConstraint:`: the audited "no contract needed" verdict (lld-0029 C2a, PR #3456)
183
+
184
+ The third disposition a yaml can carry. Where `allowedParents` /
185
+ `allowedChildren` declare a containment edge, `noCompositionConstraint`
186
+ records that the authoring-rule audit above was actually run (read
187
+ `<name>.class.js` for `querySelector` / `closest` / `this.children`
188
+ child-tag expectations, then test any parent/child hypothesis against real
189
+ markup in `apps/`, `site/`, `catalog/`, `packages/web-components/patterns/`)
190
+ and found no constraint worth declaring. Its whole purpose is to let a
191
+ reader, or a gate, tell "audited, categorically needs none" apart from
192
+ "never audited". Do not write it as a shortcut: if the audit turns up a
193
+ real constraint, the fix is a genuine `allowedParents` / `allowedChildren`
194
+ addition, not this marker.
195
+
196
+ Shape (`scripts/schemas/component.yaml.schema.json`, `a2ui.noCompositionConstraint`):
197
+ an object with two required keys and nothing else. `reason` is the verdict
198
+ quoted from the disposition table (schema `minLength: 8`); `ticket` is the
199
+ issue whose table recorded it (`^gh#[0-9]+$`). As with every other schema
200
+ constraint (ADR-0057), the shape is IDE-visible only: `compileComponent()`
201
+ never reads this key, so a malformed block does not stop the build.
202
+
203
+ Real example, `packages/web-components/components/badge/badge.yaml`:
204
+
205
+ ```yaml
206
+ a2ui:
207
+ noCompositionConstraint:
208
+ reason: 'Referenced generically from a dozen+ unrelated components, not scoped to one parent; own rule says positioning, not ancestry.'
209
+ ticket: 'gh#3270'
210
+ rules:
211
+ - 'Use for small status/count labels attached to another element (notification counts, status pills, version tags).'
212
+ ```
213
+
214
+ Relationship to the two lists. A yaml carries either the marker or a
215
+ containment list, never both. This is now enforced (gh#3497), not just
216
+ convention: `component.yaml.schema.json` declares the two mutually
217
+ exclusive via the `a2ui` object's own `not` clause, and
218
+ `compileCompositionConstraints()` in `scripts/build/components.mjs` throws
219
+ at build time, naming the file and the offending field, when a yaml
220
+ declares both. `npm run check:components-valid` (part of `npm run
221
+ build:components`) is where that throw surfaces.
222
+
223
+ Pipeline: none. The marker is a yaml-only annotation with no sidecar,
224
+ `x-adiaui`, catalog, or generated-lint-rule forwarding (lld-0029 C2a, by
225
+ design), so a PR that only adds it changes no derived artifact and commits
226
+ no regen. The planned `check:composition-coverage` gate (lld-0029 C3,
227
+ `scripts/verify/check-composition-coverage.mjs`, not landed as of
228
+ 2026-09-04) fails any yaml whose `a2ui` block has none of `allowedParents`,
229
+ `allowedChildren`, or `noCompositionConstraint`, so the marker counts as
230
+ coverage exactly as either list does.
231
+
135
232
  ---
136
233
 
137
234
  ## `examples:` field — a2ui example ids (semantic-id grammar, gh#2492)