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

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 (213) hide show
  1. package/README.md +108 -79
  2. package/ai/component-catalog-extension.schema.json +79 -0
  3. package/ai/component-catalog.json +499 -0
  4. package/ai/component-catalog.schema.json +15 -0
  5. package/ai/public-api-signatures-v1.md +67 -31
  6. package/ai/skill.md +58 -49
  7. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  8. package/dist/app-tab.d.ts +1 -1
  9. package/dist/app-tab.js +1 -1
  10. package/dist/catalog.d.ts +10 -3
  11. package/dist/catalog.js +279 -90
  12. package/dist/catalog.js.map +1 -1
  13. package/dist/chunk-43FUQH35.js +83 -0
  14. package/dist/chunk-43FUQH35.js.map +1 -0
  15. package/dist/{chunk-YGN7OMLT.js → chunk-4TRDV6RL.js} +2 -2
  16. package/dist/chunk-4TRDV6RL.js.map +1 -0
  17. package/dist/chunk-6ETZKKAE.js +38 -0
  18. package/dist/chunk-6ETZKKAE.js.map +1 -0
  19. package/dist/chunk-6WTETUZ6.js +21 -0
  20. package/dist/chunk-6WTETUZ6.js.map +1 -0
  21. package/dist/chunk-7L6EHUMV.js +31 -0
  22. package/dist/chunk-7L6EHUMV.js.map +1 -0
  23. package/dist/chunk-BBRXX6PF.js +108 -0
  24. package/dist/chunk-BBRXX6PF.js.map +1 -0
  25. package/dist/chunk-DPDBVKNA.js +113 -0
  26. package/dist/chunk-DPDBVKNA.js.map +1 -0
  27. package/dist/chunk-ELSJX4W7.js +68 -0
  28. package/dist/chunk-ELSJX4W7.js.map +1 -0
  29. package/dist/{chunk-6NLRBSVX.js → chunk-ENVTNLYC.js} +125 -68
  30. package/dist/chunk-ENVTNLYC.js.map +1 -0
  31. package/dist/{chunk-AHQI52QO.js → chunk-FEY65TBV.js} +6 -2
  32. package/dist/chunk-FEY65TBV.js.map +1 -0
  33. package/dist/chunk-JTHUXDN2.js +58 -0
  34. package/dist/chunk-JTHUXDN2.js.map +1 -0
  35. package/dist/chunk-LPOMRC4C.js +25 -0
  36. package/dist/chunk-LPOMRC4C.js.map +1 -0
  37. package/dist/chunk-NENBUYX5.js +79 -0
  38. package/dist/chunk-NENBUYX5.js.map +1 -0
  39. package/dist/{chunk-XXMOARTH.js → chunk-NVWXKU37.js} +57 -16
  40. package/dist/chunk-NVWXKU37.js.map +1 -0
  41. package/dist/chunk-PIDTEQ3U.js +105 -0
  42. package/dist/chunk-PIDTEQ3U.js.map +1 -0
  43. package/dist/chunk-PX3YSS5C.js +32 -0
  44. package/dist/chunk-PX3YSS5C.js.map +1 -0
  45. package/dist/chunk-PZQVSDLH.js +61 -0
  46. package/dist/chunk-PZQVSDLH.js.map +1 -0
  47. package/dist/chunk-QIRXYKCE.js +113 -0
  48. package/dist/chunk-QIRXYKCE.js.map +1 -0
  49. package/dist/chunk-QO55FN2Y.js +20 -0
  50. package/dist/chunk-QO55FN2Y.js.map +1 -0
  51. package/dist/chunk-QQFJMRIB.js +45 -0
  52. package/dist/chunk-QQFJMRIB.js.map +1 -0
  53. package/dist/chunk-S3HKPI7I.js +218 -0
  54. package/dist/chunk-S3HKPI7I.js.map +1 -0
  55. package/dist/{chunk-WXMGNX2Y.js → chunk-S3PCZ7NV.js} +2 -2
  56. package/dist/chunk-S3PCZ7NV.js.map +1 -0
  57. package/dist/{chunk-7BR3DO2A.js → chunk-SRSJO5QE.js} +13 -4
  58. package/dist/chunk-SRSJO5QE.js.map +1 -0
  59. package/dist/chunk-U5IFGQCD.js +117 -0
  60. package/dist/chunk-U5IFGQCD.js.map +1 -0
  61. package/dist/{chunk-56RJXPHG.js → chunk-Y6LYKFXY.js} +1 -1
  62. package/dist/{chunk-56RJXPHG.js.map → chunk-Y6LYKFXY.js.map} +1 -1
  63. package/dist/chunk-ZK2G3VR5.js +35 -0
  64. package/dist/chunk-ZK2G3VR5.js.map +1 -0
  65. package/dist/chunk-ZVI5YST2.js +41 -0
  66. package/dist/chunk-ZVI5YST2.js.map +1 -0
  67. package/dist/collapsible-panel.d.ts +2 -2
  68. package/dist/collapsible-panel.js +47 -6
  69. package/dist/collapsible-panel.js.map +1 -1
  70. package/dist/device-class.js +33 -5
  71. package/dist/device-class.js.map +1 -1
  72. package/dist/disclosure-arrow.js +1 -1
  73. package/dist/empty-state.d.ts +1 -1
  74. package/dist/empty-state.js +1 -1
  75. package/dist/floating-toolbar.d.ts +1 -1
  76. package/dist/floating-toolbar.js +1 -1
  77. package/dist/index.js +25 -25
  78. package/dist/list-action-row.d.ts +1 -1
  79. package/dist/list-action-row.js +1 -1
  80. package/dist/list-header.d.ts +1 -1
  81. package/dist/list-header.js +1 -1
  82. package/dist/list-inset-control.d.ts +1 -1
  83. package/dist/list-inset-control.js +1 -1
  84. package/dist/list-inset-text.d.ts +1 -1
  85. package/dist/list-inset-text.js +1 -1
  86. package/dist/list-item.d.ts +1 -1
  87. package/dist/list-item.js +1 -1
  88. package/dist/loading-spinner.js +1 -1
  89. package/dist/nav-stack.d.ts +1 -1
  90. package/dist/nav-stack.js +1 -1
  91. package/dist/panel-header.d.ts +1 -1
  92. package/dist/panel-header.js +1 -1
  93. package/dist/resizable-region.d.ts +1 -1
  94. package/dist/resizable-region.js +1 -1
  95. package/dist/segmented-control.js +1 -1
  96. package/dist/select.d.ts +1 -1
  97. package/dist/select.js +1 -1
  98. package/dist/skeleton.d.ts +1 -1
  99. package/dist/skeleton.js +1 -1
  100. package/dist/split-view.d.ts +1 -1
  101. package/dist/split-view.js +70 -10
  102. package/dist/split-view.js.map +1 -1
  103. package/dist/state-banner.d.ts +1 -1
  104. package/dist/state-banner.js +1 -1
  105. package/dist/styles/catalog.css +26 -0
  106. package/dist/tab-bar.d.ts +1 -1
  107. package/dist/tab-bar.js +1 -1
  108. package/dist/tab-scaffold.d.ts +1 -1
  109. package/dist/tab-scaffold.js +43 -8
  110. package/dist/tab-scaffold.js.map +1 -1
  111. package/dist/token-search-field.js +1 -1
  112. package/dist/toolbar-control-group.d.ts +1 -1
  113. package/dist/toolbar-control-group.js +1 -1
  114. package/dist/toolbar-text.js +1 -1
  115. package/dist/toolbar.d.ts +1 -1
  116. package/dist/toolbar.js +1 -1
  117. package/dist/value-table.d.ts +2 -2
  118. package/dist/value-table.js +1 -1
  119. package/dist/wire-catalog.d.ts +31 -2
  120. package/dist/wire-catalog.js +203 -8
  121. package/dist/wire-catalog.js.map +1 -1
  122. package/dist/wire-nav-stack.js +22 -7
  123. package/dist/wire-nav-stack.js.map +1 -1
  124. package/dist/wire-resizable-regions.d.ts +1 -1
  125. package/dist/wire-resizable-regions.js +1 -1
  126. package/dist/wire-sidebar.js +52 -41
  127. package/dist/wire-sidebar.js.map +1 -1
  128. package/dist/wire-tab-bars.js +1 -1
  129. package/dist/wire-tab-scaffold.js +9 -4
  130. package/dist/wire-tab-scaffold.js.map +1 -1
  131. package/dist/wire-token-search-fields.d.ts +1 -1
  132. package/dist/wire-token-search-fields.js +1 -1
  133. package/dist/workbench.d.ts +1 -1
  134. package/dist/workbench.js +56 -9
  135. package/dist/workbench.js.map +1 -1
  136. package/docs/app-layouts.md +7 -7
  137. package/docs/catalog.md +108 -24
  138. package/docs/collapsible-panel.md +38 -11
  139. package/docs/component-contract.md +30 -2
  140. package/docs/component-selection.md +71 -63
  141. package/docs/design/templates.md +1 -1
  142. package/docs/design-philosophy.md +5 -5
  143. package/docs/device-class.md +8 -8
  144. package/docs/examples/command-palette-adapter.tsx +67 -17
  145. package/docs/examples/component-catalog-extension.json +45 -0
  146. package/docs/layout.md +19 -19
  147. package/docs/nav-stack.md +15 -6
  148. package/docs/recipes.md +4 -4
  149. package/docs/split-view.md +3 -3
  150. package/docs/tab-scaffold.md +16 -7
  151. package/docs/webawesome-theme.md +8 -8
  152. package/docs/workbench.md +18 -5
  153. package/llms.txt +20 -14
  154. package/package.json +6 -4
  155. package/ux-demo/recipes/app-shell.tsx +206 -10
  156. package/ux-demo/recipes/collapsible-sidebar.tsx +167 -44
  157. package/ux-demo/recipes/compact-toolbar.tsx +114 -12
  158. package/ux-demo/recipes/composer-form.tsx +106 -18
  159. package/ux-demo/recipes/list-detail-dialog.tsx +144 -8
  160. package/ux-demo/recipes/list-workspace-states.tsx +165 -8
  161. package/ux-demo/recipes/loaders.ts +5 -2
  162. package/ux-demo/recipes/loading-inspector.tsx +124 -41
  163. package/ux-demo/recipes/mount-recipe.ts +27 -6
  164. package/ux-demo/recipes/navigation-sidebar.tsx +120 -9
  165. package/ux-demo/recipes/navigation-stack.tsx +61 -19
  166. package/ux-demo/recipes/types.ts +3 -1
  167. package/ux-demo/recipes/workspace-header.tsx +57 -2
  168. package/dist/chunk-2Z5TWHKQ.js +0 -34
  169. package/dist/chunk-2Z5TWHKQ.js.map +0 -1
  170. package/dist/chunk-37T3VNIZ.js +0 -19
  171. package/dist/chunk-37T3VNIZ.js.map +0 -1
  172. package/dist/chunk-55DATV3A.js +0 -10
  173. package/dist/chunk-55DATV3A.js.map +0 -1
  174. package/dist/chunk-5ATCDSQS.js +0 -42
  175. package/dist/chunk-5ATCDSQS.js.map +0 -1
  176. package/dist/chunk-5E2HVJ33.js +0 -10
  177. package/dist/chunk-5E2HVJ33.js.map +0 -1
  178. package/dist/chunk-6NLRBSVX.js.map +0 -1
  179. package/dist/chunk-7BR3DO2A.js.map +0 -1
  180. package/dist/chunk-7JB7VPRI.js +0 -34
  181. package/dist/chunk-7JB7VPRI.js.map +0 -1
  182. package/dist/chunk-7LDCTU7D.js +0 -134
  183. package/dist/chunk-7LDCTU7D.js.map +0 -1
  184. package/dist/chunk-AHQI52QO.js.map +0 -1
  185. package/dist/chunk-AQNKJRFH.js +0 -79
  186. package/dist/chunk-AQNKJRFH.js.map +0 -1
  187. package/dist/chunk-AVZHJN6C.js +0 -14
  188. package/dist/chunk-AVZHJN6C.js.map +0 -1
  189. package/dist/chunk-B24C4A6T.js +0 -22
  190. package/dist/chunk-B24C4A6T.js.map +0 -1
  191. package/dist/chunk-CH4SQ6H5.js +0 -39
  192. package/dist/chunk-CH4SQ6H5.js.map +0 -1
  193. package/dist/chunk-DAFFDO33.js +0 -14
  194. package/dist/chunk-DAFFDO33.js.map +0 -1
  195. package/dist/chunk-I7INRJO4.js +0 -17
  196. package/dist/chunk-I7INRJO4.js.map +0 -1
  197. package/dist/chunk-L4OKFDJY.js +0 -24
  198. package/dist/chunk-L4OKFDJY.js.map +0 -1
  199. package/dist/chunk-M5JRI6MN.js +0 -27
  200. package/dist/chunk-M5JRI6MN.js.map +0 -1
  201. package/dist/chunk-RNPGFQ33.js +0 -10
  202. package/dist/chunk-RNPGFQ33.js.map +0 -1
  203. package/dist/chunk-VPCTCUED.js +0 -10
  204. package/dist/chunk-VPCTCUED.js.map +0 -1
  205. package/dist/chunk-WRTAQ7BZ.js +0 -23
  206. package/dist/chunk-WRTAQ7BZ.js.map +0 -1
  207. package/dist/chunk-WVPYVDXY.js +0 -15
  208. package/dist/chunk-WVPYVDXY.js.map +0 -1
  209. package/dist/chunk-WXMGNX2Y.js.map +0 -1
  210. package/dist/chunk-XXMOARTH.js.map +0 -1
  211. package/dist/chunk-YGN7OMLT.js.map +0 -1
  212. package/dist/chunk-ZQQ6P4SV.js +0 -20
  213. 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).
@@ -47,7 +47,7 @@ instead of hand-rolled example markup, so labels, notes, and left-edge alignment
47
47
  stay consistent:
48
48
 
49
49
  ```tsx
50
- import { CatalogExample, CatalogExampleStack } from '@kerfjs/ui/catalog';
50
+ import { CatalogExample, CatalogExampleStack } from "@kerfjs/ui/catalog";
51
51
 
52
52
  const buttonPreview = (
53
53
  <CatalogExampleStack label="Button variants">
@@ -72,26 +72,99 @@ content-item/composition that already owns its geometry. The inset is published
72
72
  the `--kui-catalog-example-align` custom property so a debug overlay can exclude it
73
73
  from a specimen's measured margin.
74
74
 
75
+ ## Geometry inspection
76
+
77
+ Pass `geometryOverlay` to `Catalog` when individual component previews should
78
+ show otherwise-invisible geometry. The controlled boolean draws a dashed outer
79
+ bound around specimens with transparent backgrounds and devtools-style orange
80
+ bands over positive margins. Keep the prop present while switching entries so
81
+ `wireCatalog` can reuse one overlay layer; set it to `true` for focused component
82
+ previews and `false` for full compositions whose child geometry should remain
83
+ unmarked.
84
+
85
+ ```tsx
86
+ <Catalog
87
+ {...props}
88
+ geometryOverlay={activeEntry.kind === "component"}
89
+ content={renderers[active.value]()}
90
+ />
91
+ ```
92
+
93
+ Call `wireCatalogGeometryOverlay(root)` after the first render to synchronize the
94
+ opt-in layer across rerenders, theme changes, resizes, and scrolling, and retain
95
+ its disposer alongside `wireCatalog`'s. `CatalogExample` labels and notes are
96
+ excluded; its `align` inset is also subtracted so alignment scaffolding is not
97
+ reported as intrinsic component margin. Put
98
+ `data-catalog-geometry-overlay-skip` on a preview subtree that is intentionally
99
+ explanatory chrome rather than a specimen.
100
+
101
+ Use the overlay together with machine-readable geometry ownership metadata; the
102
+ overlay verifies what is rendered, while metadata tells people and AI tools
103
+ whether the component, its parent, or its children are responsible for margin,
104
+ border, and padding. Catalogs for downstream components should conform to the
105
+ [`component-catalog-extension.schema.json`](../ai/component-catalog-extension.schema.json)
106
+ contract and can start from the checked
107
+ [`component-catalog-extension.json`](./examples/component-catalog-extension.json)
108
+ example; provide those entries beside Kerf's shipped catalog to AI tools.
109
+
110
+ ## Selection reveal
111
+
112
+ Set `revealSelection: true` on `wireCatalog` for a long desktop sidebar. After
113
+ `onSelect` updates controlled state, the helper waits one animation frame, finds
114
+ the exact matching `data-item-id`, and scrolls it into view without changing
115
+ focus. A newer selection or disposal cancels the pending reveal. The default
116
+ media guard is the Catalog desktop layout (`min-width: 52.01rem`), so compact
117
+ layouts keep their existing scroll position.
118
+
119
+ Pass an options object instead of `true` to customize `block`, `inline`,
120
+ `behavior`, or `media`; `media: false` deliberately enables the behavior at all
121
+ sizes. For an initial deep link that did not come through `wireCatalog`, call
122
+ `revealCatalogEntry(app, initialId, { block: "center" })` after the first mount.
123
+
75
124
  ## Complete example
76
125
 
77
126
  ```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
127
+ import { mount, signal } from "kerfjs";
128
+ import { Catalog, type CatalogSection } from "@kerfjs/ui/catalog";
129
+ import {
130
+ revealCatalogEntry,
131
+ wireCatalog,
132
+ wireCatalogGeometryOverlay,
133
+ } from "@kerfjs/ui/wire-catalog";
134
+ import "@kerfjs/ui/styles.css"; // or import each primitive's CSS + @kerfjs/ui/catalog.css
82
135
 
83
136
  // 1. Describe your components once.
84
137
  const sections: CatalogSection[] = [
85
138
  {
86
- category: 'Controls',
139
+ category: "Controls",
87
140
  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' }] },
141
+ {
142
+ id: "button",
143
+ name: "Button",
144
+ description: "A pressable control.",
145
+ resources: [
146
+ {
147
+ label: "Source",
148
+ href: "/src/button.tsx",
149
+ detail: "src/button.tsx",
150
+ },
151
+ ],
152
+ },
153
+ {
154
+ id: "field",
155
+ name: "Field",
156
+ description: "A labeled input.",
157
+ tags: ["Discouraged"],
158
+ related: [{ id: "button", name: "Button", group: "Used with" }],
159
+ },
160
+ ],
161
+ },
162
+ {
163
+ category: "Feedback",
164
+ entries: [
165
+ { id: "toast", name: "Toast", description: "A transient message." },
92
166
  ],
93
167
  },
94
- { category: 'Feedback', entries: [{ id: 'toast', name: 'Toast', description: 'A transient message.' }] },
95
168
  ];
96
169
 
97
170
  // 2. One preview render per entry id.
@@ -102,32 +175,43 @@ const renderers: Record<string, () => ReturnType<typeof Button>> = {
102
175
  };
103
176
 
104
177
  // 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;
178
+ const initial =
179
+ new URLSearchParams(location.search).get("c") ?? sections[0].entries[0].id;
106
180
  const active = signal(initial);
107
181
  const collapsed = signal(false);
108
- const theme = signal<'light' | 'dark'>('light');
182
+ const theme = signal<"light" | "dark">("light");
109
183
 
110
- const app = document.getElementById('app')!;
184
+ const app = document.getElementById("app")!;
111
185
  mount(app, () => (
112
186
  <Catalog
113
- brand={{ title: 'Acme UI', subtitle: 'Design system' }}
187
+ brand={{ title: "Acme UI", subtitle: "Design system" }}
114
188
  sections={sections}
115
189
  active={active.value}
116
190
  content={renderers[active.value]?.() ?? <></>}
117
191
  collapsed={collapsed.value}
118
192
  theme={theme.value}
193
+ geometryOverlay={true}
119
194
  />
120
195
  ));
121
196
 
122
197
  wireCatalog(app, {
123
- onSelect: (id) => { active.value = id; },
124
- onToggleSidebar: () => { collapsed.value = !collapsed.value; },
198
+ onSelect: (id) => {
199
+ active.value = id;
200
+ },
201
+ onToggleSidebar: () => {
202
+ collapsed.value = !collapsed.value;
203
+ },
125
204
  onToggleTheme: () => {
126
- theme.value = theme.value === 'dark' ? 'light' : 'dark';
205
+ theme.value = theme.value === "dark" ? "light" : "dark";
127
206
  document.documentElement.dataset.theme = theme.value; // apply your theme however you like
128
207
  },
129
- urlParam: 'c', // mirror the active id into ?c=<id>
208
+ urlParam: "c", // mirror the active id into ?c=<id>
209
+ revealSelection: true, // reveal long desktop sidebars without moving focus
130
210
  });
211
+ wireCatalogGeometryOverlay(app);
212
+
213
+ // Optional for an initial deep link whose row may start outside the viewport.
214
+ revealCatalogEntry(app, initial, { block: "center" });
131
215
  ```
132
216
 
133
217
  ## 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
  ```
@@ -20,6 +20,34 @@ remains authored rather than generated.
20
20
 
21
21
  ## Ownership boundaries
22
22
 
23
+ ### Geometry ownership metadata
24
+
25
+ The canonical [`component-catalog.json`](../ai/component-catalog.json) records
26
+ the margin, border, and padding owner for every component and composition under
27
+ `geometry`. Read it before adding a wrapper or local spacing rule:
28
+
29
+ - `self` means the entry's public visual contract supplies that geometry.
30
+ - `parent` means the embedding layout must supply it.
31
+ - `child` means composed descendants supply it; the entry's outer shell does not.
32
+ - `none` means that dimension is intentionally absent from the standard contract.
33
+ - `conditional` means a variant, part, or configuration changes ownership; the
34
+ accompanying `notes` explain the boundary.
35
+
36
+ The values describe public ownership, including themed shadow parts, rather
37
+ than merely asking whether a CSS declaration appears on the host element.
38
+ Recipes are exempt because they arrange entries whose individual ownership is
39
+ already cataloged.
40
+
41
+ Downstream packages and applications should publish the same contract for their
42
+ reusable visual components. Use
43
+ [`component-catalog-extension.schema.json`](../ai/component-catalog-extension.schema.json)
44
+ and copy the structure of the checked
45
+ [`component-catalog-extension.json`](./examples/component-catalog-extension.json)
46
+ example. Keep app and Kerf entries as package-qualified inputs, then let people
47
+ or AI tools search their combined entries and compare `geometry` before adding
48
+ wrappers or insets. Do not add app-owned entries to Kerf's canonical catalog or
49
+ claim Kerf delivery paths for them.
50
+
23
51
  - Components own semantic markup, stable anatomy, documented variants, ARIA projection, and package CSS hooks.
24
52
  - Applications own signals/stores, product copy, domain-state mapping, persistence, routing, permissions, and transport.
25
53
  - Actions are `data-action` strings. Wire them at a stable root with `delegate()` or `delegateActions()` and retain the disposer.
@@ -166,14 +194,14 @@ render, hands in its own via `collapsible.signals`, drives it through
166
194
  UI is consistent by default without every app reinventing it.
167
195
 
168
196
  `wireTokenSearchFields` is a deliberate exception, not the rule for `wire…`
169
- helpers. Its collapse behavior was *rich and error-prone* — reveal, focus
197
+ helpers. Its collapse behavior was _rich and error-prone_ — reveal, focus
170
198
  transfer, Escape, empty-blur collapse, focus return — the kind of transient chrome
171
199
  apps kept reimplementing inconsistently, so the helper owns it. Everywhere else the
172
200
  app's state is **domain or persisted, not transient chrome, and stays app-owned**: a
173
201
  `NavStack`'s view stack is navigation history, a `TabBar`/`TabScaffold`'s selection
174
202
  and tab order are data, a `ResizableRegion`'s committed size and a
175
203
  `Workbench`/`SplitView` rail's `collapsed` flag are persisted layout preferences.
176
- Each helper already owns only the *ephemeral mechanics* around that state —
204
+ Each helper already owns only the _ephemeral mechanics_ around that state —
177
205
  `wireNavStack` the push/pop animation, `wireTabBars` the overflow autoscroll and
178
206
  drag preview, `wireResizableRegions` the live drag preview — and reports committed
179
207
  changes through callbacks. A `ListHeader` `toggle` disclosure's `expanded` is