@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
@@ -54,12 +54,30 @@ pointer targets. Override `--kui-wa-carousel-icon-size`,
54
54
  `--kui-wa-carousel-dot-hit-size` on a carousel or containing scope when a
55
55
  product needs different geometry.
56
56
 
57
+ Code text uses Kerf's named monospace stack, `--kui-font-mono`
58
+ (`ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono",
59
+ monospace`), and the theme points `--wa-font-family-code` at it. Web Awesome's
60
+ own `ui-monospace, monospace` renders the generic face (Courier on macOS) in
61
+ Chromium, which does not recognize `ui-monospace`; because the generic
62
+ `monospace` ends any stack, Kerf's token owns the stack rather than appending
63
+ fallbacks after Web Awesome's. Override `--kui-font-mono` to change code text in
64
+ both Kerf and Web Awesome components.
65
+
57
66
  Form fields carry the same content-item inset as the Kerf primitives: a 1px
58
67
  border with 8px inside it (`--wa-form-control-border-width`,
59
68
  `--wa-form-control-padding-block`, and `--wa-form-control-padding-inline` are set
60
69
  to the `--kui-layout-item-border-width` / `--kui-layout-item-padding` values), so
61
- a single-line control lands at the standard ~40px height and the value sits 9px
62
- in from the field edge. Each field's top label is inset by that same border +
70
+ the value sits 9px in from the field edge. Default-size single-line controls
71
+ share Kerf's 44px control height — the height of a Kerf `Select`, a toolbar
72
+ group, and a list row — so a `wa-input` or `wa-button` sits flush beside a Kerf
73
+ `Select`. The theme sets `--wa-form-control-height` to `2.75em` (44px at the
74
+ 16px default) on Button, Color Picker, Input, Known Date, Number Input, Radio
75
+ Group, Select, Textarea (its minimum height), and Time Input, so `size="large"`
76
+ scales proportionally while `size="small"` keeps Web Awesome's compact secondary
77
+ height. The token is scoped to those controls rather than the root because Web
78
+ Awesome also derives tag, copy-button, and toast geometry from it. Buttons
79
+ inside a `ToolbarControlGroup` keep the group's 40px (32px compact) control
80
+ height. Each field's top label is inset by that same border +
63
81
  padding (9px) so it lines up with the value inside the field, and is styled
64
82
  exactly like a `ListHeader` label — uppercase, `--kui-font-xs`, weight 650, quiet
65
83
  foreground. This applies to every free field that exposes a top label
package/docs/workbench.md CHANGED
@@ -12,6 +12,12 @@ import { Workbench } from "@kerfjs/ui/workbench";
12
12
  import "@kerfjs/ui/workbench.css";
13
13
  ```
14
14
 
15
+ A top-level `Workbench` needs a definite containing height. Import the opt-in
16
+ `@kerfjs/ui/document.css` baseline and add `.kui-app-root` to the direct mount
17
+ container, or provide an equivalent definite height in application-owned
18
+ layout. The component intentionally uses `height: 100%` rather than `100dvh` so
19
+ it can also be embedded. See [Document baseline](document-baseline.md).
20
+
15
21
  ## State lives in the app
16
22
 
17
23
  `Workbench` is declarative and the collapse is **pure CSS** — no wire. The app
@@ -90,7 +96,10 @@ with state-derived props:
90
96
  ```
91
97
 
92
98
  `wireResizableRegions` marks the active region with `data-resizing` and the
93
- package CSS suppresses content motion during pointer resize. A collapsed,
99
+ package CSS suppresses content motion during pointer resize. It applies the
100
+ live size to both the track and the slide-motion content, so content fills the
101
+ region throughout a drag, including any safe-area edge extent, and a later
102
+ collapse slides it out at the resized width. A collapsed,
94
103
  overlay, or hidden region is not resizeable. Together these policies replace
95
104
  app CSS for separator suppression, instant-track/composited-content collapse,
96
105
  popup overflow, mobile overlay/hidden replacement, resize-transition guards,
@@ -110,6 +119,16 @@ must classify public application selectors. Prefer the component props and two
110
119
  size tokens before selecting internal anatomy, and do not target its data
111
120
  attributes or descendant tags as styling contracts.
112
121
 
122
+ ## Safe areas
123
+
124
+ Each rail and the drawer grows by the unsafe inset of the screen edge it docks
125
+ to, so its surface and separator paint through while its content keeps the
126
+ configured size and is padded for the edges it touches. The main area pads for
127
+ the edges it reaches, inside its scroller: an expanded inline rail or drawer
128
+ takes that edge away, and collapsing it (or presenting it as an overlay) hands
129
+ the edge back. A region whose only child is a `Pane` or layout lets that child
130
+ own the insets. See [Choosing an app layout › Safe areas](app-layouts.md#safe-areas).
131
+
113
132
  ## How the collapse animates
114
133
 
115
134
  Collapsing snaps the panel's flex track to zero in a single reflow (so the work
@@ -336,6 +336,50 @@ async function inspectPage(page, geometryContracts) {
336
336
  return null;
337
337
  };
338
338
 
339
+ const hitExtent = (element, rect) => {
340
+ const root = element.getRootNode();
341
+ const probe = root.elementFromPoint ? root : document;
342
+ const inViewport = (x, y) =>
343
+ x >= 0 && y >= 0 && x < innerWidth && y < innerHeight;
344
+ const owns = (x, y) => {
345
+ const hit = probe.elementFromPoint(x, y);
346
+ return !!hit && (hit === element || element.contains(hit));
347
+ };
348
+ const x = rect.left + rect.width / 2;
349
+ const y = rect.top + rect.height / 2;
350
+ if (!inViewport(x, y) || !owns(x, y)) return null;
351
+ // Walk out in 1px steps (no further than the 44px goal needs), then
352
+ // refine the last step in quarter pixels; the reach is the last owned
353
+ // sample, so it never overstates the target. A walk stopped by
354
+ // the viewport edge cannot see the rest of the layer, so it is marked
355
+ // and mirrored from the opposite side (hit layers are symmetric insets).
356
+ const reach = (dx, dy) => {
357
+ const at = (distance) => [x + dx * distance, y + dy * distance];
358
+ let owned = 0;
359
+ while (owned < 24) {
360
+ const point = at(owned + 1);
361
+ if (!inViewport(...point)) return { distance: owned, clipped: true };
362
+ if (!owns(...point)) break;
363
+ owned += 1;
364
+ }
365
+ if (owned >= 24) return { distance: owned, clipped: false };
366
+ while (owns(...at(owned + 0.25))) owned += 0.25;
367
+ return { distance: owned, clipped: false };
368
+ };
369
+ const span = (first, second) =>
370
+ first.clipped && second.clipped
371
+ ? first.distance + second.distance
372
+ : first.clipped
373
+ ? 2 * second.distance
374
+ : second.clipped
375
+ ? 2 * first.distance
376
+ : first.distance + second.distance;
377
+ return {
378
+ width: span(reach(-1, 0), reach(1, 0)),
379
+ height: span(reach(0, -1), reach(0, 1)),
380
+ };
381
+ };
382
+
339
383
  for (const element of interactives) {
340
384
  const target = selector(element);
341
385
  const rect = element.getBoundingClientRect();
@@ -495,14 +539,35 @@ async function inspectPage(page, geometryContracts) {
495
539
  'button,input:not([type="hidden"]),select,textarea,[role]',
496
540
  ) &&
497
541
  (rect.width < 43.5 || rect.height < 43.5)
498
- )
499
- diagnostics.push({
500
- code: 'KUI-B050',
501
- selector: target,
502
- message: `Hit target is ${rect.width.toFixed(1)}×${rect.height.toFixed(1)}px; expected at least 44×44px.`,
503
- repair: repair.target,
504
- evidence: { width: rect.width, height: rect.height },
505
- });
542
+ ) {
543
+ // The border box understates a control whose pointer target is
544
+ // extended by a transparent hit layer (a positioned ::before/::after
545
+ // that receives pointer events). Hit-test outward from the center to
546
+ // measure the area the pointer can actually reach; a probe that finds
547
+ // no hit (off-screen, covered center) keeps the border box.
548
+ const hit = hitExtent(element, rect);
549
+ const width = Math.max(rect.width, hit?.width ?? 0);
550
+ const height = Math.max(rect.height, hit?.height ?? 0);
551
+ // Compact density is a deliberate trade-off: its rows are 36px tall,
552
+ // and a taller hit layer would reach into the neighboring row. Inside
553
+ // `[data-density="compact"]` a target still needs the full 44px width
554
+ // but only the 36px compact row height.
555
+ const compact = element.closest('[data-density="compact"]') !== null;
556
+ const requiredHeight = compact ? 36 : 44;
557
+ if (width < 43.5 || height < requiredHeight - 0.5)
558
+ diagnostics.push({
559
+ code: 'KUI-B050',
560
+ selector: target,
561
+ message: `Hit target is ${width.toFixed(1)}×${height.toFixed(1)}px; expected at least 44×${requiredHeight}px${compact ? ' (compact density)' : ''}.`,
562
+ repair: repair.target,
563
+ evidence: {
564
+ width,
565
+ height,
566
+ box: { width: rect.width, height: rect.height },
567
+ required: { width: 44, height: requiredHeight },
568
+ },
569
+ });
570
+ }
506
571
  }
507
572
 
508
573
  const parse = (value) => {
package/llms.txt CHANGED
@@ -34,7 +34,9 @@ the folder modules, and CSS only imports their owner stylesheets.
34
34
  when app-owned components must participate in selection or alignment.
35
35
  Load the [Composition catalog v2](./ai/component-catalog-v2.json) to
36
36
  mechanically evaluate parents, contexts, zones/cardinality, children, state,
37
- wiring, responsive/layout/accessibility rules, and public boundaries. V1
37
+ wiring (including the wiring-owned `stateAttributes` an app must never
38
+ render or remove), responsive/layout/accessibility rules, and public
39
+ boundaries. V1
38
40
  remains compatible; v2 joins catalogs by `package:id`. Consumer v2 catalogs
39
41
  use [the v2 extension schema](./ai/component-catalog-extension-v2.schema.json).
40
42
  Load [compile-time contracts](./ai/compile-time-contracts-v1.json) for stable
@@ -70,7 +72,9 @@ the folder modules, and CSS only imports their owner stylesheets.
70
72
  [catalog-authoring.json](./ai/catalog-authoring.json), then follow its
71
73
  authoritative [Catalog demo authoring contract](./docs/catalog.md#catalog-demo-authoring-contract).
72
74
  Use its component/composition/recipe taxonomy and functional-group,
73
- importance, alphabetical-tie, and recipes-last ordering policy.
75
+ importance, alphabetical-tie, and recipes-last ordering policy. Focused
76
+ demos and recipes use public components plus `CatalogExample.viewport`,
77
+ never local stylesheets, inline styles, or styling-only classes.
74
78
  5. **Apply the rules below and in** [skill.md](./ai/skill.md).
75
79
  6. **Before calling an integration complete**, run
76
80
  [`kerf-ui-doctor`](./docs/ui-doctor.md). Its portable JSON report merges
@@ -103,6 +107,11 @@ the folder modules, and CSS only imports their owner stylesheets.
103
107
  own the geometry (8px inline margin + 1px border + 8px padding). Do not add a
104
108
  container padding on top of a content-item's own margin — pick one owner. Use
105
109
  `.kui-content` for 24px major vertical rhythm.
110
+ - **Opt into document globals explicitly.** Import `@kerfjs/ui/document.css`
111
+ once for border-box sizing, token-backed body/link presentation, and zero body
112
+ margin. Put `.kui-app-root` on the one direct mount container only when a
113
+ percentage-height shell needs the provided `html` → `body` → app-root height
114
+ chain. The baseline is not part of `styles.css` or component browser imports.
106
115
  - **Use `Row` for horizontal and `List` for vertical layout.** Both expose
107
116
  physical `hAlign`/`vAlign` props and typed gaps. Row defaults to left/full,
108
117
  an `xs` gap, and no wrapping; List retains full/top and zero-gap defaults.
@@ -117,13 +126,19 @@ the folder modules, and CSS only imports their owner stylesheets.
117
126
  shorthand (`gap="xs"`, `gap="m"`) or a complete `CssLength` from the CSS-free
118
127
  `@kerfjs/ui/css-values` builders; raw CSS strings are rejected. Use `flex()`
119
128
  for List flex, typed lengths for Skeleton geometry, and
120
- `uiColor()`/`colorVar()` for choice-icon colors. These brands are intentionally
129
+ foreground colors for choice icons: `uiColor()` with an `*-on-*`/text token
130
+ or `foregroundColorVar()`; fill aliases such as `uiColor('success')` do not
131
+ type-check. These brands are intentionally
121
132
  not interchangeable; rows use classes/tokens/props rather than raw `style`.
122
133
  - **Use `Grid` for a fixed positive count of equal-width columns.** It uses
123
134
  zero-minimum fractional tracks, so intrinsic content cannot make one column
124
135
  wider than its peers. The app owns responsive count changes; use custom CSS
125
136
  grid for asymmetric/intrinsic tracks and `ResizableRegion` for adjustable
126
137
  boundaries. `Grid.gap` and `Grid.flex` use the same typed grammars as Row.
138
+ - **Give a layout root `fill`, not a frame `Pane`.** A Row, List, or Grid that
139
+ is the sole layout root of a definite-height parent takes its full height
140
+ with `fill` and carries safe `data-*` markers through `rootAttributes`.
141
+ Inside a flex layout use `flex` instead.
127
142
  - **Keep Web Awesome on the shared inset tiers.** `webawesome.css` uses
128
143
  `--kui-wa-control-inset` (8px controls/items) and
129
144
  `--kui-wa-surface-margin` / `--kui-wa-surface-inset` (8px around/inside
@@ -173,22 +188,25 @@ the folder modules, and CSS only imports their owner stylesheets.
173
188
  need a Web Awesome feature, chiefly the `slot="trigger"` button of a `wa-dropdown`
174
189
  popup menu. A toolbar popup menu is a `single` `ToolbarControlGroup` around a
175
190
  `wa-dropdown` (its `slot="trigger"` `wa-button` is the toolbar button, kept under
176
- `data-morph-skip-children`). Compact mixed groups keep standard item padding
177
- and raised selected chrome without an internal separator; the selected item
178
- paints over the outer border. For an avatar control, set `content="avatar"` and
179
- `avatarImage` on the group instead of inserting an `img`; a lone control paints
180
- the group and a multi-button group paints only its pressed highlight.
191
+ `data-morph-skip-children`). Let the group size the trigger from its icon,
192
+ localized text, and caret; do not force a dropdown or button width. Compact
193
+ mixed groups keep standard item padding and raised selected chrome without an
194
+ internal separator; the selected item paints over the outer border. For an
195
+ avatar control, set `content="avatar"` and `avatarImage` on the group instead of
196
+ inserting an `img`; a lone control paints the group and a multi-button group
197
+ paints only its pressed highlight.
181
198
  - **Use one direct toolbar composition for panel/dialog/page headings.** Put an
182
199
  optional icon in a `ToolbarControlGroup`, an extra-large `ToolbarText` directly
183
200
  in the leading zone, and actions in a trailing group. Supporting copy is
184
201
  app-owned content below. Do not create a private heading wrapper or restyle the
185
202
  toolbar.
186
- - **Use `Text` for ordinary semantic headings and paragraphs outside toolbar
187
- title zones.** Omit `variant` for the default `p`, or pass `h1`–`h6` based on
188
- the document outline. Independently compose `tone="quiet|danger"`,
203
+ - **Use `Text` for ordinary semantic headings, paragraphs, and inline secondary
204
+ text outside toolbar title zones.** Omit `variant` for the default `p`, pass
205
+ `h1`–`h6` based on the document outline, or use `variant="span"` inside a row,
206
+ label, or table cell without adding box geometry. Independently compose `tone="quiet|danger"`,
189
207
  `size="compact"`, and `font="monospace"` for supporting, error, compact, and
190
208
  code roles instead of global utility classes. It passes native attributes
191
- through and owns the standard transparent border plus item padding; keep
209
+ through; block variants own the standard transparent border plus item padding, while `span` owns none. Keep
192
210
  toolbar titles on `ToolbarText` and list-aligned bare prose on `ListInsetText`.
193
211
  - **Space by the five-step scale, by relationship not by eye.** `0`
194
212
  `--kui-space-none`, `4px` `--kui-space-2xs`, `8px` `--kui-space-xs` (inside a
@@ -279,7 +297,7 @@ tree-shakeable, declarative with `wire…` helpers, each with a companion CSS im
279
297
  `@kerfjs/ui/device-class`. For a standalone collapsible rail/drawer outside a full
280
298
  shell, `@kerfjs/ui/collapsible-panel` (`CollapsiblePanel`, `CollapsiblePanelToggle`)
281
299
 
282
- - `@kerfjs/ui/wire-sidebar` (`wireSidebar`: toggle, focus, compact overlay, persistence).
300
+ - `@kerfjs/ui/wire-sidebar` (`wireSidebar`: toggle, focus, compact overlay, persistence; a compact overlay starts closed and opens only on a user action, and `inlineCollapsed` declares a panel's inline default).
283
301
  `@kerfjs/ui/catalog` is another opt-in shell — a
284
302
  `Catalog` component + `wireCatalog` helper for building a component-gallery catalog
285
303
  (collapsible category sidebar + preview stage + resources footer, with an opt-in
@@ -300,7 +318,7 @@ shell, `@kerfjs/ui/collapsible-panel` (`CollapsiblePanel`, `CollapsiblePanelTogg
300
318
  - [component-contract.md](./docs/component-contract.md): what a component owns vs. what the app owns.
301
319
  - [accessibility.md](./docs/accessibility.md): keyboard and ARIA contracts per component.
302
320
  - [recipes.md](./docs/recipes.md): production compositions (app shell, list-detail dialog, composer form, …) as copyable references.
303
- - [app-layouts.md](./docs/app-layouts.md): choosing `NavStack`/`SplitView`/`Workbench`/`TabScaffold` by data + interaction + device, and dialog presentation.
321
+ - [app-layouts.md](./docs/app-layouts.md): choosing `NavStack`/`SplitView`/`Workbench`/`TabScaffold` by data + interaction + device, and dialog presentation, plus the safe-area model (surfaces paint through unsafe areas; content pads only the edges it reaches; `Pane.safeAreaEdges` for app-owned arrangements, `Toolbar.safeAreaEdges` for an app bar or bottom bar at a screen edge).
304
322
  - [webawesome-theme.md](./docs/webawesome-theme.md): the optional Web Awesome theme; catalog coverage means themed-and-supported, not preferred (prefer the Kerf primitive when one exists). The UX sidebar visibly tags superseded and exceptional Web Awesome choices as `Discouraged`; Popup remains an available conditional positioning primitive.
305
323
  - [component-catalog.json](./ai/component-catalog.json) + [component-catalog-extension.schema.json](./ai/component-catalog-extension.schema.json) + [catalog-authoring.json](./ai/catalog-authoring.json) + [public-api-signatures-v1.md](./ai/public-api-signatures-v1.md) + [webawesome-jsx-signatures-v1.md](./ai/webawesome-jsx-signatures-v1.md): machine-readable selection facts, a reusable app-component extension contract, Catalog authoring discovery, and exact signatures.
306
324
  - [compile-time-contracts-v1.json](./ai/compile-time-contracts-v1.json) + [type-contracts.md](./docs/type-contracts.md): stable compile diagnostics, packed-declaration proof, migrations, and explicit non-goals.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerfjs/ui",
3
- "version": "5.0.0-beta.50",
3
+ "version": "5.0.0-beta.52",
4
4
  "description": "Accessible, composable UI primitives for kerf applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -292,6 +292,7 @@
292
292
  "import": "./dist/index.js"
293
293
  },
294
294
  "./foundation.css": "./dist/styles/foundation.css",
295
+ "./document.css": "./dist/styles/document.css",
295
296
  "./layout.css": "./dist/styles/layout.css",
296
297
  "./webawesome.css": "./dist/styles/webawesome.css",
297
298
  "./styles.css": "./dist/styles/styles.css",
@@ -351,7 +352,8 @@
351
352
  "scripts": {
352
353
  "build": "tsup && node ../scripts/clean-dist-output.mjs dist && node scripts/build-css.mjs && node scripts/build-browser-entries.mjs",
353
354
  "dev": "npm run build && vite --config ux-demo/vite.config.ts",
354
- "demo:build": "npm run build && vite build --config ux-demo/vite.config.ts && node scripts/check-demo-bundle.mjs",
355
+ "demo:bundle": "npm run build && vite build --config ux-demo/vite.config.ts",
356
+ "demo:build": "npm run demo:bundle && node scripts/check-demo-bundle.mjs",
355
357
  "demo:serve": "vite preview --config ux-demo/vite.config.ts",
356
358
  "test:e2e:serve": "npm run check:catalog && npm run check:demo-conformance && npm run demo:build && npm run demo:serve",
357
359
  "format": "prettier --write . --ignore-unknown",