@adia-ai/adia-ui-forge 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/.claude-plugin/plugin.json +23 -0
  2. package/CHANGELOG.md +26 -0
  3. package/README.md +54 -0
  4. package/bin/forge-lint +263 -0
  5. package/bin/lib/audit-axes.mjs +555 -0
  6. package/bin/lib/dry-run-irreversible.mjs +236 -0
  7. package/bin/lib/run-skill-evals.mjs +487 -0
  8. package/bin/lib/teach-router.mjs +250 -0
  9. package/commands/adia-forge-a2ui.md +10 -0
  10. package/commands/adia-forge-author.md +10 -0
  11. package/commands/adia-forge-dogfood.md +8 -0
  12. package/commands/adia-forge-llm.md +8 -0
  13. package/commands/adia-forge-orient.md +10 -0
  14. package/commands/adia-forge-release.md +8 -0
  15. package/commands/adia-forge-review.md +10 -0
  16. package/hooks/hooks.json +15 -0
  17. package/package.json +41 -0
  18. package/references/shared/content-trust.md +76 -0
  19. package/references/shared/pev-rationale.md +64 -0
  20. package/references/shared/skill-conventions.md +133 -0
  21. package/skills/adia-ui-a2ui/CHANGELOG.md +32 -0
  22. package/skills/adia-ui-a2ui/SKILL.md +243 -0
  23. package/skills/adia-ui-a2ui/evals/adversarial-corpus.json +75 -0
  24. package/skills/adia-ui-a2ui/evals/routing-corpus.json +31 -0
  25. package/skills/adia-ui-a2ui/evals/teach-routing-cases.json +100 -0
  26. package/skills/adia-ui-a2ui/references/anti-patterns.md +24 -0
  27. package/skills/adia-ui-a2ui/references/chunk-authoring.md +88 -0
  28. package/skills/adia-ui-a2ui/references/corpus-discipline.md +56 -0
  29. package/skills/adia-ui-a2ui/references/eval-diagnostics.md +127 -0
  30. package/skills/adia-ui-a2ui/references/fragment-graph.md +91 -0
  31. package/skills/adia-ui-a2ui/references/mcp-pipeline-ops.md +106 -0
  32. package/skills/adia-ui-a2ui/references/mcp-tool-reference.md +398 -0
  33. package/skills/adia-ui-a2ui/references/pipeline-overview.md +175 -0
  34. package/skills/adia-ui-a2ui/references/semantic-fail-lifting.md +120 -0
  35. package/skills/adia-ui-a2ui/references/strategy-engines.md +111 -0
  36. package/skills/adia-ui-a2ui/references/teach-protocol.md +220 -0
  37. package/skills/adia-ui-a2ui/references/zettel-calibration.md +93 -0
  38. package/skills/adia-ui-a2ui/scripts/audit-a2ui-roster.mjs +96 -0
  39. package/skills/adia-ui-a2ui/scripts/teach-route.mjs +157 -0
  40. package/skills/adia-ui-a2ui/skill.json +38 -0
  41. package/skills/adia-ui-authoring/CHANGELOG.md +32 -0
  42. package/skills/adia-ui-authoring/SKILL.md +256 -0
  43. package/skills/adia-ui-authoring/assets/case-studies/admin-shell-decomposition.md +101 -0
  44. package/skills/adia-ui-authoring/assets/case-studies/maxtokens-32768-discovery.md +109 -0
  45. package/skills/adia-ui-authoring/assets/case-studies/theme-panel-promotion.md +113 -0
  46. package/skills/adia-ui-authoring/evals/adversarial-design-plan-gates.json +138 -0
  47. package/skills/adia-ui-authoring/evals/routing-corpus.json +245 -0
  48. package/skills/adia-ui-authoring/references/anti-patterns.md +606 -0
  49. package/skills/adia-ui-authoring/references/api-contract.md +205 -0
  50. package/skills/adia-ui-authoring/references/authoring-cycle.md +211 -0
  51. package/skills/adia-ui-authoring/references/canonical-pattern-index.md +179 -0
  52. package/skills/adia-ui-authoring/references/code-style.md +329 -0
  53. package/skills/adia-ui-authoring/references/common-gotchas.md +93 -0
  54. package/skills/adia-ui-authoring/references/composite-demo-protocol.md +1084 -0
  55. package/skills/adia-ui-authoring/references/css-patterns.md +364 -0
  56. package/skills/adia-ui-authoring/references/lifecycle-patterns.md +302 -0
  57. package/skills/adia-ui-authoring/references/llm-bridge.md +254 -0
  58. package/skills/adia-ui-authoring/references/module-promotion.md +289 -0
  59. package/skills/adia-ui-authoring/references/primitive-audit.md +123 -0
  60. package/skills/adia-ui-authoring/references/shell-patterns.md +561 -0
  61. package/skills/adia-ui-authoring/references/teach-protocol.md +428 -0
  62. package/skills/adia-ui-authoring/references/token-contract.md +120 -0
  63. package/skills/adia-ui-authoring/references/worked-example.md +351 -0
  64. package/skills/adia-ui-authoring/references/yaml-contract.md +224 -0
  65. package/skills/adia-ui-authoring/scripts/audit-authoring-roster.mjs +148 -0
  66. package/skills/adia-ui-authoring/scripts/build-canonical-pattern-index.mjs +199 -0
  67. package/skills/adia-ui-authoring/skill.json +45 -0
  68. package/skills/adia-ui-dogfood/CHANGELOG.md +17 -0
  69. package/skills/adia-ui-dogfood/README.md +62 -0
  70. package/skills/adia-ui-dogfood/SKILL.md +866 -0
  71. package/skills/adia-ui-dogfood/scripts/analyze.mjs +603 -0
  72. package/skills/adia-ui-dogfood/skill.json +40 -0
  73. package/skills/adia-ui-forge/SKILL.md +88 -0
  74. package/skills/adia-ui-forge/evals/routing-corpus.json +30 -0
  75. package/skills/adia-ui-gen-review/CHANGELOG.md +108 -0
  76. package/skills/adia-ui-gen-review/SKILL.md +266 -0
  77. package/skills/adia-ui-gen-review/references/loop-protocol.md +712 -0
  78. package/skills/adia-ui-gen-review/references/rubric-cosmetic.md +144 -0
  79. package/skills/adia-ui-gen-review/references/rubric-decompose.md +117 -0
  80. package/skills/adia-ui-gen-review/references/rubric-score.md +249 -0
  81. package/skills/adia-ui-gen-review/references/scores.schema.json +125 -0
  82. package/skills/adia-ui-gen-review/references/teach-protocol.md +214 -0
  83. package/skills/adia-ui-gen-review/scripts/gen-review-coverage-audit.mjs +127 -0
  84. package/skills/adia-ui-gen-review/scripts/gen-review-decompose.mjs +569 -0
  85. package/skills/adia-ui-gen-review/scripts/gen-review-status.mjs +176 -0
  86. package/skills/adia-ui-gen-review/scripts/validate-cycle-scores.mjs +198 -0
  87. package/skills/adia-ui-gen-review/skill.json +22 -0
  88. package/skills/adia-ui-llm/CHANGELOG.md +25 -0
  89. package/skills/adia-ui-llm/SKILL.md +165 -0
  90. package/skills/adia-ui-llm/evals/adversarial-corpus.json +75 -0
  91. package/skills/adia-ui-llm/evals/routing-corpus.json +30 -0
  92. package/skills/adia-ui-llm/evals/teach-routing-cases.json +73 -0
  93. package/skills/adia-ui-llm/references/adapter-contract.md +99 -0
  94. package/skills/adia-ui-llm/references/add-a-provider.md +90 -0
  95. package/skills/adia-ui-llm/references/bridge-facade.md +94 -0
  96. package/skills/adia-ui-llm/references/browser-proxy-boundary.md +89 -0
  97. package/skills/adia-ui-llm/references/model-registry.md +75 -0
  98. package/skills/adia-ui-llm/references/streaming-sse.md +95 -0
  99. package/skills/adia-ui-llm/references/teach-protocol.md +78 -0
  100. package/skills/adia-ui-llm/scripts/audit-llm-roster.mjs +93 -0
  101. package/skills/adia-ui-llm/scripts/teach-route.mjs +119 -0
  102. package/skills/adia-ui-llm/skill.json +33 -0
  103. package/skills/adia-ui-release/CHANGELOG.md +23 -0
  104. package/skills/adia-ui-release/SKILL.md +295 -0
  105. package/skills/adia-ui-release/assets/case-studies/2026-05-20-batch-push-v0.6.14-v0.6.15.md +144 -0
  106. package/skills/adia-ui-release/assets/case-studies/2026-05-20-corpus-drift-remediation-v0.6.15.md +155 -0
  107. package/skills/adia-ui-release/assets/case-studies/2026-05-20-version-skip-correction-v0.6.12.md +114 -0
  108. package/skills/adia-ui-release/assets/case-studies/2026-05-21-author-from-scratch-v0.6.18.md +139 -0
  109. package/skills/adia-ui-release/assets/case-studies/2026-05-21-feedback37-retraction-v0.6.21.md +124 -0
  110. package/skills/adia-ui-release/assets/case-studies/2026-05-21-fn1-enrichment-pass-v0.6.19.md +125 -0
  111. package/skills/adia-ui-release/assets/case-studies/2026-05-21-stale-test-detection-v0.6.20.md +142 -0
  112. package/skills/adia-ui-release/assets/case-studies/2026-05-23-freshness-gate-recovery-v0.6.32.md +97 -0
  113. package/skills/adia-ui-release/assets/case-studies/2026-05-26-catalog-drift-recurring-v0.6.40.md +147 -0
  114. package/skills/adia-ui-release/assets/templates/stub-changelog.template.md +22 -0
  115. package/skills/adia-ui-release/evals/evals.json +164 -0
  116. package/skills/adia-ui-release/references/changelog-discipline.md +250 -0
  117. package/skills/adia-ui-release/references/cycle-happy-path.md +520 -0
  118. package/skills/adia-ui-release/references/exe-deploy.md +149 -0
  119. package/skills/adia-ui-release/references/gates-catalog.md +778 -0
  120. package/skills/adia-ui-release/references/ledger-discipline.md +232 -0
  121. package/skills/adia-ui-release/references/migration-guide-authoring.md +174 -0
  122. package/skills/adia-ui-release/references/multi-agent-baseline.md +207 -0
  123. package/skills/adia-ui-release/references/notes-authoring.md +212 -0
  124. package/skills/adia-ui-release/references/recovery-paths.md +215 -0
  125. package/skills/adia-ui-release/references/rollup-notes.md +208 -0
  126. package/skills/adia-ui-release/references/teach-protocol.md +468 -0
  127. package/skills/adia-ui-release/scripts/assert-monorepo-root.mjs +49 -0
  128. package/skills/adia-ui-release/scripts/audit-gate-roster.mjs +196 -0
  129. package/skills/adia-ui-release/scripts/bump.mjs +118 -0
  130. package/skills/adia-ui-release/scripts/dispatch-publish.mjs +151 -0
  131. package/skills/adia-ui-release/scripts/insert-stub.mjs +127 -0
  132. package/skills/adia-ui-release/scripts/make-ledger.mjs +179 -0
  133. package/skills/adia-ui-release/scripts/promote-unreleased.mjs +88 -0
  134. package/skills/adia-ui-release/scripts/release-pack.mjs +350 -0
  135. package/skills/adia-ui-release/scripts/tag-lockstep.mjs +110 -0
  136. package/skills/adia-ui-release/skill.json +75 -0
@@ -0,0 +1,561 @@
1
+ # Authoring shell components — Mode 3
2
+
3
+ Shell components are a distinct authoring shape from single-element primitives. They wrap an entire product surface — admin app, chat streaming surface, code/canvas editor, docs page, simple splash — and exist almost entirely to wire JS behaviors that CSS can't express on top of author-provided DOM.
4
+
5
+ The **canonical authoring shape is bespoke cluster-namespaced children with state-as-attribute semantics**, per ADR-0023 (bespoke shell-tier children). Every shell-specific concern earns its own custom element with a documented attribute API; state lives as reflected attributes; parent shells coordinate via `querySelector` + slot routing without centralizing child behavior.
6
+
7
+ Legacy `data-*` shapes (`[data-canvas]`, `<aside-ui slot="leading">`, `<dialog data-command>`) were retired per ADR-0024 (legacy shell shapes retired). A brief pedagogical summary is at §Legacy `data-*` shape (retired) below; new code should follow the bespoke vocabulary.
8
+
9
+ Absorbed from the legacy `bespoke-shell-children` skill (now a redirect; this file is the deep mode-3 reference).
10
+
11
+ ---
12
+
13
+ ## When to use mode 3
14
+
15
+ You're authoring a shell when:
16
+
17
+ - The component holds no visual content of its own — the page author provides everything inside it.
18
+ - The component's value is in _behavior_ — keyboard shortcuts, resize handles, ResizeObserver-driven breakpoints, localStorage persistence.
19
+ - More than one component instance on a page would feel wrong (one shell per surface).
20
+ - You're decomposing a new module-tier shell (chat / editor / simple / docs / future).
21
+ - You're adding a new bespoke child to an existing cluster (`<admin-*>`, `<chat-*>`, `<editor-*>`, `<simple-*>`).
22
+ - You're reviewing whether shell behavior should be promoted to a bespoke child vs. stay in the host.
23
+
24
+ You are NOT authoring a shell when:
25
+
26
+ - The component injects its own structure (header, body, footer slots filled with default markup) — that's a regular composite. Use the `card-ui` pattern (mode 1/2).
27
+ - The component renders a single semantic element with cosmetic variants — that's a leaf primitive. Use the button-ui / badge pattern (mode 1).
28
+ - You're extracting a cross-cluster reusable element (theme panel, command palette shared across shells) — that's mode 4 (module promotion); see [module-promotion.md](module-promotion.md).
29
+
30
+ ## Architectural principles (read first)
31
+
32
+ 1. **Web components ARE the abstraction.** No `data-*` proxy attributes when a custom element can own the concern.
33
+ 2. **State-as-attribute.** Every consumer-queryable state is a **reflected attribute** on the relevant element. CSS `:has(<el>[state])`, JS `.hasAttribute('state')`. No threshold-math state inference.
34
+ 3. **Cluster namespace.** Bespoke children use `<cluster-thing>` (no `-ui` suffix) per ADR-0015. `<admin-*>` for admin shell, `<chat-*>` for chat shell, `<editor-*>` for editor shell, `<simple-*>` for simple shell.
35
+ 4. **Each child owns one concern.** If a child accumulates 3+ unrelated behaviors, split it.
36
+ 5. **Backwards-compat window during introduction, then deprecate.** When introducing a NEW cluster's bespoke family, ship 1–2 patch releases where the host reads BOTH legacy and bespoke shapes via `:is()` selectors (so consumers can migrate). Then deprecate the legacy shape in the next MINOR cut — strip the host's legacy reads, drop legacy CSS lifts, document in an ADR. The admin/chat/editor families completed this cycle (ADR-0024) — legacy retired after a ~9-day compat window. Future cluster expansions should follow: introduce → migrate consumers → smoke-probe → deprecate.
37
+
38
+ ## The 4-concern decomposition heuristic
39
+
40
+ When decomposing a shell, identify behavior in this order:
41
+
42
+ | Concern | Owner | Becomes |
43
+ | --- | --- | --- |
44
+ | 1. **Side-region behavior** (resize, collapse, persist, narrow-mode RO) | one child per side region | `<X-sidebar>` (mirrors `<admin-sidebar>`) |
45
+ | 2. **Keyboard-driven overlay** (palette, menu, search) | one child per overlay | `<X-command>` or `<X-search>` (mirrors `<admin-command>`) |
46
+ | 3. **Cluster-specific orchestration** (LLM streaming, canvas state, etc.) | the host OR a dedicated coordinator | stays in `<X-shell>` if cluster-tied; promote to coordinator if reusable |
47
+ | 4. **Structural composition** (chrome bars, scroll surfaces, page bands) | CSS-only stubs | one stub per region (mirrors `<admin-content>` / `<admin-topbar>` / etc.) |
48
+
49
+ **Default split for a 5-concern shell** — 2-3 JS-bearing + 5-7 CSS-only stubs = 7-10 bespoke children.
50
+
51
+ ## File scaffold per child
52
+
53
+ ### JS-bearing child (~150–250 LOC)
54
+
55
+ ```text
56
+ packages/web-modules/<cluster>/<cluster>-<role>/
57
+ <cluster>-<role>.js # behavior + reflected attributes
58
+ <cluster>-<role>.yaml # SoT — props/events/slots/states/keywords
59
+ <cluster>-<role>.a2ui.json # GENERATED via npm run build:components
60
+ <cluster>-<role>.html # demo shell that fetches .examples.html
61
+ <cluster>-<role>.examples.html # living examples + behavior wiring tables
62
+ <cluster>-<role>.test.js # unit tests (8-10 minimum)
63
+ ```
64
+
65
+ ### CSS-only structural stub
66
+
67
+ ```text
68
+ packages/web-modules/<cluster>/<cluster>-<role>/
69
+ <cluster>-<role>.yaml # SoT — slots only, no props/events
70
+ <cluster>-<role>.a2ui.json # GENERATED
71
+ <cluster>-<role>.html # demo shell
72
+ <cluster>-<role>.examples.html # composition snippet + slot vocabulary
73
+ ```
74
+
75
+ NO `.js`, NO own `.css` for CSS-only stubs. They render as unknown `HTMLElement`; styling comes from the parent shell's CSS via tag-presence selectors.
76
+
77
+ ## State-as-attribute conventions
78
+
79
+ | Pattern | When to use |
80
+ | --- | --- |
81
+ | **Reflected boolean** `[collapsed]` `[open]` `[streaming]` `[disabled]` `[resizing]` | The state is a binary the consumer might style on |
82
+ | **Reflected enum** `[status="connected\|connecting\|error"]` | The state has 2-5 distinct values |
83
+ | **Reflected string** `[name="..."]` `[shortcut="cmd+k\|ctrl+k\|both"]` | Author-supplied configuration that other code reads |
84
+ | **Property only (no reflect)** | Internal state, perf-sensitive, or values too varied to enumerate |
85
+
86
+ **Multi-word camelCase props** map to kebab-case attributes via `attribute: '...'` (NOT `attr:` — see Pitfall #1):
87
+
88
+ ```js
89
+ static properties = {
90
+ noShortcut: { type: Boolean, default: false, reflect: true, attribute: 'no-shortcut' },
91
+ minWidth: { type: String, default: '', reflect: true, attribute: 'min-width' },
92
+ };
93
+ ```
94
+
95
+ ## CSS bridge pattern
96
+
97
+ Each cluster's bespoke children get styled via a single bridge file:
98
+
99
+ ```text
100
+ packages/web-modules/<cluster>/<cluster>-shell/css/<cluster>-shell.bespoke.css
101
+ ```
102
+
103
+ Imported **last** in `<cluster>-shell.css` so its rules layer over the legacy CSS without modification. Don't expand `:is(legacy, slot-ui, ...)` selectors throughout the legacy CSS — that's invasive and makes Phase 3 (legacy removal) harder.
104
+
105
+ ## Step-by-step procedure
106
+
107
+ ### 1. Audit existing shell
108
+
109
+ Read the host's `.js` file. Identify the 4 concerns + which are already in primitive sub-elements (e.g., `<pane-ui resizable>` already owns pane resize — don't duplicate).
110
+
111
+ ```bash
112
+ wc -l packages/web-modules/<cluster>/<cluster>-shell/<cluster>-shell.js
113
+ grep -nE 'connected|setup|wire|#' packages/web-modules/<cluster>/<cluster>-shell/<cluster>-shell.js
114
+ ```
115
+
116
+ Document the decomposition before writing code. A simple mapping table from legacy author markup to bespoke vocabulary helps.
117
+
118
+ ### 2. Author yamls first
119
+
120
+ For each bespoke child, write the yaml before the JS. The yaml is the SoT. This forces you to declare the contract before implementing.
121
+
122
+ ### 3. Author the JS (JS-bearing children only)
123
+
124
+ ```js
125
+ import { UIElement } from '../../../web-components/core/element.js';
126
+
127
+ class ChildName extends UIElement {
128
+ static properties = {
129
+ state: { type: Boolean, default: false, reflect: true },
130
+ config: { type: String, default: '', reflect: true },
131
+ camelProp: { type: String, default: '', reflect: true, attribute: 'kebab-prop' },
132
+ };
133
+
134
+ static template = () => null; // stamp nothing — author owns DOM
135
+
136
+ #cleanups = [];
137
+
138
+ connected() {
139
+ this.#setupBehavior1();
140
+ this.#setupBehavior2();
141
+ }
142
+
143
+ disconnected() {
144
+ for (const c of this.#cleanups) c();
145
+ this.#cleanups = [];
146
+ }
147
+
148
+ toggle() { /* ... */ return this.state; }
149
+
150
+ #setupBehavior1() { /* ... */ }
151
+ }
152
+
153
+ customElements.define('cluster-role', ChildName);
154
+ export { ChildName };
155
+ ```
156
+
157
+ **The "cleanup-closure pile" pattern** (`#cleanups: [() => …, () => …]`) scales to N drag handles — each handle's setup pushes a closure that removes its specific listeners and undoes any body-level pointer-events mutation. `disconnected()` just drains the pile.
158
+
159
+ The path to `core/element.js` from `packages/web-modules/<cluster>/<cluster>-<role>/` is **`../../../web-components/core/element.js`** (3 levels up).
160
+
161
+ ### 4. Author tests
162
+
163
+ Minimum 8-10 tests per JS-bearing child covering: registration, default reflected values, property-attribute round-trip, public API return values, event dispatch, disconnect cleanup, behavior under config attributes, persistence (if applicable).
164
+
165
+ **Critical test-setup pitfalls**:
166
+
167
+ ```js
168
+ // happy-dom returns 0 from getBoundingClientRect — patch in beforeEach
169
+ HTMLElement.prototype.getBoundingClientRect = function () {
170
+ const w = parseFloat(this.style?.width) || 240;
171
+ return { width: w, height: 600, top: 0, left: 0, right: w, bottom: 600, x: 0, y: 0 };
172
+ };
173
+
174
+ // happy-dom doesn't ship ResizeObserver — stub it
175
+ globalThis.ResizeObserver = class {
176
+ observe() {} unobserve() {} disconnect() {}
177
+ };
178
+
179
+ // happy-dom's <dialog> is partial — polyfill showModal / close / open getter
180
+ function patchDialogPolyfill(dialog) {
181
+ let isOpen = false;
182
+ Object.defineProperty(dialog, 'open', {
183
+ get: () => isOpen, set: (v) => { isOpen = !!v; }, configurable: true,
184
+ });
185
+ dialog.showModal = function () { isOpen = true; };
186
+ dialog.close = function () { isOpen = false; this.dispatchEvent(new Event('close')); };
187
+ }
188
+ ```
189
+
190
+ ### 5. Author CSS bridge
191
+
192
+ For each bespoke tag, declare structural CSS in `<cluster>-shell.bespoke.css`. Reuse tokens from the existing `<cluster>-shell.tokens.css`. Don't introduce new tokens unless the structural concern is genuinely new.
193
+
194
+ Shells follow the standard two-block `@scope` + L3 token + zero-raw-color rules from [css-patterns.md](css-patterns.md) and [token-contract.md](token-contract.md) — the shell tag stem prefixes its own tokens (`--admin-shell-sidebar-w`, `--chat-shell-header-h`).
195
+
196
+ ### 6. Refactor the host to coordinate
197
+
198
+ The host loses behavior it used to centralize. It now reflects host-level config like `[mode]`, wires cross-sibling concerns, and forwards events for navigation routing.
199
+
200
+ **Once the cluster reaches Phase 3** (legacy deprecated, ADR-0024 pattern), the host queries ONLY the bespoke children:
201
+
202
+ ```js
203
+ // Bespoke-only (post-Phase-3 — current state for admin/chat/editor):
204
+ #findSidebar(name) {
205
+ return this.querySelector(`<cluster>-sidebar[slot="${name}"], <cluster>-sidebar[name="${name}"]`);
206
+ }
207
+
208
+ btn.addEventListener('click', () => {
209
+ const sidebar = this.#findSidebar(name);
210
+ sidebar?.toggle?.(); // direct .toggle() call on the bespoke child
211
+ });
212
+ ```
213
+
214
+ **During the introduction window** (1–2 patch releases before deprecation), the host reads both shapes via `:is()` selectors with a typeof-check + legacyToggle fallback:
215
+
216
+ ```js
217
+ // Compat window (Phase 1 + Phase 2 — pre-deprecation):
218
+ static SIDEBAR_SEL =
219
+ ':is(<cluster>-sidebar[slot="leading"], <cluster>-sidebar[slot="trailing"], ' +
220
+ '[data-<cluster>-sidebar], aside-ui[slot="leading"], aside-ui[slot="trailing"])';
221
+
222
+ btn.addEventListener('click', () => {
223
+ const sidebar = this.querySelector(`:is(<cluster>-sidebar[slot="${name}"], [data-<cluster>-sidebar="${name}"])`);
224
+ if (typeof sidebar.toggle === 'function') sidebar.toggle();
225
+ else this.legacyToggle(sidebar, name);
226
+ });
227
+ ```
228
+
229
+ **LOC pattern**: the host grows during compat (admin-shell hit ~305 LOC during introduction), then shrinks dramatically at deprecation (admin-shell dropped to ~87 LOC, ≈ −71%). The shrinkage is the architectural signal that the abstraction was correct.
230
+
231
+ ### 7. Update the cluster's `index.js`
232
+
233
+ ```js
234
+ export { ClusterShell } from './cluster-shell/cluster-shell.js';
235
+ export { ClusterSidebar } from './cluster-sidebar/cluster-sidebar.js';
236
+ export { ClusterCommand } from './cluster-command/cluster-command.js';
237
+ // CSS-only stubs don't need exports — they don't register customElements
238
+ ```
239
+
240
+ ### 8. Run the build + verify gates
241
+
242
+ ```bash
243
+ node scripts/build/components.mjs # picks up new yamls, generates .a2ui.json
244
+ node scripts/build/components.mjs --verify # must say "clean — N files up-to-date"
245
+ npm run check:lockstep
246
+ npm run verify:traits
247
+ npm run smoke:engines
248
+ npm run smoke:register-engine
249
+ npm run test:a2ui
250
+ npx vitest run packages/web-modules/<cluster>/<cluster>-<role>/<cluster>-<role>.test.js
251
+ ```
252
+
253
+ ### 9. Update the host's `examples.html`
254
+
255
+ Add a "Basic shape (bespoke — recommended)" section alongside the existing legacy section. Show the full bespoke composition. Add a "State as attribute" section with CSS `:has()` examples + JS API examples + ADR-0023 cross-reference.
256
+
257
+ ### 10. Update sibling demo pages
258
+
259
+ Add a "Family pattern (forward-looking)" section to the shell's `<X>-shell.examples.html` showing planned bespoke children. This documents the family even before all children land.
260
+
261
+ ### 11. Sweep verification (post-migration grep audit)
262
+
263
+ After landing a vocabulary migration or deprecation, run a comprehensive grep audit across **all extensions** for the legacy pattern set. Markup-only commits frequently leave **CSS selectors and JS comments** referencing the old vocabulary — they're in different files from the markup, so a markup-only commit looks complete but leaves drift.
264
+
265
+ ```bash
266
+ LEGACY_PATTERNS=(
267
+ 'adia-editor-ui' 'adia-chat-ui' 'app-shell-ui' 'gen-ui-ui'
268
+ '\[data-canvas\]' '\[data-chat-messages\]' '\[data-chat-input\]'
269
+ '\[data-chat-empty\]' '\[data-sidebar=' '\[data-editor-body\]'
270
+ '<aside-ui slot=' '<dialog data-command'
271
+ 'data-pane-side' 'data-pane-grow' 'data-app-shell-toggle'
272
+ )
273
+ for pat in "${LEGACY_PATTERNS[@]}"; do
274
+ echo "=== $pat ==="
275
+ grep -rln -E "$pat" apps --include='*.css' --include='*.html' \
276
+ --include='*.js' --include='*.yaml' 2>/dev/null
277
+ done
278
+ ```
279
+
280
+ **0 hits across all patterns** = sweep verified clean. Hits should be acted on:
281
+
282
+ - Markup hits → update to bespoke vocabulary
283
+ - CSS selector hits → update to bespoke selector (tag-name or new attribute)
284
+ - JS code hits → update query selectors + remove dead `customElements.whenDefined` deps
285
+ - JS comment hits → update to current vocabulary so future readers don't get misled by stale documentation
286
+
287
+ This step was added after a sweep revealed apps with leftover `adia-editor-ui` + `[data-canvas]` selectors that a markup-only migration missed.
288
+
289
+ #### Four additional categories the path-only sweep misses
290
+
291
+ After an apps/playgrounds/catalog reorg (ADR-0026) surfaced 4 categories that survived a full-path substitution sweep. **Full-path grep is necessary but insufficient.** Add these to any vocabulary migration's verification:
292
+
293
+ ##### Category A — Bare-name prose mentions in narrative docs
294
+
295
+ ```bash
296
+ LEGACY_NAMES=( 'old-folder-name' 'old-thing-name' )
297
+ for n in "${LEGACY_NAMES[@]}"; do
298
+ echo "=== bare '$n' ==="
299
+ grep -rn -E "\b${n}\b" . \
300
+ --include='*.md' --include='*.yaml' \
301
+ | grep -v '/CHANGELOG.md'
302
+ done
303
+ ```
304
+
305
+ ##### Category B — Skill directory names that follow folder-name convention
306
+
307
+ ```bash
308
+ find . -type d -name '*-expert' -not -path '*/node_modules/*' \
309
+ | grep -E '(old-name|stale-name)'
310
+
311
+ for skill in $(find . -name 'SKILL.md' -not -path '*/node_modules/*'); do
312
+ dir=$(basename $(dirname "$skill"))
313
+ name=$(grep '^name:' "$skill" | head -1 | sed 's/name: *//')
314
+ [ "$dir" != "$name" ] && echo "MISMATCH: $skill (dir=$dir, name=$name)"
315
+ done
316
+ ```
317
+
318
+ ##### Category C — JSON metadata fields at filename granularity
319
+
320
+ Critical: full-path sweeps catch directory moves but miss **file renames within moved directories**.
321
+
322
+ ```bash
323
+ # Corpus chunks, sitemaps, catalog manifests — any JSON with file paths.
324
+ LEGACY_FILENAMES=( 'old-name.html' 'old-name.contents.html' 'old-name.contents.js' )
325
+ for f in "${LEGACY_FILENAMES[@]}"; do
326
+ grep -rn "$f" --include='*.json' \
327
+ | grep -v '/dist/' | grep -v '/node_modules/'
328
+ done
329
+ ```
330
+
331
+ This is the **highest-impact category** — corpus chunks with stale `source` or `page` fields cause silent harvest miss on the next rebuild (the chunk loads but won't be re-harvestable; no error).
332
+
333
+ ##### Category D — Inventory tables in cross-cutting docs
334
+
335
+ ```bash
336
+ grep -ln 'old-name' README.md AGENTS.md docs/ROADMAP.md docs/specs/INDEX.md
337
+ ```
338
+
339
+ **Pre-rename verification check**: before the rename, take a snapshot:
340
+
341
+ ```bash
342
+ grep -rln 'OLD_NAME' . --include='*.md' --include='*.json' \
343
+ | sort > /tmp/pre-rename-refs.txt
344
+ ```
345
+
346
+ After the rename + path sweep, take a fresh snapshot and diff — anything in the post-snapshot is a stale ref to investigate.
347
+
348
+ **ADR-0026 case study**: a corpus chunk's `page` field pointed at the pre-rename `app-shell.contents.html` inside the post-rename `/playgrounds/admin-shell/app/` directory. Full-path sweep caught the directory rename but missed the filename mismatch within it. Would have caused silent harvest miss on the next rebuild.
349
+
350
+ ## Verification gates per child
351
+
352
+ | Gate | Where it runs |
353
+ | --- | --- |
354
+ | `components --verify` clean | local + CI |
355
+ | Custom element registers | per-child test file |
356
+ | Reflected attribute round-trip | per-child test file |
357
+ | Public API present + returns expected | per-child test file |
358
+ | Event dispatch | per-child test file |
359
+ | Cleanup on disconnect | per-child test file |
360
+ | Backwards-compat — legacy markup still works | manual visual smoke against `apps/<X>-shell/` or `site/` |
361
+
362
+ ## Trigger-attribute conventions (bespoke-compatible)
363
+
364
+ Author-controlled action buttons that the shell wires up follow a consistent convention. These are TRIGGER attributes (on buttons), not state attributes (on shell children) — they still work in the bespoke world:
365
+
366
+ ```html
367
+ <button-ui icon="list" data-sidebar-toggle="leading"></button-ui>
368
+ ```
369
+
370
+ The shell finds these via `querySelectorAll('[data-sidebar-toggle]')` and binds click handlers that call `.toggle()` on the matching `<admin-sidebar>`. The author owns the button's appearance; the bespoke child owns the behavior.
371
+
372
+ Other trigger attributes:
373
+
374
+ - `[data-command-trigger]` — opens `<admin-command>` via `.show()`
375
+ - `[data-toolbar-action="<name>"]` — buttons inside `<editor-toolbar>` that bubble `toolbar-action` events
376
+
377
+ ## Persistence (localStorage namespacing)
378
+
379
+ Sidebar-collapsed state, pane widths, drawer-open state — anything the user customizes — persists in `localStorage`, keyed by the cluster + region:
380
+
381
+ ```js
382
+ const KEY = `adia-sidebar:${this.name}:width`; // adia-sidebar:leading:width
383
+ localStorage.setItem(KEY, String(width));
384
+ ```
385
+
386
+ Read on `connected()`, write on resize/toggle. Always wrap reads in try/catch — quota errors and disabled-storage modes happen.
387
+
388
+ ```js
389
+ #restoreWidth() {
390
+ try {
391
+ const saved = localStorage.getItem(`adia-sidebar:${this.name}:width`);
392
+ if (saved) this.style.setProperty('--width', `${saved}px`);
393
+ } catch { /* storage unavailable */ }
394
+ }
395
+ ```
396
+
397
+ **Each cluster's persisted state must use a cluster-distinct key prefix.** Admin uses `adia-sidebar-{name}`; chat uses `adia-chat-sidebar-{name}`. Without distinct prefixes, two sidebars sharing the same slot value (`leading`) on different shells would clobber each other.
398
+
399
+ ## Document-level listeners belong to the shell, not the children
400
+
401
+ `Cmd+K` for the command palette, `Esc` for closing a drawer, `?` for a shortcuts overlay — these listen on `document`, not on the shell element. The shell binds them in `connected()` with `addEventListener` on `document`, removes them in `disconnected()`. NEVER attach `document`-level listeners from a child component (a button-ui etc.) — they'd persist after the child unmounts.
402
+
403
+ ## Custom events for downstream consumers
404
+
405
+ Pane-resize, breakpoint-change, sidebar-collapse — fire `CustomEvent`s that bubble through the shell so consumers can react:
406
+
407
+ ```js
408
+ this.dispatchEvent(new CustomEvent('sidebar-resize', {
409
+ detail: { name: this.name, width },
410
+ bubbles: true,
411
+ composed: false, // light-DOM: composed=false is the right default
412
+ }));
413
+ ```
414
+
415
+ Consumers (CodeMirror layout, canvas redraw, dependent UI) listen on the shell or higher up the tree. The shell publishes; it doesn't know who subscribes.
416
+
417
+ ## Pitfalls
418
+
419
+ 1. **`attr:` vs `attribute:`.** The framework uses `attribute: '...'` for camelCase to kebab-case mapping. `attr:` is silently ignored — your custom element won't react to the kebab-case attribute. Always `attribute:`. Every JS-bearing bespoke child has at least one camelCase prop (`minWidth`, `noShortcut`, `proxyUrl`, etc.); the wrong key fails silently.
420
+
421
+ 2. **Path depth in test imports.** From `packages/web-modules/<cluster>/<cluster>-<role>/`, `core/element.js` is **3 levels up** (`../../../web-components/core/element.js`), not 2.
422
+
423
+ 3. **Tagged template literal for `static template = () =>`.** If you stamp a template, use `html` tagged template literal, not a plain backtick string. Plain string fails with "Cannot read 'length' of undefined" inside the framework's template parser. For light-DOM components (the convention here), use `static template = () => null` and construct DOM in `connected()` if needed.
424
+
425
+ 4. **happy-dom `<dialog>` partial support.** Polyfill `showModal` / `close` / `open` getter in tests (see test pitfalls above). The real browser is fine.
426
+
427
+ 5. **Spy timing for synchronous custom-element lifecycle.** `vi.spyOn(document, 'addEventListener')` set BEFORE `mount()` should still catch listeners added in `connected()`, but happy-dom's customElements lifecycle quirks sometimes break this. When in doubt, test the **behavior** (does Cmd+K toggle?) instead of the **mechanism** (was a listener added?).
428
+
429
+ 6. **Don't expand legacy `:is()` selectors.** The temptation when introducing a new cluster is to add `, <X-sidebar>[slot=leading]` to every existing `:is([data-X-sidebar], aside-ui[slot=leading])` selector in the legacy CSS. Resist. Use a new bespoke.css bridge file instead — easier to drop at Phase 3 deprecation. (admin/chat/editor deprecation pass collapsed all `:is(legacy, bespoke)` lifts to bespoke-only in 6 layered CSS files via a single regex pass; would have been 100x more work if lifts were sprinkled inline.)
430
+
431
+ 7. **Host LOC will grow during the compat window, then shrink at deprecation.** When introducing a new cluster's bespoke family, both code paths coexist in the host. Expected (admin-shell hit ~305 LOC during introduction). At deprecation (Phase 3 — see ADR-0024 for the canonical playbook), the host shrinks dramatically: admin-shell dropped to ~87 LOC (−71%). **Don't try to "consolidate" before deprecation lands** — the dual paths are intentional during the migration window.
432
+
433
+ 8. **Sibling cluster siblings.** `web-modules/shell/admin-shell/` lives next to `web-modules/shell/admin-sidebar/` (cluster-namespace siblings). For chat — `web-modules/chat/chat-shell/` next to `web-modules/chat/chat-thread/`. The directory layout makes the cluster family visible at `ls`.
434
+
435
+ 9. **localStorage namespace per cluster.** Each cluster's persisted state must use a cluster-distinct key prefix. Without distinct prefixes, two sidebars sharing the same slot value (`leading`) on different shells would clobber each other. Verify with a test that checks both namespaces are isolated.
436
+
437
+ 10. **Bespoke event names ≠ legacy event names.** When a bespoke wrapper forwards an event from an inner primitive, give it a NEW name (e.g. `composer-submit` from `<chat-composer>` wrapping the inner `<chat-input-ui>`'s `submit` event). If the wrapper re-emits `submit`, the host's listener fires twice (once from the bubble, once from the re-emit). Lesson — pick a bespoke event name (`<wrapper>-<concept>` like `composer-submit`, `sidebar-toggle`, `command-select`) and document the legacy→bespoke mapping in the host's `connected()`.
438
+
439
+ 11. **Delegate to primitives when they already own the concern, but don't lose state-as-attribute.** When a bespoke child wraps a primitive that already does the physical work (e.g. `<editor-sidebar>` wrapping `<pane-ui resizable>`), the bespoke tier still owns: cluster-namespace ID, state-as-attribute (`[collapsed]` reflected via `ResizeObserver` on inner pane), localStorage persistence, public API. Don't reimplement what the primitive does — but don't skip the bespoke tier just because the primitive exists. Principles 4 ("each child owns one concern") + 3 ("cluster namespace") combine here — the bespoke child owns the _cluster-specific framing_ of the primitive's work.
440
+
441
+ 12. **Persist BEFORE snapping, not after.** When `.collapse()` is called on a sidebar, the persisted width must capture the pre-collapse expanded width so `.expand()` can restore it. If you persist after setting `style.width = SNAP_THRESHOLD`, you've overwritten storage with the collapsed value and `.expand()` falls through to its default. Pattern: `if (!this.collapsed) this.#persistWidth();` BEFORE the snap. Caught in editor-sidebar via the `expand() restores from stored width or defaults to 240` test.
442
+
443
+ 13. **Stamping default content "to be helpful."** A shell that injects a default header / nav / footer commits the page author to the shell's visual language. They will copy-paste markup INTO the shell to override; you've now created two sources of truth. Stamp nothing (`static template = () => null`). The exception: a shell MAY append a tiny structural affordance the author can't reasonably write themselves — a resize-handle thumb, for instance. Keep it minimal and clean it up in `disconnected()`.
444
+
445
+ 14. **Document-level keydown handlers on child components.** If a child wants to react to global keys, it should listen for an event the SHELL emits, not bind a document-level listener itself. Otherwise the child outlives its hosting shell, leaks its listener, and starts reacting to keys in surfaces it has no business seeing.
446
+
447
+ 15. **ResizeObserver vs media queries.** Shells respond to _their own_ width, not the viewport. When the shell collapses (because main content gets crowded), the _shell instance_ is narrow — but the viewport may still be wide. Use a `ResizeObserver` on `this` (or on a bespoke child region) to compute breakpoints, and reflect the result as a reflected attribute the CSS can match. Container queries are a complementary tool; ResizeObserver fits when you need _imperative_ state (e.g. moving an element between inline and dropdown placement based on width).
448
+
449
+ ## Examples — 4 canonical clusters
450
+
451
+ ### admin cluster (canonical reference)
452
+
453
+ - **3 JS-bearing children** — `<admin-shell>` (host coordinator), `<admin-sidebar>` (resize+collapse+persist), `<admin-command>` (Cmd+K palette)
454
+ - **7 CSS-only structural children** — `<admin-content>`, `<admin-topbar>`, `<admin-statusbar>`, `<admin-scroll>`, `<admin-page>`, `<admin-page-header>`, `<admin-page-body>`
455
+ - **CSS bridge** — `packages/web-modules/shell/admin-shell/css/admin-shell.bespoke.css` (~240 LOC)
456
+ - **Tests** — `admin-sidebar.test.js` 10/10, `admin-command.test.js` 9/9
457
+
458
+ ### chat cluster (replicated pattern)
459
+
460
+ - **3 JS-bearing children** — `<chat-shell>` (host coordinator), `<chat-thread>` (scroll+streaming+empty), `<chat-composer>` (input wrapper+disabled propagation), `<chat-sidebar>` (mirrors admin-sidebar geometry)
461
+ - **3 CSS-only structural children** — `<chat-header>`, `<chat-status>`, `<chat-empty>`
462
+ - **CSS bridge** — `packages/web-modules/chat/chat-shell/css/chat-shell.bespoke.css`
463
+ - **Replication notes** — `chat-sidebar.js` was a near-copy of `admin-sidebar.js` with mechanical `s/admin/chat/g` + cluster-distinct localStorage prefix. The thread and composer required new code because their concerns (scroll-to-bottom, `[streaming]` reflection, `composer-submit` forwarding) are chat-specific. **Pattern mechanically usable for clusters with structural-mirror needs (sidebars) + adapted for cluster-specific concerns.**
464
+
465
+ ### editor cluster (third replication — confirms convention is locked in)
466
+
467
+ - **3 JS-bearing children** — `<editor-shell>` (host with `[focus-mode]` reflected), `<editor-toolbar>` (`[full-screen]` reflected, click-bubble for `[data-toolbar-action]`), `<editor-canvas>` (`[empty]` + `[focused]` reflected, zoom API), `<editor-sidebar>` (wraps `<pane-ui resizable>` rather than reimplementing drag — see Pitfall #11)
468
+ - **2 CSS-only structural children** — `<editor-statusbar>`, `<editor-canvas-empty>`
469
+ - **CSS bridge** — `packages/web-modules/editor/editor-shell/css/editor-shell.bespoke.css`
470
+ - **Replication notes** — Editor cluster has the **smallest bespoke family** of the three (5 children vs 9 admin / 7 chat) because:
471
+ 1. `<pane-ui resizable>` already owns drag (delegation — Pitfall #11)
472
+ 2. No command palette (different interaction model than admin)
473
+ 3. Toolbar + statusbar suffice for chrome bars (no separate header/status separation like chat)
474
+ - **Surfaces 2 new pitfalls** (#11 delegation, #12 persist-before-snap) caught at test time
475
+ - **Family pattern proven across 3 distinct shell archetypes** — admin (productivity), chat (LLM streaming), editor (design tooling). Convention is now canonical.
476
+
477
+ ### Phase 3 complete — legacy deprecation
478
+
479
+ ADR-0024 closes the ADR-0023 arc. All three shell hosts dropped their priority-chain reads of legacy `data-*` / `<aside-ui slot>` / `<dialog data-command>` / `<header>` / `<footer>` shapes. The bespoke vocabulary is now the ONLY recognized authoring shape.
480
+
481
+ **LOC payoff at deprecation:**
482
+
483
+ - `admin-shell.js`: ~305 → ~87 LOC (−71%) — host now does only mode reflection + attribute-forwarding
484
+ - `chat-shell.js`: simpler `connected()` (drops 4-way `||` chains)
485
+ - `editor-shell.js`: simpler `connected()` (drops 2-way `||` chains)
486
+ - CSS layered files (`admin-shell.sidebar.css`, `chat-shell.empty.css`, etc.) all `:is(legacy, bespoke)` lifts collapsed to bespoke-only
487
+
488
+ **Lesson from deprecation:** the backwards-compat priority-chain reads were the right transition mechanism, but they accumulated 6 distinct private methods + 4 private fields + dual event-name listeners in `<admin-shell>` alone. **Keep the compat window short** — ~9 days was enough time to migrate all 6 consumers + prove the pattern via smoke probe, and avoiding longer kept the host code from rotting under the dual-shape burden.
489
+
490
+ ### simple cluster (4th replication — pattern compounds)
491
+
492
+ The 4th cluster proves the pattern generalizes across **four distinct archetypes** now: admin (productivity), chat (LLM streaming), editor (design tooling), simple (minimal page). 23 bespoke shell-tier children total across the 4 clusters.
493
+
494
+ **Family:**
495
+
496
+ - `<simple-shell>` (JS-bearing host) — 2 reflected attrs: `[centered]` (vertical-center content), `[full-bleed]` (drops max-width)
497
+ - `<simple-content>` (CSS-only) — article-body container with token-correct vertical rhythm
498
+ - `<simple-hero>` (CSS-only) — optional top strip with 3 named slots: `heading`, `lede`, `actions`
499
+
500
+ **Deliberately minimal**: 1 host + 2 CSS-only children. No sidebars, no chrome bars, no command palette. Use cases: marketing splashes, error pages (404 / 500 / maintenance), thank-you pages, single-card flows (sign-in, password reset).
501
+
502
+ **Compounding insight:**
503
+
504
+ | Cluster | Approx. build time | Notes |
505
+ | --- | --- | --- |
506
+ | admin (1st) | ~4 hours | Inventing the pattern + 10 children |
507
+ | chat (2nd) | ~2 hours | Replication confirmed pattern works |
508
+ | editor (3rd) | ~2 hours | Confirms convention is canonical; introduces delegation (wraps `<pane-ui>`) |
509
+ | simple (4th) | **~30 minutes** | Mechanical follow-template; smallest cluster |
510
+
511
+ **Lesson**: each successive cluster is dramatically faster than the previous. The 4th cluster used this reference as a checklist + lifted demo HTMLs from the 3rd cluster + ran tests once at the end. **Skills compound when they're maintained.** If you're considering a 5th cluster (e.g., docs-shell — but see the decision-log note below about deferring it), expect ~20 minutes for a thin family or ~45 minutes for a richer one. The pattern is mechanically replicable.
512
+
513
+ **Decision-log note on docs-shell (deferred):**
514
+
515
+ A 5th cluster `docs-shell` was considered for documentation pages (markdown rendering, TOC right-rail, frontmatter strip). **Deferred** because `<admin-shell>` already serves the docs surface at `site/index.html` — the only differentiating concern (TOC right-rail) is better served by a `<docs-toc>` element placed in `<admin-shell slot="trailing">` than by a competing shell. Cluster proliferation is a real anti-pattern; introduce new clusters only when a distinct archetype emerges that admin/chat/editor/simple don't cover.
516
+
517
+ ## Legacy `data-*` shape (retired)
518
+
519
+ The legacy shape used `data-*` region attributes (`[data-canvas]`, `<aside-ui slot="leading">`, `<dialog data-command>`) and CSS selectors that matched those data-attributes. **Retired per ADR-0024.**
520
+
521
+ Pedagogically the design philosophy was identical to the bespoke shape — behavior-only shells, author-owned DOM, persistence keyed by region, ResizeObserver for breakpoint signals, document-level listeners owned by the shell. The bespoke shape moves all of those concerns onto custom elements with reflected-attribute APIs instead of data-attribute sniffing. See ADR-0024 for the full deprecation rationale and the canonical "introduce → migrate → deprecate" timing.
522
+
523
+ When you encounter a legacy reference in old documentation or commit history, the migration map is:
524
+
525
+ | Legacy | Bespoke |
526
+ | --- | --- |
527
+ | `<aside-ui slot="leading">` inside `<app-shell-ui>` | `<admin-sidebar slot="leading">` inside `<admin-shell>` |
528
+ | `<dialog data-command>` | `<admin-command>` |
529
+ | `[data-canvas]` inside `<adia-editor-ui>` | `<editor-canvas>` inside `<editor-shell>` |
530
+ | `[data-chat-messages]` / `[data-chat-input]` / `[data-chat-empty]` | `<chat-thread>` / `<chat-composer>` / `<chat-empty>` |
531
+ | `[data-pane-side]` / `[data-pane-grow]` | `<editor-sidebar>` wrapping `<pane-ui resizable>` |
532
+ | `data-app-shell-toggle` (trigger) | unchanged — trigger attributes are still data-\* |
533
+
534
+ ## After implementation
535
+
536
+ 1. Update this reference if you discover new pitfalls
537
+ 2. Record the cluster's family as an architectural milestone in the repo's journal / decision log
538
+ 3. Cut a lockstep release using the **adia-ui-release** skill
539
+ 4. Announce the new family in the team's usual channel
540
+ 5. **When all consumers have migrated**, file an ADR for the deprecation (see ADR-0024 as the canonical template) and cut a breaking-change release. Don't let the compat window outlive its usefulness.
541
+
542
+ ## Anti-patterns specific to shells (deduplicated)
543
+
544
+ - **Stamping default content "to be helpful."** Stamp nothing (Pitfall #13).
545
+ - **Hand-rolling toggle behavior in the page** instead of using `data-<shell>-toggle`. The shell wires localStorage persistence + the ResizeObserver breakpoint logic; rolling your own re-derives both badly. If the shell's trigger-attribute convention doesn't fit, the shell is missing a feature — request it, don't work around it.
546
+ - **Document-level keydown handlers on child components.** Pitfall #14.
547
+ - **Persisting state without a cluster-scoped key prefix.** Pitfall #9.
548
+ - **Stamping invasive `:is(legacy, bespoke)` lifts in legacy CSS.** Pitfall #6 — use a new bespoke.css bridge file instead.
549
+
550
+ ## Cross-references
551
+
552
+ - [authoring-cycle.md](authoring-cycle.md) — general 5-step authoring procedure (modes 1 + 2); shell-specific rules layer on top
553
+ - [api-contract.md](api-contract.md) — prop naming, reflection policy
554
+ - [css-patterns.md](css-patterns.md) — two-block `@scope`, variants vs modes
555
+ - [lifecycle-patterns.md](lifecycle-patterns.md) — teardown patterns; the cleanup-closure pile and document-listener rules above layer on top
556
+ - [token-contract.md](token-contract.md) — zero-raw-color + L3 alias rules apply identically to shells
557
+ - [module-promotion.md](module-promotion.md) — mode 4, the _different_ activity of lifting a cross-cluster reusable element (theme panel, command palette) into a shared module
558
+ - ADR-0023 — bespoke shell-tier children architectural rationale
559
+ - ADR-0024 — legacy shell shapes retired (the canonical deprecation playbook)
560
+ - ADR-0015 — `<cluster-thing>` naming convention (no `-ui` suffix for bespoke children)
561
+ - the **adia-ui-factory** plugin — the _consumer_ view: how to compose with these shells once they exist