@adia-ai/adia-ui-factory 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/.claude-plugin/plugin.json +12 -3
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +24 -1
  4. package/bin/adia-scaffold +93 -13
  5. package/package.json +2 -2
  6. package/references/shell-embed.md +10 -0
  7. package/references/spa-architecture.md +7 -0
  8. package/skills/adia-ui-kit/CHANGELOG.md +41 -0
  9. package/skills/adia-ui-kit/SKILL.md +442 -0
  10. package/skills/adia-ui-kit/assets/figma-make/guidelines/Guidelines.md +87 -0
  11. package/skills/adia-ui-kit/assets/figma-make/guidelines/components.md +142 -0
  12. package/skills/adia-ui-kit/assets/figma-make/guidelines/styles.md +118 -0
  13. package/skills/adia-ui-kit/assets/figma-make/guidelines/tokens.md +120 -0
  14. package/skills/adia-ui-kit/assets/team-scaffold/archive-README.md +30 -0
  15. package/skills/adia-ui-kit/assets/team-scaffold/drafts-README.md +57 -0
  16. package/skills/adia-ui-kit/assets/team-scaffold/inbox-README.md +76 -0
  17. package/skills/adia-ui-kit/assets/team-scaffold/outbox-README.md +46 -0
  18. package/skills/adia-ui-kit/assets/team-scaffold/team-root.README.md +51 -0
  19. package/skills/adia-ui-kit/assets/team-scaffold/tickets-README.md +208 -0
  20. package/skills/adia-ui-kit/assets/team-scaffold/tickets-REGISTRY.md +81 -0
  21. package/skills/adia-ui-kit/assets/templates/AGENTS.template.md +128 -0
  22. package/skills/adia-ui-kit/assets/templates/CLAUDE.template.md +1 -0
  23. package/skills/adia-ui-kit/assets/templates/FEEDBACK-OMISSIONS-DEFENSE.template.md +150 -0
  24. package/skills/adia-ui-kit/assets/templates/FEEDBACK.template.md +213 -0
  25. package/skills/adia-ui-kit/assets/templates/RESPONSE.template.md +114 -0
  26. package/skills/adia-ui-kit/assets/templates/figma-make-guidelines.md +90 -0
  27. package/skills/adia-ui-kit/evals/adversarial-content-trust.json +83 -0
  28. package/skills/adia-ui-kit/evals/evals.json +55 -0
  29. package/skills/adia-ui-kit/evals/routing-corpus.json +121 -0
  30. package/skills/adia-ui-kit/evals/teach-routing-cases.json +92 -0
  31. package/skills/adia-ui-kit/references/capabilities.md +245 -0
  32. package/skills/adia-ui-kit/references/catalog-lookup.md +186 -0
  33. package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v1.md +8 -0
  34. package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v2.md +3585 -0
  35. package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v3.md +1899 -0
  36. package/skills/adia-ui-kit/references/common-gotchas-consumer.md +522 -0
  37. package/skills/adia-ui-kit/references/component-literacy.md +291 -0
  38. package/skills/adia-ui-kit/references/composition.md +245 -0
  39. package/skills/adia-ui-kit/references/correction-loop.md +462 -0
  40. package/skills/adia-ui-kit/references/css-policy.md +343 -0
  41. package/skills/adia-ui-kit/references/decision-model.md +158 -0
  42. package/skills/adia-ui-kit/references/diagnostic-output.md +57 -0
  43. package/skills/adia-ui-kit/references/docs-catalog.md +239 -0
  44. package/skills/adia-ui-kit/references/exclusions.md +225 -0
  45. package/skills/adia-ui-kit/references/feedback-authoring.md +466 -0
  46. package/skills/adia-ui-kit/references/figma-make.md +244 -0
  47. package/skills/adia-ui-kit/references/flows-auth.md +307 -0
  48. package/skills/adia-ui-kit/references/flows-content.md +206 -0
  49. package/skills/adia-ui-kit/references/flows-data.md +348 -0
  50. package/skills/adia-ui-kit/references/flows-interaction.md +181 -0
  51. package/skills/adia-ui-kit/references/flows-shells.md +336 -0
  52. package/skills/adia-ui-kit/references/gap-detection.md +266 -0
  53. package/skills/adia-ui-kit/references/harness.md +284 -0
  54. package/skills/adia-ui-kit/references/loading-protocol.md +406 -0
  55. package/skills/adia-ui-kit/references/local-link.md +198 -0
  56. package/skills/adia-ui-kit/references/mcp.md +401 -0
  57. package/skills/adia-ui-kit/references/migration.md +116 -0
  58. package/skills/adia-ui-kit/references/mission.md +194 -0
  59. package/skills/adia-ui-kit/references/mode-routing.md +298 -0
  60. package/skills/adia-ui-kit/references/patterns-agent.md +223 -0
  61. package/skills/adia-ui-kit/references/patterns-data.md +1066 -0
  62. package/skills/adia-ui-kit/references/patterns-forms.md +223 -0
  63. package/skills/adia-ui-kit/references/patterns-layout.md +326 -0
  64. package/skills/adia-ui-kit/references/patterns-recipes.md +1416 -0
  65. package/skills/adia-ui-kit/references/patterns-shells.md +1097 -0
  66. package/skills/adia-ui-kit/references/patterns-traits.md +259 -0
  67. package/skills/adia-ui-kit/references/patterns-wiring.md +339 -0
  68. package/skills/adia-ui-kit/references/pev-rationale.md +137 -0
  69. package/skills/adia-ui-kit/references/reasoning-gate.md +146 -0
  70. package/skills/adia-ui-kit/references/reasoning-methodologies.md +112 -0
  71. package/skills/adia-ui-kit/references/recon.md +192 -0
  72. package/skills/adia-ui-kit/references/rendering-model.md +431 -0
  73. package/skills/adia-ui-kit/references/response-authoring.md +398 -0
  74. package/skills/adia-ui-kit/references/session-log-schema.md +203 -0
  75. package/skills/adia-ui-kit/references/session-log-template.md +118 -0
  76. package/skills/adia-ui-kit/references/setup.md +652 -0
  77. package/skills/adia-ui-kit/references/skill-hygiene.md +135 -0
  78. package/skills/adia-ui-kit/references/spec-to-ui-reasoning.md +394 -0
  79. package/skills/adia-ui-kit/references/state.md +229 -0
  80. package/skills/adia-ui-kit/references/teach-protocol.md +421 -0
  81. package/skills/adia-ui-kit/references/theme.md +142 -0
  82. package/skills/adia-ui-kit/references/version-check.md +69 -0
  83. package/skills/adia-ui-kit/scripts/audit-decision-trace.mjs +198 -0
  84. package/skills/adia-ui-kit/scripts/audit-kit-roster.mjs +366 -0
  85. package/skills/adia-ui-kit/scripts/changelog-archive.mjs +126 -0
  86. package/skills/adia-ui-kit/scripts/check-adia-versions.py +404 -0
  87. package/skills/adia-ui-kit/scripts/run-skill-evals.mjs +358 -0
  88. package/skills/adia-ui-kit/scripts/validate-session-log.mjs +242 -0
  89. package/skills/adia-ui-kit/skill.json +102 -0
  90. package/skills/adia-ui-migration/CHANGELOG.md +47 -0
  91. package/skills/adia-ui-migration/SKILL.md +381 -0
  92. package/skills/adia-ui-migration/skill.json +29 -0
  93. package/skills/adia-ui-spa/SKILL.md +1 -1
  94. package/skills/adia-ui-verify/SKILL.md +2 -2
@@ -0,0 +1,522 @@
1
+ ---
2
+ name: common-gotchas-consumer
3
+ load-when: Before Phase 4 (Author) when composing AdiaUI primitives in a consumer app, OR when debugging visual defects in already-composed surfaces. Use as a checklist alongside `component-literacy.md`'s pre-flight gate.
4
+ load-size-tokens: ~2,200
5
+ required-for:
6
+ - Mode 1-4 composition that embeds composites with their own @scope CSS layouts
7
+ - Any surface containing toolbars / button clusters / form-control rows
8
+ - Any surface using container queries for responsive layout
9
+ - Debugging a "passes audit but looks broken" defect
10
+ companion-skill: "adia-ui-authoring v1.8.2 (sister skill's `references/common-gotchas.md` — same patterns, substrate-side framing)"
11
+ sister-postmortems:
12
+ - ".brain/postmortems/2026-05-24-component-css-illiteracy.md"
13
+ - ".brain/postmortems/2026-05-24-card-structure-audit-enforcement-gap.md"
14
+ ---
15
+
16
+ # Common gotchas — consumer composition traps
17
+
18
+ Four concrete failure modes recurring across the 2026-05-24 billing-overview + 8-demo cohort. The kit's `component-literacy.md` gate forces CSS reads at selection time, but **reading is not recognizing**. These four patterns recur in consumer code even after literacy is established — they're the next layer of discipline.
19
+
20
+ Each entry: pattern + how to detect + the right fix + sister-skill citation.
21
+
22
+ This reference parallels `adia-ui-authoring`'s `references/common-gotchas.md` (5 gotchas; the 5th — nested HTML comments in design-plan blocks — is substrate-side authoring-specific and not relevant to consumer composition).
23
+
24
+ ---
25
+
26
+ ## §1 — Composite used without using its composition grammar
27
+
28
+ **Pattern**: Consumer wraps multiple `<X-ui>` primitives in an embedded composite (e.g., card-ui, drawer-ui) and stamps arbitrary `<div>` children inside instead of using the composite's expected slot grammar. Composite's `@scope` rules ONLY activate when their canonical children are present. Direct `<div>` children fall through to `display: block` → no inset, no grid, no slot positioning.
29
+
30
+ **Example** (consumer-side):
31
+
32
+ ```html
33
+ <!-- WRONG — card-ui's grammar never activates -->
34
+ <card-ui>
35
+ <div class="my-icon"><icon-ui name="user"></icon-ui></div>
36
+ <div class="my-text">Profile · Settings</div>
37
+ <div class="my-actions"><button-ui></button-ui></div>
38
+ </card-ui>
39
+
40
+ <!-- RIGHT — card-ui's header grammar activates -->
41
+ <card-ui>
42
+ <header>
43
+ <span slot="icon"><icon-ui name="user"></icon-ui></span>
44
+ <span slot="heading">Profile · Settings</span>
45
+ <span slot="action"><button-ui></button-ui></span>
46
+ </header>
47
+ </card-ui>
48
+ ```
49
+
50
+ **How to detect**:
51
+ - `npm run audit:card-structure --strict` in the consuming repo (substrate audit; runs against any HTML the consumer authors)
52
+ - Visual: chrome looks "flat" — no inset between border and content, sibling divs touching the card edge
53
+
54
+ **The right fix**: USE the composite's grammar. Pre-flight Component Literacy (`references/component-literacy.md`) is the upstream defense; this gotcha is what happens when literacy is skipped.
55
+
56
+ **Sister postmortem**: `.brain/postmortems/2026-05-24-component-css-illiteracy.md`
57
+ **Sister commit**: chat-ui `4e86400c9` (payment-method-list rewrite to canonical grammar)
58
+
59
+ ---
60
+
61
+ ## §2 — Parent CSS overriding child primitive's intrinsic display
62
+
63
+ **Pattern**: Consumer writes a parent that toggles visibility of an embedded primitive via `display: none` ↔ `display: block`. The `display: block` override beats the primitive's own `:scope { display: flex }` from its own `@scope` (specificity 0,2,0 vs 0,1,0). Primitive's intrinsic layout silently collapses.
64
+
65
+ **Example** (consumer-side):
66
+
67
+ ```css
68
+ /* WRONG — clobbers <empty-state-ui>'s flex column layout */
69
+ .my-list-wrapper > [data-empty] { display: none; }
70
+ .my-list-wrapper[empty] > [data-empty] { display: block; }
71
+ ```
72
+
73
+ When the `[empty]` state activates, the empty-state-ui's `display: flex; flex-direction: column; align-items: center` is overridden to `display: block`. Icon, heading, description flow inline as a mash:
74
+ **`⊡ No itemsAdd one to get started.`**
75
+
76
+ **Right fix** — invert the toggle so no display value is set when shown:
77
+
78
+ ```css
79
+ /* RIGHT — child primitive's intrinsic display remains intact */
80
+ .my-list-wrapper:not([empty]) > [data-empty] { display: none; }
81
+ ```
82
+
83
+ **How to detect**:
84
+ - Visual: empty-state / loading-state / error-state primitives that render inline-text instead of stacked
85
+ - DevTools: inspect the affected primitive; look for `display: block` coming from a parent rule beating the `:scope { display: flex }` rule
86
+
87
+ **Sister commit**: chat-ui `4223a4147` (4 billing composites fixed simultaneously — same pattern)
88
+
89
+ ---
90
+
91
+ ## §3 — Mixed sizes across form/control groups
92
+
93
+ **Pattern**: Consumer composes a toolbar or button cluster mixing multiple form/control primitives. Each primitive has a different default height. `<button-ui>` defaults to `md`; `<input-ui>` defaults larger; `<search-ui>` (which wraps `<input-ui>`) doesn't forward size to its inner control. Same row, mismatched baselines.
94
+
95
+ **Example** (consumer-side):
96
+
97
+ ```html
98
+ <!-- WRONG — three different control heights side-by-side -->
99
+ <row-ui>
100
+ <button-ui icon="filter" text="Filter"></button-ui> <!-- 32px default -->
101
+ <search-ui placeholder="Find..."></search-ui> <!-- 36px (no size) -->
102
+ <tag-ui data-filter-chip text="Active"></tag-ui> <!-- 24px sm -->
103
+ </row-ui>
104
+
105
+ <!-- RIGHT — all controls explicitly sized the same -->
106
+ <row-ui>
107
+ <button-ui size="sm" icon="filter" text="Filter"></button-ui>
108
+ <search-ui placeholder="Find..."></search-ui> <!-- no size= prop; search-ui fills its row (height from the inner input) -->
109
+ <tag-ui size="sm" data-filter-chip text="Active"></tag-ui>
110
+ </row-ui>
111
+ ```
112
+
113
+ **How to detect**:
114
+ - Visual: controls in a row don't share a baseline
115
+ - Playwright probe: measure `getBoundingClientRect().height` of each control; mismatched → fix
116
+
117
+ **Right fix**:
118
+ - Set the SAME `size` attribute on every control in the group explicitly (don't rely on defaults — they diverge)
119
+ - If a wrapper primitive doesn't forward `[size]` to its inner control, file a substrate FB ticket; do NOT work around it by reaching into the wrapper's internals
120
+
121
+ **Sister commit**: chat-ui `1298fb064` (search-ui forwarding + toolbar size=sm consistency)
122
+
123
+ ---
124
+
125
+ ## §4 — `minmax(<min>, 1fr)` inside `repeat()` fighting container queries
126
+
127
+ **Pattern**: Consumer writes a grid with `repeat(N, minmax(<min>, 1fr))` AND the container has `@container` queries collapsing column count at breakpoints. The minmax fights the breakpoints — narrow viewports hit the minmax floor and overflow BEFORE the breakpoint reduces N.
128
+
129
+ **Example** (consumer-side):
130
+
131
+ ```css
132
+ /* WRONG — minmax fights container queries */
133
+ .my-kpi-grid {
134
+ display: grid;
135
+ grid-template-columns: repeat(4, minmax(16em, 1fr));
136
+ }
137
+ @container (max-width: 48em) {
138
+ .my-kpi-grid { grid-template-columns: 1fr; } /* this fights the minmax floor */
139
+ }
140
+
141
+ /* RIGHT — clean equal share; breakpoints own responsive */
142
+ .my-kpi-grid {
143
+ display: grid;
144
+ grid-template-columns: repeat(4, 1fr);
145
+ }
146
+ @container (max-width: 48em) { .my-kpi-grid { grid-template-columns: 1fr; } }
147
+ ```
148
+
149
+ **How to detect**:
150
+ - Visual: at narrow widths the grid overflows horizontally BEFORE the breakpoint collapses it
151
+ - Code review: `repeat()` with `minmax()` AND `@container` queries in the same stylesheet = fighting
152
+
153
+ **Right fix**: When using container queries for responsive collapse, use plain `repeat(N, 1fr)` columns. minmax is for grids WITHOUT container-query-driven N changes.
154
+
155
+ **Sister commit**: chat-ui `94d497557` (dashboard-layout KPI grid)
156
+
157
+ ---
158
+
159
+ ## §5 — `<admin-page-header>` / `<admin-page-body>` without inner column-owner
160
+
161
+ **Pattern**: Consumer drops content directly inside `<admin-page-header>` or `<admin-page-body>` (bare `<h1>`, bare `<card-ui>`, `<col-ui>`, `<grid-ui>`, etc.) without the required inner `<header>` / `<section>` element. The bespoke wrappers handle full-width chrome (sticky, border, background, scroll sizing); padding + max-width + section-gap all live on `admin-page-header > :is(header, header-ui)` and `admin-page-body > :is(section, section-ui)`. Skipping the inner element drops 40px of inset silently. Page renders, no errors, just looks "off."
162
+
163
+ A second axis of the same trap: wrapping `<admin-page>`'s children in a routing `<div>` (`<admin-page><div id="view">...</div></admin-page>`) breaks the `admin-page > admin-page-header` direct-child selector entirely. `<admin-page-body>` falls back to `display: inline` (HTMLUnknownElement) — catastrophic, but still silent.
164
+
165
+ **Example** (consumer-side):
166
+
167
+ ```html
168
+ <!-- WRONG — bare h1 + bare card; loses padding/max-width on both axes -->
169
+ <admin-page>
170
+ <admin-page-header>
171
+ <h1>Dashboard</h1>
172
+ </admin-page-header>
173
+ <admin-page-body>
174
+ <card-ui>…</card-ui>
175
+ </admin-page-body>
176
+ </admin-page>
177
+
178
+ <!-- RIGHT — inner column-owners present -->
179
+ <admin-page>
180
+ <admin-page-header>
181
+ <header><h1>Dashboard</h1></header>
182
+ </admin-page-header>
183
+ <admin-page-body>
184
+ <section>
185
+ <card-ui>…</card-ui>
186
+ </section>
187
+ </admin-page-body>
188
+ </admin-page>
189
+ ```
190
+
191
+ **How to detect**:
192
+ - `npm run audit:admin-page-structure --strict` in the consuming repo (substrate audit; flags any `<admin-page-{header,body}>` whose direct child isn't a canonical column-owner — `:is(header, header-ui, section, section-ui, iframe, editor-shell, pane-ui)`)
193
+ - Visual: cards/grids/charts flush against the leading edge of the content area; no inset to viewport edge
194
+ - DevTools: `getComputedStyle(adminPageBody).padding` returns `0px` (should be `var(--page-content-inset)` ≈ 40px)
195
+ - DevTools: `getComputedStyle(adminPageHeader).position` returns `static` instead of `sticky` (root cause: a wrapping `<div>` broke the direct-child selector)
196
+
197
+ **Right fix**: Always nest `<header>` inside `<admin-page-header>` and `<section>` inside `<admin-page-body>`. For routing/view-switching between multiple `<admin-page>` instances, use `<router-ui>` or sibling `<admin-page id="…" hidden>` — never wrap `<admin-page>`'s children in a `<div>`. See `patterns-shells.md` §Wrapper-vs-column rule + §Routing inside admin-shell.
198
+
199
+ **Sister postmortem**: filed via FB-68 / FB-69 (claims-ui-v11, 2026-05-25)
200
+ **Substrate audit gate**: none yet (forward work — see §What the substrate-side audit currently catches table below)
201
+
202
+ ---
203
+
204
+ ## §6 — Bare `[slot="icon"]` + `[slot="heading"]` brand chrome instead of `<admin-entity-item>`
205
+
206
+ **Pattern**: Consumer authors `<admin-topbar slot="header">` brand chrome by composing `[slot="icon"]` + `[slot="heading"]` siblings (or wrapping them in `<a>`) instead of using the canonical `<admin-entity-item>` primitive. The bare-slot shape is structurally valid — both slots are documented in admin-topbar's CSS — but it misses the dedicated identity-row primitive (shipped `@adia-ai/web-modules@0.7.13`, FB-38) whose entire purpose is "shell-tier icon + label + badge identity row that collapses as one slottable unit and aligns with `<nav-item-ui>` icons."
207
+
208
+ Two consequences:
209
+
210
+ 1. **8px horizontal misalignment with `<nav-item-ui>` icons.** Bare brand icon sits at `--page-sidebar-px` (4px); `<nav-item-ui>` icons in `<section-ui><nav-ui>` sit at 12px (`<section-ui>` 4px + `<nav-item-ui>` 8px). admin-entity-item's geometry mirrors `<select-ui>`'s trigger row → brand icon at 12px → 0px delta with nav.
211
+ 2. **No shared collapse boundary** in collapsible sidebars. Bare `[slot="icon"]` and `[slot="heading"]` rely on separate CSS cleanup rules; admin-entity-item collapses as one unit (icon survives, label + badge hide together).
212
+
213
+ Per admin-entity-item's own a2ui rule: *"Do NOT re-implement it with a bare `<span>` or an ad-hoc flex `<div>` — those have no shared collapse boundary."* The kit skill agrees in v3.7.5+.
214
+
215
+ **Example** (consumer-side):
216
+
217
+ ```html
218
+ <!-- WRONG — bare slots, 8px misaligned with nav, no shared collapse boundary -->
219
+ <admin-topbar slot="header">
220
+ <span slot="icon"><icon-ui name="lightning"></icon-ui></span>
221
+ <span slot="heading">Claims Portal</span>
222
+ </admin-topbar>
223
+
224
+ <!-- RIGHT — canonical identity-row primitive -->
225
+ <admin-topbar slot="header">
226
+ <admin-entity-item slot="heading">
227
+ <icon-ui slot="icon" name="lightning"></icon-ui>
228
+ <span slot="label">Claims Portal</span>
229
+ <!-- Optional: <badge-ui slot="badge" text="Staging" size="sm" variant="warning"></badge-ui> -->
230
+ </admin-entity-item>
231
+ </admin-topbar>
232
+ ```
233
+
234
+ **How to detect**:
235
+ - `npm run audit:brand-chrome --strict` in the consuming repo (substrate audit; flags any `<admin-topbar slot="header">` inside `<admin-sidebar slot="leading">` whose direct child carries `slot="icon"` or `slot="heading"` AND isn't `<admin-entity-item>`)
236
+ - Visual: brand icon sits at the leading edge (4px); nav icons sit indented (12px). 8px gap reads as inconsistent padding.
237
+ - DevTools probe: `document.querySelector('admin-topbar [slot="icon"]').getBoundingClientRect().left` returns `4` (should be `12` to match nav).
238
+ - Code review: any `[slot="icon"]` + `[slot="heading"]` pair in `<admin-topbar slot="header">` of a `<admin-sidebar slot="leading">` should be `<admin-entity-item slot="heading">` with the icon/label as inner slots.
239
+
240
+ **Right fix**: Wrap the icon + label (+ optional badge) in `<admin-entity-item slot="heading">`. Same primitive already used in the `<admin-statusbar slot="footer">` for user identity — consistent identity-row geometry top and bottom of the sidebar.
241
+
242
+ **Sister ticket**: FB-71 (claims-ui-v11, 2026-05-25). The discovery gap: v3.7.4's §Brand chrome H3 canonized bare-slot Pattern A without pointing at `<admin-entity-item>`, which was buried in a footer-only comment block in `patterns-recipes.md`. Closed in v3.7.5.
243
+
244
+ ---
245
+
246
+ ## §7 — `<router-ui>` silently renders empty when the provider isn't registered
247
+
248
+ **Pattern**: Consumer does `import '@adia-ai/web-components'` (the main barrel), drops `<router-ui></router-ui>` into the admin-shell scroll region, sets `router.routes = [...]` — and the route content area stays empty. No console error, no warning, no network activity. The shell chrome renders correctly; only the route content is missing.
249
+
250
+ **Root cause** (pre-v0.6.40): `<router-ui>` lives at `core/provider.js`, not in the components index, and the main barrel did not side-effect-import it. `document.querySelector('router-ui')` returned `HTMLUnknownElement`, and the `routes` property assignment was a no-op on a plain DOM element. Symptomatic only when route content was expected — completely silent otherwise.
251
+
252
+ **Fixed**: At `@adia-ai/web-components@0.7.13+` the main barrel now side-effect-imports `core/provider.js` alongside `core/icons-phosphor.js`. The cold-start path "just works" with the standard barrel import.
253
+
254
+ **Remaining gotcha — subpath-import consumers**: Apps with bundle-size SLOs that skip the barrel (`import '@adia-ai/web-components/components/<x>'` per primitive) still need to register the provider explicitly:
255
+
256
+ ```ts
257
+ // Bundle-size-conscious cold-start — barrel skipped
258
+ import '@adia-ai/web-components/components/button';
259
+ import '@adia-ai/web-components/components/table';
260
+ // ...
261
+ import '@adia-ai/web-components/core/provider'; // <-- router lives here
262
+ ```
263
+
264
+ **How to detect**:
265
+ - Browser DevTools: `document.querySelector('router-ui').constructor.name === 'HTMLUnknownElement'` (should be `UIRouter`)
266
+ - Network panel: no request for the `content:` URLs after `router.routes = […]` assignment
267
+ - The route content area stays at its initial empty state across navigation
268
+
269
+ **Right fix**: Use the main barrel (default), OR add `import '@adia-ai/web-components/core/provider'` to your subpath-import cold-start.
270
+
271
+ **Sister ticket**: FB-72 (claims-ui-v15, 2026-05-25). Closed in v3.7.8 — substrate barrel patched + skill callout added to `patterns-recipes.md` "Admin shell anatomy" Route setup.
272
+
273
+ ---
274
+
275
+ ## §8 — `<table-toolbar-ui>` double chrome from hand-rolled children
276
+
277
+ **Pattern**: Consumer drops filter inputs (`<input-ui>`, `<select-ui>`) as **children** of `<table-toolbar-ui>`, expecting them to appear in the toolbar bar. They render — and so does the toolbar's auto-rendered Filter / Sort / Columns / Search row beneath them. Two competing chrome bands.
278
+
279
+ **Root cause**: `<table-toolbar-ui>` is a **chrome-owning primitive**, not a chrome container. Its default rendering shows search + filter + sort + columns affordances; each is opt-out-able via a `noXxx` boolean attribute. Slotted children render as additional content **above** the auto-chrome, producing the double-band layout. (Reference: `table-toolbar.yaml` props lines 32-81 + a2ui rule "Do NOT also use `<card-ui>`'s `<header>` on the same card — that produces a doubled chrome row.")
280
+
281
+ The mirror anti-pattern: replicating the same filter / search buttons in the parent `<card-ui>`'s `<header>` while `<table-toolbar-ui>` is also mounted. Same doubled-chrome class.
282
+
283
+ **Example**:
284
+
285
+ ```html
286
+ <!-- WRONG — double chrome from hand-rolled children -->
287
+ <table-toolbar-ui for="orders">
288
+ <input-ui placeholder="Search…" icon="magnifying-glass"></input-ui>
289
+ <select-ui placeholder="Status"></select-ui>
290
+ </table-toolbar-ui>
291
+
292
+ <!-- WRONG — chrome duplication in card header -->
293
+ <card-ui>
294
+ <header>
295
+ <h3 slot="heading">Orders</h3>
296
+ <span slot="action">
297
+ <button-ui icon="funnel" aria-label="Filter"></button-ui> <!-- redundant -->
298
+ <button-ui icon="magnifying-glass" aria-label="Search"></button-ui> <!-- redundant -->
299
+ </span>
300
+ </header>
301
+ <section bleed>
302
+ <table-toolbar-ui for="orders"></table-toolbar-ui>
303
+ <table-ui id="orders"></table-ui>
304
+ </section>
305
+ </card-ui>
306
+
307
+ <!-- RIGHT — let the toolbar own its chrome; use slot="actions" for primary buttons -->
308
+ <table-toolbar-ui for="orders" text="Recent orders">
309
+ <button-ui slot="actions" text="New" variant="primary" icon="plus"></button-ui>
310
+ </table-toolbar-ui>
311
+ ```
312
+
313
+ **How to detect**:
314
+ - Visual: two adjacent rows of toolbar-shaped chrome above the table
315
+ - DOM: `<table-toolbar-ui>` with any non-`[slot="actions"]` child element
316
+ - Code review: presence of filter / search affordances in a card's `<header>` while `<table-toolbar-ui>` is also mounted on the same card
317
+
318
+ **Right fix**: Drop hand-rolled toolbar children. Use the boolean opt-outs (`no-filter`, `no-sort`, `no-columns`, `no-search`) to suppress unwanted auto-chrome. Use `slot="actions"` for trailing primary buttons (Invite / Export / +New). Keep the toolbar as the sole chrome owner — no parallel chrome in card headers.
319
+
320
+ **Sister ticket**: FB-72 Finding 2 (claims-ui-v15, 2026-05-25). Closed in v3.7.8 — canonical no-children shape added to `patterns-data.md` "Data Table".
321
+
322
+ ---
323
+
324
+ ## §9 — `<table-toolbar-ui>` + `<table-ui>` inside one `<section bleed>` makes toolbar controls touch card edge
325
+
326
+ **Pattern**: Consumer wraps both `<table-toolbar-ui>` and `<table-ui>` in a single `<section bleed>` inside `<card-ui>`, because "the table needs bleed for edge-to-edge rows." The toolbar's Filter / Sort / Columns buttons + search input then sit flush against the card's leading edge — no inset, looks like a layout bug.
327
+
328
+ **Root cause**: `bleed` is a per-`<section>` knob, not a per-`<card-ui>` knob. When two children share a single bleed section, both get edge-to-edge treatment. The toolbar's chrome wants normal card-inset; the table wants edge-to-edge. They need separate sections.
329
+
330
+ Likewise, both inside one bare `<section>` (no bleed) puts card-padding on every table row — wasted horizontal real estate.
331
+
332
+ **Example**:
333
+
334
+ ```html
335
+ <!-- WRONG — toolbar buttons touch card edge -->
336
+ <card-ui>
337
+ <header><h3 slot="heading">Orders</h3></header>
338
+ <section bleed>
339
+ <table-toolbar-ui for="orders"></table-toolbar-ui>
340
+ <table-ui id="orders" sortable></table-ui>
341
+ </section>
342
+ </card-ui>
343
+
344
+ <!-- WRONG — table rows waste horizontal space -->
345
+ <card-ui>
346
+ <header><h3 slot="heading">Orders</h3></header>
347
+ <section>
348
+ <table-toolbar-ui for="orders"></table-toolbar-ui>
349
+ <table-ui id="orders" sortable></table-ui>
350
+ </section>
351
+ </card-ui>
352
+
353
+ <!-- RIGHT — two adjacent sections, per-element bleed -->
354
+ <card-ui>
355
+ <header><h3 slot="heading">Orders</h3></header>
356
+ <section>
357
+ <table-toolbar-ui for="orders"></table-toolbar-ui>
358
+ </section>
359
+ <section bleed>
360
+ <table-ui id="orders" sortable></table-ui>
361
+ </section>
362
+ </card-ui>
363
+
364
+ <!-- ALSO RIGHT — yaml-canonical: toolbar OUTSIDE card -->
365
+ <col-ui gap="3">
366
+ <table-toolbar-ui for="orders" text="Orders"></table-toolbar-ui>
367
+ <card-ui>
368
+ <section bleed>
369
+ <table-ui id="orders" sortable></table-ui>
370
+ </section>
371
+ </card-ui>
372
+ </col-ui>
373
+ ```
374
+
375
+ **How to detect**:
376
+ - Visual: toolbar's leftmost control (filter button or search input) leftmost-pixel aligned to card's leading border, zero gap
377
+ - DevTools probe: `getBoundingClientRect().left` for the first toolbar control = card's leading border position
378
+ - DOM scan: `<card-ui> <section[bleed]> table-toolbar-ui ~ table-ui` (both siblings, bleed parent)
379
+
380
+ **Right fix**: Either the two-section split (toolbar in normal `<section>`, table in `<section bleed>`) when keeping the toolbar inside the card, OR yaml-canonical: pair them as `<col-ui gap="3"><table-toolbar-ui></table-toolbar-ui><card-ui>...</card-ui></col-ui>` with the toolbar OUTSIDE the card. The yaml rule in `table-toolbar.yaml` a2ui.rules prefers the outside-card form.
381
+
382
+ **Sister ticket**: FB-73 (claims-ui-v15, 2026-05-25). Closed in v3.7.8 — both patterns documented in `patterns-data.md` "Data Table".
383
+
384
+ ---
385
+
386
+ ## §10 — `<table-ui>` columns rendered as flat text — cell-types not engaged
387
+
388
+ **Pattern**: Consumer pre-formats every value in the data layer (`amount: '$240.00'`, `status: 'Approved'`, `submitted: 'May 19, 2026'`, `tags: 'priority, paid'`) and uses bare `[{key, label}]` column descriptors. Result: status column is flat text (no badge / no color coding), amount column is left-aligned (no right-alignment for numeric columns), tags column is a comma-joined string with substring truncation (no chips).
389
+
390
+ **Root cause**: `<table-ui>` ships **12 built-in cell types** (`text` / `number` / `currency` / `percent` / `date` / `datetime` / `boolean` / `badge` / `avatar` / `link` / `progress` / `actions`) + a `render(value, row, cell, meta)` escape hatch for custom rendering. Each cell type owns:
391
+
392
+ - How to **format** the value (`Intl.NumberFormat` / `Intl.DateTimeFormat` / etc.)
393
+ - How to **render** into the cell (text-only, or `<badge-ui>` / `<avatar-ui>` / `<progress-ui>` / etc.)
394
+ - The default **alignment** (`text` → left, `number` / `currency` / `percent` → right, `boolean` / `actions` → center)
395
+ - The default **sort comparator** (alphanumeric / numeric / datetime / basic)
396
+
397
+ Pre-formatting in the data layer means the cell-type never engages — every column falls back to `type: 'text'`, left-aligned, alphanumeric sort, no rendering primitives. A consumer-set `align: 'end'` on the column descriptor has no effect without a matching `type:`.
398
+
399
+ **Example**:
400
+
401
+ ```js
402
+ // WRONG — pre-formatted strings, cell-types defeated
403
+ table.columns = [
404
+ { key: 'status', label: 'Status' },
405
+ { key: 'amount', label: 'Amount', align: 'end' }, // no effect — text type ignores align:end
406
+ { key: 'submitted', label: 'Submitted' },
407
+ ];
408
+ table.data = [{
409
+ status: 'Approved', // → flat text
410
+ amount: '$240.00', // → left-aligned text
411
+ submitted: 'May 19, 2026', // → flat text
412
+ }];
413
+
414
+ // RIGHT — raw values + cell-types
415
+ table.columns = [
416
+ { key: 'status', label: 'Status', type: 'badge',
417
+ meta: { variants: { approved: 'success', denied: 'danger' } } },
418
+ { key: 'amount', label: 'Amount', type: 'currency',
419
+ meta: { currency: 'USD' } },
420
+ { key: 'submitted', label: 'Submitted', type: 'date' },
421
+ ];
422
+ table.data = [{
423
+ status: 'approved', // → <badge-ui variant="success">
424
+ amount: 240.00, // → "$240.00" right-aligned
425
+ submitted: '2026-05-19T22:34:03Z', // → "May 19, 2026" via Intl.DateTimeFormat
426
+ }];
427
+ ```
428
+
429
+ For multi-element cells (tag chip rows, avatar stacks), use the
430
+ `render(value, row, cell, meta)` escape hatch — write DOM directly into
431
+ the cell via `cell.replaceChildren()` + `cell.appendChild(...)`.
432
+
433
+ **How to detect**:
434
+ - Visual: numeric columns aren't right-aligned; status columns are flat text with no color cues; date columns aren't formatted
435
+ - DevTools: `document.querySelector('table-ui').columns[N].type === undefined` (should be one of the 12 registered types)
436
+ - Code review: any data-layer string that contains `'$'` / `'%'` / a comma-joined chip list is a candidate for hoisting into a cell-type
437
+
438
+ **Right fix**: Pass raw values (numbers, ISO dates, enum strings, arrays) in `table.data` and configure `type:` / `render:` on every column descriptor. See `patterns-data.md` §Data Table for the full 12-type catalog + render-constraints (cell-element-write, memoize-per-row, `sortable: false` for non-scalar cells, `format()` text-export fallback).
439
+
440
+ **Sister ticket**: FB-74 (claims-ui-v15, 2026-05-25). Closed in v3.7.8 — `patterns-data.md` "Data Table" gained cell-types catalog table + worked example + custom-render section.
441
+
442
+ ---
443
+
444
+ ## §11 — Wrong attribute name on a primitive — silent no-op + layout-as-content
445
+
446
+ **Pattern**: Consumer authors a primitive with an attribute that "feels right" (matches the visible label, mirrors a sibling primitive's API, follows generic HTML conventions) but isn't the name the primitive's yaml documents. The browser accepts any attribute; the primitive's installProps only binds the names it declared. The intended behavior silently no-ops — no console error, no visual cue at the call site. Cluster of seven that recur across consumer-authored composites and gen-UI outputs.
447
+
448
+ **The seven**:
449
+
450
+ | Wrong | Right | Why it fails | yaml citation |
451
+ |---|---|---|---|
452
+ | `<accordion-item-ui label="…">` | `<accordion-item-ui text="…">` | Header label is the `text` prop (matches `<button-ui>` / `<nav-item-ui>` family convention). `label=` is silently ignored; the row renders with no visible header text — first child becomes the de-facto label. | `accordion-item.yaml` props.text |
453
+ | `<col-def field="amount" …>` | `<col-def key="amount" …>` | `<table-ui>` parser reads `el.getAttribute('key')` for the row-data accessor. `field=` is silently dropped; column renders as empty cells (header shows, rows blank). | `table.class.js:358-365` parseColDefs |
454
+ | `<nav-group-ui text="Settings">…children…</nav-group-ui>` | `<nav-group-ui text="Settings" open>…children…</nav-group-ui>` | Default `open: false` collapses the group; only the chevron + label render. Group children stay hidden until a click toggles it. Sidebar nav typically wants groups expanded by default. | `nav-group.yaml` props.open default false |
455
+ | `<chat-input-ui></chat-input-ui><button-ui icon="paper-plane-right" variant="primary">Send</button-ui>` | `<chat-input-ui></chat-input-ui>` (alone) | `<chat-input-ui>` stamps its OWN send button (`paper-plane-right`, `variant="primary"`). Adding a sibling Button produces two side-by-side send buttons. | `chat-input.yaml` description + a2ui.rules + anti_patterns[0] |
456
+ | `<card-ui><header>Snippet</header><section><code-ui language="js">…</code-ui></section><footer><button-ui icon="copy">Copy</button-ui></footer></card-ui>` | `<card-ui><header><h3>Snippet</h3></header><section bleed><code-ui language="js">…</code-ui></section></card-ui>` | `<code-ui>` renders its own header chrome (language label + copy button) UNLESS `[bare]` is set. A wrapper card with explicit Copy footer duplicates that chrome. The copy button is built in. | `code.yaml` description + props.bare |
457
+ | `<progress-row-ui label="Storage" value="0.42" meta="42 GB / 100 GB">` | `<progress-row-ui label="Storage" value="42" meta="42 GB / 100 GB">` | `value` is the 0–100 percent (not a 0.0–1.0 fraction). 0.42 renders as a 0.42% fill (essentially empty bar). Mirrors `<progress-ui>` `value` semantics — yaml documents "0-100; null = indeterminate". | `progress-row.yaml` props.value range |
458
+ | `<integration-card-ui provider="slack" name="Slack"><button-ui slot="action" text="Connect"></button-ui></integration-card-ui>` | `<integration-card-ui provider="slack" name="Slack" status="available"></integration-card-ui>` | The action button is derived from `[status]` — `available → Connect (primary)`, `connected → Configure (outline)`, `error → Retry`, `pending → spinner`, `coming-soon → hidden`. Slotting a Button produces TWO buttons side-by-side. The `actions` slot is for an overflow `<menu-ui>` only (Reauthenticate / Disconnect entries). | `integration-card.yaml` props.status enum + a2ui.rules + anti_patterns |
459
+
460
+ **How to detect**:
461
+ - Visual: row / column / group / button renders empty, wrong-shaped, or duplicated relative to the spec
462
+ - DOM: `document.querySelector('accordion-item-ui').text` is `undefined` instead of the intended string (silent prop-bind miss)
463
+ - Component Literacy gate: every primitive's `embed_gotchas` field should capture these — when a primitive in your composition appears in the table above and you didn't read its yaml, you skipped the gate
464
+ - Code review: any attribute that looks like it "should match the visible string" but isn't `text` / `key` / `label` is a candidate; verify against `<name>.yaml` props block
465
+
466
+ **Right fix**: Read the primitive's `<name>.yaml` `props:` block before authoring. The seven above all stem from "the obvious attribute name isn't the one the primitive declared." `component-literacy.md`'s `default_render` + `slot_sizing_rules` + `exposed_tokens` fields are unfabricable precisely to catch this class — you can't fill them in without reading the file.
467
+
468
+ **Sister source**: chat-ui `adia-ui-gen-review` cycle 17 (2026-05-26) — gallery review of 59 pre-hydrated prompts surfaced these seven as the recurring "wrong attribute = silent no-op" cluster across multiple chunks (accordion-faq, api-keys-table, sidebar-nav, chat-interface, code-snippet, billing-overview, integrations-grid). All seven are visible at canvas time, none at HTML-author time.
469
+
470
+ ---
471
+
472
+ ## §12 — Same attribute name, different enum across components (`drawer` `side` ≠ `pane` `side`)
473
+
474
+ **Pattern**: Consumer authors `<drawer-ui side="trailing">` (or `side="leading"`), carrying over the `side` vocabulary from `<pane-ui>`. The drawer "opens" (`showModal()` fires) but the panel never appears — it renders at `opacity:0`, collapsed in the top-left corner. Looks like the drawer is broken.
475
+
476
+ **Root cause**: two components share the attribute NAME `side` but use DIFFERENT enums. `<drawer-ui>` takes **physical** edges — `left | right | top | bottom` (default `right`). `<pane-ui>` takes **logical** sibling-edges — `leading | trailing`. `trailing`/`leading` aren't in drawer's enum, so no `[side="…"]` rule matches and the panel keeps its pre-open (hidden, off-canvas, `opacity:0`) state. The browser accepts any attribute value; only the matching CSS rule is missing, so there's no error and no console warning.
477
+
478
+ **Fix**: use the component's own enum — a right-anchored drawer is `side="right"`, not `side="trailing"`.
479
+
480
+ ```html
481
+ <!-- WRONG — pane vocabulary on a drawer → invisible panel -->
482
+ <drawer-ui side="trailing" size="md">…</drawer-ui>
483
+ <!-- RIGHT — drawer takes physical edges -->
484
+ <drawer-ui side="right" size="md">…</drawer-ui>
485
+ ```
486
+
487
+ **Recognition**: an overlay/drawer that "opens but shows nothing." Check the `side` value against the component's *own* yaml enum — sibling primitives that share an attribute name don't necessarily share its vocabulary. Inverse trap: `<pane-ui side="right">` (pane wants `leading`/`trailing`). Caught as bug-50 (v0.7.0): a demo used `side="trailing"` on a `<drawer-ui>`.
488
+
489
+ ---
490
+
491
+ ## §Meta-pattern across all twelve
492
+
493
+ **Composites and primitives have layered contracts. Consumer code shouldn't reach into a primitive's layout territory; primitive code shouldn't fight its consumer's responsive constraints. The audit catches structural shape, not visual proportion; visual review catches the rest until proportional audits land.**
494
+
495
+ These gotchas all share a structural cause: **defaults aren't contracts.** The primitive's default display, default size, default columns are starting points — when consumers override them, the override beats the default by virtue of specificity, NOT by correctness. Reading the primitive's `.css` end-to-end (`component-literacy.md` pre-flight gate) catches what `setAttribute()` calls reveal at consumer-write time.
496
+
497
+ ---
498
+
499
+ ## §When to apply
500
+
501
+ - **Before Phase 4 Author**: review your sketch/plan for all four patterns
502
+ - **When debugging "audit passes but looks broken"**: walk this list first; one of these four covers most cases
503
+ - **Visual review checklist**: open the rendered surface; specifically look at empty-states, toolbars, KPI grids, and embedded composites
504
+
505
+ ## §What the substrate-side audit currently catches
506
+
507
+ | Gotcha | Substrate audit gate |
508
+ |---|---|
509
+ | §1 Composition grammar | `audit:card-structure` (HTML + JS scan — see chat-ui commit `2778367e4`); similar for drawer-ui exists; avatar/alert/aside-ui forward work |
510
+ | §2 Parent overrides child display | None; visual review only |
511
+ | §3 Mixed control sizes | None; visual review only |
512
+ | §4 `minmax(min, 1fr)` vs `@container` | None; visual review only |
513
+ | §5 admin-page-{header,body} inner-column-owner missing | `audit:admin-page-structure` / `check:admin-page-structure` (chat-ui infra; runs against any HTML the consumer authors). Scans for `<admin-page-{header,body}>` whose direct child is NOT `:is(header, header-ui, section, section-ui, iframe, editor-shell, pane-ui)`. Wired into the master `check` cascade. |
514
+ | §6 bare-slot brand chrome instead of `<admin-entity-item>` | `audit:brand-chrome` / `check:brand-chrome` (chat-ui infra; runs against any HTML the consumer authors). Scans for `<admin-topbar slot="header">` inside `<admin-sidebar slot="leading">` whose direct child carries `slot="icon"` or `slot="heading"` AND isn't `<admin-entity-item>`. Trailing-sidebar panel headers correctly excluded. Wired into the master `check` cascade. |
515
+ | §7 router-ui silently renders empty when provider isn't registered | None yet (substrate fix at @adia-ai/web-components@0.7.13 retires the cold-start case — barrel side-effect-imports `core/provider.js`). Subpath-import consumers still vulnerable; forward work: add a console.warn at `<router-ui>` connect if `customElements.get('router-ui')` is HTMLUnknownElement (heuristic). |
516
+ | §8 table-toolbar-ui double chrome from hand-rolled children | None yet; forward work: lint-style audit that flags any `<table-toolbar-ui>` with non-`[slot="actions"]` children. Visual symptom obvious during normal QA. |
517
+ | §9 table-toolbar-ui + table-ui in same `<section bleed>` | None yet; forward work: HTML scan for `<card-ui> <section[bleed]> :is(table-toolbar-ui) ~ :is(table-ui)` (siblings inside bleed). |
518
+ | §10 table-ui columns rendered as flat text — cell-types not engaged | None yet (data-layer state, not HTML state — audits don't see data binding). Forward work: dev-only runtime warning in `table-ui` if a column descriptor has `key` matching `amount` / `total` / `price` / `*_at` / `status` etc. heuristic names without a `type:` field. |
519
+ | §11 Wrong attribute name — silent no-op | None mechanizable from HTML alone (any attribute is legal HTML). Component Literacy gate (`component-literacy.md`'s `embed_gotchas` field) is the upstream defense; cycle-style visual review catches the rest. Forward work per-primitive: dev-only `console.warn('<accordion-item-ui>: [label] is not a known prop; did you mean [text]?')` heuristic in `connected()`. |
520
+ | §12 Cross-component enum confusion (drawer `side` vs pane `side`) | None from HTML alone (any value is legal HTML); the value is only wrong relative to the component's yaml enum. Component Literacy gate + visual review ("opens but shows nothing") catch it. Forward work per-primitive: dev-only `console.warn` in `connected()` when `[side]` isn't in the declared enum. |
521
+
522
+ Forward work on substrate audit coverage is tracked in `.brain/handoffs/2026-05-25-session-handoff.md` (next-20 items #12-14, #15) + the five §7-11 forward-work entries above.