@kerfjs/ui 5.0.0-beta.50 → 5.0.0-beta.52

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 (212) hide show
  1. package/README.md +29 -5
  2. package/ai/compile-time-contracts-v1.json +5 -2
  3. package/ai/component-catalog-v2.d.ts +19 -1
  4. package/ai/component-catalog-v2.json +842 -127
  5. package/ai/component-catalog-v2.schema.json +33 -1
  6. package/ai/component-catalog.json +603 -126
  7. package/ai/component-catalog.schema.json +21 -1
  8. package/ai/public-api-signatures-v1.md +218 -31
  9. package/ai/skill.md +27 -24
  10. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  11. package/analyzer/index.mjs +105 -35
  12. package/dist/app-tab.d.ts +10 -1
  13. package/dist/app-tab.js +1 -1
  14. package/dist/catalog-resources.d.ts +1 -1
  15. package/dist/catalog.d.ts +28 -5
  16. package/dist/catalog.js +66 -25
  17. package/dist/catalog.js.map +1 -1
  18. package/dist/{chunk-OL4TC3U2.js → chunk-2CE22Q2K.js} +9 -2
  19. package/dist/chunk-2CE22Q2K.js.map +1 -0
  20. package/dist/{chunk-SIU2AC5W.js → chunk-2S7OWAEV.js} +3 -3
  21. package/dist/{chunk-SIU2AC5W.js.map → chunk-2S7OWAEV.js.map} +1 -1
  22. package/dist/{chunk-2YOUUEWR.js → chunk-3S7OUHUN.js} +4 -4
  23. package/dist/{chunk-2YOUUEWR.js.map → chunk-3S7OUHUN.js.map} +1 -1
  24. package/dist/{chunk-2WMSE4MK.js → chunk-4XE2CDYB.js} +2 -2
  25. package/dist/{chunk-2WMSE4MK.js.map → chunk-4XE2CDYB.js.map} +1 -1
  26. package/dist/{chunk-V6RZWDII.js → chunk-7GUQYHKA.js} +19 -2
  27. package/dist/chunk-7GUQYHKA.js.map +1 -0
  28. package/dist/{chunk-ZNZ2JDBW.js → chunk-7IUSV4XG.js} +1 -1
  29. package/dist/chunk-7IUSV4XG.js.map +1 -0
  30. package/dist/{chunk-UMGY5JSP.js → chunk-7XJO4D6A.js} +22 -2
  31. package/dist/chunk-7XJO4D6A.js.map +1 -0
  32. package/dist/{chunk-KKNK3JUT.js → chunk-ABAXNUXU.js} +5 -5
  33. package/dist/chunk-ABAXNUXU.js.map +1 -0
  34. package/dist/{chunk-AQSKJ6U6.js → chunk-DNCOFEUL.js} +21 -2
  35. package/dist/chunk-DNCOFEUL.js.map +1 -0
  36. package/dist/chunk-EKYZCRCX.js +256 -0
  37. package/dist/chunk-EKYZCRCX.js.map +1 -0
  38. package/dist/{chunk-COUPLRZG.js → chunk-IR53BQUJ.js} +16 -12
  39. package/dist/chunk-IR53BQUJ.js.map +1 -0
  40. package/dist/{chunk-3V75EQIP.js → chunk-JGFZ42ZF.js} +3 -3
  41. package/dist/{chunk-3V75EQIP.js.map → chunk-JGFZ42ZF.js.map} +1 -1
  42. package/dist/{chunk-54HM36IQ.js → chunk-KAQUUTZR.js} +4 -2
  43. package/dist/chunk-KAQUUTZR.js.map +1 -0
  44. package/dist/{chunk-ZSYLBVWG.js → chunk-LIOXPQ7N.js} +4 -4
  45. package/dist/chunk-LIOXPQ7N.js.map +1 -0
  46. package/dist/{chunk-S6ZTTTV7.js → chunk-M6GLI6A6.js} +3 -3
  47. package/dist/{chunk-S6ZTTTV7.js.map → chunk-M6GLI6A6.js.map} +1 -1
  48. package/dist/{chunk-OQI7RCRP.js → chunk-MTM43SJO.js} +7 -1
  49. package/dist/chunk-MTM43SJO.js.map +1 -0
  50. package/dist/{chunk-5CE64WGC.js → chunk-OSCL5TKR.js} +3 -3
  51. package/dist/{chunk-5CE64WGC.js.map → chunk-OSCL5TKR.js.map} +1 -1
  52. package/dist/{chunk-DARTMI5Z.js → chunk-SV22UIW4.js} +2 -2
  53. package/dist/{chunk-DARTMI5Z.js.map → chunk-SV22UIW4.js.map} +1 -1
  54. package/dist/{chunk-ELRMBQEC.js → chunk-T7C2VZUB.js} +17 -2
  55. package/dist/chunk-T7C2VZUB.js.map +1 -0
  56. package/dist/{chunk-DJKWFYAA.js → chunk-YZV2PTY4.js} +3 -3
  57. package/dist/{chunk-DJKWFYAA.js.map → chunk-YZV2PTY4.js.map} +1 -1
  58. package/dist/collapsible-panel.d.ts +1 -1
  59. package/dist/collapsible-panel.js.map +1 -1
  60. package/dist/css-values.d.ts +41 -3
  61. package/dist/css-values.js +1 -1
  62. package/dist/grid.d.ts +15 -1
  63. package/dist/grid.js +2 -1
  64. package/dist/index.d.ts +1 -1
  65. package/dist/index.js +19 -19
  66. package/dist/list-action-row.js +1 -1
  67. package/dist/list-header.js +1 -1
  68. package/dist/list-item.js +1 -1
  69. package/dist/list.d.ts +21 -2
  70. package/dist/list.js +2 -1
  71. package/dist/nav-stack.js +1 -1
  72. package/dist/pane.d.ts +17 -2
  73. package/dist/pane.js +1 -1
  74. package/dist/row.d.ts +19 -1
  75. package/dist/row.js +2 -1
  76. package/dist/segmented-control.js +1 -1
  77. package/dist/select.d.ts +13 -3
  78. package/dist/select.js +1 -1
  79. package/dist/skeleton.d.ts +5 -1
  80. package/dist/skeleton.js +1 -1
  81. package/dist/spacer.js +1 -1
  82. package/dist/split-view.js +1 -1
  83. package/dist/state-banner.js +1 -1
  84. package/dist/styles/app-tab.css +9 -0
  85. package/dist/styles/catalog/components/catalog-example-stack.css +4 -1
  86. package/dist/styles/catalog/components/catalog-example.css +145 -0
  87. package/dist/styles/collapsible-panel.css +132 -4
  88. package/dist/styles/document.css +23 -0
  89. package/dist/styles/foundation.css +21 -7
  90. package/dist/styles/grid.css +10 -0
  91. package/dist/styles/list-header.css +45 -0
  92. package/dist/styles/list-inset-control.css +8 -1
  93. package/dist/styles/list-item.css +6 -0
  94. package/dist/styles/list.css +11 -0
  95. package/dist/styles/nav-stack.css +89 -2
  96. package/dist/styles/pane.css +86 -1
  97. package/dist/styles/resizable-region.css +93 -5
  98. package/dist/styles/row.css +10 -0
  99. package/dist/styles/select.css +39 -4
  100. package/dist/styles/skeleton.css +4 -1
  101. package/dist/styles/split-view.css +107 -1
  102. package/dist/styles/tab-scaffold.css +64 -1
  103. package/dist/styles/text.css +6 -0
  104. package/dist/styles/toolbar-control-group.css +90 -11
  105. package/dist/styles/toolbar-text.css +4 -1
  106. package/dist/styles/toolbar.css +130 -38
  107. package/dist/styles/webawesome.css +48 -2
  108. package/dist/styles/workbench.css +136 -4
  109. package/dist/text.d.ts +4 -3
  110. package/dist/text.js +1 -1
  111. package/dist/toolbar-text.js +1 -1
  112. package/dist/toolbar.d.ts +24 -3
  113. package/dist/toolbar.js +1 -1
  114. package/dist/{types-CzgSrFXP.d.ts → types-CEsieBZi.d.ts} +6 -1
  115. package/dist/value-table.js +1 -1
  116. package/dist/wire-resizable-regions.js +1 -1
  117. package/dist/wire-sidebar.d.ts +18 -3
  118. package/dist/wire-sidebar.js +96 -15
  119. package/dist/wire-sidebar.js.map +1 -1
  120. package/docs/accessibility.md +49 -9
  121. package/docs/app-layouts.md +73 -0
  122. package/docs/catalog.md +23 -2
  123. package/docs/collapsible-panel.md +83 -9
  124. package/docs/component-contract.md +36 -12
  125. package/docs/component-integration.md +9 -4
  126. package/docs/component-selection.md +25 -6
  127. package/docs/design/templates/list/stack-dark.svg +1 -1
  128. package/docs/design/templates/list/stack.svg +1 -1
  129. package/docs/design/templates/list-dark.svg +1 -1
  130. package/docs/design/templates/list-header/action-dark.svg +1 -1
  131. package/docs/design/templates/list-header/action.svg +1 -1
  132. package/docs/design/templates/list-header/count-dark.svg +1 -1
  133. package/docs/design/templates/list-header/count.svg +1 -1
  134. package/docs/design/templates/list-header/inline-action-dark.svg +1 -1
  135. package/docs/design/templates/list-header/inline-action.svg +1 -1
  136. package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -1
  137. package/docs/design/templates/list-header/toggle-expanded.svg +1 -1
  138. package/docs/design/templates/list-header-dark.svg +1 -1
  139. package/docs/design/templates/list-header.svg +1 -1
  140. package/docs/design/templates/list.svg +1 -1
  141. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -1
  142. package/docs/design/templates/state-banner/info-badge.svg +1 -1
  143. package/docs/design/templates/state-banner-dark.svg +1 -1
  144. package/docs/design/templates/state-banner.svg +1 -1
  145. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -1
  146. package/docs/design/templates/toolbar/title-and-actions.svg +1 -1
  147. package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -1
  148. package/docs/design/templates/toolbar-control-group/with-text.svg +1 -1
  149. package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
  150. package/docs/design/templates/toolbar-control-group.svg +1 -1
  151. package/docs/design/templates/toolbar-dark.svg +1 -1
  152. package/docs/design/templates/toolbar-text/default-dark.svg +1 -1
  153. package/docs/design/templates/toolbar-text/default.svg +1 -1
  154. package/docs/design/templates/toolbar-text/large-dark.svg +1 -1
  155. package/docs/design/templates/toolbar-text/large.svg +1 -1
  156. package/docs/design/templates/toolbar-text/small-dark.svg +1 -1
  157. package/docs/design/templates/toolbar-text/small.svg +1 -1
  158. package/docs/design/templates/toolbar-text-dark.svg +1 -1
  159. package/docs/design/templates/toolbar-text.svg +1 -1
  160. package/docs/design/templates/toolbar.svg +1 -1
  161. package/docs/document-baseline.md +31 -0
  162. package/docs/layout.md +86 -21
  163. package/docs/nav-stack.md +8 -0
  164. package/docs/recipes.md +81 -44
  165. package/docs/split-view.md +8 -0
  166. package/docs/tab-scaffold.md +4 -2
  167. package/docs/type-contracts.md +21 -17
  168. package/docs/ui-analyzer.md +19 -0
  169. package/docs/ui-evaluator.md +17 -0
  170. package/docs/ux-demo.md +5 -5
  171. package/docs/webawesome-theme.md +20 -2
  172. package/docs/workbench.md +20 -1
  173. package/evaluator/index.mjs +73 -8
  174. package/llms.txt +32 -14
  175. package/package.json +4 -2
  176. package/ux-demo/recipes/app-shell.tsx +209 -178
  177. package/ux-demo/recipes/collapsible-sidebar.tsx +189 -159
  178. package/ux-demo/recipes/compact-toolbar.tsx +48 -35
  179. package/ux-demo/recipes/composer-form.tsx +87 -79
  180. package/ux-demo/recipes/list-detail-dialog.tsx +124 -142
  181. package/ux-demo/recipes/list-workspace-states.tsx +83 -83
  182. package/ux-demo/recipes/loaders.ts +5 -2
  183. package/ux-demo/recipes/loading-inspector.tsx +157 -160
  184. package/ux-demo/recipes/navigation-sidebar.tsx +111 -109
  185. package/ux-demo/recipes/navigation-stack.tsx +50 -49
  186. package/ux-demo/recipes/types.ts +14 -0
  187. package/ux-demo/recipes/workspace-header.tsx +73 -56
  188. package/dist/chunk-54HM36IQ.js.map +0 -1
  189. package/dist/chunk-AQSKJ6U6.js.map +0 -1
  190. package/dist/chunk-COUPLRZG.js.map +0 -1
  191. package/dist/chunk-ELRMBQEC.js.map +0 -1
  192. package/dist/chunk-J3EE6XWD.js +0 -115
  193. package/dist/chunk-J3EE6XWD.js.map +0 -1
  194. package/dist/chunk-KKNK3JUT.js.map +0 -1
  195. package/dist/chunk-OL4TC3U2.js.map +0 -1
  196. package/dist/chunk-OQI7RCRP.js.map +0 -1
  197. package/dist/chunk-UMGY5JSP.js.map +0 -1
  198. package/dist/chunk-V6RZWDII.js.map +0 -1
  199. package/dist/chunk-ZNZ2JDBW.js.map +0 -1
  200. package/dist/chunk-ZSYLBVWG.js.map +0 -1
  201. package/ux-demo/recipes/app-shell.css +0 -87
  202. package/ux-demo/recipes/collapsible-sidebar.css +0 -83
  203. package/ux-demo/recipes/compact-toolbar.css +0 -35
  204. package/ux-demo/recipes/composer-form.css +0 -19
  205. package/ux-demo/recipes/list-detail-dialog.css +0 -42
  206. package/ux-demo/recipes/list-workspace-states.css +0 -30
  207. package/ux-demo/recipes/loading-inspector.css +0 -7
  208. package/ux-demo/recipes/navigation-sidebar.css +0 -12
  209. package/ux-demo/recipes/navigation-stack.css +0 -12
  210. package/ux-demo/recipes/recipe-root.css +0 -67
  211. package/ux-demo/recipes/recipe-root.tsx +0 -87
  212. package/ux-demo/recipes/workspace-header.css +0 -7
package/docs/layout.md CHANGED
@@ -35,6 +35,14 @@ each enabled side uses `--kui-pane-separator-width` (1px) and
35
35
  `--kui-pane-separator-color` (`--kui-color-border`). Logical sides keep pane
36
36
  boundaries correct in both left-to-right and right-to-left layouts.
37
37
 
38
+ A pane is safe-area aware. Its background and separators paint through a
39
+ device's unsafe areas while its header, content, and footer pad for each side
40
+ the pane still reaches; the content's block insets are scroll padding, so its
41
+ first and last items can always be scrolled clear. Layouts clear the sides a
42
+ pane does not reach. In an app-owned arrangement, pass `safeAreaEdges` (the
43
+ same logical sides as `separators`; all four by default, `[]` to opt out). See
44
+ [Choosing an app layout › Safe areas](app-layouts.md#safe-areas).
45
+
38
46
  `.kui-content` is a vertical stack with a 24px gap between major children.
39
47
  Sections may contain adjacent `ListItem` rows without adding another major gap.
40
48
  Ordinary surface-like children use `.kui-content-item` and own their complete
@@ -66,17 +74,18 @@ semantic status.
66
74
 
67
75
  ## Public roles and tokens
68
76
 
69
- | Need | Class | Token / default |
70
- | ---------------------------------------- | ------------------------- | ------------------------------------- |
71
- | Unpadded header/content/footer structure | `Pane`, `.kui-pane` | — |
72
- | Scrolling pane content | `.kui-pane__content` | — |
73
- | Optional logical-edge separators | `Pane.separators` | `--kui-pane-separator-width: 1px` |
74
- | Major vertical rhythm | `.kui-content` | `--kui-layout-content-gap: 24px` |
75
- | Self-contained child geometry | `.kui-content-item` | 8px margin + 1px border + 8px padding |
76
- | Pill child | `.kui-content-item--pill` | `--kui-layout-pill-radius: 22px` |
77
- | Related controls | `.kui-control-cluster` | `--kui-layout-control-gap: 8px` |
78
- | Inline metadata | `.kui-inline-metadata` | `--kui-layout-metadata-gap: 4px` |
79
- | Explicit scroll owner outside a pane | `.kui-scroll-owner` | `overflow: auto` |
77
+ | Need | Class | Token / default |
78
+ | ---------------------------------------- | ------------------------- | --------------------------------------------------------------------------- |
79
+ | Unpadded header/content/footer structure | `Pane`, `.kui-pane` | — |
80
+ | Scrolling pane content | `.kui-pane__content` | — |
81
+ | Optional logical-edge separators | `Pane.separators` | `--kui-pane-separator-width: 1px` |
82
+ | Safe-area sides a pane may pad | `Pane.safeAreaEdges` | `--kui-safe-area-*: env(safe-area-inset-*)`, routed as `--kui-edge-inset-*` |
83
+ | Major vertical rhythm | `.kui-content` | `--kui-layout-content-gap: 24px` |
84
+ | Self-contained child geometry | `.kui-content-item` | 8px margin + 1px border + 8px padding |
85
+ | Pill child | `.kui-content-item--pill` | `--kui-layout-pill-radius: 22px` |
86
+ | Related controls | `.kui-control-cluster` | `--kui-layout-control-gap: 8px` |
87
+ | Inline metadata | `.kui-inline-metadata` | `--kui-layout-metadata-gap: 4px` |
88
+ | Explicit scroll owner outside a pane | `.kui-scroll-owner` | `overflow: auto` |
80
89
 
81
90
  The component layer applies the same contract to `Toolbar`, `ListHeader`,
82
91
  `ListItem`, `Toolbar`, `StateBanner`, `ValueTable`,
@@ -87,6 +96,12 @@ also keeps 8px of root-scaled block padding independently of its semantic inline
87
96
  inset. Most interactive rows and toolbar groups are 44px tall.
88
97
  Toolbar groups reserve a real 1px outer border around a 42px inner area, even
89
98
  when their border and background are transparent.
99
+ Trailing icon actions share one axis: a toolbar control centers its glyph in
100
+ that 44px slot at the 8px inline margin, and a `ListHeader` action centers its
101
+ fitted 36px square in a slot of the same size (a transparent hit layer extends
102
+ its pointer target to fill that 44px slot), so a Pane header's trailing toolbar
103
+ action and the `ListHeader` action beneath it line up (30px from the pane edge
104
+ at the default scale).
90
105
  Panel, dialog, and page headings are plain `Toolbar` compositions. The leading
91
106
  zone holds an optional icon `ToolbarControlGroup` and a direct extra-large
92
107
  `ToolbarText`; actions belong in a trailing group. Omit empty groups. Supporting
@@ -120,18 +135,30 @@ CSS strings.
120
135
 
121
136
  ## Text
122
137
 
123
- Use `Text` from `@kerfjs/ui/text` for ordinary semantic headings and
124
- paragraphs. It renders a native `p` by default; set `variant` to `h1`–`h6` when
125
- the document outline calls for a heading. Ordinary global, `data-*`, and
126
- `aria-*` attributes pass through. Every variant resets its native margin, owns a
127
- 1px transparent border, and supplies the standard 8px item padding; native
128
- typography remains available to the surrounding composition. Choose heading
129
- levels from the document outline, not for visual size. Toolbar identity and
130
- page-heading compositions continue to use `ToolbarText`.
138
+ Use `Text` from `@kerfjs/ui/text` for ordinary semantic headings, paragraphs,
139
+ and inline secondary text. It renders a native `p` by default; set `variant` to
140
+ `h1`–`h6` when the document outline calls for a heading, or `span` for inline
141
+ copy inside a row, label, or table cell. Ordinary global, `data-*`, and `aria-*`
142
+ attributes pass through. Block variants reset their native margin, own a 1px
143
+ transparent border, and supply the standard 8px item padding. The `span`
144
+ variant owns no margin, border, or padding. Choose heading levels from the
145
+ document outline, not for visual size. Toolbar identity and page-heading
146
+ compositions continue to use `ToolbarText`.
147
+
148
+ `Text` is also the answer for a normal-color subsection title. `ToolbarText`
149
+ below `xlarge` (`large`, `default`, `small`) is deliberately quiet: it labels a
150
+ toolbar's identity — a pane name or a status — and must not compete with the
151
+ content and controls around it, so it has no tone option. A subsection title is
152
+ document content, not toolbar identity: use `Text` with the `h2`–`h6` variant
153
+ the outline calls for (inside a `List` with `controlInsets`, its text lines up
154
+ with content items). Use `ListHeader` when the section is a list or menu group
155
+ that needs a count, badge, action, or disclosure, and keep extra-large
156
+ `ToolbarText` for the panel, dialog, or page title.
131
157
 
132
158
  Presentation is independent of the native element: use `tone="quiet"` for
133
159
  supporting copy, `tone="danger"` for error or validation copy, `size="compact"`
134
- for compact metadata, and `font="monospace"` for code or identifiers. These
160
+ for compact metadata, and `font="monospace"` for code or identifiers (the
161
+ `--kui-font-mono` stack, which Kerf owns even with Web Awesome loaded). These
135
162
  finite props compose with each other and with every semantic `variant`; omit
136
163
  them to inherit the surrounding color, size, and font.
137
164
 
@@ -139,8 +166,10 @@ them to inherit the surrounding color, size, and font.
139
166
  import { Text } from "@kerfjs/ui/text";
140
167
 
141
168
  <Text variant="h2" id="details-title">Details</Text>;
169
+ <Text variant="h3">Notifications</Text>; // normal-color subsection title
142
170
  <Text aria-describedby="details-title">Supporting copy</Text>;
143
171
  <Text tone="quiet" size="compact">Updated yesterday</Text>;
172
+ <strong>Inbox<Text variant="span" tone="quiet" size="compact"> · 3 msg</Text></strong>;
144
173
  <Text tone="danger" font="monospace">ERR_INVALID_ID</Text>;
145
174
  ```
146
175
 
@@ -221,6 +250,35 @@ for the CSS initial value, pass `true` for `1 1 auto`, select the finite
221
250
  shorthand. A `CssLength` is not a flex shorthand, and arbitrary strings are
222
251
  rejected by the type contract.
223
252
 
253
+ ### Filling a fixed-height parent
254
+
255
+ `flex` sizes a Row, List, or Grid inside a flex layout. When one of them is the
256
+ layout **root** of a parent with a definite height (an app mount container
257
+ with the `.kui-app-root` height chain, a dialog body, or a fixed-height catalog
258
+ frame), pass `fill` instead: the root takes the parent's full height
259
+ (`height: 100%`), so its `flex` children can grow into it. Use `fill` only on
260
+ that sole layout root; a sibling inside a flex layout keeps using `flex`.
261
+
262
+ Put route, test, or recipe metadata on the same root with `rootAttributes`,
263
+ which accepts safe `data-*` pairs and keeps each component's structural
264
+ attributes (`data-component`, `data-flex`, `data-fill`, alignment, inset, and
265
+ column markers) protected:
266
+
267
+ ```tsx
268
+ <Row gap="none" fill rootAttributes={{ "data-recipe": "sidebar" }}>
269
+ <CollapsiblePanel id="rail" side="left" collapsed={railCollapsed.value}>
270
+ …
271
+ </CollapsiblePanel>
272
+ <List flex>…</List>
273
+ </Row>
274
+ ```
275
+
276
+ Do not wrap a layout in a frame `Pane` just to get a definite height or a root
277
+ attribute: the pane's content slot becomes a scroll owner that never scrolls,
278
+ and its safe-area padding keeps an edge-docked rail or drawer from painting
279
+ through the unsafe area. A `Pane` is for a real column with a header, one
280
+ scrolling content region, or a footer.
281
+
224
282
  Both components also accept `textInsets` and `controlInsets` using the shared
225
283
  physical `Sides` union. Values follow canonical top/right/bottom/left order
226
284
  (`t`, `rb`, `tbl`, `trbl`, and so on). A selected control side adds 8px of
@@ -229,11 +287,18 @@ padding. A selected text side adds the complete content-item alignment inset —
229
287
  container padding. When both props select the same side, the text inset wins.
230
288
  Each nested `Row` or `List` resolves its own four inset sides; unselected sides
231
289
  reset to zero instead of inheriting a same-type parent's inset selection.
290
+ The same holds for `gap` and `flex` on a nested `List`, `Row`, or `Grid`: an
291
+ omitted prop falls back to that component's own default (no gap for `List`,
292
+ `xs` for `Row` and `Grid`; the CSS initial `flex`) rather than the enclosing
293
+ instance's value.
232
294
 
233
295
  `ListInsetText` and `ListInsetControl` use the same `sides` vocabulary and
234
296
  default to `trbl`. The text wrapper applies its complete 8/1/8 geometry only on
235
297
  selected sides; the control wrapper applies its 8px outer margin only on
236
- selected sides. `ListInsetText.horizontalOnly` remains a deprecated alias for
298
+ selected sides. Every direct child of `ListInsetControl` grows to fill the row
299
+ (a `Select`, a `wa-input`, or a `wa-button` spans the full inset width), and
300
+ several children share the row with an 8px gap; stack controls vertically with
301
+ a `List` using `controlInsets` instead. `ListInsetText.horizontalOnly` remains a deprecated alias for
237
302
  `sides="rl"`; an explicit `sides` value takes precedence.
238
303
 
239
304
  `--kui-space-s` (12px) and `--kui-space-xl` (32px) exist but are **off the
package/docs/nav-stack.md CHANGED
@@ -65,6 +65,14 @@ When a remembered control was removed during the controlled rerender, the same
65
65
  fallback order applies. Disposing the helper stops future transitions and
66
66
  cleans up any temporary fallback `tabindex` it added.
67
67
 
68
+ ## Safe areas
69
+
70
+ The stack paints through a device's unsafe areas. Its chrome pads for the top
71
+ and side insets, its bottom toolbar for the bottom and side insets, and each
72
+ view pads the remaining edges inside its scroller, so content scrolls under the
73
+ unsafe area but can always be scrolled clear. A view whose only child is a
74
+ `Pane` or layout lets that child own the insets. See [Choosing an app layout › Safe areas](app-layouts.md#safe-areas).
75
+
68
76
  ## Transitions
69
77
 
70
78
  `wireNavStack(root, { onBack, duration? })` observes the rendered stack and
package/docs/recipes.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Production composition recipes
2
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.
3
+ These ten reference compositions sit between individual primitives and product
4
+ code. Open each stable UX-catalog route to run it, then copy the linked TSX. The
5
+ examples import public package subpaths, compose components through their
6
+ props and tokens, and keep state in a per-instance application adapter. They
7
+ ship no stylesheet of their own and are not new monolithic components.
8
8
 
9
9
  Copy the recipe source together with the catalog-independent
10
10
  [`mount-recipe.ts`](../ux-demo/recipes/mount-recipe.ts) adapter. It mounts the
@@ -28,11 +28,22 @@ specific dispatch; either way, wire once at a stable root and retain disposal.
28
28
 
29
29
  ## Desktop application shell
30
30
 
31
- [Open the recipe](../ux-demo/?component=recipe-app-shell) · [TSX source](../ux-demo/recipes/app-shell.tsx) · [shared composition CSS](../ux-demo/recipes/recipe-components.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.
31
+ [Open the recipe](../ux-demo/?component=recipe-app-shell) · [TSX source](../ux-demo/recipes/app-shell.tsx)
32
+
33
+ A filling `List` (`fill`, with the recipe's `data-*` markers through
34
+ `rootAttributes`) stacks the app-bar `Toolbar` above a `Row` that places
35
+ controlled `ResizableRegion` navigation and inspector panes around a content
36
+ `Pane` that a one-column `Grid` grows to fill. Each region's lone `Pane` fills
37
+ the region's full height, so it reaches the bottom edge the separator does.
38
+ Only those three real panes own a
39
+ `.kui-pane__content` scroll owner; no frame `Pane` wraps the layout in a scroll
40
+ owner that never scrolls. Each pane lists the screen edges it reaches in
41
+ `safeAreaEdges` (the bottom plus its outer side, or both sides when it is the
42
+ only pane shown), and the app-bar `Toolbar` claims the top edge and both sides
43
+ with its own `safeAreaEdges`, so it clears the status area. Below desktop sizes the app reads `deviceClass()` and shows one
44
+ pane at a time, switched by a pressed-state `ToolbarControlGroup`. The recipe
45
+ owns the shell topology; the app owns routing, responsive pane visibility,
46
+ sizes, persistence, and data.
36
47
  Keep each visible pane's collapse action in its own toolbar. Once hidden, put
37
48
  its restore action in the adjacent main toolbar on the same logical edge:
38
49
  leading for an inline-start sidebar and trailing for an inline-end inspector.
@@ -64,17 +75,19 @@ controls relocate without changing focus order.
64
75
 
65
76
  [Open the recipe](../ux-demo/?component=recipe-list-detail-dialog) · [TSX source](../ux-demo/recipes/list-detail-dialog.tsx)
66
77
 
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
- `Toolbar`, `ListHeader`, `ListItem`, and `ValueTable` own their included
70
- anatomy. The application owns open state, selection, dismissal policy, and
71
- record actions. A `ToolbarControlGroup` places the app's trailing controls in
72
- the 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
+ The production Web Awesome dialog owns modal focus, Escape, its labeled header,
79
+ and its close control; the thin recipe adapter restores the invoking control
80
+ after the hide event. `DialogSurface` chooses the large modal with no body
81
+ inset, because `SplitView` and its rows own list geometry, and a comfortable
82
+ footer inset for the record actions. On roomy devices `SplitView` shows the
83
+ project list beside its detail; each pane opens with a `ListHeader` on one
84
+ shared line, and the dialog title, both headers, and the list text share the
85
+ 17px text edge while the `ValueTable` keeps the 8px outer margin. On compact
86
+ devices the dialog becomes a full-screen sheet and `SplitView` becomes a
87
+ `NavStack` drill-down whose back control the application pops. The
88
+ application owns open state, selection, the pushed detail, dismissal policy,
89
+ and record actions. Do not rebuild the dialog or reach into private shadow
90
+ parts.
78
91
 
79
92
  ## Composer form
80
93
 
@@ -82,12 +95,13 @@ Do not rebuild the dialog or reach into private shadow parts.
82
95
 
83
96
  A direct `ToolbarText` supplies the task title, with app-owned supporting copy
84
97
  below; their ids are referenced by the form. `ListInsetText` gives that bare
85
- supporting copy the same content-item text inset as the fields instead of
86
- duplicating its geometry in recipe CSS. Production fields own labels, help, and
87
- native focus. The field
88
- and footer control edges sit directly on the shared 8px inline gutter rather
89
- than acquiring a second content-item padding inset; major children remain 24px
90
- apart and related controls use 8px gaps. The app owns
98
+ supporting copy the same content-item text inset as the fields. The title and
99
+ supporting line share a nested heading `List` with `gap="none"`, so the heading
100
+ unit stays together instead of taking the form's 24px section gap. Production
101
+ fields own labels, help, and native focus. One `List` owns the 24px major
102
+ rhythm; the field `List` and the action `Row` use `controlInsets` to sit on the
103
+ shared 8px inline gutter with 8px gaps between related controls, rather than
104
+ acquiring a second content-item padding inset. The app owns
91
105
  validation, drafts, permissions, and transport. Persistent error or success
92
106
  feedback is the only nested semantic surface and uses `StateBanner`, not a toast.
93
107
  Because upgraded Web Awesome fields retain live value properties, controlled
@@ -111,8 +125,10 @@ Use `ToolbarControlGroup` for related commands, `SegmentedControl` for a few
111
125
  visible exclusive choices, `Select` for a longer value list, and an ordinary
112
126
  button for an independent command. Even that independent button sits in a
113
127
  single-control `ToolbarControlGroup`, which keeps its height, pill shape, focus,
114
- and hover treatment aligned with the adjacent toolbar controls. The app owns
115
- values, actions, persistence, and responsive priority.
128
+ and hover treatment aligned with the adjacent toolbar controls. At compact
129
+ widths the toolbar stacks its zones and wraps whole trailing groups onto another
130
+ row, so the last action is never clipped. The app owns values, actions,
131
+ persistence, and responsive priority.
116
132
 
117
133
  ## Navigation stack
118
134
 
@@ -121,8 +137,8 @@ values, actions, persistence, and responsive priority.
121
137
  Drill from a library list into a detail and back with `NavStack`
122
138
  (`@kerfjs/ui/nav-stack`): the app owns the stack as a signal of views and
123
139
  pushes/pops it, `NavStack` renders it, and `wireNavStack` slides the content and
124
- settles the chrome (reduced motion collapses the slide to instant). A live
125
- `deviceClass()` badge shows the current size/orientation. See the layout guide
140
+ settles the chrome (reduced motion collapses the slide to instant). See the
141
+ layout guide
126
142
  [`app-layouts.md`](app-layouts.md) for choosing among `NavStack`, `SplitView`,
127
143
  `Workbench`, and `TabScaffold`.
128
144
 
@@ -135,7 +151,9 @@ value-bearing component (`ToolbarText`, `ValueTable`/`ValueTableRow`, `Select`,
135
151
  `SegmentedControl`, `ListItem`, `StateBanner`) takes its `placeholder` from one
136
152
  loading flag, so the same real chrome renders a faithful loading state and then
137
153
  the populated record — no separate skeleton markup. The composition is the point;
138
- `Skeleton` is the primitive it builds on. The app owns the loading lifecycle and
154
+ `Skeleton` is the primitive it builds on. Its heading `Toolbar` uses
155
+ `responsive="wrap"`, so on a narrow inspector the toggle moves below a whole
156
+ record title instead of truncating it. The app owns the loading lifecycle and
139
157
  which values are still unknown.
140
158
 
141
159
  ## Collapsible sidebar
@@ -145,14 +163,26 @@ which values are still unknown.
145
163
  A mini app frame whose left navigation rail and bottom activity drawer are
146
164
  standalone `CollapsiblePanel`s (`@kerfjs/ui/collapsible-panel`) driven by
147
165
  `wireSidebar` (`@kerfjs/ui/wire-sidebar`). `CollapsiblePanelToggle` supplies the
148
- standard per-side glyph: a collapse toggle lives inside each panel and an expand
149
- toggle lives in the always-visible main header, so a collapsed panel is still
150
- reachable. `wireSidebar` owns the toggle delegation, moves focus into a panel on
151
- open and restores it to the trigger on close, and — when a `deviceClass()` reports
152
- `compact` — switches the open panel to a dismissable **overlay** (backdrop, Escape
153
- and backdrop-click collapse, and a trapped Tab ring, the ARIA dialog pattern). It
154
- also persists each panel's collapsed state through a supplied storage hook. The app
155
- owns each `collapsed` signal, the panel sizes, and the content; the wire owns the
166
+ standard per-side glyph: a collapse toggle lives inside each panel, and the
167
+ panel's expand toggle shows only while it is collapsed, so exactly one control
168
+ owns each action and a collapsed panel is still reachable. The rail's expand
169
+ toggle sits in the always-visible main header; the drawer's floats in a
170
+ `FloatingToolbar` at the bottom edge the drawer opens from, next to where the
171
+ action happens. `wireSidebar` owns the toggle delegation, moves focus into a
172
+ panel on open, restores it on close (to the trigger, or to the panel's expand toggle when
173
+ the trigger sits inside the collapsed panel), and — when a `deviceClass()` reports
174
+ `compact` — presents the panels as a dismissable **overlay** (backdrop, Escape
175
+ and backdrop-click collapse, and a trapped Tab ring, the ARIA dialog pattern).
176
+ The overlay starts closed and opens only from a toggle; the rail's signal is
177
+ seeded from the device class with `inlineCollapsed: false`, so the first compact
178
+ render has no overlay and a wide crossing restores the open inline rail. It
179
+ also persists each panel's inline collapsed state through a supplied storage hook. A
180
+ filling `Row` (`fill` plus `rootAttributes`) is the root: it takes the frame's
181
+ definite height and places the rail beside a `List` column, and a one-column
182
+ `Grid` grows the main pane above the drawer. Because the panels sit at the real
183
+ frame edges, each grows through its edge's safe area and hands that edge back to
184
+ its siblings when it collapses; the drawer's activity log is a `ValueTable`. The app owns each
185
+ `collapsed` signal, the panel sizes, and the content; the wire owns the
156
186
  ephemeral interaction. For a full three-pane shell use `Workbench` instead — see
157
187
  [`app-layouts.md`](app-layouts.md). This recipe is covered end-to-end across
158
188
  Chromium, Firefox, and WebKit by `tests/browser/collapsible-sidebar-recipe.spec.ts`.
@@ -164,9 +194,16 @@ Chromium, Firefox, and WebKit by `tests/browser/collapsible-sidebar-recipe.spec.
164
194
  - For direct `wa-*` JSX, import types from `@kerfjs/ui/webawesome`. Import only
165
195
  individual Web Awesome registration modules and theme them with
166
196
  `@kerfjs/ui/webawesome.css`.
167
- - Prefer public props and variables at the composition boundary. A recipe's own
168
- component may style the structure it renders, but its stylesheet must not
169
- select nested Kerf or Web Awesome components. Add missing configuration to the
170
- owning component instead of copying markup or reaching through its boundary.
197
+ - Compose, don't style. A recipe ships no stylesheet, sets no inline style, and
198
+ names no class outside the published layout vocabulary (`.kui-content`,
199
+ `.kui-content-item`, …) and themed Web Awesome classes; it configures
200
+ components through props, `rootAttributes`, and tokens. When a recipe needs a
201
+ capability no component offers, record it as a component gap rather than
202
+ papering over it with CSS. `npm run check:recipes` enforces these rules on
203
+ every file in `ux-demo/recipes/`.
204
+ - Each recipe also exports a catalog-only `presentation`: the `CatalogExample`
205
+ viewport that frames it in the UX catalog and its ownership note. An
206
+ application that copies the recipe ignores that export and mounts the
207
+ controller into its own container.
171
208
  - Start from the copyable mount adapter, or reproduce its complete boundary:
172
209
  wire stable `data-action` hooks once and retain every disposer.
@@ -13,6 +13,14 @@ import "@kerfjs/ui/split-view.css";
13
13
  // wireResizableRegions / wireNavStack for the interactive behavior.
14
14
  ```
15
15
 
16
+ ## Safe areas
17
+
18
+ The list pane grows by the inline-start unsafe inset and pads for the edges it
19
+ touches; the detail pads for its own outer edges. Neither is inset on the
20
+ shared interior edge. A pane whose only child is a `Pane` lets that child own
21
+ the insets, and the compact form hands them to its `NavStack`. See
22
+ [Choosing an app layout › Safe areas](app-layouts.md#safe-areas).
23
+
16
24
  ## Responsive by device class
17
25
 
18
26
  `SplitView` is declarative; the app derives `compact` from the device class and
@@ -45,8 +45,10 @@ const dispose = wireTabScaffold(root, {
45
45
  ```
46
46
 
47
47
  Each `TabScaffoldTab` has an `id`, `label`, optional `icon`, and `content`. The
48
- bottom bar respects the home-indicator safe area (`env(safe-area-inset-bottom)`)
49
- and keeps 44px targets. Its single-line labels preserve their full line box and
48
+ bottom bar paints through the home-indicator safe area and pads for it and for
49
+ the side insets, while each scene pads for the top and side insets (a scene
50
+ whose only child is a `NavStack` or `Pane` lets that child own them; see
51
+ [Choosing an app layout › Safe areas](app-layouts.md#safe-areas)). The bar keeps 44px targets. Its single-line labels preserve their full line box and
50
52
  truncate horizontally with an ellipsis when a destination name exceeds its
51
53
  share of the bar. On larger device classes, promote the tab set to a
52
54
  `Workbench` rail or a persistent sidebar instead of a bottom bar.
@@ -11,22 +11,22 @@ public-signature artifact.
11
11
 
12
12
  ## Strengthened contracts
13
13
 
14
- | ID | Compile-time guarantee |
15
- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
- | `KUI-T001` | `ListHeader` is a passive heading, a fully named/icon-bearing trailing action, or a controlled disclosure with `action` + `expanded`. Props ignored by the selected mode are rejected. |
17
- | `KUI-T002` | `Select` has a visible `label` or an `ariaLabel`. |
18
- | `KUI-T003` | A literal `Select.value` belongs to its literal `choices`. |
19
- | `KUI-T004` | A literal `SegmentedControl.value` belongs to its literal `choices`. |
20
- | `KUI-T005` | `expanded`, `expandAction`, and `expandLabel` exist only on `TokenSearchField({ collapsible: true })`. |
21
- | `KUI-T006` | `TokenSearchEditorAttributes` accepts application `data-*` metadata but rejects component-owned identity, morph, count, and placeholder attributes. |
22
- | `KUI-T007` | `ToolbarText.maxLines` requires `wrap: true`; it is never silently ignored in typed code. |
23
- | `KUI-T008` | A literal `TabScaffold.active` id belongs to its literal `tabs`. |
24
- | `KUI-T009` | Adjacent-token keyboard removal requires `onRemoveToken`; disabling removal rejects the now-meaningless callback. |
25
- | `KUI-T010` | Finite public variants have named exported union types, including divider sides, tab activation, banner urgency, and all AppTab, TabBar, ToolbarControlGroup, and Select presentation axes; the convenience root barrel re-exports them. |
26
- | `KUI-T011` | Semantic component zones use the recursive `KerfUiContent` type: `SafeHtml`, runtime-empty booleans/nullish values, and readonly nested arrays are valid; arbitrary strings, numbers, and signals are rejected. Explicit text positions such as `ListInsetText` retain their text exception. |
27
- | `KUI-T012` | `List.gap` accepts finite `UiSpaceName` shorthands or a complete branded `CssLength`; raw strings and non-standalone `CssLengthExpression` arithmetic are rejected. `calc(plus(...))` promotes a typed expression to a complete value. |
28
- | `KUI-T013` | CSS-valued props preserve property grammar: `List.flex` uses `CssFlex`, `Skeleton` dimensions use `CssSize`/`CssLength`, and choice icons use `CssColor`; row declaration strings are absent. |
29
- | `KUI-T014` | Stable single-root visual components accept the explicit native `slot` attribute. Multi-root `ResizableRegion` and `CollapsiblePanel` remain excluded because no single returned element can own the assignment, and other arbitrary native attributes remain rejected. |
14
+ | ID | Compile-time guarantee |
15
+ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
+ | `KUI-T001` | `ListHeader` is a passive heading, a fully named/icon-bearing trailing action, or a controlled disclosure with `action` + `expanded`. Props ignored by the selected mode are rejected. |
17
+ | `KUI-T002` | `Select` has a visible `label` or an `ariaLabel`. |
18
+ | `KUI-T003` | A literal `Select.value` belongs to its literal `choices`. |
19
+ | `KUI-T004` | A literal `SegmentedControl.value` belongs to its literal `choices`. |
20
+ | `KUI-T005` | `expanded`, `expandAction`, and `expandLabel` exist only on `TokenSearchField({ collapsible: true })`. |
21
+ | `KUI-T006` | `TokenSearchEditorAttributes` accepts application `data-*` metadata but rejects component-owned identity, morph, count, and placeholder attributes. |
22
+ | `KUI-T007` | `ToolbarText.maxLines` requires `wrap: true`; it is never silently ignored in typed code. |
23
+ | `KUI-T008` | A literal `TabScaffold.active` id belongs to its literal `tabs`. |
24
+ | `KUI-T009` | Adjacent-token keyboard removal requires `onRemoveToken`; disabling removal rejects the now-meaningless callback. |
25
+ | `KUI-T010` | Finite public variants have named exported union types, including divider sides, tab activation, banner urgency, and all AppTab, TabBar, ToolbarControlGroup, and Select presentation axes; the convenience root barrel re-exports them. |
26
+ | `KUI-T011` | Semantic component zones use the recursive `KerfUiContent` type: `SafeHtml`, runtime-empty booleans/nullish values, and readonly nested arrays are valid; arbitrary strings, numbers, and signals are rejected. Explicit text positions such as `ListInsetText` retain their text exception. |
27
+ | `KUI-T012` | `List.gap` accepts finite `UiSpaceName` shorthands or a complete branded `CssLength`; raw strings and non-standalone `CssLengthExpression` arithmetic are rejected. `calc(plus(...))` promotes a typed expression to a complete value. |
28
+ | `KUI-T013` | CSS-valued props preserve property grammar: `List.flex` uses `CssFlex`, `Skeleton` dimensions use `CssSize`/`CssLength`, and choice icons use `CssForegroundColor` (foreground tokens via `uiColor()` or `foregroundColorVar()`; fill aliases such as `uiColor('success')` are rejected); row declaration strings are absent. |
29
+ | `KUI-T014` | Stable single-root visual components accept the explicit native `slot` attribute. Multi-root `ResizableRegion` and `CollapsiblePanel` remain excluded because no single returned element can own the assignment, and other arbitrary native attributes remain rejected. |
30
30
 
31
31
  `KerfUiContent` lets conditionals and mapped component collections be direct
32
32
  siblings without an otherwise-unnecessary `Fragment`:
@@ -120,7 +120,11 @@ move errors into casts without making the integration safer.
120
120
  `gap="0.25rem"` with `gap={rem(0.25)}` and wrap sums with `calc(plus(...))`.
121
121
  - Replace raw `List.flex` shorthands with `flex(grow, shrink, basis)` or a finite
122
122
  keyword; replace raw Skeleton dimensions with `px`/`rem`/`em`/`pct` builders;
123
- and replace raw choice colors with `uiColor()` or `colorVar()`.
123
+ and replace raw choice colors with a foreground token such as
124
+ `uiColor('success-on-quiet')` or an app-owned `foregroundColorVar()`.
125
+ `SelectChoice.color` rejects fill aliases (`uiColor('success')`) and
126
+ unqualified `colorVar()` values because a fill paints a nearly invisible
127
+ icon.
124
128
  - Remove `style` from `ListItem` and `ListActionRow`; use `className`, public
125
129
  tokens, and cataloged props. These breaking cleanups land before 5.0 stable.
126
130
 
@@ -17,6 +17,25 @@ composition catalog by `package:id`, parses literal JavaScript/TypeScript
17
17
  component calls, TSX/JSX, and CSS, and reports portable repository-relative
18
18
  locations.
19
19
 
20
+ An imported component name is resolved to a catalog entry by `package:id`, not
21
+ by display name: several entries share one (the Kerf `Select` and the Web
22
+ Awesome `wa-select` are both "Select"), so the analyzer joins each composition
23
+ entry to its selection-catalog public exports and import subpaths and picks
24
+ the entry that the import actually names. That keeps the Kerf component's
25
+ CSS-value contracts (`KUI-L013`–`KUI-L017`) in force for `Select` and
26
+ `Skeleton`.
27
+
28
+ Only component exports resolve to an entry. A selection-catalog
29
+ `publicExports` list also carries the helpers a component's props consume
30
+ (`Select` ships `uiColor`, `List` ships `px`, `rem`, and `flex`), and a helper
31
+ never inherits its entry's contracts, so `uiColor({ choices: [...] })` is not
32
+ checked as a `Select` call. A component export is one whose name starts with
33
+ an uppercase letter, the same rule JSX uses to tell a component tag from an
34
+ intrinsic element; `eslint-plugin-kerfjs` applies the identical rule, and the
35
+ package's catalog check fails if a cataloged render function (a value
36
+ returning `SafeHtml`) is lowercase or any other runtime value is uppercase.
37
+ Third-party catalogs should follow the same naming convention.
38
+
20
39
  Analysis is scoped per source file. Each TSX/JSX file resolves package,
21
40
  workspace, and parent-to-child directory profiles from its own location, then
22
41
  follows its relative CSS imports (including nested CSS `@import`s). Stylesheet
@@ -61,6 +61,23 @@ shared container and `data-kui-align-edge` on two or more elements whose logical
61
61
  start edges must align. Use `data-kui-scroll-scope` only for an application-owned
62
62
  scope that is not a `.kui-pane`.
63
63
 
64
+ Target size (`KUI-B050`) measures the area the pointer can actually reach, not
65
+ just the border box. When a control's box is under 44 × 44, the evaluator
66
+ hit-tests outward from its center with `elementFromPoint`, so a transparent hit
67
+ layer (a positioned `::before`/`::after` that receives pointer events, as on the
68
+ `ListHeader` action) counts toward the target, while a pseudo-element with
69
+ `pointer-events: none`, a clipping ancestor, or an overlapping neighbor does
70
+ not. Where the probe reaches the viewport edge it mirrors the opposite side's
71
+ reach; a control whose center is off-screen or covered keeps its border box.
72
+ The diagnostic evidence reports the measured `width`/`height` and the border
73
+ `box`.
74
+
75
+ Compact density is a documented exception. Inside `[data-density="compact"]` —
76
+ the compact `List`/`ListHeader` family, whose rows are 36px tall — a target
77
+ still needs the full 44px width but only the 36px row height, because a taller
78
+ hit layer would reach into the neighboring row. The evidence's `required` field
79
+ names the floor that applied (`44×44`, or `44×36` in compact density).
80
+
64
81
  Accessible names follow ARIA/native naming inputs, including labels, referenced
65
82
  content with `aria-hidden` descendants removed, and native input values.
66
83
  Geometry checks use each catalog entry's explicit `boundaries.rootClass`; the
package/docs/ux-demo.md CHANGED
@@ -18,20 +18,20 @@ deterministic state, and semantic layout owners from the [recipe guide](./recipe
18
18
 
19
19
  - It imports public component subpaths, exercising their browser-selected reachable CSS rather than a catalog-only style path.
20
20
  - The `ListActionRow` route demonstrates sibling primary/trailing native controls, independent disabled states, controlled selection, first-line leading-icon alignment for wrapped labels, and an application-owned native popover relationship without nesting controls. The `ListItem` route demonstrates the same multiline leading-icon anchor alongside its single-control navigation contract.
21
- - The `AppTab` and `TabBar` routes are focused component specimens: AppTab shows its selected, compact, segmented, icon-only, truncation, placeholder, metadata, and close-icon axes inside the required tablist context; TabBar shows its rail, segmented, inspector, allocation, placement, and accessory-zone axes, including an adjacent action plus a separately pinned end action whose tabs overflow at narrow widths, without application readouts. The separate `Application tabs` composition route demonstrates controlled selection, matching panel content, overflow, adding, closing, keyboard/pointer reordering, and application-owned ordering. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
21
+ - The `AppTab` and `TabBar` routes are focused component specimens: AppTab shows its selected, compact, segmented, icon-only, truncation, placeholder, pending, metadata, and close-icon axes inside the required tablist context; TabBar shows its rail, segmented, inspector, allocation, placement, and accessory-zone axes, including an adjacent action plus a separately pinned end action whose tabs overflow at narrow widths, without application readouts. The separate `Application tabs` composition route demonstrates controlled selection, matching panel content, overflow, adding, closing, keyboard/pointer reordering, and application-owned ordering. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
22
22
  - Every first-party visual component and every free Web Awesome 3.12 component has its own stable `?component=<id>` URL and focused demo. Composite header, menu, and feedback scenarios remain addressable alongside their subcomponents; the root opens the first Kerf component. The former aggregate Web Awesome theme specimen is intentionally omitted because its component families are covered by those focused routes.
23
23
  - `ai/component-catalog.json` is the canonical catalog metadata. A deterministic checked-in projection supplies the demo's unique id, category, kind (`component`, `composition`, or `recipe`), source (`kerf` or `webawesome`), name, description, recommendation, direct `uses` dependencies, existing documentation path, and demo-source path; first-party, recipe, and Web Awesome renderer maps are exhaustively typed against its generated id unions. Functional groups follow their authored importance order; components precede compositions inside each group, equal-importance entries use alphabetical order, and the Recipes group comes last. Composition names stay concise and project a visible `Composition` sidebar tag. Non-supported Web Awesome recommendations project to a visible `Discouraged` tag, with Popup as the deliberate conditional exception. Demo paths resolve to the matching `ui/ux-demo/demos/<id>.tsx` or `ui/ux-demo/recipes/*.tsx` file, or to `ui/ux-demo/webawesome-demos.tsx` for Web Awesome entries. `ui/ux-demo/main.tsx` owns only the catalog shell, shared state, routing, and interaction wiring. First-party component entries additionally derive `ui/src/<public-subpath>.tsx` from their canonical `delivery.browserImport` or opt-in `delivery.moduleImport`.
24
24
  - Every detail footer groups compact `Demo source` and `Guidance` actions; first-party component details also expose `Component source`. Web Awesome details say `Integration guidance` to distinguish the local integration contract from the upstream component implementation. Each button retains its repository-relative path in the DOM for verification and uses a deploy-safe absolute GitHub blob URL with a contextual new-tab name; the demo does not depend on a local source server or add a package runtime export. At phone widths, the resource group and related-component selector stack as full-width rows, with any resource overflow contained locally so labels cannot overlap the selector or widen the document.
25
25
  - The master/detail shell groups first-party routes by category with production `ListHeader` and `ListItem` components. Catalog entries can supply short reusable trailing tags; the Web Awesome projection uses them to label exactly the 15 discouraged choices. The 70 ecosystem components live in a separate `Web Awesome` disclosure with seven subgroups, so they remain discoverable without being presented as first-class `@kerfjs/ui` exports. Direct Web Awesome routes open the disclosure and reveal the selected row at wide sizes; narrow layouts keep the expanded list in document flow rather than introducing a competing nested scroller.
26
26
  - The shell itself uses `@kerfjs/ui/layout.css`: sidebars, details, previews, dialogs, and resize specimens share unpadded panes plus self-contained content items. Its sidebar identity uses the Kerf logo beside a vertically centered title, with supporting copy in its own aligned row; the current-view heading is also a toolbar. Collapsing the catalog removes the pane completely and moves its restore action to the main toolbar's leading edge instead of leaving an icon-only rail. The selected view description remains inset below the page toolbar. The checkerboard belongs to the preview scroll owner rather than a viewport-height stage or nested card, and one explicitly repeated SVG tile keeps the pattern continuous through below-fold content. A lightly separated footer keeps live status above compact source, guidance, and relationship groups. The focused `ResizableRegion` specimen stretches its surface through the available stage height and reports committed width in that shared status footer; narrow layouts retain local horizontal access when the pane is resized wider than the stage, and the split remains readable at 200% root scaling. Geometry coverage checks wide, intermediate, narrow, and 200% zoom layouts.
27
27
  - The composer-form recipe demonstrates one coherent visible form surface with toolbar title/supporting-copy hierarchy, fields and actions on the shared 8px control gutter, and 24px major rhythm. Its conditional error or success `StateBanner` is the only nested semantic surface. Reset synchronizes the upgraded Web Awesome fields' live value properties with their empty controlled attributes and announces `Draft reset`; browser coverage preserves behavior and verifies wide, narrow, dark, validation, success, reset, 200% zoom, and forced-colors states.
28
- - The list-detail dialog recipe places its header directly on the dialog edge while preserving the header's internal inset, gives the selected project title the complete outer-plus-inner content gutter, lets the value table fill the available detail column between the shared outer margins, and keeps record actions on one outer gutter without an extra content-item inset. Browser geometry coverage protects those relationships at wide and narrow widths.
28
+ - The list-detail dialog recipe composes `DialogSurface`, the dialog's own labeled header and close control, `SplitView`, and footer actions. Its `SplitView` sits flush in the dialog body, both panes open with a `ListHeader` on one shared line, the dialog title and header text share the 17px text edge, the value table keeps the 8px outer margin, and the trailing actions keep the 16px footer gutter. Compact devices get a full-screen sheet with a list-to-detail drill-down. Browser geometry coverage protects those relationships at wide and narrow widths.
29
29
  - Catalog-only brand marks, preview dimensions, checkerboard tiles, and the compound display-settings control retain local geometry because they are specimen chrome or intrinsic control anatomy rather than reusable application spacing roles.
30
30
  - The menu composition route demonstrates toolbar/content/footer anatomy, 24px major gaps, 8/1/8 child geometry, the full-width `ListHeader` count-or-badge/action split, logical-end 44px action targets with 18px visuals, and 44px row/header/toolbar targets. Its one chevron is a production 18px `DisclosureArrow` inside a controlled Tools header that actually reveals and hides content; ordinary Projects navigation has no false disclosure affordance. Browser assertions cover stable naming, expanded/content transitions, visual rotation, node identity, reduced motion, RTL alignment and containment, and wide, narrow, dark, and 200% layouts. The focused ListHeader route separately shows the shared neutral count pill for Attachments, Notes (including zero), and Duplicates plus a non-count legacy badge, full-width logical-end actions, and no disclosure without content.
31
31
  - The `DisclosureArrow` route renders the production 18px root-scaled default without a catalog-only size override. Its two native buttons independently control the default right-to-down chevron and a recognizable right-facing replacement arrow with left-to-up custom directions; both use shortest-path rotation and their names stay stable while `aria-expanded` communicates state. Browser coverage verifies the custom clockwise path, pointer and native keyboard activation, focus, narrow and 200%-zoom stacking with contained labels, consumer sizing, and the independent `.5` Web Awesome glyph scale retained by Kerf `Select`.
32
32
  - A detail with relationships renders one content-fitted `Components` footer selector: `Uses` links to direct dependencies and `Used by` links to reverse dependents. Its popup owns the standard surface inset so headings and even long component names stay clear of the menu border. Choosing either navigates to that component's demo. The relationship group is absent when both sets are empty; no duplicate summary is rendered.
33
- - Demo-only CSS supplies only the catalog shell and stages; component appearance stays in package CSS. Decorative chrome and Web Awesome controls share the same semantic system palette instead of carrying a separate demo identity.
34
- - Scenarios use deterministic data and cover real variants, long content, disabled state, selection, progress, feedback, overlays, media, helpers, and stateful interactions. Individual Web Awesome action, form, layout, navigation, feedback, media, and helper routes provide focused theme specimens without a redundant aggregate route; ListItem and ListHeader demonstrate typed domain metadata, protected native button/disclosure semantics, and a native popover trigger relationship without product props; the Headers composition demonstrates direct toolbar headings with an extra-large title, optional supporting copy, and grouped icon/actions; the Select route verifies that option icons survive unrelated Kerf rerenders and selected slot content follows value changes; the Animation route exposes preset, easing, duration, playback-rate, and transport controls with live lifecycle status; observer routes provide user-driven intersection, mutation, and resize events with live results; ToolbarControlGroup exposes all eight Hot Sheet 2 variants, loads the Web Awesome registrations needed by its popup specimen, and preserves the upgraded dropdown's managed light-DOM children across Kerf rerenders; the Toolbar route composes a collapsible TokenSearchField inside a group, animates between the empty iconic and complete field states, stays open while populated, and focuses the revealed editor; SegmentedControl covers toolbar, rounded-rectangle, pill, equal-width, and disabled presentations; TokenSearchField covers editable ordered chips, pointer and keyboard removal, caret-preserving controlled deletion, editing, clearing, free text, disabled state, and fixed first-line alignment while content wraps; StateBanner exposes every built-in tone with tone-tinted badges plus a scoped override; the Application tabs composition demonstrates overflow, edge autoscroll, and controlled pointer/keyboard reordering.
33
+ - Focused demos ship no local stylesheets, inline styles, or styling-only class names. They compose Kerf components first, Web Awesome surfaces where Kerf has no equivalent, and ordinary semantic HTML for the remainder. The catalog-owned `CatalogExample.viewport` contract supplies the few specimen constraints needed to demonstrate layout behavior. Recipes follow the same rule: they ship no stylesheets, inline styles, or styling-only classes, and the catalog frames each one in the `CatalogExample` viewport its exported `presentation` declares (full-width application arrangements span the canvas measure; narrower recipes sit in the centered example column). Demo-only CSS is limited to the document-shell `style.css`, which carries no recipe rules. Decorative chrome and Web Awesome controls share the same semantic system palette instead of carrying a separate demo identity.
34
+ - Scenarios use deterministic data and cover real variants, long content, disabled state, selection, progress, feedback, overlays, media, helpers, and stateful interactions. Individual Web Awesome action, form, layout, navigation, feedback, media, and helper routes provide focused theme specimens without a redundant aggregate route; ListItem and ListHeader demonstrate typed domain metadata, protected native button/disclosure semantics, and a native popover trigger relationship without product props; the Headers composition demonstrates direct toolbar headings with an extra-large title, optional supporting copy, and grouped icon/actions; the Select route verifies that option icons survive unrelated Kerf rerenders and selected slot content follows value changes; the Animation route exposes preset, easing, duration, playback-rate, and transport controls with live lifecycle status; observer routes provide user-driven intersection, mutation, and resize events with live results; ToolbarControlGroup exposes all eight Hot Sheet 2 variants, loads the Web Awesome registrations needed by its popup specimen, and preserves the upgraded dropdown's managed light-DOM children across Kerf rerenders; the Toolbar route composes a collapsible TokenSearchField inside a group, animates between the empty iconic and complete field states, stays open while populated, and focuses the revealed editor; SegmentedControl covers toolbar, rounded-rectangle, pill, equal-width, and disabled presentations; TokenSearchField covers editable ordered chips, pointer and keyboard removal, caret-preserving controlled deletion, editing, clearing, free text, disabled state, and fixed first-line alignment while content wraps; StateBanner exposes every built-in tone with tone-tinted badges plus a scoped override; the Application tabs composition uses a Web Awesome card with semantic header/footer slots and Kerf `Text` while demonstrating overflow, edge autoscroll, and controlled pointer/keyboard reordering.
35
35
  - The ToolbarControlGroup route also demonstrates the public compact/tight,
36
36
  mixed-content, filled-selection, nested-dropdown, and avatar/scrim contracts
37
37
  added for consumers that previously restyled group descendants. The compact
@@ -42,7 +42,7 @@ deterministic state, and semantic layout owners from the [recipe guide](./recipe
42
42
  paints only the pressed selection, with contain fitting in both cases. Its
43
43
  collapsible-search specimen follows the selected group shape with a
44
44
  concentric inner radius in both collapsed and expanded states.
45
- - Recipe ownership notes are hidden by default. A page-toolbar toggle reveals them as floating warning-toned notes with a folded corner, keeping instructional copy visually distinct and removable from the specimen flow.
45
+ - Recipe ownership notes are hidden by default. A page-toolbar toggle reveals each recipe's note as its `CatalogExample` note above the framed specimen, so instructional copy never covers the composition.
46
46
  - Settings exercise light/dark, increased contrast, and reduced motion without changing component code. The theme action starts from the operating system's effective color scheme, always names the appearance it will switch to, and installs an explicit light or dark override when activated; contrast and reduced motion expose their independent preferences through `aria-pressed`.
47
47
  - The footer action log is an `aria-live` region, making interactions visible to both people and browser tests.
48
48
  - The focused Toast route calls Web Awesome's documented programmatic stack API and notes that Hot Sheet 2 still uses its own app-level toast implementation; the catalog demonstrates ecosystem support without implying current product adoption.