@kerfjs/ui 5.0.0-beta.18 → 5.0.0-beta.21
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.
- package/README.md +114 -3
- package/ai/application-ui-diagnostic-ids-v1.json +92 -0
- package/ai/application-ui-profile-sync.cjs +529 -0
- package/ai/application-ui-profile.d.mts +1 -0
- package/ai/application-ui-profile.d.ts +102 -0
- package/ai/application-ui-profile.defaults.json +41 -0
- package/ai/application-ui-profile.mjs +626 -0
- package/ai/application-ui-profile.schema.json +130 -0
- package/ai/catalog-authoring.json +25 -0
- package/ai/catalog-authoring.schema.json +61 -0
- package/ai/compile-time-contracts-v1.json +101 -0
- package/ai/compile-time-contracts-v1.schema.json +49 -0
- package/ai/component-catalog-extension-v2.d.ts +7 -0
- package/ai/component-catalog-extension-v2.schema.json +7 -0
- package/ai/component-catalog-v2-overrides.json +326 -0
- package/ai/component-catalog-v2.d.ts +67 -0
- package/ai/component-catalog-v2.json +4441 -0
- package/ai/component-catalog-v2.schema.json +246 -0
- package/ai/component-catalog.json +150 -6
- package/ai/public-api-signatures-v1.md +294 -64
- package/ai/skill.md +41 -5
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/analyzer/cli.mjs +46 -0
- package/analyzer/index.d.mts +45 -0
- package/analyzer/index.mjs +743 -0
- package/analyzer/report.schema.json +66 -0
- package/dist/browser/pane.js +3 -0
- package/dist/browser/sunken-panel.js +3 -0
- package/dist/catalog-resources.d.ts +34 -0
- package/dist/catalog-resources.js +22 -0
- package/dist/catalog-resources.js.map +1 -0
- package/dist/catalog.d.ts +21 -3
- package/dist/catalog.js +239 -210
- package/dist/catalog.js.map +1 -1
- package/dist/{chunk-ZK2G3VR5.js → chunk-5VTB2BKP.js} +1 -1
- package/dist/chunk-5VTB2BKP.js.map +1 -0
- package/dist/{chunk-S3HKPI7I.js → chunk-7KEUJIIC.js} +1 -1
- package/dist/chunk-7KEUJIIC.js.map +1 -0
- package/dist/{chunk-NVWXKU37.js → chunk-AASLYRGE.js} +1 -1
- package/dist/chunk-AASLYRGE.js.map +1 -0
- package/dist/{chunk-ENVTNLYC.js → chunk-CN2XNTAM.js} +6 -6
- package/dist/chunk-CN2XNTAM.js.map +1 -0
- package/dist/{chunk-QIRXYKCE.js → chunk-DSNIYOZ3.js} +3 -2
- package/dist/chunk-DSNIYOZ3.js.map +1 -0
- package/dist/chunk-JRKSK2HX.js +79 -0
- package/dist/chunk-JRKSK2HX.js.map +1 -0
- package/dist/{chunk-U5IFGQCD.js → chunk-K57KRAOV.js} +1 -1
- package/dist/chunk-K57KRAOV.js.map +1 -0
- package/dist/{chunk-ZVI5YST2.js → chunk-M7JBER5U.js} +1 -1
- package/dist/chunk-M7JBER5U.js.map +1 -0
- package/dist/{chunk-43FUQH35.js → chunk-OHU6CI22.js} +2 -2
- package/dist/{chunk-43FUQH35.js.map → chunk-OHU6CI22.js.map} +1 -1
- package/dist/{chunk-4TRDV6RL.js → chunk-OZWU5ZPZ.js} +1 -1
- package/dist/chunk-OZWU5ZPZ.js.map +1 -0
- package/dist/{chunk-JTHUXDN2.js → chunk-RYRIFZCT.js} +3 -3
- package/dist/chunk-RYRIFZCT.js.map +1 -0
- package/dist/{chunk-6ETZKKAE.js → chunk-VLYV65LK.js} +1 -1
- package/dist/chunk-VLYV65LK.js.map +1 -0
- package/dist/chunk-W4CXQYSG.js +23 -0
- package/dist/chunk-W4CXQYSG.js.map +1 -0
- package/dist/{chunk-S3PCZ7NV.js → chunk-W7YD6E4N.js} +1 -1
- package/dist/chunk-W7YD6E4N.js.map +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +14 -12
- package/dist/list-header.d.ts +29 -8
- package/dist/list-header.js +1 -1
- package/dist/nav-stack.js +1 -1
- package/dist/pane.d.ts +49 -0
- package/dist/pane.js +4 -0
- package/dist/pane.js.map +1 -0
- package/dist/panel-header.d.ts +9 -3
- package/dist/panel-header.js +1 -1
- package/dist/segmented-control.d.ts +6 -6
- package/dist/segmented-control.js +1 -1
- package/dist/select.d.ts +10 -4
- package/dist/select.js +1 -1
- package/dist/split-view.js +2 -2
- package/dist/state-banner.d.ts +3 -2
- package/dist/state-banner.js +1 -1
- package/dist/styles/catalog.css +6 -6
- package/dist/styles/layout.css +2 -20
- package/dist/styles/pane.css +62 -0
- package/dist/styles/segmented-control.css +3 -7
- package/dist/styles/sunken-panel.css +18 -0
- package/dist/sunken-panel.d.ts +16 -0
- package/dist/sunken-panel.js +3 -0
- package/dist/sunken-panel.js.map +1 -0
- package/dist/tab-bar.d.ts +3 -2
- package/dist/tab-bar.js +1 -1
- package/dist/tab-scaffold.d.ts +6 -6
- package/dist/tab-scaffold.js.map +1 -1
- package/dist/token-search-field.d.ts +25 -9
- package/dist/token-search-field.js +1 -1
- package/dist/toolbar-control-group.d.ts +9 -5
- package/dist/toolbar-control-group.js +1 -1
- package/dist/toolbar-text.d.ts +10 -11
- package/dist/toolbar-text.js +1 -1
- package/dist/wire-tab-bars.d.ts +5 -2
- package/dist/wire-tab-bars.js +1 -1
- package/dist/wire-token-search-fields.d.ts +11 -6
- package/dist/wire-token-search-fields.js +1 -1
- package/docs/catalog.md +186 -67
- package/docs/component-contract.md +102 -5
- package/docs/component-selection.md +9 -5
- package/docs/design-philosophy.md +2 -4
- package/docs/examples/application-ui-profile.json +60 -0
- package/docs/examples/component-catalog-extension-v2.json +81 -0
- package/docs/layout.md +42 -23
- package/docs/sunken-panel.md +43 -0
- package/docs/type-contracts.md +99 -0
- package/docs/ui-analyzer.md +81 -0
- package/docs/ui-doctor.md +77 -0
- package/docs/ui-evaluator.md +122 -0
- package/doctor/cli.mjs +115 -0
- package/doctor/config.schema.json +68 -0
- package/doctor/index.d.mts +116 -0
- package/doctor/index.mjs +1256 -0
- package/doctor/report.schema.json +118 -0
- package/evaluator/cli.mjs +89 -0
- package/evaluator/index.d.mts +90 -0
- package/evaluator/index.mjs +1138 -0
- package/evaluator/report.schema.json +152 -0
- package/llms.txt +40 -3
- package/package.json +66 -8
- package/ux-demo/recipes/compact-toolbar.tsx +2 -2
- package/ux-demo/recipes/composer-form.tsx +1 -1
- package/dist/chunk-4TRDV6RL.js.map +0 -1
- package/dist/chunk-6ETZKKAE.js.map +0 -1
- package/dist/chunk-ENVTNLYC.js.map +0 -1
- package/dist/chunk-JTHUXDN2.js.map +0 -1
- package/dist/chunk-NVWXKU37.js.map +0 -1
- package/dist/chunk-QIRXYKCE.js.map +0 -1
- package/dist/chunk-S3HKPI7I.js.map +0 -1
- package/dist/chunk-S3PCZ7NV.js.map +0 -1
- package/dist/chunk-U5IFGQCD.js.map +0 -1
- package/dist/chunk-ZK2G3VR5.js.map +0 -1
- package/dist/chunk-ZVI5YST2.js.map +0 -1
package/docs/catalog.md
CHANGED
|
@@ -40,47 +40,113 @@ Per-entry `resources` render as "open in new tab" links in the footer, and
|
|
|
40
40
|
around a `wa-dropdown` (grouped by each entry's `group`), so register its elements
|
|
41
41
|
with `@kerfjs/ui/select/register` when you use it.
|
|
42
42
|
|
|
43
|
-
##
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
## Catalog demo authoring contract
|
|
44
|
+
|
|
45
|
+
This section is the single authoritative contract for tools and people that
|
|
46
|
+
author Catalog previews. The machine-readable discovery entry is
|
|
47
|
+
[`catalog-authoring.json`](../ai/catalog-authoring.json); exact props remain in
|
|
48
|
+
[`public-api-signatures-v1.md`](../ai/public-api-signatures-v1.md#kerfjsuicatalog).
|
|
49
|
+
The component catalog deliberately does not duplicate these rules: it describes
|
|
50
|
+
which component to choose, while this contract describes how to present the
|
|
51
|
+
chosen component.
|
|
52
|
+
|
|
53
|
+
### Choose the demo mode
|
|
54
|
+
|
|
55
|
+
Classify every entry before rendering it:
|
|
56
|
+
|
|
57
|
+
| Entry kind | Preview purpose | Geometry overlay |
|
|
58
|
+
| --------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
59
|
+
| Focused component | Show one public component, its meaningful variants, and adverse states. | `true`; the overlay inspects each selected specimen. |
|
|
60
|
+
| Composition or recipe | Show several components cooperating as one product surface. | `false`; child geometry remains unmarked so the composition can be read as a whole. |
|
|
61
|
+
|
|
62
|
+
Keep `geometryOverlay` present and compute it from the active entry. Do not make
|
|
63
|
+
it a permanent catalog-wide `true`, and do not give individual specimens their
|
|
64
|
+
own overlay implementation.
|
|
65
|
+
|
|
66
|
+
### Required structure
|
|
67
|
+
|
|
68
|
+
- `Catalog` is the one shell. The app owns active-entry state and passes one
|
|
69
|
+
active preview through `content`.
|
|
70
|
+
- `CatalogExampleStack` is the group for one preview's rows. Put route/test
|
|
71
|
+
metadata such as `data-demo` on its rendered root through `rootAttributes`.
|
|
72
|
+
Pass `label` when the stack needs an accessible name: the rendered `section`
|
|
73
|
+
is then exposed as a named region. An unlabeled stack remains an ordinary
|
|
74
|
+
grouping rather than adding an unnecessary landmark.
|
|
75
|
+
- `CatalogExample` is one row: optional generated `ListHeader` label, optional
|
|
76
|
+
generated note, then one specimen or one intentionally coupled specimen
|
|
77
|
+
cluster. Use one row per variant/state; do not hand-author the helper's private
|
|
78
|
+
classes.
|
|
79
|
+
- The specimen is an immediate child of `CatalogExample`. A focused component
|
|
80
|
+
row should place the component root there, without a decorative card or
|
|
81
|
+
spacing wrapper. A composition row may place the composition root there.
|
|
82
|
+
|
|
83
|
+
Use `align="glyph"` for a bare glyph/text specimen, `align="inline-control"`
|
|
84
|
+
for a control whose own inline padding contributes about 8px, and `align="none"`
|
|
85
|
+
(the default) for a content item or composition that owns its geometry.
|
|
48
86
|
|
|
49
87
|
```tsx
|
|
50
88
|
import { CatalogExample, CatalogExampleStack } from "@kerfjs/ui/catalog";
|
|
51
89
|
|
|
52
90
|
const buttonPreview = (
|
|
53
|
-
<CatalogExampleStack
|
|
91
|
+
<CatalogExampleStack
|
|
92
|
+
label="Button variants"
|
|
93
|
+
rootAttributes={{ "data-demo": "button" }}
|
|
94
|
+
>
|
|
54
95
|
<CatalogExample label="Icon" note="A bare glyph." align="glyph">
|
|
55
96
|
<LucideIcon icon={Plus} name="plus" />
|
|
56
97
|
</CatalogExample>
|
|
57
98
|
<CatalogExample label="Control" align="inline-control">
|
|
58
99
|
<SegmentedControl id="view" label="View" value="list" choices={choices} />
|
|
59
100
|
</CatalogExample>
|
|
60
|
-
<CatalogExample
|
|
61
|
-
|
|
101
|
+
<CatalogExample
|
|
102
|
+
label="Authoring note"
|
|
103
|
+
note="Explanatory chrome is not a specimen."
|
|
104
|
+
rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}
|
|
105
|
+
>
|
|
106
|
+
<p>Use the public helper contract.</p>
|
|
62
107
|
</CatalogExample>
|
|
63
108
|
</CatalogExampleStack>
|
|
64
109
|
);
|
|
65
110
|
```
|
|
66
111
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
the
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
112
|
+
The overlay selects every immediate child of a `CatalogExample` except the
|
|
113
|
+
helper-generated label and note. It does not recursively promote a nested child
|
|
114
|
+
to be the specimen. Outside an example row, it selects only top-level
|
|
115
|
+
`[data-component]` roots in the canvas and ignores nested component descendants.
|
|
116
|
+
These rules keep a row's label/group scaffolding out of the measurement and make
|
|
117
|
+
the authored nesting determine exactly what is inspected.
|
|
118
|
+
|
|
119
|
+
### Metadata ownership
|
|
120
|
+
|
|
121
|
+
Use `rootAttributes` on either helper for authoring metadata such as `data-demo`
|
|
122
|
+
or `data-catalog-geometry-overlay-skip`; the metadata lands on that helper's
|
|
123
|
+
rendered root. The slot accepts only `data-*` strings. Structural
|
|
124
|
+
`data-catalog-example`, `data-catalog-example-stack`, and `data-align` semantics
|
|
125
|
+
remain helper-owned and are rejected case-insensitively at runtime, including
|
|
126
|
+
from structurally widened or JavaScript objects. Do not copy the helpers'
|
|
127
|
+
private `kui-catalog-*` classes into preview markup.
|
|
128
|
+
|
|
129
|
+
The app owns entry ids, `kind`, routing, sources, relationships, and test hooks.
|
|
130
|
+
The helpers own their structural markers, label/note anatomy, alignment marker,
|
|
131
|
+
and private classes. Component metadata such as margin/border/padding ownership
|
|
132
|
+
lives in `component-catalog.json`; do not infer or overwrite it from overlay
|
|
133
|
+
pixels.
|
|
134
|
+
|
|
135
|
+
### Geometry overlay and legend
|
|
136
|
+
|
|
137
|
+
Pass the conditional `geometryOverlay` boolean to `Catalog`, then call
|
|
138
|
+
`wireCatalogGeometryOverlay(root)` once after the first render and retain its
|
|
139
|
+
disposer alongside `wireCatalog`'s.
|
|
140
|
+
|
|
141
|
+
| Overlay mark | Meaning | It is not |
|
|
142
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
143
|
+
| Orange translucent band | A positive computed margin on the selected specimen, after subtracting the helper's alignment inset. | Padding, gap, or empty content. Zero and negative margins are not drawn. |
|
|
144
|
+
| Quiet dashed outline | The border-box outer bound of a selected specimen whose computed background is transparent. | A real CSS border, focus ring, padding edge, or proof that the specimen owns its size. |
|
|
145
|
+
|
|
146
|
+
The overlay does not visualize padding, borders, gaps, negative/zero margins,
|
|
147
|
+
scroll overflow, hit targets, nested descendants, or geometry ownership. Inspect
|
|
148
|
+
computed styles and the machine-readable `geometry` metadata for those facts.
|
|
149
|
+
Opaque specimens do not receive the transparent-bound outline.
|
|
84
150
|
|
|
85
151
|
```tsx
|
|
86
152
|
<Catalog
|
|
@@ -90,13 +156,12 @@ unmarked.
|
|
|
90
156
|
/>
|
|
91
157
|
```
|
|
92
158
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
explanatory chrome rather than a specimen.
|
|
159
|
+
Put `rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}` on a
|
|
160
|
+
`CatalogExample` or `CatalogExampleStack` only when that whole subtree is
|
|
161
|
+
explanatory chrome rather than a specimen. The marker excludes the marked root
|
|
162
|
+
and every descendant from selection; it does not merely hide one band. It is
|
|
163
|
+
normally unnecessary in a composition because the active entry already sets the
|
|
164
|
+
global overlay to `false`.
|
|
100
165
|
|
|
101
166
|
Use the overlay together with machine-readable geometry ownership metadata; the
|
|
102
167
|
overlay verifies what is rendered, while metadata tells people and AI tools
|
|
@@ -107,6 +172,36 @@ contract and can start from the checked
|
|
|
107
172
|
[`component-catalog-extension.json`](./examples/component-catalog-extension.json)
|
|
108
173
|
example; provide those entries beside Kerf's shipped catalog to AI tools.
|
|
109
174
|
|
|
175
|
+
### Automated conformance and reviewed exceptions
|
|
176
|
+
|
|
177
|
+
Run `npm run check:demo-conformance` after changing a first-party demo, its
|
|
178
|
+
catalog kind, or the shell's overlay logic. The TypeScript-AST gate verifies
|
|
179
|
+
facts that source can prove without guessing at rendered intent:
|
|
180
|
+
|
|
181
|
+
- focused component routes import and use `CatalogExampleStack` and
|
|
182
|
+
`CatalogExample` from the public package;
|
|
183
|
+
- focused route metadata uses the helpers' `rootAttributes` slot, example rows
|
|
184
|
+
are not empty, and composition routes do not add redundant skip markers;
|
|
185
|
+
- every `@kerfjs/ui` import is a published package export and relative imports
|
|
186
|
+
do not reach into `ui/src`;
|
|
187
|
+
- demo JSX does not copy private `kui-catalog-*` structural classes; and
|
|
188
|
+
- the shell derives both geometry-overlay enablement and documented demo mode
|
|
189
|
+
from the active entry's source and kind.
|
|
190
|
+
|
|
191
|
+
The gate deliberately does not infer component ownership from arbitrary class
|
|
192
|
+
names, margins, borders, or nested descendants. Runtime selection and geometry
|
|
193
|
+
remain the browser suite's job.
|
|
194
|
+
|
|
195
|
+
A focused route may bypass the two public layout helpers only when the route's
|
|
196
|
+
stage geometry is itself the reviewed specimen. Add the narrow waiver to
|
|
197
|
+
[`catalog-conformance-exceptions.json`](../ux-demo/catalog-conformance-exceptions.json)
|
|
198
|
+
with the exact route, source file, stable diagnostic ids, a substantive reason,
|
|
199
|
+
and the reviewing `KF-*` ticket. Only helper/metadata rules are waivable;
|
|
200
|
+
private imports, private markup, empty examples, composition overlay drift, and
|
|
201
|
+
shell-mode drift always fail. The gate rejects duplicate, malformed, unused,
|
|
202
|
+
and stale exceptions, so delete a waiver when its route adopts the standard
|
|
203
|
+
helpers.
|
|
204
|
+
|
|
110
205
|
## Selection reveal
|
|
111
206
|
|
|
112
207
|
Set `revealSelection: true` on `wireCatalog` for a long desktop sidebar. After
|
|
@@ -124,8 +219,13 @@ sizes. For an initial deep link that did not come through `wireCatalog`, call
|
|
|
124
219
|
## Complete example
|
|
125
220
|
|
|
126
221
|
```tsx
|
|
127
|
-
import { mount, signal } from "kerfjs";
|
|
128
|
-
import {
|
|
222
|
+
import { mount, signal, type SafeHtml } from "kerfjs";
|
|
223
|
+
import {
|
|
224
|
+
Catalog,
|
|
225
|
+
CatalogExample,
|
|
226
|
+
CatalogExampleStack,
|
|
227
|
+
type CatalogSection,
|
|
228
|
+
} from "@kerfjs/ui/catalog";
|
|
129
229
|
import {
|
|
130
230
|
revealCatalogEntry,
|
|
131
231
|
wireCatalog,
|
|
@@ -133,45 +233,63 @@ import {
|
|
|
133
233
|
} from "@kerfjs/ui/wire-catalog";
|
|
134
234
|
import "@kerfjs/ui/styles.css"; // or import each primitive's CSS + @kerfjs/ui/catalog.css
|
|
135
235
|
|
|
136
|
-
|
|
137
|
-
|
|
236
|
+
type DemoKind = "component" | "composition";
|
|
237
|
+
type DemoEntry = CatalogSection["entries"][number] & { kind: DemoKind };
|
|
238
|
+
|
|
239
|
+
// 1. Describe selection and overlay mode once.
|
|
240
|
+
const entries: DemoEntry[] = [
|
|
138
241
|
{
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
resources: [
|
|
146
|
-
{
|
|
147
|
-
label: "Source",
|
|
148
|
-
href: "/src/button.tsx",
|
|
149
|
-
detail: "src/button.tsx",
|
|
150
|
-
},
|
|
151
|
-
],
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
id: "field",
|
|
155
|
-
name: "Field",
|
|
156
|
-
description: "A labeled input.",
|
|
157
|
-
tags: ["Discouraged"],
|
|
158
|
-
related: [{ id: "button", name: "Button", group: "Used with" }],
|
|
159
|
-
},
|
|
242
|
+
id: "button",
|
|
243
|
+
name: "Button",
|
|
244
|
+
kind: "component",
|
|
245
|
+
description: "A pressable control.",
|
|
246
|
+
resources: [
|
|
247
|
+
{ label: "Source", href: "/src/button.tsx", detail: "src/button.tsx" },
|
|
160
248
|
],
|
|
161
249
|
},
|
|
162
250
|
{
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
251
|
+
id: "profile-form",
|
|
252
|
+
name: "Profile form",
|
|
253
|
+
kind: "composition",
|
|
254
|
+
description: "A labeled field and save action working together.",
|
|
255
|
+
},
|
|
256
|
+
];
|
|
257
|
+
const sections: CatalogSection[] = [
|
|
258
|
+
{
|
|
259
|
+
category: "Examples",
|
|
260
|
+
entries: entries.map(({ kind: _kind, ...entry }) => entry),
|
|
167
261
|
},
|
|
168
262
|
];
|
|
169
263
|
|
|
170
|
-
// 2.
|
|
171
|
-
const renderers: Record<string, () =>
|
|
172
|
-
button: () =>
|
|
173
|
-
|
|
174
|
-
|
|
264
|
+
// 2. Every preview uses one public group and public example rows.
|
|
265
|
+
const renderers: Record<string, () => SafeHtml> = {
|
|
266
|
+
button: () => (
|
|
267
|
+
<CatalogExampleStack
|
|
268
|
+
label="Button states"
|
|
269
|
+
rootAttributes={{ "data-demo": "button" }}
|
|
270
|
+
>
|
|
271
|
+
<CatalogExample label="Default" align="inline-control">
|
|
272
|
+
<Button label="Save" />
|
|
273
|
+
</CatalogExample>
|
|
274
|
+
<CatalogExample
|
|
275
|
+
label="Authoring note"
|
|
276
|
+
note="This explanatory row is deliberately excluded from inspection."
|
|
277
|
+
rootAttributes={{ "data-catalog-geometry-overlay-skip": "" }}
|
|
278
|
+
>
|
|
279
|
+
<p>The application owns product copy and actions.</p>
|
|
280
|
+
</CatalogExample>
|
|
281
|
+
</CatalogExampleStack>
|
|
282
|
+
),
|
|
283
|
+
"profile-form": () => (
|
|
284
|
+
<CatalogExampleStack
|
|
285
|
+
label="Profile form composition"
|
|
286
|
+
rootAttributes={{ "data-demo": "profile-form" }}
|
|
287
|
+
>
|
|
288
|
+
<CatalogExample label="Complete composition">
|
|
289
|
+
<ProfileForm />
|
|
290
|
+
</CatalogExample>
|
|
291
|
+
</CatalogExampleStack>
|
|
292
|
+
),
|
|
175
293
|
};
|
|
176
294
|
|
|
177
295
|
// 3. App-owned state (domain: which entry; transient: collapsed; global: theme).
|
|
@@ -180,6 +298,7 @@ const initial =
|
|
|
180
298
|
const active = signal(initial);
|
|
181
299
|
const collapsed = signal(false);
|
|
182
300
|
const theme = signal<"light" | "dark">("light");
|
|
301
|
+
const activeEntry = () => entries.find(({ id }) => id === active.value) ?? entries[0];
|
|
183
302
|
|
|
184
303
|
const app = document.getElementById("app")!;
|
|
185
304
|
mount(app, () => (
|
|
@@ -190,7 +309,7 @@ mount(app, () => (
|
|
|
190
309
|
content={renderers[active.value]?.() ?? <></>}
|
|
191
310
|
collapsed={collapsed.value}
|
|
192
311
|
theme={theme.value}
|
|
193
|
-
geometryOverlay={
|
|
312
|
+
geometryOverlay={activeEntry().kind === "component"}
|
|
194
313
|
/>
|
|
195
314
|
));
|
|
196
315
|
|
|
@@ -18,6 +18,94 @@ package delivery paths, Web Awesome's installed manifest, AI guidance, and
|
|
|
18
18
|
documentation links. Nuanced prose in this contract and the selection guide
|
|
19
19
|
remains authored rather than generated.
|
|
20
20
|
|
|
21
|
+
### Composition catalog v2
|
|
22
|
+
|
|
23
|
+
[`component-catalog-v2.json`](../ai/component-catalog-v2.json) adds a formal,
|
|
24
|
+
machine-evaluable composition layer without changing the v1 selection and
|
|
25
|
+
delivery contract. Every v1 entry projects once, in order, under the stable
|
|
26
|
+
qualified key `package:id`. Each v2 entry explicitly covers parents/contexts,
|
|
27
|
+
named zones and cardinality, child concepts, state ownership, required wiring,
|
|
28
|
+
responsive ownership, layout and geometry, accessibility obligations, public
|
|
29
|
+
CSS boundaries, diagnostics, and provenance.
|
|
30
|
+
|
|
31
|
+
The generator starts with permissive defaults. An `any` mode records that the
|
|
32
|
+
catalog has no defensible prohibition; it does not claim every composition is
|
|
33
|
+
recommended. Objective rules live in
|
|
34
|
+
`component-catalog-v2-overrides.json` and may carry stable `KUI-C###`
|
|
35
|
+
diagnostics. A tool reports a diagnostic only after proving its exact `when`
|
|
36
|
+
condition. Subjective choice, product policy, and visual taste remain prose.
|
|
37
|
+
|
|
38
|
+
V1 consumers continue unchanged. V2 consumers read v1 for selection/delivery
|
|
39
|
+
and v2 for composition. `npm run catalog:sync` projects every new component,
|
|
40
|
+
recipe, and supported Web Awesome entry, and the completeness gate prevents
|
|
41
|
+
silent omissions. Downstream catalogs use the v2 extension schema and types,
|
|
42
|
+
retain their own package identity, and qualify cross-catalog references. See
|
|
43
|
+
the checked
|
|
44
|
+
[`component-catalog-extension-v2.json`](./examples/component-catalog-extension-v2.json)
|
|
45
|
+
application-owned example.
|
|
46
|
+
|
|
47
|
+
### Compile-time contract boundary
|
|
48
|
+
|
|
49
|
+
The versioned
|
|
50
|
+
[`compile-time-contracts-v1.json`](../ai/compile-time-contracts-v1.json)
|
|
51
|
+
artifact maps stable `KUI-T###` ids to public imports, emitted symbols, and
|
|
52
|
+
catalog identities. Its positive/negative fixture compiles against both source
|
|
53
|
+
and a freshly packed package, so source declarations and shipped declarations
|
|
54
|
+
cannot silently diverge. See [Compile-time contracts](./type-contracts.md) for
|
|
55
|
+
the full audit, migration guidance, and the relationships deliberately left to
|
|
56
|
+
runtime/catalog checks because TypeScript cannot prove them.
|
|
57
|
+
|
|
58
|
+
### Application UI profile
|
|
59
|
+
|
|
60
|
+
The component catalogs describe what packages provide; an application profile
|
|
61
|
+
describes which supported choices a project has approved. A workspace may
|
|
62
|
+
check in `.kerf-ui-profile.json` conforming to
|
|
63
|
+
[`application-ui-profile.schema.json`](../ai/application-ui-profile.schema.json).
|
|
64
|
+
Keep package-qualified catalog locations and recurring-concept preferences,
|
|
65
|
+
allowed color schemes and density, public semantic-token overrides,
|
|
66
|
+
layout/responsive conventions, and narrow rule exceptions there. Product
|
|
67
|
+
records, copy, permissions, user preferences, and transport state do not belong
|
|
68
|
+
in this policy file.
|
|
69
|
+
|
|
70
|
+
Every catalog location names a v2 composition artifact. `selection` is optional
|
|
71
|
+
for consumer packages whose generated metadata declares v1 selection guidance
|
|
72
|
+
not applicable; `@kerfjs/ui` retains its required v1 selection artifact. This
|
|
73
|
+
lets a generated `component-catalog-v2.json` participate directly without a
|
|
74
|
+
fabricated compatibility file.
|
|
75
|
+
|
|
76
|
+
Discovery and precedence are deterministic:
|
|
77
|
+
|
|
78
|
+
1. Load `application-ui-profile.defaults.json` from `@kerfjs/ui`.
|
|
79
|
+
2. Load `.kerf-ui-profile.json` at the workspace root when present.
|
|
80
|
+
3. Walk from the workspace root toward the target directory and load each
|
|
81
|
+
directory-local profile in parent-to-child order.
|
|
82
|
+
|
|
83
|
+
Later scalar and object-map values win. Catalogs merge by package; preferences
|
|
84
|
+
and token overrides merge by key; exceptions merge by stable id. Lists such as
|
|
85
|
+
allowed themes/densities replace the earlier list rather than accumulating.
|
|
86
|
+
Every resolved field retains its source file. The shipped
|
|
87
|
+
`application-ui-profile.mjs` API implements discovery, merge, loading, and
|
|
88
|
+
validation and reports actionable originating file + JSON-path diagnostics for stale
|
|
89
|
+
catalogs, unknown component/rule/token references, preference conflicts, and
|
|
90
|
+
broad exceptions. Each raw layer is validated against the catalogs effective at
|
|
91
|
+
that exact precedence point before merge, so a broken parent catalog, stale
|
|
92
|
+
parent reference, invalid value, or unknown field cannot disappear merely
|
|
93
|
+
because a child profile replaces it. The shipped
|
|
94
|
+
`application-ui-profile-sync.cjs` projects the same merge and validation contract
|
|
95
|
+
for synchronous hosts such as ESLint rules; it deliberately performs no async
|
|
96
|
+
I/O.
|
|
97
|
+
See the
|
|
98
|
+
[`application-ui-profile.json`](./examples/application-ui-profile.json) example.
|
|
99
|
+
|
|
100
|
+
Catalog detail footers use one standard resource vocabulary and order. Build
|
|
101
|
+
them with `catalogResources()` from `@kerfjs/ui/catalog-resources`: `Demo source`
|
|
102
|
+
first, optional `Component source` and `Design template`, then `Guidance`.
|
|
103
|
+
Third-party entries use the typed `integrationGuidance` kind, which renders
|
|
104
|
+
`Integration guidance`, and omit `Component source` when the implementation is
|
|
105
|
+
not owned by the catalog's project. Prefer these semantic kinds over local labels
|
|
106
|
+
such as “View source” or “Read UI guidance”; the helper makes that convention a
|
|
107
|
+
compile-time choice while still returning ordinary `CatalogResource[]` values.
|
|
108
|
+
|
|
21
109
|
## Ownership boundaries
|
|
22
110
|
|
|
23
111
|
### Geometry ownership metadata
|
|
@@ -55,7 +143,8 @@ claim Kerf delivery paths for them.
|
|
|
55
143
|
- Consumers style through `--kui-*` semantic tokens and public component classes. Foundation tokens provide opinionated neutral, brand/info, success, warning, and danger fill/border/foreground roles. Stateful components expose local override variables; prefer an equivalent prop or token before writing a selector.
|
|
56
144
|
|
|
57
145
|
`ListItem.rootAttributes`, `ListActionRow.rootAttributes`,
|
|
58
|
-
`ListHeader.rootAttributes`,
|
|
146
|
+
`ListHeader.rootAttributes`, `AppTab.rootAttributes`,
|
|
147
|
+
`CatalogExample.rootAttributes`, and `CatalogExampleStack.rootAttributes` accept
|
|
59
148
|
application-owned `data-*` metadata without
|
|
60
149
|
adding product fields to the shared API. A `ListActionRow` trailing action uses
|
|
61
150
|
`trailingActionAttributes`, and a `ListHeader` action/disclosure uses
|
|
@@ -64,7 +153,7 @@ the corresponding `aria-controls`/`aria-haspopup` relationship. These slots do
|
|
|
64
153
|
not accept roles or component-owned action, selection, disclosure, accessible
|
|
65
154
|
name, disabled, or icon fields. Extension objects are filtered at runtime,
|
|
66
155
|
including case-insensitive rejection of protected `data-*` names, and the
|
|
67
|
-
component writes its protected attributes after the accepted metadata. Typed,
|
|
156
|
+
component or helper writes its protected attributes after the accepted metadata. Typed,
|
|
68
157
|
structurally widened, and JavaScript callers therefore cannot replace its
|
|
69
158
|
contract.
|
|
70
159
|
|
|
@@ -93,6 +182,11 @@ layout cannot be expressed by a prop or token. A class being public does not
|
|
|
93
182
|
make copied component markup an invocation or transfer state and accessibility
|
|
94
183
|
ownership to the application.
|
|
95
184
|
|
|
185
|
+
The v2 composition catalog additionally requires `boundaries.rootClass` to be
|
|
186
|
+
either one exact member of `publicClasses` or `null` when the entry has no
|
|
187
|
+
rendered class root. Runtime geometry tooling uses this explicit field; array
|
|
188
|
+
order never implies root ownership.
|
|
189
|
+
|
|
96
190
|
Do not select a component's descendant by element name, id, attribute alone, or
|
|
97
191
|
an unlisted implementation class. Selectors such as `.kui-state-banner span`,
|
|
98
192
|
`.kui-list-item [data-state]`, and `.kui-list-item .local-label` depend on
|
|
@@ -131,9 +225,12 @@ separator with a compact grip that appears on hover or keyboard focus. Override
|
|
|
131
225
|
`--kui-resizable-region-handle-active-color` on a region when its containing
|
|
132
226
|
surface needs a different contrast level.
|
|
133
227
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
228
|
+
`Pane` defines one structural model for sidebars, main areas, inspectors, and
|
|
229
|
+
dialogs. Its `.kui-pane` root is unpadded and contains an optional vertical
|
|
230
|
+
header, one scrolling vertical `.kui-pane__content`, and an optional footer.
|
|
231
|
+
Logical-edge separator lines are independently opt-in and default off.
|
|
232
|
+
`@kerfjs/ui/layout.css` retains the pane roles and supplies the related content
|
|
233
|
+
geometry classes.
|
|
137
234
|
`.kui-content` gives major children 24px vertical separation.
|
|
138
235
|
`.kui-content-item` gives one child 8px inline margin, a real 1px border,
|
|
139
236
|
8px padding, and 12px corners; border and background may be transparent without
|
|
@@ -45,6 +45,11 @@ application boundary:
|
|
|
45
45
|
Recipes use public production exports and show ownership boundaries; they are
|
|
46
46
|
copyable reference compositions, not new monolithic components.
|
|
47
47
|
|
|
48
|
+
When presenting a component or recipe in a Catalog, use the authoritative
|
|
49
|
+
[Catalog demo authoring contract](./catalog.md#catalog-demo-authoring-contract)
|
|
50
|
+
rather than deriving preview structure or geometry-overlay behavior from these
|
|
51
|
+
selection entries.
|
|
52
|
+
|
|
48
53
|
## Missing recurring concepts
|
|
49
54
|
|
|
50
55
|
Kerf UI does not export a command-palette component. Do not invent a package
|
|
@@ -71,7 +76,8 @@ an upstream component or recipe request.
|
|
|
71
76
|
| Navigation section heading — `ListHeader` | A menu section needs a full-width label, semantic count, non-count badge, logical-end action, or real disclosure state. | Do not concatenate counts into `label` or put numeric content in `badge`; use `count` with the localized full phrase in `countLabel`. Do not add a disclosure arrow to navigation that reveals nothing. Do not shrink its 44px action target to the 18px visual. Do not use it as a page, panel, or dialog title; use `PanelHeader`. | Delegate its optional action; the app controls expanded state and revealed content. Toggle mode supplies `DisclosureArrow` unless `actionIcon` replaces it. Use `triggerAttributes` only for domain `data-*` or a native popover relationship. | Section organization, valid count and localized count label, disclosure state and content, non-count badge content, popover target behavior, and policy. | `@kerfjs/ui/list-header` | [Pane geometry](../README.md#pane-and-content-geometry) |
|
|
72
77
|
| Inset self-bordered control — `ListInsetControl` | A control that owns its own border and padding but no outer margin (a search input, a `SegmentedControl`) must line up inside a `.kui-content` list with the standard 8px inline margins and stretch to fill the row. | Do not wrap a `.kui-content-item` or a `ListItem`/`ListHeader` that already owns its inline margin — that double-insets it. Do not add ad-hoc `margin`/`padding` around a bare control to align it; use this instead. | Place the self-bordered control(s) as children; they stretch to fill. It applies only the inline margin, flex stretch, and 8px gap — the child owns its own border and padding. | The control(s), their state, and action wiring. | `@kerfjs/ui/list-inset-control` | [Pane geometry](../README.md#component-subpaths) |
|
|
73
78
|
| Inset bare text — `ListInsetText` | A plain string or inline content with no margin, border, or padding of its own must sit in a `.kui-content` list with its text edge aligned to the bordered items around it. | Do not use it to wrap a component that already owns content-item geometry (`ListItem`, `StateBanner`, a `.kui-content-item`) — that double-insets it. Do not hand-roll the 8px margin / 1px border / 8px padding. | Pass the text or inline `SafeHtml` as children; it supplies the 8px inline margin, 1px transparent border, and 8px padding so the text edge lands at the standard 17px inset. Pass `horizontalOnly` to keep that horizontal inset but drop the vertical margin, border, and padding for tight text layout. | The text, copy, and localization. | `@kerfjs/ui/list-inset-text` | [Pane geometry](../README.md#component-subpaths) |
|
|
74
|
-
| Application
|
|
79
|
+
| Application column — `Pane` | A sidebar, main area, inspector, or dialog needs shared vertical header/content/footer organization, one scroll owner, and optional edge separators. | Do not pad the pane shell, wrap child-owned geometry in competing insets, invent unrelated centered measures, or leave an icon-only rail for a hidden pane. | Import `Pane`, provide optional fixed header/footer slots around its primary vertical content, and opt into only the needed logical-edge separators; add `.kui-content-item` as needed. A visible pane owns collapse in its toolbar; move a hidden inline-start pane's restore control to the main toolbar leading edge and an inline-end pane's restore control to its trailing edge. | Layout hierarchy, reading width, responsive relocation, pane visibility state, slot semantics, labels, and separator placement. | `@kerfjs/ui/pane` | [Pane anatomy](./layout.md#anatomy) |
|
|
80
|
+
| Lowered content surface — `SunkenPanel` | A main work area or nested content group needs one visually lowered surface with a compact inset and vertical stack. | Do not use it merely to add padding, as a replacement for `Pane` anatomy, or around children that already own the same outer surface. | Compose the application-owned content as children and pass `ariaLabel` only when the surface is a distinct named region. | Region semantics, accessible name, scroll ownership, child ordering, and content. | `@kerfjs/ui/sunken-panel` | [SunkenPanel](./sunken-panel.md) |
|
|
75
81
|
| Menu composition | Navigation sections need full-size rows and the same content-item geometry as every other pane. | Do not add sidebar-specific wrapper padding, shrink targets to icon size, nest an interactive trailing control in `ListItem`, or use a chevron on a row that does not disclose content. Use ordinary links for a different navigation contract. | Compose `ListHeader`, `ListItem`, and `ListActionRow` in `.kui-content`; use `ListHeader` toggle mode with real controlled content, and use `.kui-content-item` for other surfaces plus a pane footer for toolbar actions. | Information architecture, disclosure content and state, responsive drawer/shell behavior, and token overrides. | `@kerfjs/ui/layout.css` | [Pane geometry](../README.md#pane-and-content-geometry) |
|
|
76
82
|
| Resizable application pane — `ResizableRegion`, `clampRegionSize`, `resizeRegionFromPointer` | A controlled split pane needs the Kerf separator, collapse state, pointer plus keyboard resizing, or a product-specific decorative grip. | Do not use it for a static two-column layout; use CSS grid. Prefer it over Web Awesome `wa-split-panel` unless that component's distinct API is required. Keep `handleIcon` noninteractive. | Call `wireResizableRegions` from `@kerfjs/ui/wire-resizable-regions` once and retain its disposer. | Size signal, min/max policy, collapse policy, persistence, and optional decorative handle icon. | `@kerfjs/ui/resizable-region` | [ResizableRegion contract](./accessibility.md#resizableregion) |
|
|
77
83
|
| One application tab — `AppTab` | A controlled app tab needs selection, close, drag, leading/trailing anatomy, safe domain metadata, or a product-specific close glyph. | Do not render it alone or use it for a small settings choice; compose in `TabBar`, or use `SegmentedControl`. Keep `closeIcon` noninteractive. | Compose in `TabBar`; let `wireTabBars` manage interaction. Put only domain `data-*` values in `rootAttributes`. | Tab identity, order, selection, close policy, content, and domain metadata values. | `@kerfjs/ui/app-tab` | [Tabs contract](./accessibility.md#tabs) |
|
|
@@ -142,8 +148,7 @@ Correct: let the pane stay unpadded while its children own the shared 8/1/8
|
|
|
142
148
|
geometry and 44px targets.
|
|
143
149
|
|
|
144
150
|
```tsx
|
|
145
|
-
<aside
|
|
146
|
-
<nav class="kui-pane__content kui-content">
|
|
151
|
+
<Pane element="aside" contentElement="nav" label="Workspace" contentLabel="Workspace pages">
|
|
147
152
|
<section>
|
|
148
153
|
<ListHeader label="Workspace" />
|
|
149
154
|
<ListItem action="open" label="Inbox" icon={inboxIcon} />
|
|
@@ -156,8 +161,7 @@ geometry and 44px targets.
|
|
|
156
161
|
/>
|
|
157
162
|
</section>
|
|
158
163
|
<div class="kui-content-item">Workspace details</div>
|
|
159
|
-
|
|
160
|
-
</aside>
|
|
164
|
+
</Pane>
|
|
161
165
|
```
|
|
162
166
|
|
|
163
167
|
Incorrect: duplicating component-like rows and compensating for nested padding
|
|
@@ -50,16 +50,14 @@ toolbar groups use 8px. Rows and actions keep 44px targets.
|
|
|
50
50
|
Use the package composition so the geometry has one owner:
|
|
51
51
|
|
|
52
52
|
```tsx
|
|
53
|
-
<aside
|
|
54
|
-
<nav class="kui-pane__content kui-content">
|
|
53
|
+
<Pane element="aside" contentElement="nav" label="Workspace" contentLabel="Workspace pages">
|
|
55
54
|
<section>
|
|
56
55
|
<ListHeader label="Workspace" />
|
|
57
56
|
<ListItem action="open" label="Inbox" icon={inboxIcon} />
|
|
58
57
|
<ListItem action="open" label="Drafts" />
|
|
59
58
|
</section>
|
|
60
59
|
<div class="kui-content-item">Panel contents</div>
|
|
61
|
-
|
|
62
|
-
</aside>
|
|
60
|
+
</Pane>
|
|
63
61
|
```
|
|
64
62
|
|
|
65
63
|
Do not pad the pane and then pad every wrapper. That duplicates the geometry
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../ai/application-ui-profile.schema.json",
|
|
3
|
+
"schemaVersion": 1,
|
|
4
|
+
"scope": "workspace",
|
|
5
|
+
"catalogs": [
|
|
6
|
+
{
|
|
7
|
+
"package": "@kerfjs/ui",
|
|
8
|
+
"selection": {
|
|
9
|
+
"path": "../../ai/component-catalog.json",
|
|
10
|
+
"schemaVersion": 1
|
|
11
|
+
},
|
|
12
|
+
"composition": {
|
|
13
|
+
"path": "../../ai/component-catalog-v2.json",
|
|
14
|
+
"schemaVersion": 2
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"package": "@acme/ui",
|
|
19
|
+
"composition": {
|
|
20
|
+
"path": "./component-catalog-extension-v2.json",
|
|
21
|
+
"schemaVersion": 2
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"preferences": {
|
|
26
|
+
"application-shell": {
|
|
27
|
+
"preferred": "@kerfjs/ui:recipe-app-shell",
|
|
28
|
+
"rationale": "The workspace uses the standard resizable three-region shell."
|
|
29
|
+
},
|
|
30
|
+
"record-inspector": {
|
|
31
|
+
"preferred": "@acme/ui:inspector",
|
|
32
|
+
"rationale": "The application-owned inspector maps record policy onto Kerf panel primitives."
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"theme": {
|
|
36
|
+
"colorScheme": "light",
|
|
37
|
+
"allowedColorSchemes": ["light", "dark"],
|
|
38
|
+
"density": "standard",
|
|
39
|
+
"allowedDensities": ["standard"]
|
|
40
|
+
},
|
|
41
|
+
"tokens": {
|
|
42
|
+
"--kui-color-brand-fill-normal": "oklch(0.62 0.16 255)"
|
|
43
|
+
},
|
|
44
|
+
"layout": {
|
|
45
|
+
"shell": "@kerfjs/ui:recipe-app-shell",
|
|
46
|
+
"pane": "@kerfjs/ui:pane",
|
|
47
|
+
"responsiveStrategy": "device-class",
|
|
48
|
+
"compactNavigation": "nav-stack",
|
|
49
|
+
"scrollOwnership": "one-per-pane",
|
|
50
|
+
"spacingScale": "kerf-five-step"
|
|
51
|
+
},
|
|
52
|
+
"exceptions": [
|
|
53
|
+
{
|
|
54
|
+
"id": "legacy-report-grid",
|
|
55
|
+
"rules": ["KUI-C101"],
|
|
56
|
+
"target": "src/features/legacy-report/report-grid.tsx",
|
|
57
|
+
"rationale": "The embedded report engine owns this isolated toolbar-compatible control row."
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|