@kerfjs/ui 4.4.1 → 5.0.0-beta.3

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 (158) hide show
  1. package/README.md +231 -5
  2. package/ai/component-catalog.json +4467 -0
  3. package/ai/component-catalog.schema.json +79 -0
  4. package/ai/public-api-signatures-v1.md +619 -0
  5. package/ai/skill.md +64 -11
  6. package/ai/webawesome-jsx-signatures-v1.md +106 -0
  7. package/dist/app-tab.d.ts +12 -2
  8. package/dist/app-tab.js +2 -1
  9. package/dist/browser/app-tab.js +2 -2
  10. package/dist/browser/dialog-header.js +4 -2
  11. package/dist/browser/disclosure-arrow.js +4 -0
  12. package/dist/browser/empty-state.js +3 -3
  13. package/dist/browser/loading-spinner.js +2 -2
  14. package/dist/browser/lucide-icon.js +2 -2
  15. package/dist/browser/menu-action-row.js +3 -0
  16. package/dist/browser/menu-header.js +4 -2
  17. package/dist/browser/menu-item.js +2 -2
  18. package/dist/browser/page-header.js +2 -2
  19. package/dist/browser/resizable-region.js +2 -2
  20. package/dist/browser/segmented-control.js +2 -2
  21. package/dist/browser/select.js +3 -3
  22. package/dist/browser/state-banner.js +2 -2
  23. package/dist/browser/tab-bar.js +2 -2
  24. package/dist/browser/token-search-field.js +4 -0
  25. package/dist/browser/toolbar-control-group.js +2 -2
  26. package/dist/browser/toolbar-text.js +2 -2
  27. package/dist/browser/toolbar.js +2 -2
  28. package/dist/browser/value-table.js +2 -2
  29. package/dist/chunk-56RJXPHG.js +43 -0
  30. package/dist/chunk-56RJXPHG.js.map +1 -0
  31. package/dist/chunk-7BR3DO2A.js +31 -0
  32. package/dist/chunk-7BR3DO2A.js.map +1 -0
  33. package/dist/chunk-7LDCTU7D.js +134 -0
  34. package/dist/chunk-7LDCTU7D.js.map +1 -0
  35. package/dist/{chunk-4HHCYSGT.js → chunk-AQNKJRFH.js} +2 -2
  36. package/dist/{chunk-4HHCYSGT.js.map → chunk-AQNKJRFH.js.map} +1 -1
  37. package/dist/{chunk-NHXX4JI5.js → chunk-CTQKI2XL.js} +3 -3
  38. package/dist/chunk-CTQKI2XL.js.map +1 -0
  39. package/dist/chunk-EGHL5S4P.js +33 -0
  40. package/dist/chunk-EGHL5S4P.js.map +1 -0
  41. package/dist/chunk-H5AGGVU5.js +75 -0
  42. package/dist/chunk-H5AGGVU5.js.map +1 -0
  43. package/dist/chunk-K3G72I6D.js +24 -0
  44. package/dist/chunk-K3G72I6D.js.map +1 -0
  45. package/dist/{chunk-3O7ZEUW2.js → chunk-M5JRI6MN.js} +3 -3
  46. package/dist/chunk-M5JRI6MN.js.map +1 -0
  47. package/dist/chunk-NPPD2A6O.js +40 -0
  48. package/dist/chunk-NPPD2A6O.js.map +1 -0
  49. package/dist/chunk-R6JHHBKQ.js +19 -0
  50. package/dist/chunk-R6JHHBKQ.js.map +1 -0
  51. package/dist/chunk-S3TPM2LP.js +23 -0
  52. package/dist/chunk-S3TPM2LP.js.map +1 -0
  53. package/dist/{chunk-T5XNMVOI.js → chunk-XZXRLY5Q.js} +14 -5
  54. package/dist/chunk-XZXRLY5Q.js.map +1 -0
  55. package/dist/dialog-header.d.ts +3 -2
  56. package/dist/dialog-header.js +3 -1
  57. package/dist/disclosure-arrow.d.ts +14 -0
  58. package/dist/disclosure-arrow.js +4 -0
  59. package/dist/disclosure-arrow.js.map +1 -0
  60. package/dist/index.d.ts +5 -1
  61. package/dist/index.js +16 -11
  62. package/dist/menu-action-row.d.ts +50 -0
  63. package/dist/menu-action-row.js +4 -0
  64. package/dist/menu-action-row.js.map +1 -0
  65. package/dist/menu-header.d.ts +28 -2
  66. package/dist/menu-header.js +4 -1
  67. package/dist/menu-item.d.ts +10 -1
  68. package/dist/menu-item.js +2 -1
  69. package/dist/resizable-region.d.ts +3 -1
  70. package/dist/resizable-region.js +1 -1
  71. package/dist/select.js +1 -1
  72. package/dist/styles/app-tab.css +152 -0
  73. package/dist/styles/dialog-header.css +87 -0
  74. package/dist/styles/disclosure-arrow.css +23 -0
  75. package/dist/styles/empty-state.css +47 -0
  76. package/dist/styles/foundation.css +326 -0
  77. package/dist/styles/layout.css +73 -0
  78. package/dist/styles/loading-spinner.css +21 -0
  79. package/dist/styles/lucide-icon.css +6 -0
  80. package/dist/styles/menu-action-row.css +154 -0
  81. package/dist/styles/menu-header.css +143 -0
  82. package/dist/styles/menu-item.css +118 -0
  83. package/dist/styles/page-header.css +26 -0
  84. package/dist/styles/resizable-region.css +111 -0
  85. package/{src → dist/styles}/segmented-control.css +62 -31
  86. package/dist/styles/select.css +96 -0
  87. package/dist/styles/state-banner.css +173 -0
  88. package/dist/styles/styles.css +22 -0
  89. package/dist/styles/tab-bar.css +45 -0
  90. package/dist/styles/token-search-field.css +281 -0
  91. package/dist/styles/toolbar-control-group.css +161 -0
  92. package/dist/styles/toolbar-text.css +29 -0
  93. package/dist/styles/toolbar.css +52 -0
  94. package/dist/styles/value-table.css +75 -0
  95. package/{src → dist/styles}/webawesome.css +119 -29
  96. package/dist/token-search-field.d.ts +43 -0
  97. package/dist/token-search-field.js +4 -0
  98. package/dist/token-search-field.js.map +1 -0
  99. package/dist/value-table.d.ts +10 -2
  100. package/dist/value-table.js +1 -1
  101. package/dist/webawesome.d.ts +100 -0
  102. package/dist/webawesome.js +3 -0
  103. package/dist/webawesome.js.map +1 -0
  104. package/dist/wire-resizable-regions.js +1 -1
  105. package/dist/wire-token-search-fields.d.ts +11 -0
  106. package/dist/wire-token-search-fields.js +5 -0
  107. package/dist/wire-token-search-fields.js.map +1 -0
  108. package/docs/accessibility.md +60 -3
  109. package/docs/component-contract.md +128 -3
  110. package/docs/component-selection.md +133 -0
  111. package/docs/design-philosophy.md +38 -0
  112. package/docs/examples/command-palette-adapter.tsx +70 -0
  113. package/docs/layout.md +95 -0
  114. package/docs/recipes.md +116 -0
  115. package/docs/ux-demo.md +17 -5
  116. package/docs/webawesome-theme.md +15 -0
  117. package/llms.txt +25 -2
  118. package/package.json +70 -27
  119. package/ux-demo/recipes/app-shell.tsx +32 -0
  120. package/ux-demo/recipes/compact-toolbar.tsx +43 -0
  121. package/ux-demo/recipes/composer-form.tsx +62 -0
  122. package/ux-demo/recipes/list-workspace-states.tsx +26 -0
  123. package/ux-demo/recipes/loaders.ts +27 -0
  124. package/ux-demo/recipes/master-detail-dialog.tsx +45 -0
  125. package/ux-demo/recipes/mount-recipe.ts +38 -0
  126. package/ux-demo/recipes/navigation-sidebar.tsx +26 -0
  127. package/ux-demo/recipes/recipes.css +328 -0
  128. package/ux-demo/recipes/types.ts +11 -0
  129. package/ux-demo/recipes/workspace-header.tsx +12 -0
  130. package/dist/chunk-3O7ZEUW2.js.map +0 -1
  131. package/dist/chunk-723YNJBH.js +0 -17
  132. package/dist/chunk-723YNJBH.js.map +0 -1
  133. package/dist/chunk-NHXX4JI5.js.map +0 -1
  134. package/dist/chunk-OPCJRCYO.js +0 -19
  135. package/dist/chunk-OPCJRCYO.js.map +0 -1
  136. package/dist/chunk-T5XNMVOI.js.map +0 -1
  137. package/dist/chunk-VKH2JLDZ.js +0 -10
  138. package/dist/chunk-VKH2JLDZ.js.map +0 -1
  139. package/dist/chunk-WY4CAEPG.js +0 -14
  140. package/dist/chunk-WY4CAEPG.js.map +0 -1
  141. package/src/app-tab.css +0 -20
  142. package/src/dialog-header.css +0 -9
  143. package/src/empty-state.css +0 -6
  144. package/src/foundation.css +0 -106
  145. package/src/loading-spinner.css +0 -4
  146. package/src/lucide-icon.css +0 -1
  147. package/src/menu-header.css +0 -13
  148. package/src/menu-item.css +0 -12
  149. package/src/page-header.css +0 -3
  150. package/src/resizable-region.css +0 -19
  151. package/src/select.css +0 -18
  152. package/src/state-banner.css +0 -31
  153. package/src/styles.css +0 -18
  154. package/src/tab-bar.css +0 -4
  155. package/src/toolbar-control-group.css +0 -25
  156. package/src/toolbar-text.css +0 -4
  157. package/src/toolbar.css +0 -9
  158. package/src/value-table.css +0 -5
@@ -0,0 +1,116 @@
1
+ # Production composition recipes
2
+
3
+ These seven reference compositions sit between individual primitives and product
4
+ code. Open each stable UX-catalog route to run it, then copy the linked TSX and
5
+ CSS. The examples import public package subpaths, use semantic layout owners,
6
+ and keep state in a per-instance application adapter. They are not new
7
+ monolithic components.
8
+
9
+ Copy the recipe source together with the catalog-independent
10
+ [`mount-recipe.ts`](../ux-demo/recipes/mount-recipe.ts) adapter. It mounts the
11
+ controller at one stable application root, uses `delegateActions()` for recipe
12
+ commands, forwards form and dialog lifecycle events, wires resize commits with
13
+ the public `onCommit` callback, retains every disposer, and returns one
14
+ idempotent disposer:
15
+
16
+ ```ts
17
+ import { createRecipe } from './navigation-sidebar.js';
18
+ import { mountRecipe } from './mount-recipe.js';
19
+
20
+ const root = document.querySelector<HTMLElement>('#navigation')!;
21
+ const stopRecipe = mountRecipe(root, createRecipe(announce));
22
+ window.addEventListener('pagehide', stopRecipe, { once: true });
23
+ ```
24
+
25
+ The adapter is delivered as reference source, not a new package runtime export.
26
+ `delegate()` remains a valid alternative when an application needs selector-
27
+ specific dispatch; either way, wire once at a stable root and retain disposal.
28
+
29
+ ## Desktop application shell
30
+
31
+ [Open the recipe](../ux-demo/?component=recipe-app-shell) · [TSX source](../ux-demo/recipes/app-shell.tsx) · [shared CSS](../ux-demo/recipes/recipes.css)
32
+
33
+ Use `Toolbar`, controlled `ResizableRegion` panes, and one
34
+ `.kui-pane__content` scroll owner per pane. The recipe owns the shell topology;
35
+ the app owns routing, responsive pane visibility, sizes, persistence, and data.
36
+ Adapt only public `--kui-layout-*` and component variables.
37
+
38
+ ## Navigation sidebar
39
+
40
+ [Open the recipe](../ux-demo/?component=recipe-navigation-sidebar) · [TSX source](../ux-demo/recipes/navigation-sidebar.tsx)
41
+
42
+ One unpadded `.kui-pane` owns toolbar/content/footer structure. Its
43
+ `.kui-content` uses 24px major gaps; `MenuHeader`, `MenuItem`, and other
44
+ `.kui-content-item` children own their 8px margin, 1px border, and 8px padding.
45
+ Rows and footer toolbar groups remain 44px tall. The app owns routes,
46
+ permissions, labels, selection, valid section counts and their localized
47
+ `countLabel` phrases, non-count badge content, disclosure state, and revealed
48
+ content. A toggled `MenuHeader` supplies the production `DisclosureArrow` when
49
+ no custom `actionIcon` is needed; ordinary navigation rows stay chevron-free.
50
+
51
+ ## Workspace header
52
+
53
+ [Open the recipe](../ux-demo/?component=recipe-workspace-header) · [TSX source](../ux-demo/recipes/workspace-header.tsx)
54
+
55
+ `PageHeader` owns the page heading while one control cluster holds secondary,
56
+ overflow, and primary actions. The app owns authorization and command policy;
57
+ controls relocate without changing focus order.
58
+
59
+ ## Master-detail dialog
60
+
61
+ [Open the recipe](../ux-demo/?component=recipe-master-detail-dialog) · [TSX source](../ux-demo/recipes/master-detail-dialog.tsx)
62
+
63
+ The production Web Awesome dialog owns modal focus and Escape; the thin recipe
64
+ adapter restores the invoking control consistently after the hide event.
65
+ `DialogHeader`, `MenuHeader`, `MenuItem`, and `ValueTable` own their included
66
+ anatomy. The application owns open state, selection, dismissal policy, and
67
+ record actions. Pass the dialog action children directly; `DialogHeader` places
68
+ them in its top toolbar's action group and uses the localized `actionsLabel`.
69
+ Do not rebuild the dialog or reach into private shadow parts.
70
+
71
+ ## Composer form
72
+
73
+ [Open the recipe](../ux-demo/?component=recipe-composer-form) · [TSX source](../ux-demo/recipes/composer-form.tsx)
74
+
75
+ Production fields own labels, help, and native focus; semantic layout classes
76
+ compose one visible form surface from exactly three transparent content items:
77
+ introduction, fields, and a footer containing actions plus ownership guidance.
78
+ The three sections retain 8px margin, 1px transparent border, and 8px padding,
79
+ with 24px between major children and 8px inside each section. The app owns
80
+ validation, drafts, permissions, and transport. Persistent error or success
81
+ feedback is the only nested semantic surface and uses `StateBanner`, not a toast.
82
+ Because upgraded Web Awesome fields retain live value properties, controlled
83
+ resets synchronize both those properties and the rendered value attributes;
84
+ the Reset action also announces `Draft reset` through the catalog live region.
85
+
86
+ ## List workspace states
87
+
88
+ [Open the recipe](../ux-demo/?component=recipe-list-workspace-states) · [TSX source](../ux-demo/recipes/list-workspace-states.tsx)
89
+
90
+ The same content region moves deterministically through loading, empty,
91
+ populated, stale/background refresh, and error/retry states. The recipe owns
92
+ feedback placement; the app owns fetching, cache age, retry policy, and domain
93
+ rows.
94
+
95
+ ## Compact toolbar choices and actions
96
+
97
+ [Open the recipe](../ux-demo/?component=recipe-compact-toolbar) · [TSX source](../ux-demo/recipes/compact-toolbar.tsx)
98
+
99
+ Use `ToolbarControlGroup` for related commands, `SegmentedControl` for a few
100
+ visible exclusive choices, `Select` for a longer value list, and an ordinary
101
+ button for an independent command. The app owns values, actions, persistence,
102
+ and responsive priority.
103
+
104
+ ## Rules shared by every recipe
105
+
106
+ - Import `@kerfjs/ui/layout.css`; keep every pane unpadded and use exactly one
107
+ `.kui-pane__content` scroll owner for each real boundary.
108
+ - For direct `wa-*` JSX, import types from `@kerfjs/ui/webawesome`. Import only
109
+ individual Web Awesome registration modules and theme them with
110
+ `@kerfjs/ui/webawesome.css`.
111
+ - Prefer public props and variables at the composition boundary. When a recipe
112
+ needs responsive topology, join only classes listed in the catalog's
113
+ `publicClasses`; do not copy component markup or select descendants by tag,
114
+ id, attribute alone, or an unlisted implementation class.
115
+ - Start from the copyable mount adapter, or reproduce its complete boundary:
116
+ wire stable `data-action` hooks once and retain every disposer.
package/docs/ux-demo.md CHANGED
@@ -2,17 +2,29 @@
2
2
 
3
3
  Run `npm run dev` from `ui/`. The catalog is a development and review surface, not a second implementation.
4
4
 
5
+ Seven lazy production-composition routes live under the `Recipes` category.
6
+ Each `?component=recipe-*` route uses public package primitives, real wiring,
7
+ deterministic state, and semantic layout owners from the [recipe guide](./recipes.md).
8
+
5
9
  - It imports public component subpaths, exercising their browser-selected reachable CSS rather than a catalog-only style path.
10
+ - The `MenuActionRow` route demonstrates sibling primary/trailing native controls, independent disabled states, controlled selection, and an application-owned native popover relationship without nesting controls.
11
+ - The `AppTab` route demonstrates a runtime-filtered domain metadata slot and a decorative replacement close icon without changing TabBar selection, close, reorder, or focus ownership. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
6
12
  - Every first-party visual component and every free Web Awesome 3.12 component has its own stable `?component=<id>` URL and focused demo. Composite header, menu, feedback, and theme scenarios remain addressable alongside their subcomponents; the root opens the first Kerf component.
7
- - Catalog metadata has a unique id, category, kind (`component` or `composition`), source (`kerf` or `webawesome`), name, description, and direct `uses` dependencies.
8
- - The master/detail shell groups first-party routes by category with production `MenuHeader` and `MenuItem` components. The 70 ecosystem components live in a separate `Web Awesome` disclosure with seven subgroups, so they remain discoverable without being presented as first-class `@kerfjs/ui` exports. Direct Web Awesome routes open the disclosure and reveal the selected row at wide sizes; narrow layouts constrain the expanded list to its own scroll region.
13
+ - `ai/component-catalog.json` is the canonical catalog metadata. A deterministic checked-in projection supplies the demo's unique id, category, kind (`component`, `composition`, or `recipe`), source (`kerf` or `webawesome`), name, description, direct `uses` dependencies, existing documentation path, and demo-source path; first-party, recipe, and Web Awesome renderer maps are exhaustively typed against its generated id unions. Demo paths resolve to `ui/ux-demo/main.tsx`, the matching `ui/ux-demo/recipes/*.tsx` file, or `ui/ux-demo/webawesome-demos.tsx` according to the canonical entry. First-party component entries additionally derive `ui/src/<browser-subpath>.tsx` from their canonical `delivery.browserImport`.
14
+ - Every detail header visibly pairs `View demo source` with `Read guidance`; first-party component details also expose `View component source`. Web Awesome details say `Read Kerf integration guidance` to distinguish the local integration contract from the upstream component implementation. All links show their repository-relative path and use deploy-safe absolute GitHub blob URLs with contextual new-tab names; the demo does not depend on a local source server or add a package runtime export.
15
+ - The master/detail shell groups first-party routes by category with production `MenuHeader` and `MenuItem` components. The 70 ecosystem components live in a separate `Web Awesome` disclosure with seven subgroups, so they remain discoverable without being presented as first-class `@kerfjs/ui` exports. Direct Web Awesome routes open the disclosure and reveal the selected row at wide sizes; narrow layouts keep the expanded list in document flow rather than introducing a competing nested scroller.
16
+ - The shell itself uses `@kerfjs/ui/layout.css`: sidebars, details, previews, dialogs, and resize specimens share unpadded panes plus self-contained content items. At narrow widths, the focused `ResizableRegion` specimen moves its committed-width status below the controlled pane while retaining local horizontal access when the pane is resized wider than the stage; its split remains readable at 200% root scaling. Geometry coverage checks wide, intermediate, narrow, and 200% zoom layouts.
17
+ - The composer-form recipe demonstrates one coherent visible form surface with exactly three transparent 8/1/8 content sections and 24px major rhythm. Its conditional error or success `StateBanner` is the only nested semantic surface. Reset synchronizes the upgraded Web Awesome fields' live value properties with their empty controlled attributes and announces `Draft reset`; browser coverage preserves behavior and verifies wide, narrow, dark, validation, success, reset, 200% zoom, and forced-colors states.
18
+ - Catalog-only brand marks, preview dimensions, checkerboard tiles, and the compound display-settings control retain local geometry because they are specimen chrome or intrinsic control anatomy rather than reusable application spacing roles.
19
+ - The menu composition route demonstrates toolbar/content/footer anatomy, 24px major gaps, 8/1/8 child geometry, the full-width `MenuHeader` count-or-badge/action split, logical-end 44px action targets with 18px visuals, and 44px row/header/toolbar targets. Its one chevron is a production 18px `DisclosureArrow` inside a controlled Tools header that actually reveals and hides content; ordinary Projects navigation has no false disclosure affordance. Browser assertions cover stable naming, expanded/content transitions, visual rotation, node identity, reduced motion, RTL alignment and containment, and wide, narrow, dark, and 200% layouts. The focused MenuHeader route separately shows the shared neutral count pill for Attachments, Notes (including zero), and Duplicates plus a non-count legacy badge, full-width logical-end actions, and no disclosure without content.
20
+ - The `DisclosureArrow` route renders the production 18px root-scaled default without a catalog-only size override. Its two native buttons independently control the default right-to-down chevron and a recognizable right-facing replacement arrow with left-to-up custom directions; both use shortest-path rotation and their names stay stable while `aria-expanded` communicates state. Browser coverage verifies the custom clockwise path, pointer and native keyboard activation, focus, narrow and 200%-zoom stacking with contained labels, consumer sizing, and the independent `.5` Web Awesome glyph scale retained by Kerf `Select`.
9
21
  - A detail with relationships renders one grouped selector: `Uses` links to direct dependencies and `Used by` links to reverse dependents. Choosing either navigates to that component's demo. The footer is absent when both groups are empty; no duplicate relationship summary is rendered.
10
22
  - Demo-only CSS supplies only the catalog shell and stages; component appearance stays in package CSS. Decorative chrome and Web Awesome controls share the same semantic system palette instead of carrying a separate demo identity.
11
- - Scenarios use deterministic data and cover real variants, long content, disabled state, selection, progress, feedback, overlays, media, helpers, and stateful interactions. The Web Awesome theme route remains the broad system view, while individual action, form, layout, navigation, feedback, media, and helper routes provide focused specimens; the Animation route exposes preset, easing, duration, playback-rate, and transport controls with live lifecycle status; observer routes provide user-driven intersection, mutation, and resize events with live results; ToolbarControlGroup exposes all eight Hot Sheet 2 variants; SegmentedControl covers toolbar, rounded-rectangle, pill, equal-width, and disabled presentations; StateBanner exposes every built-in tone plus a scoped override; TabBar demonstrates overflow, edge autoscroll, and controlled pointer/keyboard reordering.
23
+ - Scenarios use deterministic data and cover real variants, long content, disabled state, selection, progress, feedback, overlays, media, helpers, and stateful interactions. The Web Awesome theme route remains the broad system view, while individual action, form, layout, navigation, feedback, media, and helper routes provide focused specimens; MenuItem and MenuHeader demonstrate typed domain metadata, protected native button/disclosure semantics, and a native popover trigger relationship without product props; DialogHeader demonstrates its top-toolbar identity/action groups, optional subtitle row, and 24px icon inside a 34px circle; the Select route verifies that option icons survive unrelated Kerf rerenders and selected slot content follows value changes; the Animation route exposes preset, easing, duration, playback-rate, and transport controls with live lifecycle status; observer routes provide user-driven intersection, mutation, and resize events with live results; ToolbarControlGroup exposes all eight Hot Sheet 2 variants, loads the Web Awesome registrations needed by its popup specimen, and preserves the upgraded dropdown's managed light-DOM children across Kerf rerenders; the Toolbar route composes a collapsible TokenSearchField inside a group, animates between the empty iconic and complete field states, stays open while populated, and focuses the revealed editor; SegmentedControl covers toolbar, rounded-rectangle, pill, equal-width, and disabled presentations; TokenSearchField covers editable ordered chips, pointer and keyboard removal, caret-preserving controlled deletion, editing, clearing, free text, disabled state, and fixed first-line alignment while content wraps; StateBanner exposes every built-in tone plus a scoped override; TabBar demonstrates overflow, edge autoscroll, and controlled pointer/keyboard reordering.
12
24
  - Settings exercise light/dark, increased contrast, and reduced motion without changing component code, and expose each preference through `aria-pressed`.
13
25
  - The action log is an `aria-live` region, making interactions visible to both people and browser tests.
14
26
  - The focused Toast route calls Web Awesome's documented programmatic stack API and notes that Hot Sheet 2 still uses its own app-level toast implementation; the catalog demonstrates ecosystem support without implying current product adoption.
15
- - Browser coverage walks every Kerf, composition, and Web Awesome route, checks every listed Web Awesome custom element is registered in its focused specimen, exercises the ecosystem disclosure and both relationship directions, and covers tab selection/reordering/overflow, semantic palettes and scoped overrides, light/dark rendering, toolbar popup behavior, menu alignment, banner urgency, keyboard resizing, live custom-element values, and representative wide/narrow captures.
16
- - The production build keeps the catalog shell and Kerf routes in the entry chunk, then dynamically imports the complete Web Awesome specimen and registration surface when an ecosystem route (or the broad Web Awesome theme route) is selected. Directly addressed ecosystem URLs pass through the same loader. `npm run demo:build` enforces a 150 kB gzip ceiling for any JavaScript chunk and a 235 kB gzip ceiling for the complete JavaScript payload, so both accidental chunk collapse and aggregate growth fail the build.
27
+ - Browser coverage walks every Kerf, composition, and Web Awesome route, checks every listed Web Awesome custom element is registered in its focused specimen, exercises the ecosystem disclosure and both relationship directions, validates offline source/guidance hrefs and accessible wide/narrow/200%-zoom wrapping, and covers tab selection/reordering/overflow, semantic palettes and scoped overrides, light/dark rendering, toolbar popup and responsive find behavior, single/multiline search alignment, menu alignment, banner urgency, keyboard resizing, live custom-element values, and representative wide/narrow captures.
28
+ - The production build keeps the catalog shell and Kerf routes in the entry chunk, then dynamically imports the complete Web Awesome specimen and registration surface when an ecosystem route, the broad Web Awesome theme route, or the ToolbarControlGroup popup specimen is selected. Directly addressed routes pass through the same loader.
17
29
 
18
30
  Before handoff, inspect the actual captures for readability, context, alignment, clipping, spacing, responsiveness, focus, and obvious defects. Fix and recapture rather than treating the screenshot as proof by itself.
@@ -4,11 +4,18 @@
4
4
  free components and the Kerf/Hot Sheet 2 design system.
5
5
 
6
6
  ```ts
7
+ import type {} from '@kerfjs/ui/webawesome';
7
8
  import '@kerfjs/ui/webawesome.css';
8
9
  import '@awesome.me/webawesome/dist/components/button/button.js';
9
10
  import '@awesome.me/webawesome/dist/components/input/input.js';
10
11
  ```
11
12
 
13
+ The type-only package import augments `kerfjs/jsx-runtime` with the 70
14
+ catalog-supported `wa-*` elements. It has no runtime side effects. The catalog
15
+ gate keeps the declaration tags aligned with Web Awesome's installed custom
16
+ elements manifest, while individual component imports remain the only
17
+ registration boundary.
18
+
12
19
  The CSS entry includes Web Awesome's native, utility, palette, and default-theme
13
20
  styles, then overrides the public semantic contract in
14
21
  `@layer wa-theme-overrides`. It does not import or register component
@@ -53,6 +60,11 @@ chevrons keep the same visual weight. The Accordion and Details components
53
60
  retain their own rotation behavior because scaling uses `transform` while
54
61
  their open state uses the independent `rotate` property.
55
62
 
63
+ That scale applies to each Web Awesome component's intrinsic glyph and is
64
+ independent of Kerf `DisclosureArrow`, whose default box is 18px relative to
65
+ the root font size and whose consumer override is
66
+ `--kui-disclosure-arrow-size`.
67
+
56
68
  Tooltip and Popover use arrowless floating surfaces by default, matching Hot
57
69
  Sheet 2. The theme sets Web Awesome's public `--wa-tooltip-arrow-size` token to
58
70
  `0px` and maps each popover's public `--arrow-size` property from
@@ -129,6 +141,9 @@ static content and labels that constraint next to the rendered output.
129
141
 
130
142
  The catalog lists every free Web Awesome component so support can be inspected;
131
143
  listing does not make each component the preferred Kerf application pattern.
144
+ The [need-first decision matrix](./component-selection.md#web-awesome-overlap-policy)
145
+ is the exhaustive Kerf/Web Awesome overlap index; the rules below explain the
146
+ highest-risk choices.
132
147
 
133
148
  - Consider `wa-popup` for low-level anchored positioning when its flip, shift,
134
149
  or placement behavior avoids custom positioning code. Prefer the higher-level
package/llms.txt CHANGED
@@ -4,6 +4,11 @@ Accessible, production-backed UI primitives for kerf applications.
4
4
 
5
5
  - [Package guide](./README.md)
6
6
  - [AI skill](./ai/skill.md)
7
+ - [Machine-readable component catalog](./ai/component-catalog.json)
8
+ - [Public API signature snapshot](./ai/public-api-signatures-v1.md)
9
+ - [Web Awesome JSX signature snapshot](./ai/webawesome-jsx-signatures-v1.md)
10
+ - [Need-first component selection matrix](./docs/component-selection.md)
11
+ - [Production composition recipes](./docs/recipes.md)
7
12
  - [Component contract](./docs/component-contract.md)
8
13
  - [Web Awesome theme contract](./docs/webawesome-theme.md)
9
14
  - [Accessibility and keyboard contracts](./docs/accessibility.md)
@@ -13,9 +18,27 @@ Accessible, production-backed UI primitives for kerf applications.
13
18
 
14
19
  The UX catalog is a category-grouped master/detail review surface with a focused route for every public visual component plus composition demos. Its collapsible ecosystem section also lists all 70 free Web Awesome 3.12 components, each with a focused themed route. Related entries use one grouped `Uses` / `Used by` selector across both sets; the shell, display preferences, and selected component share one semantic theme.
15
20
 
16
- Public components: `LucideIcon`, `Toolbar`, `ToolbarControlGroup`, `ToolbarText`, `SegmentedControl`, `MenuItem`, `MenuHeader`, `ResizableRegion`, `wireResizableRegions`, `AppTab`, `TabBar`, `wireTabBars`, `reorderTabs`, `PageHeader`, `LoadingSpinner`, `Select`, `StateBanner`, `EmptyState`, `DialogHeader`, and `ValueTable`. `SegmentedControl` is controlled and supports toolbar, rounded-rectangle, and pill presentations with native pressed buttons in sequential Tab order. Semantic state palettes are opinionated, Web Awesome-compatible, and overridable globally, by tone, or per instance.
21
+ Choose from the interface need: search the catalog, reuse a semantic match, compose existing primitives, add a thin application adapter for product concerns, and use custom markup only for a genuinely different contract. The selection matrix covers every public visual/helper export and the supported-but-not-preferred Web Awesome overlaps, including required wiring, application ownership, import subpaths, alternatives, and recipe links.
17
22
 
18
- Import visual components from explicit JavaScript subpaths. A CSS-aware browser bundler selects generated wrappers that bring in the foundation, the component CSS, and styles for reachable UI subcomponents; unrelated CSS stays out. The root barrel and `@kerfjs/ui/unstyled` are CSS-free; use `styles.css` with the root barrel only when the complete layer is intentional. Manual CSS exports remain available for custom pipelines, and application overrides follow package styles in the cascade. `@kerfjs/ui/webawesome.css` is an optional CSS-only theme for Web Awesome's free components; it includes Web Awesome's base stylesheet, registers no elements, and remains overridable through public `--wa-*` tokens. CSS, its generated browser wrappers, and `@kerfjs/ui/select/register` are the declared side effects. `kerfjs` is an external peer. The Web Awesome peer is optional and needed for its components. `wireTabBars` includes proximity-based horizontal edge autoscroll during same-bar drag reordering.
23
+ There is no `@kerfjs/ui` command-palette runtime export or production recipe. The typed application-local example at `docs/examples/command-palette-adapter.tsx` demonstrates how a missing concept can reuse public layout vocabulary without inventing a package API. Ranking, history, permissions, availability, focus policy, actions, and copy stay in the app; propose recurring behavior upstream.
24
+
25
+ AI tools that need exhaustive retrieval should load `@kerfjs/ui/ai/component-catalog.json`. It is the canonical checked-in source for stable ids, exports, purpose, selection guidance, relationships, delivery and side effects, wiring, application ownership, variants, accessibility, public CSS hooks, catalog routes, and current documentation/recipe links. Its adjacent JSON Schema describes the format.
26
+
27
+ Prefer an equivalent component prop or `--kui-*` token for customization. When composition-specific layout needs CSS, the catalog's `publicClasses` arrays are the exact supported anatomy boundary: selectors may join documented public classes, but must not reach into a component by tag, id, attribute alone, or an unlisted class.
28
+
29
+ Before emitting code, load `@kerfjs/ui/ai/public-api-signatures-v1.md` for the exact declaration signatures used by the authoring corpus. It records accepted import paths, prop names, callback shapes, and disposer return types from emitted declarations rather than prose examples.
30
+
31
+ Public components: `LucideIcon`, `DisclosureArrow`, `Toolbar`, `ToolbarControlGroup`, `ToolbarText`, `SegmentedControl`, `TokenSearchField`, `readTokenSearchField`, `placeTokenSearchCaret`, `wireTokenSearchFields`, `MenuItem`, `MenuActionRow`, `MenuHeader`, `ResizableRegion`, `wireResizableRegions`, `AppTab`, `TabBar`, `wireTabBars`, `reorderTabs`, `PageHeader`, `LoadingSpinner`, `Select`, `StateBanner`, `EmptyState`, `DialogHeader`, `ValueTable`, and `ValueTableRow`. `MenuItem`, `MenuActionRow`, `MenuHeader`, and `AppTab` accept typed, runtime-filtered application `data-*` metadata through `rootAttributes`; MenuActionRow `trailingActionAttributes` and MenuHeader `triggerAttributes` additionally support native popover target/action and `aria-controls`/`aria-haspopup`, while component-owned action, identity, selection, disclosure, naming, disabled, icon, and role semantics stay protected. `MenuHeader` uses the mutually exclusive `count` plus required localized `countLabel` pair for non-negative safe-integer section quantities, including zero; it renders the shared neutral pill and joins the spoken phrase to the owning heading or disclosure button. Never concatenate a count into `label` or put numeric content in the legacy non-count `badge` SafeHtml slot. In real toggle mode, omitting `actionIcon` supplies one production 18px `DisclosureArrow` driven by `expanded`; a custom icon replaces it, and the app must reveal matching content. Ordinary navigation must not borrow a disclosure chevron. `AppTab.closeIcon` and `ResizableRegion.handleIcon` replace decorative dormant glyphs without taking control ownership. `MenuItem.trailing` is dormant; use `MenuActionRow` for sibling primary/trailing native buttons, keeping its `label`, `icon`, and `trailingActionIcon` SafeHtml dormant too. `ValueTableRow` owns semantic `dt`/`dd` markup, 8px of root-scaled top and bottom padding, and an optional 24px leading icon; its separator begins 8px from the left without an icon or 40px with one and ends 8px from the right. `DisclosureArrow` is a decorative, controlled open/closed indicator with configurable directions, shortest-path rotation (counterclockwise for a 180-degree closed-to-open tie), replaceable right-oriented icon content, an 18px root-scaled default, and a scoped `--kui-disclosure-arrow-size` override; the owning control retains interaction and expanded-state semantics. `SegmentedControl` is controlled and supports toolbar, rounded-rectangle, and pill presentations with native pressed buttons in sequential Tab order. `Select` preserves icon-bearing option slots across Kerf rerenders, keys custom selected content by the controlled value, and separately keeps its intrinsic Web Awesome expand glyph at `--kui-disclosure-icon-scale: .5`. `TokenSearchField` controls tokens while keeping editable text DOM-owned between token changes, combining free text with ordered atomic filter chips while leaving query parsing and results to the application; its leading icon, first text line, clear action, and trailing slot stay aligned to one fixed row when text wraps. Its optional `collapsible` mode animates between an empty iconic action and the complete field, works standalone or inside `ToolbarControlGroup`, and keeps populated searches expanded while the app controls transient focus/open state. Call `wireTokenSearchFields` once to make Enter submit without inserting a contenteditable line break and to preserve focus plus the text-relative caret across controlled keyboard chip deletion; visual wrapping remains available. Semantic state palettes are opinionated, Web Awesome-compatible, and overridable globally, by tone, or per instance.
32
+
33
+ `DialogHeader` composes a real top `Toolbar`: its borderless identity group keeps the optional 24px icon in a 34px circular background and the first title line centered against an automatic action `ToolbarControlGroup`. Pass action children directly and add a localized `actionsLabel` when that group needs a name. The optional `summary`/`summaryId` row remains below the title; the application owns modal focus, dismissal, commands, and action handling.
34
+
35
+ Pane and navigation composition: import `@kerfjs/ui/layout.css` and use an unpadded `.kui-pane` with optional `.kui-pane__toolbar`, one scrolling `.kui-pane__content`, and optional `.kui-pane__footer`. Add `.kui-content` for 24px major vertical gaps. Use `.kui-content-item` for children that own 8px inline margin, 1px transparent-or-visible border, 8px padding, and 12px rounded corners; the pill modifier uses 22px. This vocabulary applies equally to sidebars, main areas, inspectors, and dialogs.
36
+
37
+ Composer form recipe: keep the form as one visible surface with exactly three transparent 8/1/8 content sections—introduction, fields, and actions-plus-ownership footer—separated by the 24px major rhythm. A conditional `StateBanner` is the only nested semantic surface. Do not make every form section another card. When resetting controlled upgraded Web Awesome fields, synchronize their live `value` properties as well as the rendered value attributes.
38
+
39
+ Toolbar and split-item geometry: wrap every toolbar item, including dormant text, in `ToolbarControlGroup`. Groups stay 44px outside (`calc(2px + remify(42px))`) even when their border and background are transparent, with 8px between groups and inside items. `MenuHeader` fills the available inline width and separates its dormant title/count-or-badge cluster from its optional logical-end 44px action; the visible action glyph defaults to 18px through `--kui-menu-header-action-icon-size`, while disclosure mode makes the title cluster the action. `MenuActionRow` keeps primary and trailing 44px actions as sibling native buttons inside a noninteractive row. Do not pad pane shells or add competing wrapper insets. See `docs/layout.md`.
40
+
41
+ Import visual components from explicit JavaScript subpaths. A CSS-aware browser bundler selects generated wrappers that bring in the foundation, the component CSS, and styles for reachable UI subcomponents; unrelated CSS stays out. The root barrel and `@kerfjs/ui/unstyled` are CSS-free; use `styles.css` with the root barrel only when the complete layer is intentional. Manual CSS exports remain available for custom pipelines, and application overrides follow package styles in the cascade. Direct `wa-*` JSX uses `import type {} from '@kerfjs/ui/webawesome'`, the side-effect-free declaration boundary synchronized with all 70 catalog-supported elements. `@kerfjs/ui/webawesome.css` is the optional CSS-only theme; it includes Web Awesome's base stylesheet, registers no elements, and remains overridable through public `--wa-*` tokens. CSS, its generated browser wrappers, and `@kerfjs/ui/select/register` are the declared side effects. `kerfjs` is an external peer. The Web Awesome peer is optional and needed for its components. `wireTabBars` includes proximity-based horizontal edge autoscroll during same-bar drag reordering.
19
42
 
20
43
  The Web Awesome theme renders Tooltip and Popover without pointer arrows by default. Restore them only when the anchor relationship needs the pointer, using `--wa-tooltip-arrow-size`, `--kui-wa-popover-arrow-size`, or one popover's public `--arrow-size`; `without-arrow` remains available for explicit local intent.
21
44
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@kerfjs/ui",
3
- "version": "4.4.1",
3
+ "version": "5.0.0-beta.3",
4
4
  "description": "Accessible, composable UI primitives for kerf applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Brian Westphal <brian.westphal@bleugris.com>",
8
- "homepage": "https://brianwestphal.github.io/kerf/docs/ui-package/",
8
+ "homepage": "https://brianwestphal.github.io/kerf/docs/component-packages/",
9
9
  "repository": {
10
10
  "type": "git",
11
11
  "url": "https://github.com/brianwestphal/kerf",
@@ -44,6 +44,11 @@
44
44
  "browser": "./dist/browser/lucide-icon.js",
45
45
  "import": "./dist/lucide-icon.js"
46
46
  },
47
+ "./disclosure-arrow": {
48
+ "types": "./dist/disclosure-arrow.d.ts",
49
+ "browser": "./dist/browser/disclosure-arrow.js",
50
+ "import": "./dist/disclosure-arrow.js"
51
+ },
47
52
  "./toolbar": {
48
53
  "types": "./dist/toolbar.d.ts",
49
54
  "browser": "./dist/browser/toolbar.js",
@@ -59,6 +64,11 @@
59
64
  "browser": "./dist/browser/toolbar-text.js",
60
65
  "import": "./dist/toolbar-text.js"
61
66
  },
67
+ "./menu-action-row": {
68
+ "types": "./dist/menu-action-row.d.ts",
69
+ "browser": "./dist/browser/menu-action-row.js",
70
+ "import": "./dist/menu-action-row.js"
71
+ },
62
72
  "./menu-item": {
63
73
  "types": "./dist/menu-item.d.ts",
64
74
  "browser": "./dist/browser/menu-item.js",
@@ -112,10 +122,23 @@
112
122
  "browser": "./dist/browser/segmented-control.js",
113
123
  "import": "./dist/segmented-control.js"
114
124
  },
125
+ "./token-search-field": {
126
+ "types": "./dist/token-search-field.d.ts",
127
+ "browser": "./dist/browser/token-search-field.js",
128
+ "import": "./dist/token-search-field.js"
129
+ },
130
+ "./wire-token-search-fields": {
131
+ "types": "./dist/wire-token-search-fields.d.ts",
132
+ "import": "./dist/wire-token-search-fields.js"
133
+ },
115
134
  "./select/register": {
116
135
  "types": "./dist/select-register.d.ts",
117
136
  "import": "./dist/select-register.js"
118
137
  },
138
+ "./webawesome": {
139
+ "types": "./dist/webawesome.d.ts",
140
+ "import": "./dist/webawesome.js"
141
+ },
119
142
  "./state-banner": {
120
143
  "types": "./dist/state-banner.d.ts",
121
144
  "browser": "./dist/browser/state-banner.js",
@@ -140,55 +163,73 @@
140
163
  "types": "./dist/index.d.ts",
141
164
  "import": "./dist/index.js"
142
165
  },
143
- "./foundation.css": "./src/foundation.css",
144
- "./webawesome.css": "./src/webawesome.css",
145
- "./styles.css": "./src/styles.css",
146
- "./lucide-icon.css": "./src/lucide-icon.css",
147
- "./toolbar.css": "./src/toolbar.css",
148
- "./toolbar-control-group.css": "./src/toolbar-control-group.css",
149
- "./toolbar-text.css": "./src/toolbar-text.css",
150
- "./menu-item.css": "./src/menu-item.css",
151
- "./menu-header.css": "./src/menu-header.css",
152
- "./resizable-region.css": "./src/resizable-region.css",
153
- "./app-tab.css": "./src/app-tab.css",
154
- "./tab-bar.css": "./src/tab-bar.css",
155
- "./page-header.css": "./src/page-header.css",
156
- "./loading-spinner.css": "./src/loading-spinner.css",
157
- "./select.css": "./src/select.css",
158
- "./segmented-control.css": "./src/segmented-control.css",
159
- "./state-banner.css": "./src/state-banner.css",
160
- "./empty-state.css": "./src/empty-state.css",
161
- "./dialog-header.css": "./src/dialog-header.css",
162
- "./value-table.css": "./src/value-table.css",
166
+ "./foundation.css": "./dist/styles/foundation.css",
167
+ "./layout.css": "./dist/styles/layout.css",
168
+ "./webawesome.css": "./dist/styles/webawesome.css",
169
+ "./styles.css": "./dist/styles/styles.css",
170
+ "./lucide-icon.css": "./dist/styles/lucide-icon.css",
171
+ "./disclosure-arrow.css": "./dist/styles/disclosure-arrow.css",
172
+ "./toolbar.css": "./dist/styles/toolbar.css",
173
+ "./toolbar-control-group.css": "./dist/styles/toolbar-control-group.css",
174
+ "./toolbar-text.css": "./dist/styles/toolbar-text.css",
175
+ "./menu-action-row.css": "./dist/styles/menu-action-row.css",
176
+ "./menu-item.css": "./dist/styles/menu-item.css",
177
+ "./menu-header.css": "./dist/styles/menu-header.css",
178
+ "./resizable-region.css": "./dist/styles/resizable-region.css",
179
+ "./app-tab.css": "./dist/styles/app-tab.css",
180
+ "./tab-bar.css": "./dist/styles/tab-bar.css",
181
+ "./page-header.css": "./dist/styles/page-header.css",
182
+ "./loading-spinner.css": "./dist/styles/loading-spinner.css",
183
+ "./select.css": "./dist/styles/select.css",
184
+ "./segmented-control.css": "./dist/styles/segmented-control.css",
185
+ "./token-search-field.css": "./dist/styles/token-search-field.css",
186
+ "./state-banner.css": "./dist/styles/state-banner.css",
187
+ "./empty-state.css": "./dist/styles/empty-state.css",
188
+ "./dialog-header.css": "./dist/styles/dialog-header.css",
189
+ "./value-table.css": "./dist/styles/value-table.css",
163
190
  "./ai/*": "./ai/*",
164
191
  "./docs/*": "./docs/*"
165
192
  },
166
193
  "files": [
167
194
  "dist",
168
- "src/*.css",
169
195
  "ai",
170
196
  "docs",
197
+ "ux-demo/recipes",
171
198
  "llms.txt",
172
199
  "README.md",
173
200
  "LICENSE"
174
201
  ],
175
202
  "scripts": {
176
- "build": "tsup && node ../scripts/clean-dist-output.mjs dist && node scripts/build-browser-entries.mjs",
203
+ "build": "tsup && node ../scripts/clean-dist-output.mjs dist && node scripts/build-css.mjs && node scripts/build-browser-entries.mjs",
177
204
  "dev": "npm run build && vite --config ux-demo/vite.config.ts",
178
205
  "demo:build": "npm run build && vite build --config ux-demo/vite.config.ts && node scripts/check-demo-bundle.mjs",
179
206
  "demo:serve": "vite preview --config ux-demo/vite.config.ts",
180
- "lint": "eslint src tests ux-demo --max-warnings 0",
207
+ "format:css": "prettier --write \"src/**/*.css\" \"ux-demo/**/*.css\"",
208
+ "check:format": "prettier --check \"src/**/*.css\" \"ux-demo/**/*.css\"",
209
+ "catalog:sync": "node scripts/sync-component-catalog.mjs",
210
+ "check:catalog": "node scripts/sync-component-catalog.mjs --check && node scripts/check-component-catalog.mjs",
211
+ "check:recipes": "node scripts/check-recipes.mjs",
212
+ "check:ai-regressions": "node scripts/check-ai-regressions.mjs && node scripts/audit-ai-regression-results.mjs",
213
+ "check:ai-signatures": "node scripts/sync-ai-public-signatures.mjs --check",
214
+ "ai:signatures:sync": "npm run build && node scripts/sync-ai-public-signatures.mjs",
215
+ "ai:regressions:compile": "node scripts/compile-ai-regression-response.mjs",
216
+ "ai:regressions:prepare": "node scripts/prepare-ai-regression.mjs",
217
+ "ai:regressions:record": "node scripts/record-ai-regression-run.mjs",
218
+ "ai:regressions:score": "node scripts/score-ai-regression.mjs",
219
+ "check:guidance": "node scripts/check-decision-guidance.mjs",
220
+ "lint": "eslint src tests ux-demo docs/examples --max-warnings 0",
181
221
  "typecheck": "node node_modules/typescript7/bin/tsc --noEmit",
182
222
  "test": "npm run test:unit && npm run test:bundle",
183
223
  "test:unit": "vitest run tests/unit --coverage",
184
224
  "test:bundle": "npm run build && vitest run tests/bundle",
225
+ "test:consumer-types": "npm run build && node node_modules/typescript7/bin/tsc -p tests/consumer-types/webawesome/tsconfig.json",
185
226
  "test:e2e": "npm run demo:build && playwright test",
186
- "check": "npm run lint && npm run typecheck && npm test && npm run demo:build && npm pack --dry-run",
227
+ "check": "npm run check:format && npm run check:catalog && npm run check:recipes && npm run check:guidance && npm run check:ai-regressions && npm run lint && npm run typecheck && npm test && npm run test:consumer-types && npm run check:ai-signatures && npm run demo:build && npm pack --dry-run",
187
228
  "prepublishOnly": "npm run check"
188
229
  },
189
230
  "peerDependencies": {
190
231
  "@awesome.me/webawesome": "^3.12.0",
191
- "kerfjs": "^4.0.0",
232
+ "kerfjs": "^5.0.0-0",
192
233
  "lucide": "^1.43.0"
193
234
  },
194
235
  "peerDependenciesMeta": {
@@ -209,6 +250,8 @@
209
250
  "eslint-plugin-simple-import-sort": "^12.1.1",
210
251
  "happy-dom": "^20.9.0",
211
252
  "lucide": "1.43.0",
253
+ "postcss": "^8.5.28",
254
+ "prettier": "^3.9.6",
212
255
  "tsup": "^8.3.0",
213
256
  "typescript": "^6.0.3",
214
257
  "typescript7": "npm:typescript@^7.0.2",
@@ -0,0 +1,32 @@
1
+ import '@kerfjs/ui/layout.css';
2
+ import './recipes.css';
3
+
4
+ import { LucideIcon } from '@kerfjs/ui/lucide-icon';
5
+ import { MenuHeader } from '@kerfjs/ui/menu-header';
6
+ import { MenuItem } from '@kerfjs/ui/menu-item';
7
+ import { PageHeader } from '@kerfjs/ui/page-header';
8
+ import { ResizableRegion } from '@kerfjs/ui/resizable-region';
9
+ import { Toolbar } from '@kerfjs/ui/toolbar';
10
+ import { ToolbarControlGroup } from '@kerfjs/ui/toolbar-control-group';
11
+ import { ToolbarText } from '@kerfjs/ui/toolbar-text';
12
+ import { ValueTable, ValueTableRow } from '@kerfjs/ui/value-table';
13
+ import { signal } from 'kerfjs';
14
+ import { Bell, Folder, Inbox, Settings } from 'lucide';
15
+
16
+ import type { RecipeFactory } from './types.js';
17
+
18
+ export const createRecipe: RecipeFactory = (announce) => {
19
+ const selected = signal('inbox');
20
+ const navigationSize = signal(224);
21
+ const inspectorSize = signal(240);
22
+ const responsivePane = signal<'content' | 'navigation' | 'inspector'>('content');
23
+ const render = () => <section class="kui-recipe recipe-shell kui-recipe__surface kui-layout" data-recipe="recipe-app-shell" data-responsive-pane={responsivePane.value}>
24
+ <Toolbar label="Atlas workspace" leading={<ToolbarControlGroup appearance="borderless" single><ToolbarText text="Atlas" /></ToolbarControlGroup>} trailing={<div class="recipe-shell__toolbar-actions"><ToolbarControlGroup className="recipe-shell__responsive-controls" appearance="borderless" label="Workspace panes"><button type="button" aria-label="Show navigation" aria-controls="recipe-shell-navigation" aria-pressed={String(responsivePane.value === 'navigation')} data-action="recipe-action" data-recipe-command="show-navigation"><LucideIcon icon={Folder} name="folder" /></button><button type="button" aria-label="Show content" aria-controls="recipe-shell-content" aria-pressed={String(responsivePane.value === 'content')} data-action="recipe-action" data-recipe-command="show-content"><LucideIcon icon={Inbox} name="inbox" /></button><button type="button" aria-label="Show inspector" aria-controls="recipe-shell-inspector" aria-pressed={String(responsivePane.value === 'inspector')} data-action="recipe-action" data-recipe-command="show-inspector"><LucideIcon icon={Settings} name="settings" /></button></ToolbarControlGroup><ToolbarControlGroup appearance="borderless" label="Workspace controls"><button type="button" aria-label="Notifications" data-action="recipe-action" data-recipe-command="notify"><LucideIcon icon={Bell} name="bell" /></button><button type="button" aria-label="Settings" data-action="recipe-action" data-recipe-command="settings"><LucideIcon icon={Settings} name="settings" /></button></ToolbarControlGroup></div>} />
25
+ <div class="recipe-shell__body">
26
+ <div id="recipe-shell-navigation" class="recipe-shell__nav-region"><ResizableRegion id="recipe-navigation" label="Navigation" size={navigationSize.value} min={180} max={320}><aside class="recipe-shell__nav kui-pane"><MenuHeader label="Workspace" /><nav class="recipe-shell__nav-list kui-pane__content kui-content" aria-label="Workspace"><section><MenuItem action="recipe-action" itemId="inbox" label="Inbox" icon={<LucideIcon icon={Inbox} name="inbox" />} selected={selected.value === 'inbox'} /><MenuItem action="recipe-action" itemId="projects" label="Projects with a deliberately wrapping title" icon={<LucideIcon icon={Folder} name="folder" />} selected={selected.value === 'projects'} multiline /></section></nav></aside></ResizableRegion></div>
27
+ <main id="recipe-shell-content" class="recipe-shell__main kui-pane"><PageHeader title={selected.value === 'inbox' ? 'Inbox triage' : 'Active projects'} action={<button class="kui-recipe__button" data-primary="true" type="button" data-action="recipe-action" data-recipe-command="new">New task</button>} /><div class="recipe-shell__main-body kui-pane__content kui-content"><p class="kui-recipe__ownership kui-content-item">Recipe owns pane geometry and one scroll owner per pane. The app owns routing, data, pane visibility, sizes, and persistence.</p><div class="recipe-shell__cards">{['Release accessibility audit', 'Prepare tablet navigation', 'Review stale-data states', 'Confirm package boundaries'].map((title) => <article class="recipe-shell__card kui-content-item"><strong>{title}</strong><p class="kui-recipe__muted">Assigned to the interface systems team · due this week</p></article>)}</div></div></main>
28
+ <div id="recipe-shell-inspector" class="recipe-shell__inspector-region"><ResizableRegion id="recipe-inspector" label="Inspector" size={inspectorSize.value} min={200} max={360} edge="start"><aside class="recipe-shell__inspector kui-pane"><PageHeader title="Inspector" /><div class="recipe-shell__inspector-body kui-pane__content kui-content"><ValueTable label="Selected task"><ValueTableRow label="Status" value="In review" /><ValueTableRow label="Owner" value="Mara Chen" /><ValueTableRow label="Priority" value="High" /></ValueTable></div></aside></ResizableRegion></div>
29
+ </div>
30
+ </section>;
31
+ return { render, action(command, element) { const pane = command.match(/^show-(navigation|content|inspector)$/)?.[1] as typeof responsivePane.value | undefined; if (pane) responsivePane.value = pane; const id = element.dataset.itemId; if (id) selected.value = id; announce(id ? `Opened ${id}` : pane ? `Showing ${pane}` : command === 'new' ? 'New task requested' : `${command} requested`); }, resize(id, size) { if (id === 'recipe-navigation') navigationSize.value = size; if (id === 'recipe-inspector') inspectorSize.value = size; announce(`${id} resized to ${size}px`); } };
32
+ };
@@ -0,0 +1,43 @@
1
+ import '@kerfjs/ui/layout.css';
2
+ import '@kerfjs/ui/select/register';
3
+ import './recipes.css';
4
+
5
+ import { LucideIcon } from '@kerfjs/ui/lucide-icon';
6
+ import { SegmentedControl } from '@kerfjs/ui/segmented-control';
7
+ import { Select } from '@kerfjs/ui/select';
8
+ import { Toolbar } from '@kerfjs/ui/toolbar';
9
+ import { ToolbarControlGroup } from '@kerfjs/ui/toolbar-control-group';
10
+ import { ToolbarText } from '@kerfjs/ui/toolbar-text';
11
+ import { signal } from 'kerfjs';
12
+ import { Filter, MoreHorizontal, RefreshCw } from 'lucide';
13
+
14
+ import type { RecipeFactory } from './types.js';
15
+
16
+ export const createRecipe: RecipeFactory = (announce) => {
17
+ const view = signal('list');
18
+ const sort = signal('updated');
19
+ const filtered = signal(false);
20
+ const render = () => <section class="kui-recipe recipe-compact-toolbar kui-recipe__surface kui-layout" data-recipe="recipe-compact-toolbar">
21
+ <Toolbar
22
+ label="Task workspace controls"
23
+ leading={<ToolbarControlGroup appearance="borderless" single><ToolbarText text="Tasks" size="small" /></ToolbarControlGroup>}
24
+ center={<ToolbarControlGroup><SegmentedControl id="recipe-view" label="View" value={view.value} action="recipe-action" appearance="toolbar" shape="pill" size="small" choices={[{ value: 'list', label: 'List' }, { value: 'board', label: 'Board' }, { value: 'timeline', label: 'Timeline' }]} /></ToolbarControlGroup>}
25
+ trailing={<div class="kui-control-cluster"><Select name="recipe-sort" value={sort.value} ariaLabel="Sort tasks" fitMenu choices={[{ value: 'updated', label: 'Recently updated' }, { value: 'priority', label: 'Priority' }, { value: 'owner', label: 'Owner' }, { value: 'created', label: 'Created date' }]} /><ToolbarControlGroup label="Task actions" buttonAppearance="push"><button type="button" aria-label="Toggle filters" aria-pressed={String(filtered.value)} data-action="recipe-action" data-recipe-command="filter"><LucideIcon icon={Filter} name="filter" /></button><button type="button" aria-label="Refresh tasks" data-action="recipe-action" data-recipe-command="refresh"><LucideIcon icon={RefreshCw} name="refresh-cw" /></button></ToolbarControlGroup><button class="kui-recipe__button" type="button" aria-label="More task actions" data-action="recipe-action" data-recipe-command="more"><LucideIcon icon={MoreHorizontal} name="more-horizontal" /></button></div>}
26
+ />
27
+ <div class="recipe-compact-toolbar__guide kui-content-item"><div><strong>ToolbarControlGroup</strong><span class="kui-recipe__muted">Related commands or pressed tools.</span></div><div><strong>SegmentedControl</strong><span class="kui-recipe__muted">A few visible exclusive choices.</span></div><div><strong>Select</strong><span class="kui-recipe__muted">A longer controlled value list.</span></div><div><strong>Ordinary button</strong><span class="kui-recipe__muted">One independent command.</span></div></div>
28
+ <p class="kui-recipe__ownership kui-content-item">The recipe owns control semantics and wrapping. The app owns values, actions, persistence, and responsive priority.</p>
29
+ </section>;
30
+ return {
31
+ render,
32
+ action(command, element) {
33
+ const value = element.dataset.segmentValue;
34
+ if (value) view.value = value;
35
+ if (command === 'filter') filtered.value = !filtered.value;
36
+ announce(value ? `View: ${value}` : command === 'filter' ? `Filters ${filtered.value ? 'on' : 'off'}` : `${command} requested`);
37
+ },
38
+ change(element) {
39
+ const value = (element as HTMLElement & { value?: string }).value;
40
+ if (element.getAttribute('name') === 'recipe-sort' && value) { sort.value = value; announce(`Sort: ${value}`); }
41
+ },
42
+ };
43
+ };
@@ -0,0 +1,62 @@
1
+ import '@kerfjs/ui/layout.css';
2
+ import '@kerfjs/ui/select/register';
3
+ import '@kerfjs/ui/webawesome.css';
4
+ import '@awesome.me/webawesome/dist/components/button/button.js';
5
+ import '@awesome.me/webawesome/dist/components/input/input.js';
6
+ import '@awesome.me/webawesome/dist/components/textarea/textarea.js';
7
+ import './recipes.css';
8
+
9
+ import { Select } from '@kerfjs/ui/select';
10
+ import { StateBanner } from '@kerfjs/ui/state-banner';
11
+ import { signal } from 'kerfjs';
12
+
13
+ import type { RecipeFactory } from './types.js';
14
+
15
+ type ValueField = HTMLElement & { value: string };
16
+
17
+ const syncControlledFieldValues = (action: HTMLElement, values: Record<string, string>) => {
18
+ const form = action.closest<HTMLElement>('[data-recipe="recipe-composer-form"]');
19
+ if (!form) return;
20
+ for (const [name, value] of Object.entries(values)) {
21
+ const field = form.querySelector<ValueField>(`[name="${name}"]`);
22
+ if (field && field.value !== value) field.value = value;
23
+ }
24
+ };
25
+
26
+ export const createRecipe: RecipeFactory = (announce) => {
27
+ const title = signal('');
28
+ const body = signal('The tablet layout now keeps navigation, content, and inspector focus order aligned.');
29
+ const audience = signal('team');
30
+ const status = signal<'idle' | 'error' | 'saved'>('idle');
31
+ const render = () => <form class="kui-recipe recipe-form kui-recipe__surface kui-content" data-recipe="recipe-composer-form" noValidate>
32
+ <header class="recipe-form__section recipe-form__introduction kui-content-item"><h2>Publish workspace update</h2><p class="kui-recipe__muted">Share a concise, actionable update with collaborators.</p></header>
33
+ {status.value === 'error' && <StateBanner title="Add a title before publishing" detail="The update body and audience are preserved." tone="danger" urgency="alert" />}
34
+ {status.value === 'saved' && <StateBanner title="Update published" detail="The team audience can now read it." tone="success" />}
35
+ <div class="recipe-form__section recipe-form__fields kui-content-item">
36
+ <wa-input name="recipe-title" label="Update title" hint="Summarize the outcome in one line." required value={title.value}></wa-input>
37
+ <wa-textarea name="recipe-body" label="Details" hint="Include decisions, owners, and the next checkpoint." rows="5" maxlength="400" with-count value={body.value}></wa-textarea>
38
+ <Select name="recipe-audience" value={audience.value} label="Audience" choices={[{ value: 'team', label: 'Workspace team' }, { value: 'reviewers', label: 'Reviewers' }, { value: 'organization', label: 'Entire organization' }]} />
39
+ </div>
40
+ <footer class="recipe-form__section recipe-form__footer kui-content-item"><div class="recipe-form__actions kui-control-cluster"><wa-button appearance="outlined" data-action="recipe-action" data-recipe-command="reset">Reset</wa-button><wa-button variant="brand" appearance="accent" data-action="recipe-action" data-recipe-command="submit">Publish update</wa-button></div><p class="kui-recipe__ownership">The recipe owns field, message, and action rhythm. The app owns validation rules, draft persistence, permissions, and transport.</p></footer>
41
+ </form>;
42
+ return {
43
+ render,
44
+ action(command, element) {
45
+ if (command === 'reset') {
46
+ title.value = '';
47
+ body.value = '';
48
+ status.value = 'idle';
49
+ syncControlledFieldValues(element, { 'recipe-title': title.value, 'recipe-body': body.value });
50
+ announce('Draft reset');
51
+ return;
52
+ }
53
+ if (command === 'submit') { status.value = title.value.trim() ? 'saved' : 'error'; announce(status.value === 'saved' ? 'Update published' : 'Title required'); }
54
+ },
55
+ change(element) {
56
+ const value = (element as HTMLElement & { value?: string | null }).value ?? '';
57
+ if (element.getAttribute('name') === 'recipe-title') title.value = value;
58
+ if (element.getAttribute('name') === 'recipe-body') body.value = value;
59
+ if (element.getAttribute('name') === 'recipe-audience') audience.value = value;
60
+ },
61
+ };
62
+ };