@kerfjs/ui 4.4.1 → 5.0.0-beta.10

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 (292) hide show
  1. package/README.md +240 -10
  2. package/ai/component-catalog.json +4766 -0
  3. package/ai/component-catalog.schema.json +79 -0
  4. package/ai/public-api-signatures-v1.md +1327 -0
  5. package/ai/skill.md +73 -11
  6. package/ai/webawesome-jsx-signatures-v1.md +106 -0
  7. package/dist/app-tab.d.ts +14 -2
  8. package/dist/app-tab.js +3 -1
  9. package/dist/browser/app-tab.js +3 -2
  10. package/dist/browser/disclosure-arrow.js +4 -0
  11. package/dist/browser/empty-state.js +3 -3
  12. package/dist/browser/list-action-row.js +4 -0
  13. package/dist/browser/list-header.js +6 -0
  14. package/dist/browser/list-item.js +4 -0
  15. package/dist/browser/loading-spinner.js +2 -2
  16. package/dist/browser/lucide-icon.js +2 -2
  17. package/dist/browser/panel-header.js +7 -0
  18. package/dist/browser/resizable-region.js +2 -2
  19. package/dist/browser/segmented-control.js +3 -2
  20. package/dist/browser/select.js +4 -3
  21. package/dist/browser/skeleton.js +3 -0
  22. package/dist/browser/state-banner.js +3 -2
  23. package/dist/browser/tab-bar.js +2 -2
  24. package/dist/browser/token-search-field.js +4 -0
  25. package/dist/browser/toolbar-control-group.js +2 -2
  26. package/dist/browser/toolbar-text.js +3 -2
  27. package/dist/browser/toolbar.js +2 -2
  28. package/dist/browser/value-table.js +3 -2
  29. package/dist/catalog.d.ts +119 -0
  30. package/dist/catalog.js +143 -0
  31. package/dist/catalog.js.map +1 -0
  32. package/dist/chunk-2Z5TWHKQ.js +34 -0
  33. package/dist/chunk-2Z5TWHKQ.js.map +1 -0
  34. package/dist/chunk-37T3VNIZ.js +19 -0
  35. package/dist/chunk-37T3VNIZ.js.map +1 -0
  36. package/dist/chunk-56RJXPHG.js +43 -0
  37. package/dist/chunk-56RJXPHG.js.map +1 -0
  38. package/dist/chunk-7BR3DO2A.js +31 -0
  39. package/dist/chunk-7BR3DO2A.js.map +1 -0
  40. package/dist/chunk-7JB7VPRI.js +34 -0
  41. package/dist/chunk-7JB7VPRI.js.map +1 -0
  42. package/dist/chunk-7LDCTU7D.js +134 -0
  43. package/dist/chunk-7LDCTU7D.js.map +1 -0
  44. package/dist/{chunk-4HHCYSGT.js → chunk-AQNKJRFH.js} +2 -2
  45. package/dist/{chunk-4HHCYSGT.js.map → chunk-AQNKJRFH.js.map} +1 -1
  46. package/dist/{chunk-MBR3TYNL.js → chunk-AVZHJN6C.js} +3 -3
  47. package/dist/chunk-AVZHJN6C.js.map +1 -0
  48. package/dist/chunk-B24C4A6T.js +22 -0
  49. package/dist/chunk-B24C4A6T.js.map +1 -0
  50. package/dist/chunk-BLK3UEEI.js +23 -0
  51. package/dist/chunk-BLK3UEEI.js.map +1 -0
  52. package/dist/chunk-CVZYNJJ5.js +265 -0
  53. package/dist/chunk-CVZYNJJ5.js.map +1 -0
  54. package/dist/chunk-I7INRJO4.js +17 -0
  55. package/dist/chunk-I7INRJO4.js.map +1 -0
  56. package/dist/chunk-L4OKFDJY.js +24 -0
  57. package/dist/chunk-L4OKFDJY.js.map +1 -0
  58. package/dist/chunk-LC3YSAZC.js +39 -0
  59. package/dist/chunk-LC3YSAZC.js.map +1 -0
  60. package/dist/{chunk-3O7ZEUW2.js → chunk-M5JRI6MN.js} +3 -3
  61. package/dist/chunk-M5JRI6MN.js.map +1 -0
  62. package/dist/chunk-RUNWEPR4.js +10 -0
  63. package/dist/chunk-RUNWEPR4.js.map +1 -0
  64. package/dist/chunk-W5EQ3HYQ.js +42 -0
  65. package/dist/chunk-W5EQ3HYQ.js.map +1 -0
  66. package/dist/{chunk-MQIZAD4A.js → chunk-XXMOARTH.js} +5 -3
  67. package/dist/chunk-XXMOARTH.js.map +1 -0
  68. package/dist/chunk-Y5MRDTLC.js +20 -0
  69. package/dist/chunk-Y5MRDTLC.js.map +1 -0
  70. package/dist/{chunk-TRNBHPNW.js → chunk-YGN7OMLT.js} +11 -8
  71. package/dist/chunk-YGN7OMLT.js.map +1 -0
  72. package/dist/collapsible-panel.d.ts +66 -0
  73. package/dist/collapsible-panel.js +22 -0
  74. package/dist/collapsible-panel.js.map +1 -0
  75. package/dist/device-class.d.ts +62 -0
  76. package/dist/device-class.js +75 -0
  77. package/dist/device-class.js.map +1 -0
  78. package/dist/disclosure-arrow.d.ts +14 -0
  79. package/dist/disclosure-arrow.js +4 -0
  80. package/dist/disclosure-arrow.js.map +1 -0
  81. package/dist/index.d.ts +10 -6
  82. package/dist/index.js +21 -16
  83. package/dist/list-action-row.d.ts +52 -0
  84. package/dist/list-action-row.js +5 -0
  85. package/dist/list-action-row.js.map +1 -0
  86. package/dist/list-header.d.ts +43 -0
  87. package/dist/list-header.js +7 -0
  88. package/dist/list-header.js.map +1 -0
  89. package/dist/list-item.d.ts +33 -0
  90. package/dist/list-item.js +5 -0
  91. package/dist/list-item.js.map +1 -0
  92. package/dist/nav-stack.d.ts +38 -0
  93. package/dist/nav-stack.js +5 -0
  94. package/dist/nav-stack.js.map +1 -0
  95. package/dist/panel-header.d.ts +36 -0
  96. package/dist/panel-header.js +7 -0
  97. package/dist/panel-header.js.map +1 -0
  98. package/dist/resizable-region.d.ts +3 -1
  99. package/dist/resizable-region.js +1 -1
  100. package/dist/segmented-control.d.ts +3 -1
  101. package/dist/segmented-control.js +2 -1
  102. package/dist/select-register.d.ts +5 -1
  103. package/dist/select-register.js +3 -0
  104. package/dist/select-register.js.map +1 -1
  105. package/dist/select.d.ts +5 -2
  106. package/dist/select.js +2 -1
  107. package/dist/skeleton.d.ts +24 -0
  108. package/dist/skeleton.js +3 -0
  109. package/dist/skeleton.js.map +1 -0
  110. package/dist/split-view.d.ts +42 -0
  111. package/dist/split-view.js +21 -0
  112. package/dist/split-view.js.map +1 -0
  113. package/dist/state-banner.d.ts +3 -1
  114. package/dist/state-banner.js +2 -1
  115. package/dist/styles/app-tab.css +152 -0
  116. package/dist/styles/catalog.css +498 -0
  117. package/dist/styles/collapsible-panel.css +146 -0
  118. package/dist/styles/disclosure-arrow.css +23 -0
  119. package/dist/styles/empty-state.css +47 -0
  120. package/dist/styles/foundation.css +347 -0
  121. package/dist/styles/layout.css +73 -0
  122. package/dist/styles/list-action-row.css +160 -0
  123. package/dist/styles/list-header.css +160 -0
  124. package/dist/styles/list-item.css +123 -0
  125. package/dist/styles/loading-spinner.css +21 -0
  126. package/dist/styles/lucide-icon.css +6 -0
  127. package/dist/styles/nav-stack.css +111 -0
  128. package/dist/styles/panel-header.css +64 -0
  129. package/dist/styles/resizable-region.css +111 -0
  130. package/{src → dist/styles}/segmented-control.css +62 -31
  131. package/dist/styles/select.css +138 -0
  132. package/dist/styles/skeleton.css +31 -0
  133. package/dist/styles/split-view.css +35 -0
  134. package/dist/styles/state-banner.css +173 -0
  135. package/dist/styles/styles.css +23 -0
  136. package/dist/styles/tab-bar.css +45 -0
  137. package/dist/styles/tab-scaffold.css +84 -0
  138. package/dist/styles/token-search-field.css +289 -0
  139. package/dist/styles/toolbar-control-group.css +211 -0
  140. package/dist/styles/toolbar-text.css +40 -0
  141. package/dist/styles/toolbar.css +61 -0
  142. package/dist/styles/value-table.css +75 -0
  143. package/{src → dist/styles}/webawesome.css +119 -29
  144. package/dist/styles/workbench.css +94 -0
  145. package/dist/tab-bar.d.ts +8 -1
  146. package/dist/tab-bar.js +1 -1
  147. package/dist/tab-scaffold.d.ts +30 -0
  148. package/dist/tab-scaffold.js +16 -0
  149. package/dist/tab-scaffold.js.map +1 -0
  150. package/dist/token-search-field.d.ts +43 -0
  151. package/dist/token-search-field.js +4 -0
  152. package/dist/token-search-field.js.map +1 -0
  153. package/dist/toolbar-text.d.ts +15 -3
  154. package/dist/toolbar-text.js +2 -1
  155. package/dist/value-table.d.ts +12 -2
  156. package/dist/value-table.js +2 -1
  157. package/dist/webawesome.d.ts +100 -0
  158. package/dist/webawesome.js +3 -0
  159. package/dist/webawesome.js.map +1 -0
  160. package/dist/wire-catalog.d.ts +26 -0
  161. package/dist/wire-catalog.js +48 -0
  162. package/dist/wire-catalog.js.map +1 -0
  163. package/dist/wire-nav-stack.d.ts +15 -0
  164. package/dist/wire-nav-stack.js +88 -0
  165. package/dist/wire-nav-stack.js.map +1 -0
  166. package/dist/wire-resizable-regions.js +1 -1
  167. package/dist/wire-sidebar.d.ts +43 -0
  168. package/dist/wire-sidebar.js +117 -0
  169. package/dist/wire-sidebar.js.map +1 -0
  170. package/dist/wire-tab-bars.d.ts +15 -2
  171. package/dist/wire-tab-bars.js +1 -1
  172. package/dist/wire-tab-scaffold.d.ts +11 -0
  173. package/dist/wire-tab-scaffold.js +16 -0
  174. package/dist/wire-tab-scaffold.js.map +1 -0
  175. package/dist/wire-token-search-fields.d.ts +97 -0
  176. package/dist/wire-token-search-fields.js +5 -0
  177. package/dist/wire-token-search-fields.js.map +1 -0
  178. package/dist/workbench.d.ts +33 -0
  179. package/dist/workbench.js +17 -0
  180. package/dist/workbench.js.map +1 -0
  181. package/docs/accessibility.md +101 -3
  182. package/docs/app-layouts.md +61 -0
  183. package/docs/catalog.md +155 -0
  184. package/docs/collapsible-panel.md +102 -0
  185. package/docs/component-contract.md +169 -4
  186. package/docs/component-selection.md +185 -0
  187. package/docs/design/templates/panel-header/icon-actions-dark.svg +1 -0
  188. package/docs/design/templates/panel-header/icon-actions.svg +1 -0
  189. package/docs/design/templates/panel-header/icon-summary-actions-dark.svg +1 -0
  190. package/docs/design/templates/panel-header/icon-summary-actions.svg +1 -0
  191. package/docs/design/templates/panel-header/no-icon-dark.svg +1 -0
  192. package/docs/design/templates/panel-header/no-icon.svg +1 -0
  193. package/docs/design/templates/panel-header/page-heading-dark.svg +1 -0
  194. package/docs/design/templates/panel-header/page-heading.svg +1 -0
  195. package/docs/design/templates/panel-header/title-only-dark.svg +1 -0
  196. package/docs/design/templates/panel-header/title-only.svg +1 -0
  197. package/docs/design/templates/panel-header-dark.svg +13 -0
  198. package/docs/design/templates/panel-header.svg +13 -0
  199. package/docs/design/templates/toolbar-control-group/borderless-single-dark.svg +1 -0
  200. package/docs/design/templates/toolbar-control-group/borderless-single.svg +1 -0
  201. package/docs/design/templates/toolbar-control-group/icon-buttons-dark.svg +1 -0
  202. package/docs/design/templates/toolbar-control-group/icon-buttons.svg +1 -0
  203. package/docs/design/templates/toolbar-control-group/push-buttons-dark.svg +1 -0
  204. package/docs/design/templates/toolbar-control-group/push-buttons.svg +1 -0
  205. package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -0
  206. package/docs/design/templates/toolbar-control-group/with-text.svg +1 -0
  207. package/docs/design/templates/toolbar-control-group-dark.svg +11 -0
  208. package/docs/design/templates/toolbar-control-group.svg +11 -0
  209. package/docs/design/templates.md +83 -0
  210. package/docs/design-philosophy.md +61 -1
  211. package/docs/device-class.md +54 -0
  212. package/docs/examples/command-palette-adapter.tsx +70 -0
  213. package/docs/layout.md +131 -0
  214. package/docs/nav-stack.md +47 -0
  215. package/docs/recipes.md +167 -0
  216. package/docs/split-view.md +49 -0
  217. package/docs/tab-scaffold.md +41 -0
  218. package/docs/ux-demo.md +23 -9
  219. package/docs/webawesome-theme.md +15 -0
  220. package/docs/workbench.md +47 -0
  221. package/llms.txt +123 -14
  222. package/package.json +139 -44
  223. package/ux-demo/recipes/app-shell.tsx +32 -0
  224. package/ux-demo/recipes/collapsible-sidebar.tsx +100 -0
  225. package/ux-demo/recipes/compact-toolbar.tsx +43 -0
  226. package/ux-demo/recipes/composer-form.tsx +63 -0
  227. package/ux-demo/recipes/list-detail-dialog.tsx +45 -0
  228. package/ux-demo/recipes/list-workspace-states.tsx +26 -0
  229. package/ux-demo/recipes/loaders.ts +33 -0
  230. package/ux-demo/recipes/loading-inspector.tsx +81 -0
  231. package/ux-demo/recipes/mount-recipe.ts +43 -0
  232. package/ux-demo/recipes/navigation-sidebar.tsx +26 -0
  233. package/ux-demo/recipes/navigation-stack.tsx +76 -0
  234. package/ux-demo/recipes/recipes.css +448 -0
  235. package/ux-demo/recipes/types.ts +17 -0
  236. package/ux-demo/recipes/workspace-header.tsx +12 -0
  237. package/dist/browser/dialog-header.js +0 -3
  238. package/dist/browser/menu-header.js +0 -3
  239. package/dist/browser/menu-item.js +0 -3
  240. package/dist/browser/page-header.js +0 -3
  241. package/dist/chunk-2PES33HS.js +0 -13
  242. package/dist/chunk-2PES33HS.js.map +0 -1
  243. package/dist/chunk-3O7ZEUW2.js.map +0 -1
  244. package/dist/chunk-723YNJBH.js +0 -17
  245. package/dist/chunk-723YNJBH.js.map +0 -1
  246. package/dist/chunk-GY5WH7TO.js +0 -10
  247. package/dist/chunk-GY5WH7TO.js.map +0 -1
  248. package/dist/chunk-MBR3TYNL.js.map +0 -1
  249. package/dist/chunk-MQIZAD4A.js.map +0 -1
  250. package/dist/chunk-NHXX4JI5.js +0 -28
  251. package/dist/chunk-NHXX4JI5.js.map +0 -1
  252. package/dist/chunk-OPCJRCYO.js +0 -19
  253. package/dist/chunk-OPCJRCYO.js.map +0 -1
  254. package/dist/chunk-T5XNMVOI.js +0 -24
  255. package/dist/chunk-T5XNMVOI.js.map +0 -1
  256. package/dist/chunk-TRNBHPNW.js.map +0 -1
  257. package/dist/chunk-VHW5YNIB.js +0 -17
  258. package/dist/chunk-VHW5YNIB.js.map +0 -1
  259. package/dist/chunk-VKH2JLDZ.js +0 -10
  260. package/dist/chunk-VKH2JLDZ.js.map +0 -1
  261. package/dist/chunk-WY4CAEPG.js +0 -14
  262. package/dist/chunk-WY4CAEPG.js.map +0 -1
  263. package/dist/dialog-header.d.ts +0 -14
  264. package/dist/dialog-header.js +0 -3
  265. package/dist/dialog-header.js.map +0 -1
  266. package/dist/menu-header.d.ts +0 -15
  267. package/dist/menu-header.js +0 -3
  268. package/dist/menu-header.js.map +0 -1
  269. package/dist/menu-item.d.ts +0 -22
  270. package/dist/menu-item.js +0 -3
  271. package/dist/menu-item.js.map +0 -1
  272. package/dist/page-header.d.ts +0 -9
  273. package/dist/page-header.js +0 -3
  274. package/dist/page-header.js.map +0 -1
  275. package/src/app-tab.css +0 -20
  276. package/src/dialog-header.css +0 -9
  277. package/src/empty-state.css +0 -6
  278. package/src/foundation.css +0 -106
  279. package/src/loading-spinner.css +0 -4
  280. package/src/lucide-icon.css +0 -1
  281. package/src/menu-header.css +0 -13
  282. package/src/menu-item.css +0 -12
  283. package/src/page-header.css +0 -3
  284. package/src/resizable-region.css +0 -19
  285. package/src/select.css +0 -18
  286. package/src/state-banner.css +0 -31
  287. package/src/styles.css +0 -18
  288. package/src/tab-bar.css +0 -4
  289. package/src/toolbar-control-group.css +0 -25
  290. package/src/toolbar-text.css +0 -4
  291. package/src/toolbar.css +0 -9
  292. package/src/value-table.css +0 -5
package/docs/layout.md ADDED
@@ -0,0 +1,131 @@
1
+ # Pane and content layout
2
+
3
+ Import `@kerfjs/ui/layout.css` for the shared geometry used by sidebars, main
4
+ areas, inspectors, and dialogs. The vocabulary deliberately describes structure
5
+ rather than location: the same pane can be placed anywhere.
6
+
7
+ ## Anatomy
8
+
9
+ ```tsx
10
+ <aside class="kui-pane">
11
+ <div class="kui-pane__toolbar"><Toolbar label="Workspace" ... /></div>
12
+ <div class="kui-pane__content kui-content">
13
+ <section>...</section>
14
+ <section>...</section>
15
+ </div>
16
+ <footer class="kui-pane__footer"><Toolbar label="Actions" ... /></footer>
17
+ </aside>
18
+ ```
19
+
20
+ `.kui-pane` has no padding. It reserves rows for an optional toolbar, one
21
+ scrolling content area, and an optional footer. A main area or dialog often
22
+ omits the footer; a navigation pane commonly uses all three. Fixed chrome stays
23
+ outside `.kui-pane__content`, which is the pane's scroll owner.
24
+
25
+ `.kui-content` is a vertical stack with a 24px gap between major children.
26
+ Sections may contain adjacent `ListItem` rows without adding another major gap.
27
+ Ordinary surface-like children use `.kui-content-item` and own their complete
28
+ geometry:
29
+
30
+ - 8px inline margin from the pane edge
31
+ - 1px border, transparent by default
32
+ - 8px internal padding and 8px internal gap
33
+ - `calc(1px + remify(11px))`, or 12px, rounded corners
34
+
35
+ Use `.kui-content-item--pill` for the 22px pill radius expressed as
36
+ `calc(1px + remify(21px))`. Consumers can make a content item visible without
37
+ changing its geometry by setting `--kui-content-item-border` and
38
+ `--kui-content-item-background`.
39
+
40
+ A visible parent surface does not make every child another visible card. The
41
+ composer recipe keeps its form as the single surface, uses `PanelHeader` for
42
+ its task hierarchy, and places field and action control edges on the shared 8px
43
+ inline gutter instead of nesting them inside another padded content item. A
44
+ conditional `StateBanner` remains visibly distinct because it communicates
45
+ semantic status.
46
+
47
+ ## Public roles and tokens
48
+
49
+ | Need | Class | Token / default |
50
+ | --- | --- | --- |
51
+ | Unpadded toolbar/content/footer structure | `.kui-pane` | — |
52
+ | Scrolling pane content | `.kui-pane__content` | — |
53
+ | Major vertical rhythm | `.kui-content` | `--kui-layout-content-gap: 24px` |
54
+ | Self-contained child geometry | `.kui-content-item` | 8px margin + 1px border + 8px padding |
55
+ | Pill child | `.kui-content-item--pill` | `--kui-layout-pill-radius: 22px` |
56
+ | Related controls | `.kui-control-cluster` | `--kui-layout-control-gap: 8px` |
57
+ | Inline metadata | `.kui-inline-metadata` | `--kui-layout-metadata-gap: 4px` |
58
+ | Explicit scroll owner outside a pane | `.kui-scroll-owner` | `overflow: auto` |
59
+
60
+ The component layer applies the same contract to `Toolbar`, `ListHeader`,
61
+ `ListItem`, `PanelHeader`, `StateBanner`, `ValueTable`,
62
+ `ValueTableRow`, tabs, and form controls. A value-table row separator starts at
63
+ the row's 8px content inset, or at 40px when the row contains its 24px leading
64
+ icon and 8px gap, and always ends 8px from the right edge. Each value-table row
65
+ also keeps 8px of root-scaled block padding independently of its semantic inline
66
+ inset. Most interactive rows and toolbar groups are 44px tall.
67
+ Toolbar groups reserve a real 1px outer border around a 42px inner area, even
68
+ when their border and background are transparent.
69
+ `PanelHeader` is a plain top `Toolbar` used as a panel, dialog, or page heading
70
+ and overrides no Toolbar styles. The leading zone holds the optional icon (a
71
+ normal bordered `ToolbarControlGroup` given a brand fill with a matching border,
72
+ a 22px glyph) and the title as extra-large `ToolbarText`; the app's trailing
73
+ controls go straight into the trailing zone, and the icon group is omitted when
74
+ no icon is passed. The optional subtitle is a separate row below the toolbar, its
75
+ text left-aligned with the title text.
76
+
77
+ ## Spacing scale
78
+
79
+ Spacing is not a free choice. The official scale is five canonical steps, each
80
+ expressing exactly one relationship — pick the step by **how connected two
81
+ elements are**, not by eye. Every scalable value is `remify`-authored against the
82
+ fixed 16px baseline, so it delivers as `rem`.
83
+
84
+ | Value | Token | Relationship — when to use |
85
+ | --- | --- | --- |
86
+ | 0px | `--kui-space-none` | No separation. The elements read as a single unit (a control and its own affordance, adjacent `ListItem` rows). |
87
+ | 4px | `--kui-space-2xs` | Very minor. Still one connected cluster, but readability or aesthetics demand a hair of air (inline metadata, an icon beside its label). |
88
+ | 8px | `--kui-space-xs` | Standard. Between elements **within a group** — the content-item padding/gutter, gaps between toolbar controls in a group. |
89
+ | 16px | `--kui-space-m` | Minor. Between **homogeneous groups** — two lists, two sibling sections of the same kind. |
90
+ | 24px | `--kui-space-l` | Major. Between **heterogeneous groups** — the `.kui-content` rhythm between major, differing regions. |
91
+
92
+ The two relationships that get confused most are 8px vs 24px: 8px is *inside* a
93
+ group, 24px is *between* major, differing regions. 16px sits between them for
94
+ same-kind groups.
95
+
96
+ `--kui-space-s` (12px) and `--kui-space-xl` (32px) exist but are **off the
97
+ canonical rhythm** — reach for them only as a deliberate exception, never as a
98
+ default step. Prefer the five canonical tokens so spacing stays legible and
99
+ consistent across every surface.
100
+
101
+ ## Ownership rules
102
+
103
+ 1. Do not pad a sidebar, main area, dialog, or `.kui-pane` shell. Children own
104
+ their own margin, border, background, padding, and radius.
105
+ 2. Use 24px gaps for major vertical separation and 8px gaps inside an item or
106
+ between toolbar groups. Do not confuse the two relationships.
107
+ 3. Wrap toolbar content in `ToolbarControlGroup`, including dormant text. A
108
+ transparent group still reserves the same 44px geometry as a visible group.
109
+ 4. Keep one scrolling content owner per pane. Toolbar and footer siblings stay
110
+ fixed while the content scrolls.
111
+ 5. A split item keeps dormant and interactive regions separate. For example,
112
+ `ListHeader` renders its title/count-or-badge cluster separately from its optional
113
+ logical-end 44px action. The header fills the available inline width and its
114
+ action glyph defaults to 18px; disclosure mode makes the title cluster itself
115
+ the button and supplies the production `DisclosureArrow` unless `actionIcon`
116
+ replaces it.
117
+ `ListActionRow` uses a noninteractive row root around sibling 44px primary
118
+ and trailing buttons. `ListItem.trailing` remains dormant content.
119
+ In multiline `ListItem` and `ListActionRow` rows, the leading icon stays
120
+ centered on the label's first line rather than the full wrapped label.
121
+ 6. Reading width, column placement, and responsive relocation remain application
122
+ decisions. The shared classes define local geometry, not the whole shell.
123
+ 7. A visible collapsible pane owns its collapse control in that pane's toolbar.
124
+ When the pane is hidden, move the restore control into the adjacent main
125
+ toolbar on the same logical edge: an inline-start pane restores from the
126
+ main toolbar's leading group, and an inline-end pane restores from its
127
+ trailing group. Do not leave an otherwise empty icon-only rail behind.
128
+
129
+ At narrow widths or 200% zoom, relocate or stack panes before shrinking targets.
130
+ The 8/1/8 item contract and 44px controls remain stable, so screenshots and
131
+ focus-order tests exercise the same model at every viewport.
@@ -0,0 +1,47 @@
1
+ # Navigation stack
2
+
3
+ `@kerfjs/ui/nav-stack` is an iOS-style push/pop navigation stack: views slide in
4
+ and out over one another while the top chrome settles. A **single-pane layout is
5
+ a `NavStack` with one entry**. It is one of the opt-in app layouts (see
6
+ [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
7
+
8
+ Import the component and its companion stylesheet:
9
+
10
+ ```ts
11
+ import { NavStack, type NavStackView } from '@kerfjs/ui/nav-stack';
12
+ import { wireNavStack } from '@kerfjs/ui/wire-nav-stack';
13
+ import '@kerfjs/ui/nav-stack.css';
14
+ ```
15
+
16
+ ## State lives in the app
17
+
18
+ Like every `@kerfjs/ui` component, `NavStack` is declarative: the app owns the
19
+ stack as a `signal<NavStackView[]>`, `NavStack({ views })` renders it, and
20
+ `wireNavStack` animates the transitions.
21
+
22
+ ```tsx
23
+ const views = signal<NavStackView[]>([{ key: 'inbox', title: 'Inbox', content: <InboxView /> }]);
24
+
25
+ // render inside mount():
26
+ <NavStack id="mail" label="Mail" views={views.value} />;
27
+
28
+ // once, after first render:
29
+ const dispose = wireNavStack(root, { onBack: () => { views.value = views.value.slice(0, -1); } });
30
+
31
+ // push / pop by editing the signal:
32
+ views.value = [...views.value, { key: id, title: 'Message', content: <MessageView id={id} /> }];
33
+ ```
34
+
35
+ `NavStack` renders every entry stacked, the last one active and the rest kept
36
+ mounted (so their DOM state and focus survive) but hidden. Each entry carries a
37
+ `key` (stable identity), `content`, an optional `title`, and optional per-view
38
+ `toolbar` actions. The back control appears automatically once the stack has more
39
+ than one entry; `wireNavStack`'s `onBack` is where the app pops its own signal.
40
+
41
+ ## Transitions
42
+
43
+ `wireNavStack(root, { onBack, duration? })` observes the rendered stack and
44
+ animates each change: a pushed view slides in from the trailing edge; a popped
45
+ view slides back off it over the revealed view. It returns a disposer. The
46
+ animation honors `prefers-reduced-motion` (transitions collapse to instant) and
47
+ `duration: 0` disables it. Applicable at every device size and inside dialogs.
@@ -0,0 +1,167 @@
1
+ # Production composition recipes
2
+
3
+ These seven reference compositions sit between individual primitives and product
4
+ code. Open each stable UX-catalog route to run it, then copy the linked TSX and
5
+ CSS. The examples import public package subpaths, use semantic layout owners,
6
+ and keep state in a per-instance application adapter. They are not new
7
+ monolithic components.
8
+
9
+ Copy the recipe source together with the catalog-independent
10
+ [`mount-recipe.ts`](../ux-demo/recipes/mount-recipe.ts) adapter. It mounts the
11
+ controller at one stable application root, uses `delegateActions()` for recipe
12
+ commands, forwards form and dialog lifecycle events, wires resize commits with
13
+ the public `onCommit` callback, retains every disposer, and returns one
14
+ idempotent disposer:
15
+
16
+ ```ts
17
+ import { createRecipe } from './navigation-sidebar.js';
18
+ import { mountRecipe } from './mount-recipe.js';
19
+
20
+ const root = document.querySelector<HTMLElement>('#navigation')!;
21
+ const stopRecipe = mountRecipe(root, createRecipe(announce));
22
+ window.addEventListener('pagehide', stopRecipe, { once: true });
23
+ ```
24
+
25
+ The adapter is delivered as reference source, not a new package runtime export.
26
+ `delegate()` remains a valid alternative when an application needs selector-
27
+ specific dispatch; either way, wire once at a stable root and retain disposal.
28
+
29
+ ## Desktop application shell
30
+
31
+ [Open the recipe](../ux-demo/?component=recipe-app-shell) · [TSX source](../ux-demo/recipes/app-shell.tsx) · [shared CSS](../ux-demo/recipes/recipes.css)
32
+
33
+ Use `Toolbar`, controlled `ResizableRegion` panes, and one
34
+ `.kui-pane__content` scroll owner per pane. The recipe owns the shell topology;
35
+ the app owns routing, responsive pane visibility, sizes, persistence, and data.
36
+ Keep each visible pane's collapse action in its own toolbar. Once hidden, put
37
+ its restore action in the adjacent main toolbar on the same logical edge:
38
+ leading for an inline-start sidebar and trailing for an inline-end inspector.
39
+ Collapse the pane completely rather than preserving an empty icon rail.
40
+ Adapt only public `--kui-layout-*` and component variables.
41
+
42
+ ## Navigation sidebar
43
+
44
+ [Open the recipe](../ux-demo/?component=recipe-navigation-sidebar) · [TSX source](../ux-demo/recipes/navigation-sidebar.tsx)
45
+
46
+ One unpadded `.kui-pane` owns toolbar/content/footer structure. Its
47
+ `.kui-content` uses 24px major gaps; `ListHeader`, `ListItem`, and other
48
+ `.kui-content-item` children own their 8px margin, 1px border, and 8px padding.
49
+ Rows and footer toolbar groups remain 44px tall. The app owns routes,
50
+ permissions, labels, selection, valid section counts and their localized
51
+ `countLabel` phrases, non-count badge content, disclosure state, and revealed
52
+ content. A toggled `ListHeader` supplies the production `DisclosureArrow` when
53
+ no custom `actionIcon` is needed; ordinary navigation rows stay chevron-free.
54
+
55
+ ## Workspace header
56
+
57
+ [Open the recipe](../ux-demo/?component=recipe-workspace-header) · [TSX source](../ux-demo/recipes/workspace-header.tsx)
58
+
59
+ `PanelHeader` owns the page heading while one control cluster holds secondary,
60
+ overflow, and primary actions. The app owns authorization and command policy;
61
+ controls relocate without changing focus order.
62
+
63
+ ## List-detail dialog
64
+
65
+ [Open the recipe](../ux-demo/?component=recipe-list-detail-dialog) · [TSX source](../ux-demo/recipes/list-detail-dialog.tsx)
66
+
67
+ The production Web Awesome dialog owns modal focus and Escape; the thin recipe
68
+ adapter restores the invoking control consistently after the hide event.
69
+ `PanelHeader`, `ListHeader`, `ListItem`, and `ValueTable` own their included
70
+ anatomy. The application owns open state, selection, dismissal policy, and
71
+ record actions. `PanelHeader` places the trailing controls the app passes
72
+ directly into its top toolbar's trailing zone.
73
+ The header sits on the dialog edge while retaining its internal control inset;
74
+ the selected title receives the full content gutter, the metadata table fills
75
+ the available detail width between the usual outer margins, and the action
76
+ cluster uses one outer gutter without a second content-item inset.
77
+ Do not rebuild the dialog or reach into private shadow parts.
78
+
79
+ ## Composer form
80
+
81
+ [Open the recipe](../ux-demo/?component=recipe-composer-form) · [TSX source](../ux-demo/recipes/composer-form.tsx)
82
+
83
+ `PanelHeader` supplies the task title and summary, with their ids referenced
84
+ by the form. Production fields own labels, help, and native focus. The field
85
+ and footer control edges sit directly on the shared 8px inline gutter rather
86
+ than acquiring a second content-item padding inset; major children remain 24px
87
+ apart and related controls use 8px gaps. The app owns
88
+ validation, drafts, permissions, and transport. Persistent error or success
89
+ feedback is the only nested semantic surface and uses `StateBanner`, not a toast.
90
+ Because upgraded Web Awesome fields retain live value properties, controlled
91
+ resets synchronize both those properties and the rendered value attributes;
92
+ the Reset action also announces `Draft reset` through the catalog live region.
93
+
94
+ ## List workspace states
95
+
96
+ [Open the recipe](../ux-demo/?component=recipe-list-workspace-states) · [TSX source](../ux-demo/recipes/list-workspace-states.tsx)
97
+
98
+ The same content region moves deterministically through loading, empty,
99
+ populated, stale/background refresh, and error/retry states. The recipe owns
100
+ feedback placement; the app owns fetching, cache age, retry policy, and domain
101
+ rows.
102
+
103
+ ## Compact toolbar choices and actions
104
+
105
+ [Open the recipe](../ux-demo/?component=recipe-compact-toolbar) · [TSX source](../ux-demo/recipes/compact-toolbar.tsx)
106
+
107
+ Use `ToolbarControlGroup` for related commands, `SegmentedControl` for a few
108
+ visible exclusive choices, `Select` for a longer value list, and an ordinary
109
+ button for an independent command. The app owns values, actions, persistence,
110
+ and responsive priority.
111
+
112
+ ## Navigation stack
113
+
114
+ [Open the recipe](../ux-demo/?component=recipe-navigation-stack) · [TSX source](../ux-demo/recipes/navigation-stack.tsx)
115
+
116
+ Drill from a library list into a detail and back with `NavStack`
117
+ (`@kerfjs/ui/nav-stack`): the app owns the stack as a signal of views and
118
+ pushes/pops it, `NavStack` renders it, and `wireNavStack` slides the content and
119
+ settles the chrome (reduced motion collapses the slide to instant). A live
120
+ `deviceClass()` badge shows the current size/orientation. See the layout guide
121
+ [`app-layouts.md`](app-layouts.md) for choosing among `NavStack`, `SplitView`,
122
+ `Workbench`, and `TabScaffold`.
123
+
124
+ ## Loading inspector
125
+
126
+ [Open the recipe](../ux-demo/?component=recipe-loading-inspector) · [TSX source](../ux-demo/recipes/loading-inspector.tsx)
127
+
128
+ A record inspector whose per-record values load asynchronously. Every
129
+ value-bearing component (`PanelHeader`, `ValueTable`/`ValueTableRow`, `Select`,
130
+ `SegmentedControl`, `ListItem`, `StateBanner`) takes its `placeholder` from one
131
+ loading flag, so the same real chrome renders a faithful loading state and then
132
+ the populated record — no separate skeleton markup. The composition is the point;
133
+ `Skeleton` is the primitive it builds on. The app owns the loading lifecycle and
134
+ which values are still unknown.
135
+
136
+ ## Collapsible sidebar
137
+
138
+ [Open the recipe](../ux-demo/?component=recipe-collapsible-sidebar) · [TSX source](../ux-demo/recipes/collapsible-sidebar.tsx)
139
+
140
+ A mini app frame whose left navigation rail and bottom activity drawer are
141
+ standalone `CollapsiblePanel`s (`@kerfjs/ui/collapsible-panel`) driven by
142
+ `wireSidebar` (`@kerfjs/ui/wire-sidebar`). `CollapsiblePanelToggle` supplies the
143
+ standard per-side glyph: a collapse toggle lives inside each panel and an expand
144
+ toggle lives in the always-visible main header, so a collapsed panel is still
145
+ reachable. `wireSidebar` owns the toggle delegation, moves focus into a panel on
146
+ open and restores it to the trigger on close, and — when a `deviceClass()` reports
147
+ `compact` — switches the open panel to a dismissable **overlay** (backdrop, Escape
148
+ and backdrop-click collapse, and a trapped Tab ring, the ARIA dialog pattern). It
149
+ also persists each panel's collapsed state through a supplied storage hook. The app
150
+ owns each `collapsed` signal, the panel sizes, and the content; the wire owns the
151
+ ephemeral interaction. For a full three-pane shell use `Workbench` instead — see
152
+ [`app-layouts.md`](app-layouts.md). This recipe is covered end-to-end across
153
+ Chromium, Firefox, and WebKit by `tests/browser/collapsible-sidebar-recipe.spec.ts`.
154
+
155
+ ## Rules shared by every recipe
156
+
157
+ - Import `@kerfjs/ui/layout.css`; keep every pane unpadded and use exactly one
158
+ `.kui-pane__content` scroll owner for each real boundary.
159
+ - For direct `wa-*` JSX, import types from `@kerfjs/ui/webawesome`. Import only
160
+ individual Web Awesome registration modules and theme them with
161
+ `@kerfjs/ui/webawesome.css`.
162
+ - Prefer public props and variables at the composition boundary. When a recipe
163
+ needs responsive topology, join only classes listed in the catalog's
164
+ `publicClasses`; do not copy component markup or select descendants by tag,
165
+ id, attribute alone, or an unlisted implementation class.
166
+ - Start from the copyable mount adapter, or reproduce its complete boundary:
167
+ wire stable `data-action` hooks once and retain every disposer.
@@ -0,0 +1,49 @@
1
+ # Split view (list-detail)
2
+
3
+ `@kerfjs/ui/split-view` is a list-detail layout: a list and a
4
+ detail side by side on roomy screens, collapsing to a `NavStack` (list → detail)
5
+ on compact ones. One of the opt-in app layouts (see
6
+ [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
7
+
8
+ ```ts
9
+ import { SplitView } from '@kerfjs/ui/split-view';
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.
14
+ ```
15
+
16
+ ## Responsive by device class
17
+
18
+ `SplitView` is declarative; the app derives `compact` from the device class and
19
+ tracks its own selection:
20
+
21
+ ```tsx
22
+ const device = deviceClass();
23
+ const selected = signal<string | null>(null);
24
+
25
+ <SplitView
26
+ id="mail"
27
+ label="Mail"
28
+ compact={device.value.compact}
29
+ detailActive={selected.value !== null}
30
+ list={<ThreadList />}
31
+ detail={<Message id={selected.value} />}
32
+ listTitle="Threads"
33
+ detailTitle="Message"
34
+ resizable={{ size: 320, min: 220, max: 480 }}
35
+ />;
36
+ ```
37
+
38
+ - **Roomy** (`compact: false`): both panes show. With `resizable`, the list sits
39
+ in a `ResizableRegion` (wire it with `wireResizableRegions`); without it the
40
+ list takes a fixed `--kui-split-view-list-width` (default 320px) and the detail
41
+ fills the rest.
42
+ - **Compact** (`compact: true`): the split collapses to a `NavStack`. The list is
43
+ the root; when `detailActive` is true the detail is pushed over it with an
44
+ automatic back control (wire it with `wireNavStack`, whose `onBack` clears the
45
+ app's selection). This is the portrait-tablet / handset presentation; as a
46
+ dialog the compact form is a full-screen or large partial-cover modal.
47
+
48
+ Compose the interactive wiring from the existing helpers — `SplitView` adds no
49
+ wire of its own.
@@ -0,0 +1,41 @@
1
+ # Tab scaffold
2
+
3
+ `@kerfjs/ui/tab-scaffold` is a mobile-first, iOS-like bottom tab bar that switches
4
+ between major app sections, where **each tab keeps its own content mounted** —
5
+ usually a `NavStack`, so each tab's stack and scroll survive a switch. One of the
6
+ opt-in app layouts (see [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
7
+ It is distinct from `TabBar` (document-oriented, reorderable strips).
8
+
9
+ ```ts
10
+ import { TabScaffold } from '@kerfjs/ui/tab-scaffold';
11
+ import { wireTabScaffold } from '@kerfjs/ui/wire-tab-scaffold';
12
+ import '@kerfjs/ui/tab-scaffold.css';
13
+ ```
14
+
15
+ ## Controlled selection
16
+
17
+ The app owns the active tab (a signal); `TabScaffold` renders every tab's scene
18
+ (only the active one visible) plus the bottom bar, and `wireTabScaffold` reports
19
+ clicks.
20
+
21
+ ```tsx
22
+ const active = signal('home');
23
+
24
+ <TabScaffold
25
+ id="app"
26
+ label="Sections"
27
+ active={active.value}
28
+ tabs={[
29
+ { id: 'home', label: 'Home', icon: <HomeIcon />, content: <HomeStack /> },
30
+ { id: 'search', label: 'Search', icon: <SearchIcon />, content: <SearchStack /> },
31
+ ]}
32
+ />;
33
+
34
+ // once, after first render:
35
+ const dispose = wireTabScaffold(root, { onSelect: (id) => { active.value = id; } });
36
+ ```
37
+
38
+ Each `TabScaffoldTab` has an `id`, `label`, optional `icon`, and `content`. The
39
+ bottom bar respects the home-indicator safe area (`env(safe-area-inset-bottom)`)
40
+ and keeps 44px targets. On larger device classes, promote the tab set to a
41
+ `Workbench` rail or a persistent sidebar instead of a bottom bar.
package/docs/ux-demo.md CHANGED
@@ -1,18 +1,32 @@
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.
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.
4
+
5
+ Seven lazy production-composition routes live under the `Recipes` category.
6
+ Each `?component=recipe-*` route uses public package primitives, real wiring,
7
+ deterministic state, and semantic layout owners from the [recipe guide](./recipes.md).
4
8
 
5
9
  - It imports public component subpaths, exercising their browser-selected reachable CSS rather than a catalog-only style path.
10
+ - The `ListActionRow` route demonstrates sibling primary/trailing native controls, independent disabled states, controlled selection, first-line leading-icon alignment for wrapped labels, and an application-owned native popover relationship without nesting controls. The `ListItem` route demonstrates the same multiline leading-icon anchor alongside its single-control navigation contract.
11
+ - The `AppTab` route demonstrates a runtime-filtered domain metadata slot and a decorative replacement close icon without changing TabBar selection, close, reorder, or focus ownership. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
6
12
  - Every first-party visual component and every free Web Awesome 3.12 component has its own stable `?component=<id>` URL and focused demo. Composite header, menu, feedback, and theme scenarios remain addressable alongside their subcomponents; the root opens the first Kerf component.
7
- - Catalog metadata has a unique id, category, kind (`component` or `composition`), source (`kerf` or `webawesome`), name, description, and direct `uses` dependencies.
8
- - The master/detail shell groups first-party routes by category with production `MenuHeader` and `MenuItem` components. The 70 ecosystem components live in a separate `Web Awesome` disclosure with seven subgroups, so they remain discoverable without being presented as first-class `@kerfjs/ui` exports. Direct Web Awesome routes open the disclosure and reveal the selected row at wide sizes; narrow layouts constrain the expanded list to its own scroll region.
9
- - A detail with relationships renders one grouped selector: `Uses` links to direct dependencies and `Used by` links to reverse dependents. Choosing either navigates to that component's demo. The footer is absent when both groups are empty; no duplicate relationship summary is rendered.
13
+ - `ai/component-catalog.json` is the canonical catalog metadata. A deterministic checked-in projection supplies the demo's unique id, category, kind (`component`, `composition`, or `recipe`), source (`kerf` or `webawesome`), name, description, direct `uses` dependencies, existing documentation path, and demo-source path; first-party, recipe, and Web Awesome renderer maps are exhaustively typed against its generated id unions. Demo paths resolve to `ui/ux-demo/main.tsx`, the matching `ui/ux-demo/recipes/*.tsx` file, or `ui/ux-demo/webawesome-demos.tsx` according to the canonical entry. First-party component entries additionally derive `ui/src/<browser-subpath>.tsx` from their canonical `delivery.browserImport`.
14
+ - Every detail footer groups compact `Demo source` and `Guidance` actions; first-party component details also expose `Component source`. Web Awesome details say `Integration guidance` to distinguish the local integration contract from the upstream component implementation. Each button retains its repository-relative path in the DOM for verification and uses a deploy-safe absolute GitHub blob URL with a contextual new-tab name; the demo does not depend on a local source server or add a package runtime export. At phone widths, the resource group and related-component selector stack as full-width rows, with any resource overflow contained locally so labels cannot overlap the selector or widen the document.
15
+ - The master/detail shell groups first-party routes by category with production `ListHeader` and `ListItem` components. The 70 ecosystem components live in a separate `Web Awesome` disclosure with seven subgroups, so they remain discoverable without being presented as first-class `@kerfjs/ui` exports. Direct Web Awesome routes open the disclosure and reveal the selected row at wide sizes; narrow layouts keep the expanded list in document flow rather than introducing a competing nested scroller.
16
+ - The shell itself uses `@kerfjs/ui/layout.css`: sidebars, details, previews, dialogs, and resize specimens share unpadded panes plus self-contained content items. Its sidebar identity uses the Kerf logo beside a vertically centered title, with the subtitle in its own aligned row like `PanelHeader`; the current-view heading is also a toolbar. Collapsing the catalog removes the pane completely and moves its restore action to the main toolbar's leading edge instead of leaving an icon-only rail. The selected view description remains inset below the page toolbar. The checkerboard belongs to the complete scrollable preview region rather than a nested card. A lightly separated footer keeps live status above compact source, guidance, and relationship groups. The focused `ResizableRegion` specimen stretches its surface through the available stage height and reports committed width in that shared status footer; narrow layouts retain local horizontal access when the pane is resized wider than the stage, and the split remains readable at 200% root scaling. Geometry coverage checks wide, intermediate, narrow, and 200% zoom layouts.
17
+ - The composer-form recipe demonstrates one coherent visible form surface with `PanelHeader` title/summary hierarchy, fields and actions on the shared 8px control gutter, and 24px major rhythm. Its conditional error or success `StateBanner` is the only nested semantic surface. Reset synchronizes the upgraded Web Awesome fields' live value properties with their empty controlled attributes and announces `Draft reset`; browser coverage preserves behavior and verifies wide, narrow, dark, validation, success, reset, 200% zoom, and forced-colors states.
18
+ - The list-detail dialog recipe places its header directly on the dialog edge while preserving the header's internal inset, gives the selected project title the complete outer-plus-inner content gutter, lets the value table fill the available detail column between the shared outer margins, and keeps record actions on one outer gutter without an extra content-item inset. Browser geometry coverage protects those relationships at wide and narrow widths.
19
+ - Catalog-only brand marks, preview dimensions, checkerboard tiles, and the compound display-settings control retain local geometry because they are specimen chrome or intrinsic control anatomy rather than reusable application spacing roles.
20
+ - The menu composition route demonstrates toolbar/content/footer anatomy, 24px major gaps, 8/1/8 child geometry, the full-width `ListHeader` count-or-badge/action split, logical-end 44px action targets with 18px visuals, and 44px row/header/toolbar targets. Its one chevron is a production 18px `DisclosureArrow` inside a controlled Tools header that actually reveals and hides content; ordinary Projects navigation has no false disclosure affordance. Browser assertions cover stable naming, expanded/content transitions, visual rotation, node identity, reduced motion, RTL alignment and containment, and wide, narrow, dark, and 200% layouts. The focused ListHeader route separately shows the shared neutral count pill for Attachments, Notes (including zero), and Duplicates plus a non-count legacy badge, full-width logical-end actions, and no disclosure without content.
21
+ - The `DisclosureArrow` route renders the production 18px root-scaled default without a catalog-only size override. Its two native buttons independently control the default right-to-down chevron and a recognizable right-facing replacement arrow with left-to-up custom directions; both use shortest-path rotation and their names stay stable while `aria-expanded` communicates state. Browser coverage verifies the custom clockwise path, pointer and native keyboard activation, focus, narrow and 200%-zoom stacking with contained labels, consumer sizing, and the independent `.5` Web Awesome glyph scale retained by Kerf `Select`.
22
+ - A detail with relationships renders one grouped footer selector: `Uses` links to direct dependencies and `Used by` links to reverse dependents. Choosing either navigates to that component's demo. The relationship group is absent when both sets are empty; no duplicate summary is rendered.
10
23
  - Demo-only CSS supplies only the catalog shell and stages; component appearance stays in package CSS. Decorative chrome and Web Awesome controls share the same semantic system palette instead of carrying a separate demo identity.
11
- - Scenarios use deterministic data and cover real variants, long content, disabled state, selection, progress, feedback, overlays, media, helpers, and stateful interactions. The Web Awesome theme route remains the broad system view, while individual action, form, layout, navigation, feedback, media, and helper routes provide focused specimens; the Animation route exposes preset, easing, duration, playback-rate, and transport controls with live lifecycle status; observer routes provide user-driven intersection, mutation, and resize events with live results; ToolbarControlGroup exposes all eight Hot Sheet 2 variants; SegmentedControl covers toolbar, rounded-rectangle, pill, equal-width, and disabled presentations; StateBanner exposes every built-in tone plus a scoped override; TabBar demonstrates overflow, edge autoscroll, and controlled pointer/keyboard reordering.
12
- - Settings exercise light/dark, increased contrast, and reduced motion without changing component code, and expose each preference through `aria-pressed`.
13
- - The action log is an `aria-live` region, making interactions visible to both people and browser tests.
24
+ - Scenarios use deterministic data and cover real variants, long content, disabled state, selection, progress, feedback, overlays, media, helpers, and stateful interactions. The Web Awesome theme route remains the broad system view, while individual action, form, layout, navigation, feedback, media, and helper routes provide focused specimens; ListItem and ListHeader demonstrate typed domain metadata, protected native button/disclosure semantics, and a native popover trigger relationship without product props; PanelHeader demonstrates its plain top-toolbar identity, extra-large title, optional subtitle row, and bordered brand-filled icon group; the Select route verifies that option icons survive unrelated Kerf rerenders and selected slot content follows value changes; the Animation route exposes preset, easing, duration, playback-rate, and transport controls with live lifecycle status; observer routes provide user-driven intersection, mutation, and resize events with live results; ToolbarControlGroup exposes all eight Hot Sheet 2 variants, loads the Web Awesome registrations needed by its popup specimen, and preserves the upgraded dropdown's managed light-DOM children across Kerf rerenders; the Toolbar route composes a collapsible TokenSearchField inside a group, animates between the empty iconic and complete field states, stays open while populated, and focuses the revealed editor; SegmentedControl covers toolbar, rounded-rectangle, pill, equal-width, and disabled presentations; TokenSearchField covers editable ordered chips, pointer and keyboard removal, caret-preserving controlled deletion, editing, clearing, free text, disabled state, and fixed first-line alignment while content wraps; StateBanner exposes every built-in tone plus a scoped override; TabBar demonstrates overflow, edge autoscroll, and controlled pointer/keyboard reordering.
25
+ - Recipe ownership notes are hidden by default. A page-toolbar toggle reveals them as floating warning-toned notes with a folded corner, keeping instructional copy visually distinct and removable from the specimen flow.
26
+ - Settings exercise light/dark, increased contrast, and reduced motion without changing component code. The theme action starts from the operating system's effective color scheme, always names the appearance it will switch to, and installs an explicit light or dark override when activated; contrast and reduced motion expose their independent preferences through `aria-pressed`.
27
+ - The footer action log is an `aria-live` region, making interactions visible to both people and browser tests.
14
28
  - 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.
15
- - 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, and covers tab selection/reordering/overflow, semantic palettes and scoped overrides, light/dark rendering, toolbar popup behavior, menu alignment, banner urgency, keyboard resizing, live custom-element values, and representative wide/narrow captures.
16
- - 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 broad Web Awesome theme route) is selected. Directly addressed ecosystem URLs pass through the same loader. `npm run demo:build` enforces a 150 kB gzip ceiling for any JavaScript chunk and a 235 kB gzip ceiling for the complete JavaScript payload, so both accidental chunk collapse and aggregate growth fail the build.
29
+ - 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.
30
+ - 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, the broad Web Awesome theme route, or the ToolbarControlGroup popup specimen is selected. Directly addressed routes pass through the same loader.
17
31
 
18
32
  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.
@@ -4,11 +4,18 @@
4
4
  free components and the Kerf/Hot Sheet 2 design system.
5
5
 
6
6
  ```ts
7
+ import type {} from '@kerfjs/ui/webawesome';
7
8
  import '@kerfjs/ui/webawesome.css';
8
9
  import '@awesome.me/webawesome/dist/components/button/button.js';
9
10
  import '@awesome.me/webawesome/dist/components/input/input.js';
10
11
  ```
11
12
 
13
+ The type-only package import augments `kerfjs/jsx-runtime` with the 70
14
+ catalog-supported `wa-*` elements. It has no runtime side effects. The catalog
15
+ gate keeps the declaration tags aligned with Web Awesome's installed custom
16
+ elements manifest, while individual component imports remain the only
17
+ registration boundary.
18
+
12
19
  The CSS entry includes Web Awesome's native, utility, palette, and default-theme
13
20
  styles, then overrides the public semantic contract in
14
21
  `@layer wa-theme-overrides`. It does not import or register component
@@ -53,6 +60,11 @@ chevrons keep the same visual weight. The Accordion and Details components
53
60
  retain their own rotation behavior because scaling uses `transform` while
54
61
  their open state uses the independent `rotate` property.
55
62
 
63
+ That scale applies to each Web Awesome component's intrinsic glyph and is
64
+ independent of Kerf `DisclosureArrow`, whose default box is 18px relative to
65
+ the root font size and whose consumer override is
66
+ `--kui-disclosure-arrow-size`.
67
+
56
68
  Tooltip and Popover use arrowless floating surfaces by default, matching Hot
57
69
  Sheet 2. The theme sets Web Awesome's public `--wa-tooltip-arrow-size` token to
58
70
  `0px` and maps each popover's public `--arrow-size` property from
@@ -129,6 +141,9 @@ static content and labels that constraint next to the rendered output.
129
141
 
130
142
  The catalog lists every free Web Awesome component so support can be inspected;
131
143
  listing does not make each component the preferred Kerf application pattern.
144
+ The [need-first decision matrix](./component-selection.md#web-awesome-overlap-policy)
145
+ is the exhaustive Kerf/Web Awesome overlap index; the rules below explain the
146
+ highest-risk choices.
132
147
 
133
148
  - Consider `wa-popup` for low-level anchored positioning when its flip, shift,
134
149
  or placement behavior avoids custom positioning code. Prefer the higher-level
@@ -0,0 +1,47 @@
1
+ # Workbench
2
+
3
+ `@kerfjs/ui/workbench` is the Xcode-like multi-panel workspace: a collapsible
4
+ left rail, right rail, and bottom drawer around a central work area (any absent).
5
+ It generalizes the instant-width / sliding-content collapse used by the catalog
6
+ sidebar. One of the opt-in app layouts (see
7
+ [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)); best on
8
+ desktop-class devices.
9
+
10
+ ```ts
11
+ import { Workbench } from '@kerfjs/ui/workbench';
12
+ import '@kerfjs/ui/workbench.css';
13
+ ```
14
+
15
+ ## State lives in the app
16
+
17
+ `Workbench` is declarative and the collapse is **pure CSS** — no wire. The app
18
+ owns each panel's `collapsed` flag (usually a signal) and toggles it; the panel
19
+ animates itself.
20
+
21
+ ```tsx
22
+ const navCollapsed = signal(false);
23
+
24
+ <Workbench
25
+ id="studio"
26
+ label="Studio"
27
+ main={<Editor />}
28
+ leftRail={{ content: <Navigator />, label: 'Navigator', collapsed: navCollapsed.value, size: 280 }}
29
+ rightRail={{ content: <Inspector />, label: 'Inspector', collapsed: inspectorCollapsed.value }}
30
+ bottomDrawer={{ content: <Console />, label: 'Console', collapsed: consoleCollapsed.value }}
31
+ />;
32
+ ```
33
+
34
+ Each `WorkbenchPanel` takes `content`, an optional `collapsed`, an optional
35
+ `size` (rail width or drawer height in px, overriding the CSS default —
36
+ `--kui-workbench-rail-width` 280px, `--kui-workbench-drawer-height` 220px), and
37
+ an optional `label`.
38
+
39
+ ## How the collapse animates
40
+
41
+ Collapsing snaps the panel's flex track to zero in a single reflow (so the work
42
+ area relayouts once, not per frame) while the panel's fixed-size content slides
43
+ out via a composited `transform` — a rail slides horizontally, the drawer
44
+ vertically — clipped by the shell's overflow. It honors `prefers-reduced-motion`
45
+ (the slide collapses to instant). On smaller device classes, present the rails'
46
+ contents through a `NavStack` or overlay drawers rather than shrinking the
47
+ three-panel shell.