@kerfjs/ui 5.0.0-beta.49 → 5.0.0-beta.51

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 (319) hide show
  1. package/README.md +69 -15
  2. package/ai/catalog-authoring.json +12 -1
  3. package/ai/catalog-authoring.schema.json +49 -1
  4. package/ai/compile-time-contracts-v1.json +19 -2
  5. package/ai/compile-time-contracts-v1.schema.json +1 -0
  6. package/ai/component-catalog-v2-overrides.json +7 -0
  7. package/ai/component-catalog-v2.d.ts +19 -1
  8. package/ai/component-catalog-v2.json +920 -149
  9. package/ai/component-catalog-v2.schema.json +33 -1
  10. package/ai/component-catalog.json +689 -153
  11. package/ai/component-catalog.schema.json +21 -1
  12. package/ai/public-api-signatures-v1.md +404 -184
  13. package/ai/skill.md +31 -14
  14. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  15. package/analyzer/index.mjs +105 -35
  16. package/dist/app-tab.d.ts +3 -1
  17. package/dist/app-tab.js +1 -1
  18. package/dist/badge.d.ts +24 -0
  19. package/dist/badge.js +3 -0
  20. package/dist/badge.js.map +1 -0
  21. package/dist/browser/badge.js +3 -0
  22. package/dist/browser/list-header.js +1 -0
  23. package/dist/browser/state-banner.js +1 -0
  24. package/dist/catalog-resources.d.ts +2 -2
  25. package/dist/catalog.d.ts +47 -125
  26. package/dist/catalog.js +429 -262
  27. package/dist/catalog.js.map +1 -1
  28. package/dist/{chunk-W2FK7ZNY.js → chunk-4R777DUZ.js} +10 -2
  29. package/dist/chunk-4R777DUZ.js.map +1 -0
  30. package/dist/{chunk-OMSRSL3B.js → chunk-5FZZMIMD.js} +6 -4
  31. package/dist/chunk-5FZZMIMD.js.map +1 -0
  32. package/dist/{chunk-AQFWBG23.js → chunk-5PMGQDG7.js} +5 -3
  33. package/dist/chunk-5PMGQDG7.js.map +1 -0
  34. package/dist/{chunk-SOK5EUF7.js → chunk-6ZXOQEAN.js} +4 -2
  35. package/dist/chunk-6ZXOQEAN.js.map +1 -0
  36. package/dist/{chunk-I4V3UDSA.js → chunk-7GUQYHKA.js} +25 -6
  37. package/dist/chunk-7GUQYHKA.js.map +1 -0
  38. package/dist/{chunk-XKFRMGMW.js → chunk-7IUSV4XG.js} +3 -1
  39. package/dist/chunk-7IUSV4XG.js.map +1 -0
  40. package/dist/{chunk-IU4QUY5I.js → chunk-AEPXGCNF.js} +20 -3
  41. package/dist/chunk-AEPXGCNF.js.map +1 -0
  42. package/dist/{chunk-VDUJZF5G.js → chunk-CMUIXJZM.js} +2 -2
  43. package/dist/{chunk-VDUJZF5G.js.map → chunk-CMUIXJZM.js.map} +1 -1
  44. package/dist/{chunk-DAO2VFTT.js → chunk-D547PH4L.js} +7 -5
  45. package/dist/chunk-D547PH4L.js.map +1 -0
  46. package/dist/{chunk-2TYUFZUC.js → chunk-D7GWXIAS.js} +4 -2
  47. package/dist/chunk-D7GWXIAS.js.map +1 -0
  48. package/dist/{chunk-E34TE2ZI.js → chunk-DVZCMMB3.js} +6 -4
  49. package/dist/chunk-DVZCMMB3.js.map +1 -0
  50. package/dist/{chunk-6NDBE2OK.js → chunk-E36EVCKG.js} +5 -3
  51. package/dist/chunk-E36EVCKG.js.map +1 -0
  52. package/dist/chunk-EKYZCRCX.js +256 -0
  53. package/dist/chunk-EKYZCRCX.js.map +1 -0
  54. package/dist/{chunk-TIGSG6BX.js → chunk-HJKRCKDR.js} +7 -3
  55. package/dist/chunk-HJKRCKDR.js.map +1 -0
  56. package/dist/{chunk-Y6LYKFXY.js → chunk-HMQ2ZDGG.js} +5 -3
  57. package/dist/chunk-HMQ2ZDGG.js.map +1 -0
  58. package/dist/{chunk-UDHHKZWO.js → chunk-JM53SMVF.js} +9 -5
  59. package/dist/chunk-JM53SMVF.js.map +1 -0
  60. package/dist/{chunk-OGTJNHRG.js → chunk-JMGKM7RK.js} +6 -4
  61. package/dist/chunk-JMGKM7RK.js.map +1 -0
  62. package/dist/{chunk-GYGM363P.js → chunk-KX7DP2GO.js} +17 -7
  63. package/dist/chunk-KX7DP2GO.js.map +1 -0
  64. package/dist/{chunk-OL4TC3U2.js → chunk-MO56ZZLG.js} +6 -2
  65. package/dist/chunk-MO56ZZLG.js.map +1 -0
  66. package/dist/{chunk-VXFGMDA7.js → chunk-MTM43SJO.js} +16 -2
  67. package/dist/chunk-MTM43SJO.js.map +1 -0
  68. package/dist/{chunk-ZPWZIVI6.js → chunk-N3GKMPDW.js} +7 -3
  69. package/dist/chunk-N3GKMPDW.js.map +1 -0
  70. package/dist/{chunk-DYBPI5O2.js → chunk-NDUGJ4TU.js} +4 -2
  71. package/dist/chunk-NDUGJ4TU.js.map +1 -0
  72. package/dist/{chunk-I45QO2IZ.js → chunk-NMGTHYW3.js} +7 -5
  73. package/dist/chunk-NMGTHYW3.js.map +1 -0
  74. package/dist/{chunk-WSXUPSID.js → chunk-NN6GYOA2.js} +7 -3
  75. package/dist/chunk-NN6GYOA2.js.map +1 -0
  76. package/dist/{chunk-OGNE3OFT.js → chunk-NZXWU4R5.js} +24 -3
  77. package/dist/chunk-NZXWU4R5.js.map +1 -0
  78. package/dist/{chunk-V6GQKOX2.js → chunk-O3RCCSQJ.js} +6 -4
  79. package/dist/chunk-O3RCCSQJ.js.map +1 -0
  80. package/dist/{chunk-WVLMZ5SG.js → chunk-S4QRSBFI.js} +25 -3
  81. package/dist/chunk-S4QRSBFI.js.map +1 -0
  82. package/dist/{chunk-CK6P5AUK.js → chunk-UBH743HV.js} +10 -7
  83. package/dist/chunk-UBH743HV.js.map +1 -0
  84. package/dist/{chunk-T7DOWS6T.js → chunk-ULCDZTWL.js} +7 -4
  85. package/dist/chunk-ULCDZTWL.js.map +1 -0
  86. package/dist/{chunk-V6HCTCVD.js → chunk-UPZ6EEJN.js} +9 -5
  87. package/dist/chunk-UPZ6EEJN.js.map +1 -0
  88. package/dist/{chunk-CAB6NQ2T.js → chunk-VOCPXGQN.js} +3 -1
  89. package/dist/chunk-VOCPXGQN.js.map +1 -0
  90. package/dist/chunk-XD7DWYXG.js +34 -0
  91. package/dist/chunk-XD7DWYXG.js.map +1 -0
  92. package/dist/{chunk-B6JYRX3P.js → chunk-ZIYOJFZD.js} +43 -4
  93. package/dist/chunk-ZIYOJFZD.js.map +1 -0
  94. package/dist/{chunk-EYALJQQD.js → chunk-ZK5EGVFP.js} +4 -2
  95. package/dist/chunk-ZK5EGVFP.js.map +1 -0
  96. package/dist/{chunk-6WTETUZ6.js → chunk-ZLWQZUGF.js} +8 -2
  97. package/dist/chunk-ZLWQZUGF.js.map +1 -0
  98. package/dist/collapsible-panel.d.ts +4 -2
  99. package/dist/collapsible-panel.js +4 -2
  100. package/dist/collapsible-panel.js.map +1 -1
  101. package/dist/css-values.d.ts +39 -3
  102. package/dist/css-values.js +1 -1
  103. package/dist/disclosure-arrow.d.ts +3 -1
  104. package/dist/disclosure-arrow.js +1 -1
  105. package/dist/empty-state.d.ts +3 -1
  106. package/dist/empty-state.js +1 -1
  107. package/dist/floating-toolbar.d.ts +3 -1
  108. package/dist/floating-toolbar.js +1 -1
  109. package/dist/grid.d.ts +17 -1
  110. package/dist/grid.js +2 -1
  111. package/dist/index.d.ts +4 -3
  112. package/dist/index.js +34 -33
  113. package/dist/list-action-row.d.ts +3 -1
  114. package/dist/list-action-row.js +1 -1
  115. package/dist/list-header.d.ts +6 -1
  116. package/dist/list-header.js +2 -1
  117. package/dist/list-inset-control.d.ts +3 -1
  118. package/dist/list-inset-control.js +1 -1
  119. package/dist/list-inset-text.d.ts +3 -1
  120. package/dist/list-inset-text.js +1 -1
  121. package/dist/list-item.d.ts +3 -1
  122. package/dist/list-item.js +1 -1
  123. package/dist/list.d.ts +23 -2
  124. package/dist/list.js +2 -1
  125. package/dist/loading-spinner.d.ts +3 -1
  126. package/dist/loading-spinner.js +1 -1
  127. package/dist/lucide-icon.d.ts +3 -1
  128. package/dist/lucide-icon.js +1 -1
  129. package/dist/nav-stack.d.ts +3 -1
  130. package/dist/nav-stack.js +1 -1
  131. package/dist/pane.d.ts +19 -2
  132. package/dist/pane.js +1 -1
  133. package/dist/resizable-region.d.ts +5 -2
  134. package/dist/resizable-region.js +1 -1
  135. package/dist/row.d.ts +21 -1
  136. package/dist/row.js +2 -1
  137. package/dist/segmented-control.d.ts +3 -1
  138. package/dist/segmented-control.js +1 -1
  139. package/dist/select.d.ts +16 -4
  140. package/dist/select.js +1 -1
  141. package/dist/skeleton.d.ts +3 -1
  142. package/dist/skeleton.js +1 -1
  143. package/dist/spacer.d.ts +3 -1
  144. package/dist/spacer.js +1 -1
  145. package/dist/split-view.d.ts +3 -1
  146. package/dist/split-view.js +24 -5
  147. package/dist/split-view.js.map +1 -1
  148. package/dist/state-banner.d.ts +3 -1
  149. package/dist/state-banner.js +2 -1
  150. package/dist/styles/badge.css +86 -0
  151. package/dist/styles/catalog/components/catalog-detail.css +35 -0
  152. package/dist/styles/catalog/components/catalog-example-stack.css +9 -0
  153. package/dist/styles/catalog/components/catalog-example.css +180 -0
  154. package/dist/styles/catalog/components/catalog-resource-footer.css +37 -0
  155. package/dist/styles/catalog/components/catalog-secondary-sections.css +38 -0
  156. package/dist/styles/catalog/components/catalog-section-list.css +22 -0
  157. package/dist/styles/catalog/components/catalog-sidebar.css +82 -0
  158. package/dist/styles/catalog/components/catalog-stage.css +60 -0
  159. package/dist/styles/catalog/components/catalog.css +22 -0
  160. package/dist/styles/catalog.css +10 -587
  161. package/dist/styles/collapsible-panel.css +132 -4
  162. package/dist/styles/document.css +23 -0
  163. package/dist/styles/foundation.css +21 -7
  164. package/dist/styles/grid.css +10 -0
  165. package/dist/styles/list-header.css +49 -60
  166. package/dist/styles/list-inset-control.css +8 -1
  167. package/dist/styles/list-item.css +6 -0
  168. package/dist/styles/list.css +11 -0
  169. package/dist/styles/nav-stack.css +89 -2
  170. package/dist/styles/pane.css +86 -1
  171. package/dist/styles/resizable-region.css +117 -5
  172. package/dist/styles/row.css +10 -0
  173. package/dist/styles/select.css +39 -7
  174. package/dist/styles/split-view.css +108 -2
  175. package/dist/styles/state-banner.css +0 -27
  176. package/dist/styles/styles.css +1 -0
  177. package/dist/styles/tab-bar.css +6 -1
  178. package/dist/styles/tab-scaffold.css +64 -1
  179. package/dist/styles/text.css +10 -0
  180. package/dist/styles/token-search-field.css +5 -40
  181. package/dist/styles/toolbar-control-group.css +198 -25
  182. package/dist/styles/toolbar-text.css +8 -1
  183. package/dist/styles/toolbar.css +169 -5
  184. package/dist/styles/webawesome.css +70 -9
  185. package/dist/styles/workbench.css +136 -4
  186. package/dist/sunken-panel.d.ts +3 -1
  187. package/dist/sunken-panel.js +1 -1
  188. package/dist/surface-scaffold.d.ts +6 -2
  189. package/dist/surface-scaffold.js +1 -1
  190. package/dist/tab-bar.d.ts +8 -1
  191. package/dist/tab-bar.js +1 -1
  192. package/dist/tab-scaffold.d.ts +3 -1
  193. package/dist/tab-scaffold.js +3 -1
  194. package/dist/tab-scaffold.js.map +1 -1
  195. package/dist/text.d.ts +9 -5
  196. package/dist/text.js +1 -1
  197. package/dist/token-search-field.d.ts +5 -1
  198. package/dist/token-search-field.js +1 -1
  199. package/dist/toolbar-control-group.d.ts +33 -3
  200. package/dist/toolbar-control-group.js +1 -1
  201. package/dist/toolbar-text.d.ts +3 -1
  202. package/dist/toolbar-text.js +1 -1
  203. package/dist/toolbar.d.ts +30 -1
  204. package/dist/toolbar.js +1 -1
  205. package/dist/types-CEsieBZi.d.ts +69 -0
  206. package/dist/value-table.d.ts +6 -2
  207. package/dist/value-table.js +1 -1
  208. package/dist/wire-resizable-regions.js +1 -1
  209. package/dist/wire-sidebar.d.ts +18 -3
  210. package/dist/wire-sidebar.js +95 -14
  211. package/dist/wire-sidebar.js.map +1 -1
  212. package/dist/wire-token-search-fields.js +1 -1
  213. package/dist/workbench.d.ts +3 -1
  214. package/dist/workbench.js +3 -1
  215. package/dist/workbench.js.map +1 -1
  216. package/docs/accessibility.md +56 -9
  217. package/docs/app-layouts.md +73 -0
  218. package/docs/catalog.md +48 -9
  219. package/docs/collapsible-panel.md +81 -8
  220. package/docs/component-contract.md +96 -28
  221. package/docs/component-integration.md +9 -4
  222. package/docs/component-selection.md +36 -15
  223. package/docs/design/templates/list/stack-dark.svg +1 -1
  224. package/docs/design/templates/list/stack.svg +1 -1
  225. package/docs/design/templates/list-dark.svg +1 -1
  226. package/docs/design/templates/list-header/action-dark.svg +1 -1
  227. package/docs/design/templates/list-header/action.svg +1 -1
  228. package/docs/design/templates/list-header/count-dark.svg +1 -1
  229. package/docs/design/templates/list-header/count.svg +1 -1
  230. package/docs/design/templates/list-header/inline-action-dark.svg +1 -1
  231. package/docs/design/templates/list-header/inline-action.svg +1 -1
  232. package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -1
  233. package/docs/design/templates/list-header/toggle-expanded.svg +1 -1
  234. package/docs/design/templates/list-header-dark.svg +1 -1
  235. package/docs/design/templates/list-header.svg +1 -1
  236. package/docs/design/templates/list.svg +1 -1
  237. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -1
  238. package/docs/design/templates/state-banner/info-badge.svg +1 -1
  239. package/docs/design/templates/state-banner-dark.svg +1 -1
  240. package/docs/design/templates/state-banner.svg +1 -1
  241. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -1
  242. package/docs/design/templates/toolbar/title-and-actions.svg +1 -1
  243. package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -1
  244. package/docs/design/templates/toolbar-control-group/with-text.svg +1 -1
  245. package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
  246. package/docs/design/templates/toolbar-control-group.svg +1 -1
  247. package/docs/design/templates/toolbar-dark.svg +1 -1
  248. package/docs/design/templates/toolbar-text/default-dark.svg +1 -1
  249. package/docs/design/templates/toolbar-text/default.svg +1 -1
  250. package/docs/design/templates/toolbar-text/large-dark.svg +1 -1
  251. package/docs/design/templates/toolbar-text/large.svg +1 -1
  252. package/docs/design/templates/toolbar-text/small-dark.svg +1 -1
  253. package/docs/design/templates/toolbar-text/small.svg +1 -1
  254. package/docs/design/templates/toolbar-text-dark.svg +1 -1
  255. package/docs/design/templates/toolbar-text.svg +1 -1
  256. package/docs/design/templates/toolbar.svg +1 -1
  257. package/docs/design-philosophy.md +6 -5
  258. package/docs/document-baseline.md +31 -0
  259. package/docs/layout.md +86 -21
  260. package/docs/nav-stack.md +8 -0
  261. package/docs/recipes.md +78 -43
  262. package/docs/split-view.md +8 -0
  263. package/docs/tab-scaffold.md +4 -2
  264. package/docs/type-contracts.md +21 -16
  265. package/docs/ui-analyzer.md +19 -0
  266. package/docs/ui-evaluator.md +17 -0
  267. package/docs/ux-demo.md +8 -8
  268. package/docs/webawesome-theme.md +25 -6
  269. package/docs/workbench.md +20 -1
  270. package/evaluator/index.mjs +73 -8
  271. package/llms.txt +61 -23
  272. package/package.json +12 -3
  273. package/ux-demo/recipes/app-shell.tsx +244 -184
  274. package/ux-demo/recipes/collapsible-sidebar.tsx +175 -116
  275. package/ux-demo/recipes/compact-toolbar.tsx +119 -102
  276. package/ux-demo/recipes/composer-form.tsx +79 -69
  277. package/ux-demo/recipes/list-detail-dialog.tsx +120 -136
  278. package/ux-demo/recipes/list-workspace-states.tsx +85 -79
  279. package/ux-demo/recipes/loaders.ts +5 -2
  280. package/ux-demo/recipes/loading-inspector.tsx +151 -134
  281. package/ux-demo/recipes/navigation-sidebar.tsx +93 -80
  282. package/ux-demo/recipes/navigation-stack.tsx +33 -22
  283. package/ux-demo/recipes/types.ts +14 -0
  284. package/ux-demo/recipes/workspace-header.tsx +73 -57
  285. package/dist/chunk-2TYUFZUC.js.map +0 -1
  286. package/dist/chunk-6NDBE2OK.js.map +0 -1
  287. package/dist/chunk-6WTETUZ6.js.map +0 -1
  288. package/dist/chunk-AQFWBG23.js.map +0 -1
  289. package/dist/chunk-B6JYRX3P.js.map +0 -1
  290. package/dist/chunk-CAB6NQ2T.js.map +0 -1
  291. package/dist/chunk-CK6P5AUK.js.map +0 -1
  292. package/dist/chunk-DAO2VFTT.js.map +0 -1
  293. package/dist/chunk-DYBPI5O2.js.map +0 -1
  294. package/dist/chunk-E34TE2ZI.js.map +0 -1
  295. package/dist/chunk-EYALJQQD.js.map +0 -1
  296. package/dist/chunk-GYGM363P.js.map +0 -1
  297. package/dist/chunk-I45QO2IZ.js.map +0 -1
  298. package/dist/chunk-I4V3UDSA.js.map +0 -1
  299. package/dist/chunk-IU4QUY5I.js.map +0 -1
  300. package/dist/chunk-OGNE3OFT.js.map +0 -1
  301. package/dist/chunk-OGTJNHRG.js.map +0 -1
  302. package/dist/chunk-OL4TC3U2.js.map +0 -1
  303. package/dist/chunk-OMSRSL3B.js.map +0 -1
  304. package/dist/chunk-SOK5EUF7.js.map +0 -1
  305. package/dist/chunk-T7DOWS6T.js.map +0 -1
  306. package/dist/chunk-TIGSG6BX.js.map +0 -1
  307. package/dist/chunk-UDHHKZWO.js.map +0 -1
  308. package/dist/chunk-V6GQKOX2.js.map +0 -1
  309. package/dist/chunk-V6HCTCVD.js.map +0 -1
  310. package/dist/chunk-VXFGMDA7.js.map +0 -1
  311. package/dist/chunk-W2FK7ZNY.js.map +0 -1
  312. package/dist/chunk-WN52USHV.js +0 -115
  313. package/dist/chunk-WN52USHV.js.map +0 -1
  314. package/dist/chunk-WSXUPSID.js.map +0 -1
  315. package/dist/chunk-WVLMZ5SG.js.map +0 -1
  316. package/dist/chunk-XKFRMGMW.js.map +0 -1
  317. package/dist/chunk-Y6LYKFXY.js.map +0 -1
  318. package/dist/chunk-ZPWZIVI6.js.map +0 -1
  319. package/ux-demo/recipes/recipes.css +0 -453
package/docs/ux-demo.md CHANGED
@@ -12,26 +12,26 @@ of the current `src/` and `ux-demo/` inputs, so a stale `dist-demo` cannot pass
12
12
  after a route or component edit. `npm run demo:serve` remains a preview-only
13
13
  command for an already-built catalog.
14
14
 
15
- Seven lazy production-composition routes live under the `Recipes` category.
15
+ Ten lazy production-composition routes live under the `Recipes` category.
16
16
  Each `?component=recipe-*` route uses public package primitives, real wiring,
17
17
  deterministic state, and semantic layout owners from the [recipe guide](./recipes.md).
18
18
 
19
19
  - It imports public component subpaths, exercising their browser-selected reachable CSS rather than a catalog-only style path.
20
20
  - The `ListActionRow` route demonstrates sibling primary/trailing native controls, independent disabled states, controlled selection, first-line leading-icon alignment for wrapped labels, and an application-owned native popover relationship without nesting controls. The `ListItem` route demonstrates the same multiline leading-icon anchor alongside its single-control navigation contract.
21
- - The `AppTab` and `TabBar` routes are focused component specimens: AppTab shows its selected, compact, segmented, icon-only, truncation, placeholder, metadata, and close-icon axes inside the required tablist context; TabBar shows its rail, segmented, inspector, allocation, placement, and accessory-zone axes without application readouts. The separate `Application tabs composition` route demonstrates controlled selection, matching panel content, overflow, adding, closing, keyboard/pointer reordering, and application-owned ordering. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
21
+ - The `AppTab` and `TabBar` routes are focused component specimens: AppTab shows its selected, compact, segmented, icon-only, truncation, placeholder, metadata, and close-icon axes inside the required tablist context; TabBar shows its rail, segmented, inspector, allocation, placement, and accessory-zone axes, including an adjacent action plus a separately pinned end action whose tabs overflow at narrow widths, without application readouts. The separate `Application tabs` composition route demonstrates controlled selection, matching panel content, overflow, adding, closing, keyboard/pointer reordering, and application-owned ordering. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
22
22
  - Every first-party visual component and every free Web Awesome 3.12 component has its own stable `?component=<id>` URL and focused demo. Composite header, menu, and feedback scenarios remain addressable alongside their subcomponents; the root opens the first Kerf component. The former aggregate Web Awesome theme specimen is intentionally omitted because its component families are covered by those focused routes.
23
- - `ai/component-catalog.json` is the canonical catalog metadata. A deterministic checked-in projection supplies the demo's unique id, category, kind (`component`, `composition`, or `recipe`), source (`kerf` or `webawesome`), name, description, recommendation, direct `uses` dependencies, existing documentation path, and demo-source path; first-party, recipe, and Web Awesome renderer maps are exhaustively typed against its generated id unions. Non-supported Web Awesome recommendations project to a visible `Discouraged` sidebar tag, with Popup as the deliberate conditional exception. Demo paths resolve to the matching `ui/ux-demo/demos/<id>.tsx` or `ui/ux-demo/recipes/*.tsx` file, or to `ui/ux-demo/webawesome-demos.tsx` for Web Awesome entries. `ui/ux-demo/main.tsx` owns only the catalog shell, shared state, routing, and interaction wiring. First-party component entries additionally derive `ui/src/<public-subpath>.tsx` from their canonical `delivery.browserImport` or opt-in `delivery.moduleImport`.
23
+ - `ai/component-catalog.json` is the canonical catalog metadata. A deterministic checked-in projection supplies the demo's unique id, category, kind (`component`, `composition`, or `recipe`), source (`kerf` or `webawesome`), name, description, recommendation, direct `uses` dependencies, existing documentation path, and demo-source path; first-party, recipe, and Web Awesome renderer maps are exhaustively typed against its generated id unions. Functional groups follow their authored importance order; components precede compositions inside each group, equal-importance entries use alphabetical order, and the Recipes group comes last. Composition names stay concise and project a visible `Composition` sidebar tag. Non-supported Web Awesome recommendations project to a visible `Discouraged` tag, with Popup as the deliberate conditional exception. Demo paths resolve to the matching `ui/ux-demo/demos/<id>.tsx` or `ui/ux-demo/recipes/*.tsx` file, or to `ui/ux-demo/webawesome-demos.tsx` for Web Awesome entries. `ui/ux-demo/main.tsx` owns only the catalog shell, shared state, routing, and interaction wiring. First-party component entries additionally derive `ui/src/<public-subpath>.tsx` from their canonical `delivery.browserImport` or opt-in `delivery.moduleImport`.
24
24
  - Every detail footer groups compact `Demo source` and `Guidance` actions; first-party component details also expose `Component source`. Web Awesome details say `Integration guidance` to distinguish the local integration contract from the upstream component implementation. Each button retains its repository-relative path in the DOM for verification and uses a deploy-safe absolute GitHub blob URL with a contextual new-tab name; the demo does not depend on a local source server or add a package runtime export. At phone widths, the resource group and related-component selector stack as full-width rows, with any resource overflow contained locally so labels cannot overlap the selector or widen the document.
25
25
  - The master/detail shell groups first-party routes by category with production `ListHeader` and `ListItem` components. Catalog entries can supply short reusable trailing tags; the Web Awesome projection uses them to label exactly the 15 discouraged choices. The 70 ecosystem components live in a separate `Web Awesome` disclosure with seven subgroups, so they remain discoverable without being presented as first-class `@kerfjs/ui` exports. Direct Web Awesome routes open the disclosure and reveal the selected row at wide sizes; narrow layouts keep the expanded list in document flow rather than introducing a competing nested scroller.
26
- - The shell itself uses `@kerfjs/ui/layout.css`: sidebars, details, previews, dialogs, and resize specimens share unpadded panes plus self-contained content items. Its sidebar identity uses the Kerf logo beside a vertically centered title, with supporting copy in its own aligned row; the current-view heading is also a toolbar. Collapsing the catalog removes the pane completely and moves its restore action to the main toolbar's leading edge instead of leaving an icon-only rail. The selected view description remains inset below the page toolbar. The checkerboard belongs to the complete scrollable preview region rather than a nested card. A lightly separated footer keeps live status above compact source, guidance, and relationship groups. The focused `ResizableRegion` specimen stretches its surface through the available stage height and reports committed width in that shared status footer; narrow layouts retain local horizontal access when the pane is resized wider than the stage, and the split remains readable at 200% root scaling. Geometry coverage checks wide, intermediate, narrow, and 200% zoom layouts.
26
+ - The shell itself uses `@kerfjs/ui/layout.css`: sidebars, details, previews, dialogs, and resize specimens share unpadded panes plus self-contained content items. Its sidebar identity uses the Kerf logo beside a vertically centered title, with supporting copy in its own aligned row; the current-view heading is also a toolbar. Collapsing the catalog removes the pane completely and moves its restore action to the main toolbar's leading edge instead of leaving an icon-only rail. The selected view description remains inset below the page toolbar. The checkerboard belongs to the preview scroll owner rather than a viewport-height stage or nested card, and one explicitly repeated SVG tile keeps the pattern continuous through below-fold content. A lightly separated footer keeps live status above compact source, guidance, and relationship groups. The focused `ResizableRegion` specimen stretches its surface through the available stage height and reports committed width in that shared status footer; narrow layouts retain local horizontal access when the pane is resized wider than the stage, and the split remains readable at 200% root scaling. Geometry coverage checks wide, intermediate, narrow, and 200% zoom layouts.
27
27
  - The composer-form recipe demonstrates one coherent visible form surface with toolbar title/supporting-copy hierarchy, fields and actions on the shared 8px control gutter, and 24px major rhythm. Its conditional error or success `StateBanner` is the only nested semantic surface. Reset synchronizes the upgraded Web Awesome fields' live value properties with their empty controlled attributes and announces `Draft reset`; browser coverage preserves behavior and verifies wide, narrow, dark, validation, success, reset, 200% zoom, and forced-colors states.
28
- - The list-detail dialog recipe places its header directly on the dialog edge while preserving the header's internal inset, gives the selected project title the complete outer-plus-inner content gutter, lets the value table fill the available detail column between the shared outer margins, and keeps record actions on one outer gutter without an extra content-item inset. Browser geometry coverage protects those relationships at wide and narrow widths.
28
+ - The list-detail dialog recipe composes `DialogSurface`, the dialog's own labeled header and close control, `SplitView`, and footer actions. Its `SplitView` sits flush in the dialog body, both panes open with a `ListHeader` on one shared line, the dialog title and header text share the 17px text edge, the value table keeps the 8px outer margin, and the trailing actions keep the 16px footer gutter. Compact devices get a full-screen sheet with a list-to-detail drill-down. Browser geometry coverage protects those relationships at wide and narrow widths.
29
29
  - Catalog-only brand marks, preview dimensions, checkerboard tiles, and the compound display-settings control retain local geometry because they are specimen chrome or intrinsic control anatomy rather than reusable application spacing roles.
30
30
  - The menu composition route demonstrates toolbar/content/footer anatomy, 24px major gaps, 8/1/8 child geometry, the full-width `ListHeader` count-or-badge/action split, logical-end 44px action targets with 18px visuals, and 44px row/header/toolbar targets. Its one chevron is a production 18px `DisclosureArrow` inside a controlled Tools header that actually reveals and hides content; ordinary Projects navigation has no false disclosure affordance. Browser assertions cover stable naming, expanded/content transitions, visual rotation, node identity, reduced motion, RTL alignment and containment, and wide, narrow, dark, and 200% layouts. The focused ListHeader route separately shows the shared neutral count pill for Attachments, Notes (including zero), and Duplicates plus a non-count legacy badge, full-width logical-end actions, and no disclosure without content.
31
31
  - The `DisclosureArrow` route renders the production 18px root-scaled default without a catalog-only size override. Its two native buttons independently control the default right-to-down chevron and a recognizable right-facing replacement arrow with left-to-up custom directions; both use shortest-path rotation and their names stay stable while `aria-expanded` communicates state. Browser coverage verifies the custom clockwise path, pointer and native keyboard activation, focus, narrow and 200%-zoom stacking with contained labels, consumer sizing, and the independent `.5` Web Awesome glyph scale retained by Kerf `Select`.
32
32
  - A detail with relationships renders one content-fitted `Components` footer selector: `Uses` links to direct dependencies and `Used by` links to reverse dependents. Its popup owns the standard surface inset so headings and even long component names stay clear of the menu border. Choosing either navigates to that component's demo. The relationship group is absent when both sets are empty; no duplicate summary is rendered.
33
- - Demo-only CSS supplies only the catalog shell and stages; component appearance stays in package CSS. Decorative chrome and Web Awesome controls share the same semantic system palette instead of carrying a separate demo identity.
34
- - Scenarios use deterministic data and cover real variants, long content, disabled state, selection, progress, feedback, overlays, media, helpers, and stateful interactions. Individual Web Awesome action, form, layout, navigation, feedback, media, and helper routes provide focused theme specimens without a redundant aggregate route; ListItem and ListHeader demonstrate typed domain metadata, protected native button/disclosure semantics, and a native popover trigger relationship without product props; the Headers composition demonstrates direct toolbar headings with an extra-large title, optional supporting copy, and grouped icon/actions; the Select route verifies that option icons survive unrelated Kerf rerenders and selected slot content follows value changes; the Animation route exposes preset, easing, duration, playback-rate, and transport controls with live lifecycle status; observer routes provide user-driven intersection, mutation, and resize events with live results; ToolbarControlGroup exposes all eight Hot Sheet 2 variants, loads the Web Awesome registrations needed by its popup specimen, and preserves the upgraded dropdown's managed light-DOM children across Kerf rerenders; the Toolbar route composes a collapsible TokenSearchField inside a group, animates between the empty iconic and complete field states, stays open while populated, and focuses the revealed editor; SegmentedControl covers toolbar, rounded-rectangle, pill, equal-width, and disabled presentations; TokenSearchField covers editable ordered chips, pointer and keyboard removal, caret-preserving controlled deletion, editing, clearing, free text, disabled state, and fixed first-line alignment while content wraps; StateBanner exposes every built-in tone with tone-tinted badges plus a scoped override; the Application tabs composition demonstrates overflow, edge autoscroll, and controlled pointer/keyboard reordering.
33
+ - Focused demos ship no local stylesheets, inline styles, or styling-only class names. They compose Kerf components first, Web Awesome surfaces where Kerf has no equivalent, and ordinary semantic HTML for the remainder. The catalog-owned `CatalogExample.viewport` contract supplies the few specimen constraints needed to demonstrate layout behavior. Recipes follow the same rule: they ship no stylesheets, inline styles, or styling-only classes, and the catalog frames each one in the `CatalogExample` viewport its exported `presentation` declares (full-width application arrangements span the canvas measure; narrower recipes sit in the centered example column). Demo-only CSS is limited to the document-shell `style.css`, which carries no recipe rules. Decorative chrome and Web Awesome controls share the same semantic system palette instead of carrying a separate demo identity.
34
+ - Scenarios use deterministic data and cover real variants, long content, disabled state, selection, progress, feedback, overlays, media, helpers, and stateful interactions. Individual Web Awesome action, form, layout, navigation, feedback, media, and helper routes provide focused theme specimens without a redundant aggregate route; ListItem and ListHeader demonstrate typed domain metadata, protected native button/disclosure semantics, and a native popover trigger relationship without product props; the Headers composition demonstrates direct toolbar headings with an extra-large title, optional supporting copy, and grouped icon/actions; the Select route verifies that option icons survive unrelated Kerf rerenders and selected slot content follows value changes; the Animation route exposes preset, easing, duration, playback-rate, and transport controls with live lifecycle status; observer routes provide user-driven intersection, mutation, and resize events with live results; ToolbarControlGroup exposes all eight Hot Sheet 2 variants, loads the Web Awesome registrations needed by its popup specimen, and preserves the upgraded dropdown's managed light-DOM children across Kerf rerenders; the Toolbar route composes a collapsible TokenSearchField inside a group, animates between the empty iconic and complete field states, stays open while populated, and focuses the revealed editor; SegmentedControl covers toolbar, rounded-rectangle, pill, equal-width, and disabled presentations; TokenSearchField covers editable ordered chips, pointer and keyboard removal, caret-preserving controlled deletion, editing, clearing, free text, disabled state, and fixed first-line alignment while content wraps; StateBanner exposes every built-in tone with tone-tinted badges plus a scoped override; the Application tabs composition uses a Web Awesome card with semantic header/footer slots and Kerf `Text` while demonstrating overflow, edge autoscroll, and controlled pointer/keyboard reordering.
35
35
  - The ToolbarControlGroup route also demonstrates the public compact/tight,
36
36
  mixed-content, filled-selection, nested-dropdown, and avatar/scrim contracts
37
37
  added for consumers that previously restyled group descendants. The compact
@@ -42,7 +42,7 @@ deterministic state, and semantic layout owners from the [recipe guide](./recipe
42
42
  paints only the pressed selection, with contain fitting in both cases. Its
43
43
  collapsible-search specimen follows the selected group shape with a
44
44
  concentric inner radius in both collapsed and expanded states.
45
- - Recipe ownership notes are hidden by default. A page-toolbar toggle reveals them as floating warning-toned notes with a folded corner, keeping instructional copy visually distinct and removable from the specimen flow.
45
+ - Recipe ownership notes are hidden by default. A page-toolbar toggle reveals each recipe's note as its `CatalogExample` note above the framed specimen, so instructional copy never covers the composition.
46
46
  - Settings exercise light/dark, increased contrast, and reduced motion without changing component code. The theme action starts from the operating system's effective color scheme, always names the appearance it will switch to, and installs an explicit light or dark override when activated; contrast and reduced motion expose their independent preferences through `aria-pressed`.
47
47
  - The footer action log is an `aria-live` region, making interactions visible to both people and browser tests.
48
48
  - The focused Toast route calls Web Awesome's documented programmatic stack API and notes that Hot Sheet 2 still uses its own app-level toast implementation; the catalog demonstrates ecosystem support without implying current product adoption.
@@ -54,12 +54,30 @@ pointer targets. Override `--kui-wa-carousel-icon-size`,
54
54
  `--kui-wa-carousel-dot-hit-size` on a carousel or containing scope when a
55
55
  product needs different geometry.
56
56
 
57
+ Code text uses Kerf's named monospace stack, `--kui-font-mono`
58
+ (`ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono",
59
+ monospace`), and the theme points `--wa-font-family-code` at it. Web Awesome's
60
+ own `ui-monospace, monospace` renders the generic face (Courier on macOS) in
61
+ Chromium, which does not recognize `ui-monospace`; because the generic
62
+ `monospace` ends any stack, Kerf's token owns the stack rather than appending
63
+ fallbacks after Web Awesome's. Override `--kui-font-mono` to change code text in
64
+ both Kerf and Web Awesome components.
65
+
57
66
  Form fields carry the same content-item inset as the Kerf primitives: a 1px
58
67
  border with 8px inside it (`--wa-form-control-border-width`,
59
68
  `--wa-form-control-padding-block`, and `--wa-form-control-padding-inline` are set
60
69
  to the `--kui-layout-item-border-width` / `--kui-layout-item-padding` values), so
61
- a single-line control lands at the standard ~40px height and the value sits 9px
62
- in from the field edge. Each field's top label is inset by that same border +
70
+ the value sits 9px in from the field edge. Default-size single-line controls
71
+ share Kerf's 44px control height — the height of a Kerf `Select`, a toolbar
72
+ group, and a list row — so a `wa-input` or `wa-button` sits flush beside a Kerf
73
+ `Select`. The theme sets `--wa-form-control-height` to `2.75em` (44px at the
74
+ 16px default) on Button, Color Picker, Input, Known Date, Number Input, Radio
75
+ Group, Select, Textarea (its minimum height), and Time Input, so `size="large"`
76
+ scales proportionally while `size="small"` keeps Web Awesome's compact secondary
77
+ height. The token is scoped to those controls rather than the root because Web
78
+ Awesome also derives tag, copy-button, and toast geometry from it. Buttons
79
+ inside a `ToolbarControlGroup` keep the group's 40px (32px compact) control
80
+ height. Each field's top label is inset by that same border +
63
81
  padding (9px) so it lines up with the value inside the field, and is styled
64
82
  exactly like a `ListHeader` label — uppercase, `--kui-font-xs`, weight 650, quiet
65
83
  foreground. This applies to every free field that exposes a top label
@@ -97,10 +115,11 @@ tiers:
97
115
  complete surface and the inset within it. Accordion applies the outer margin
98
116
  once to the group rather than separating connected items; Card's header,
99
117
  body, and footer all receive the full inset.
100
- - Plain Accordion and Details remove inline padding from both their trigger and
101
- content so their text aligns directly with the surrounding flow. Outlined and
102
- sunken disclosure triggers use the 16px `--kui-wa-container-inset`, one 8px
103
- step beyond their compact body inset.
118
+ - Plain Details use the 8px `--kui-wa-surface-inset` on the trigger and keep the
119
+ body flush; plain Accordion items use that 8px inset on both trigger and body.
120
+ Outlined and sunken Details use the 16px `--kui-wa-container-inset` on the
121
+ trigger and the 8px surface inset on the body, while framed Accordion items
122
+ use the 16px container inset for both regions.
104
123
  - `appearance="sunken"` is a Kerf theme extension for Accordion, Card, and
105
124
  Details. It removes raised chrome, paints the lowered semantic surface, and
106
125
  uses the shared rounded-rectangle radius. Override
package/docs/workbench.md CHANGED
@@ -12,6 +12,12 @@ import { Workbench } from "@kerfjs/ui/workbench";
12
12
  import "@kerfjs/ui/workbench.css";
13
13
  ```
14
14
 
15
+ A top-level `Workbench` needs a definite containing height. Import the opt-in
16
+ `@kerfjs/ui/document.css` baseline and add `.kui-app-root` to the direct mount
17
+ container, or provide an equivalent definite height in application-owned
18
+ layout. The component intentionally uses `height: 100%` rather than `100dvh` so
19
+ it can also be embedded. See [Document baseline](document-baseline.md).
20
+
15
21
  ## State lives in the app
16
22
 
17
23
  `Workbench` is declarative and the collapse is **pure CSS** — no wire. The app
@@ -90,7 +96,10 @@ with state-derived props:
90
96
  ```
91
97
 
92
98
  `wireResizableRegions` marks the active region with `data-resizing` and the
93
- package CSS suppresses content motion during pointer resize. A collapsed,
99
+ package CSS suppresses content motion during pointer resize. It applies the
100
+ live size to both the track and the slide-motion content, so content fills the
101
+ region throughout a drag, including any safe-area edge extent, and a later
102
+ collapse slides it out at the resized width. A collapsed,
94
103
  overlay, or hidden region is not resizeable. Together these policies replace
95
104
  app CSS for separator suppression, instant-track/composited-content collapse,
96
105
  popup overflow, mobile overlay/hidden replacement, resize-transition guards,
@@ -110,6 +119,16 @@ must classify public application selectors. Prefer the component props and two
110
119
  size tokens before selecting internal anatomy, and do not target its data
111
120
  attributes or descendant tags as styling contracts.
112
121
 
122
+ ## Safe areas
123
+
124
+ Each rail and the drawer grows by the unsafe inset of the screen edge it docks
125
+ to, so its surface and separator paint through while its content keeps the
126
+ configured size and is padded for the edges it touches. The main area pads for
127
+ the edges it reaches, inside its scroller: an expanded inline rail or drawer
128
+ takes that edge away, and collapsing it (or presenting it as an overlay) hands
129
+ the edge back. A region whose only child is a `Pane` or layout lets that child
130
+ own the insets. See [Choosing an app layout › Safe areas](app-layouts.md#safe-areas).
131
+
113
132
  ## How the collapse animates
114
133
 
115
134
  Collapsing snaps the panel's flex track to zero in a single reflow (so the work
@@ -336,6 +336,50 @@ async function inspectPage(page, geometryContracts) {
336
336
  return null;
337
337
  };
338
338
 
339
+ const hitExtent = (element, rect) => {
340
+ const root = element.getRootNode();
341
+ const probe = root.elementFromPoint ? root : document;
342
+ const inViewport = (x, y) =>
343
+ x >= 0 && y >= 0 && x < innerWidth && y < innerHeight;
344
+ const owns = (x, y) => {
345
+ const hit = probe.elementFromPoint(x, y);
346
+ return !!hit && (hit === element || element.contains(hit));
347
+ };
348
+ const x = rect.left + rect.width / 2;
349
+ const y = rect.top + rect.height / 2;
350
+ if (!inViewport(x, y) || !owns(x, y)) return null;
351
+ // Walk out in 1px steps (no further than the 44px goal needs), then
352
+ // refine the last step in quarter pixels; the reach is the last owned
353
+ // sample, so it never overstates the target. A walk stopped by
354
+ // the viewport edge cannot see the rest of the layer, so it is marked
355
+ // and mirrored from the opposite side (hit layers are symmetric insets).
356
+ const reach = (dx, dy) => {
357
+ const at = (distance) => [x + dx * distance, y + dy * distance];
358
+ let owned = 0;
359
+ while (owned < 24) {
360
+ const point = at(owned + 1);
361
+ if (!inViewport(...point)) return { distance: owned, clipped: true };
362
+ if (!owns(...point)) break;
363
+ owned += 1;
364
+ }
365
+ if (owned >= 24) return { distance: owned, clipped: false };
366
+ while (owns(...at(owned + 0.25))) owned += 0.25;
367
+ return { distance: owned, clipped: false };
368
+ };
369
+ const span = (first, second) =>
370
+ first.clipped && second.clipped
371
+ ? first.distance + second.distance
372
+ : first.clipped
373
+ ? 2 * second.distance
374
+ : second.clipped
375
+ ? 2 * first.distance
376
+ : first.distance + second.distance;
377
+ return {
378
+ width: span(reach(-1, 0), reach(1, 0)),
379
+ height: span(reach(0, -1), reach(0, 1)),
380
+ };
381
+ };
382
+
339
383
  for (const element of interactives) {
340
384
  const target = selector(element);
341
385
  const rect = element.getBoundingClientRect();
@@ -495,14 +539,35 @@ async function inspectPage(page, geometryContracts) {
495
539
  'button,input:not([type="hidden"]),select,textarea,[role]',
496
540
  ) &&
497
541
  (rect.width < 43.5 || rect.height < 43.5)
498
- )
499
- diagnostics.push({
500
- code: 'KUI-B050',
501
- selector: target,
502
- message: `Hit target is ${rect.width.toFixed(1)}×${rect.height.toFixed(1)}px; expected at least 44×44px.`,
503
- repair: repair.target,
504
- evidence: { width: rect.width, height: rect.height },
505
- });
542
+ ) {
543
+ // The border box understates a control whose pointer target is
544
+ // extended by a transparent hit layer (a positioned ::before/::after
545
+ // that receives pointer events). Hit-test outward from the center to
546
+ // measure the area the pointer can actually reach; a probe that finds
547
+ // no hit (off-screen, covered center) keeps the border box.
548
+ const hit = hitExtent(element, rect);
549
+ const width = Math.max(rect.width, hit?.width ?? 0);
550
+ const height = Math.max(rect.height, hit?.height ?? 0);
551
+ // Compact density is a deliberate trade-off: its rows are 36px tall,
552
+ // and a taller hit layer would reach into the neighboring row. Inside
553
+ // `[data-density="compact"]` a target still needs the full 44px width
554
+ // but only the 36px compact row height.
555
+ const compact = element.closest('[data-density="compact"]') !== null;
556
+ const requiredHeight = compact ? 36 : 44;
557
+ if (width < 43.5 || height < requiredHeight - 0.5)
558
+ diagnostics.push({
559
+ code: 'KUI-B050',
560
+ selector: target,
561
+ message: `Hit target is ${width.toFixed(1)}×${height.toFixed(1)}px; expected at least 44×${requiredHeight}px${compact ? ' (compact density)' : ''}.`,
562
+ repair: repair.target,
563
+ evidence: {
564
+ width,
565
+ height,
566
+ box: { width: rect.width, height: rect.height },
567
+ required: { width: 44, height: requiredHeight },
568
+ },
569
+ });
570
+ }
506
571
  }
507
572
 
508
573
  const parse = (value) => {
package/llms.txt CHANGED
@@ -12,6 +12,10 @@ npm install kerfjs @kerfjs/ui # kerfjs is a peer; Web Awesome is an optional pee
12
12
  Import each component from its own subpath (`@kerfjs/ui/list-item`, `@kerfjs/ui/toolbar`,
13
13
  …); the browser build pulls that component's reachable CSS automatically, so do not
14
14
  maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
15
+ For a public subpath containing several visual components, use a folder with one
16
+ component per source file and one owned stylesheet per visual component. Keep its
17
+ compatibility entrypoints implementation-free: JavaScript/TypeScript only re-exports
18
+ the folder modules, and CSS only imports their owner stylesheets.
15
19
 
16
20
  ## How to build a screen (retrieval order)
17
21
 
@@ -30,7 +34,9 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
30
34
  when app-owned components must participate in selection or alignment.
31
35
  Load the [Composition catalog v2](./ai/component-catalog-v2.json) to
32
36
  mechanically evaluate parents, contexts, zones/cardinality, children, state,
33
- wiring, responsive/layout/accessibility rules, and public boundaries. V1
37
+ wiring (including the wiring-owned `stateAttributes` an app must never
38
+ render or remove), responsive/layout/accessibility rules, and public
39
+ boundaries. V1
34
40
  remains compatible; v2 joins catalogs by `package:id`. Consumer v2 catalogs
35
41
  use [the v2 extension schema](./ai/component-catalog-extension-v2.schema.json).
36
42
  Load [compile-time contracts](./ai/compile-time-contracts-v1.json) for stable
@@ -56,9 +62,19 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
56
62
  `ListInsetText`. These are typed props, not native `<slot>` elements,
57
63
  wrapper slot components, or a generic `slots` object. Leave unresolved
58
64
  dynamic expressions unknown.
65
+ Stable single-root Kerf UI components separately accept the explicit native
66
+ `slot="name"` prop for light-DOM composition inside a custom element and
67
+ preserve it across rerenders. Do not infer arbitrary native-attribute
68
+ spreading from this narrow interop contract. `ResizableRegion` and
69
+ `CollapsiblePanel` remain excluded because their optional restore controls
70
+ make their output multi-root; `CollapsiblePanelToggle` remains slot-capable.
59
71
  4. **When authoring a component gallery**, load
60
72
  [catalog-authoring.json](./ai/catalog-authoring.json), then follow its
61
73
  authoritative [Catalog demo authoring contract](./docs/catalog.md#catalog-demo-authoring-contract).
74
+ Use its component/composition/recipe taxonomy and functional-group,
75
+ importance, alphabetical-tie, and recipes-last ordering policy. Focused
76
+ demos and recipes use public components plus `CatalogExample.viewport`,
77
+ never local stylesheets, inline styles, or styling-only classes.
62
78
  5. **Apply the rules below and in** [skill.md](./ai/skill.md).
63
79
  6. **Before calling an integration complete**, run
64
80
  [`kerf-ui-doctor`](./docs/ui-doctor.md). Its portable JSON report merges
@@ -81,16 +97,21 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
81
97
  ## Core rules (do the right thing)
82
98
 
83
99
  - **Reach for the primitive, not for CSS.** The package is built to look right
84
- _unstyled_. Before adding `padding`, `margin`, `width`, `height`, `border`,
85
- `background`, a wrapper card, or a decoration, check whether the component, the
86
- pane, or the content-item already owns it — it almost always does. Prefer a
87
- component prop or a `--kui-*` token; write CSS only for genuinely new structure,
88
- and then only join a component's documented `publicClasses` (never reach in by
89
- tag/id/attribute or an unlisted class).
100
+ without an application customization layer. Before adding `padding`, `margin`,
101
+ `width`, `height`, `border`, `background`, a wrapper card, or a decoration,
102
+ check whether the component, pane, or content-item already owns it. Prefer a
103
+ component prop or documented `--kui-*` token. CSS for genuinely new structure
104
+ belongs to the application-owned component that renders it and must not select
105
+ nested Kerf or Web Awesome components.
90
106
  - **Never double-inset.** A `.kui-pane` has no padding; its `.kui-content` children
91
107
  own the geometry (8px inline margin + 1px border + 8px padding). Do not add a
92
108
  container padding on top of a content-item's own margin — pick one owner. Use
93
109
  `.kui-content` for 24px major vertical rhythm.
110
+ - **Opt into document globals explicitly.** Import `@kerfjs/ui/document.css`
111
+ once for border-box sizing, token-backed body/link presentation, and zero body
112
+ margin. Put `.kui-app-root` on the one direct mount container only when a
113
+ percentage-height shell needs the provided `html` → `body` → app-root height
114
+ chain. The baseline is not part of `styles.css` or component browser imports.
94
115
  - **Use `Row` for horizontal and `List` for vertical layout.** Both expose
95
116
  physical `hAlign`/`vAlign` props and typed gaps. Row defaults to left/full,
96
117
  an `xs` gap, and no wrapping; List retains full/top and zero-gap defaults.
@@ -105,19 +126,26 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
105
126
  shorthand (`gap="xs"`, `gap="m"`) or a complete `CssLength` from the CSS-free
106
127
  `@kerfjs/ui/css-values` builders; raw CSS strings are rejected. Use `flex()`
107
128
  for List flex, typed lengths for Skeleton geometry, and
108
- `uiColor()`/`colorVar()` for choice-icon colors. These brands are intentionally
129
+ foreground colors for choice icons: `uiColor()` with an `*-on-*`/text token
130
+ or `foregroundColorVar()`; fill aliases such as `uiColor('success')` do not
131
+ type-check. These brands are intentionally
109
132
  not interchangeable; rows use classes/tokens/props rather than raw `style`.
110
133
  - **Use `Grid` for a fixed positive count of equal-width columns.** It uses
111
134
  zero-minimum fractional tracks, so intrinsic content cannot make one column
112
135
  wider than its peers. The app owns responsive count changes; use custom CSS
113
136
  grid for asymmetric/intrinsic tracks and `ResizableRegion` for adjustable
114
137
  boundaries. `Grid.gap` and `Grid.flex` use the same typed grammars as Row.
138
+ - **Give a layout root `fill`, not a frame `Pane`.** A Row, List, or Grid that
139
+ is the sole layout root of a definite-height parent takes its full height
140
+ with `fill` and carries safe `data-*` markers through `rootAttributes`.
141
+ Inside a flex layout use `flex` instead.
115
142
  - **Keep Web Awesome on the shared inset tiers.** `webawesome.css` uses
116
143
  `--kui-wa-control-inset` (8px controls/items) and
117
144
  `--kui-wa-surface-margin` / `--kui-wa-surface-inset` (8px around/inside
118
145
  Accordion, Card, Details, Callout, and Include), while
119
- plain Accordion/Details remove inline trigger/body padding and their
120
- outlined/sunken headers use the 16px container inset,
146
+ plain Details keep their body flush while plain Accordion items inset both
147
+ trigger and body, framed Details use the 16px container inset only on the
148
+ trigger, and framed Accordion items use it on both regions;
121
149
  `--kui-wa-container-inset` keeps Tab Panel at 16px. Dialog body and footer use
122
150
  the surface (8px) and container (16px) tiers respectively, independent of the
123
151
  dialog's shared `--spacing`. Override the tier at the narrowest useful scope
@@ -160,22 +188,25 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
160
188
  need a Web Awesome feature, chiefly the `slot="trigger"` button of a `wa-dropdown`
161
189
  popup menu. A toolbar popup menu is a `single` `ToolbarControlGroup` around a
162
190
  `wa-dropdown` (its `slot="trigger"` `wa-button` is the toolbar button, kept under
163
- `data-morph-skip-children`). Compact mixed groups keep standard item padding
164
- and raised selected chrome without an internal separator; the selected item
165
- paints over the outer border. For an avatar control, set `content="avatar"` and
166
- `avatarImage` on the group instead of inserting an `img`; a lone control paints
167
- the group and a multi-button group paints only its pressed highlight.
191
+ `data-morph-skip-children`). Let the group size the trigger from its icon,
192
+ localized text, and caret; do not force a dropdown or button width. Compact
193
+ mixed groups keep standard item padding and raised selected chrome without an
194
+ internal separator; the selected item paints over the outer border. For an
195
+ avatar control, set `content="avatar"` and `avatarImage` on the group instead of
196
+ inserting an `img`; a lone control paints the group and a multi-button group
197
+ paints only its pressed highlight.
168
198
  - **Use one direct toolbar composition for panel/dialog/page headings.** Put an
169
199
  optional icon in a `ToolbarControlGroup`, an extra-large `ToolbarText` directly
170
200
  in the leading zone, and actions in a trailing group. Supporting copy is
171
201
  app-owned content below. Do not create a private heading wrapper or restyle the
172
202
  toolbar.
173
- - **Use `Text` for ordinary semantic headings and paragraphs outside toolbar
174
- title zones.** Omit `variant` for the default `p`, or pass `h1`–`h6` based on
175
- the document outline. Independently compose `tone="quiet|danger"`,
203
+ - **Use `Text` for ordinary semantic headings, paragraphs, and inline secondary
204
+ text outside toolbar title zones.** Omit `variant` for the default `p`, pass
205
+ `h1`–`h6` based on the document outline, or use `variant="span"` inside a row,
206
+ label, or table cell without adding box geometry. Independently compose `tone="quiet|danger"`,
176
207
  `size="compact"`, and `font="monospace"` for supporting, error, compact, and
177
208
  code roles instead of global utility classes. It passes native attributes
178
- through and owns the standard transparent border plus item padding; keep
209
+ through; block variants own the standard transparent border plus item padding, while `span` owns none. Keep
179
210
  toolbar titles on `ToolbarText` and list-aligned bare prose on `ListInsetText`.
180
211
  - **Space by the five-step scale, by relationship not by eye.** `0`
181
212
  `--kui-space-none`, `4px` `--kui-space-2xs`, `8px` `--kui-space-xs` (inside a
@@ -207,7 +238,11 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
207
238
  overflow autoscroll, drag preview, and
208
239
  replacement focus after controlled automatic tab activation,
209
240
  `wireResizableRegions` the live drag preview; they report committed changes via
210
- callbacks. Managed TokenSearchField clear also preserves open state and replacement
241
+ callbacks. For a tab-local action plus a separate far-edge action, set
242
+ `TabBar trailingPlacement="adjacent"`, put the local action in `trailing`, and
243
+ the workspace action in `end`; the component keeps both visible while its tab
244
+ list shrinks and scrolls, without application descendant CSS. Managed
245
+ TokenSearchField clear also preserves open state and replacement
211
246
  editor focus before the next input task, so shortcut-letter typing stays in search.
212
247
  Select All deletion does the same; deletion
213
248
  restoration finishes before the next keystroke rather than on a delayed frame.
@@ -244,10 +279,13 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
244
279
  optional string `badge` for a terse count or status beside the title. It
245
280
  inherits the banner tone and is announced with the banner, so keep its text
246
281
  meaningful and never rely on color alone.
282
+ - **Use `Badge` for compact non-interactive metadata.** Configure its semantic
283
+ tone, appearance, shape, and size instead of creating badge CSS in an app or
284
+ consuming component. Use a real button or link for interactive content.
247
285
 
248
286
  ## Public exports
249
287
 
250
- `LucideIcon`, `DisclosureArrow`, `Toolbar`, `ToolbarControlGroup`, `FloatingToolbar`, `ToolbarText`, `Text`,
288
+ `Badge`, `LucideIcon`, `DisclosureArrow`, `Toolbar`, `ToolbarControlGroup`, `ToolbarActionLink`, `FloatingToolbar`, `ToolbarText`, `Text`,
251
289
  `ListItem`, `ListActionRow`, `ListHeader`, `ListInsetControl`,
252
290
  `ListInsetText`, `SegmentedControl`,
253
291
  `Select`, `TokenSearchField` (+ `readTokenSearchField`, `placeTokenSearchCaret`,
@@ -259,7 +297,7 @@ tree-shakeable, declarative with `wire…` helpers, each with a companion CSS im
259
297
  `@kerfjs/ui/device-class`. For a standalone collapsible rail/drawer outside a full
260
298
  shell, `@kerfjs/ui/collapsible-panel` (`CollapsiblePanel`, `CollapsiblePanelToggle`)
261
299
 
262
- - `@kerfjs/ui/wire-sidebar` (`wireSidebar`: toggle, focus, compact overlay, persistence).
300
+ - `@kerfjs/ui/wire-sidebar` (`wireSidebar`: toggle, focus, compact overlay, persistence; a compact overlay starts closed and opens only on a user action, and `inlineCollapsed` declares a panel's inline default).
263
301
  `@kerfjs/ui/catalog` is another opt-in shell — a
264
302
  `Catalog` component + `wireCatalog` helper for building a component-gallery catalog
265
303
  (collapsible category sidebar + preview stage + resources footer, with an opt-in
@@ -280,7 +318,7 @@ shell, `@kerfjs/ui/collapsible-panel` (`CollapsiblePanel`, `CollapsiblePanelTogg
280
318
  - [component-contract.md](./docs/component-contract.md): what a component owns vs. what the app owns.
281
319
  - [accessibility.md](./docs/accessibility.md): keyboard and ARIA contracts per component.
282
320
  - [recipes.md](./docs/recipes.md): production compositions (app shell, list-detail dialog, composer form, …) as copyable references.
283
- - [app-layouts.md](./docs/app-layouts.md): choosing `NavStack`/`SplitView`/`Workbench`/`TabScaffold` by data + interaction + device, and dialog presentation.
321
+ - [app-layouts.md](./docs/app-layouts.md): choosing `NavStack`/`SplitView`/`Workbench`/`TabScaffold` by data + interaction + device, and dialog presentation, plus the safe-area model (surfaces paint through unsafe areas; content pads only the edges it reaches; `Pane.safeAreaEdges` for app-owned arrangements, `Toolbar.safeAreaEdges` for an app bar or bottom bar at a screen edge).
284
322
  - [webawesome-theme.md](./docs/webawesome-theme.md): the optional Web Awesome theme; catalog coverage means themed-and-supported, not preferred (prefer the Kerf primitive when one exists). The UX sidebar visibly tags superseded and exceptional Web Awesome choices as `Discouraged`; Popup remains an available conditional positioning primitive.
285
323
  - [component-catalog.json](./ai/component-catalog.json) + [component-catalog-extension.schema.json](./ai/component-catalog-extension.schema.json) + [catalog-authoring.json](./ai/catalog-authoring.json) + [public-api-signatures-v1.md](./ai/public-api-signatures-v1.md) + [webawesome-jsx-signatures-v1.md](./ai/webawesome-jsx-signatures-v1.md): machine-readable selection facts, a reusable app-component extension contract, Catalog authoring discovery, and exact signatures.
286
324
  - [compile-time-contracts-v1.json](./ai/compile-time-contracts-v1.json) + [type-contracts.md](./docs/type-contracts.md): stable compile diagnostics, packed-declaration proof, migrations, and explicit non-goals.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerfjs/ui",
3
- "version": "5.0.0-beta.49",
3
+ "version": "5.0.0-beta.51",
4
4
  "description": "Accessible, composable UI primitives for kerf applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -58,6 +58,11 @@
58
58
  "browser": "./dist/browser/disclosure-arrow.js",
59
59
  "import": "./dist/disclosure-arrow.js"
60
60
  },
61
+ "./badge": {
62
+ "types": "./dist/badge.d.ts",
63
+ "browser": "./dist/browser/badge.js",
64
+ "import": "./dist/badge.js"
65
+ },
61
66
  "./toolbar": {
62
67
  "types": "./dist/toolbar.d.ts",
63
68
  "browser": "./dist/browser/toolbar.js",
@@ -287,10 +292,12 @@
287
292
  "import": "./dist/index.js"
288
293
  },
289
294
  "./foundation.css": "./dist/styles/foundation.css",
295
+ "./document.css": "./dist/styles/document.css",
290
296
  "./layout.css": "./dist/styles/layout.css",
291
297
  "./webawesome.css": "./dist/styles/webawesome.css",
292
298
  "./styles.css": "./dist/styles/styles.css",
293
299
  "./lucide-icon.css": "./dist/styles/lucide-icon.css",
300
+ "./badge.css": "./dist/styles/badge.css",
294
301
  "./disclosure-arrow.css": "./dist/styles/disclosure-arrow.css",
295
302
  "./toolbar.css": "./dist/styles/toolbar.css",
296
303
  "./toolbar-control-group.css": "./dist/styles/toolbar-control-group.css",
@@ -345,7 +352,8 @@
345
352
  "scripts": {
346
353
  "build": "tsup && node ../scripts/clean-dist-output.mjs dist && node scripts/build-css.mjs && node scripts/build-browser-entries.mjs",
347
354
  "dev": "npm run build && vite --config ux-demo/vite.config.ts",
348
- "demo:build": "npm run build && vite build --config ux-demo/vite.config.ts && node scripts/check-demo-bundle.mjs",
355
+ "demo:bundle": "npm run build && vite build --config ux-demo/vite.config.ts",
356
+ "demo:build": "npm run demo:bundle && node scripts/check-demo-bundle.mjs",
349
357
  "demo:serve": "vite preview --config ux-demo/vite.config.ts",
350
358
  "test:e2e:serve": "npm run check:catalog && npm run check:demo-conformance && npm run demo:build && npm run demo:serve",
351
359
  "format": "prettier --write . --ignore-unknown",
@@ -360,6 +368,7 @@
360
368
  "check:profile": "node scripts/check-application-ui-profile.mjs",
361
369
  "check:demo-conformance": "node scripts/check-catalog-demo-conformance.mjs",
362
370
  "check:recipes": "node scripts/check-recipes.mjs",
371
+ "check:css-ownership": "node scripts/check-css-ownership.mjs",
363
372
  "check:ai-regressions": "node scripts/check-ai-regressions.mjs && node scripts/audit-ai-regression-results.mjs && node scripts/audit-ai-regression-results-v3.mjs",
364
373
  "check:ai-signatures": "node scripts/sync-ai-public-signatures.mjs --check",
365
374
  "ai:signatures:sync": "npm run build && node scripts/sync-ai-public-signatures.mjs",
@@ -385,7 +394,7 @@
385
394
  "test:contract-types:packed": "node scripts/check-packed-type-contracts.mjs",
386
395
  "test:consumer-types": "npm run test:contract-types:source && npm run build && node node_modules/typescript7/bin/tsc -p tests/consumer-types/webawesome/tsconfig.json && npm run test:contract-types:packed",
387
396
  "test:e2e": "playwright test",
388
- "check": "npm run check:catalog && npm run check:component-integrations && npm run check:profile && npm run check:demo-conformance && npm run check:recipes && npm run check:guidance && npm run check:ai-compatibility && npm run check:ai-regressions && npm run lint && npm run typecheck && npm test && npm run test:consumer-types && npm run check:ai-signatures && npm run demo:build && npm run check:design-templates && npm pack --dry-run",
397
+ "check": "npm run check:catalog && npm run check:component-integrations && npm run check:profile && npm run check:demo-conformance && npm run check:recipes && npm run check:css-ownership && npm run check:guidance && npm run check:ai-compatibility && npm run check:ai-regressions && npm run lint && npm run typecheck && npm test && npm run test:consumer-types && npm run check:ai-signatures && npm run demo:build && npm run check:design-templates && npm pack --dry-run",
389
398
  "prepublishOnly": "npm run check",
390
399
  "check:design-templates": "node scripts/check-design-templates.mjs",
391
400
  "check:design-templates:drift": "node scripts/check-design-templates-drift.mjs",