@kerfjs/ui 5.0.0-beta.17 → 5.0.0-beta.21

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 (274) hide show
  1. package/README.md +221 -81
  2. package/ai/application-ui-diagnostic-ids-v1.json +92 -0
  3. package/ai/application-ui-profile-sync.cjs +529 -0
  4. package/ai/application-ui-profile.d.mts +1 -0
  5. package/ai/application-ui-profile.d.ts +102 -0
  6. package/ai/application-ui-profile.defaults.json +41 -0
  7. package/ai/application-ui-profile.mjs +626 -0
  8. package/ai/application-ui-profile.schema.json +130 -0
  9. package/ai/catalog-authoring.json +25 -0
  10. package/ai/catalog-authoring.schema.json +61 -0
  11. package/ai/compile-time-contracts-v1.json +101 -0
  12. package/ai/compile-time-contracts-v1.schema.json +49 -0
  13. package/ai/component-catalog-extension-v2.d.ts +7 -0
  14. package/ai/component-catalog-extension-v2.schema.json +7 -0
  15. package/ai/component-catalog-extension.schema.json +79 -0
  16. package/ai/component-catalog-v2-overrides.json +326 -0
  17. package/ai/component-catalog-v2.d.ts +67 -0
  18. package/ai/component-catalog-v2.json +4441 -0
  19. package/ai/component-catalog-v2.schema.json +246 -0
  20. package/ai/component-catalog.json +649 -6
  21. package/ai/component-catalog.schema.json +15 -0
  22. package/ai/public-api-signatures-v1.md +357 -91
  23. package/ai/skill.md +95 -50
  24. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  25. package/analyzer/cli.mjs +46 -0
  26. package/analyzer/index.d.mts +45 -0
  27. package/analyzer/index.mjs +743 -0
  28. package/analyzer/report.schema.json +66 -0
  29. package/dist/app-tab.d.ts +1 -1
  30. package/dist/app-tab.js +1 -1
  31. package/dist/browser/pane.js +3 -0
  32. package/dist/browser/sunken-panel.js +3 -0
  33. package/dist/catalog-resources.d.ts +34 -0
  34. package/dist/catalog-resources.js +22 -0
  35. package/dist/catalog-resources.js.map +1 -0
  36. package/dist/catalog.d.ts +29 -4
  37. package/dist/catalog.js +302 -84
  38. package/dist/catalog.js.map +1 -1
  39. package/dist/chunk-5VTB2BKP.js +35 -0
  40. package/dist/chunk-5VTB2BKP.js.map +1 -0
  41. package/dist/chunk-6WTETUZ6.js +21 -0
  42. package/dist/chunk-6WTETUZ6.js.map +1 -0
  43. package/dist/chunk-7KEUJIIC.js +218 -0
  44. package/dist/chunk-7KEUJIIC.js.map +1 -0
  45. package/dist/chunk-7L6EHUMV.js +31 -0
  46. package/dist/chunk-7L6EHUMV.js.map +1 -0
  47. package/dist/{chunk-XXMOARTH.js → chunk-AASLYRGE.js} +57 -16
  48. package/dist/chunk-AASLYRGE.js.map +1 -0
  49. package/dist/chunk-BBRXX6PF.js +108 -0
  50. package/dist/chunk-BBRXX6PF.js.map +1 -0
  51. package/dist/{chunk-6NLRBSVX.js → chunk-CN2XNTAM.js} +129 -72
  52. package/dist/chunk-CN2XNTAM.js.map +1 -0
  53. package/dist/chunk-DPDBVKNA.js +113 -0
  54. package/dist/chunk-DPDBVKNA.js.map +1 -0
  55. package/dist/chunk-DSNIYOZ3.js +114 -0
  56. package/dist/chunk-DSNIYOZ3.js.map +1 -0
  57. package/dist/chunk-ELSJX4W7.js +68 -0
  58. package/dist/chunk-ELSJX4W7.js.map +1 -0
  59. package/dist/{chunk-AHQI52QO.js → chunk-FEY65TBV.js} +6 -2
  60. package/dist/chunk-FEY65TBV.js.map +1 -0
  61. package/dist/chunk-JRKSK2HX.js +79 -0
  62. package/dist/chunk-JRKSK2HX.js.map +1 -0
  63. package/dist/chunk-K57KRAOV.js +117 -0
  64. package/dist/chunk-K57KRAOV.js.map +1 -0
  65. package/dist/chunk-LPOMRC4C.js +25 -0
  66. package/dist/chunk-LPOMRC4C.js.map +1 -0
  67. package/dist/chunk-M7JBER5U.js +41 -0
  68. package/dist/chunk-M7JBER5U.js.map +1 -0
  69. package/dist/chunk-NENBUYX5.js +79 -0
  70. package/dist/chunk-NENBUYX5.js.map +1 -0
  71. package/dist/chunk-OHU6CI22.js +83 -0
  72. package/dist/chunk-OHU6CI22.js.map +1 -0
  73. package/dist/{chunk-YGN7OMLT.js → chunk-OZWU5ZPZ.js} +2 -2
  74. package/dist/chunk-OZWU5ZPZ.js.map +1 -0
  75. package/dist/chunk-PIDTEQ3U.js +105 -0
  76. package/dist/chunk-PIDTEQ3U.js.map +1 -0
  77. package/dist/chunk-PX3YSS5C.js +32 -0
  78. package/dist/chunk-PX3YSS5C.js.map +1 -0
  79. package/dist/chunk-PZQVSDLH.js +61 -0
  80. package/dist/chunk-PZQVSDLH.js.map +1 -0
  81. package/dist/chunk-QO55FN2Y.js +20 -0
  82. package/dist/chunk-QO55FN2Y.js.map +1 -0
  83. package/dist/chunk-QQFJMRIB.js +45 -0
  84. package/dist/chunk-QQFJMRIB.js.map +1 -0
  85. package/dist/chunk-RYRIFZCT.js +58 -0
  86. package/dist/chunk-RYRIFZCT.js.map +1 -0
  87. package/dist/{chunk-7BR3DO2A.js → chunk-SRSJO5QE.js} +13 -4
  88. package/dist/chunk-SRSJO5QE.js.map +1 -0
  89. package/dist/chunk-VLYV65LK.js +38 -0
  90. package/dist/chunk-VLYV65LK.js.map +1 -0
  91. package/dist/chunk-W4CXQYSG.js +23 -0
  92. package/dist/chunk-W4CXQYSG.js.map +1 -0
  93. package/dist/{chunk-WXMGNX2Y.js → chunk-W7YD6E4N.js} +2 -2
  94. package/dist/chunk-W7YD6E4N.js.map +1 -0
  95. package/dist/{chunk-56RJXPHG.js → chunk-Y6LYKFXY.js} +1 -1
  96. package/dist/{chunk-56RJXPHG.js.map → chunk-Y6LYKFXY.js.map} +1 -1
  97. package/dist/collapsible-panel.d.ts +2 -2
  98. package/dist/collapsible-panel.js +47 -6
  99. package/dist/collapsible-panel.js.map +1 -1
  100. package/dist/device-class.js +33 -5
  101. package/dist/device-class.js.map +1 -1
  102. package/dist/disclosure-arrow.js +1 -1
  103. package/dist/empty-state.d.ts +1 -1
  104. package/dist/empty-state.js +1 -1
  105. package/dist/floating-toolbar.d.ts +1 -1
  106. package/dist/floating-toolbar.js +1 -1
  107. package/dist/index.d.ts +6 -4
  108. package/dist/index.js +27 -25
  109. package/dist/list-action-row.d.ts +1 -1
  110. package/dist/list-action-row.js +1 -1
  111. package/dist/list-header.d.ts +30 -9
  112. package/dist/list-header.js +1 -1
  113. package/dist/list-inset-control.d.ts +1 -1
  114. package/dist/list-inset-control.js +1 -1
  115. package/dist/list-inset-text.d.ts +1 -1
  116. package/dist/list-inset-text.js +1 -1
  117. package/dist/list-item.d.ts +1 -1
  118. package/dist/list-item.js +1 -1
  119. package/dist/loading-spinner.js +1 -1
  120. package/dist/nav-stack.d.ts +1 -1
  121. package/dist/nav-stack.js +1 -1
  122. package/dist/pane.d.ts +49 -0
  123. package/dist/pane.js +4 -0
  124. package/dist/pane.js.map +1 -0
  125. package/dist/panel-header.d.ts +10 -4
  126. package/dist/panel-header.js +1 -1
  127. package/dist/resizable-region.d.ts +1 -1
  128. package/dist/resizable-region.js +1 -1
  129. package/dist/segmented-control.d.ts +6 -6
  130. package/dist/segmented-control.js +1 -1
  131. package/dist/select.d.ts +11 -5
  132. package/dist/select.js +1 -1
  133. package/dist/skeleton.d.ts +1 -1
  134. package/dist/skeleton.js +1 -1
  135. package/dist/split-view.d.ts +1 -1
  136. package/dist/split-view.js +70 -10
  137. package/dist/split-view.js.map +1 -1
  138. package/dist/state-banner.d.ts +4 -3
  139. package/dist/state-banner.js +1 -1
  140. package/dist/styles/catalog.css +32 -6
  141. package/dist/styles/layout.css +2 -20
  142. package/dist/styles/pane.css +62 -0
  143. package/dist/styles/segmented-control.css +3 -7
  144. package/dist/styles/sunken-panel.css +18 -0
  145. package/dist/sunken-panel.d.ts +16 -0
  146. package/dist/sunken-panel.js +3 -0
  147. package/dist/sunken-panel.js.map +1 -0
  148. package/dist/tab-bar.d.ts +4 -3
  149. package/dist/tab-bar.js +1 -1
  150. package/dist/tab-scaffold.d.ts +6 -6
  151. package/dist/tab-scaffold.js +43 -8
  152. package/dist/tab-scaffold.js.map +1 -1
  153. package/dist/token-search-field.d.ts +25 -9
  154. package/dist/token-search-field.js +1 -1
  155. package/dist/toolbar-control-group.d.ts +10 -6
  156. package/dist/toolbar-control-group.js +1 -1
  157. package/dist/toolbar-text.d.ts +10 -11
  158. package/dist/toolbar-text.js +1 -1
  159. package/dist/toolbar.d.ts +1 -1
  160. package/dist/toolbar.js +1 -1
  161. package/dist/value-table.d.ts +2 -2
  162. package/dist/value-table.js +1 -1
  163. package/dist/wire-catalog.d.ts +31 -2
  164. package/dist/wire-catalog.js +203 -8
  165. package/dist/wire-catalog.js.map +1 -1
  166. package/dist/wire-nav-stack.js +22 -7
  167. package/dist/wire-nav-stack.js.map +1 -1
  168. package/dist/wire-resizable-regions.d.ts +1 -1
  169. package/dist/wire-resizable-regions.js +1 -1
  170. package/dist/wire-sidebar.js +52 -41
  171. package/dist/wire-sidebar.js.map +1 -1
  172. package/dist/wire-tab-bars.d.ts +5 -2
  173. package/dist/wire-tab-bars.js +1 -1
  174. package/dist/wire-tab-scaffold.js +9 -4
  175. package/dist/wire-tab-scaffold.js.map +1 -1
  176. package/dist/wire-token-search-fields.d.ts +12 -7
  177. package/dist/wire-token-search-fields.js +1 -1
  178. package/dist/workbench.d.ts +1 -1
  179. package/dist/workbench.js +56 -9
  180. package/dist/workbench.js.map +1 -1
  181. package/docs/app-layouts.md +7 -7
  182. package/docs/catalog.md +249 -46
  183. package/docs/collapsible-panel.md +38 -11
  184. package/docs/component-contract.md +132 -7
  185. package/docs/component-selection.md +79 -67
  186. package/docs/design/templates.md +1 -1
  187. package/docs/design-philosophy.md +7 -9
  188. package/docs/device-class.md +8 -8
  189. package/docs/examples/application-ui-profile.json +60 -0
  190. package/docs/examples/command-palette-adapter.tsx +67 -17
  191. package/docs/examples/component-catalog-extension-v2.json +81 -0
  192. package/docs/examples/component-catalog-extension.json +45 -0
  193. package/docs/layout.md +51 -32
  194. package/docs/nav-stack.md +15 -6
  195. package/docs/recipes.md +4 -4
  196. package/docs/split-view.md +3 -3
  197. package/docs/sunken-panel.md +43 -0
  198. package/docs/tab-scaffold.md +16 -7
  199. package/docs/type-contracts.md +99 -0
  200. package/docs/ui-analyzer.md +81 -0
  201. package/docs/ui-doctor.md +77 -0
  202. package/docs/ui-evaluator.md +122 -0
  203. package/docs/webawesome-theme.md +8 -8
  204. package/docs/workbench.md +18 -5
  205. package/doctor/cli.mjs +115 -0
  206. package/doctor/config.schema.json +68 -0
  207. package/doctor/index.d.mts +116 -0
  208. package/doctor/index.mjs +1256 -0
  209. package/doctor/report.schema.json +118 -0
  210. package/evaluator/cli.mjs +89 -0
  211. package/evaluator/index.d.mts +90 -0
  212. package/evaluator/index.mjs +1138 -0
  213. package/evaluator/report.schema.json +152 -0
  214. package/llms.txt +58 -15
  215. package/package.json +70 -10
  216. package/ux-demo/recipes/app-shell.tsx +206 -10
  217. package/ux-demo/recipes/collapsible-sidebar.tsx +167 -44
  218. package/ux-demo/recipes/compact-toolbar.tsx +114 -12
  219. package/ux-demo/recipes/composer-form.tsx +106 -18
  220. package/ux-demo/recipes/list-detail-dialog.tsx +144 -8
  221. package/ux-demo/recipes/list-workspace-states.tsx +165 -8
  222. package/ux-demo/recipes/loaders.ts +5 -2
  223. package/ux-demo/recipes/loading-inspector.tsx +124 -41
  224. package/ux-demo/recipes/mount-recipe.ts +27 -6
  225. package/ux-demo/recipes/navigation-sidebar.tsx +120 -9
  226. package/ux-demo/recipes/navigation-stack.tsx +61 -19
  227. package/ux-demo/recipes/types.ts +3 -1
  228. package/ux-demo/recipes/workspace-header.tsx +57 -2
  229. package/dist/chunk-2Z5TWHKQ.js +0 -34
  230. package/dist/chunk-2Z5TWHKQ.js.map +0 -1
  231. package/dist/chunk-37T3VNIZ.js +0 -19
  232. package/dist/chunk-37T3VNIZ.js.map +0 -1
  233. package/dist/chunk-55DATV3A.js +0 -10
  234. package/dist/chunk-55DATV3A.js.map +0 -1
  235. package/dist/chunk-5ATCDSQS.js +0 -42
  236. package/dist/chunk-5ATCDSQS.js.map +0 -1
  237. package/dist/chunk-5E2HVJ33.js +0 -10
  238. package/dist/chunk-5E2HVJ33.js.map +0 -1
  239. package/dist/chunk-6NLRBSVX.js.map +0 -1
  240. package/dist/chunk-7BR3DO2A.js.map +0 -1
  241. package/dist/chunk-7JB7VPRI.js +0 -34
  242. package/dist/chunk-7JB7VPRI.js.map +0 -1
  243. package/dist/chunk-7LDCTU7D.js +0 -134
  244. package/dist/chunk-7LDCTU7D.js.map +0 -1
  245. package/dist/chunk-AHQI52QO.js.map +0 -1
  246. package/dist/chunk-AQNKJRFH.js +0 -79
  247. package/dist/chunk-AQNKJRFH.js.map +0 -1
  248. package/dist/chunk-AVZHJN6C.js +0 -14
  249. package/dist/chunk-AVZHJN6C.js.map +0 -1
  250. package/dist/chunk-B24C4A6T.js +0 -22
  251. package/dist/chunk-B24C4A6T.js.map +0 -1
  252. package/dist/chunk-CH4SQ6H5.js +0 -39
  253. package/dist/chunk-CH4SQ6H5.js.map +0 -1
  254. package/dist/chunk-DAFFDO33.js +0 -14
  255. package/dist/chunk-DAFFDO33.js.map +0 -1
  256. package/dist/chunk-I7INRJO4.js +0 -17
  257. package/dist/chunk-I7INRJO4.js.map +0 -1
  258. package/dist/chunk-L4OKFDJY.js +0 -24
  259. package/dist/chunk-L4OKFDJY.js.map +0 -1
  260. package/dist/chunk-M5JRI6MN.js +0 -27
  261. package/dist/chunk-M5JRI6MN.js.map +0 -1
  262. package/dist/chunk-RNPGFQ33.js +0 -10
  263. package/dist/chunk-RNPGFQ33.js.map +0 -1
  264. package/dist/chunk-VPCTCUED.js +0 -10
  265. package/dist/chunk-VPCTCUED.js.map +0 -1
  266. package/dist/chunk-WRTAQ7BZ.js +0 -23
  267. package/dist/chunk-WRTAQ7BZ.js.map +0 -1
  268. package/dist/chunk-WVPYVDXY.js +0 -15
  269. package/dist/chunk-WVPYVDXY.js.map +0 -1
  270. package/dist/chunk-WXMGNX2Y.js.map +0 -1
  271. package/dist/chunk-XXMOARTH.js.map +0 -1
  272. package/dist/chunk-YGN7OMLT.js.map +0 -1
  273. package/dist/chunk-ZQQ6P4SV.js +0 -20
  274. package/dist/chunk-ZQQ6P4SV.js.map +0 -1
package/docs/catalog.md CHANGED
@@ -7,7 +7,7 @@ you get the same shell without rebuilding it. Like the app layouts, it is a
7
7
  subpath-only, tree-shakeable module that adds nothing to the main barrel.
8
8
 
9
9
  ```bash
10
- npm install @kerfjs/ui # kerfjs is a peer; @kerfjs/ui/select/register is needed only if entries use `related`
10
+ npm install @kerfjs/ui # kerfjs is a peer; @kerfjs/ui/select/register is needed only if entries use `related`
11
11
  ```
12
12
 
13
13
  - `Catalog(props)` returns the shell as `SafeHtml` (a `<main class="kui-catalog">`).
@@ -17,20 +17,20 @@ npm install @kerfjs/ui # kerfjs is a peer; @kerfjs/ui/select/register is neede
17
17
  - `wireCatalog(root, options)` wires the interactions (sidebar selection, the
18
18
  related-entry popup menu, and the collapse/theme toggles) with one delegated
19
19
  listener set and returns a disposer; it can also mirror the active id into the
20
- URL.
20
+ URL and reveal the active sidebar row after a controlled render.
21
21
 
22
22
  ## What you supply
23
23
 
24
24
  - **`sections`** — category-grouped entries: `{ category, entries: [{ id, name,
25
- description?, tags?, resources?, related? }] }`. Each entry becomes a sidebar
25
+ description?, tags?, resources?, related? }] }`. Each entry becomes a sidebar
26
26
  `ListItem` under a `ListHeader` for its category. Short `tags` render as quiet
27
27
  trailing pills for decision metadata such as `Discouraged`.
28
28
  - **`content`** — the rendered preview for the active entry. Keep a map of `id →
29
- () => SafeHtml` in your app and call `renderers[active]()` in your render.
29
+ () => SafeHtml` in your app and call `renderers[active]()` in your render.
30
30
  - **`brand`** — `{ title, subtitle?, logoUrl? }` for the sidebar header.
31
31
  - **`secondarySections`** — an optional secondary "ecosystem" group shown below the
32
32
  primary sections with a quieter treatment: `{ label, sections, collapsible?,
33
- expanded? }`. When `collapsible`, the label is a disclosure toggle controlling
33
+ expanded? }`. When `collapsible`, the label is a disclosure toggle controlling
34
34
  `expanded` (the app owns it; wire it with `wireCatalog`'s `onToggleSecondary`).
35
35
  - Optional slots: `headerActions` (extra header controls), `sidebarFooter` (extra
36
36
  sidebar content), and `status` (a footer status line).
@@ -40,94 +40,297 @@ Per-entry `resources` render as "open in new tab" links in the footer, and
40
40
  around a `wa-dropdown` (grouped by each entry's `group`), so register its elements
41
41
  with `@kerfjs/ui/select/register` when you use it.
42
42
 
43
- ## Preview examples
43
+ ## Catalog demo authoring contract
44
44
 
45
- Compose each entry's `content` from `CatalogExample` (and `CatalogExampleStack`)
46
- instead of hand-rolled example markup, so labels, notes, and left-edge alignment
47
- stay consistent:
45
+ This section is the single authoritative contract for tools and people that
46
+ author Catalog previews. The machine-readable discovery entry is
47
+ [`catalog-authoring.json`](../ai/catalog-authoring.json); exact props remain in
48
+ [`public-api-signatures-v1.md`](../ai/public-api-signatures-v1.md#kerfjsuicatalog).
49
+ The component catalog deliberately does not duplicate these rules: it describes
50
+ which component to choose, while this contract describes how to present the
51
+ chosen component.
52
+
53
+ ### Choose the demo mode
54
+
55
+ Classify every entry before rendering it:
56
+
57
+ | Entry kind | Preview purpose | Geometry overlay |
58
+ | --------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
59
+ | Focused component | Show one public component, its meaningful variants, and adverse states. | `true`; the overlay inspects each selected specimen. |
60
+ | Composition or recipe | Show several components cooperating as one product surface. | `false`; child geometry remains unmarked so the composition can be read as a whole. |
61
+
62
+ Keep `geometryOverlay` present and compute it from the active entry. Do not make
63
+ it a permanent catalog-wide `true`, and do not give individual specimens their
64
+ own overlay implementation.
65
+
66
+ ### Required structure
67
+
68
+ - `Catalog` is the one shell. The app owns active-entry state and passes one
69
+ active preview through `content`.
70
+ - `CatalogExampleStack` is the group for one preview's rows. Put route/test
71
+ metadata such as `data-demo` on its rendered root through `rootAttributes`.
72
+ Pass `label` when the stack needs an accessible name: the rendered `section`
73
+ is then exposed as a named region. An unlabeled stack remains an ordinary
74
+ grouping rather than adding an unnecessary landmark.
75
+ - `CatalogExample` is one row: optional generated `ListHeader` label, optional
76
+ generated note, then one specimen or one intentionally coupled specimen
77
+ cluster. Use one row per variant/state; do not hand-author the helper's private
78
+ classes.
79
+ - The specimen is an immediate child of `CatalogExample`. A focused component
80
+ row should place the component root there, without a decorative card or
81
+ spacing wrapper. A composition row may place the composition root there.
82
+
83
+ Use `align="glyph"` for a bare glyph/text specimen, `align="inline-control"`
84
+ for a control whose own inline padding contributes about 8px, and `align="none"`
85
+ (the default) for a content item or composition that owns its geometry.
48
86
 
49
87
  ```tsx
50
- import { CatalogExample, CatalogExampleStack } from '@kerfjs/ui/catalog';
88
+ import { CatalogExample, CatalogExampleStack } from "@kerfjs/ui/catalog";
51
89
 
52
90
  const buttonPreview = (
53
- <CatalogExampleStack label="Button variants">
91
+ <CatalogExampleStack
92
+ label="Button variants"
93
+ rootAttributes={{ "data-demo": "button" }}
94
+ >
54
95
  <CatalogExample label="Icon" note="A bare glyph." align="glyph">
55
96
  <LucideIcon icon={Plus} name="plus" />
56
97
  </CatalogExample>
57
98
  <CatalogExample label="Control" align="inline-control">
58
99
  <SegmentedControl id="view" label="View" value="list" choices={choices} />
59
100
  </CatalogExample>
60
- <CatalogExample label="In composition">
61
- <ValueTable label="Metadata">{rows}</ValueTable>
101
+ <CatalogExample
102
+ label="Authoring note"
103
+ note="Explanatory chrome is not a specimen."
104
+ rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}
105
+ >
106
+ <p>Use the public helper contract.</p>
62
107
  </CatalogExample>
63
108
  </CatalogExampleStack>
64
109
  );
65
110
  ```
66
111
 
67
- `label` is optional omit it for a bare specimen with no `ListHeader`. `align`
68
- lines a specimen's visible left edge up with its label text: `'glyph'`
69
- (16px) for a bare glyph/text specimen, `'inline-control'` (8px) for a control that
70
- already carries ~8px of its own inline padding, and `'none'` (the default) for a
71
- content-item/composition that already owns its geometry. The inset is published as
72
- the `--kui-catalog-example-align` custom property so a debug overlay can exclude it
73
- from a specimen's measured margin.
112
+ The overlay selects every immediate child of a `CatalogExample` except the
113
+ helper-generated label and note. It does not recursively promote a nested child
114
+ to be the specimen. Outside an example row, it selects only top-level
115
+ `[data-component]` roots in the canvas and ignores nested component descendants.
116
+ These rules keep a row's label/group scaffolding out of the measurement and make
117
+ the authored nesting determine exactly what is inspected.
118
+
119
+ ### Metadata ownership
120
+
121
+ Use `rootAttributes` on either helper for authoring metadata such as `data-demo`
122
+ or `data-catalog-geometry-overlay-skip`; the metadata lands on that helper's
123
+ rendered root. The slot accepts only `data-*` strings. Structural
124
+ `data-catalog-example`, `data-catalog-example-stack`, and `data-align` semantics
125
+ remain helper-owned and are rejected case-insensitively at runtime, including
126
+ from structurally widened or JavaScript objects. Do not copy the helpers'
127
+ private `kui-catalog-*` classes into preview markup.
128
+
129
+ The app owns entry ids, `kind`, routing, sources, relationships, and test hooks.
130
+ The helpers own their structural markers, label/note anatomy, alignment marker,
131
+ and private classes. Component metadata such as margin/border/padding ownership
132
+ lives in `component-catalog.json`; do not infer or overwrite it from overlay
133
+ pixels.
134
+
135
+ ### Geometry overlay and legend
136
+
137
+ Pass the conditional `geometryOverlay` boolean to `Catalog`, then call
138
+ `wireCatalogGeometryOverlay(root)` once after the first render and retain its
139
+ disposer alongside `wireCatalog`'s.
140
+
141
+ | Overlay mark | Meaning | It is not |
142
+ | ----------------------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
143
+ | Orange translucent band | A positive computed margin on the selected specimen, after subtracting the helper's alignment inset. | Padding, gap, or empty content. Zero and negative margins are not drawn. |
144
+ | Quiet dashed outline | The border-box outer bound of a selected specimen whose computed background is transparent. | A real CSS border, focus ring, padding edge, or proof that the specimen owns its size. |
145
+
146
+ The overlay does not visualize padding, borders, gaps, negative/zero margins,
147
+ scroll overflow, hit targets, nested descendants, or geometry ownership. Inspect
148
+ computed styles and the machine-readable `geometry` metadata for those facts.
149
+ Opaque specimens do not receive the transparent-bound outline.
150
+
151
+ ```tsx
152
+ <Catalog
153
+ {...props}
154
+ geometryOverlay={activeEntry.kind === "component"}
155
+ content={renderers[active.value]()}
156
+ />
157
+ ```
158
+
159
+ Put `rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}` on a
160
+ `CatalogExample` or `CatalogExampleStack` only when that whole subtree is
161
+ explanatory chrome rather than a specimen. The marker excludes the marked root
162
+ and every descendant from selection; it does not merely hide one band. It is
163
+ normally unnecessary in a composition because the active entry already sets the
164
+ global overlay to `false`.
165
+
166
+ Use the overlay together with machine-readable geometry ownership metadata; the
167
+ overlay verifies what is rendered, while metadata tells people and AI tools
168
+ whether the component, its parent, or its children are responsible for margin,
169
+ border, and padding. Catalogs for downstream components should conform to the
170
+ [`component-catalog-extension.schema.json`](../ai/component-catalog-extension.schema.json)
171
+ contract and can start from the checked
172
+ [`component-catalog-extension.json`](./examples/component-catalog-extension.json)
173
+ example; provide those entries beside Kerf's shipped catalog to AI tools.
174
+
175
+ ### Automated conformance and reviewed exceptions
176
+
177
+ Run `npm run check:demo-conformance` after changing a first-party demo, its
178
+ catalog kind, or the shell's overlay logic. The TypeScript-AST gate verifies
179
+ facts that source can prove without guessing at rendered intent:
180
+
181
+ - focused component routes import and use `CatalogExampleStack` and
182
+ `CatalogExample` from the public package;
183
+ - focused route metadata uses the helpers' `rootAttributes` slot, example rows
184
+ are not empty, and composition routes do not add redundant skip markers;
185
+ - every `@kerfjs/ui` import is a published package export and relative imports
186
+ do not reach into `ui/src`;
187
+ - demo JSX does not copy private `kui-catalog-*` structural classes; and
188
+ - the shell derives both geometry-overlay enablement and documented demo mode
189
+ from the active entry's source and kind.
190
+
191
+ The gate deliberately does not infer component ownership from arbitrary class
192
+ names, margins, borders, or nested descendants. Runtime selection and geometry
193
+ remain the browser suite's job.
194
+
195
+ A focused route may bypass the two public layout helpers only when the route's
196
+ stage geometry is itself the reviewed specimen. Add the narrow waiver to
197
+ [`catalog-conformance-exceptions.json`](../ux-demo/catalog-conformance-exceptions.json)
198
+ with the exact route, source file, stable diagnostic ids, a substantive reason,
199
+ and the reviewing `KF-*` ticket. Only helper/metadata rules are waivable;
200
+ private imports, private markup, empty examples, composition overlay drift, and
201
+ shell-mode drift always fail. The gate rejects duplicate, malformed, unused,
202
+ and stale exceptions, so delete a waiver when its route adopts the standard
203
+ helpers.
204
+
205
+ ## Selection reveal
206
+
207
+ Set `revealSelection: true` on `wireCatalog` for a long desktop sidebar. After
208
+ `onSelect` updates controlled state, the helper waits one animation frame, finds
209
+ the exact matching `data-item-id`, and scrolls it into view without changing
210
+ focus. A newer selection or disposal cancels the pending reveal. The default
211
+ media guard is the Catalog desktop layout (`min-width: 52.01rem`), so compact
212
+ layouts keep their existing scroll position.
213
+
214
+ Pass an options object instead of `true` to customize `block`, `inline`,
215
+ `behavior`, or `media`; `media: false` deliberately enables the behavior at all
216
+ sizes. For an initial deep link that did not come through `wireCatalog`, call
217
+ `revealCatalogEntry(app, initialId, { block: "center" })` after the first mount.
74
218
 
75
219
  ## Complete example
76
220
 
77
221
  ```tsx
78
- import { mount, signal } from 'kerfjs';
79
- import { Catalog, type CatalogSection } from '@kerfjs/ui/catalog';
80
- import { wireCatalog } from '@kerfjs/ui/wire-catalog';
81
- import '@kerfjs/ui/styles.css'; // or import each primitive's CSS + @kerfjs/ui/catalog.css
222
+ import { mount, signal, type SafeHtml } from "kerfjs";
223
+ import {
224
+ Catalog,
225
+ CatalogExample,
226
+ CatalogExampleStack,
227
+ type CatalogSection,
228
+ } from "@kerfjs/ui/catalog";
229
+ import {
230
+ revealCatalogEntry,
231
+ wireCatalog,
232
+ wireCatalogGeometryOverlay,
233
+ } from "@kerfjs/ui/wire-catalog";
234
+ import "@kerfjs/ui/styles.css"; // or import each primitive's CSS + @kerfjs/ui/catalog.css
82
235
 
83
- // 1. Describe your components once.
84
- const sections: CatalogSection[] = [
236
+ type DemoKind = "component" | "composition";
237
+ type DemoEntry = CatalogSection["entries"][number] & { kind: DemoKind };
238
+
239
+ // 1. Describe selection and overlay mode once.
240
+ const entries: DemoEntry[] = [
85
241
  {
86
- category: 'Controls',
87
- entries: [
88
- { id: 'button', name: 'Button', description: 'A pressable control.',
89
- resources: [{ label: 'Source', href: '/src/button.tsx', detail: 'src/button.tsx' }] },
90
- { id: 'field', name: 'Field', description: 'A labeled input.', tags: ['Discouraged'],
91
- related: [{ id: 'button', name: 'Button', group: 'Used with' }] },
242
+ id: "button",
243
+ name: "Button",
244
+ kind: "component",
245
+ description: "A pressable control.",
246
+ resources: [
247
+ { label: "Source", href: "/src/button.tsx", detail: "src/button.tsx" },
92
248
  ],
93
249
  },
94
- { category: 'Feedback', entries: [{ id: 'toast', name: 'Toast', description: 'A transient message.' }] },
250
+ {
251
+ id: "profile-form",
252
+ name: "Profile form",
253
+ kind: "composition",
254
+ description: "A labeled field and save action working together.",
255
+ },
256
+ ];
257
+ const sections: CatalogSection[] = [
258
+ {
259
+ category: "Examples",
260
+ entries: entries.map(({ kind: _kind, ...entry }) => entry),
261
+ },
95
262
  ];
96
263
 
97
- // 2. One preview render per entry id.
98
- const renderers: Record<string, () => ReturnType<typeof Button>> = {
99
- button: () => <Button label="Save" />,
100
- field: () => <Field label="Name" />,
101
- toast: () => <Toast>Saved</Toast>,
264
+ // 2. Every preview uses one public group and public example rows.
265
+ const renderers: Record<string, () => SafeHtml> = {
266
+ button: () => (
267
+ <CatalogExampleStack
268
+ label="Button states"
269
+ rootAttributes={{ "data-demo": "button" }}
270
+ >
271
+ <CatalogExample label="Default" align="inline-control">
272
+ <Button label="Save" />
273
+ </CatalogExample>
274
+ <CatalogExample
275
+ label="Authoring note"
276
+ note="This explanatory row is deliberately excluded from inspection."
277
+ rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}
278
+ >
279
+ <p>The application owns product copy and actions.</p>
280
+ </CatalogExample>
281
+ </CatalogExampleStack>
282
+ ),
283
+ "profile-form": () => (
284
+ <CatalogExampleStack
285
+ label="Profile form composition"
286
+ rootAttributes={{ "data-demo": "profile-form" }}
287
+ >
288
+ <CatalogExample label="Complete composition">
289
+ <ProfileForm />
290
+ </CatalogExample>
291
+ </CatalogExampleStack>
292
+ ),
102
293
  };
103
294
 
104
295
  // 3. App-owned state (domain: which entry; transient: collapsed; global: theme).
105
- const initial = new URLSearchParams(location.search).get('c') ?? sections[0].entries[0].id;
296
+ const initial =
297
+ new URLSearchParams(location.search).get("c") ?? sections[0].entries[0].id;
106
298
  const active = signal(initial);
107
299
  const collapsed = signal(false);
108
- const theme = signal<'light' | 'dark'>('light');
300
+ const theme = signal<"light" | "dark">("light");
301
+ const activeEntry = () => entries.find(({ id }) => id === active.value) ?? entries[0];
109
302
 
110
- const app = document.getElementById('app')!;
303
+ const app = document.getElementById("app")!;
111
304
  mount(app, () => (
112
305
  <Catalog
113
- brand={{ title: 'Acme UI', subtitle: 'Design system' }}
306
+ brand={{ title: "Acme UI", subtitle: "Design system" }}
114
307
  sections={sections}
115
308
  active={active.value}
116
309
  content={renderers[active.value]?.() ?? <></>}
117
310
  collapsed={collapsed.value}
118
311
  theme={theme.value}
312
+ geometryOverlay={activeEntry().kind === "component"}
119
313
  />
120
314
  ));
121
315
 
122
316
  wireCatalog(app, {
123
- onSelect: (id) => { active.value = id; },
124
- onToggleSidebar: () => { collapsed.value = !collapsed.value; },
317
+ onSelect: (id) => {
318
+ active.value = id;
319
+ },
320
+ onToggleSidebar: () => {
321
+ collapsed.value = !collapsed.value;
322
+ },
125
323
  onToggleTheme: () => {
126
- theme.value = theme.value === 'dark' ? 'light' : 'dark';
324
+ theme.value = theme.value === "dark" ? "light" : "dark";
127
325
  document.documentElement.dataset.theme = theme.value; // apply your theme however you like
128
326
  },
129
- urlParam: 'c', // mirror the active id into ?c=<id>
327
+ urlParam: "c", // mirror the active id into ?c=<id>
328
+ revealSelection: true, // reveal long desktop sidebars without moving focus
130
329
  });
330
+ wireCatalogGeometryOverlay(app);
331
+
332
+ // Optional for an initial deep link whose row may start outside the viewport.
333
+ revealCatalogEntry(app, initial, { block: "center" });
131
334
  ```
132
335
 
133
336
  ## Ownership boundary
@@ -7,7 +7,7 @@ a full shell so you can dock one panel wherever your layout needs it. They are
7
7
  subpath-only, tree-shakeable modules that add nothing to the main barrel.
8
8
 
9
9
  ```bash
10
- npm install @kerfjs/ui # kerfjs is a peer
10
+ npm install @kerfjs/ui # kerfjs is a peer
11
11
  ```
12
12
 
13
13
  Import the panel CSS (`@kerfjs/ui/collapsible-panel.css`) alongside `foundation.css`.
@@ -46,33 +46,60 @@ Import the panel CSS (`@kerfjs/ui/collapsible-panel.css`) alongside `foundation.
46
46
  ## Example
47
47
 
48
48
  ```tsx
49
- import { signal, mount } from 'kerfjs';
50
- import { deviceClass } from '@kerfjs/ui/device-class';
51
- import { CollapsiblePanel, CollapsiblePanelToggle } from '@kerfjs/ui/collapsible-panel';
52
- import { wireSidebar } from '@kerfjs/ui/wire-sidebar';
53
- import '@kerfjs/ui/collapsible-panel.css';
49
+ import { signal, mount } from "kerfjs";
50
+ import { deviceClass } from "@kerfjs/ui/device-class";
51
+ import {
52
+ CollapsiblePanel,
53
+ CollapsiblePanelToggle,
54
+ } from "@kerfjs/ui/collapsible-panel";
55
+ import { wireSidebar } from "@kerfjs/ui/wire-sidebar";
56
+ import "@kerfjs/ui/collapsible-panel.css";
54
57
 
55
58
  const navCollapsed = signal(false);
56
59
  const device = deviceClass();
57
60
 
58
- const app = document.querySelector('#app')!;
61
+ const app = document.querySelector("#app")!;
59
62
  mount(app, () => (
60
63
  <div class="layout">
61
- <CollapsiblePanel id="nav" side="left" collapsed={navCollapsed.value} label="Navigator">
64
+ <CollapsiblePanel
65
+ id="nav"
66
+ side="left"
67
+ collapsed={navCollapsed.value}
68
+ label="Navigator"
69
+ >
62
70
  <header>
63
- <CollapsiblePanelToggle side="left" collapsed={navCollapsed.value} action="toggle-nav" panelId="nav" />
71
+ <CollapsiblePanelToggle
72
+ side="left"
73
+ collapsed={navCollapsed.value}
74
+ action="toggle-nav"
75
+ panelId="nav"
76
+ />
64
77
  </header>
65
78
  {/* nav items */}
66
79
  </CollapsiblePanel>
67
80
  <main>
68
- {navCollapsed.value && <CollapsiblePanelToggle side="left" collapsed action="toggle-nav" label="Show navigator" />}
81
+ {navCollapsed.value && (
82
+ <CollapsiblePanelToggle
83
+ side="left"
84
+ collapsed
85
+ action="toggle-nav"
86
+ label="Show navigator"
87
+ />
88
+ )}
69
89
  {/* content */}
70
90
  </main>
71
91
  </div>
72
92
  ));
73
93
 
74
94
  const stop = wireSidebar(app, {
75
- panels: [{ id: 'nav', collapsed: navCollapsed, toggleAction: 'toggle-nav', storageKey: 'app.nav-collapsed' }],
95
+ panels: [
96
+ {
97
+ id: "nav",
98
+ collapsed: navCollapsed,
99
+ toggleAction: "toggle-nav",
100
+ storageKey: "app.nav-collapsed",
101
+ },
102
+ ],
76
103
  deviceClass: device,
77
104
  });
78
105
  ```
@@ -18,8 +18,124 @@ package delivery paths, Web Awesome's installed manifest, AI guidance, and
18
18
  documentation links. Nuanced prose in this contract and the selection guide
19
19
  remains authored rather than generated.
20
20
 
21
+ ### Composition catalog v2
22
+
23
+ [`component-catalog-v2.json`](../ai/component-catalog-v2.json) adds a formal,
24
+ machine-evaluable composition layer without changing the v1 selection and
25
+ delivery contract. Every v1 entry projects once, in order, under the stable
26
+ qualified key `package:id`. Each v2 entry explicitly covers parents/contexts,
27
+ named zones and cardinality, child concepts, state ownership, required wiring,
28
+ responsive ownership, layout and geometry, accessibility obligations, public
29
+ CSS boundaries, diagnostics, and provenance.
30
+
31
+ The generator starts with permissive defaults. An `any` mode records that the
32
+ catalog has no defensible prohibition; it does not claim every composition is
33
+ recommended. Objective rules live in
34
+ `component-catalog-v2-overrides.json` and may carry stable `KUI-C###`
35
+ diagnostics. A tool reports a diagnostic only after proving its exact `when`
36
+ condition. Subjective choice, product policy, and visual taste remain prose.
37
+
38
+ V1 consumers continue unchanged. V2 consumers read v1 for selection/delivery
39
+ and v2 for composition. `npm run catalog:sync` projects every new component,
40
+ recipe, and supported Web Awesome entry, and the completeness gate prevents
41
+ silent omissions. Downstream catalogs use the v2 extension schema and types,
42
+ retain their own package identity, and qualify cross-catalog references. See
43
+ the checked
44
+ [`component-catalog-extension-v2.json`](./examples/component-catalog-extension-v2.json)
45
+ application-owned example.
46
+
47
+ ### Compile-time contract boundary
48
+
49
+ The versioned
50
+ [`compile-time-contracts-v1.json`](../ai/compile-time-contracts-v1.json)
51
+ artifact maps stable `KUI-T###` ids to public imports, emitted symbols, and
52
+ catalog identities. Its positive/negative fixture compiles against both source
53
+ and a freshly packed package, so source declarations and shipped declarations
54
+ cannot silently diverge. See [Compile-time contracts](./type-contracts.md) for
55
+ the full audit, migration guidance, and the relationships deliberately left to
56
+ runtime/catalog checks because TypeScript cannot prove them.
57
+
58
+ ### Application UI profile
59
+
60
+ The component catalogs describe what packages provide; an application profile
61
+ describes which supported choices a project has approved. A workspace may
62
+ check in `.kerf-ui-profile.json` conforming to
63
+ [`application-ui-profile.schema.json`](../ai/application-ui-profile.schema.json).
64
+ Keep package-qualified catalog locations and recurring-concept preferences,
65
+ allowed color schemes and density, public semantic-token overrides,
66
+ layout/responsive conventions, and narrow rule exceptions there. Product
67
+ records, copy, permissions, user preferences, and transport state do not belong
68
+ in this policy file.
69
+
70
+ Every catalog location names a v2 composition artifact. `selection` is optional
71
+ for consumer packages whose generated metadata declares v1 selection guidance
72
+ not applicable; `@kerfjs/ui` retains its required v1 selection artifact. This
73
+ lets a generated `component-catalog-v2.json` participate directly without a
74
+ fabricated compatibility file.
75
+
76
+ Discovery and precedence are deterministic:
77
+
78
+ 1. Load `application-ui-profile.defaults.json` from `@kerfjs/ui`.
79
+ 2. Load `.kerf-ui-profile.json` at the workspace root when present.
80
+ 3. Walk from the workspace root toward the target directory and load each
81
+ directory-local profile in parent-to-child order.
82
+
83
+ Later scalar and object-map values win. Catalogs merge by package; preferences
84
+ and token overrides merge by key; exceptions merge by stable id. Lists such as
85
+ allowed themes/densities replace the earlier list rather than accumulating.
86
+ Every resolved field retains its source file. The shipped
87
+ `application-ui-profile.mjs` API implements discovery, merge, loading, and
88
+ validation and reports actionable originating file + JSON-path diagnostics for stale
89
+ catalogs, unknown component/rule/token references, preference conflicts, and
90
+ broad exceptions. Each raw layer is validated against the catalogs effective at
91
+ that exact precedence point before merge, so a broken parent catalog, stale
92
+ parent reference, invalid value, or unknown field cannot disappear merely
93
+ because a child profile replaces it. The shipped
94
+ `application-ui-profile-sync.cjs` projects the same merge and validation contract
95
+ for synchronous hosts such as ESLint rules; it deliberately performs no async
96
+ I/O.
97
+ See the
98
+ [`application-ui-profile.json`](./examples/application-ui-profile.json) example.
99
+
100
+ Catalog detail footers use one standard resource vocabulary and order. Build
101
+ them with `catalogResources()` from `@kerfjs/ui/catalog-resources`: `Demo source`
102
+ first, optional `Component source` and `Design template`, then `Guidance`.
103
+ Third-party entries use the typed `integrationGuidance` kind, which renders
104
+ `Integration guidance`, and omit `Component source` when the implementation is
105
+ not owned by the catalog's project. Prefer these semantic kinds over local labels
106
+ such as “View source” or “Read UI guidance”; the helper makes that convention a
107
+ compile-time choice while still returning ordinary `CatalogResource[]` values.
108
+
21
109
  ## Ownership boundaries
22
110
 
111
+ ### Geometry ownership metadata
112
+
113
+ The canonical [`component-catalog.json`](../ai/component-catalog.json) records
114
+ the margin, border, and padding owner for every component and composition under
115
+ `geometry`. Read it before adding a wrapper or local spacing rule:
116
+
117
+ - `self` means the entry's public visual contract supplies that geometry.
118
+ - `parent` means the embedding layout must supply it.
119
+ - `child` means composed descendants supply it; the entry's outer shell does not.
120
+ - `none` means that dimension is intentionally absent from the standard contract.
121
+ - `conditional` means a variant, part, or configuration changes ownership; the
122
+ accompanying `notes` explain the boundary.
123
+
124
+ The values describe public ownership, including themed shadow parts, rather
125
+ than merely asking whether a CSS declaration appears on the host element.
126
+ Recipes are exempt because they arrange entries whose individual ownership is
127
+ already cataloged.
128
+
129
+ Downstream packages and applications should publish the same contract for their
130
+ reusable visual components. Use
131
+ [`component-catalog-extension.schema.json`](../ai/component-catalog-extension.schema.json)
132
+ and copy the structure of the checked
133
+ [`component-catalog-extension.json`](./examples/component-catalog-extension.json)
134
+ example. Keep app and Kerf entries as package-qualified inputs, then let people
135
+ or AI tools search their combined entries and compare `geometry` before adding
136
+ wrappers or insets. Do not add app-owned entries to Kerf's canonical catalog or
137
+ claim Kerf delivery paths for them.
138
+
23
139
  - Components own semantic markup, stable anatomy, documented variants, ARIA projection, and package CSS hooks.
24
140
  - Applications own signals/stores, product copy, domain-state mapping, persistence, routing, permissions, and transport.
25
141
  - Actions are `data-action` strings. Wire them at a stable root with `delegate()` or `delegateActions()` and retain the disposer.
@@ -27,7 +143,8 @@ remains authored rather than generated.
27
143
  - Consumers style through `--kui-*` semantic tokens and public component classes. Foundation tokens provide opinionated neutral, brand/info, success, warning, and danger fill/border/foreground roles. Stateful components expose local override variables; prefer an equivalent prop or token before writing a selector.
28
144
 
29
145
  `ListItem.rootAttributes`, `ListActionRow.rootAttributes`,
30
- `ListHeader.rootAttributes`, and `AppTab.rootAttributes` accept
146
+ `ListHeader.rootAttributes`, `AppTab.rootAttributes`,
147
+ `CatalogExample.rootAttributes`, and `CatalogExampleStack.rootAttributes` accept
31
148
  application-owned `data-*` metadata without
32
149
  adding product fields to the shared API. A `ListActionRow` trailing action uses
33
150
  `trailingActionAttributes`, and a `ListHeader` action/disclosure uses
@@ -36,7 +153,7 @@ the corresponding `aria-controls`/`aria-haspopup` relationship. These slots do
36
153
  not accept roles or component-owned action, selection, disclosure, accessible
37
154
  name, disabled, or icon fields. Extension objects are filtered at runtime,
38
155
  including case-insensitive rejection of protected `data-*` names, and the
39
- component writes its protected attributes after the accepted metadata. Typed,
156
+ component or helper writes its protected attributes after the accepted metadata. Typed,
40
157
  structurally widened, and JavaScript callers therefore cannot replace its
41
158
  contract.
42
159
 
@@ -65,6 +182,11 @@ layout cannot be expressed by a prop or token. A class being public does not
65
182
  make copied component markup an invocation or transfer state and accessibility
66
183
  ownership to the application.
67
184
 
185
+ The v2 composition catalog additionally requires `boundaries.rootClass` to be
186
+ either one exact member of `publicClasses` or `null` when the entry has no
187
+ rendered class root. Runtime geometry tooling uses this explicit field; array
188
+ order never implies root ownership.
189
+
68
190
  Do not select a component's descendant by element name, id, attribute alone, or
69
191
  an unlisted implementation class. Selectors such as `.kui-state-banner span`,
70
192
  `.kui-list-item [data-state]`, and `.kui-list-item .local-label` depend on
@@ -103,9 +225,12 @@ separator with a compact grip that appears on hover or keyboard focus. Override
103
225
  `--kui-resizable-region-handle-active-color` on a region when its containing
104
226
  surface needs a different contrast level.
105
227
 
106
- `@kerfjs/ui/layout.css` defines one structural model for sidebars, main areas,
107
- inspectors, and dialogs. `.kui-pane` is unpadded and contains an optional
108
- toolbar, one scrolling `.kui-pane__content`, and an optional footer.
228
+ `Pane` defines one structural model for sidebars, main areas, inspectors, and
229
+ dialogs. Its `.kui-pane` root is unpadded and contains an optional vertical
230
+ header, one scrolling vertical `.kui-pane__content`, and an optional footer.
231
+ Logical-edge separator lines are independently opt-in and default off.
232
+ `@kerfjs/ui/layout.css` retains the pane roles and supplies the related content
233
+ geometry classes.
109
234
  `.kui-content` gives major children 24px vertical separation.
110
235
  `.kui-content-item` gives one child 8px inline margin, a real 1px border,
111
236
  8px padding, and 12px corners; border and background may be transparent without
@@ -166,14 +291,14 @@ render, hands in its own via `collapsible.signals`, drives it through
166
291
  UI is consistent by default without every app reinventing it.
167
292
 
168
293
  `wireTokenSearchFields` is a deliberate exception, not the rule for `wire…`
169
- helpers. Its collapse behavior was *rich and error-prone* — reveal, focus
294
+ helpers. Its collapse behavior was _rich and error-prone_ — reveal, focus
170
295
  transfer, Escape, empty-blur collapse, focus return — the kind of transient chrome
171
296
  apps kept reimplementing inconsistently, so the helper owns it. Everywhere else the
172
297
  app's state is **domain or persisted, not transient chrome, and stays app-owned**: a
173
298
  `NavStack`'s view stack is navigation history, a `TabBar`/`TabScaffold`'s selection
174
299
  and tab order are data, a `ResizableRegion`'s committed size and a
175
300
  `Workbench`/`SplitView` rail's `collapsed` flag are persisted layout preferences.
176
- Each helper already owns only the *ephemeral mechanics* around that state —
301
+ Each helper already owns only the _ephemeral mechanics_ around that state —
177
302
  `wireNavStack` the push/pop animation, `wireTabBars` the overflow autoscroll and
178
303
  drag preview, `wireResizableRegions` the live drag preview — and reports committed
179
304
  changes through callbacks. A `ListHeader` `toggle` disclosure's `expanded` is