@kerfjs/ui 5.0.0-beta.4 → 5.0.0-beta.6

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 (95) hide show
  1. package/README.md +8 -8
  2. package/ai/component-catalog.json +303 -167
  3. package/ai/public-api-signatures-v1.md +333 -26
  4. package/ai/skill.md +14 -7
  5. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  6. package/dist/browser/{dialog-header.js → panel-header.js} +3 -2
  7. package/dist/{chunk-GY5WH7TO.js → chunk-24Z2XE6C.js} +3 -3
  8. package/dist/chunk-24Z2XE6C.js.map +1 -0
  9. package/dist/chunk-2RJBFNB6.js +172 -0
  10. package/dist/chunk-2RJBFNB6.js.map +1 -0
  11. package/dist/chunk-KZKSBUKC.js +19 -0
  12. package/dist/chunk-KZKSBUKC.js.map +1 -0
  13. package/dist/chunk-RBTVBGRD.js +23 -0
  14. package/dist/chunk-RBTVBGRD.js.map +1 -0
  15. package/dist/device-class.d.ts +62 -0
  16. package/dist/device-class.js +75 -0
  17. package/dist/device-class.js.map +1 -0
  18. package/dist/index.d.ts +2 -3
  19. package/dist/index.js +8 -9
  20. package/dist/nav-stack.d.ts +38 -0
  21. package/dist/nav-stack.js +4 -0
  22. package/dist/nav-stack.js.map +1 -0
  23. package/dist/panel-header.d.ts +26 -0
  24. package/dist/panel-header.js +6 -0
  25. package/dist/panel-header.js.map +1 -0
  26. package/dist/split-view.d.ts +42 -0
  27. package/dist/split-view.js +20 -0
  28. package/dist/split-view.js.map +1 -0
  29. package/dist/styles/foundation.css +21 -0
  30. package/dist/styles/menu-header.css +19 -2
  31. package/dist/styles/nav-stack.css +112 -0
  32. package/dist/styles/panel-header.css +59 -0
  33. package/dist/styles/split-view.css +35 -0
  34. package/dist/styles/styles.css +1 -2
  35. package/dist/styles/tab-scaffold.css +84 -0
  36. package/dist/styles/toolbar-control-group.css +52 -2
  37. package/dist/styles/toolbar-text.css +11 -0
  38. package/dist/styles/toolbar.css +9 -0
  39. package/dist/styles/workbench.css +94 -0
  40. package/dist/tab-scaffold.d.ts +30 -0
  41. package/dist/tab-scaffold.js +16 -0
  42. package/dist/tab-scaffold.js.map +1 -0
  43. package/dist/toolbar-text.d.ts +4 -2
  44. package/dist/toolbar-text.js +1 -1
  45. package/dist/wire-nav-stack.d.ts +15 -0
  46. package/dist/wire-nav-stack.js +88 -0
  47. package/dist/wire-nav-stack.js.map +1 -0
  48. package/dist/wire-tab-scaffold.d.ts +11 -0
  49. package/dist/wire-tab-scaffold.js +16 -0
  50. package/dist/wire-tab-scaffold.js.map +1 -0
  51. package/dist/wire-token-search-fields.d.ts +46 -4
  52. package/dist/wire-token-search-fields.js +1 -1
  53. package/dist/workbench.d.ts +33 -0
  54. package/dist/workbench.js +17 -0
  55. package/dist/workbench.js.map +1 -0
  56. package/docs/accessibility.md +10 -10
  57. package/docs/app-layouts.md +57 -0
  58. package/docs/component-contract.md +34 -9
  59. package/docs/component-selection.md +56 -7
  60. package/docs/design-philosophy.md +23 -1
  61. package/docs/device-class.md +54 -0
  62. package/docs/layout.md +33 -8
  63. package/docs/nav-stack.md +47 -0
  64. package/docs/recipes.md +17 -5
  65. package/docs/split-view.md +49 -0
  66. package/docs/tab-scaffold.md +41 -0
  67. package/docs/ux-demo.md +3 -3
  68. package/docs/workbench.md +47 -0
  69. package/llms.txt +107 -43
  70. package/package.json +37 -11
  71. package/ux-demo/recipes/app-shell.tsx +3 -3
  72. package/ux-demo/recipes/composer-form.tsx +2 -2
  73. package/ux-demo/recipes/list-workspace-states.tsx +2 -2
  74. package/ux-demo/recipes/loaders.ts +2 -0
  75. package/ux-demo/recipes/master-detail-dialog.tsx +2 -2
  76. package/ux-demo/recipes/mount-recipe.ts +3 -0
  77. package/ux-demo/recipes/navigation-stack.tsx +76 -0
  78. package/ux-demo/recipes/recipes.css +19 -8
  79. package/ux-demo/recipes/workspace-header.tsx +2 -2
  80. package/dist/browser/page-header.js +0 -3
  81. package/dist/chunk-2PES33HS.js +0 -13
  82. package/dist/chunk-2PES33HS.js.map +0 -1
  83. package/dist/chunk-GY5WH7TO.js.map +0 -1
  84. package/dist/chunk-H5AGGVU5.js +0 -75
  85. package/dist/chunk-H5AGGVU5.js.map +0 -1
  86. package/dist/chunk-K3G72I6D.js +0 -24
  87. package/dist/chunk-K3G72I6D.js.map +0 -1
  88. package/dist/dialog-header.d.ts +0 -15
  89. package/dist/dialog-header.js +0 -5
  90. package/dist/dialog-header.js.map +0 -1
  91. package/dist/page-header.d.ts +0 -9
  92. package/dist/page-header.js +0 -3
  93. package/dist/page-header.js.map +0 -1
  94. package/dist/styles/dialog-header.css +0 -87
  95. package/dist/styles/page-header.css +0 -33
@@ -18,12 +18,27 @@ A reference image defines hierarchy, relationships, and intended feel. It does n
18
18
 
19
19
  ### Continuity is correctness
20
20
 
21
- Unrelated updates must not close controls, move focus, reset a draft, replace a selection, or jump scroll. Give every durable and transient state an explicit owner. Preserve DOM identity when meaning has not changed. Distinguish blocking foreground work from background synchronization.
21
+ Unrelated updates must not close controls, move focus, reset a draft, replace a selection, or jump scroll. Give every durable and transient state an explicit owner. The default owner is the application: domain data and persisted layout preferences — a navigation stack, a selection, a tab order, a pane size, a `collapsed`/`expanded` disclosure flag — are the app's signals, which it reads to render. A `wire…` helper owns only the *ephemeral mechanics* around that state (a push/pop animation, overflow autoscroll, a live resize preview) and reports committed changes through callbacks. A helper takes over a piece of transient state itself only when hand-rolling that behavior is substantial and error-prone enough to cause real, inconsistent variation across apps — `wireTokenSearchFields`'s collapsible reveal/focus/Escape/blur-collapse is the bar; a one-line boolean toggle is not. Preserve DOM identity when meaning has not changed. Distinguish blocking foreground work from background synchronization.
22
22
 
23
23
  ### Hierarchy precedes decoration
24
24
 
25
25
  Establish one reading order and one dominant action per decision point. Group first with alignment, spacing, typography, and shared surfaces. A border, fill, badge, or nested card must communicate a real distinction.
26
26
 
27
+ ### Reach for the primitive, not for CSS
28
+
29
+ The package is designed to look right *unstyled*. A screen built from the
30
+ primitives, their props, and the semantic tokens should already read well, so
31
+ custom CSS is the exception. Before adding any `padding`, `margin`, `width`,
32
+ `height`, `border`, `background`, wrapper card, or decoration, check whether the
33
+ component, the pane, or the content-item already owns it — it almost always does,
34
+ and adding more usually **double-insets** or fights the component (the most common
35
+ mistake). Trust component defaults: render a control at its natural size and color
36
+ and fix the surrounding layout rather than overriding the control. Legitimate
37
+ custom CSS is limited to genuinely new structure, and even then may only join a
38
+ component's documented `publicClasses` or override documented `--kui-*` tokens at a
39
+ real composition boundary — never size, space, or re-skin a primitive by hand, and
40
+ never build a heading, toolbar, card, or pane geometry that a primitive provides.
41
+
27
42
  ### Panes share one child-owned geometry
28
43
 
29
44
  Sidebars, main areas, inspectors, and dialogs use the same unpadded pane. Their
@@ -80,6 +95,13 @@ foregrounds compatible with Hot Sheet 2 and Web Awesome. Consumers may replace
80
95
  the global palette, one semantic role, or one component instance through
81
96
  documented custom properties without rewriting component CSS.
82
97
 
98
+ Spacing is likewise a finite, semantic scale, not a free measurement. Five
99
+ canonical steps — 0, 4, 8, 16, and 24px, each a `--kui-space-*` token — map to a
100
+ single relationship apiece, chosen by how connected two elements are: no
101
+ separation, very minor air, standard within a group, between homogeneous groups,
102
+ and major between heterogeneous groups. Off-scale values (12px, 32px) are
103
+ deliberate exceptions. See [`layout.md`](layout.md) "Spacing scale".
104
+
83
105
  ### Responsive design reprioritizes
84
106
 
85
107
  Protect primary content, readable type, recognizable icons, and usable targets. Relocate secondary information before compressing it below a usable scale. Keep one clear scroll owner per region and test narrow, wide, zoomed, and intermediate layouts.
@@ -0,0 +1,54 @@
1
+ # Device class
2
+
3
+ `@kerfjs/ui/device-class` reports the current viewport as a reactive **device
4
+ class** — a size bucket × orientation × viewport-segment count — so a layout can
5
+ pick its presentation from one signal instead of hand-wiring `matchMedia`. It is
6
+ the foundation the app/dialog layouts build on (see
7
+ [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
8
+
9
+ ```ts
10
+ import { deviceClass } from '@kerfjs/ui/device-class';
11
+
12
+ const device = deviceClass(); // ReadonlySignal<DeviceClass>
13
+
14
+ effect(() => {
15
+ if (device.value.compact) showStack();
16
+ else showSplit();
17
+ });
18
+ ```
19
+
20
+ ## `DeviceClass`
21
+
22
+ | Field | Meaning |
23
+ | --- | --- |
24
+ | `size` | `xs-mobile` \| `mobile` \| `tablet` \| `desktop` \| `xl-desktop` |
25
+ | `orientation` | `portrait` \| `landscape` |
26
+ | `segments` / `verticalSegments` | viewport segment counts (foldables / dual-screen); `1` on ordinary devices |
27
+ | `handset` | `xs-mobile` or `mobile` |
28
+ | `compact` | "one pane at a time" — a handset or a portrait tablet |
29
+ | `atLeast(size)` | true when the current size is `size` or larger |
30
+
31
+ Reading `device.value` inside an `effect`/`computed` re-runs when the viewport
32
+ crosses a breakpoint, rotates, or changes its segment count. One shared viewport
33
+ source backs every reader.
34
+
35
+ ## Breakpoints
36
+
37
+ Default minimum widths (px): `mobile` 360, `tablet` 720, `desktop` 1024,
38
+ `xl-desktop` 1440. Override per reader:
39
+
40
+ ```ts
41
+ const device = deviceClass({ breakpoints: { tablet: 900 } });
42
+ ```
43
+
44
+ The same numbers are mirrored as CSS custom properties in the foundation
45
+ (`--kui-bp-mobile`, `--kui-bp-tablet`, `--kui-bp-desktop`, `--kui-bp-xl-desktop`)
46
+ so CSS media queries and the JS signal read one source. `classifyViewport(width,
47
+ orientation, segments?, verticalSegments?, breakpoints?)` is exported as the
48
+ pure, DOM-free bucketing core.
49
+
50
+ ## Server rendering
51
+
52
+ Without a DOM, `deviceClass()` resolves to an SSR default (1024×768, landscape,
53
+ one segment) and hydrates to the real class on the client. Override the assumed
54
+ viewport with `deviceClass({ ssr: { width, height } })`.
package/docs/layout.md CHANGED
@@ -38,7 +38,7 @@ changing its geometry by setting `--kui-content-item-border` and
38
38
  `--kui-content-item-background`.
39
39
 
40
40
  A visible parent surface does not make every child another visible card. The
41
- composer recipe keeps its form as the single surface, uses `DialogHeader` for
41
+ composer recipe keeps its form as the single surface, uses `PanelHeader` for
42
42
  its task hierarchy, and places field and action control edges on the shared 8px
43
43
  inline gutter instead of nesting them inside another padded content item. A
44
44
  conditional `StateBanner` remains visibly distinct because it communicates
@@ -58,7 +58,7 @@ semantic status.
58
58
  | Explicit scroll owner outside a pane | `.kui-scroll-owner` | `overflow: auto` |
59
59
 
60
60
  The component layer applies the same contract to `Toolbar`, `MenuHeader`,
61
- `MenuItem`, `PageHeader`, `DialogHeader`, `StateBanner`, `ValueTable`,
61
+ `MenuItem`, `PanelHeader`, `StateBanner`, `ValueTable`,
62
62
  `ValueTableRow`, tabs, and form controls. A value-table row separator starts at
63
63
  the row's 8px content inset, or at 40px when the row contains its 24px leading
64
64
  icon and 8px gap, and always ends 8px from the right edge. Each value-table row
@@ -66,12 +66,37 @@ also keeps 8px of root-scaled block padding independently of its semantic inline
66
66
  inset. Most interactive rows and toolbar groups are 44px tall.
67
67
  Toolbar groups reserve a real 1px outer border around a 42px inner area, even
68
68
  when their border and background are transparent.
69
- `PageHeader` keeps its title on the shared inset while its action border aligns
70
- with the logical edge of the following `.kui-content-item` border.
71
- `DialogHeader` applies the same rule internally: its icon/title identity is a
72
- borderless group in a top toolbar, its direct action children are wrapped in a
73
- contained group, and its optional subtitle is a separate row aligned below the
74
- title. The icon visual is 24px inside a 34px circular background.
69
+ `PanelHeader` is a plain top `Toolbar` used as a panel, dialog, or page heading
70
+ and overrides no Toolbar styles. The leading zone holds the optional icon (a
71
+ normal bordered `ToolbarControlGroup` given a brand fill with a matching border,
72
+ a 22px glyph) and the title as extra-large `ToolbarText`; the app's trailing
73
+ controls go straight into the trailing zone, and the icon group is omitted when
74
+ no icon is passed. The optional subtitle is a separate row below the toolbar, its
75
+ text left-aligned with the title text.
76
+
77
+ ## Spacing scale
78
+
79
+ Spacing is not a free choice. The official scale is five canonical steps, each
80
+ expressing exactly one relationship — pick the step by **how connected two
81
+ elements are**, not by eye. Every scalable value is `remify`-authored against the
82
+ fixed 16px baseline, so it delivers as `rem`.
83
+
84
+ | Value | Token | Relationship — when to use |
85
+ | --- | --- | --- |
86
+ | 0px | `--kui-space-none` | No separation. The elements read as a single unit (a control and its own affordance, adjacent `MenuItem` rows). |
87
+ | 4px | `--kui-space-2xs` | Very minor. Still one connected cluster, but readability or aesthetics demand a hair of air (inline metadata, an icon beside its label). |
88
+ | 8px | `--kui-space-xs` | Standard. Between elements **within a group** — the content-item padding/gutter, gaps between toolbar controls in a group. |
89
+ | 16px | `--kui-space-m` | Minor. Between **homogeneous groups** — two lists, two sibling sections of the same kind. |
90
+ | 24px | `--kui-space-l` | Major. Between **heterogeneous groups** — the `.kui-content` rhythm between major, differing regions. |
91
+
92
+ The two relationships that get confused most are 8px vs 24px: 8px is *inside* a
93
+ group, 24px is *between* major, differing regions. 16px sits between them for
94
+ same-kind groups.
95
+
96
+ `--kui-space-s` (12px) and `--kui-space-xl` (32px) exist but are **off the
97
+ canonical rhythm** — reach for them only as a deliberate exception, never as a
98
+ default step. Prefer the five canonical tokens so spacing stays legible and
99
+ consistent across every surface.
75
100
 
76
101
  ## Ownership rules
77
102
 
@@ -0,0 +1,47 @@
1
+ # Navigation stack
2
+
3
+ `@kerfjs/ui/nav-stack` is an iOS-style push/pop navigation stack: views slide in
4
+ and out over one another while the top chrome settles. A **single-pane layout is
5
+ a `NavStack` with one entry**. It is one of the opt-in app layouts (see
6
+ [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
7
+
8
+ Import the component and its companion stylesheet:
9
+
10
+ ```ts
11
+ import { NavStack, type NavStackView } from '@kerfjs/ui/nav-stack';
12
+ import { wireNavStack } from '@kerfjs/ui/wire-nav-stack';
13
+ import '@kerfjs/ui/nav-stack.css';
14
+ ```
15
+
16
+ ## State lives in the app
17
+
18
+ Like every `@kerfjs/ui` component, `NavStack` is declarative: the app owns the
19
+ stack as a `signal<NavStackView[]>`, `NavStack({ views })` renders it, and
20
+ `wireNavStack` animates the transitions.
21
+
22
+ ```tsx
23
+ const views = signal<NavStackView[]>([{ key: 'inbox', title: 'Inbox', content: <InboxView /> }]);
24
+
25
+ // render inside mount():
26
+ <NavStack id="mail" label="Mail" views={views.value} />;
27
+
28
+ // once, after first render:
29
+ const dispose = wireNavStack(root, { onBack: () => { views.value = views.value.slice(0, -1); } });
30
+
31
+ // push / pop by editing the signal:
32
+ views.value = [...views.value, { key: id, title: 'Message', content: <MessageView id={id} /> }];
33
+ ```
34
+
35
+ `NavStack` renders every entry stacked, the last one active and the rest kept
36
+ mounted (so their DOM state and focus survive) but hidden. Each entry carries a
37
+ `key` (stable identity), `content`, an optional `title`, and optional per-view
38
+ `toolbar` actions. The back control appears automatically once the stack has more
39
+ than one entry; `wireNavStack`'s `onBack` is where the app pops its own signal.
40
+
41
+ ## Transitions
42
+
43
+ `wireNavStack(root, { onBack, duration? })` observes the rendered stack and
44
+ animates each change: a pushed view slides in from the trailing edge; a popped
45
+ view slides back off it over the revealed view. It returns a disposer. The
46
+ animation honors `prefers-reduced-motion` (transitions collapse to instant) and
47
+ `duration: 0` disables it. Applicable at every device size and inside dialogs.
package/docs/recipes.md CHANGED
@@ -56,7 +56,7 @@ no custom `actionIcon` is needed; ordinary navigation rows stay chevron-free.
56
56
 
57
57
  [Open the recipe](../ux-demo/?component=recipe-workspace-header) · [TSX source](../ux-demo/recipes/workspace-header.tsx)
58
58
 
59
- `PageHeader` owns the page heading while one control cluster holds secondary,
59
+ `PanelHeader` owns the page heading while one control cluster holds secondary,
60
60
  overflow, and primary actions. The app owns authorization and command policy;
61
61
  controls relocate without changing focus order.
62
62
 
@@ -66,10 +66,10 @@ controls relocate without changing focus order.
66
66
 
67
67
  The production Web Awesome dialog owns modal focus and Escape; the thin recipe
68
68
  adapter restores the invoking control consistently after the hide event.
69
- `DialogHeader`, `MenuHeader`, `MenuItem`, and `ValueTable` own their included
69
+ `PanelHeader`, `MenuHeader`, `MenuItem`, and `ValueTable` own their included
70
70
  anatomy. The application owns open state, selection, dismissal policy, and
71
- record actions. Pass the dialog action children directly; `DialogHeader` places
72
- them in its top toolbar's action group and uses the localized `actionsLabel`.
71
+ record actions. `PanelHeader` places the trailing controls the app passes
72
+ directly into its top toolbar's trailing zone.
73
73
  The header sits on the dialog edge while retaining its internal control inset;
74
74
  the selected title receives the full content gutter, the metadata table fills
75
75
  the available detail width between the usual outer margins, and the action
@@ -80,7 +80,7 @@ Do not rebuild the dialog or reach into private shadow parts.
80
80
 
81
81
  [Open the recipe](../ux-demo/?component=recipe-composer-form) · [TSX source](../ux-demo/recipes/composer-form.tsx)
82
82
 
83
- `DialogHeader` supplies the task title and summary, with their ids referenced
83
+ `PanelHeader` supplies the task title and summary, with their ids referenced
84
84
  by the form. Production fields own labels, help, and native focus. The field
85
85
  and footer control edges sit directly on the shared 8px inline gutter rather
86
86
  than acquiring a second content-item padding inset; major children remain 24px
@@ -109,6 +109,18 @@ visible exclusive choices, `Select` for a longer value list, and an ordinary
109
109
  button for an independent command. The app owns values, actions, persistence,
110
110
  and responsive priority.
111
111
 
112
+ ## Navigation stack
113
+
114
+ [Open the recipe](../ux-demo/?component=recipe-navigation-stack) · [TSX source](../ux-demo/recipes/navigation-stack.tsx)
115
+
116
+ Drill from a library list into a detail and back with `NavStack`
117
+ (`@kerfjs/ui/nav-stack`): the app owns the stack as a signal of views and
118
+ pushes/pops it, `NavStack` renders it, and `wireNavStack` slides the content and
119
+ settles the chrome (reduced motion collapses the slide to instant). A live
120
+ `deviceClass()` badge shows the current size/orientation. See the layout guide
121
+ [`app-layouts.md`](app-layouts.md) for choosing among `NavStack`, `SplitView`,
122
+ `Workbench`, and `TabScaffold`.
123
+
112
124
  ## Rules shared by every recipe
113
125
 
114
126
  - Import `@kerfjs/ui/layout.css`; keep every pane unpadded and use exactly one
@@ -0,0 +1,49 @@
1
+ # Split view (list-detail)
2
+
3
+ `@kerfjs/ui/split-view` is a list-detail (master-detail) layout: a list and a
4
+ detail side by side on roomy screens, collapsing to a `NavStack` (list → detail)
5
+ on compact ones. One of the opt-in app layouts (see
6
+ [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
7
+
8
+ ```ts
9
+ import { SplitView } from '@kerfjs/ui/split-view';
10
+ import { deviceClass } from '@kerfjs/ui/device-class';
11
+ import '@kerfjs/ui/split-view.css';
12
+ // plus nav-stack.css when the compact path is reachable, and
13
+ // wireResizableRegions / wireNavStack for the interactive behavior.
14
+ ```
15
+
16
+ ## Responsive by device class
17
+
18
+ `SplitView` is declarative; the app derives `compact` from the device class and
19
+ tracks its own selection:
20
+
21
+ ```tsx
22
+ const device = deviceClass();
23
+ const selected = signal<string | null>(null);
24
+
25
+ <SplitView
26
+ id="mail"
27
+ label="Mail"
28
+ compact={device.value.compact}
29
+ detailActive={selected.value !== null}
30
+ list={<ThreadList />}
31
+ detail={<Message id={selected.value} />}
32
+ listTitle="Threads"
33
+ detailTitle="Message"
34
+ resizable={{ size: 320, min: 220, max: 480 }}
35
+ />;
36
+ ```
37
+
38
+ - **Roomy** (`compact: false`): both panes show. With `resizable`, the list sits
39
+ in a `ResizableRegion` (wire it with `wireResizableRegions`); without it the
40
+ list takes a fixed `--kui-split-view-list-width` (default 320px) and the detail
41
+ fills the rest.
42
+ - **Compact** (`compact: true`): the split collapses to a `NavStack`. The list is
43
+ the root; when `detailActive` is true the detail is pushed over it with an
44
+ automatic back control (wire it with `wireNavStack`, whose `onBack` clears the
45
+ app's selection). This is the portrait-tablet / handset presentation; as a
46
+ dialog the compact form is a full-screen or large partial-cover modal.
47
+
48
+ Compose the interactive wiring from the existing helpers — `SplitView` adds no
49
+ wire of its own.
@@ -0,0 +1,41 @@
1
+ # Tab scaffold
2
+
3
+ `@kerfjs/ui/tab-scaffold` is a mobile-first, iOS-like bottom tab bar that switches
4
+ between major app sections, where **each tab keeps its own content mounted** —
5
+ usually a `NavStack`, so each tab's stack and scroll survive a switch. One of the
6
+ opt-in app layouts (see [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
7
+ It is distinct from `TabBar` (document-oriented, reorderable strips).
8
+
9
+ ```ts
10
+ import { TabScaffold } from '@kerfjs/ui/tab-scaffold';
11
+ import { wireTabScaffold } from '@kerfjs/ui/wire-tab-scaffold';
12
+ import '@kerfjs/ui/tab-scaffold.css';
13
+ ```
14
+
15
+ ## Controlled selection
16
+
17
+ The app owns the active tab (a signal); `TabScaffold` renders every tab's scene
18
+ (only the active one visible) plus the bottom bar, and `wireTabScaffold` reports
19
+ clicks.
20
+
21
+ ```tsx
22
+ const active = signal('home');
23
+
24
+ <TabScaffold
25
+ id="app"
26
+ label="Sections"
27
+ active={active.value}
28
+ tabs={[
29
+ { id: 'home', label: 'Home', icon: <HomeIcon />, content: <HomeStack /> },
30
+ { id: 'search', label: 'Search', icon: <SearchIcon />, content: <SearchStack /> },
31
+ ]}
32
+ />;
33
+
34
+ // once, after first render:
35
+ const dispose = wireTabScaffold(root, { onSelect: (id) => { active.value = id; } });
36
+ ```
37
+
38
+ Each `TabScaffoldTab` has an `id`, `label`, optional `icon`, and `content`. The
39
+ bottom bar respects the home-indicator safe area (`env(safe-area-inset-bottom)`)
40
+ and keeps 44px targets. On larger device classes, promote the tab set to a
41
+ `Workbench` rail or a persistent sidebar instead of a bottom bar.
package/docs/ux-demo.md CHANGED
@@ -13,15 +13,15 @@ deterministic state, and semantic layout owners from the [recipe guide](./recipe
13
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
14
  - Every detail footer groups compact `Demo source` and `Guidance` actions; first-party component details also expose `Component source`. Web Awesome details say `Integration guidance` to distinguish the local integration contract from the upstream component implementation. Each button retains its repository-relative path in the DOM for verification and uses a deploy-safe absolute GitHub blob URL with a contextual new-tab name; the demo does not depend on a local source server or add a package runtime export. At phone widths, the resource group and related-component selector stack as full-width rows, with any resource overflow contained locally so labels cannot overlap the selector or widen the document.
15
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. Its sidebar identity uses the Kerf logo beside a vertically centered title, with the subtitle in its own aligned row like `DialogHeader`; the current-view heading is also a toolbar. Collapsing the catalog removes the pane completely and moves its restore action to the main toolbar's leading edge instead of leaving an icon-only rail. The selected view description remains inset below the page toolbar. The checkerboard belongs to the complete scrollable preview region rather than a nested card. A lightly separated footer keeps live status above compact source, guidance, and relationship groups. The focused `ResizableRegion` specimen stretches its surface through the available stage height and reports committed width in that shared status footer; narrow layouts retain local horizontal access when the pane is resized wider than the stage, and the split remains readable at 200% root scaling. Geometry coverage checks wide, intermediate, narrow, and 200% zoom layouts.
17
- - The composer-form recipe demonstrates one coherent visible form surface with `DialogHeader` title/summary hierarchy, fields and actions on the shared 8px control gutter, and 24px major rhythm. Its conditional error or success `StateBanner` is the only nested semantic surface. Reset synchronizes the upgraded Web Awesome fields' live value properties with their empty controlled attributes and announces `Draft reset`; browser coverage preserves behavior and verifies wide, narrow, dark, validation, success, reset, 200% zoom, and forced-colors states.
16
+ - The shell itself uses `@kerfjs/ui/layout.css`: sidebars, details, previews, dialogs, and resize specimens share unpadded panes plus self-contained content items. Its sidebar identity uses the Kerf logo beside a vertically centered title, with the subtitle in its own aligned row like `PanelHeader`; the current-view heading is also a toolbar. Collapsing the catalog removes the pane completely and moves its restore action to the main toolbar's leading edge instead of leaving an icon-only rail. The selected view description remains inset below the page toolbar. The checkerboard belongs to the complete scrollable preview region rather than a nested card. A lightly separated footer keeps live status above compact source, guidance, and relationship groups. The focused `ResizableRegion` specimen stretches its surface through the available stage height and reports committed width in that shared status footer; narrow layouts retain local horizontal access when the pane is resized wider than the stage, and the split remains readable at 200% root scaling. Geometry coverage checks wide, intermediate, narrow, and 200% zoom layouts.
17
+ - The composer-form recipe demonstrates one coherent visible form surface with `PanelHeader` title/summary hierarchy, fields and actions on the shared 8px control gutter, and 24px major rhythm. Its conditional error or success `StateBanner` is the only nested semantic surface. Reset synchronizes the upgraded Web Awesome fields' live value properties with their empty controlled attributes and announces `Draft reset`; browser coverage preserves behavior and verifies wide, narrow, dark, validation, success, reset, 200% zoom, and forced-colors states.
18
18
  - The master-detail dialog recipe places its header directly on the dialog edge while preserving the header's internal inset, gives the selected project title the complete outer-plus-inner content gutter, lets the value table fill the available detail column between the shared outer margins, and keeps record actions on one outer gutter without an extra content-item inset. Browser geometry coverage protects those relationships at wide and narrow widths.
19
19
  - Catalog-only brand marks, preview dimensions, checkerboard tiles, and the compound display-settings control retain local geometry because they are specimen chrome or intrinsic control anatomy rather than reusable application spacing roles.
20
20
  - 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.
21
21
  - The `DisclosureArrow` route renders the production 18px root-scaled default without a catalog-only size override. Its two native buttons independently control the default right-to-down chevron and a recognizable right-facing replacement arrow with left-to-up custom directions; both use shortest-path rotation and their names stay stable while `aria-expanded` communicates state. Browser coverage verifies the custom clockwise path, pointer and native keyboard activation, focus, narrow and 200%-zoom stacking with contained labels, consumer sizing, and the independent `.5` Web Awesome glyph scale retained by Kerf `Select`.
22
22
  - A detail with relationships renders one grouped footer selector: `Uses` links to direct dependencies and `Used by` links to reverse dependents. Choosing either navigates to that component's demo. The relationship group is absent when both sets are empty; no duplicate summary is rendered.
23
23
  - Demo-only CSS supplies only the catalog shell and stages; component appearance stays in package CSS. Decorative chrome and Web Awesome controls share the same semantic system palette instead of carrying a separate demo identity.
24
- - Scenarios use deterministic data and cover real variants, long content, disabled state, selection, progress, feedback, overlays, media, helpers, and stateful interactions. The Web Awesome theme route remains the broad system view, while individual action, form, layout, navigation, feedback, media, and helper routes provide focused specimens; 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.
24
+ - Scenarios use deterministic data and cover real variants, long content, disabled state, selection, progress, feedback, overlays, media, helpers, and stateful interactions. The Web Awesome theme route remains the broad system view, while individual action, form, layout, navigation, feedback, media, and helper routes provide focused specimens; MenuItem and MenuHeader demonstrate typed domain metadata, protected native button/disclosure semantics, and a native popover trigger relationship without product props; PanelHeader demonstrates its plain top-toolbar identity, extra-large title, optional subtitle row, and bordered brand-filled icon group; the Select route verifies that option icons survive unrelated Kerf rerenders and selected slot content follows value changes; the Animation route exposes preset, easing, duration, playback-rate, and transport controls with live lifecycle status; observer routes provide user-driven intersection, mutation, and resize events with live results; ToolbarControlGroup exposes all eight Hot Sheet 2 variants, loads the Web Awesome registrations needed by its popup specimen, and preserves the upgraded dropdown's managed light-DOM children across Kerf rerenders; the Toolbar route composes a collapsible TokenSearchField inside a group, animates between the empty iconic and complete field states, stays open while populated, and focuses the revealed editor; SegmentedControl covers toolbar, rounded-rectangle, pill, equal-width, and disabled presentations; TokenSearchField covers editable ordered chips, pointer and keyboard removal, caret-preserving controlled deletion, editing, clearing, free text, disabled state, and fixed first-line alignment while content wraps; StateBanner exposes every built-in tone plus a scoped override; TabBar demonstrates overflow, edge autoscroll, and controlled pointer/keyboard reordering.
25
25
  - Recipe ownership notes are hidden by default. A page-toolbar toggle reveals them as floating warning-toned notes with a folded corner, keeping instructional copy visually distinct and removable from the specimen flow.
26
26
  - Settings exercise light/dark, increased contrast, and reduced motion without changing component code. The theme action starts from the operating system's effective color scheme, always names the appearance it will switch to, and installs an explicit light or dark override when activated; contrast and reduced motion expose their independent preferences through `aria-pressed`.
27
27
  - The footer action log is an `aria-live` region, making interactions visible to both people and browser tests.
@@ -0,0 +1,47 @@
1
+ # Workbench
2
+
3
+ `@kerfjs/ui/workbench` is the Xcode-like multi-panel workspace: a collapsible
4
+ left rail, right rail, and bottom drawer around a central work area (any absent).
5
+ It generalizes the instant-width / sliding-content collapse used by the catalog
6
+ sidebar. One of the opt-in app layouts (see
7
+ [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)); best on
8
+ desktop-class devices.
9
+
10
+ ```ts
11
+ import { Workbench } from '@kerfjs/ui/workbench';
12
+ import '@kerfjs/ui/workbench.css';
13
+ ```
14
+
15
+ ## State lives in the app
16
+
17
+ `Workbench` is declarative and the collapse is **pure CSS** — no wire. The app
18
+ owns each panel's `collapsed` flag (usually a signal) and toggles it; the panel
19
+ animates itself.
20
+
21
+ ```tsx
22
+ const navCollapsed = signal(false);
23
+
24
+ <Workbench
25
+ id="studio"
26
+ label="Studio"
27
+ main={<Editor />}
28
+ leftRail={{ content: <Navigator />, label: 'Navigator', collapsed: navCollapsed.value, size: 280 }}
29
+ rightRail={{ content: <Inspector />, label: 'Inspector', collapsed: inspectorCollapsed.value }}
30
+ bottomDrawer={{ content: <Console />, label: 'Console', collapsed: consoleCollapsed.value }}
31
+ />;
32
+ ```
33
+
34
+ Each `WorkbenchPanel` takes `content`, an optional `collapsed`, an optional
35
+ `size` (rail width or drawer height in px, overriding the CSS default —
36
+ `--kui-workbench-rail-width` 280px, `--kui-workbench-drawer-height` 220px), and
37
+ an optional `label`.
38
+
39
+ ## How the collapse animates
40
+
41
+ Collapsing snaps the panel's flex track to zero in a single reflow (so the work
42
+ area relayouts once, not per frame) while the panel's fixed-size content slides
43
+ out via a composited `transform` — a rail slides horizontally, the drawer
44
+ vertically — clipped by the shell's overflow. It honors `prefers-reduced-motion`
45
+ (the slide collapses to instant). On smaller device classes, present the rails'
46
+ contents through a `NavStack` or overlay drawers rather than shrinking the
47
+ three-panel shell.
package/llms.txt CHANGED
@@ -1,45 +1,109 @@
1
1
  # @kerfjs/ui
2
2
 
3
- Accessible, production-backed UI primitives for kerf applications.
4
-
5
- - [Package guide](./README.md)
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)
12
- - [Component contract](./docs/component-contract.md)
13
- - [Web Awesome theme contract](./docs/webawesome-theme.md)
14
- - [Accessibility and keyboard contracts](./docs/accessibility.md)
15
- - [Design philosophy](./docs/design-philosophy.md)
16
- - [Apple HIG translation](./docs/apple-hig.md)
17
- - [UX catalog](./docs/ux-demo.md)
18
-
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.
20
-
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.
22
-
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. A visible collapsible pane owns collapse in its own toolbar. When hidden, move restore into the adjacent main toolbar on the same logical edgeleading for an inline-start pane, trailing for inline-end—and remove the pane instead of leaving an icon-only rail.
36
-
37
- Composer form recipe: keep the form as one visible surface, use `DialogHeader` title/summary ids to label and describe it, and align field and action control edges on the shared 8px inline gutter without a second content-item padding inset. Retain 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. Multiline `MenuItem` and `MenuActionRow` leading icons stay centered on the first text line while later lines extend below. 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.
42
-
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.
44
-
45
- Web Awesome catalog coverage means themed and inspectable, not preferred. Consider Popup when it replaces custom anchored positioning. Prefer Kerf Select over direct Dropdown/Dropdown Item/Select/Option composition, SegmentedControl over Button Group, TabBar or SegmentedControl over Web Awesome Tabs, LucideIcon over Web Awesome Icon, and ResizableRegion over Split Panel. ResizableRegion carries Hot Sheet 2's 1px separator plus hover/focus grip. Use Tree/Tree Item, Animated Image, and Comparison only for a specific required behavior; avoid Zoomable Frame.
3
+ > Accessible, production-backed UI primitives for [kerf](https://github.com/brianwestphal/kerf)
4
+ > applications. Components are plain functions returning `SafeHtml`; the app owns
5
+ > state and wires stable `data-action` hooks once. This file orients an LLM to
6
+ > generate correct, good-looking kerf UI — it is not a catalog to browse.
7
+
8
+ ```bash
9
+ npm install kerfjs @kerfjs/ui # kerfjs is a peer; Web Awesome is an optional peer
10
+ ```
11
+
12
+ Import each component from its own subpath (`@kerfjs/ui/menu-item`, `@kerfjs/ui/toolbar`,
13
+ …); the browser build pulls that component's reachable CSS automatically, so do not
14
+ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
15
+
16
+ ## How to build a screen (retrieval order)
17
+
18
+ 1. **Pick the component from the interface need**, not by appearance — read
19
+ [component-selection.md](./docs/component-selection.md) (the problem→component
20
+ matrix, with imports, wiring, ownership, and alternatives).
21
+ 2. **Get exact signatures before emitting code** read
22
+ [public-api-signatures-v1.md](./ai/public-api-signatures-v1.md) (real prop names,
23
+ callback shapes, disposer return types).
24
+ 3. **For exhaustive, structured facts** (ids, exports, variants, `publicClasses`,
25
+ relationships, side effects) load the
26
+ [Machine-readable component catalog](./ai/component-catalog.json) — the
27
+ canonical checked-in source; its schema is alongside it.
28
+ 4. **Apply the rules below and in** [skill.md](./ai/skill.md).
29
+
30
+ ## Core rules (do the right thing)
31
+
32
+ - **Reach for the primitive, not for CSS.** The package is built to look right
33
+ *unstyled*. Before adding `padding`, `margin`, `width`, `height`, `border`,
34
+ `background`, a wrapper card, or a decoration, check whether the component, the
35
+ pane, or the content-item already owns itit almost always does. Prefer a
36
+ component prop or a `--kui-*` token; write CSS only for genuinely new structure,
37
+ and then only join a component's documented `publicClasses` (never reach in by
38
+ tag/id/attribute or an unlisted class).
39
+ - **Never double-inset.** A `.kui-pane` has no padding; its `.kui-content` children
40
+ own the geometry (8px inline margin + 1px border + 8px padding). Do not add a
41
+ container padding on top of a content-item's own margin pick one owner. Use
42
+ `.kui-content` for 24px major vertical rhythm.
43
+ - **Trust the defaults.** Render a control at its natural size and color (a
44
+ `LucideIcon` is 24px); if it looks wrong, fix the surrounding layout, not the
45
+ control. Never force a component's width/height to size it that leaves a halo
46
+ or a stretched oval.
47
+ - **Toolbars hold only `ToolbarText` and `ToolbarControlGroup`.** Never put a bare
48
+ button, input, link, or loose markup in a `leading`/`center`/`trailing` zone.
49
+ Wrap every control — `SegmentedControl`, `Select`, a collapsible
50
+ `TokenSearchField`, Web Awesome controls — inside a `ToolbarControlGroup`. A
51
+ toolbar popup menu is a `single` `ToolbarControlGroup` around a `wa-dropdown`
52
+ (its `slot="trigger"` `wa-button` is the toolbar button, kept under
53
+ `data-morph-skip-children`).
54
+ - **`PanelHeader` is *the* panel/dialog/page heading.** A plain toolbar with an
55
+ optional bordered icon group, an extra-large `ToolbarText` title, the trailing
56
+ controls you pass, and an optional subtitle below — do not hand-build a heading
57
+ row or restyle a toolbar into one.
58
+ - **Space by the five-step scale, by relationship not by eye.** `0`
59
+ `--kui-space-none`, `4px` `--kui-space-2xs`, `8px` `--kui-space-xs` (inside a
60
+ group), `16px` `--kui-space-m`, `24px` `--kui-space-l` (between major differing
61
+ regions). 12px/32px are off-scale exceptions.
62
+ - **Panes share one child-owned geometry.** Sidebars, main areas, inspectors, and
63
+ dialogs all use an unpadded `.kui-pane` (optional `.kui-pane__toolbar`, one
64
+ scrolling `.kui-pane__content`, optional `.kui-pane__footer`). A visible pane owns
65
+ its collapse control; a hidden pane's restore moves to the adjacent main
66
+ toolbar's matching edge — no icon-only rail. Keep one scroll owner per region.
67
+ - **State and behavior live in the app.** Components take semantic `data-action`
68
+ strings and controlled values; wire them once with `delegate()`/`delegateActions()`
69
+ or the disposer-returning `wire…` helpers, and own domain copy, transport, and
70
+ policy in a thin adapter. Every element must earn its place — cut chrome, labels,
71
+ and readouts that do not help a person decide or act.
72
+ - **App state is domain/persisted; helpers own only ephemeral mechanics.** Keep
73
+ navigation stacks, tab selection and order, pane sizes, and `collapsed`/`expanded`
74
+ disclosure flags in the app — they are data or persisted layout, and the app reads
75
+ them to render. The `wire…` helpers own the *ephemeral* parts: `wireNavStack` the
76
+ push/pop animation, `wireTabBars` the overflow autoscroll and drag preview,
77
+ `wireResizableRegions` the live drag preview; they report committed changes via
78
+ callbacks. The one richer case is `wireTokenSearchFields`, which manages the
79
+ collapsible field's expand/collapse/focus by default (on, per-behavior opt-out) and
80
+ exposes the `expanded` signal on its handle — bind it in render or hand in your own
81
+ via `collapsible.signals`. Do not hand-roll open/collapse/focus that helper
82
+ provides; but do keep a plain disclosure toggle (a one-line boolean the app already
83
+ renders from) in the app.
84
+
85
+ ## Public exports
86
+
87
+ `LucideIcon`, `DisclosureArrow`, `Toolbar`, `ToolbarControlGroup`, `ToolbarText`,
88
+ `PanelHeader`, `MenuItem`, `MenuActionRow`, `MenuHeader`, `SegmentedControl`,
89
+ `Select`, `TokenSearchField` (+ `readTokenSearchField`, `placeTokenSearchCaret`,
90
+ `wireTokenSearchFields`), `AppTab`, `TabBar` (+ `wireTabBars`, `reorderTabs`),
91
+ `ResizableRegion` (+ `wireResizableRegions`), `StateBanner`, `EmptyState`,
92
+ `LoadingSpinner`, `ValueTable`, `ValueTableRow`. Whole-screen layouts (opt-in,
93
+ tree-shakeable, declarative with `wire…` helpers, each with a companion CSS import):
94
+ `NavStack`, `SplitView`, `Workbench`, `TabScaffold`, driven by `deviceClass()` from
95
+ `@kerfjs/ui/device-class`. `@kerfjs/ui/webawesome` is a type-only boundary for
96
+ `wa-*` JSX; `@kerfjs/ui/select/register` and the CSS are the declared side effects.
97
+
98
+ ## Reference
99
+
100
+ - [skill.md](./ai/skill.md): the full rule set + a common-mistakes table (the shipped AI skill).
101
+ - [design-philosophy.md](./docs/design-philosophy.md): why kerf UI is shaped this way — the decision principles behind the rules above.
102
+ - [layout.md](./docs/layout.md): the pane/content-item geometry, public roles/tokens, and the spacing scale.
103
+ - [component-contract.md](./docs/component-contract.md): what a component owns vs. what the app owns.
104
+ - [accessibility.md](./docs/accessibility.md): keyboard and ARIA contracts per component.
105
+ - [recipes.md](./docs/recipes.md): production compositions (app shell, master-detail dialog, composer form, …) as copyable references.
106
+ - [app-layouts.md](./docs/app-layouts.md): choosing `NavStack`/`SplitView`/`Workbench`/`TabScaffold` by data + interaction + device, and dialog presentation.
107
+ - [webawesome-theme.md](./docs/webawesome-theme.md): the optional Web Awesome theme; catalog coverage means themed-and-supported, not preferred (prefer the Kerf primitive when one exists).
108
+ - [component-catalog.json](./ai/component-catalog.json) + [public-api-signatures-v1.md](./ai/public-api-signatures-v1.md) + [webawesome-jsx-signatures-v1.md](./ai/webawesome-jsx-signatures-v1.md): machine-readable facts and exact signatures.
109
+ - [README.md](./README.md): package overview and the component subpath table.