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

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 (188) hide show
  1. package/README.md +1 -0
  2. package/ai/application-ui-diagnostic-ids-v1.json +1 -0
  3. package/ai/component-catalog-v2.d.ts +3 -0
  4. package/ai/component-catalog-v2.json +85 -7
  5. package/ai/component-catalog-v2.schema.json +9 -1
  6. package/ai/component-catalog.json +108 -11
  7. package/ai/public-api-signatures-v1.md +215 -25
  8. package/ai/skill.md +23 -17
  9. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  10. package/analyzer/index.mjs +48 -0
  11. package/analyzer/loud-pairs.d.mts +49 -0
  12. package/analyzer/loud-pairs.mjs +357 -0
  13. package/dist/app-tab.d.ts +7 -5
  14. package/dist/app-tab.js +1 -1
  15. package/dist/browser/segmented-control.js +0 -1
  16. package/dist/catalog.js +4 -4
  17. package/dist/{chunk-EKYZCRCX.js → chunk-3CGQ7ZNO.js} +54 -22
  18. package/dist/chunk-3CGQ7ZNO.js.map +1 -0
  19. package/dist/{chunk-7XJO4D6A.js → chunk-3NBL3OUQ.js} +2 -2
  20. package/dist/{chunk-7XJO4D6A.js.map → chunk-3NBL3OUQ.js.map} +1 -1
  21. package/dist/{chunk-2S7OWAEV.js → chunk-44IG7PFV.js} +3 -3
  22. package/dist/{chunk-2S7OWAEV.js.map → chunk-44IG7PFV.js.map} +1 -1
  23. package/dist/{chunk-UKKSMZYT.js → chunk-46263QLK.js} +51 -1
  24. package/dist/chunk-46263QLK.js.map +1 -0
  25. package/dist/{chunk-DNCOFEUL.js → chunk-5I22LMCV.js} +2 -2
  26. package/dist/{chunk-DNCOFEUL.js.map → chunk-5I22LMCV.js.map} +1 -1
  27. package/dist/{chunk-NDUGJ4TU.js → chunk-5NDQABWK.js} +1 -1
  28. package/dist/{chunk-NDUGJ4TU.js.map → chunk-5NDQABWK.js.map} +1 -1
  29. package/dist/{chunk-KAQUUTZR.js → chunk-ATHXW6RP.js} +2 -2
  30. package/dist/{chunk-KAQUUTZR.js.map → chunk-ATHXW6RP.js.map} +1 -1
  31. package/dist/{chunk-JGFZ42ZF.js → chunk-BJJKFVWW.js} +14 -5
  32. package/dist/chunk-BJJKFVWW.js.map +1 -0
  33. package/dist/{chunk-T7C2VZUB.js → chunk-C2FAYFDE.js} +2 -2
  34. package/dist/{chunk-T7C2VZUB.js.map → chunk-C2FAYFDE.js.map} +1 -1
  35. package/dist/{chunk-SV22UIW4.js → chunk-DQA5FMOX.js} +2 -2
  36. package/dist/{chunk-SV22UIW4.js.map → chunk-DQA5FMOX.js.map} +1 -1
  37. package/dist/{chunk-LIOXPQ7N.js → chunk-GUIHMIT2.js} +12 -10
  38. package/dist/chunk-GUIHMIT2.js.map +1 -0
  39. package/dist/{chunk-3S7OUHUN.js → chunk-KPNTVB7A.js} +9 -4
  40. package/dist/chunk-KPNTVB7A.js.map +1 -0
  41. package/dist/{chunk-IR53BQUJ.js → chunk-NE5JAOQS.js} +9 -9
  42. package/dist/chunk-NE5JAOQS.js.map +1 -0
  43. package/dist/chunk-PSHUSWQH.js +18 -0
  44. package/dist/chunk-PSHUSWQH.js.map +1 -0
  45. package/dist/{chunk-OSCL5TKR.js → chunk-RNZAKSME.js} +11 -8
  46. package/dist/chunk-RNZAKSME.js.map +1 -0
  47. package/dist/{chunk-ABAXNUXU.js → chunk-T22EM4ET.js} +7 -5
  48. package/dist/chunk-T22EM4ET.js.map +1 -0
  49. package/dist/{chunk-YZV2PTY4.js → chunk-TZ5IHWB2.js} +3 -3
  50. package/dist/{chunk-YZV2PTY4.js.map → chunk-TZ5IHWB2.js.map} +1 -1
  51. package/dist/{chunk-VOCPXGQN.js → chunk-VHADGP3B.js} +9 -8
  52. package/dist/chunk-VHADGP3B.js.map +1 -0
  53. package/dist/{chunk-2CE22Q2K.js → chunk-WZ2GLEY4.js} +2 -1
  54. package/dist/chunk-WZ2GLEY4.js.map +1 -0
  55. package/dist/{chunk-M6GLI6A6.js → chunk-X5QSICJ3.js} +5 -5
  56. package/dist/chunk-X5QSICJ3.js.map +1 -0
  57. package/dist/chunk-XQV2B4IL.js +14 -0
  58. package/dist/chunk-XQV2B4IL.js.map +1 -0
  59. package/dist/{chunk-4XE2CDYB.js → chunk-ZMBU522F.js} +2 -2
  60. package/dist/{chunk-4XE2CDYB.js.map → chunk-ZMBU522F.js.map} +1 -1
  61. package/dist/collapsible-panel.d.ts +6 -2
  62. package/dist/collapsible-panel.js +1 -0
  63. package/dist/collapsible-panel.js.map +1 -1
  64. package/dist/css-values.d.ts +1 -1
  65. package/dist/css-values.js +1 -1
  66. package/dist/floating-toolbar.d.ts +5 -2
  67. package/dist/floating-toolbar.js +1 -1
  68. package/dist/grid.js +1 -1
  69. package/dist/index.js +20 -19
  70. package/dist/list-action-row.d.ts +2 -1
  71. package/dist/list-action-row.js +1 -1
  72. package/dist/list-header.d.ts +1 -0
  73. package/dist/list-header.js +1 -1
  74. package/dist/list-item.d.ts +5 -3
  75. package/dist/list-item.js +1 -1
  76. package/dist/list.js +1 -1
  77. package/dist/nav-stack.js +1 -1
  78. package/dist/resizable-region.d.ts +4 -1
  79. package/dist/resizable-region.js +1 -1
  80. package/dist/row.js +1 -1
  81. package/dist/segmented-control.d.ts +1 -1
  82. package/dist/segmented-control.js +1 -3
  83. package/dist/select.js +1 -1
  84. package/dist/skeleton.js +1 -1
  85. package/dist/spacer.js +1 -1
  86. package/dist/split-view.js +2 -2
  87. package/dist/state-banner.d.ts +1 -1
  88. package/dist/state-banner.js +1 -1
  89. package/dist/styles/badge.css +17 -6
  90. package/dist/styles/collapsible-panel.css +89 -11
  91. package/dist/styles/floating-toolbar.css +11 -6
  92. package/dist/styles/foundation.css +82 -26
  93. package/dist/styles/list-action-row.css +29 -8
  94. package/dist/styles/list-header.css +19 -7
  95. package/dist/styles/list-item.css +11 -3
  96. package/dist/styles/resizable-region.css +82 -14
  97. package/dist/styles/segmented-control.css +21 -1
  98. package/dist/styles/select.css +24 -13
  99. package/dist/styles/skeleton.css +33 -2
  100. package/dist/styles/state-banner.css +48 -12
  101. package/dist/styles/webawesome.css +47 -20
  102. package/dist/styles/workbench.css +473 -11
  103. package/dist/toolbar-text.js +1 -1
  104. package/dist/value-table.js +1 -1
  105. package/dist/wire-resizable-regions.d.ts +2 -1
  106. package/dist/wire-resizable-regions.js +2 -1
  107. package/dist/wire-tab-bars.js +1 -1
  108. package/dist/wire-workbench.d.ts +109 -0
  109. package/dist/wire-workbench.js +355 -0
  110. package/dist/wire-workbench.js.map +1 -0
  111. package/dist/workbench.d.ts +65 -6
  112. package/dist/workbench.js +180 -76
  113. package/dist/workbench.js.map +1 -1
  114. package/docs/accessibility.md +54 -9
  115. package/docs/app-layouts.md +12 -1
  116. package/docs/collapsible-panel.md +59 -1
  117. package/docs/component-contract.md +76 -4
  118. package/docs/component-selection.md +16 -6
  119. package/docs/design/templates/empty-state/with-action-dark.svg +1 -1
  120. package/docs/design/templates/empty-state-dark.svg +1 -1
  121. package/docs/design/templates/list/stack-dark.svg +1 -1
  122. package/docs/design/templates/list/stack.svg +1 -1
  123. package/docs/design/templates/list-action-row/selected.svg +1 -1
  124. package/docs/design/templates/list-action-row.svg +1 -1
  125. package/docs/design/templates/list-dark.svg +1 -1
  126. package/docs/design/templates/list-header/action-dark.svg +1 -1
  127. package/docs/design/templates/list-header/action.svg +1 -1
  128. package/docs/design/templates/list-header/count-dark.svg +1 -1
  129. package/docs/design/templates/list-header/count.svg +1 -1
  130. package/docs/design/templates/list-header/inline-action-dark.svg +1 -1
  131. package/docs/design/templates/list-header/inline-action.svg +1 -1
  132. package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -1
  133. package/docs/design/templates/list-header/toggle-expanded.svg +1 -1
  134. package/docs/design/templates/list-header-dark.svg +1 -1
  135. package/docs/design/templates/list-header.svg +1 -1
  136. package/docs/design/templates/list-item/selected.svg +1 -1
  137. package/docs/design/templates/list-item.svg +1 -1
  138. package/docs/design/templates/list.svg +1 -1
  139. package/docs/design/templates/segmented-control/equal-dark.svg +1 -1
  140. package/docs/design/templates/segmented-control/equal.svg +1 -1
  141. package/docs/design/templates/segmented-control/pill-small-dark.svg +1 -1
  142. package/docs/design/templates/segmented-control/pill-small.svg +1 -1
  143. package/docs/design/templates/segmented-control-dark.svg +1 -1
  144. package/docs/design/templates/segmented-control.svg +1 -1
  145. package/docs/design/templates/state-banner/danger-dark.svg +1 -1
  146. package/docs/design/templates/state-banner/danger.svg +1 -1
  147. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -1
  148. package/docs/design/templates/state-banner/info-badge.svg +1 -1
  149. package/docs/design/templates/state-banner/info-dark.svg +1 -1
  150. package/docs/design/templates/state-banner/info.svg +1 -1
  151. package/docs/design/templates/state-banner/success-dark.svg +1 -1
  152. package/docs/design/templates/state-banner/success.svg +1 -1
  153. package/docs/design/templates/state-banner/warning-dark.svg +1 -1
  154. package/docs/design/templates/state-banner/warning.svg +1 -1
  155. package/docs/design/templates/state-banner-dark.svg +1 -1
  156. package/docs/design/templates/state-banner.svg +1 -1
  157. package/docs/design/templates/tab-bar/workspace-tabs-dark.svg +1 -1
  158. package/docs/design/templates/tab-bar/workspace-tabs.svg +1 -1
  159. package/docs/design/templates/tab-bar-dark.svg +1 -1
  160. package/docs/design/templates/tab-bar.svg +1 -1
  161. package/docs/design/templates/token-search-field/tokens.svg +1 -1
  162. package/docs/design/templates/token-search-field.svg +1 -1
  163. package/docs/design/templates.md +11 -1
  164. package/docs/device-class.md +2 -1
  165. package/docs/document-baseline.md +4 -0
  166. package/docs/layout.md +5 -1
  167. package/docs/recipes.md +5 -3
  168. package/docs/type-contracts.md +2 -1
  169. package/docs/ui-analyzer.md +94 -19
  170. package/docs/ui-doctor.md +6 -0
  171. package/docs/ux-demo.md +2 -2
  172. package/docs/webawesome-theme.md +18 -2
  173. package/docs/workbench.md +302 -7
  174. package/llms.txt +14 -6
  175. package/package.json +5 -1
  176. package/ux-demo/recipes/collapsible-sidebar.tsx +8 -7
  177. package/ux-demo/recipes/loading-inspector.tsx +3 -2
  178. package/dist/chunk-2CE22Q2K.js.map +0 -1
  179. package/dist/chunk-3S7OUHUN.js.map +0 -1
  180. package/dist/chunk-ABAXNUXU.js.map +0 -1
  181. package/dist/chunk-EKYZCRCX.js.map +0 -1
  182. package/dist/chunk-IR53BQUJ.js.map +0 -1
  183. package/dist/chunk-JGFZ42ZF.js.map +0 -1
  184. package/dist/chunk-LIOXPQ7N.js.map +0 -1
  185. package/dist/chunk-M6GLI6A6.js.map +0 -1
  186. package/dist/chunk-OSCL5TKR.js.map +0 -1
  187. package/dist/chunk-UKKSMZYT.js.map +0 -1
  188. package/dist/chunk-VOCPXGQN.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  # Public API signatures for the UI authoring corpus
2
2
 
3
- Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.52` and `kerfjs@5.0.0-beta.52`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
3
+ Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.54` and `kerfjs@5.0.0-beta.54`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
4
4
 
5
5
  ## `@kerfjs/ui/css-values`
6
6
 
@@ -60,7 +60,7 @@ type CssFlexBasis = CssLength | 'auto' | 'content' | 'min-content' | 'max-conten
60
60
  type CssSizeKeyword = 'auto' | 'min-content' | 'max-content' | 'fit-content';
61
61
  /** A complete width/height value accepted by dimension-valued UI props. */
62
62
  type CssSize = CssLength | CssSizeKeyword;
63
- declare const uiColorNames: readonly ["accent", "accent-text", "border", "border-quiet", "brand-border-loud", "brand-border-normal", "brand-border-quiet", "brand-fill-loud", "brand-fill-normal", "brand-fill-quiet", "brand-on-fill", "brand-on-loud", "brand-on-normal", "brand-on-quiet", "danger", "danger-border-loud", "danger-border-normal", "danger-border-quiet", "danger-fill-loud", "danger-fill-normal", "danger-fill-quiet", "danger-on-loud", "danger-on-normal", "danger-on-quiet", "danger-text", "neutral-border-loud", "neutral-border-normal", "neutral-border-quiet", "neutral-fill-loud", "neutral-fill-normal", "neutral-fill-quiet", "neutral-on-loud", "neutral-on-normal", "neutral-on-quiet", "pop", "pop-border-loud", "pop-border-normal", "pop-border-quiet", "pop-fill-loud", "pop-fill-normal", "pop-fill-quiet", "pop-on-fill", "pop-on-loud", "pop-on-normal", "pop-on-quiet", "pop-text", "success", "success-border-loud", "success-border-normal", "success-border-quiet", "success-fill-loud", "success-fill-normal", "success-fill-quiet", "success-on-fill", "success-on-loud", "success-on-normal", "success-on-quiet", "success-text", "surface", "surface-lowered", "surface-raised", "text", "text-link", "text-quiet", "warning", "warning-border-loud", "warning-border-normal", "warning-border-quiet", "warning-fill-loud", "warning-fill-normal", "warning-fill-quiet", "warning-on-fill", "warning-on-loud", "warning-on-normal", "warning-on-quiet", "warning-text"];
63
+ declare const uiColorNames: readonly ["accent", "accent-text", "border", "border-quiet", "brand-border-loud", "brand-border-normal", "brand-border-quiet", "brand-fill-loud", "brand-fill-normal", "brand-fill-quiet", "brand-on-fill", "brand-on-loud", "brand-on-normal", "brand-on-quiet", "danger", "danger-border-loud", "danger-border-normal", "danger-border-quiet", "danger-fill-loud", "danger-fill-normal", "danger-fill-quiet", "danger-on-fill", "danger-on-loud", "danger-on-normal", "danger-on-quiet", "danger-text", "neutral-border-loud", "neutral-border-normal", "neutral-border-quiet", "neutral-fill-loud", "neutral-fill-normal", "neutral-fill-quiet", "neutral-on-loud", "neutral-on-normal", "neutral-on-quiet", "pop", "pop-border-loud", "pop-border-normal", "pop-border-quiet", "pop-fill-loud", "pop-fill-normal", "pop-fill-quiet", "pop-on-fill", "pop-on-loud", "pop-on-normal", "pop-on-quiet", "pop-text", "success", "success-border-loud", "success-border-normal", "success-border-quiet", "success-fill-loud", "success-fill-normal", "success-fill-quiet", "success-on-fill", "success-on-loud", "success-on-normal", "success-on-quiet", "success-text", "surface", "surface-lowered", "surface-raised", "text", "text-link", "text-quiet", "warning", "warning-border-loud", "warning-border-normal", "warning-border-quiet", "warning-fill-loud", "warning-fill-normal", "warning-fill-quiet", "warning-on-fill", "warning-on-loud", "warning-on-normal", "warning-on-quiet", "warning-text"];
64
64
  /** Names of the public `--kui-color-*` semantic tokens. */
65
65
  type UiColorName = (typeof uiColorNames)[number];
66
66
  /**
@@ -340,8 +340,11 @@ interface FloatingToolbarProps {
340
340
  * restore button) that sits over the content but NOT over dialogs or overlays
341
341
  * (it is not in the top layer). It is inset from the container edges by
342
342
  * `--kui-floating-toolbar-inset` (default `--kui-space-m`, i.e. 8px more than a
343
- * top toolbar's own inset); override that token to move it. The app owns the
344
- * controls and their behavior — wire them with `delegate()` as usual.
343
+ * top toolbar's own inset); override that token, on the toolbar or an
344
+ * ancestor, to move it. Inside a `Workbench`, `CollapsiblePanel`, or
345
+ * `ResizableRegion` restore corner the corner owns the inset, so the toolbar
346
+ * floats from the corner's own position. The app owns the controls and their
347
+ * behavior — wire them with `delegate()` as usual.
345
348
  */
346
349
  declare function FloatingToolbar({ children, label, position, className, slot, }: FloatingToolbarProps): kerfjs.SafeHtml;
347
350
 
@@ -367,6 +370,7 @@ type ListHeaderRootAttributes = Readonly<Record<`data-${string}`, string | undef
367
370
  }>;
368
371
  type ListHeaderTriggerAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
369
372
  'data-action'?: never;
373
+ 'data-kui-disabled'?: never;
370
374
  popoverTarget?: string;
371
375
  popoverTargetAction?: 'toggle' | 'show' | 'hide';
372
376
  'aria-controls'?: string;
@@ -518,6 +522,7 @@ type ListActionRowTrailingAttributes = Readonly<Record<`data-${string}`, string
518
522
  'data-component'?: never;
519
523
  'data-action'?: never;
520
524
  'data-item-id'?: never;
525
+ 'data-kui-disabled'?: never;
521
526
  popoverTarget?: string;
522
527
  popoverTargetAction?: 'toggle' | 'show' | 'hide';
523
528
  'aria-controls'?: string;
@@ -543,7 +548,7 @@ interface ListActionRowProps {
543
548
  state?: string;
544
549
  disabled?: boolean;
545
550
  tabIndex?: number;
546
- /** Render as an unanimated loading skeleton, disabling both actions. */
551
+ /** Render as an unanimated loading skeleton, disabling both actions: label, description, icon, and status text become skeletons; the `busy` indicator stays live. */
547
552
  placeholder?: boolean;
548
553
  trailingAction: string;
549
554
  trailingActionLabel: string;
@@ -580,16 +585,18 @@ type ListItemRootAttributes = Readonly<Record<`data-${string}`, string | undefin
580
585
  'data-divider'?: never;
581
586
  'data-busy'?: never;
582
587
  'data-state'?: never;
588
+ 'data-kui-disabled'?: never;
583
589
  }>;
584
590
  interface ListItemProps {
585
591
  label: string | SafeHtml;
586
592
  /** App-owned supporting text rendered in the component's stable label stack. */
587
593
  description?: string | SafeHtml;
588
594
  icon?: SafeHtml;
595
+ /** Dormant author content at the row's end; a placeholder renders it as a skeleton. */
589
596
  trailing?: KerfUiContent;
590
- /** Dormant status metadata rendered before trailing content. */
597
+ /** Dormant status metadata rendered before trailing content; a placeholder renders it as a skeleton. */
591
598
  status?: string | SafeHtml;
592
- /** Show a progress indicator and expose the row as busy without replacing its content. */
599
+ /** Show a progress indicator and expose the row as busy without replacing its content; a placeholder keeps the indicator. */
593
600
  busy?: boolean;
594
601
  density?: 'standard' | 'compact';
595
602
  divider?: 'none' | 'before' | 'after' | 'both';
@@ -604,7 +611,7 @@ interface ListItemProps {
604
611
  state?: string;
605
612
  disabled?: boolean;
606
613
  tabIndex?: number;
607
- /** Render the row as an unanimated loading skeleton, disabling its action. */
614
+ /** Render the row as an unanimated loading skeleton, disabling its action: label, description, icon, status, and trailing content become skeletons; the `busy` indicator stays live. */
608
615
  placeholder?: boolean;
609
616
  rootAttributes?: ListItemRootAttributes;
610
617
  /** Native named-slot assignment when composed inside a web component. */
@@ -747,11 +754,13 @@ interface AppTabProps {
747
754
  /** Render as an unanimated loading skeleton, disabling select/close and dragging. */
748
755
  placeholder?: boolean;
749
756
  /**
750
- * Dormant but named: the tab is known and still opening. Keeps `name`
751
- * visible in the quiet text color and as the tab's accessible name, shows
752
- * `trailing` (for example a `LoadingSpinner`), and disables select, close,
753
- * and dragging with `aria-busy`, like `placeholder`. Same pill geometry as
754
- * the live tab, so swapping it in place does not shift the bar.
757
+ * Named and still opening: the tab is known but its content is loading.
758
+ * Keeps `name` visible in the quiet text color and as the tab's accessible
759
+ * name, shows `trailing` (for example a `LoadingSpinner`), and marks the
760
+ * tab `aria-busy`. Unlike `placeholder` it stays selectable — the
761
+ * application shows placeholder content in its panel until loading
762
+ * completes — while close and dragging stay disabled. Same pill geometry
763
+ * as the live tab, so swapping it in place does not shift the bar.
755
764
  * `placeholder` wins when both are set.
756
765
  */
757
766
  pending?: boolean;
@@ -1047,24 +1056,64 @@ import { SafeHtml } from 'kerfjs';
1047
1056
  import { ResizableRegionSeparator, ResizableRegionCollapseMotion, ResizableRegionContentOverflow, ResizableRegionPresentation, ResizableRegionRestorePosition } from './resizable-region.js';
1048
1057
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
1049
1058
 
1059
+ /**
1060
+ * The Workbench container breakpoint below which a panel presents as an
1061
+ * overlay: `narrow` (704px or less) or `compact` (448px or less) — the same
1062
+ * breakpoints as `ResizableRegion`'s `responsiveFillAt`.
1063
+ */
1064
+ type WorkbenchResponsiveOverlayAt = 'compact' | 'narrow';
1065
+ /** Drag-resize limits for a resizable Workbench panel, in px. */
1066
+ interface WorkbenchPanelResizable {
1067
+ /** Smallest size (default 180 for a rail, 120 for the drawer). */
1068
+ min?: number;
1069
+ /** Largest size (default 480). */
1070
+ max?: number;
1071
+ }
1050
1072
  /** A collapsible Workbench panel — a side rail or the bottom drawer. */
1051
1073
  interface WorkbenchPanel {
1052
1074
  content: KerfUiContent;
1053
1075
  /** Whether the panel is currently collapsed (the app owns this). */
1054
1076
  collapsed?: boolean;
1055
- /** Rail width, or drawer height, in px. Overrides the CSS default. */
1077
+ /**
1078
+ * Rail width, or drawer height, in px. Overrides the CSS default. For a
1079
+ * resizable panel it is the current size (default 280 for a rail, 220 for
1080
+ * the drawer), clamped to the limits; the app owns it and `wireWorkbench`
1081
+ * reports each resize.
1082
+ */
1056
1083
  size?: number;
1084
+ /**
1085
+ * Opt in to drag and keyboard resizing on the panel's inner separator:
1086
+ * `true` for the default limits, or `{ min, max }`. Off by default. Pair it
1087
+ * with `wireWorkbench` from `@kerfjs/ui/wire-workbench`.
1088
+ */
1089
+ resizable?: boolean | WorkbenchPanelResizable;
1057
1090
  /** Accessible name for the panel region. */
1058
1091
  label?: string;
1059
1092
  separator?: ResizableRegionSeparator;
1060
1093
  collapseMotion?: ResizableRegionCollapseMotion;
1061
1094
  contentOverflow?: ResizableRegionContentOverflow;
1062
1095
  presentation?: ResizableRegionPresentation;
1063
- /** Control shown in a safe-area-aware viewport corner while collapsed. */
1096
+ /**
1097
+ * Present the panel as an overlay, without a separator, below a Workbench
1098
+ * container breakpoint, and inline above it — the CSS decides, so the app
1099
+ * needs no device-class check. A rail overlays from its side at full
1100
+ * height; the bottom drawer overlays the bottom of the work-area column.
1101
+ */
1102
+ responsiveOverlayAt?: WorkbenchResponsiveOverlayAt;
1103
+ /**
1104
+ * Control shown while collapsed, in a safe-area-aware corner of the
1105
+ * Workbench (not the viewport); the bottom drawer's sits in the work-area
1106
+ * column.
1107
+ */
1064
1108
  restoreControl?: SafeHtml;
1065
1109
  restorePosition?: ResizableRegionRestorePosition;
1066
1110
  }
1067
1111
  interface WorkbenchProps {
1112
+ /**
1113
+ * The Workbench's `id`. Each panel's `id` derives from it —
1114
+ * `<id>-left-rail`, `<id>-right-rail`, `<id>-bottom-drawer` — so a toggle
1115
+ * can name the panel it shows and hides with `aria-controls`.
1116
+ */
1068
1117
  id: string;
1069
1118
  label: string;
1070
1119
  /** The central work area. */
@@ -1072,6 +1121,22 @@ interface WorkbenchProps {
1072
1121
  leftRail?: WorkbenchPanel;
1073
1122
  rightRail?: WorkbenchPanel;
1074
1123
  bottomDrawer?: WorkbenchPanel;
1124
+ /**
1125
+ * Minimum width, in px, the work area keeps beside the inline rails, fixed
1126
+ * or `resizable` (default 320; `0` turns it off). Resizing a rail stops
1127
+ * where the work area would drop below it, and inline rails shrink
1128
+ * proportionally when the Workbench gets narrower. Workbenches without a
1129
+ * rail ignore it.
1130
+ */
1131
+ mainMinSize?: number;
1132
+ /**
1133
+ * Minimum height, in px, the work area keeps above an inline bottom drawer,
1134
+ * fixed or `resizable` (default 120; `0` turns it off) — the drawer's
1135
+ * counterpart of `mainMinSize`. Resizing the drawer stops where the work
1136
+ * area would drop below it, and the drawer shrinks when the Workbench gets
1137
+ * shorter. Workbenches without a drawer ignore it.
1138
+ */
1139
+ mainMinHeight?: number;
1075
1140
  className?: string;
1076
1141
  /** Native named-slot assignment when composed inside a web component. */
1077
1142
  slot?: string;
@@ -1083,12 +1148,129 @@ interface WorkbenchProps {
1083
1148
  * a composited transform — the instant-width / sliding-content technique, so the
1084
1149
  * work area relayouts once, not per frame. Bottom-drawer content stays anchored
1085
1150
  * to the shell's stable bottom edge throughout that transition. The app owns
1086
- * each `collapsed` flag; the collapse is pure CSS (no wire). See
1087
- * `docs/23-app-layouts.md` §3.3.
1151
+ * each `collapsed` flag; the collapse is pure CSS. A collapsed panel renders
1152
+ * `inert` and `aria-hidden`, so neither Tab nor assistive technology reaches
1153
+ * controls that have slid out of view or an empty landmark (its restore control
1154
+ * lives outside it and stays reachable). A panel may opt in to drag and keyboard resizing with
1155
+ * `resizable`, which `wireWorkbench` drives. See `docs/23-app-layouts.md` §3.3.
1156
+ */
1157
+ declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, mainMinSize, mainMinHeight, className, slot, }: WorkbenchProps): SafeHtml;
1158
+
1159
+ export { Workbench, type WorkbenchPanel, type WorkbenchPanelResizable, type WorkbenchProps, type WorkbenchResponsiveOverlayAt };
1160
+ ```
1161
+
1162
+ ## `@kerfjs/ui/wire-workbench`
1163
+
1164
+ ```ts
1165
+ import { Signal, ReadonlySignal } from 'kerfjs';
1166
+ import { DeviceClass } from './device-class.js';
1167
+
1168
+ /** A Workbench panel slot, named after its `WorkbenchProps` prop. */
1169
+ type WorkbenchPanelKey = 'leftRail' | 'rightRail' | 'bottomDrawer';
1170
+
1171
+ /** Minimal `localStorage`-shaped store, so the persistence hook is testable. */
1172
+ interface WorkbenchStorage {
1173
+ getItem(key: string): string | null;
1174
+ setItem(key: string, value: string): void;
1175
+ }
1176
+ /** One Workbench panel's app-owned state. */
1177
+ interface WireWorkbenchPanel {
1178
+ /**
1179
+ * The app-owned size signal a `resizable` panel renders as its `size`.
1180
+ * `wireWorkbench` writes each committed resize here; collapsing never
1181
+ * touches it, so an expanded panel returns at the size it had. Omit it for
1182
+ * a panel that is not resizable.
1183
+ */
1184
+ size?: Signal<number>;
1185
+ /**
1186
+ * When set with `size`, the size is loaded from and saved to `storage`
1187
+ * under this key, so the panel remembers the user's size.
1188
+ */
1189
+ storageKey?: string;
1190
+ /**
1191
+ * The app-owned signal the panel renders as its `collapsed`. With it (and
1192
+ * `dismissOverlays`, on by default) `wireWorkbench` treats the panel as a
1193
+ * transient overlay while it presents as one: it collapses when its
1194
+ * `responsiveOverlayAt` breakpoint begins to apply and gets its inline state
1195
+ * back when the breakpoint stops applying, an open overlay takes focus and
1196
+ * keeps Tab inside it, and it closes on Escape or a press outside it.
1197
+ */
1198
+ collapsed?: Signal<boolean>;
1199
+ }
1200
+ /** A resize the user made, after `wireWorkbench` wrote it to the size signal. */
1201
+ interface WorkbenchResize {
1202
+ panel: WorkbenchPanelKey;
1203
+ size: number;
1204
+ source: 'keyboard' | 'pointer';
1205
+ }
1206
+ interface WireWorkbenchOptions {
1207
+ /** The `id` the `Workbench` was rendered with. */
1208
+ id: string;
1209
+ /**
1210
+ * The wired panels, keyed like the `Workbench` props. Render a panel given
1211
+ * a `size` with `resizable` and `size={panel.size.value}`, and one given a
1212
+ * `collapsed` signal with `collapsed={panel.collapsed.value}`.
1213
+ */
1214
+ panels: Partial<Record<WorkbenchPanelKey, WireWorkbenchPanel>>;
1215
+ /**
1216
+ * When provided, resizing is suspended while `deviceClass.compact` is true —
1217
+ * the classes where rails become overlay drawers or are replaced.
1218
+ */
1219
+ deviceClass?: ReadonlySignal<DeviceClass>;
1220
+ /** Persistence store (default `globalThis.localStorage`, if present). */
1221
+ storage?: WorkbenchStorage;
1222
+ /** Keyboard step in px (default 16). */
1223
+ step?: number;
1224
+ /** Shift+arrow step in px (default 64). */
1225
+ largeStep?: number;
1226
+ /** Called after each committed resize. */
1227
+ onResize?: (change: WorkbenchResize) => void;
1228
+ /**
1229
+ * Treat the panels given a `collapsed` signal as transient overlays while
1230
+ * they present as overlays (default `true`), like `wireSidebar`'s compact
1231
+ * overlay: a panel whose `responsiveOverlayAt` breakpoint begins to apply
1232
+ * starts collapsed, with no collapse motion, and gets its inline collapsed
1233
+ * state back when the breakpoint stops applying (and on disposal); an open
1234
+ * overlay panel, responsive or `presentation: "overlay"`, closes on Escape
1235
+ * or a press that starts and ends outside it. A panel that opens as an
1236
+ * overlay takes focus on its first focusable control, and while it is open
1237
+ * Tab and Shift+Tab cycle through its controls instead of reaching the
1238
+ * work area it covers (the ARIA dialog pattern of `wireSidebar`'s compact
1239
+ * overlay); inline panels never move focus. Focus stranded in a closing
1240
+ * panel — however it closed, the app's own control inside it included —
1241
+ * returns to the control that had it when the panel opened, else to the
1242
+ * panel's restore control, else to a control outside the panel whose
1243
+ * `aria-controls` names it (each panel's `id` is its region id, e.g.
1244
+ * `studio-left-rail`) — the fallback for a panel already open at wire-up.
1245
+ * An opener inside a panel that has closed since is skipped. `false` leaves
1246
+ * every `collapsed` write and all focus handling to the app.
1247
+ */
1248
+ dismissOverlays?: boolean;
1249
+ /**
1250
+ * Keep overlays exclusive (default `true`), like `wireSidebar`'s
1251
+ * `exclusiveCompact`: when a panel opens while it presents as an overlay,
1252
+ * every other open overlay panel — rails and the bottom drawer alike —
1253
+ * closes, so one overlay never covers another's controls. Panels presenting
1254
+ * inline are never closed by it. Applies only with `dismissOverlays`.
1255
+ */
1256
+ exclusiveOverlays?: boolean;
1257
+ }
1258
+ /**
1259
+ * Wire a `Workbench`'s panels. For `resizable` panels given a `size` signal:
1260
+ * pointer drags and arrow / Shift+arrow / Home / End on each panel's
1261
+ * separator, clamped to the panel's limits and to the room that leaves the
1262
+ * work area its minimum width and height, committed to the app-owned size signals.
1263
+ * Optional persistence loads and saves each size; optional `deviceClass`
1264
+ * suspends resizing on compact classes. Collapse stays the app's `collapsed`
1265
+ * flag and never changes a size. For panels given a `collapsed` signal,
1266
+ * overlays are transient (`dismissOverlays`): a responsive overlay starts
1267
+ * collapsed, an open overlay takes focus and keeps Tab inside it, it closes
1268
+ * on Escape or an outside press, and
1269
+ * opening one overlay closes the others (`exclusiveOverlays`). Returns a disposer. See `docs/23-app-layouts.md` §3.3.
1088
1270
  */
1089
- declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, className, slot, }: WorkbenchProps): SafeHtml;
1271
+ declare function wireWorkbench(root: HTMLElement, { id, panels, deviceClass, storage, step, largeStep, onResize, dismissOverlays, exclusiveOverlays, }: WireWorkbenchOptions): () => void;
1090
1272
 
1091
- export { Workbench, type WorkbenchPanel, type WorkbenchProps };
1273
+ export { type WireWorkbenchOptions, type WireWorkbenchPanel, type WorkbenchPanelKey, type WorkbenchResize, type WorkbenchStorage, wireWorkbench };
1092
1274
  ```
1093
1275
 
1094
1276
  ## `@kerfjs/ui/collapsible-panel`
@@ -1153,7 +1335,7 @@ interface CollapsiblePanelProps {
1153
1335
  collapseMotion?: ResizableRegionCollapseMotion;
1154
1336
  contentOverflow?: ResizableRegionContentOverflow;
1155
1337
  presentation?: ResizableRegionPresentation;
1156
- /** Control shown in a safe-area-aware viewport corner while collapsed. */
1338
+ /** Control shown while collapsed, in a safe-area-aware corner of the panel's container (not the viewport). */
1157
1339
  restoreControl?: SafeHtml;
1158
1340
  restorePosition?: ResizableRegionRestorePosition;
1159
1341
  className?: string;
@@ -1165,7 +1347,11 @@ interface CollapsiblePanelProps {
1165
1347
  * slides out via `transform` (one reflow, composited — the same technique
1166
1348
  * `Workbench` and the catalog sidebar use). Bottom-drawer content stays anchored
1167
1349
  * to the panel's fixed bottom edge, so the track cannot move its layout origin
1168
- * underneath the transform transition. The app owns the `collapsed` signal;
1350
+ * underneath the transform transition. A collapsed panel renders `inert` (with
1351
+ * `aria-hidden`), so neither Tab, pointer, nor assistive technology reaches
1352
+ * content that has slid out of view; its `restoreControl` renders outside the
1353
+ * panel and stays reachable.
1354
+ * The app owns the `collapsed` signal;
1169
1355
  * pair it with `wireSidebar` for the toggle, focus, compact-overlay, keyboard,
1170
1356
  * and persistence semantics, and with `CollapsiblePanelToggle` for the standard
1171
1357
  * affordance. See `ui/docs/collapsible-panel.md` and `docs/23-app-layouts.md`.
@@ -1319,6 +1505,9 @@ interface ResizableRegionProps {
1319
1505
  max: number;
1320
1506
  axis?: ResizableRegionAxis;
1321
1507
  edge?: ResizableRegionEdge;
1508
+ /** Snap the track to zero. The region renders `inert` and `aria-hidden`
1509
+ * while collapsed, so neither Tab nor assistive technology reaches it or
1510
+ * lands on an empty landmark; `restoreControl` renders outside the region. */
1322
1511
  collapsed?: boolean;
1323
1512
  transitioning?: boolean;
1324
1513
  /** Whether the separator line is painted. The resize hit target remains available. */
@@ -1331,7 +1520,7 @@ interface ResizableRegionProps {
1331
1520
  presentation?: ResizableRegionPresentation;
1332
1521
  /** Always-available control rendered while collapsed, outside the clipped region. */
1333
1522
  restoreControl?: SafeHtml;
1334
- /** Safe-area-aware viewport corner for `restoreControl`. */
1523
+ /** Safe-area-aware corner of the region's container (not the viewport) for `restoreControl`. */
1335
1524
  restorePosition?: ResizableRegionRestorePosition;
1336
1525
  /** Fill the available inline track and hide the separator below a container breakpoint. */
1337
1526
  responsiveFillAt?: ResizableRegionResponsiveFillAt;
@@ -1360,8 +1549,9 @@ interface WireResizableRegionsOptions {
1360
1549
  onPreview?: (change: ResizeCommit) => void;
1361
1550
  onCommit: (change: ResizeCommit) => void;
1362
1551
  }
1552
+
1363
1553
  /** Wire pointer and separator-keyboard behavior for every ResizableRegion below root. */
1364
- declare function wireResizableRegions(root: HTMLElement, { step, largeStep, onPreview, onCommit, }: WireResizableRegionsOptions): () => void;
1554
+ declare function wireResizableRegions(root: HTMLElement, options: WireResizableRegionsOptions): () => void;
1365
1555
 
1366
1556
  export { type ResizeCommit, type WireResizableRegionsOptions, wireResizableRegions };
1367
1557
  ```
@@ -1641,7 +1831,7 @@ interface SegmentedControlProps<Value extends string = string> {
1641
1831
  size?: SegmentedControlSize;
1642
1832
  layout?: SegmentedControlLayout;
1643
1833
  className?: string;
1644
- /** Render as an unanimated loading skeleton, disabling every segment. */
1834
+ /** Render as a loading placeholder: every choice keeps its live label or icon, none is selected, and every segment is disabled. */
1645
1835
  placeholder?: boolean;
1646
1836
  /** Native named-slot assignment when composed inside a web component. */
1647
1837
  slot?: string;
@@ -1740,7 +1930,7 @@ interface StateBannerProps {
1740
1930
  tone?: StateBannerTone;
1741
1931
  urgency?: StateBannerUrgency;
1742
1932
  className?: string;
1743
- /** Render the title, badge, and detail as unanimated loading skeletons, keeping the icon and tone. */
1933
+ /** Render the title, badge, and detail as unanimated loading skeletons, keeping the icon and tone. The detail line appears only when `detail` is set, as in the live banner. */
1744
1934
  placeholder?: boolean;
1745
1935
  /** Native named-slot assignment when composed inside a web component. */
1746
1936
  slot?: string;
package/ai/skill.md CHANGED
@@ -19,7 +19,12 @@ documented dynamic-data widening. Do not extend those diagnostics to child
19
19
  markup, live DOM relationships, disposer invocation, or other conditions that
20
20
  TypeScript cannot prove from one call.
21
21
  Read `../docs/webawesome-theme.md` when using Web Awesome components or changing
22
- shared theme tokens.
22
+ shared theme tokens. When overriding a loud fill
23
+ (`--wa-color-{tone}-fill-loud`), set the matching `--wa-color-{tone}-on-loud`
24
+ in the same selector and at-rule scope with a foreground that clears 4.5:1;
25
+ the analyzer reports an override with no governing `on-loud` (same or ancestor
26
+ scope, in any stylesheet the entry loads), or a literal pair below 4.5:1, as
27
+ review finding `KUI-L018`.
23
28
 
24
29
  Before choosing or aligning components, look for a consumer-owned catalog that
25
30
  conforms to `./component-catalog-extension.schema.json`. Treat its entries as a
@@ -83,21 +88,21 @@ Choose from the need, not from visual resemblance:
83
88
 
84
89
  Quick routing:
85
90
 
86
- | Need | Choose | Nearest alternatives / boundary |
87
- | ------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
88
- | Navigation row | `ListItem` | Use an ordinary link or button when sidebar/menu anatomy and state do not apply. Its rounded root clips overflowing descendants; set `multiline` when the primary label should wrap. Multiline leading icons stay aligned with the first text line. |
89
- | Horizontal flex layout | `Row` | Use `hAlign`/`vAlign`, a typed `gap`, and opt-in `wrap`; use `List` when the main axis is vertical. |
90
- | Equal-width columns | `Grid` | Use a positive fixed `columns` count plus typed `gap`/`flex`. The app owns responsive count changes; use CSS grid for asymmetric/intrinsic tracks and `ResizableRegion` for adjustable boundaries. |
91
- | Navigation row with a trailing action | `ListActionRow` | Use `ListItem` when the trailing region is dormant; never put controls inside either component's SafeHtml slots. Multiline leading icons stay aligned with the first text line. |
92
- | Page, panel, or dialog heading | `Toolbar` + `ToolbarText` | Use a direct extra-large `ToolbarText`, optional grouped icon, grouped trailing controls, and app-owned supporting copy below. |
93
- | Exclusive choice | `TabBar`, `SegmentedControl`, or `Select` | Tabs switch tabpanels; segments expose a few choices; Select handles a longer value list. |
94
- | Status or absent content | `StateBanner` or `EmptyState` | `StateBanner` accepts an optional terse, tone-tinted `badge` beside its title for a count or compact status; do not rely on color alone. Web Awesome callouts suit ecosystem content; toasts are transient and never the only record of important state. |
95
- | Compact metadata | `Badge` | Configure status, count, category, or metadata presentation with tone, appearance, shape, and size props. Do not build badges with application CSS or use them as interactive controls. |
96
- | Loading placeholder | a component's `placeholder` prop, or `Skeleton` | Set `placeholder` on a value-bearing component (`Select`, `ListHeader`, `ListItem`, `ValueTableRow`, `SegmentedControl`, `StateBanner`, `AppTab`, `ToolbarText`, `ListActionRow`) to render its real chrome with value slots as subtle **unanimated** skeletons and interactivity disabled — do not hand-rebuild an empty state. `Skeleton` is the standalone block for a custom slot. A known `AppTab` that is still opening takes `pending` instead: it keeps its name (quiet, and as its accessible name), stays dormant with the same geometry, and shows your `trailing` spinner. Use `LoadingSpinner` for known busy activity, not a skeleton; never animate a skeleton; prefer it over `wa-skeleton`. |
97
- | Adjustable or asymmetric columns | `ResizableRegion` or CSS grid | Use `ResizableRegion` only for a user-operable controlled separator, CSS grid for asymmetric/intrinsic tracks, and `Grid` for static equal-width tracks. |
98
- | Structured or ordinary search | `TokenSearchField` or an input | Use token search only when text and ordered filter chips form one editor. |
99
- | Key/value facts | `ValueTable` with `ValueTableRow` | Use rows with `icon` only when the optional 24px leading visual adds context. |
100
- | Command search | Application-local semantics using shared layout vocabulary | No runtime component or production recipe is exported; the app owns behavior, policy, and copy. |
91
+ | Need | Choose | Nearest alternatives / boundary |
92
+ | ------------------------------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
93
+ | Navigation row | `ListItem` | Use an ordinary link or button when sidebar/menu anatomy and state do not apply. Its rounded root clips overflowing descendants; set `multiline` when the primary label should wrap. Multiline leading icons stay aligned with the first text line. |
94
+ | Horizontal flex layout | `Row` | Use `hAlign`/`vAlign`, a typed `gap`, and opt-in `wrap`; use `List` when the main axis is vertical. |
95
+ | Equal-width columns | `Grid` | Use a positive fixed `columns` count plus typed `gap`/`flex`. The app owns responsive count changes; use CSS grid for asymmetric/intrinsic tracks and `ResizableRegion` for adjustable boundaries. |
96
+ | Navigation row with a trailing action | `ListActionRow` | Use `ListItem` when the trailing region is dormant; never put controls inside either component's SafeHtml slots. Multiline leading icons stay aligned with the first text line. |
97
+ | Page, panel, or dialog heading | `Toolbar` + `ToolbarText` | Use a direct extra-large `ToolbarText`, optional grouped icon, grouped trailing controls, and app-owned supporting copy below. |
98
+ | Exclusive choice | `TabBar`, `SegmentedControl`, or `Select` | Tabs switch tabpanels; segments expose a few choices; Select handles a longer value list. |
99
+ | Status or absent content | `StateBanner` or `EmptyState` | `StateBanner` accepts an optional terse, tone-tinted `badge` beside its title for a count or compact status; do not rely on color alone. Web Awesome callouts suit ecosystem content; toasts are transient and never the only record of important state. |
100
+ | Compact metadata | `Badge` | Configure status, count, category, or metadata presentation with tone, appearance, shape, and size props. Do not build badges with application CSS or use them as interactive controls. |
101
+ | Loading placeholder | a component's `placeholder` prop, or `Skeleton` | Set `placeholder` on a value-bearing component (`Select`, `ListHeader`, `ListItem`, `ValueTableRow`, `SegmentedControl`, `StateBanner`, `AppTab`, `ToolbarText`, `ListActionRow`) to render its real chrome with value slots as subtle **unanimated** skeletons and interactivity disabled; only unknown values change, so a placeholder is never dimmed or styled as disabled (a control whose own `disabled` / `trailingActionDisabled` / choice `disabled` / `actionDisabled` prop is set keeps its live disabled tone via the reserved `data-kui-disabled` marker) — do not hand-rebuild an empty state. Component-drawn chrome stays live too: a `ListItem` or `ListActionRow` placeholder keeps its `busy` spinner while its `status` text (and a `ListItem`'s `trailing` content, always a value) becomes a skeleton. `Skeleton` is the standalone block for a custom slot. A known `AppTab` that is still opening takes `pending` instead: it keeps its name (quiet, and as its accessible name) and the same geometry, shows your `trailing` spinner, and stays selectable (close and drag disabled) so your panel can show placeholders until it loads. Use `LoadingSpinner` for known busy activity, not a skeleton; never animate a skeleton; prefer it over `wa-skeleton`. |
102
+ | Adjustable or asymmetric columns | `ResizableRegion` or CSS grid | Use `ResizableRegion` only for a user-operable controlled separator, CSS grid for asymmetric/intrinsic tracks, and `Grid` for static equal-width tracks. |
103
+ | Structured or ordinary search | `TokenSearchField` or an input | Use token search only when text and ordered filter chips form one editor. |
104
+ | Key/value facts | `ValueTable` with `ValueTableRow` | Use rows with `icon` only when the optional 24px leading visual adds context. |
105
+ | Command search | Application-local semantics using shared layout vocabulary | No runtime component or production recipe is exported; the app owns behavior, policy, and copy. |
101
106
 
102
107
  For a complete shell, sidebar, workspace header, list-detail dialog, composer
103
108
  form, list-state lifecycle, or mixed-control toolbar, start from the runnable
@@ -153,7 +158,7 @@ Hard rules:
153
158
  19. Compose `ValueTable` from typed `ValueTableRow` entries instead of handwritten `dt`/`dd` wrappers. Pass `icon` for the optional 24px leading visual; the row owns 8px of root-scaled top and bottom padding, the 8px iconless or 40px icon-bearing separator start, and the common 8px right inset.
154
159
  20. Use the standard direct `Toolbar` composition for a panel, dialog, or page heading, not a private wrapper or custom heading row. Put an optional icon in a `ToolbarControlGroup`, the title directly in the leading zone as extra-large `ToolbarText`, and actions in a trailing group. Set `headingLevel` for page/section landmarks. Supporting copy stays below the toolbar as app-owned content. The app owns modal behavior, focus, dismissal, command policy, ids, and action handling.
155
160
  21. Space with the official five-step scale, picked by how connected two elements are — not by eye. `0` `--kui-space-none` = no separation (one unit); `4px` `--kui-space-2xs` = very minor air on a connected cluster; `8px` `--kui-space-xs` = standard, between elements within a group; `16px` `--kui-space-m` = minor, between homogeneous groups; `24px` `--kui-space-l` = major, between heterogeneous groups (the `.kui-content` rhythm). The 8px-vs-24px distinction is inside-a-group vs between-major-differing-regions. `--kui-space-s` (12px) and `--kui-space-xl` (32px) are off-scale exceptions. For `Row.gap`, `Grid.gap`, or `List.gap`, pass a finite shorthand such as `gap="xs"` or `gap="m"`; use `Spacer` for one intentional typed empty width/height or a flexible main-axis gap. When one token is insufficient, import typed builders from the CSS-free `@kerfjs/ui/css-values` subpath. Preserve property grammar: lengths for gap/Spacer/Skeleton geometry, `flex()` for Row/Grid/List flex, and a foreground color for choice icons: `uiColor()` with a `*-on-*` or text token (`success-on-quiet`, `text-quiet`) or `foregroundColorVar()`. The bare `success`/`warning`/`danger`/`pop`/`accent` tokens are quiet fill aliases, not foregrounds, and `SelectChoice.color` rejects them. Never cast between brands or emit raw row `style` declarations. `plus` is not standalone and must be wrapped in `calc`; `remify()` remains source-CSS syntax and is never a runtime prop value. See `docs/layout.md` "Spacing scale".
156
- 22. Pick a whole-screen layout from the opt-in, tree-shakeable subpaths by data + interaction + device, and derive responsiveness from `@kerfjs/ui/device-class`'s `deviceClass()` (`compact` = handset or portrait tablet = one pane at a time). Simple/flat → `NavStack` with one entry (single pane), plus `TabScaffold` (`@kerfjs/ui/tab-scaffold`, iOS bottom tabs, each tab its own `NavStack`) for 2–5 co-equal sections on `compact`. Drill-down → `NavStack` (`@kerfjs/ui/nav-stack`), upgrading to `SplitView` (`@kerfjs/ui/split-view`, list-detail) once both panes fit (`atLeast('tablet')` landscape); `SplitView` collapses to a `NavStack` on `compact`. Keep each stack's ordered views controlled; put view-specific bottom chrome on `NavStackView.bottomToolbar` (or use the component-level persistent fallback), then call `wireNavStack` so content slides while the active view's top and bottom chrome cross-fade and focus moves into the new top view/restores on pop. Mark a preferred initial heading or control with `data-nav-focus` when DOM order is not sufficient. Complex tool with peripheral panels → `Workbench` (`@kerfjs/ui/workbench`, collapsible rails + drawer) `atLeast('desktop')`, degrading to `NavStack`/overlays below. These are declarative (the app owns the stack/selection/collapsed/active state as signals) with disposer-returning `wire…` helpers; each ships a companion CSS import and stays out of the barrel. For a standalone collapsible rail or bottom drawer outside a full shell, use `CollapsiblePanel` + `CollapsiblePanelToggle` (`@kerfjs/ui/collapsible-panel`) with `wireSidebar` (`@kerfjs/ui/wire-sidebar`): the standard collapse animation and per-side icon convention (`PanelLeft*`/`PanelRight*`/`PanelBottom*`) plus toggle, focus, compact overlay, and persistence semantics. A compact overlay opens only on a user action: wire-up on a compact device and a wide-to-compact crossing collapse every panel, and a wide crossing restores the inline state. Seed the signal with `signal(device.value.compact)` and pass the panel's `inlineCollapsed` default so the first compact render has no overlay. Dialogs pick the same inner layout, then present per device class (full-screen modal on `compact`, inline on desktop). A top-level percentage-height shell needs a definite containing height: opt into `@kerfjs/ui/document.css` and put `.kui-app-root` on the one direct mount container, or provide equivalent application-owned geometry. The global baseline is deliberately absent from `styles.css` and component imports. See `docs/app-layouts.md`, `docs/document-baseline.md`, and `docs/collapsible-panel.md`.
161
+ 22. Pick a whole-screen layout from the opt-in, tree-shakeable subpaths by data + interaction + device, and derive responsiveness from `@kerfjs/ui/device-class`'s `deviceClass()` (`compact` = handset or portrait tablet = one pane at a time). Simple/flat → `NavStack` with one entry (single pane), plus `TabScaffold` (`@kerfjs/ui/tab-scaffold`, iOS bottom tabs, each tab its own `NavStack`) for 2–5 co-equal sections on `compact`. Drill-down → `NavStack` (`@kerfjs/ui/nav-stack`), upgrading to `SplitView` (`@kerfjs/ui/split-view`, list-detail) once both panes fit (`atLeast('tablet')` landscape); `SplitView` collapses to a `NavStack` on `compact`. Keep each stack's ordered views controlled; put view-specific bottom chrome on `NavStackView.bottomToolbar` (or use the component-level persistent fallback), then call `wireNavStack` so content slides while the active view's top and bottom chrome cross-fade and focus moves into the new top view/restores on pop. Mark a preferred initial heading or control with `data-nav-focus` when DOM order is not sufficient. Complex tool with peripheral panels → `Workbench` (`@kerfjs/ui/workbench`, collapsible rails + drawer) `atLeast('desktop')`, degrading to `NavStack`/overlays below. Workbench panels are fixed-size unless a panel opts in with `resizable` (`true` or `{ min, max }`); then render `size={sizeSignal.value}` and call `wireWorkbench(root, { id, panels: { leftRail: { size: sizeSignal, storageKey? } }, deviceClass })` from `@kerfjs/ui/wire-workbench` for pointer drags, arrow/Shift+arrow/Home/End on the separator, persistence, and no resizing on `compact` classes. Inline rails, fixed or resizable, leave the work area `mainMinSize` px of width (a `Workbench` prop, default 320, `0` off), and an inline bottom drawer leaves it `mainMinHeight` px of height (default 120, `0` off): resizable panels stop growing there, rails shrink in proportion when the Workbench narrows and the drawer when it gets shorter, so do not add your own main-area `min-width` / `min-height`. Give a rail or the bottom drawer `responsiveOverlayAt: "narrow"` (or `"compact"`) to present it as an overlay below that Workbench container breakpoint (704px / 448px, as `ResizableRegion`'s `responsiveFillAt`) instead of deriving `presentation` from `deviceClass`; overlays never resize. Pass each such panel's `collapsed` signal to `wireWorkbench` too (`panels: { leftRail: { collapsed: navCollapsed } }`, `size` optional) so the overlay is transient like `wireSidebar`'s compact overlay: it collapses when the breakpoint begins to apply (the inline state returns when it ends), takes focus on its first control and keeps Tab inside it while open, and closes on Escape or an outside press with focus returned to its toggle (give each toggle `aria-controls` naming its panel's derived id, `<workbench id>-left-rail` / `-right-rail` / `-bottom-drawer`, so a panel open at wire-up has a focus target too); opening one overlay closes the other open overlays, so they never cover each other's controls; do not hand-roll those listeners (`dismissOverlays: false` opts out of all of it, `exclusiveOverlays: false` of exclusivity only). Collapse stays the app's `collapsed` flag and keeps the size, so an expanded panel returns at its last size. A collapsed panel renders `inert` and `aria-hidden` by itself, on the rail/drawer and its content (its `restoreControl` stays outside and reachable), so do not add your own `tabindex`, `aria-hidden`, or `inert` to hide it. These are declarative (the app owns the stack/selection/collapsed/active state as signals) with disposer-returning `wire…` helpers; each ships a companion CSS import and stays out of the barrel. For a standalone collapsible rail or bottom drawer outside a full shell, use `CollapsiblePanel` + `CollapsiblePanelToggle` (`@kerfjs/ui/collapsible-panel`) with `wireSidebar` (`@kerfjs/ui/wire-sidebar`): the standard collapse animation and per-side icon convention (`PanelLeft*`/`PanelRight*`/`PanelBottom*`) plus toggle, focus, compact overlay, and persistence semantics. A collapsed `CollapsiblePanel`, `ResizableRegion`, or Workbench panel renders `inert` and `aria-hidden` by itself, so do not hide collapsed content yourself either. A compact overlay opens only on a user action: wire-up on a compact device and a wide-to-compact crossing collapse every panel, and a wide crossing restores the inline state. Seed the signal with `signal(device.value.compact)` and pass the panel's `inlineCollapsed` default so the first compact render has no overlay. Dialogs pick the same inner layout, then present per device class (full-screen modal on `compact`, inline on desktop). A top-level percentage-height shell needs a definite containing height: opt into `@kerfjs/ui/document.css` and put `.kui-app-root` on the one direct mount container, or provide equivalent application-owned geometry. The global baseline is deliberately absent from `styles.css` and component imports. See `docs/app-layouts.md`, `docs/document-baseline.md`, and `docs/collapsible-panel.md`.
157
162
  23. Layouts and `Pane` are safe-area aware without configuration: backgrounds, separators, and dividers paint through a device's unsafe areas (a collapsible rail or drawer grows by the inset it docks to), content is padded only on the sides a region actually reaches, and block insets are scroll padding inside the scroller so first/last content can always be scrolled clear. Interior edges get nothing, and a collapsed rail hands its edge back to the center. Never add your own `env(safe-area-inset-*)` padding around a kerf layout or pane — that double-insets. In an app-owned arrangement of panes, pass `Pane.safeAreaEdges` (the sides that pane reaches; `[]` opts out); give an app bar or bottom bar that sits at a screen edge outside a Pane header or footer `Toolbar.safeAreaEdges` for the sides it reaches (for example `['block-start', 'inline-start', 'inline-end']`); or set `--kui-edge-inset-*: 0px` on a region for each edge it does not reach. The device insets are `--kui-safe-area-*` (defaulting to `env()`), overridable on `:root`. See `docs/app-layouts.md` "Safe areas".
158
163
 
159
164
  Common mistakes:
@@ -180,6 +185,7 @@ Common mistakes:
180
185
  | Invent an `@kerfjs/ui` command-palette export | Keep the semantics application-local, reuse public layout vocabulary, and propose recurring behavior upstream |
181
186
  | Handwrite `ValueTable` row wrappers or compensate their padding or separators | Compose `ValueTableRow`; its root-scaled block padding, optional icon hook, and separator geometry are public contract |
182
187
  | Rebuild a panel/dialog heading with bespoke CSS or a private wrapper | Compose a plain `Toolbar` with a direct xl `ToolbarText`, optional grouped icon, grouped trailing controls, and app-owned supporting copy below |
188
+ | Put an xl `ToolbarText` in a narrow rail or drawer header | Use the default size there as pane identity; xl truncates at the minimum size ("Ins…" in a 160px rail) |
183
189
  | Force a `width`/`height`/`padding` on a component to size or space it | Let it size to its content and tokens; a forced box leaves a halo or a stretched oval — adjust an icon-size or spacing token, not the box |
184
190
  | Wrap a component or region in a card, border, backdrop, or outline to "contain" it | Let it sit on the surface; add a `.kui-content-item` only for a real distinction — hierarchy comes from alignment, spacing, and type first |
185
191
  | Add another container's padding on top of a content-item's own margin | Pick one owner of the inset; a pane has no padding and its `.kui-content` children own the 8/1/8 geometry — stacking them double-insets |
@@ -1,6 +1,6 @@
1
1
  # Web Awesome JSX signatures for the UI authoring corpus
2
2
 
3
- Generated from the emitted `@kerfjs/ui@5.0.0-beta.52` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
3
+ Generated from the emitted `@kerfjs/ui@5.0.0-beta.54` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
4
4
 
5
5
  ```ts
6
6
  import { KerfCustomElement } from 'kerfjs/jsx-runtime';