@kerfjs/ui 5.0.0-beta.55 → 5.0.0-beta.57

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 (418) hide show
  1. package/README.md +74 -41
  2. package/ai/application-ui-diagnostic-ids-v1.json +9 -1
  3. package/ai/application-ui-profile-sync.cjs +1 -1
  4. package/ai/application-ui-profile.defaults.json +63 -8
  5. package/ai/application-ui-profile.mjs +1 -1
  6. package/ai/compile-time-contracts-v1.json +2 -2
  7. package/ai/component-catalog.json +407 -80
  8. package/ai/component-catalog.schema.json +2 -0
  9. package/ai/component-class-owners.cjs +83 -0
  10. package/ai/component-composition-extension.d.ts +7 -0
  11. package/ai/component-composition-extension.schema.json +7 -0
  12. package/ai/{component-catalog-v2-overrides.json → component-composition-rules.json} +2 -1
  13. package/ai/{component-catalog-v2.d.ts → component-composition.d.ts} +9 -5
  14. package/ai/{component-catalog-v2.json → component-composition.json} +302 -61
  15. package/ai/{component-catalog-v2.schema.json → component-composition.schema.json} +21 -5
  16. package/ai/components/README.md +170 -0
  17. package/ai/components/application-tabs.md +65 -0
  18. package/ai/components/badge.md +72 -0
  19. package/ai/components/collapsible-panel.md +102 -0
  20. package/ai/components/content-item.md +90 -0
  21. package/ai/components/disclosure-arrow.md +77 -0
  22. package/ai/components/document-baseline.md +75 -0
  23. package/ai/components/empty-state.md +80 -0
  24. package/ai/components/feedback.md +71 -0
  25. package/ai/components/floating-toolbar.md +80 -0
  26. package/ai/components/foundation.md +65 -0
  27. package/ai/components/grid.md +93 -0
  28. package/ai/components/headers.md +63 -0
  29. package/ai/components/layout.md +75 -0
  30. package/ai/components/list-action-row.md +91 -0
  31. package/ai/components/list-header.md +89 -0
  32. package/ai/components/list-inset-control.md +74 -0
  33. package/ai/components/list-inset-text.md +72 -0
  34. package/ai/components/list-item.md +88 -0
  35. package/ai/components/list.md +103 -0
  36. package/ai/components/loading-spinner.md +72 -0
  37. package/ai/components/lucide-icon.md +66 -0
  38. package/ai/components/nav-stack.md +103 -0
  39. package/ai/components/pane.md +100 -0
  40. package/ai/components/popup-menu.md +82 -0
  41. package/ai/components/recipe-app-shell.md +57 -0
  42. package/ai/components/recipe-collapsible-sidebar.md +57 -0
  43. package/ai/components/recipe-compact-toolbar.md +57 -0
  44. package/ai/components/recipe-composer-form.md +57 -0
  45. package/ai/components/recipe-list-detail-dialog.md +57 -0
  46. package/ai/components/recipe-list-workspace-states.md +57 -0
  47. package/ai/components/recipe-loading-inspector.md +57 -0
  48. package/ai/components/recipe-navigation-sidebar.md +57 -0
  49. package/ai/components/recipe-navigation-stack.md +57 -0
  50. package/ai/components/recipe-workspace-header.md +57 -0
  51. package/ai/components/resize.md +101 -0
  52. package/ai/components/row.md +96 -0
  53. package/ai/components/segmented-control.md +82 -0
  54. package/ai/components/select.md +107 -0
  55. package/ai/components/skeleton.md +84 -0
  56. package/ai/components/spacer.md +86 -0
  57. package/ai/components/split-view.md +102 -0
  58. package/ai/components/state-banner.md +92 -0
  59. package/ai/components/sunken-panel.md +79 -0
  60. package/ai/components/surface-scaffold.md +78 -0
  61. package/ai/components/tab-bar.md +112 -0
  62. package/ai/components/tab-scaffold.md +94 -0
  63. package/ai/components/tabs.md +90 -0
  64. package/ai/components/text.md +82 -0
  65. package/ai/components/token-search-field.md +101 -0
  66. package/ai/components/toolbar-control-group.md +100 -0
  67. package/ai/components/toolbar-text.md +76 -0
  68. package/ai/components/toolbar.md +95 -0
  69. package/ai/components/value-table.md +72 -0
  70. package/ai/components/wa-accordion-item.md +55 -0
  71. package/ai/components/wa-accordion.md +65 -0
  72. package/ai/components/wa-animated-image.md +55 -0
  73. package/ai/components/wa-animation.md +55 -0
  74. package/ai/components/wa-avatar.md +55 -0
  75. package/ai/components/wa-badge.md +55 -0
  76. package/ai/components/wa-breadcrumb-item.md +53 -0
  77. package/ai/components/wa-breadcrumb.md +55 -0
  78. package/ai/components/wa-button-group.md +59 -0
  79. package/ai/components/wa-button.md +55 -0
  80. package/ai/components/wa-callout.md +58 -0
  81. package/ai/components/wa-card.md +59 -0
  82. package/ai/components/wa-carousel-item.md +53 -0
  83. package/ai/components/wa-carousel.md +55 -0
  84. package/ai/components/wa-checkbox-group.md +55 -0
  85. package/ai/components/wa-checkbox.md +55 -0
  86. package/ai/components/wa-color-picker.md +55 -0
  87. package/ai/components/wa-comparison.md +55 -0
  88. package/ai/components/wa-copy-button.md +55 -0
  89. package/ai/components/wa-details.md +61 -0
  90. package/ai/components/wa-dialog.md +59 -0
  91. package/ai/components/wa-divider.md +53 -0
  92. package/ai/components/wa-drawer.md +55 -0
  93. package/ai/components/wa-dropdown-item.md +60 -0
  94. package/ai/components/wa-dropdown.md +60 -0
  95. package/ai/components/wa-format-bytes.md +53 -0
  96. package/ai/components/wa-format-date.md +53 -0
  97. package/ai/components/wa-format-number.md +53 -0
  98. package/ai/components/wa-icon.md +58 -0
  99. package/ai/components/wa-include.md +53 -0
  100. package/ai/components/wa-input.md +59 -0
  101. package/ai/components/wa-intersection-observer.md +53 -0
  102. package/ai/components/wa-known-date.md +53 -0
  103. package/ai/components/wa-markdown.md +53 -0
  104. package/ai/components/wa-mutation-observer.md +53 -0
  105. package/ai/components/wa-number-input.md +55 -0
  106. package/ai/components/wa-option.md +59 -0
  107. package/ai/components/wa-otp-input.md +53 -0
  108. package/ai/components/wa-page.md +53 -0
  109. package/ai/components/wa-pagination.md +55 -0
  110. package/ai/components/wa-popover.md +55 -0
  111. package/ai/components/wa-popup.md +53 -0
  112. package/ai/components/wa-progress-bar.md +53 -0
  113. package/ai/components/wa-progress-ring.md +53 -0
  114. package/ai/components/wa-qr-code.md +53 -0
  115. package/ai/components/wa-radio-group.md +59 -0
  116. package/ai/components/wa-radio.md +55 -0
  117. package/ai/components/wa-random-content.md +53 -0
  118. package/ai/components/wa-rating.md +55 -0
  119. package/ai/components/wa-relative-time.md +53 -0
  120. package/ai/components/wa-resize-observer.md +53 -0
  121. package/ai/components/wa-scroller.md +53 -0
  122. package/ai/components/wa-select.md +63 -0
  123. package/ai/components/wa-skeleton.md +53 -0
  124. package/ai/components/wa-slider.md +55 -0
  125. package/ai/components/wa-spinner.md +58 -0
  126. package/ai/components/wa-split-panel.md +58 -0
  127. package/ai/components/wa-switch.md +53 -0
  128. package/ai/components/wa-tab-group.md +69 -0
  129. package/ai/components/wa-tab-panel.md +60 -0
  130. package/ai/components/wa-tab.md +59 -0
  131. package/ai/components/wa-tag.md +59 -0
  132. package/ai/components/wa-textarea.md +53 -0
  133. package/ai/components/wa-time-input.md +55 -0
  134. package/ai/components/wa-toast-item.md +60 -0
  135. package/ai/components/wa-toast.md +60 -0
  136. package/ai/components/wa-tooltip.md +55 -0
  137. package/ai/components/wa-tree-item.md +55 -0
  138. package/ai/components/wa-tree.md +55 -0
  139. package/ai/components/wa-zoomable-frame.md +55 -0
  140. package/ai/components/workbench.md +114 -0
  141. package/ai/public-api-signatures-v1.md +572 -101
  142. package/ai/skill.md +107 -52
  143. package/ai/webawesome-jsx-signatures-v1.md +8 -2
  144. package/analyzer/component-ownership.d.mts +56 -0
  145. package/analyzer/component-ownership.mjs +184 -0
  146. package/analyzer/index.d.mts +3 -0
  147. package/analyzer/index.mjs +274 -20
  148. package/analyzer/selectors.d.mts +21 -0
  149. package/analyzer/selectors.mjs +212 -0
  150. package/dist/app-tab.js +1 -1
  151. package/dist/badge.d.ts +35 -7
  152. package/dist/badge.js +1 -1
  153. package/dist/browser/catalog.js +25 -0
  154. package/dist/browser/collapsible-panel.js +7 -0
  155. package/dist/browser/content-item.js +3 -0
  156. package/dist/browser/nav-stack.js +8 -0
  157. package/dist/browser/select.js +1 -0
  158. package/dist/browser/split-view.js +10 -0
  159. package/dist/browser/tab-scaffold.js +4 -0
  160. package/dist/browser/workbench.js +10 -0
  161. package/dist/catalog-resources.d.ts +12 -1
  162. package/dist/catalog.d.ts +14 -3
  163. package/dist/catalog.js +53 -43
  164. package/dist/catalog.js.map +1 -1
  165. package/dist/{chunk-WZ2GLEY4.js → chunk-2G2ZJQYK.js} +2 -14
  166. package/dist/chunk-2G2ZJQYK.js.map +1 -0
  167. package/dist/{chunk-R754MHXX.js → chunk-553ASUK6.js} +8 -8
  168. package/dist/chunk-553ASUK6.js.map +1 -0
  169. package/dist/{chunk-D7GWXIAS.js → chunk-5I7QGQVT.js} +1 -1
  170. package/dist/chunk-5I7QGQVT.js.map +1 -0
  171. package/dist/{chunk-RNRUVPJ3.js → chunk-63ZWBGE3.js} +3 -2
  172. package/dist/chunk-63ZWBGE3.js.map +1 -0
  173. package/dist/{chunk-44IG7PFV.js → chunk-6CINWX2E.js} +3 -3
  174. package/dist/{chunk-44IG7PFV.js.map → chunk-6CINWX2E.js.map} +1 -1
  175. package/dist/{chunk-MTM43SJO.js → chunk-6YFHCLHG.js} +2 -2
  176. package/dist/chunk-6YFHCLHG.js.map +1 -0
  177. package/dist/{chunk-C2FAYFDE.js → chunk-AGXQQFKZ.js} +2 -2
  178. package/dist/{chunk-C2FAYFDE.js.map → chunk-AGXQQFKZ.js.map} +1 -1
  179. package/dist/{chunk-ATHXW6RP.js → chunk-AHEG27M7.js} +2 -2
  180. package/dist/{chunk-ATHXW6RP.js.map → chunk-AHEG27M7.js.map} +1 -1
  181. package/dist/chunk-BFPBS5Y4.js +250 -0
  182. package/dist/chunk-BFPBS5Y4.js.map +1 -0
  183. package/dist/{chunk-5OLVUEND.js → chunk-DG6G5EPF.js} +4 -6
  184. package/dist/chunk-DG6G5EPF.js.map +1 -0
  185. package/dist/{chunk-LZXQVUGN.js → chunk-EVYKTVBQ.js} +5 -14
  186. package/dist/chunk-EVYKTVBQ.js.map +1 -0
  187. package/dist/{chunk-7GUQYHKA.js → chunk-EZVNC4J6.js} +7 -1
  188. package/dist/chunk-EZVNC4J6.js.map +1 -0
  189. package/dist/{chunk-RNZAKSME.js → chunk-F4QMLOXJ.js} +5 -4
  190. package/dist/chunk-F4QMLOXJ.js.map +1 -0
  191. package/dist/{chunk-EAX2GSS4.js → chunk-G2RKFVRB.js} +54 -20
  192. package/dist/chunk-G2RKFVRB.js.map +1 -0
  193. package/dist/chunk-JCV4GXWB.js +249 -0
  194. package/dist/chunk-JCV4GXWB.js.map +1 -0
  195. package/dist/chunk-K47P7QZC.js +17 -0
  196. package/dist/chunk-K47P7QZC.js.map +1 -0
  197. package/dist/{chunk-HMQ2ZDGG.js → chunk-KROD7KZK.js} +3 -2
  198. package/dist/chunk-KROD7KZK.js.map +1 -0
  199. package/dist/{chunk-5NDQABWK.js → chunk-M7QV3IHH.js} +3 -1
  200. package/dist/chunk-M7QV3IHH.js.map +1 -0
  201. package/dist/chunk-MA73TA6D.js +42 -0
  202. package/dist/chunk-MA73TA6D.js.map +1 -0
  203. package/dist/{chunk-ZMBU522F.js → chunk-MEY33VWB.js} +2 -2
  204. package/dist/{chunk-ZMBU522F.js.map → chunk-MEY33VWB.js.map} +1 -1
  205. package/dist/chunk-MP2GDBQZ.js +50 -0
  206. package/dist/chunk-MP2GDBQZ.js.map +1 -0
  207. package/dist/{chunk-3NBL3OUQ.js → chunk-NAPE7WUS.js} +2 -2
  208. package/dist/chunk-NAPE7WUS.js.map +1 -0
  209. package/dist/{chunk-BSKEH3UC.js → chunk-NBYTIVHS.js} +2 -2
  210. package/dist/{chunk-BSKEH3UC.js.map → chunk-NBYTIVHS.js.map} +1 -1
  211. package/dist/{chunk-T22EM4ET.js → chunk-OUK6QRDR.js} +71 -31
  212. package/dist/chunk-OUK6QRDR.js.map +1 -0
  213. package/dist/chunk-PWXNJS2B.js +115 -0
  214. package/dist/chunk-PWXNJS2B.js.map +1 -0
  215. package/dist/{chunk-GUIHMIT2.js → chunk-QOWG4ZCL.js} +5 -4
  216. package/dist/chunk-QOWG4ZCL.js.map +1 -0
  217. package/dist/{chunk-5I22LMCV.js → chunk-VZOFZPSO.js} +2 -2
  218. package/dist/chunk-VZOFZPSO.js.map +1 -0
  219. package/dist/{chunk-4BIMLITI.js → chunk-WLG6SSNT.js} +5 -3
  220. package/dist/chunk-WLG6SSNT.js.map +1 -0
  221. package/dist/{chunk-NE5JAOQS.js → chunk-WOUF7YXM.js} +3 -3
  222. package/dist/{chunk-NE5JAOQS.js.map → chunk-WOUF7YXM.js.map} +1 -1
  223. package/dist/{chunk-N7FHFNT6.js → chunk-XCO6YCYM.js} +34 -5
  224. package/dist/chunk-XCO6YCYM.js.map +1 -0
  225. package/dist/{chunk-TZ5IHWB2.js → chunk-XDKAYSVD.js} +5 -3
  226. package/dist/chunk-XDKAYSVD.js.map +1 -0
  227. package/dist/collapsible-panel.d.ts +14 -3
  228. package/dist/collapsible-panel.js +13 -16
  229. package/dist/collapsible-panel.js.map +1 -1
  230. package/dist/content-item.d.ts +46 -0
  231. package/dist/content-item.js +4 -0
  232. package/dist/content-item.js.map +1 -0
  233. package/dist/css-values.d.ts +5 -11
  234. package/dist/css-values.js +1 -1
  235. package/dist/disclosure-arrow.d.ts +4 -1
  236. package/dist/disclosure-arrow.js +1 -1
  237. package/dist/floating-toolbar.d.ts +17 -6
  238. package/dist/floating-toolbar.js +1 -1
  239. package/dist/grid.js +1 -1
  240. package/dist/help-tags-register.d.ts +13 -0
  241. package/dist/help-tags-register.js +9 -0
  242. package/dist/help-tags-register.js.map +1 -0
  243. package/dist/index.d.ts +7 -5
  244. package/dist/index.js +29 -26
  245. package/dist/list-action-row.js +2 -1
  246. package/dist/list-header.d.ts +14 -1
  247. package/dist/list-header.js +1 -1
  248. package/dist/list-inset-control.d.ts +1 -1
  249. package/dist/list-inset-control.js +1 -1
  250. package/dist/list-inset-text.d.ts +3 -10
  251. package/dist/list-inset-text.js +1 -1
  252. package/dist/list-item.js +2 -1
  253. package/dist/list.d.ts +10 -3
  254. package/dist/list.js +1 -1
  255. package/dist/nav-stack.d.ts +50 -5
  256. package/dist/nav-stack.js +4 -1
  257. package/dist/pane.d.ts +37 -2
  258. package/dist/pane.js +1 -1
  259. package/dist/{panel-toolbar-BFQxFITu.d.ts → panel-toolbar-BN4x0rth.d.ts} +13 -1
  260. package/dist/popup-menu-register.js +3 -2
  261. package/dist/popup-menu-register.js.map +1 -1
  262. package/dist/popup-menu.js +1 -1
  263. package/dist/row.d.ts +1 -1
  264. package/dist/row.js +1 -1
  265. package/dist/select-register.js +70 -3
  266. package/dist/select-register.js.map +1 -1
  267. package/dist/select.d.ts +46 -8
  268. package/dist/select.js +2 -1
  269. package/dist/{divider-sides-BzB6rphT.d.ts → sides-BPSWde0A.d.ts} +1 -3
  270. package/dist/skeleton.js +1 -1
  271. package/dist/spacer.js +1 -1
  272. package/dist/split-view.d.ts +31 -3
  273. package/dist/split-view.js +34 -5
  274. package/dist/split-view.js.map +1 -1
  275. package/dist/state-banner.js +1 -1
  276. package/dist/styles/app-tab.css +31 -0
  277. package/dist/styles/badge.css +32 -11
  278. package/dist/styles/catalog/components/catalog-example.css +1 -1
  279. package/dist/styles/catalog/components/catalog-stage.css +1 -1
  280. package/dist/styles/collapsible-panel.css +199 -42
  281. package/dist/styles/content-item.css +30 -0
  282. package/dist/styles/floating-toolbar.css +26 -2
  283. package/dist/styles/foundation.css +28 -19
  284. package/dist/styles/layout.css +1 -21
  285. package/dist/styles/list-action-row.css +13 -1
  286. package/dist/styles/list-header.css +0 -12
  287. package/dist/styles/list-inset-text.css +1 -2
  288. package/dist/styles/list-item.css +14 -1
  289. package/dist/styles/list.css +5 -5
  290. package/dist/styles/nav-stack.css +63 -71
  291. package/dist/styles/pane.css +83 -1
  292. package/dist/styles/popup-menu.css +229 -0
  293. package/dist/styles/resizable-region.css +70 -15
  294. package/dist/styles/select.css +60 -19
  295. package/dist/styles/skeleton.css +30 -3
  296. package/dist/styles/split-view.css +31 -32
  297. package/dist/styles/state-banner.css +0 -19
  298. package/dist/styles/surface-scaffold.css +8 -3
  299. package/dist/styles/tab-bar.css +79 -28
  300. package/dist/styles/tab-scaffold.css +60 -14
  301. package/dist/styles/text.css +17 -1
  302. package/dist/styles/token-search-field.css +4 -4
  303. package/dist/styles/toolbar-control-group.css +143 -69
  304. package/dist/styles/toolbar-text.css +21 -0
  305. package/dist/styles/toolbar.css +22 -37
  306. package/dist/styles/value-table.css +19 -2
  307. package/dist/styles/webawesome.css +3 -1
  308. package/dist/styles/workbench.css +145 -42
  309. package/dist/tab-scaffold.d.ts +46 -2
  310. package/dist/tab-scaffold.js +52 -16
  311. package/dist/tab-scaffold.js.map +1 -1
  312. package/dist/token-search-field.d.ts +4 -2
  313. package/dist/token-search-field.js +1 -1
  314. package/dist/toolbar-text.d.ts +7 -1
  315. package/dist/toolbar-text.js +1 -1
  316. package/dist/toolbar.d.ts +20 -5
  317. package/dist/toolbar.js +1 -1
  318. package/dist/{types-D6vzj-VT.d.ts → types-BuHELdXR.d.ts} +39 -6
  319. package/dist/value-table.js +1 -1
  320. package/dist/webawesome.d.ts +7 -1
  321. package/dist/wire-catalog.d.ts +15 -2
  322. package/dist/wire-catalog.js +12 -2
  323. package/dist/wire-catalog.js.map +1 -1
  324. package/dist/wire-scroll-dividers.d.ts +69 -0
  325. package/dist/wire-scroll-dividers.js +3 -0
  326. package/dist/wire-scroll-dividers.js.map +1 -0
  327. package/dist/wire-token-search-fields.js +1 -1
  328. package/dist/workbench.d.ts +60 -22
  329. package/dist/workbench.js +1 -1
  330. package/docs/accessibility.md +116 -7
  331. package/docs/app-layouts.md +18 -8
  332. package/docs/catalog.md +26 -7
  333. package/docs/collapsible-panel.md +32 -9
  334. package/docs/component-contract.md +182 -30
  335. package/docs/component-selection.md +59 -46
  336. package/docs/design/templates/empty-state/with-action-dark.svg +1 -1
  337. package/docs/design/templates/empty-state/with-action.svg +1 -1
  338. package/docs/design/templates/empty-state-dark.svg +1 -1
  339. package/docs/design/templates/empty-state.svg +1 -1
  340. package/docs/design/templates/list/compact-dark.svg +1 -1
  341. package/docs/design/templates/list/compact.svg +1 -1
  342. package/docs/design/templates/list/stack-dark.svg +1 -1
  343. package/docs/design/templates/list/stack.svg +1 -1
  344. package/docs/design/templates/list-action-row/default-dark.svg +1 -1
  345. package/docs/design/templates/list-action-row/default.svg +1 -1
  346. package/docs/design/templates/list-action-row/selected-dark.svg +1 -1
  347. package/docs/design/templates/list-action-row/selected.svg +1 -1
  348. package/docs/design/templates/list-action-row-dark.svg +1 -1
  349. package/docs/design/templates/list-action-row.svg +1 -1
  350. package/docs/design/templates/list-dark.svg +1 -1
  351. package/docs/design/templates/list-item/default-dark.svg +1 -1
  352. package/docs/design/templates/list-item/default.svg +1 -1
  353. package/docs/design/templates/list-item/selected-dark.svg +1 -1
  354. package/docs/design/templates/list-item/selected.svg +1 -1
  355. package/docs/design/templates/list-item/trailing-dark.svg +1 -1
  356. package/docs/design/templates/list-item/trailing.svg +1 -1
  357. package/docs/design/templates/list-item-dark.svg +1 -1
  358. package/docs/design/templates/list-item.svg +1 -1
  359. package/docs/design/templates/list.svg +1 -1
  360. package/docs/design/templates/tab-bar/workspace-tabs-dark.svg +1 -1
  361. package/docs/design/templates/tab-bar/workspace-tabs.svg +1 -1
  362. package/docs/design/templates/tab-bar-dark.svg +1 -1
  363. package/docs/design/templates/tab-bar.svg +1 -1
  364. package/docs/design/templates/value-table/details-dark.svg +1 -1
  365. package/docs/design/templates/value-table/details.svg +1 -1
  366. package/docs/design/templates/value-table-dark.svg +1 -1
  367. package/docs/design/templates/value-table.svg +1 -1
  368. package/docs/design-philosophy.md +31 -4
  369. package/docs/examples/application-ui-profile.json +4 -4
  370. package/docs/examples/command-palette-adapter.tsx +3 -2
  371. package/docs/examples/{component-catalog-extension-v2.json → component-composition-extension.json} +3 -3
  372. package/docs/layout.md +148 -29
  373. package/docs/nav-stack.md +77 -3
  374. package/docs/recipes.md +4 -3
  375. package/docs/split-view.md +54 -5
  376. package/docs/tab-scaffold.md +73 -2
  377. package/docs/type-contracts.md +21 -20
  378. package/docs/ui-analyzer.md +99 -5
  379. package/docs/ui-doctor.md +10 -0
  380. package/docs/ux-demo.md +2 -2
  381. package/docs/webawesome-theme.md +1 -2
  382. package/docs/workbench.md +57 -11
  383. package/doctor/index.mjs +6 -1
  384. package/llms.txt +117 -24
  385. package/package.json +29 -6
  386. package/ux-demo/recipes/collapsible-sidebar.tsx +3 -3
  387. package/ux-demo/recipes/composer-form.tsx +0 -1
  388. package/ux-demo/recipes/list-detail-dialog.tsx +0 -2
  389. package/ux-demo/recipes/list-workspace-states.tsx +0 -1
  390. package/ux-demo/recipes/loading-inspector.tsx +0 -1
  391. package/ux-demo/recipes/navigation-sidebar.tsx +3 -2
  392. package/ux-demo/recipes/navigation-stack.tsx +3 -2
  393. package/ux-demo/recipes/workspace-header.tsx +0 -1
  394. package/ai/component-catalog-extension-v2.d.ts +0 -7
  395. package/ai/component-catalog-extension-v2.schema.json +0 -7
  396. package/dist/chunk-3NBL3OUQ.js.map +0 -1
  397. package/dist/chunk-4BIMLITI.js.map +0 -1
  398. package/dist/chunk-5I22LMCV.js.map +0 -1
  399. package/dist/chunk-5NDQABWK.js.map +0 -1
  400. package/dist/chunk-5OLVUEND.js.map +0 -1
  401. package/dist/chunk-7GUQYHKA.js.map +0 -1
  402. package/dist/chunk-D7GWXIAS.js.map +0 -1
  403. package/dist/chunk-DQA5FMOX.js +0 -85
  404. package/dist/chunk-DQA5FMOX.js.map +0 -1
  405. package/dist/chunk-EAX2GSS4.js.map +0 -1
  406. package/dist/chunk-GUIHMIT2.js.map +0 -1
  407. package/dist/chunk-HMQ2ZDGG.js.map +0 -1
  408. package/dist/chunk-LZXQVUGN.js.map +0 -1
  409. package/dist/chunk-MTM43SJO.js.map +0 -1
  410. package/dist/chunk-N7FHFNT6.js.map +0 -1
  411. package/dist/chunk-R754MHXX.js.map +0 -1
  412. package/dist/chunk-RNRUVPJ3.js.map +0 -1
  413. package/dist/chunk-RNZAKSME.js.map +0 -1
  414. package/dist/chunk-T22EM4ET.js.map +0 -1
  415. package/dist/chunk-TZ5IHWB2.js.map +0 -1
  416. package/dist/chunk-WZ2GLEY4.js.map +0 -1
  417. package/dist/chunk-XD7DWYXG.js +0 -34
  418. package/dist/chunk-XD7DWYXG.js.map +0 -1
package/docs/nav-stack.md CHANGED
@@ -5,14 +5,19 @@ and out over one another while the top chrome settles. A **single-pane layout is
5
5
  a `NavStack` with one entry**. It is one of the opt-in app layouts (see
6
6
  [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
7
7
 
8
- Import the component and its companion stylesheet:
8
+ Import the component and its wiring helper:
9
9
 
10
10
  ```ts
11
11
  import { NavStack, type NavStackView } from "@kerfjs/ui/nav-stack";
12
12
  import { wireNavStack } from "@kerfjs/ui/wire-nav-stack";
13
- import "@kerfjs/ui/nav-stack.css";
14
13
  ```
15
14
 
15
+ In a browser bundler that honors the `browser` export condition (Vite, esbuild, and
16
+ webpack do by default), the import above also
17
+ loads NavStack's stylesheet and those of the components it renders internally
18
+ (`Toolbar`, `ToolbarControlGroup`, `ToolbarText`, `LucideIcon`). Without that condition, import the manual stylesheets instead:
19
+ `@kerfjs/ui/nav-stack.css` plus those components' CSS, or `@kerfjs/ui/styles.css`.
20
+
16
21
  ## State lives in the app
17
22
 
18
23
  Like every `@kerfjs/ui` component, `NavStack` is declarative: the app owns the
@@ -49,11 +54,55 @@ views.value = [
49
54
  `NavStack` renders every entry stacked, the last one active and the rest kept
50
55
  mounted (so their DOM state survives) but hidden. Each entry carries a
51
56
  `key` (stable identity), `content`, an optional `title`, optional per-view
52
- `toolbar` actions, and an optional per-view `bottomToolbar`. The component-level
57
+ `leading`, `center`, and trailing `toolbar` groups for the top toolbar, and an
58
+ optional per-view `bottomToolbar`. The component-level
53
59
  `bottomToolbar` remains a persistent fallback for views that do not provide one.
54
60
  The back control appears automatically once the stack has more than one entry;
55
61
  `wireNavStack`'s `onBack` is where the app pops its own signal.
56
62
 
63
+ ## The top toolbar
64
+
65
+ The top chrome is a real `Toolbar`, so it follows the same zone,
66
+ group, and responsive rules as every other toolbar. Its leading zone holds the
67
+ back control (a borderless `ToolbarControlGroup`, shown once the stack has
68
+ depth), then the active view's `leading` groups, then the view's `title` as a
69
+ `ToolbarText`. The view's `center` fills the center zone and its `toolbar`
70
+ fills the trailing zone. Give each zone `ToolbarControlGroup`s or `ToolbarText`,
71
+ as in any toolbar.
72
+
73
+ Configure the toolbar with `toolbarConfig` instead of styling it:
74
+
75
+ ```tsx
76
+ <NavStack
77
+ id="mail"
78
+ label="Mail"
79
+ views={views.value}
80
+ backText={previousTitle}
81
+ toolbarConfig={{
82
+ headingLevel: 1,
83
+ titleSize: "xlarge",
84
+ dividerSides: "b",
85
+ responsive: "wrap",
86
+ }}
87
+ />
88
+ ```
89
+
90
+ - `toolbarConfig` forwards the `Toolbar` configuration (`dividerSides`,
91
+ `centerAlign`, `responsive`, `responsiveAt`, `safeAreaEdges`) and adds
92
+ `label` (the toolbar's accessible name), `titleSize` (the title's
93
+ `ToolbarText` size, default `large`), and `headingLevel` (expose the title as
94
+ a heading; default a plain span). The defaults keep the stack's own chrome:
95
+ no divider, the standard Toolbar height (a 44px control band with 8px above and below), and the top and side safe-area edges
96
+ claimed.
97
+ - `backIcon` replaces the default chevron-left icon. `backText` adds visible
98
+ text beside it (for example, the previous view's title); visible text names
99
+ the control, so `backLabel` applies only to the icon-only control.
100
+
101
+ The catalog's **Configured toolbar** example shows these together: a level-2
102
+ heading title over a bottom divider, leading and center groups on the root
103
+ view, a trailing group on the pushed view, and `backText` set to the previous
104
+ view's title.
105
+
57
106
  Focus follows the controlled stack automatically. On push, `wireNavStack`
58
107
  remembers the focused descendant of the departing view and moves focus into the
59
108
  new top view. On pop it restores that exact descendant when it still exists. A
@@ -65,6 +114,31 @@ When a remembered control was removed during the controlled rerender, the same
65
114
  fallback order applies. Disposing the helper stops future transitions and
66
115
  cleans up any temporary fallback `tabindex` it added.
67
116
 
117
+ ## Scroll dividers
118
+
119
+ The stack draws no permanent line between its chrome and the view. With
120
+ `wireScrollDividers(appRoot)` installed (see
121
+ [Layout › Scroll dividers](layout.md#scroll-dividers)), the top chrome shows a
122
+ line along its bottom edge only while the active view's content is scrolled
123
+ beneath it, and the bottom toolbar a line along its top edge only while more
124
+ content lies below it — neither when the content fits. The line follows the
125
+ active view across a push or pop. The chrome's line is overlaid on its bottom
126
+ edge and the bottom toolbar's is its always-present 1px top border, colored
127
+ only while shown, so neither ever moves the chrome or the content. When the view's only child is a `Pane`,
128
+ the line keys on whichever actually scrolls — the view, or the Pane's content —
129
+ and a Pane header or footer draws its own boundary instead. An explicit
130
+ `toolbarConfig.dividerSides` still draws a permanent Toolbar edge.
131
+
132
+ `chromeDividers` configures both lines, with the same values as a `Pane`'s:
133
+ `scroll` (the default) follows the scroll state above and draws nothing until
134
+ the wiring runs; `always` keeps both lines, with or without the wiring; `none`
135
+ drops them even when the wiring reports scroll state. It does not change the
136
+ top Toolbar's own `dividerSides`.
137
+
138
+ ```tsx
139
+ <NavStack id="settings" label="Settings" views={views.value} chromeDividers="always" />
140
+ ```
141
+
68
142
  ## Safe areas
69
143
 
70
144
  The stack paints through a device's unsafe areas. Its chrome pads for the top
package/docs/recipes.md CHANGED
@@ -57,8 +57,8 @@ component variables.
57
57
  [Open the recipe](../ux-demo/?component=recipe-navigation-sidebar) · [TSX source](../ux-demo/recipes/navigation-sidebar.tsx)
58
58
 
59
59
  One unpadded `.kui-pane` owns toolbar/content/footer structure. Its
60
- `.kui-content` uses 24px major gaps; `ListHeader`, `ListItem`, and other
61
- `.kui-content-item` children own their 8px margin, 1px border, and 8px padding.
60
+ `.kui-content` uses 24px major gaps; `ListHeader`, `ListItem`, and
61
+ `ContentItem` children own their 8px margin, 1px border, and 8px padding.
62
62
  Rows and footer toolbar groups remain 44px tall. The app owns routes,
63
63
  permissions, labels, selection, valid section counts and their localized
64
64
  `countLabel` phrases, non-count badge content, disclosure state, and revealed
@@ -205,7 +205,8 @@ Chromium, Firefox, and WebKit by `tests/browser/collapsible-sidebar-recipe.spec.
205
205
  `@kerfjs/ui/webawesome.css`.
206
206
  - Compose, don't style. A recipe ships no stylesheet, sets no inline style, and
207
207
  names no class outside the published layout vocabulary (`.kui-content`,
208
- `.kui-content-item`, …) and themed Web Awesome classes; it configures
208
+ `.kui-control-cluster`, …) and themed Web Awesome classes — a content item is
209
+ a `ContentItem`, not a hand-written `.kui-content-item`; it configures
209
210
  components through props, `rootAttributes`, and tokens. When a recipe needs a
210
211
  capability no component offers, record it as a component gap rather than
211
212
  papering over it with CSS. `npm run check:recipes` enforces these rules on
@@ -8,11 +8,16 @@ on compact ones. One of the opt-in app layouts (see
8
8
  ```ts
9
9
  import { SplitView } from "@kerfjs/ui/split-view";
10
10
  import { deviceClass } from "@kerfjs/ui/device-class";
11
- import "@kerfjs/ui/split-view.css";
12
- // plus nav-stack.css when the compact path is reachable, and
13
- // wireResizableRegions / wireNavStack for the interactive behavior.
11
+ // plus wireResizableRegions / wireNavStack for the interactive behavior.
14
12
  ```
15
13
 
14
+ In a browser bundler that honors the `browser` export condition (Vite, esbuild, and
15
+ webpack do by default), the import above also
16
+ loads SplitView's stylesheet and those of the components it renders internally
17
+ (its compact `NavStack`, `ResizableRegion`, and the toolbar
18
+ components). Without that condition, import the manual stylesheets instead:
19
+ `@kerfjs/ui/split-view.css` plus those components' CSS, or `@kerfjs/ui/styles.css`.
20
+
16
21
  ## Safe areas
17
22
 
18
23
  The list pane grows by the inline-start unsafe inset and pads for the edges it
@@ -53,11 +58,55 @@ const selected = signal<string | null>(null);
53
58
  app's selection). This is the portrait-tablet / handset presentation; as a
54
59
  dialog the compact form is a full-screen or large partial-cover modal.
55
60
 
61
+ ## Configuring the list region and the compact stack
62
+
63
+ `resizable` forwards the list's `ResizableRegion` configuration alongside
64
+ `size`, `min`, and `max`: `separator` (`"hidden"` keeps the resize hit target
65
+ but paints no line), `handleIcon`, `contentOverflow`, and collapse —
66
+ `collapsed`, `transitioning`, `collapseMotion`, `restoreControl`, and
67
+ `restorePosition`. A collapsed list snaps to zero width, renders `inert` and
68
+ `aria-hidden`, and the detail fills the split; the app owns the `collapsed`
69
+ flag and renders the restore control (usually a `FloatingToolbar`). Omitted
70
+ options keep the region's defaults.
71
+
72
+ ```tsx
73
+ resizable={{
74
+ size: listWidth.value,
75
+ min: 220,
76
+ max: 480,
77
+ collapsed: listCollapsed.value,
78
+ restoreControl: <ShowThreadsToolbar />,
79
+ }}
80
+ ```
81
+
82
+ `compactStack` configures the compact `NavStack`: `toolbarConfig`, `backIcon`,
83
+ `backText`, `hideToolbar`, the persistent `bottomToolbar`, and `chromeDividers`
84
+ forward to the stack, and `list` / `detail` give each view its own top-toolbar groups
85
+ (`leading`, `center`, trailing `toolbar`) and `bottomToolbar`:
86
+
87
+ ```tsx
88
+ compactStack={{
89
+ backText: "Threads",
90
+ toolbarConfig: { headingLevel: 1 },
91
+ list: { toolbar: <ComposeGroup /> },
92
+ detail: { toolbar: <ReplyGroup />, bottomToolbar: <MessageActions /> },
93
+ }}
94
+ ```
95
+
56
96
  The catalog's **Interactive compact drill-down** example exercises this exact
57
97
  controlled flow: a list action sets the selected message, `detailActive` pushes
58
98
  its detail, and the wired Back action clears the selection to reveal the
59
- preserved list. Use the same structure in application demos so compact examples
60
- show the interaction rather than rendering a disconnected detail state.
99
+ preserved list. It also forwards `compactStack`: a level-2 heading title over
100
+ a bottom divider, a Compose group on the list view, and a Reply group plus a
101
+ bottom toolbar on the detail. Use the same structure in application demos so
102
+ compact examples show the interaction rather than rendering a disconnected
103
+ detail state.
104
+
105
+ The catalog's **Resizable, collapsible list** example forwards `resizable`: a
106
+ hidden separator that still resizes the list (its committed width comes back
107
+ through `wireResizableRegions`' `onCommit` into the app's size signal), a Hide
108
+ control in the list's own toolbar, and a `FloatingToolbar` restore control in
109
+ the split's bottom-start corner.
61
110
 
62
111
  Compose the interactive wiring from the existing helpers — `SplitView` adds no
63
112
  wire of its own.
@@ -9,9 +9,14 @@ It is distinct from `TabBar` (document-oriented, reorderable strips).
9
9
  ```ts
10
10
  import { TabScaffold } from "@kerfjs/ui/tab-scaffold";
11
11
  import { wireTabScaffold } from "@kerfjs/ui/wire-tab-scaffold";
12
- import "@kerfjs/ui/tab-scaffold.css";
13
12
  ```
14
13
 
14
+ In a browser bundler that honors the `browser` export condition (Vite, esbuild, and
15
+ webpack do by default), the import above also
16
+ loads TabScaffold's stylesheet and those of the components it renders internally
17
+ (the tab `Badge`). Without that condition, import the manual stylesheets instead:
18
+ `@kerfjs/ui/tab-scaffold.css` plus those components' CSS, or `@kerfjs/ui/styles.css`.
19
+
15
20
  ## Controlled selection
16
21
 
17
22
  The app owns the active tab (a signal); `TabScaffold` renders every tab's scene
@@ -44,7 +49,8 @@ const dispose = wireTabScaffold(root, {
44
49
  });
45
50
  ```
46
51
 
47
- Each `TabScaffoldTab` has an `id`, `label`, optional `icon`, and `content`. The
52
+ Each `TabScaffoldTab` has an `id`, `label`, optional `icon`, optional `badge`
53
+ and `badgeLabel` (see [Tab badges](#tab-badges)), and `content`. The
48
54
  bottom bar paints through the home-indicator safe area and pads for it and for
49
55
  the side insets, while each scene pads for the top and side insets (a scene
50
56
  whose only child is a `NavStack` or `Pane` lets that child own them; see
@@ -52,3 +58,68 @@ whose only child is a `NavStack` or `Pane` lets that child own them; see
52
58
  truncate horizontally with an ellipsis when a destination name exceeds its
53
59
  share of the bar. On larger device classes, promote the tab set to a
54
60
  `Workbench` rail or a persistent sidebar instead of a bottom bar.
61
+
62
+ ## Scroll divider
63
+
64
+ The bar has no permanent top border. With `wireScrollDividers(appRoot)`
65
+ installed (see [Layout › Scroll dividers](layout.md#scroll-dividers)), it shows
66
+ a line along its top edge only while the active scene's content continues below
67
+ it — not at the scroll end and not when the content fits. The line keys on
68
+ whatever actually scrolls in the scene: the scene itself or, through a sole
69
+ child, a `NavStack`'s active view or a `Pane`'s content. A `NavStack` with its
70
+ own bottom toolbar sits between them, so that toolbar's line applies instead.
71
+ The line is the bar's always-present 1px top border, colored only while shown,
72
+ so it never moves the bar or a tab.
73
+
74
+ `chromeDividers` configures it, with the same values as a `Pane`'s: `scroll`
75
+ (the default) follows the scroll state and draws nothing until the wiring runs;
76
+ `always` keeps the line, with or without the wiring; `none` drops it even when
77
+ the wiring reports content below.
78
+
79
+ ## Tab badges
80
+
81
+ Give a tab a `badge` (a count or short string such as `3`, `"99+"`, or
82
+ `"New"`) to show the iOS tab-bar badge: a compact, solid `danger` `Badge` at the
83
+ top-trailing corner of the tab's icon, growing outward as the text lengthens
84
+ (without an icon it sits above the label). The badge is decorative
85
+ (`aria-hidden`); its meaning reaches assistive technology through `badgeLabel`,
86
+ which the tab folds into its accessible name as `"<label>, <badgeLabel>"`:
87
+
88
+ ```tsx
89
+ {
90
+ id: "inbox",
91
+ label: "Inbox",
92
+ icon: <LucideIcon icon={Inbox} name="inbox" />,
93
+ badge: unread.value,
94
+ badgeLabel: `${unread.value} unread`,
95
+ content: <InboxStack />,
96
+ }
97
+ ```
98
+
99
+ `badgeLabel` defaults to the badge text itself, so supply a localized phrase
100
+ whenever the bare text would be ambiguous. Omitting `badge` (or passing an
101
+ empty string or a non-finite number) renders no badge and leaves the tab's name
102
+ as its label; `0` renders, so pass `undefined` when a count should disappear.
103
+ Keep the text short — clamp large counts (`"99+"`) in the app. The tone follows
104
+ the platform convention and is fixed; do not restyle the badge.
105
+
106
+ ### Dot badges
107
+
108
+ For new content without a count, pass `badge: true`. The tab shows the iOS
109
+ tab-bar dot — an 8px solid `danger` `Badge` (`size="dot"`) centered on the
110
+ icon's top-trailing corner (above the label without an icon). A dot has no
111
+ text to fall back on, so `badgeLabel` is required by the types:
112
+
113
+ ```tsx
114
+ {
115
+ id: "feed",
116
+ label: "Feed",
117
+ icon: <LucideIcon icon={Rss} name="rss" />,
118
+ badge: hasNewPosts.value ? true : undefined,
119
+ badgeLabel: "New activity",
120
+ content: <FeedStack />,
121
+ }
122
+ ```
123
+
124
+ The tab's accessible name becomes `"Feed, New activity"`. Pass `undefined` to
125
+ clear the dot; `false` is not a badge value.
@@ -11,22 +11,22 @@ public-signature artifact.
11
11
 
12
12
  ## Strengthened contracts
13
13
 
14
- | ID | Compile-time guarantee |
15
- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
- | `KUI-T001` | `ListHeader` is a passive heading, a fully named/icon-bearing trailing action, or a controlled disclosure with `action` + `expanded`. Props ignored by the selected mode are rejected. |
17
- | `KUI-T002` | `Select` has a visible `label` or an `ariaLabel`. |
18
- | `KUI-T003` | A literal `Select.value` belongs to its literal `choices`. |
19
- | `KUI-T004` | A literal `SegmentedControl.value` belongs to its literal `choices`. |
20
- | `KUI-T005` | `expanded`, `expandAction`, and `expandLabel` exist only on `TokenSearchField({ collapsible: true })`. |
21
- | `KUI-T006` | `TokenSearchEditorAttributes` accepts application `data-*` metadata but rejects component-owned identity, morph, count, and placeholder attributes. |
22
- | `KUI-T007` | `ToolbarText.maxLines` requires `wrap: true`; it is never silently ignored in typed code. |
23
- | `KUI-T008` | A literal `TabScaffold.active` id belongs to its literal `tabs`. |
24
- | `KUI-T009` | Adjacent-token keyboard removal requires `onRemoveToken`; disabling removal rejects the now-meaningless callback. |
25
- | `KUI-T010` | Finite public variants have named exported union types, including divider sides, tab activation, banner urgency, and all AppTab, TabBar, ToolbarControlGroup, and Select presentation axes; the convenience root barrel re-exports them. |
26
- | `KUI-T011` | Semantic component zones use the recursive `KerfUiContent` type: `SafeHtml`, runtime-empty booleans/nullish values, and readonly nested arrays are valid; arbitrary strings, numbers, and signals are rejected. Explicit text positions such as `ListInsetText` retain their text exception. |
27
- | `KUI-T012` | `List.gap` accepts finite `UiSpaceName` shorthands or a complete branded `CssLength`; raw strings and non-standalone `CssLengthExpression` arithmetic are rejected. `calc(plus(...))` promotes a typed expression to a complete value. |
28
- | `KUI-T013` | CSS-valued props preserve property grammar: `List.flex` uses `CssFlex`, `Skeleton` dimensions use `CssSize`/`CssLength`, and choice icons use `CssForegroundColor` (foreground tokens via `uiColor()` or `foregroundColorVar()`; fill aliases such as `uiColor('success')` are rejected); row declaration strings are absent. |
29
- | `KUI-T014` | Stable single-root visual components accept the explicit native `slot` attribute. Multi-root `ResizableRegion` and `CollapsiblePanel` remain excluded because no single returned element can own the assignment, and other arbitrary native attributes remain rejected. |
14
+ | ID | Compile-time guarantee |
15
+ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
+ | `KUI-T001` | `ListHeader` is a passive heading, a fully named/icon-bearing trailing action, or a controlled disclosure with `action` + `expanded`. Props ignored by the selected mode are rejected. |
17
+ | `KUI-T002` | `Select` has a visible `label` or an `ariaLabel`. |
18
+ | `KUI-T003` | A literal `Select.value` belongs to its literal `choices`. |
19
+ | `KUI-T004` | A literal `SegmentedControl.value` belongs to its literal `choices`. |
20
+ | `KUI-T005` | `expanded`, `expandAction`, and `expandLabel` exist only on `TokenSearchField({ collapsible: true })`. |
21
+ | `KUI-T006` | `TokenSearchEditorAttributes` accepts application `data-*` metadata but rejects component-owned identity, morph, count, and placeholder attributes. |
22
+ | `KUI-T007` | `ToolbarText.maxLines` requires `wrap: true`; it is never silently ignored in typed code. |
23
+ | `KUI-T008` | A literal `TabScaffold.active` id belongs to its literal `tabs`. |
24
+ | `KUI-T009` | Adjacent-token keyboard removal requires `onRemoveToken`; disabling removal rejects the now-meaningless callback. |
25
+ | `KUI-T010` | Finite public variants have named exported union types, including divider sides, tab activation, banner urgency, and all AppTab, TabBar, ToolbarControlGroup, and Select presentation axes; the convenience root barrel re-exports them. |
26
+ | `KUI-T011` | Semantic component zones use the recursive `KerfUiContent` type: `SafeHtml`, runtime-empty booleans/nullish values, and readonly nested arrays are valid; arbitrary strings, numbers, and signals are rejected. Explicit text positions such as `ListInsetText` retain their text exception. |
27
+ | `KUI-T012` | `List.gap` accepts finite `UiSpaceName` shorthands or a complete branded `CssLength`; raw strings and non-standalone `CssLengthExpression` arithmetic are rejected. `calc(plus(...))` promotes a typed expression to a complete value. |
28
+ | `KUI-T013` | CSS-valued props preserve property grammar: `List.flex` uses `CssFlex`, `Skeleton` dimensions use `CssSize`/`CssLength`, and choice icons use `CssForegroundColor` (foreground tokens via `uiColor()` or `foregroundColorVar()`; fill tokens such as `uiColor('success-fill-quiet')` are rejected); row declaration strings are absent. |
29
+ | `KUI-T014` | Stable single-root visual components accept the explicit native `slot` attribute. Multi-root `ResizableRegion` and `CollapsiblePanel` remain excluded because no single returned element can own the assignment, and other arbitrary native attributes remain rejected. |
30
30
 
31
31
  `KerfUiContent` lets conditionals and mapped component collections be direct
32
32
  siblings without an otherwise-unnecessary `Fragment`:
@@ -113,9 +113,10 @@ move errors into casts without making the integration safer.
113
113
  `{ keyboard: { removeAdjacentToken: false } }` when only caret navigation is
114
114
  wanted.
115
115
  - Add `wrap: true` anywhere `ToolbarText.maxLines` is used.
116
- - Replace `Toolbar({ divider: false })` with `dividerSides: ''`; the default
117
- remains a bottom divider, while canonical combinations such as `tr` and
118
- `trbl` select more physical edges.
116
+ - Replace `Toolbar({ divider: false })` with `dividerSides: ''`, which is now
117
+ the default (a toolbar pinned over scrolling content gets its divider from
118
+ `wireScrollDividers` through its `Pane`); canonical combinations such as `b`,
119
+ `tr`, and `trbl` select permanent physical edges.
119
120
  - Replace raw `List.gap` strings with a direct spacing shorthand (`"xs"`,
120
121
  `"m"`) or a complete value from `@kerfjs/ui/css-values`. Replace
121
122
  `gap="0.25rem"` with `gap={rem(0.25)}` and wrap sums with `calc(plus(...))`.
@@ -123,7 +124,7 @@ move errors into casts without making the integration safer.
123
124
  keyword; replace raw Skeleton dimensions with `px`/`rem`/`em`/`pct` builders;
124
125
  and replace raw choice colors with a foreground token such as
125
126
  `uiColor('success-on-quiet')` or an app-owned `foregroundColorVar()`.
126
- `SelectChoice.color` rejects fill aliases (`uiColor('success')`) and
127
+ `SelectChoice.color` rejects fill tokens (`uiColor('success-fill-quiet')`) and
127
128
  unqualified `colorVar()` values because a fill paints a nearly invisible
128
129
  icon.
129
130
  - Remove `style` from `ListItem` and `ListActionRow`; use `className`, public
@@ -61,7 +61,7 @@ policy, or analysis inputs to the containing application.
61
61
  | `KUI-L002` | error | A stylesheet references an unknown or private `--kui-*` token. |
62
62
  | `KUI-L003` | error | One element combines public classes that claim the same geometry. |
63
63
  | `KUI-L004` | review | Nested literal classes both add a content inset. |
64
- | `KUI-L005` | review | Consumer CSS forces a public component's dimensions. |
64
+ | `KUI-L005` | — | Retired; folded into `KUI-L019` (see below). |
65
65
  | `KUI-L006` | review | Literal spacing falls outside Kerf's approved scale. |
66
66
  | `KUI-L007` | error | A declared scroll owner is nested inside another scroll owner. |
67
67
  | `KUI-L008` | review | A dynamic class expression cannot be classified soundly. |
@@ -75,6 +75,10 @@ policy, or analysis inputs to the containing application.
75
75
  | `KUI-L016` | error | A removed declaration-list escape hatch is used. |
76
76
  | `KUI-L017` | review | A valid but exceptional off-scale shorthand needs justification. |
77
77
  | `KUI-L018` | review | A loud fill override lacks a governing `on-loud`, or the literal pair is below 4.5:1. |
78
+ | `KUI-L019` | error | A rule's subject is another package's component (class, `data-component`, or tag). |
79
+ | `KUI-L020` | error | CSS reads or writes another component's private `--_*` variable. |
80
+ | `KUI-L021` | error | CSS overrides a component token that a typed prop sets. |
81
+ | `KUI-L022` | error | A class placed on a component's root is styled by the application (a hook class). |
78
82
 
79
83
  Errors are provable contract violations and make the command exit 1. Review
80
84
  findings are deliberately heuristic and do not fail by default; pass
@@ -92,9 +96,10 @@ CSS extension points. A component catalog may additionally list shadow-part
92
96
  names in `boundaries.publicParts`; all other `::part()` targets are private.
93
97
  Part names are scoped to that entry's `boundaries.rootClass`, so an extension
94
98
  point on one component does not authorize the same-named part on another.
95
- Parent-owned placement and selectors for application-owned content remain
96
- allowed because the analyzer only reserves `.kui-*`, `--kui-*`, and cataloged
97
- shadow boundaries.
99
+ Selectors for application-owned content remain allowed, including in a
100
+ component's context (`.kui-pane > .my-content`): a public class is an
101
+ extension point for naming a context, not for restyling the component itself,
102
+ which `KUI-L019` reports.
98
103
 
99
104
  Catalog entries may also publish `cssValueProps`. Each path names its grammar,
100
105
  finite shorthands, canonical and exceptional scale steps, accepted typed
@@ -103,6 +108,94 @@ uses that same metadata for direct component calls and JSX, including nested
103
108
  paths such as `choices[].color`; consumer catalogs receive identical checks.
104
109
  Dynamic values remain a type-system responsibility rather than being guessed.
105
110
 
111
+ ## Component ownership (`KUI-L019`–`KUI-L022`)
112
+
113
+ Components own their styles and are configured, never overridden. These
114
+ diagnostics apply the package's own `check:css-ownership` rules to
115
+ applications and component packages; the selector parsing is the same module
116
+ (`analyzer/selectors.mjs`), so the package rule and the consumer rule agree on
117
+ which element a selector styles.
118
+
119
+ - **`KUI-L019` — a rule restyles a cataloged component.** The rule's subject
120
+ (the rightmost compound of each selector, ignoring `:has()` and `:not()`
121
+ arguments) names a public class of a cataloged component (`.kui-toolbar`,
122
+ `.kui-list-item__label`, `.acme-meter`), its `[data-component="…"]` root, or
123
+ a Web Awesome tag the package themes (`wa-button`, `wa-select`), and the rule
124
+ sets at least one ordinary property. A `::part()` subject is left to
125
+ `KUI-L011`; a rule that only sets custom properties is configuration, judged
126
+ by the token rules. Forcing a component's dimensions is one such restyle
127
+ and reports here once: `.kui-pane { width: 300px }`.
128
+ - **`KUI-L020` — another component's private variable.** CSS reads or writes a
129
+ `--_<root>-*` variable of a cataloged component (`--_kui-list-gap`), or any
130
+ `--_kui-*` variable.
131
+ - **`KUI-L021` — a token a typed prop sets.** CSS assigns `--<root>-<prop>`
132
+ where the component has that typed prop (`--kui-list-gap` is `List gap`,
133
+ `--kui-floating-toolbar-inset` is `FloatingToolbar inset`). The message
134
+ quotes the prop's cataloged example.
135
+ - **`KUI-L022` — a hook class on a component's root.** A literal class on an
136
+ imported component's JSX (`<Toolbar className="header" />`) is the subject
137
+ of a rule in a stylesheet that file imports, so the application restyles the
138
+ component through it. The evidence names the stylesheet, line, and selector.
139
+
140
+ Every message names the configuration to use (the component's typed props,
141
+ public tokens, and variants from its catalog entry) and ends with the same
142
+ route for a real need that has none: report the component gap to the owning
143
+ package (open a feature request) instead of overriding it.
144
+
145
+ What stays allowed mirrors the package rule, where a child styles itself in a
146
+ parent's context:
147
+
148
+ ```css
149
+ /* Your own element in a component's context. */
150
+ .kui-toolbar > .my-widget {
151
+ margin-inline-start: auto;
152
+ }
153
+ /* Keying your own element on a component's state. */
154
+ .shell:has(.kui-select[open]) .my-status {
155
+ opacity: 0.5;
156
+ }
157
+ /* Configuring a component through a public token with no typed prop. */
158
+ .kui-list {
159
+ --kui-list-divider-color: var(--kui-color-neutral-border-quiet);
160
+ }
161
+
162
+ /* KUI-L019, KUI-L020, KUI-L021. */
163
+ .kui-toolbar {
164
+ padding: 0;
165
+ }
166
+ .app {
167
+ --_kui-list-gap: 2px;
168
+ --kui-list-gap: 4px;
169
+ }
170
+ ```
171
+
172
+ Ownership is decided per package: a stylesheet belongs to the package whose
173
+ `package.json` is nearest it, and only other packages' catalog entries are
174
+ foreign to it. A component package built on Kerf UI therefore styles its own
175
+ cataloged components (including `.kui-toolbar > .acme-meter` from its own
176
+ stylesheet) without findings, while an application that restyles
177
+ `.acme-meter` is reported against `@acme/widgets`. All four ids are
178
+ ownership-boundary diagnostics, so `--adoption` reports them as review
179
+ findings. `eslint-plugin-kerfjs`'s `ui-component-ownership` rule reports
180
+ `KUI-L020` and `KUI-L021` in JavaScript and TypeScript (style strings, style
181
+ objects, and `style.setProperty()`), where this analyzer reads only CSS.
182
+
183
+ ### Retired: `KUI-L005` (forced component dimension)
184
+
185
+ `KUI-L005` used to report, as a review finding, any `width`/`height`/`min-*`/
186
+ `max-*` declaration in a rule that mentioned a public component class
187
+ anywhere in its selector. `KUI-L019` now reports every restyle whose subject is
188
+ a cataloged component — dimensions included — so the subject case was
189
+ double-reported (an error and a review finding for one declaration). Its only
190
+ other shape, a public class as an ancestor
191
+ (`.kui-pane .app-sidebar { width: 240px }`), sizes the application's own
192
+ element in the component's context, which the ownership rules explicitly
193
+ allow. With no unique true positive left,
194
+ the rule was retired rather than narrowed. The analyzer no longer emits it;
195
+ the id stays in `ai/application-ui-diagnostic-ids-v1.json` (listed under
196
+ `retired`) so a profile exception that already names it still loads. Remove
197
+ such exceptions at your convenience.
198
+
106
199
  ## Loud fill / on-loud pairing (`KUI-L018`)
107
200
 
108
201
  The shipped Web Awesome theme pairs every `--wa-color-{tone}-fill-loud` with a
@@ -207,6 +300,7 @@ validator.
207
300
 
208
301
  Review findings remain visible until explicitly suppressed. Avoid suppressing
209
302
  `KUI-L001`, `KUI-L002`, `KUI-L003`, `KUI-L007`, `KUI-L009`, `KUI-L010`,
210
- `KUI-L011`, `KUI-L012`, `KUI-L013`, `KUI-L014`, `KUI-L015`, or `KUI-L016`:
303
+ `KUI-L011`, `KUI-L012`, `KUI-L013`, `KUI-L014`, `KUI-L015`, `KUI-L016`,
304
+ `KUI-L019`, `KUI-L020`, `KUI-L021`, or `KUI-L022`:
211
305
  those indicate a
212
306
  definite boundary or parsing failure rather than an aesthetic judgment.
package/docs/ui-doctor.md CHANGED
@@ -30,6 +30,16 @@ that lacks a governing `--wa-color-{tone}-on-loud` (in the same or an ancestor
30
30
  scope, in any stylesheet the same entry loads), or whose literal pair measures
31
31
  below 4.5:1 (see [the analyzer rule](./ui-analyzer.md#loud-fill--on-loud-pairing-kui-l018)).
32
32
 
33
+ Component ownership is part of the same loop. The analyzer stage reports
34
+ application or third-party CSS that restyles another package's component
35
+ (`KUI-L019`), touches its private `--_*` variables (`KUI-L020`), overrides a
36
+ token a typed prop sets (`KUI-L021`), or styles it through a hook class on its
37
+ root (`KUI-L022`); the ESLint stage's `ui-component-ownership` rule reports
38
+ `KUI-L020` / `KUI-L021` in script. Each finding's `action` points at the
39
+ component's configuration, and its message routes a need with no prop to a
40
+ component-gap report instead of an override (see
41
+ [the analyzer rule](./ui-analyzer.md#component-ownership-kui-l019kui-l022)).
42
+
33
43
  The browser evaluator is different: it runs the application and is disabled by default. It only runs when configuration supplies `browser.url` or the command receives `--browser-url`. Start and authorize the target application separately.
34
44
 
35
45
  An unavailable or failed stage does not prevent independent stages from reporting. Its final exit is still a configuration failure, so a partial run cannot appear clean.
package/docs/ux-demo.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # UX catalog contract
2
2
 
3
- Run `npm run dev` from `ui/`. The catalog is a development and review surface, not a second implementation. The development server allows repo-owned assets used by the catalog, and its production bundle uses relative asset URLs so the complete catalog remains intact below preview and proxy paths.
3
+ Run `npm run dev` from `ui/`. The catalog is a development and review surface, not a second implementation. The development server reloads component source edits without a restart: component entries still load through their `dist/browser` wrappers, so each component's reachable CSS is exercised as consumers receive it, but the JavaScript those wrappers re-export (and every other `@kerfjs/ui` subpath) resolves to `src/`, and CSS resolves to `src/` as well. A component that gains a new CSS dependency needs a restart, because the wrapper listing its stylesheets is regenerated only by the build. Production demo builds, previews, and the browser tests use `dist/`. The development server allows repo-owned assets used by the catalog, and its production bundle uses relative asset URLs so the complete catalog remains intact below preview and proxy paths.
4
4
 
5
5
  Every Playwright entry point builds current source before the preview server
6
6
  starts. `npm run test:e2e -- tests/browser/row.spec.ts` and a direct focused
@@ -51,6 +51,6 @@ deterministic state, and semantic layout owners from the [recipe guide](./recipe
51
51
  - The footer action log is an `aria-live` region, making interactions visible to both people and browser tests.
52
52
  - 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.
53
53
  - Browser coverage walks every Kerf, composition, and Web Awesome route, checks every listed Web Awesome custom element is registered in its focused specimen, exercises the ecosystem disclosure and both relationship directions, validates offline source/guidance hrefs and accessible wide/narrow/200%-zoom wrapping, and covers tab selection/reordering/overflow, semantic palettes and scoped overrides, light/dark rendering, toolbar popup and responsive find behavior, single/multiline search alignment, menu alignment, banner urgency, keyboard resizing, live custom-element values, and representative wide/narrow captures.
54
- - The production build keeps the catalog shell and Kerf routes in the entry chunk, then dynamically imports the complete Web Awesome specimen and registration surface when an ecosystem route or the ToolbarControlGroup popup specimen is selected. Directly addressed routes pass through the same loader.
54
+ - The production build keeps the catalog shell and Kerf routes in the entry chunk and moves the third-party code loaded at startup (the Web Awesome select and popup-menu registrations with their Lit and Floating UI runtime) into an eagerly preloaded `vendor` chunk, so neither nears the largest-chunk limit in `demo-bundle-budget.json`; only modules statically reachable from the entry qualify, so lazy-only dependencies stay in their lazy chunks. It then dynamically imports the complete Web Awesome specimen and registration surface when an ecosystem route or the ToolbarControlGroup popup specimen is selected. Directly addressed routes pass through the same loader.
55
55
 
56
56
  Before handoff, inspect the actual captures for readability, context, alignment, clipping, spacing, responsiveness, focus, and obvious defects. Fix and recapture rather than treating the screenshot as proof by itself.
@@ -176,8 +176,7 @@ their open state uses the independent `rotate` property.
176
176
 
177
177
  That scale applies to each Web Awesome component's intrinsic glyph and is
178
178
  independent of Kerf `DisclosureArrow`, whose default box is 18px relative to
179
- the root font size and whose consumer override is
180
- `--kui-disclosure-arrow-size`.
179
+ the root font size and whose consumer override is its typed `size` prop.
181
180
 
182
181
  Tooltip and Popover use arrowless floating surfaces by default, matching Hot
183
182
  Sheet 2. The theme sets Web Awesome's public `--wa-tooltip-arrow-size` token to