@kerfjs/ui 5.0.0-beta.98 → 5.0.0-beta.99

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 (407) hide show
  1. package/README.md +42 -1
  2. package/ai/application-ui-diagnostic-ids-v1.json +1 -0
  3. package/ai/component-catalog.json +487 -66
  4. package/ai/component-catalog.schema.json +1 -1
  5. package/ai/component-composition-rules.json +22 -2
  6. package/ai/component-composition.json +360 -33
  7. package/ai/component-composition.schema.json +1 -1
  8. package/ai/components/README.md +18 -17
  9. package/ai/components/anchored-surface.md +13 -0
  10. package/ai/components/application-tabs.md +1 -1
  11. package/ai/components/chip.md +9 -5
  12. package/ai/components/choice-chips.md +15 -8
  13. package/ai/components/collapsible-panel.md +3 -2
  14. package/ai/components/composer.md +83 -0
  15. package/ai/components/content-item.md +2 -2
  16. package/ai/components/corner-status.md +1 -1
  17. package/ai/components/document-baseline.md +3 -3
  18. package/ai/components/foundation.md +2 -2
  19. package/ai/components/layout.md +1 -1
  20. package/ai/components/list-action-row.md +5 -1
  21. package/ai/components/list-item.md +5 -1
  22. package/ai/components/list.md +19 -3
  23. package/ai/components/nav-stack.md +11 -2
  24. package/ai/components/pane.md +13 -4
  25. package/ai/components/popup-menu.md +4 -4
  26. package/ai/components/recipe-app-shell.md +3 -3
  27. package/ai/components/recipe-collapsible-sidebar.md +3 -3
  28. package/ai/components/recipe-compact-toolbar.md +1 -1
  29. package/ai/components/recipe-composer-form.md +3 -3
  30. package/ai/components/recipe-list-detail-dialog.md +2 -2
  31. package/ai/components/recipe-list-workspace-states.md +3 -3
  32. package/ai/components/recipe-loading-inspector.md +1 -1
  33. package/ai/components/recipe-navigation-sidebar.md +3 -3
  34. package/ai/components/recipe-navigation-stack.md +3 -3
  35. package/ai/components/recipe-workspace-header.md +3 -3
  36. package/ai/components/resize.md +2 -2
  37. package/ai/components/split-view.md +4 -2
  38. package/ai/components/sunken-panel.md +2 -2
  39. package/ai/components/surface-scaffold.md +15 -3
  40. package/ai/components/tab-bar.md +1 -0
  41. package/ai/components/tab-navigator.md +4 -0
  42. package/ai/components/toolbar-control-group.md +2 -0
  43. package/ai/components/toolbar-text.md +1 -0
  44. package/ai/components/toolbar.md +1 -0
  45. package/ai/components/workbench.md +1 -0
  46. package/ai/public-api-signatures-v1.md +487 -77
  47. package/ai/skill.md +40 -13
  48. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  49. package/analyzer/component-ownership.mjs +1 -1
  50. package/analyzer/index.mjs +41 -14
  51. package/dist/anchored-surface.d.ts +30 -2
  52. package/dist/anchored-surface.js +1 -1
  53. package/dist/app-tab.js +1 -1
  54. package/dist/browser/composer.js +4 -0
  55. package/dist/catalog-resources.d.ts +3 -2
  56. package/dist/catalog-resources.js +4 -4
  57. package/dist/catalog-resources.js.map +1 -1
  58. package/dist/catalog.d.ts +22 -5
  59. package/dist/catalog.js +50 -31
  60. package/dist/catalog.js.map +1 -1
  61. package/dist/chip.d.ts +41 -9
  62. package/dist/chip.js +1 -1
  63. package/dist/choice-chips.d.ts +40 -16
  64. package/dist/choice-chips.js +1 -1
  65. package/dist/chunk-34U4ANDJ.js +119 -0
  66. package/dist/chunk-34U4ANDJ.js.map +1 -0
  67. package/dist/{chunk-36TL6BCR.js → chunk-3GLYIIFR.js} +34 -6
  68. package/dist/chunk-3GLYIIFR.js.map +1 -0
  69. package/dist/{chunk-HWY4BSLS.js → chunk-3VXDSIBJ.js} +2 -2
  70. package/dist/{chunk-HWY4BSLS.js.map → chunk-3VXDSIBJ.js.map} +1 -1
  71. package/dist/chunk-3WYREXW4.js +120 -0
  72. package/dist/chunk-3WYREXW4.js.map +1 -0
  73. package/dist/chunk-AV7CPAPT.js +82 -0
  74. package/dist/chunk-AV7CPAPT.js.map +1 -0
  75. package/dist/{chunk-XK4E225T.js → chunk-B53PS2LE.js} +2 -2
  76. package/dist/{chunk-XK4E225T.js.map → chunk-B53PS2LE.js.map} +1 -1
  77. package/dist/{chunk-R42D2BAO.js → chunk-CA7LNX3J.js} +2 -2
  78. package/dist/{chunk-R42D2BAO.js.map → chunk-CA7LNX3J.js.map} +1 -1
  79. package/dist/{chunk-AZWE2FB5.js → chunk-CMETHPKR.js} +12 -15
  80. package/dist/chunk-CMETHPKR.js.map +1 -0
  81. package/dist/{chunk-Y43B6DS3.js → chunk-CVD5IZGB.js} +2 -2
  82. package/dist/{chunk-Y43B6DS3.js.map → chunk-CVD5IZGB.js.map} +1 -1
  83. package/dist/{chunk-6STQXRT4.js → chunk-CXZXHZTR.js} +121 -12
  84. package/dist/chunk-CXZXHZTR.js.map +1 -0
  85. package/dist/{chunk-GZE2XVKC.js → chunk-DZJ4GBSP.js} +3 -3
  86. package/dist/{chunk-GZE2XVKC.js.map → chunk-DZJ4GBSP.js.map} +1 -1
  87. package/dist/chunk-ELCMSQNJ.js +49 -0
  88. package/dist/chunk-ELCMSQNJ.js.map +1 -0
  89. package/dist/{chunk-P74VXMB6.js → chunk-EOO6PYGF.js} +7 -14
  90. package/dist/chunk-EOO6PYGF.js.map +1 -0
  91. package/dist/{chunk-PRG4CL3Q.js → chunk-FR76ZJQC.js} +21 -19
  92. package/dist/chunk-FR76ZJQC.js.map +1 -0
  93. package/dist/{chunk-QQ3TTWFH.js → chunk-G3J7JDP6.js} +1 -1
  94. package/dist/{chunk-QQ3TTWFH.js.map → chunk-G3J7JDP6.js.map} +1 -1
  95. package/dist/{chunk-IGTWXPTN.js → chunk-HGV7J4T6.js} +3 -3
  96. package/dist/{chunk-IGTWXPTN.js.map → chunk-HGV7J4T6.js.map} +1 -1
  97. package/dist/{chunk-YWQGSUF2.js → chunk-HLDRH23D.js} +3 -3
  98. package/dist/{chunk-YWQGSUF2.js.map → chunk-HLDRH23D.js.map} +1 -1
  99. package/dist/{chunk-PBT3CPPY.js → chunk-HQZYKJ56.js} +24 -4
  100. package/dist/chunk-HQZYKJ56.js.map +1 -0
  101. package/dist/{chunk-V6W56V3N.js → chunk-HZPHCSCS.js} +16 -3
  102. package/dist/chunk-HZPHCSCS.js.map +1 -0
  103. package/dist/{chunk-GJYQ27C2.js → chunk-INI4WDWC.js} +8 -1
  104. package/dist/chunk-INI4WDWC.js.map +1 -0
  105. package/dist/{chunk-U657IHFB.js → chunk-IQSRHXZR.js} +10 -4
  106. package/dist/chunk-IQSRHXZR.js.map +1 -0
  107. package/dist/{chunk-KQDU7Z7P.js → chunk-K4AT3V3J.js} +2 -1
  108. package/dist/chunk-K4AT3V3J.js.map +1 -0
  109. package/dist/{chunk-QIAPOWHH.js → chunk-KCS6NJ3O.js} +2 -2
  110. package/dist/{chunk-QIAPOWHH.js.map → chunk-KCS6NJ3O.js.map} +1 -1
  111. package/dist/{chunk-UKKRPP56.js → chunk-LOSRXZAH.js} +2 -2
  112. package/dist/{chunk-UKKRPP56.js.map → chunk-LOSRXZAH.js.map} +1 -1
  113. package/dist/{chunk-ROZLJULF.js → chunk-MF6ZUUJZ.js} +8 -3
  114. package/dist/chunk-MF6ZUUJZ.js.map +1 -0
  115. package/dist/{chunk-6IVNMC6R.js → chunk-MHMWQ7RB.js} +2 -2
  116. package/dist/{chunk-6IVNMC6R.js.map → chunk-MHMWQ7RB.js.map} +1 -1
  117. package/dist/{chunk-JYD7G4RG.js → chunk-PIVY32AE.js} +15 -5
  118. package/dist/chunk-PIVY32AE.js.map +1 -0
  119. package/dist/{chunk-527IAEWG.js → chunk-PYBBYSZ3.js} +10 -17
  120. package/dist/chunk-PYBBYSZ3.js.map +1 -0
  121. package/dist/chunk-QDSZTQXL.js +28 -0
  122. package/dist/chunk-QDSZTQXL.js.map +1 -0
  123. package/dist/{chunk-YXOMI7ES.js → chunk-QIG4IGUP.js} +2 -2
  124. package/dist/{chunk-YXOMI7ES.js.map → chunk-QIG4IGUP.js.map} +1 -1
  125. package/dist/{chunk-6BRRUPJY.js → chunk-QROYYGWU.js} +3 -3
  126. package/dist/chunk-QROYYGWU.js.map +1 -0
  127. package/dist/{chunk-FOQK4F3L.js → chunk-RHD7RKGI.js} +28 -7
  128. package/dist/chunk-RHD7RKGI.js.map +1 -0
  129. package/dist/{chunk-BBU2SPRE.js → chunk-T4WJ5VGP.js} +4 -4
  130. package/dist/{chunk-BBU2SPRE.js.map → chunk-T4WJ5VGP.js.map} +1 -1
  131. package/dist/{chunk-6NOLL5KG.js → chunk-TGLUDQP2.js} +3 -3
  132. package/dist/{chunk-6NOLL5KG.js.map → chunk-TGLUDQP2.js.map} +1 -1
  133. package/dist/{chunk-HO6FU3G7.js → chunk-TLMMFQWM.js} +4 -4
  134. package/dist/{chunk-HO6FU3G7.js.map → chunk-TLMMFQWM.js.map} +1 -1
  135. package/dist/chunk-UMKV7YVQ.js +20 -0
  136. package/dist/chunk-UMKV7YVQ.js.map +1 -0
  137. package/dist/{chunk-M2NRCFRO.js → chunk-VFYO3UGZ.js} +2 -2
  138. package/dist/{chunk-M2NRCFRO.js.map → chunk-VFYO3UGZ.js.map} +1 -1
  139. package/dist/{chunk-YOKEL75M.js → chunk-WVS6CUCT.js} +2 -2
  140. package/dist/{chunk-YOKEL75M.js.map → chunk-WVS6CUCT.js.map} +1 -1
  141. package/dist/{chunk-MN3POJFN.js → chunk-YMUIQRWK.js} +2 -2
  142. package/dist/{chunk-MN3POJFN.js.map → chunk-YMUIQRWK.js.map} +1 -1
  143. package/dist/{chunk-Y3LC7JQO.js → chunk-YOA5W5ZH.js} +2 -2
  144. package/dist/{chunk-Y3LC7JQO.js.map → chunk-YOA5W5ZH.js.map} +1 -1
  145. package/dist/{chunk-44A6LP54.js → chunk-YQ2AC4E2.js} +2 -2
  146. package/dist/{chunk-44A6LP54.js.map → chunk-YQ2AC4E2.js.map} +1 -1
  147. package/dist/chunk-ZNROKDSZ.js +54 -0
  148. package/dist/chunk-ZNROKDSZ.js.map +1 -0
  149. package/dist/collapsible-panel.d.ts +3 -2
  150. package/dist/collapsible-panel.js +4 -3
  151. package/dist/collapsible-panel.js.map +1 -1
  152. package/dist/composer.d.ts +62 -0
  153. package/dist/composer.js +5 -0
  154. package/dist/composer.js.map +1 -0
  155. package/dist/corner-status.d.ts +1 -1
  156. package/dist/corner-status.js +1 -1
  157. package/dist/css-values.d.ts +9 -1
  158. package/dist/css-values.js +1 -1
  159. package/dist/disclosure-arrow.js +1 -1
  160. package/dist/empty-state.js +1 -1
  161. package/dist/grid.js +1 -1
  162. package/dist/icon-button.js +1 -1
  163. package/dist/icon-toggle-button.js +1 -1
  164. package/dist/index.d.ts +10 -5
  165. package/dist/index.js +40 -37
  166. package/dist/list-action-row.d.ts +10 -2
  167. package/dist/list-action-row.js +1 -1
  168. package/dist/list-header.js +1 -1
  169. package/dist/list-icon-tile-B8dr97OW.d.ts +18 -0
  170. package/dist/list-item.d.ts +10 -3
  171. package/dist/list-item.js +1 -1
  172. package/dist/list.d.ts +36 -4
  173. package/dist/list.js +1 -1
  174. package/dist/loading-spinner.js +1 -1
  175. package/dist/lucide-icon.js +1 -1
  176. package/dist/{nav-stack-Cp3Y8kJ9.d.ts → nav-stack-VXTumzKa.d.ts} +48 -3
  177. package/dist/nav-stack.d.ts +3 -2
  178. package/dist/nav-stack.js +2 -1
  179. package/dist/pane.d.ts +40 -3
  180. package/dist/pane.js +1 -1
  181. package/dist/{panel-toolbar-CL9tYOkh.d.ts → panel-toolbar-vg6Ir5Dy.d.ts} +6 -1
  182. package/dist/popup-menu-register.d.ts +2 -1
  183. package/dist/popup-menu-register.js +97 -22
  184. package/dist/popup-menu-register.js.map +1 -1
  185. package/dist/popup-menu.js +1 -1
  186. package/dist/row.js +1 -1
  187. package/dist/scroll-clearance-BikHljTc.d.ts +14 -0
  188. package/dist/select.js +1 -1
  189. package/dist/skeleton.js +1 -1
  190. package/dist/spacer.js +1 -1
  191. package/dist/split-button.js +1 -1
  192. package/dist/split-view.d.ts +7 -5
  193. package/dist/split-view.js +6 -3
  194. package/dist/split-view.js.map +1 -1
  195. package/dist/state-banner.d.ts +6 -1
  196. package/dist/state-banner.js +1 -1
  197. package/dist/styles/anchored-surface.css +27 -1
  198. package/dist/styles/catalog/components/catalog-example.css +33 -0
  199. package/dist/styles/catalog/components/catalog-stage.css +7 -0
  200. package/dist/styles/chip.css +70 -0
  201. package/dist/styles/components/actions/popup-menu/popup-menu.css +14 -4
  202. package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +75 -1
  203. package/dist/styles/components/actions/toolbar-text/toolbar-text.css +66 -0
  204. package/dist/styles/components/collections/list/list.css +77 -7
  205. package/dist/styles/components/collections/list-action-row/list-action-row.css +177 -22
  206. package/dist/styles/components/collections/list-item/list-item.css +158 -11
  207. package/dist/styles/components/feedback/anchored-surface/anchored-surface.css +27 -1
  208. package/dist/styles/components/feedback/chip/chip.css +70 -0
  209. package/dist/styles/components/feedback/state-banner/state-banner.css +24 -13
  210. package/dist/styles/components/forms/composer/composer.css +100 -0
  211. package/dist/styles/components/forms/token-search-field/token-search-field.css +13 -2
  212. package/dist/styles/components/layout/pane/pane.css +81 -2
  213. package/dist/styles/components/navigation/nav-stack/nav-stack.css +84 -0
  214. package/dist/styles/components/navigation/tab-bar/tab-bar.css +15 -4
  215. package/dist/styles/components/navigation/tab-navigator/tab-navigator.css +79 -4
  216. package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +195 -0
  217. package/dist/styles/composer.css +100 -0
  218. package/dist/styles/foundation.css +40 -0
  219. package/dist/styles/list-action-row.css +177 -22
  220. package/dist/styles/list-item.css +158 -11
  221. package/dist/styles/list.css +77 -7
  222. package/dist/styles/nav-stack.css +84 -0
  223. package/dist/styles/pane.css +81 -2
  224. package/dist/styles/popup-menu.css +14 -4
  225. package/dist/styles/state-banner.css +24 -13
  226. package/dist/styles/styles.css +1 -0
  227. package/dist/styles/surface-scaffold.css +195 -0
  228. package/dist/styles/tab-bar.css +15 -4
  229. package/dist/styles/tab-navigator.css +79 -4
  230. package/dist/styles/token-search-field.css +13 -2
  231. package/dist/styles/toolbar-control-group.css +75 -1
  232. package/dist/styles/toolbar-text.css +66 -0
  233. package/dist/surface-scaffold-register.d.ts +6 -1
  234. package/dist/surface-scaffold-register.js +89 -3
  235. package/dist/surface-scaffold-register.js.map +1 -1
  236. package/dist/surface-scaffold.d.ts +66 -7
  237. package/dist/surface-scaffold.js +1 -1
  238. package/dist/tab-navigator.d.ts +23 -2
  239. package/dist/tab-navigator.js +2 -0
  240. package/dist/tab-navigator.js.map +1 -1
  241. package/dist/token-search-field.d.ts +1 -1
  242. package/dist/token-search-field.js +1 -1
  243. package/dist/{token-search-model-DTcTXWMS.d.ts → token-search-model-aYR0YAei.d.ts} +32 -2
  244. package/dist/token-search-model.d.ts +1 -1
  245. package/dist/toolbar-control-group.js +1 -1
  246. package/dist/toolbar-text.js +1 -1
  247. package/dist/toolbar.d.ts +1 -0
  248. package/dist/value-table.js +1 -1
  249. package/dist/wire-catalog.d.ts +3 -2
  250. package/dist/wire-catalog.js +2 -2
  251. package/dist/wire-composers.d.ts +16 -0
  252. package/dist/wire-composers.js +3 -0
  253. package/dist/wire-composers.js.map +1 -0
  254. package/dist/wire-list-focus-scroll.d.ts +16 -0
  255. package/dist/wire-list-focus-scroll.js +3 -0
  256. package/dist/wire-list-focus-scroll.js.map +1 -0
  257. package/dist/wire-nav-stack.js +135 -0
  258. package/dist/wire-nav-stack.js.map +1 -1
  259. package/dist/wire-scroll-dividers.d.ts +9 -0
  260. package/dist/wire-scroll-dividers.js +1 -1
  261. package/dist/wire-tab-bars.js +1 -1
  262. package/dist/wire-tab-navigator.d.ts +3 -1
  263. package/dist/wire-tab-navigator.js +133 -1
  264. package/dist/wire-tab-navigator.js.map +1 -1
  265. package/dist/wire-token-search-fields.d.ts +1 -1
  266. package/dist/wire-token-search-fields.js +1 -1
  267. package/dist/workbench.d.ts +8 -3
  268. package/dist/workbench.js +2 -1
  269. package/docs/accessibility.md +132 -3
  270. package/docs/anchored-surface.md +46 -4
  271. package/docs/app-layouts.md +23 -2
  272. package/docs/apple-hig.md +1 -0
  273. package/docs/catalog.md +54 -8
  274. package/docs/collapsible-panel.md +9 -6
  275. package/docs/component-contract.md +41 -12
  276. package/docs/component-selection.md +143 -19
  277. package/docs/composer.md +143 -0
  278. package/docs/design/templates/choice-chips/multiple-dark.svg +1 -0
  279. package/docs/design/templates/choice-chips/multiple.svg +1 -0
  280. package/docs/design/templates/choice-chips-dark.svg +1 -1
  281. package/docs/design/templates/choice-chips.svg +1 -1
  282. package/docs/design/templates/composer/chat-dark.svg +1 -0
  283. package/docs/design/templates/composer/chat.svg +1 -0
  284. package/docs/design/templates/composer/prompt-dark.svg +1 -0
  285. package/docs/design/templates/composer/prompt.svg +1 -0
  286. package/docs/design/templates/composer-dark.svg +1 -0
  287. package/docs/design/templates/composer.svg +1 -0
  288. package/docs/design/templates/empty-state/filtered-dark.svg +1 -1
  289. package/docs/design/templates/empty-state/filtered.svg +1 -1
  290. package/docs/design/templates/empty-state/with-action-dark.svg +1 -1
  291. package/docs/design/templates/empty-state/with-action.svg +1 -1
  292. package/docs/design/templates/empty-state-dark.svg +1 -1
  293. package/docs/design/templates/empty-state.svg +1 -1
  294. package/docs/design/templates/list/compact-dark.svg +1 -1
  295. package/docs/design/templates/list/compact.svg +1 -1
  296. package/docs/design/templates/list-action-row/default-dark.svg +1 -1
  297. package/docs/design/templates/list-action-row/default.svg +1 -1
  298. package/docs/design/templates/list-action-row/icon-tile-dark.svg +1 -0
  299. package/docs/design/templates/list-action-row/icon-tile.svg +1 -0
  300. package/docs/design/templates/list-action-row/selected-dark.svg +1 -1
  301. package/docs/design/templates/list-action-row/selected.svg +1 -1
  302. package/docs/design/templates/list-action-row/trailing-menu-dark.svg +1 -1
  303. package/docs/design/templates/list-action-row/trailing-menu.svg +1 -1
  304. package/docs/design/templates/list-action-row-dark.svg +1 -1
  305. package/docs/design/templates/list-action-row.svg +1 -1
  306. package/docs/design/templates/list-dark.svg +1 -1
  307. package/docs/design/templates/list-header/size-l-dark.svg +1 -1
  308. package/docs/design/templates/list-header/size-l.svg +1 -1
  309. package/docs/design/templates/list-header/size-m-dark.svg +1 -1
  310. package/docs/design/templates/list-header/size-m.svg +1 -1
  311. package/docs/design/templates/list-header/size-xl-dark.svg +1 -1
  312. package/docs/design/templates/list-header/size-xl.svg +1 -1
  313. package/docs/design/templates/list-header-dark.svg +1 -1
  314. package/docs/design/templates/list-header.svg +1 -1
  315. package/docs/design/templates/list-item/icon-tile-dark.svg +1 -0
  316. package/docs/design/templates/list-item/icon-tile.svg +1 -0
  317. package/docs/design/templates/list-item/selected-dark.svg +1 -1
  318. package/docs/design/templates/list-item/selected.svg +1 -1
  319. package/docs/design/templates/list-item-dark.svg +1 -1
  320. package/docs/design/templates/list-item.svg +1 -1
  321. package/docs/design/templates/list.svg +1 -1
  322. package/docs/design/templates/segmented-control-dark.svg +1 -1
  323. package/docs/design/templates/segmented-control.svg +1 -1
  324. package/docs/design/templates/skeleton-dark.svg +1 -1
  325. package/docs/design/templates/skeleton.svg +1 -1
  326. package/docs/design/templates/state-banner/danger-dark.svg +1 -1
  327. package/docs/design/templates/state-banner/danger.svg +1 -1
  328. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -1
  329. package/docs/design/templates/state-banner/info-badge.svg +1 -1
  330. package/docs/design/templates/state-banner/info-dark.svg +1 -1
  331. package/docs/design/templates/state-banner/info.svg +1 -1
  332. package/docs/design/templates/state-banner/strip-dark.svg +1 -1
  333. package/docs/design/templates/state-banner/strip.svg +1 -1
  334. package/docs/design/templates/state-banner/success-dark.svg +1 -1
  335. package/docs/design/templates/state-banner/success.svg +1 -1
  336. package/docs/design/templates/state-banner/warning-dark.svg +1 -1
  337. package/docs/design/templates/state-banner/warning.svg +1 -1
  338. package/docs/design/templates/state-banner-dark.svg +1 -1
  339. package/docs/design/templates/state-banner.svg +1 -1
  340. package/docs/design/templates/tab-bar-dark.svg +1 -1
  341. package/docs/design/templates/tab-bar.svg +1 -1
  342. package/docs/design/templates/token-search-field-dark.svg +1 -1
  343. package/docs/design/templates/token-search-field.svg +1 -1
  344. package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
  345. package/docs/design/templates/toolbar-control-group.svg +1 -1
  346. package/docs/design/templates/toolbar-dark.svg +1 -1
  347. package/docs/design/templates/toolbar-text/default-dark.svg +1 -1
  348. package/docs/design/templates/toolbar-text/default.svg +1 -1
  349. package/docs/design/templates/toolbar-text/large-dark.svg +1 -1
  350. package/docs/design/templates/toolbar-text/large.svg +1 -1
  351. package/docs/design/templates/toolbar-text-dark.svg +1 -1
  352. package/docs/design/templates/toolbar-text.svg +1 -1
  353. package/docs/design/templates/toolbar.svg +1 -1
  354. package/docs/design/templates/value-table/details-dark.svg +1 -1
  355. package/docs/design/templates/value-table/details.svg +1 -1
  356. package/docs/design/templates/value-table-dark.svg +1 -1
  357. package/docs/design/templates/value-table.svg +1 -1
  358. package/docs/design-philosophy.md +9 -1
  359. package/docs/examples/command-palette-adapter.tsx +3 -3
  360. package/docs/layout.md +147 -14
  361. package/docs/nav-stack.md +112 -2
  362. package/docs/recipes.md +9 -9
  363. package/docs/split-view.md +3 -3
  364. package/docs/sunken-panel.md +2 -2
  365. package/docs/surface-scaffold.md +138 -7
  366. package/docs/tab-navigator.md +38 -1
  367. package/docs/type-contracts.md +16 -16
  368. package/docs/ui-analyzer.md +3 -1
  369. package/docs/ui-doctor.md +71 -0
  370. package/docs/ux-demo.md +6 -6
  371. package/docs/workbench.md +10 -6
  372. package/docs/writing.md +152 -0
  373. package/doctor/copy-capitalization-checks.mjs +591 -0
  374. package/doctor/index.mjs +17 -8
  375. package/llms.txt +105 -11
  376. package/package.json +15 -1
  377. package/ux-demo/recipes/app-shell.tsx +11 -11
  378. package/ux-demo/recipes/collapsible-sidebar.tsx +12 -11
  379. package/ux-demo/recipes/compact-toolbar.tsx +10 -10
  380. package/ux-demo/recipes/composer-form.tsx +8 -8
  381. package/ux-demo/recipes/list-detail-dialog.tsx +13 -13
  382. package/ux-demo/recipes/list-workspace-states.tsx +13 -13
  383. package/ux-demo/recipes/loading-inspector.tsx +10 -10
  384. package/ux-demo/recipes/navigation-sidebar.tsx +7 -7
  385. package/ux-demo/recipes/navigation-stack.tsx +5 -5
  386. package/ux-demo/recipes/workspace-header.tsx +5 -5
  387. package/dist/chunk-36TL6BCR.js.map +0 -1
  388. package/dist/chunk-4LOK4634.js +0 -21
  389. package/dist/chunk-4LOK4634.js.map +0 -1
  390. package/dist/chunk-527IAEWG.js.map +0 -1
  391. package/dist/chunk-6BRRUPJY.js.map +0 -1
  392. package/dist/chunk-6STQXRT4.js.map +0 -1
  393. package/dist/chunk-76PWEN5Y.js +0 -64
  394. package/dist/chunk-76PWEN5Y.js.map +0 -1
  395. package/dist/chunk-AZWE2FB5.js.map +0 -1
  396. package/dist/chunk-DQDTZE4W.js +0 -70
  397. package/dist/chunk-DQDTZE4W.js.map +0 -1
  398. package/dist/chunk-FOQK4F3L.js.map +0 -1
  399. package/dist/chunk-GJYQ27C2.js.map +0 -1
  400. package/dist/chunk-JYD7G4RG.js.map +0 -1
  401. package/dist/chunk-KQDU7Z7P.js.map +0 -1
  402. package/dist/chunk-P74VXMB6.js.map +0 -1
  403. package/dist/chunk-PBT3CPPY.js.map +0 -1
  404. package/dist/chunk-PRG4CL3Q.js.map +0 -1
  405. package/dist/chunk-ROZLJULF.js.map +0 -1
  406. package/dist/chunk-U657IHFB.js.map +0 -1
  407. package/dist/chunk-V6W56V3N.js.map +0 -1
package/docs/layout.md CHANGED
@@ -185,6 +185,62 @@ Firefox, scroll-state container queries only in Chromium, and neither lets
185
185
  the pinned chrome — a sibling of the scroller — read the scroller's state on
186
186
  its own. One wiring keeps the behavior identical in every engine.
187
187
 
188
+ ### Overlay header
189
+
190
+ `headerPresentation="overlay"` lays a Pane's header over the top of its
191
+ scrolling content instead of giving it its own row, the way platform
192
+ navigation bars let content scroll visibly beneath a translucent bar:
193
+
194
+ ```tsx
195
+ <Pane
196
+ label="Inbox"
197
+ headerPresentation="overlay"
198
+ header={<Toolbar label="Inbox" leading={<ToolbarText text="Inbox" size="xlarge" />} />}
199
+ >
200
+ {messages}
201
+ </Pane>
202
+ ```
203
+
204
+ The header paints the shared chrome material, `--kui-chrome-material` (the
205
+ surface at 80% opacity) behind `--kui-chrome-material-filter` (a blur and
206
+ saturation backdrop filter), so content passing beneath it reads as softened
207
+ color, not as competing text. The material becomes the opaque surface under
208
+ `prefers-reduced-transparency: reduce` or `prefers-contrast: more`, and where
209
+ the browser lacks `backdrop-filter`. In forced colors the header paints
210
+ `Canvas` with a `CanvasText` bottom edge.
211
+
212
+ `wireScrollDividers` measures each overlay header (its border-box height,
213
+ safe-area padding included) with a `ResizeObserver` and publishes it on the
214
+ Pane as the `--kui-scroll-clearance-block-start` context (see
215
+ [Scroll clearance](#scroll-clearance)). The Pane's content clears it
216
+ automatically, with no `scrollClearance` prop. At rest the first item sits just
217
+ below the header. Mid-scroll, content passes beneath it, and anything focused
218
+ or scrolled into view still comes to rest clear of it. The clearance follows
219
+ the header as its title wraps or its text grows. The header draws its scroll
220
+ divider as a docked header does, once content is scrolled beneath it. The
221
+ wiring also marks the Pane `data-header-overlay="measured"`, and only then
222
+ does the header move over the content. Until the wiring runs, the header stays
223
+ docked in its own row, so it can never cover content it has not measured. A
224
+ re-render that drops the measurement gets it back before paint.
225
+
226
+ The overlay needs pinned chrome. With `chromePlacement="auto"`, the header
227
+ stays docked. `PaneConfig` carries `headerPresentation`, so a Workbench
228
+ `mainPane` or panel `pane`, a `CollapsiblePanel` `pane`, or a `NavStack` view's
229
+ `pane` can lay its header over its content the same way. Overlay-header text
230
+ must stay at AA over whatever passes beneath: an untoned `ToolbarText` there
231
+ takes the normal text color for its title and lifts its `description` toward
232
+ it, and other quiet secondary copy belongs in the content (see
233
+ [Accessibility › Chrome material](accessibility.md#chrome-material)).
234
+
235
+ An overlay scrollbar (macOS by default) passes beneath the header like the
236
+ content. A classic, space-taking scrollbar (Windows and Linux, or macOS with
237
+ "always show scrollbars") stays uncovered: the wiring also measures the
238
+ content's vertical scrollbar (`offsetWidth` beyond `clientWidth`) and publishes
239
+ its inline size on the Pane as `--kui-pane-scrollbar-inline-size`, and the
240
+ header ends where the scrollbar's track begins, so its thumb and arrow buttons
241
+ remain visible and usable. The measurement follows the scrollbar as content
242
+ starts or stops overflowing.
243
+
188
244
  A pane is safe-area aware. Its background and separators paint through a
189
245
  device's unsafe areas while its header, content, and footer pad for each side
190
246
  the pane still reaches; the content's block insets are scroll padding, so its
@@ -313,19 +369,24 @@ component's context stays allowed (see `KUI-L019`).
313
369
 
314
370
  ## Public roles and tokens
315
371
 
316
- | Need | Class | Token / default |
317
- | ---------------------------------------- | ---------------------------- | ------------------------------------------------------------------ |
318
- | Unpadded header/content/footer structure | `Pane`, `.kui-pane` | — |
319
- | Scrolling pane content | `.kui-pane__content` | — |
320
- | Optional logical-edge separators | `Pane.separators` | `--kui-pane-separator-width: 1px` |
321
- | Safe-area sides a pane may pad | `Pane.safeAreaEdges` | `--kui-safe-area-*: env(safe-area-inset-*)`, routed by each layout |
322
- | Major vertical rhythm | `.kui-content` | `--kui-layout-content-gap: 24px` |
323
- | Self-contained child geometry | `ContentItem` | 8px margin + 1px border + 8px padding |
324
- | Pill child | `ContentItem shape="pill"` | `--kui-layout-pill-radius: 22px` |
325
- | Visibly framed child | `ContentItem frame="framed"` | neutral 1px border, same geometry |
326
- | Related controls | `.kui-control-cluster` | `--kui-layout-control-gap: 8px` |
327
- | Inline metadata | `.kui-inline-metadata` | `--kui-layout-metadata-gap: 4px` |
328
- | Explicit scroll owner outside a pane | `.kui-scroll-owner` | `overflow: auto` |
372
+ | Need | Class | Token / default |
373
+ | ---------------------------------------- | ------------------------------------ | ------------------------------------------------------------------ |
374
+ | Unpadded header/content/footer structure | `Pane`, `.kui-pane` | — |
375
+ | Scrolling pane content | `.kui-pane__content` | — |
376
+ | Optional logical-edge separators | `Pane.separators` | `--kui-pane-separator-width: 1px` |
377
+ | Safe-area sides a pane may pad | `Pane.safeAreaEdges` | `--kui-safe-area-*: env(safe-area-inset-*)`, routed by each layout |
378
+ | Header laid over scrolling content | `Pane.headerPresentation` | `--kui-chrome-material`, `--kui-chrome-material-filter` |
379
+ | Dialog title bar laid over its body | `DialogSurface.headerPresentation` | `--kui-chrome-material-raised`, `--kui-chrome-material-filter` |
380
+ | Stack top bar laid over the active view | `NavStack.chromePresentation` | `--kui-chrome-material`, `--kui-chrome-material-filter` |
381
+ | Stack bottom toolbar laid over the view | `NavStack.bottomToolbarPresentation` | `--kui-chrome-material`, `--kui-chrome-material-filter` |
382
+ | Tab bar laid over the active scene | `TabNavigator.barPresentation` | `--kui-chrome-material`, `--kui-chrome-material-filter` |
383
+ | Major vertical rhythm | `.kui-content` | `--kui-layout-content-gap: 24px` |
384
+ | Self-contained child geometry | `ContentItem` | 8px margin + 1px border + 8px padding |
385
+ | Pill child | `ContentItem shape="pill"` | `--kui-layout-pill-radius: 22px` |
386
+ | Visibly framed child | `ContentItem frame="framed"` | neutral 1px border, same geometry |
387
+ | Related controls | `.kui-control-cluster` | `--kui-layout-control-gap: 8px` |
388
+ | Inline metadata | `.kui-inline-metadata` | `--kui-layout-metadata-gap: 4px` |
389
+ | Explicit scroll owner outside a pane | `.kui-scroll-owner` | `overflow: auto` |
329
390
 
330
391
  The component layer applies the same contract to `Toolbar`, `ListHeader`,
331
392
  `ListItem`, `Toolbar`, `StateBanner`, `ValueTable`,
@@ -409,7 +470,7 @@ import { FieldLabel } from "@kerfjs/ui/text";
409
470
 
410
471
  <div role="group" aria-labelledby="needed-by-label">
411
472
  <ListInsetControl sides="trl">
412
- <FieldLabel id="needed-by-label">Needed by</FieldLabel>
473
+ <FieldLabel id="needed-by-label">Needed By</FieldLabel>
413
474
  </ListInsetControl>
414
475
  <ContentItem>October 16, 2026</ContentItem>
415
476
  </div>;
@@ -646,6 +707,78 @@ When it has focus it draws the theme focus ring inset by the ring width and
646
707
  rounded with `--kui-radius-m`, so a rounded or `overflow: hidden` parent
647
708
  never clips it. Do not style `.kui-list` focus yourself.
648
709
 
710
+ ### Scroll clearance
711
+
712
+ Chrome that lies over a scroll owner's block edge (a `DialogSurface` floating
713
+ footer over a transcript, or translucent chrome over a scrolling region)
714
+ publishes how far it covers that edge through an inherited context:
715
+ `--kui-scroll-clearance-block-start` and `--kui-scroll-clearance-block-end`.
716
+ A scroll owner opts in per edge with `scrollClearance` (`"block-start"`,
717
+ `"block-end"`, or `"both"`) on a `scrollable` `List` or a `Pane`. It adds the
718
+ published clearance to its own block padding and `scroll-padding`, so content
719
+ scrolls visibly beneath the chrome while the first and last items (and any
720
+ item focused or scrolled into view) still come to rest clear of it. Without
721
+ the prop the context is ignored. `PaneConfig` carries `scrollClearance` too,
722
+ so a composed Pane opts in the same way: a Workbench `mainPane` or panel
723
+ `pane`, a `CollapsiblePanel` `pane`, or a `NavStack` view's `pane` (for
724
+ example a NavStack view inside a dialog with a floating composer).
725
+
726
+ Opt in only the one scroller that reaches the covered edge; a nested scroller
727
+ must not opt in again. `DialogSurface` publishes block-end for its floating
728
+ footer and block-start for its floating header (see
729
+ `docs/surface-scaffold.md`), and clears the context in every dialog body. A `Pane` with `headerPresentation="overlay"` publishes block-start for
730
+ its own header, once `wireScrollDividers` has measured it, and its content
731
+ clears it without the prop (see [Overlay header](#overlay-header)). When an application overlays its own chrome on a region (for example a
732
+ translucent toolbar laid over the top of a scrolling list), it publishes the
733
+ chrome's block size on that region's container, the one place where an app
734
+ sets this context:
735
+
736
+ ```tsx
737
+ <section style="position:relative;display:grid;--kui-scroll-clearance-block-start:48px">
738
+ <List scrollable scrollClearance="block-start">{rows}</List>
739
+ {translucentToolbar}
740
+ </section>;
741
+ ```
742
+
743
+ ### Framed result pickers
744
+
745
+ For a bounded, framed list of `ListItem` rows (search results, duplicate
746
+ candidates, a recent-items picker), configure the List instead of wrapping it
747
+ in an app-owned scroller:
748
+
749
+ ```tsx
750
+ <List scrollable frame gap="2xs" minHeight={rem(4.25)} maxHeight={rem(14)}>
751
+ {candidates.map((candidate) => (
752
+ <ListItem action="pick-duplicate" itemId={candidate.id} label={candidate.title} description={candidate.detail} multiline />
753
+ ))}
754
+ </List>;
755
+ ```
756
+
757
+ `frame` draws a 1px `--kui-color-neutral-border-normal` border with the
758
+ `--kui-radius-m` control radius and a 4px inset on every side (replacing
759
+ `inset`, `textInsets`, and `controlInsets`). That inset is exactly a row's
760
+ focus-ring reach, so the first and last rows' rings stay inside the clip at
761
+ the scroll extremes. Every `scrollable` List, framed or not, carries block
762
+ scroll padding of that reach plus 2px of slack (6px; nothing moves at rest),
763
+ so a middle row focused or scrolled into view also stops with its ring inside
764
+ the clip, even where Chromium's focus scroll stops a pixel short of the padded
765
+ target. An unframed scrollable List has no inset, so its first and last rows
766
+ still meet the clip at the scroll extremes; use `frame` when those rows must
767
+ show a whole ring.
768
+ How far `focus()` scrolls a partly visible row is up to the engine: Chromium
769
+ brings it to the nearest edge, Firefox leaves it until about half of it is
770
+ hidden and then centers it, and WebKit leaves a partly visible row where it
771
+ is. Call `wireListFocusScroll(root)` from `@kerfjs/ui/wire-list-focus-scroll`
772
+ once at the app root (keep its disposer) to make keyboard focus consistent:
773
+ when a `:focus-visible` element inside a `scrollable` List has a ring that
774
+ crosses the clip, it calls `scrollIntoView({ block: 'nearest' })`, which
775
+ honors the List's scroll padding in every engine. It leaves pointer focus
776
+ alone, so a click never scrolls its row from under the pointer, and it is a
777
+ no-op where Chromium has already scrolled. `maxHeight` and `minHeight` take typed
778
+ `CssLength`s from `@kerfjs/ui/css-values` and bound the List directly, with no
779
+ flex-column ancestor; pair `maxHeight` with `scrollable`. A frame of settings
780
+ rows with dividers between them is a `FramedList`, not a framed List.
781
+
649
782
  For long reading content, such as a reader view, pass `inset="document"`: 24px
650
783
  block and 32px inline on all four sides, narrowing to 16px inline below 576px.
651
784
  It replaces any `textInsets`/`controlInsets` on that List.
package/docs/nav-stack.md CHANGED
@@ -66,7 +66,8 @@ the view renders a `Pane`: its header and footer stay pinned around the view's
66
66
  scrolling content, and slide with that view during navigation. The header and
67
67
  footer list configuration forwards to their `List`s; `pane` forwards
68
68
  `contentElement`, `contentLabel`, `separators`, `safeAreaEdges`,
69
- `chromeDividers`, and `appearance` to the Pane. Without those fields, the view
69
+ `chromeDividers`, `appearance`, `deepInset`, `scrollClearance`, `headerPresentation`, `tabIndex`,
70
+ and `outlined` to the Pane. Without those fields, the view
70
71
  keeps its simple direct scroll area. Use `pane: { appearance: "sunken" }` for a
71
72
  lowered scrolling work surface.
72
73
  The back control appears automatically once the stack has more than one entry;
@@ -136,7 +137,7 @@ Configure the toolbar with `toolbarConfig` instead of styling it:
136
137
  view's top toolbar. `Workbench` uses it for a navigation panel's standard
137
138
  collapse toggle; the active view still owns the preceding toolbar groups.
138
139
 
139
- The catalog's **Configured toolbar** example shows these together: a level-2
140
+ The catalog's **Configured Toolbar** example shows these together: a level-2
140
141
  heading title over a bottom divider, leading and center groups on the root
141
142
  view, a trailing group on the pushed view, and `backText` set to the previous
142
143
  view's title.
@@ -186,6 +187,115 @@ top Toolbar's own `dividerSides`.
186
187
  <NavStack id="settings" label="Settings" views={views.value} chromeDividers="always" />
187
188
  ```
188
189
 
190
+ ## Overlay chrome
191
+
192
+ `chromePresentation="overlay"` lays the top chrome over the active view's top
193
+ edge, like an iOS navigation bar, instead of docking it above the view
194
+ (`"docked"`, the default). The chrome paints the shared chrome material,
195
+ `--kui-chrome-material` behind `--kui-chrome-material-filter`, so content
196
+ scrolls visibly, blurred, beneath it.
197
+
198
+ ```tsx
199
+ <NavStack id="mail" label="Mail" views={views.value} chromePresentation="overlay" />
200
+ ```
201
+
202
+ - **`wireNavStack` measures it.** The helper you already call for transitions
203
+ reads the live chrome's border-box height with a `ResizeObserver`, publishes
204
+ it on the stack root, and marks the root `data-chrome-overlay="measured"`.
205
+ Only then does the chrome leave its band. Unwired, or with `hideToolbar`, the
206
+ chrome stays docked, so it never covers content it has not measured. The
207
+ measurement follows the chrome as its title wraps, text grows, or a pushed
208
+ view changes its toolbar. A re-render that strips the measurement gets it
209
+ back before paint.
210
+ - **Every view clears it automatically.** The measured height becomes the
211
+ viewport's block-start edge context (`--kui-edge-inset-block-start`), the
212
+ same context that carries safe areas. A plain view pads and scroll-pads by
213
+ it, a sole `Pane` adds it to its content's (or its header's) block-start
214
+ inset, and a nested layout hands it on. The first item rests just below the
215
+ chrome, and anything focused or scrolled into view stops clear of it. No
216
+ `scrollClearance` prop is involved, so a Pane that also opts into
217
+ `scrollClearance` does not clear the chrome twice.
218
+ - **Safe area.** The chrome keeps claiming the top safe area: its toolbar pads
219
+ for it while the material paints through it. The measured height already
220
+ includes that padding, so views clear the chrome and the safe area exactly
221
+ once.
222
+ - **Transitions.** Only the live chrome is measured, never a cross-fading
223
+ copy. The copy keeps painting over the top edge with the same material while
224
+ it fades, and the incoming view settles at the incoming chrome's clearance.
225
+ - **Classic scrollbars.** Where the active view's scroller (the view, or a
226
+ sole header-less Pane's content) shows a classic, space-taking scrollbar,
227
+ `wireNavStack` measures it and the chrome ends where its track begins, so
228
+ the thumb and arrows stay visible and usable. An overlay scrollbar takes no
229
+ space and passes beneath the material like the content.
230
+ - **Dividers.** With `wireScrollDividers`, the line under the chrome still
231
+ appears only while content is scrolled beneath it.
232
+ - **Contrast.** The material keeps normal text at AA over any content. An
233
+ untoned `ToolbarText` in the chrome lifts itself: its title takes the normal
234
+ text color at every size and its `description` mixes the quiet color halfway
235
+ toward it, and a default-tone `ToolbarControlGroup` takes the normal text
236
+ color for its controls. Other quiet copy you place in the chrome has no such
237
+ lift, so keep it in the view (see
238
+ [Accessibility › Chrome material](accessibility.md#chrome-material)). The
239
+ material turns opaque under `prefers-reduced-transparency`,
240
+ `prefers-contrast: more`, or without `backdrop-filter`, and forced colors
241
+ paint `Canvas` with a `CanvasText` bottom edge.
242
+
243
+ `SplitView`'s `compactStack.chromePresentation` and a Workbench navigation
244
+ panel's `navStack.chromePresentation` forward the same option.
245
+ `chromePresentation` governs only the top chrome; the bottom toolbar has its
246
+ own option below.
247
+
248
+ ### Overlay bottom toolbar
249
+
250
+ `bottomToolbarPresentation="overlay"` does the same for the bottom toolbar,
251
+ like an iOS toolbar: it lies over the active view's bottom edge on the same
252
+ chrome material instead of docking below the view (`"docked"`, the default).
253
+ It is a separate prop, so either bar can be overlaid alone, and it mirrors
254
+ `Pane.headerPresentation` and `DialogSurface.footerPresentation` in naming one
255
+ edge.
256
+
257
+ ```tsx
258
+ <NavStack
259
+ id="mail"
260
+ label="Mail"
261
+ views={views.value}
262
+ chromePresentation="overlay"
263
+ bottomToolbarPresentation="overlay"
264
+ bottomToolbar={{ label: 'Mail Actions', trailing: composeGroup }}
265
+ />
266
+ ```
267
+
268
+ - **`wireNavStack` measures it.** It reads the live bottom toolbar's
269
+ border-box height (never a cross-fading copy's), publishes it on the stack
270
+ root, and marks the root `data-bottom-toolbar-overlay="measured"`. Only then
271
+ does the toolbar leave its band. Unwired, or while no bottom toolbar is
272
+ shown (a view without one and no stack-level fallback), it stays docked. The
273
+ measurement follows a view-owned toolbar as views push and pop, and a
274
+ re-render that strips it gets it back before paint.
275
+ - **Every view clears it automatically.** The measured height becomes the
276
+ viewport's block-end edge context (`--kui-edge-inset-block-end`). A plain
277
+ view pads and scroll-pads by it, a sole `Pane` adds it to its content's
278
+ block-end inset (or its footer's, so the footer rests just above the
279
+ toolbar), and a nested layout hands it on. The last item rests just above
280
+ the toolbar, and anything focused or scrolled into view stops clear of it.
281
+ - **Safe area.** The toolbar keeps claiming the bottom safe area (the home
282
+ indicator), and the measured height includes it, so views clear both exactly
283
+ once. Inside a `TabNavigator` scene the tab bar owns that edge, so the
284
+ stack's toolbar sits just above the tab bar and counts no safe area.
285
+ - **Classic scrollbars and dividers.** The toolbar ends where a classic
286
+ scrollbar's track begins (a sole Pane with a footer ends its scroller above
287
+ the toolbar, so nothing needs leaving there). With `wireScrollDividers`, the
288
+ toolbar's top line still appears only while more content lies below.
289
+ - **Contrast.** An untoned `ToolbarText` lifts itself as in the top chrome,
290
+ and a default-tone `ToolbarControlGroup` in either overlaid bar takes the
291
+ normal text color for its controls, so labels and icons keep AA over the
292
+ content beneath. The material has the same opaque fallbacks; in forced
293
+ colors the toolbar paints `Canvas` and its always-present 1px top border
294
+ becomes a `CanvasText` edge, which the measurement includes.
295
+
296
+ `SplitView`'s `compactStack.bottomToolbarPresentation` and a Workbench
297
+ navigation panel's `navStack.bottomToolbarPresentation` forward it too.
298
+
189
299
  ## Safe areas
190
300
 
191
301
  The stack paints through a device's unsafe areas. Its chrome pads for the top
package/docs/recipes.md CHANGED
@@ -26,7 +26,7 @@ The adapter is delivered as reference source, not a new package runtime export.
26
26
  `delegate()` remains a valid alternative when an application needs selector-
27
27
  specific dispatch; either way, wire once at a stable root and retain disposal.
28
28
 
29
- ## Desktop application shell
29
+ ## Desktop Application Shell
30
30
 
31
31
  [Open the recipe](../ux-demo/?component=recipe-app-shell) · [TSX source](../ux-demo/recipes/app-shell.tsx)
32
32
 
@@ -52,7 +52,7 @@ whole width). The app owns each rail's collapsed and size signals — which
52
52
  routing, persistence, and data. Adapt only public `--kui-layout-*` and
53
53
  component variables.
54
54
 
55
- ## Navigation sidebar
55
+ ## Navigation Sidebar
56
56
 
57
57
  [Open the recipe](../ux-demo/?component=recipe-navigation-sidebar) · [TSX source](../ux-demo/recipes/navigation-sidebar.tsx)
58
58
 
@@ -65,7 +65,7 @@ permissions, labels, selection, valid section counts and their localized
65
65
  content. A toggled `ListHeader` supplies the production `DisclosureArrow` when
66
66
  no custom `actionIcon` is needed; ordinary navigation rows stay chevron-free.
67
67
 
68
- ## Workspace header
68
+ ## Workspace Header
69
69
 
70
70
  [Open the recipe](../ux-demo/?component=recipe-workspace-header) · [TSX source](../ux-demo/recipes/workspace-header.tsx)
71
71
 
@@ -99,7 +99,7 @@ application owns open state, selection, the pushed detail, dismissal policy,
99
99
  and record actions. Do not put the primary title in a header spanning both
100
100
  columns, rebuild the dialog, or reach into private shadow parts.
101
101
 
102
- ## Composer form
102
+ ## Composer Form
103
103
 
104
104
  [Open the recipe](../ux-demo/?component=recipe-composer-form) · [TSX source](../ux-demo/recipes/composer-form.tsx)
105
105
 
@@ -118,7 +118,7 @@ Because upgraded Web Awesome fields retain live value properties, controlled
118
118
  resets synchronize both those properties and the rendered value attributes;
119
119
  the Reset action also announces `Draft reset` through the catalog live region.
120
120
 
121
- ## List workspace states
121
+ ## List Workspace States
122
122
 
123
123
  [Open the recipe](../ux-demo/?component=recipe-list-workspace-states) · [TSX source](../ux-demo/recipes/list-workspace-states.tsx)
124
124
 
@@ -127,7 +127,7 @@ populated, stale/background refresh, and error/retry states. The recipe owns
127
127
  feedback placement; the app owns fetching, cache age, retry policy, and domain
128
128
  rows.
129
129
 
130
- ## Compact toolbar choices and actions
130
+ ## Compact Toolbar Choices and Actions
131
131
 
132
132
  [Open the recipe](../ux-demo/?component=recipe-compact-toolbar) · [TSX source](../ux-demo/recipes/compact-toolbar.tsx)
133
133
 
@@ -140,7 +140,7 @@ widths the toolbar stacks its zones and wraps whole trailing groups onto another
140
140
  row, so the last action is never clipped. The app owns values, actions,
141
141
  persistence, and responsive priority.
142
142
 
143
- ## Navigation stack
143
+ ## Navigation Stack
144
144
 
145
145
  [Open the recipe](../ux-demo/?component=recipe-navigation-stack) · [TSX source](../ux-demo/recipes/navigation-stack.tsx)
146
146
 
@@ -152,7 +152,7 @@ layout guide
152
152
  [`app-layouts.md`](app-layouts.md) for choosing among `NavStack`, `SplitView`,
153
153
  `Workbench`, and `TabNavigator`.
154
154
 
155
- ## Loading inspector
155
+ ## Loading Inspector
156
156
 
157
157
  [Open the recipe](../ux-demo/?component=recipe-loading-inspector) · [TSX source](../ux-demo/recipes/loading-inspector.tsx)
158
158
 
@@ -166,7 +166,7 @@ the populated record — no separate skeleton markup. The composition is the poi
166
166
  record title instead of truncating it. The app owns the loading lifecycle and
167
167
  which values are still unknown.
168
168
 
169
- ## Collapsible sidebar
169
+ ## Collapsible Sidebar
170
170
 
171
171
  [Open the recipe](../ux-demo/?component=recipe-collapsible-sidebar) · [TSX source](../ux-demo/recipes/collapsible-sidebar.tsx)
172
172
 
@@ -114,12 +114,12 @@ compactStack={{
114
114
  list: { toolbar: { trailing: <ComposeGroup /> } },
115
115
  detail: {
116
116
  toolbar: { trailing: <ReplyGroup /> },
117
- bottomToolbar: { label: "Message actions", leading: <MessageActions /> },
117
+ bottomToolbar: { label: "Message Actions", leading: <MessageActions /> },
118
118
  },
119
119
  }}
120
120
  ```
121
121
 
122
- The catalog's **Interactive compact drill-down** example exercises this exact
122
+ The catalog's **Interactive Compact Drill-Down** example exercises this exact
123
123
  controlled flow: a list action sets the selected message, `detailActive` pushes
124
124
  its detail, and the wired Back action clears the selection to reveal the
125
125
  preserved list. It also forwards `compactStack`: a level-2 heading title over
@@ -128,7 +128,7 @@ bottom toolbar on the detail. Use the same structure in application demos so
128
128
  compact examples show the interaction rather than rendering a disconnected
129
129
  detail state.
130
130
 
131
- The catalog's **Resizable, collapsible list** example forwards `resizable`: a
131
+ The catalog's **Resizable, Collapsible List** example forwards `resizable`: a
132
132
  hidden separator that still resizes the list (its committed width comes back
133
133
  through `wireResizableRegions`' `onCommit` into the app's size signal), a Hide
134
134
  control in the list's own toolbar, and a `FloatingToolbar` restore control in
@@ -8,7 +8,7 @@ issue tracker or component catalog.
8
8
  ```tsx
9
9
  import { SunkenPanel } from '@kerfjs/ui/sunken-panel';
10
10
 
11
- <SunkenPanel ariaLabel="Release workspace">
11
+ <SunkenPanel ariaLabel="Release Workspace">
12
12
  <ReleaseSummary />
13
13
  <ReleaseChecks />
14
14
  </SunkenPanel>;
@@ -33,7 +33,7 @@ can shrink with its parent. Like `Grid`, `flex` accepts `true` (equivalent to
33
33
 
34
34
  ```tsx
35
35
  <List fill>
36
- <Toolbar label="Work area" />
36
+ <Toolbar label="Work Area" />
37
37
  <SunkenPanel flex>
38
38
  <WorkArea />
39
39
  </SunkenPanel>
@@ -54,7 +54,7 @@ import { remify } from '@kerfjs/ui/css-values';
54
54
  import { DialogSurface } from '@kerfjs/ui/surface-scaffold';
55
55
 
56
56
  <DialogSurface preferredWidth={remify(832)} viewportGutter={remify(8)} maxHeight="viewport" bodyInset="none">
57
- <wa-dialog label="Close workspace">{/* Application-owned content and footer */}</wa-dialog>
57
+ <wa-dialog label="Close Workspace">{/* Application-owned content and footer */}</wa-dialog>
58
58
  </DialogSurface>;
59
59
  ```
60
60
 
@@ -86,12 +86,129 @@ itself stops scrolling. Combine it with `bodyInset="none"`.
86
86
 
87
87
  ```tsx
88
88
  <DialogSurface size="large" height={remify(640)} viewportGutter={remify(8)} bodyInset="none" bodyLayout="fill">
89
- <wa-dialog label="Repository status">
89
+ <wa-dialog label="Repository Status">
90
90
  <SplitView id="repo-split" label="Changes" listWidth={{ size: 432, maxFraction: 0.5 }} list={list} detail={detail} />
91
91
  </wa-dialog>
92
92
  </DialogSurface>;
93
93
  ```
94
94
 
95
+ ### Chat dialogs: a floating footer over a filling transcript
96
+
97
+ A chat-style dialog keeps a heading or notices above one scrolling transcript
98
+ and floats its composer over the body's block end. Two props compose it:
99
+
100
+ - `bodyLayout="fill-last"` stacks the body's children: the leading children
101
+ keep their natural height, and the last one takes the remaining height and
102
+ owns its scrolling (a `scrollable` `List` transcript). The body itself stops
103
+ scrolling. `PopoverDialogSurface` accepts the same layout.
104
+ - `footerPresentation="floating"` lays the footer over the body's block end,
105
+ `footerOffset` (a branded `CssLength`, default the 16px container inset)
106
+ from the panel's inline and block-end edges, on a transparent background.
107
+ The modal panel is its positioning context. The default `docked` footer
108
+ sits below the body as before.
109
+
110
+ The register measures the footer (it follows a composer that grows, wraps, or
111
+ hides) and the body reserves that height plus the offset after its own inset,
112
+ with a matching `scroll-padding`. Flowing body content scrolls beneath the
113
+ floating footer and its end stays reachable above it; with `fill` or
114
+ `fill-last` the filling child's scrollport ends at the footer's top. Content
115
+ is never hidden beneath the footer.
116
+
117
+ To let a filling transcript scroll visibly beneath the floating footer, opt
118
+ its scroller in with `scrollClearance="block-end"` (a `scrollable` `List`, or a
119
+ `Pane`, as the `fill` or last `fill-last` child). The body then stops
120
+ reserving the footer below that child, so the child reaches the panel's block
121
+ end, and publishes the footer's clearance (measured height plus offset) as the
122
+ inherited `--kui-scroll-clearance-block-end` context. The child adds it to its
123
+ own block-end padding and `scroll-padding`: messages pass beneath the
124
+ transparent footer mid-scroll, the last message rests above the composer at
125
+ the end, and focus or `scrollIntoView` never leaves an item hidden beneath it.
126
+ The context is general: chrome that overlays a region's block-start or
127
+ block-end edge publishes `--kui-scroll-clearance-block-start` / `-end`, and a
128
+ scroll owner opts in per edge (see `docs/layout.md` "Scroll clearance"). A
129
+ dialog body clears any clearance inherited from the page.
130
+
131
+ Choose the offset like any other responsive value: the scaffold adds no
132
+ breakpoint. An 8px offset lines a composer up with the transcript's
133
+ content-item edges when the body has `bodyInset="none"` and list geometry;
134
+ `viewportGutter` and `footerOffset` can follow `deviceClass()` when a phone
135
+ needs tighter edges. Put a [`Composer`](./composer.md) in the floating footer
136
+ with `footerInset="none"`: its raised frame and shadow lift it over the
137
+ transcript, and its field grows the measured footer. With `submitOnEnter` and
138
+ `wireComposers(root)`, Enter sends through the same action as the Send button.
139
+
140
+ ```tsx
141
+ <DialogSurface height={remify(560)} viewportGutter={remify(8)} bodyInset="none" bodyLayout="fill-last" footerPresentation="floating" footerOffset={remify(8)} footerInset="none">
142
+ <wa-dialog label="Assistant">
143
+ <StateBanner title="Answers use the latest nightly build." tone="neutral" />
144
+ <List gap="xs" controlInsets="tb" scrollable scrollClearance="block-end">
145
+ {messages.map((message) => (
146
+ <ContentItem bubble={message.mine ? 'end' : 'start'} appearance={message.mine ? 'brand-loud' : 'neutral'}>
147
+ <Text variant="span">{message.text}</Text>
148
+ </ContentItem>
149
+ ))}
150
+ </List>
151
+ <Composer submitOnEnter slot="footer" id="assistant-message" name="message" label="Message" placeholder="Ask about this build" actions={<wa-button variant="brand" data-action="send-message">Send</wa-button>} />
152
+ </wa-dialog>
153
+ </DialogSurface>;
154
+ ```
155
+
156
+ ### A floating title bar
157
+
158
+ `headerPresentation="floating"` lays the dialog's header (its title and close
159
+ control) over the body's block-start edge on the translucent chrome material,
160
+ `--kui-chrome-material-raised` (the raised panel surface at 80% opacity)
161
+ behind `--kui-chrome-material-filter` (a blur and saturation backdrop filter).
162
+ Content passing beneath the title bar reads as softened color, not as
163
+ competing text. The default `docked` header sits above the body as before.
164
+
165
+ The register measures the header the same way it measures the footer (it
166
+ follows a title that wraps, text that grows, or a `without-header` toggle).
167
+ The body reserves the measured height before its own inset, with a matching
168
+ `scroll-padding`, so flowing content starts below the title bar and scrolls
169
+ beneath it. A dialog without the register has no measurement, so its header
170
+ stays docked rather than covering content.
171
+
172
+ To let a filling transcript scroll visibly beneath the title bar, make it the
173
+ body's only child (`bodyLayout="fill"`, or `fill-last` with no leading rows)
174
+ and opt its scroller in with block-start `scrollClearance`. The body then
175
+ stops reserving the header, so the child reaches the panel's block-start
176
+ edge, and publishes the header's height as `--kui-scroll-clearance-block-start`.
177
+ Leading `fill-last` rows (a notice above the transcript) keep the body's
178
+ reservation: they never sit beneath the title bar. Combined with a floating
179
+ composer and `scrollClearance="both"`, the transcript scrolls beneath both:
180
+
181
+ ```tsx
182
+ <DialogSurface height={remify(560)} viewportGutter={remify(8)} bodyInset="none" bodyLayout="fill" headerPresentation="floating" footerPresentation="floating" footerOffset={remify(8)} footerInset="none">
183
+ <wa-dialog label="Release Thread">
184
+ <ToolbarText slot="label" text="Release Thread" size="xlarge" description="Engineering · 5.2 candidate" />
185
+ <List gap="xs" controlInsets="tb" scrollable scrollClearance="both">
186
+ {bubbles}
187
+ </List>
188
+ <Composer submitOnEnter slot="footer" id="thread-message" name="message" label="Message" placeholder="Reply to the thread" actions={<wa-button variant="brand" data-action="send-message">Send</wa-button>} />
189
+ </wa-dialog>
190
+ </DialogSurface>;
191
+ ```
192
+
193
+ Like a `Pane`'s overlay header, the title bar draws its divider as scroll
194
+ state: a 1px line along its bottom edge only while the body, or a filling
195
+ child that reaches beneath it, is scrolled away from its top, and none at
196
+ rest. The register follows those scrollers (a nested scroller that starts
197
+ below the title bar never counts), and the line is an inset shadow, so it
198
+ never moves the header or the content.
199
+
200
+ The material turns opaque under `prefers-reduced-transparency: reduce` or
201
+ `prefers-contrast: more`, and where the browser lacks `backdrop-filter`; in
202
+ forced colors the header paints `Canvas` with a `CanvasText` bottom edge.
203
+
204
+ For a title with a short supporting line, slot a `ToolbarText` as the label
205
+ (`<ToolbarText slot="label" text="Release Thread" size="xlarge" description="Engineering · 5.2 candidate" />`).
206
+ On the floating title bar an untoned `ToolbarText` keeps AA by itself: its
207
+ title takes the normal text color and its description lifts halfway toward
208
+ it. Any other quiet copy you place there can fall below AA over the content
209
+ passing beneath it, so keep it in the body (see
210
+ [Accessibility › Chrome material](accessibility.md#chrome-material)).
211
+
95
212
  Treat a dialog body as a `List` by default. When its rows or sections own their
96
213
  standard list geometry, set `bodyInset="none"` so the dialog does not add a
97
214
  second inset. Bare prose is still a list child: wrap it in `ListInsetText` so its
@@ -109,8 +226,22 @@ hand-built popover with application chrome CSS. It is a native
109
226
  `popover="auto"` section with `role="dialog"` that owns Kerf's dialog chrome
110
227
  (raised surface, border, large radius and shadow, and a backdrop) and the
111
228
  same geometry props as a modal `DialogSurface`: `size`, `preferredWidth`,
112
- `viewportGutter` (16px by default), `height` / `maxHeight`, and `bodyLayout`.
113
- Name it with `label` or `labelledBy`.
229
+ `viewportGutter` (16px by default), `height` / `maxHeight`, and `bodyLayout`
230
+ (`flow`, `fill`, or `fill-last`).
231
+ Name it with `label` or `labelledBy`. To give it an accessible description,
232
+ pass `describedBy`, the id of the element that holds the summary. It renders as
233
+ `aria-describedby` on the `role="dialog"` root and usually names a title
234
+ `ToolbarText`'s `descriptionId`:
235
+
236
+ ```tsx
237
+ <PopoverDialogSurface id="connection-details" labelledBy="connection-title" describedBy="connection-summary" bodyLayout="fill-last">
238
+ <Toolbar label="Connection Details Heading" leading={<ToolbarText text="Connection Details" size="xlarge" id="connection-title" description="Connected to the build server." descriptionId="connection-summary" />} />
239
+ {details}
240
+ </PopoverDialogSurface>;
241
+ ```
242
+
243
+ `DialogSurface` has no `describedBy`. It wraps a `wa-dialog` that the app
244
+ authors, so the app names and describes that dialog on its own markup.
114
245
 
115
246
  Open it with a control's `popoverTarget` (or `showPopover()`); the browser
116
247
  places it centered in the top layer, light-dismisses it, and restores nothing
@@ -121,8 +252,8 @@ layer and never wider than its container, for a catalog specimen or an inline
121
252
  preview.
122
253
 
123
254
  ```tsx
124
- <button type="button" popoverTarget="repository-status">Repository status</button>
125
- <PopoverDialogSurface id="repository-status" label="Repository status" preferredWidth={remify(1024)} height={remify(640)} viewportGutter={remify(8)} bodyLayout="fill">
255
+ <button type="button" popoverTarget="repository-status">Repository Status</button>
256
+ <PopoverDialogSurface id="repository-status" label="Repository Status" preferredWidth={remify(1024)} height={remify(640)} viewportGutter={remify(8)} bodyLayout="fill">
126
257
  <SplitView id="repo-split" label="Changes" list={list} detail={detail} />
127
258
  </PopoverDialogSurface>;
128
259
  ```
@@ -136,4 +267,4 @@ preserve `data-morph-skip-children` when its upgraded light-DOM items must retai
136
267
  identity across Kerf rerenders. Import `@kerfjs/ui/popup-menu/register` once for
137
268
  the focus handoff fix when wrapping a raw `wa-dropdown`.
138
269
 
139
- Import `@kerfjs/ui/surface-scaffold/register` once when using `DialogSurface`: it registers `wa-dialog` and repairs Web Awesome 3.12 native dialog names before the first modal opening. Headed dialogs reference the rendered title, including live slotted labels; headerless dialogs use the public `label`. Explicit host `aria-label` and pre-existing native names are preserved. A host that is itself an explicit dialog (`role="dialog"` or `"alertdialog"` with its own name) keeps that name alone: the native panel is left unnamed, so one dialog carries the name. Geometry-only markup stays CSS-free and does not register Web Awesome implicitly.
270
+ Import `@kerfjs/ui/surface-scaffold/register` once when using `DialogSurface`: it registers `wa-dialog`, measures each surface's footer for a floating footer's body clearance, draws a floating header's divider while content is scrolled beneath it, and repairs Web Awesome 3.12 native dialog names before the first modal opening. Headed dialogs reference the rendered title, including live slotted labels; headerless dialogs use the public `label`. Explicit host `aria-label` and pre-existing native names are preserved. A host that is itself an explicit dialog (`role="dialog"` or `"alertdialog"` with its own name) keeps that name alone: the native panel is left unnamed, so one dialog carries the name. Geometry-only markup stays CSS-free and does not register Web Awesome implicitly.