@kerfjs/ui 5.0.0-beta.67 → 5.0.0-beta.69

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 (103) hide show
  1. package/README.md +3 -0
  2. package/ai/application-ui-profile-sync.cjs +20 -1
  3. package/ai/application-ui-profile.d.ts +4 -0
  4. package/ai/application-ui-profile.mjs +27 -0
  5. package/ai/application-ui-profile.schema.json +10 -0
  6. package/ai/component-catalog-extension.schema.json +12 -0
  7. package/ai/component-catalog.json +14 -4
  8. package/ai/component-catalog.schema.json +14 -0
  9. package/ai/component-composition-rules.json +2 -2
  10. package/ai/component-composition.d.ts +2 -0
  11. package/ai/component-composition.json +2 -2
  12. package/ai/component-composition.schema.json +24 -0
  13. package/ai/components/loading-spinner.md +1 -0
  14. package/ai/components/tab-bar.md +5 -4
  15. package/ai/components/toolbar-control-group.md +2 -2
  16. package/ai/public-api-signatures-v1.md +34 -14
  17. package/ai/skill.md +18 -3
  18. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  19. package/analyzer/index.d.mts +1 -0
  20. package/analyzer/index.mjs +108 -12
  21. package/dist/catalog.js +4 -4
  22. package/dist/catalog.js.map +1 -1
  23. package/dist/{chunk-P4UXSYIJ.js → chunk-2ZOLX3IE.js} +2 -2
  24. package/dist/{chunk-P4UXSYIJ.js.map → chunk-2ZOLX3IE.js.map} +1 -1
  25. package/dist/{chunk-E36EVCKG.js → chunk-47CKKLBE.js} +2 -2
  26. package/dist/{chunk-E36EVCKG.js.map → chunk-47CKKLBE.js.map} +1 -1
  27. package/dist/{chunk-JCV4GXWB.js → chunk-6DFDTWGG.js} +100 -61
  28. package/dist/chunk-6DFDTWGG.js.map +1 -0
  29. package/dist/{chunk-IAOAZUOO.js → chunk-7OKX3EF6.js} +2 -2
  30. package/dist/{chunk-IAOAZUOO.js.map → chunk-7OKX3EF6.js.map} +1 -1
  31. package/dist/{chunk-C74LITXE.js → chunk-B3XWUX3A.js} +2 -2
  32. package/dist/{chunk-C74LITXE.js.map → chunk-B3XWUX3A.js.map} +1 -1
  33. package/dist/{chunk-F7OCOBTG.js → chunk-CRVDF4A6.js} +4 -1
  34. package/dist/chunk-CRVDF4A6.js.map +1 -0
  35. package/dist/{chunk-TGM424PI.js → chunk-H7IODXGY.js} +3 -3
  36. package/dist/{chunk-TGM424PI.js.map → chunk-H7IODXGY.js.map} +1 -1
  37. package/dist/chunk-KVBRMEPM.js +44 -0
  38. package/dist/chunk-KVBRMEPM.js.map +1 -0
  39. package/dist/{chunk-EN4LDR5Y.js → chunk-NC4CFOID.js} +2 -2
  40. package/dist/{chunk-EN4LDR5Y.js.map → chunk-NC4CFOID.js.map} +1 -1
  41. package/dist/{chunk-5SYE4CCT.js → chunk-PQZPVBP4.js} +25 -18
  42. package/dist/chunk-PQZPVBP4.js.map +1 -0
  43. package/dist/{chunk-NXWE5S57.js → chunk-QJ5VFCW3.js} +12 -3
  44. package/dist/chunk-QJ5VFCW3.js.map +1 -0
  45. package/dist/{chunk-RKTTF3L4.js → chunk-X3TPECWG.js} +1 -1
  46. package/dist/chunk-X3TPECWG.js.map +1 -0
  47. package/dist/{chunk-VY6YRCTV.js → chunk-X3X4KRDK.js} +2 -2
  48. package/dist/{chunk-VY6YRCTV.js.map → chunk-X3X4KRDK.js.map} +1 -1
  49. package/dist/collapsible-panel.js +2 -2
  50. package/dist/empty-state.js +2 -1
  51. package/dist/index.js +10 -10
  52. package/dist/list-action-row.js +1 -1
  53. package/dist/list-item.js +1 -1
  54. package/dist/loading-spinner.d.ts +7 -2
  55. package/dist/loading-spinner.js +2 -1
  56. package/dist/nav-stack.js +1 -1
  57. package/dist/select.d.ts +4 -2
  58. package/dist/select.js +1 -1
  59. package/dist/split-view.js +1 -1
  60. package/dist/styles/app-tab.css +29 -0
  61. package/dist/styles/loading-spinner.css +4 -0
  62. package/dist/styles/select.css +79 -0
  63. package/dist/styles/tab-bar.css +12 -8
  64. package/dist/styles/token-search-field.css +28 -10
  65. package/dist/styles/toolbar-control-group.css +96 -3
  66. package/dist/styles/webawesome.css +17 -0
  67. package/dist/tab-bar.d.ts +2 -0
  68. package/dist/tab-bar.js +1 -1
  69. package/dist/token-search-field.js +1 -1
  70. package/dist/toolbar-control-group.d.ts +13 -5
  71. package/dist/toolbar-control-group.js +2 -1
  72. package/dist/wire-catalog.js +1 -1
  73. package/dist/wire-scroll-dividers.d.ts +7 -4
  74. package/dist/wire-scroll-dividers.js +1 -1
  75. package/dist/wire-token-search-fields.js +1 -1
  76. package/dist/workbench.js +1 -1
  77. package/docs/accessibility.md +14 -0
  78. package/docs/apple-hig.md +1 -0
  79. package/docs/catalog.md +3 -0
  80. package/docs/component-contract.md +65 -10
  81. package/docs/component-selection.md +24 -6
  82. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -1
  83. package/docs/design/templates/toolbar/title-and-actions.svg +1 -1
  84. package/docs/design/templates/toolbar-dark.svg +1 -1
  85. package/docs/design/templates/toolbar.svg +1 -1
  86. package/docs/examples/component-catalog-extension.json +1 -0
  87. package/docs/layout.md +6 -0
  88. package/docs/ui-analyzer.md +12 -0
  89. package/docs/ui-doctor.md +15 -3
  90. package/docs/webawesome-theme.md +7 -0
  91. package/doctor/cli.mjs +5 -0
  92. package/doctor/config.schema.json +2 -0
  93. package/doctor/index.d.mts +3 -0
  94. package/doctor/index.mjs +27 -1
  95. package/llms.txt +15 -5
  96. package/package.json +1 -1
  97. package/dist/chunk-5SYE4CCT.js.map +0 -1
  98. package/dist/chunk-F7OCOBTG.js.map +0 -1
  99. package/dist/chunk-JCV4GXWB.js.map +0 -1
  100. package/dist/chunk-NXWE5S57.js.map +0 -1
  101. package/dist/chunk-RKTTF3L4.js.map +0 -1
  102. package/dist/chunk-ZLWQZUGF.js +0 -27
  103. package/dist/chunk-ZLWQZUGF.js.map +0 -1
package/README.md CHANGED
@@ -418,6 +418,9 @@ restyle). It reports repeated insets, exceptional spacing shorthands, and
418
418
  dynamic classes separately as review findings. Text, versioned JSON,
419
419
  and SARIF outputs carry stable `KUI-L###` ids, repository-relative locations,
420
420
  evidence, and ownership chains. See the [analyzer guide](./docs/ui-analyzer.md).
421
+ Opt into ownership between components in one package with Doctor's
422
+ `"ownership": "component"` setting and catalog `styleSources` declarations;
423
+ the default keeps package-level ownership.
421
424
 
422
425
  ### Browser-backed integration evaluation
423
426
 
@@ -28,7 +28,7 @@ function mergeApplicationUiProfiles(layers) {
28
28
  catalogs.set(catalog.package, catalog);
29
29
  provenance[`$catalogs.${catalog.package}`] = source;
30
30
  }
31
- for (const field of ['preferences', 'theme', 'tokens', 'layout'])
31
+ for (const field of ['preferences', 'theme', 'tokens', 'layout', 'wiring'])
32
32
  if (
33
33
  layer[field] &&
34
34
  typeof layer[field] === 'object' &&
@@ -166,6 +166,7 @@ function validateApplicationUiProfile(profile, options = {}) {
166
166
  'theme',
167
167
  'tokens',
168
168
  'layout',
169
+ 'wiring',
169
170
  'exceptions',
170
171
  ],
171
172
  '$',
@@ -183,7 +184,25 @@ function validateApplicationUiProfile(profile, options = {}) {
183
184
  const theme = objectAt(profile.theme, '$.theme');
184
185
  const tokens = objectAt(profile.tokens, '$.tokens') ?? {};
185
186
  const layout = objectAt(profile.layout, '$.layout');
187
+ const wiring = objectAt(profile.wiring, '$.wiring');
186
188
  const exceptions = arrayAt(profile.exceptions, '$.exceptions');
189
+ rejectUnknown(wiring, ['entries'], '$.wiring');
190
+ if (wiring?.entries !== undefined) {
191
+ if (
192
+ !stringList(wiring.entries) ||
193
+ wiring.entries.some(
194
+ (entry) =>
195
+ isAbsolute(entry) ||
196
+ entry.split('/').includes('..') ||
197
+ /[*?]/.test(entry),
198
+ )
199
+ )
200
+ add(
201
+ 'KUI-P030',
202
+ '$.wiring.entries',
203
+ 'Wiring entries must be unique workspace-relative paths without traversal or wildcards.',
204
+ );
205
+ }
187
206
  const packages = new Set();
188
207
  for (const [index, catalog] of catalogs.entries()) {
189
208
  const path = `$.catalogs[${index}]`;
@@ -39,6 +39,10 @@ export interface ApplicationUiProfile {
39
39
  scrollOwnership?: 'one-per-pane' | 'application';
40
40
  spacingScale?: 'kerf-five-step' | 'application';
41
41
  };
42
+ wiring?: {
43
+ /** Static application entry modules, relative to the workspace root. */
44
+ entries?: string[];
45
+ };
42
46
  exceptions?: Array<{
43
47
  id: string;
44
48
  rules: string[];
@@ -123,6 +123,14 @@ export function mergeApplicationUiProfiles(layers) {
123
123
  profile.layout = { ...profile.layout, ...layer.layout };
124
124
  recordProvenance(provenance, source, '$layout', layer.layout);
125
125
  }
126
+ if (
127
+ layer.wiring &&
128
+ typeof layer.wiring === 'object' &&
129
+ !Array.isArray(layer.wiring)
130
+ ) {
131
+ profile.wiring = { ...profile.wiring, ...layer.wiring };
132
+ recordProvenance(provenance, source, '$wiring', layer.wiring);
133
+ }
126
134
  for (const exception of Array.isArray(layer.exceptions)
127
135
  ? layer.exceptions
128
136
  : []) {
@@ -250,6 +258,7 @@ export function validateApplicationUiProfile(
250
258
  'theme',
251
259
  'tokens',
252
260
  'layout',
261
+ 'wiring',
253
262
  'exceptions',
254
263
  ],
255
264
  '$',
@@ -269,7 +278,25 @@ export function validateApplicationUiProfile(
269
278
  const theme = objectAt(profile?.theme, '$.theme');
270
279
  const tokens = objectAt(profile?.tokens, '$.tokens') ?? {};
271
280
  const layout = objectAt(profile?.layout, '$.layout');
281
+ const wiring = objectAt(profile?.wiring, '$.wiring');
272
282
  const exceptions = arrayAt(profile?.exceptions, '$.exceptions');
283
+ rejectUnknown(wiring, ['entries'], '$.wiring');
284
+ if (wiring?.entries !== undefined) {
285
+ if (
286
+ !stringList(wiring.entries) ||
287
+ wiring.entries.some(
288
+ (entry) =>
289
+ isAbsolute(entry) ||
290
+ entry.split('/').includes('..') ||
291
+ /[*?]/.test(entry),
292
+ )
293
+ )
294
+ add(
295
+ 'KUI-P030',
296
+ '$.wiring.entries',
297
+ 'Wiring entries must be unique workspace-relative paths without traversal or wildcards.',
298
+ );
299
+ }
273
300
 
274
301
  const catalogPackages = new Set();
275
302
  for (const [index, catalog] of catalogs.entries()) {
@@ -25,6 +25,16 @@
25
25
  "additionalProperties": { "type": "string", "minLength": 1 }
26
26
  },
27
27
  "layout": { "$ref": "#/$defs/layout" },
28
+ "wiring": {
29
+ "type": "object",
30
+ "properties": {
31
+ "entries": {
32
+ "description": "Application entry modules, relative to the workspace root, where application-scoped wiring is installed.",
33
+ "$ref": "#/$defs/stringList"
34
+ }
35
+ },
36
+ "additionalProperties": false
37
+ },
28
38
  "exceptions": {
29
39
  "type": "array",
30
40
  "items": { "$ref": "#/$defs/exception" }
@@ -59,6 +59,18 @@
59
59
  "uniqueItems": true
60
60
  },
61
61
  "source": { "$ref": "#/$defs/repositoryPath" },
62
+ "styleSources": {
63
+ "description": "Stylesheets owned by this component, relative to the consumer package root. Used by Doctor's opt-in component ownership mode.",
64
+ "type": "array",
65
+ "items": {
66
+ "allOf": [
67
+ { "$ref": "#/$defs/repositoryPath" },
68
+ { "pattern": "\\.css$" }
69
+ ]
70
+ },
71
+ "minItems": 1,
72
+ "uniqueItems": true
73
+ },
62
74
  "demoSource": { "$ref": "#/$defs/repositoryPath" },
63
75
  "geometry": { "$ref": "#/$defs/geometry" },
64
76
  "documentation": { "type": "string", "minLength": 1 }
@@ -3,6 +3,13 @@
3
3
  "schemaVersion": 1,
4
4
  "package": "@kerfjs/ui",
5
5
  "description": "Machine-readable selection, composition, delivery, ownership, and accessibility metadata for Kerf UI and its supported Web Awesome set.",
6
+ "wiringProviders": [
7
+ {
8
+ "export": "wireCatalog",
9
+ "import": "@kerfjs/ui/wire-catalog",
10
+ "provides": ["wireScrollDividers"]
11
+ }
12
+ ],
6
13
  "entries": [
7
14
  {
8
15
  "id": "badge",
@@ -1591,7 +1598,7 @@
1591
1598
  "useWhen": [
1592
1599
  "Group related controls inside a toolbar with a supported visual treatment.",
1593
1600
  "Use ToolbarActionLink for navigation or external resources that must retain native anchor semantics inside a group.",
1594
- "Fill it with plain <button> controls (the group styles > button fully); use wa-button only for a Web Awesome feature. A popup menu is a PopupMenu in a single group with nestedDropdown; it renders its own trigger, so never hand-write wa-dropdown markup. Nothing wraps inside a group: an icon sits beside its label on one row and no trigger gets a fixed width.",
1601
+ "Fill it with plain <button> controls (the group styles > button fully); use wa-button only for a Web Awesome feature. A popup menu is a PopupMenu in a single group with nestedDropdown; it renders its own trigger, so never hand-write wa-dropdown markup. Each control stays whole: an icon sits beside its label on one row and no trigger gets a fixed width. Use overflow=wrap for independent links that should move as whole actions to another row on narrow surfaces.",
1595
1602
  "For an avatar control, set content=avatar and avatarImage instead of inserting an img; one button paints the group and multiple buttons paint only the pressed highlight.",
1596
1603
  "In a Workbench or CollapsiblePanel toolbar, mark a group relocateOnCollapse to keep it available in the work-area toolbar when the panel closes; place it directly or in an array in any toolbar zone."
1597
1604
  ],
@@ -1628,7 +1635,7 @@
1628
1635
  "brand, neutral, or pop selected tone",
1629
1636
  "nested dropdown trigger",
1630
1637
  "standard, compact, or list-zero dropdown menu inset",
1631
- "visible or horizontally scrollable overflow",
1638
+ "visible, horizontally scrollable, or whole-action wrapping overflow",
1632
1639
  "always visible or compact-only responsive visibility",
1633
1640
  "relocate on panel collapse",
1634
1641
  "avatar scrim",
@@ -3592,7 +3599,8 @@
3592
3599
  "description": "Controlled tab strips with typed presentation and allocation, a scroll-owning tab region, an optional adjacent trailing action, and a separately pinned far-edge end action.",
3593
3600
  "uses": [
3594
3601
  "tabs",
3595
- "toolbar-control-group"
3602
+ "toolbar-control-group",
3603
+ "wa-button"
3596
3604
  ],
3597
3605
  "purpose": "Controlled tab strips whose tabs shrink and scroll while fixed leading, trailing, and far-edge end actions remain visible.",
3598
3606
  "publicExports": [
@@ -3637,6 +3645,7 @@
3637
3645
  "intrinsic or fill allocation",
3638
3646
  "separate or adjacent trailing action",
3639
3647
  "far-edge end action",
3648
+ "standalone Web Awesome action in trailing or end",
3640
3649
  "overflow dividers on each side with tabs scrolled out of view (wired by wireScrollDividers)"
3641
3650
  ],
3642
3651
  "accessibility": [
@@ -4691,7 +4700,8 @@
4691
4700
  ],
4692
4701
  "variants": [
4693
4702
  "labeled",
4694
- "decorative"
4703
+ "decorative",
4704
+ "sized"
4695
4705
  ],
4696
4706
  "accessibility": [
4697
4707
  "Labeled spinners use role img; decorative spinners are hidden and stop for reduced motion."
@@ -9,6 +9,20 @@
9
9
  "schemaVersion": { "const": 1 },
10
10
  "package": { "const": "@kerfjs/ui" },
11
11
  "description": { "type": "string", "minLength": 1 },
12
+ "wiringProviders": {
13
+ "description": "Public helpers whose call installs other cataloged wiring helpers.",
14
+ "type": "array",
15
+ "items": {
16
+ "type": "object",
17
+ "required": ["export", "import", "provides"],
18
+ "properties": {
19
+ "export": { "type": "string", "minLength": 1 },
20
+ "import": { "type": "string", "minLength": 1 },
21
+ "provides": { "type": "array", "items": { "type": "string", "minLength": 1 }, "minItems": 1, "uniqueItems": true }
22
+ },
23
+ "additionalProperties": false
24
+ }
25
+ },
12
26
  "entries": {
13
27
  "type": "array",
14
28
  "items": { "$ref": "#/$defs/entry" }
@@ -171,14 +171,14 @@
171
171
  {
172
172
  "id": "trailing",
173
173
  "jsx": { "prop": "trailing" },
174
- "accepts": ["toolbar-control-group"],
174
+ "accepts": ["toolbar-control-group", "wa-button"],
175
175
  "cardinality": { "min": 0, "max": 1 },
176
176
  "exclusiveWith": []
177
177
  },
178
178
  {
179
179
  "id": "end",
180
180
  "jsx": { "prop": "end" },
181
- "accepts": ["toolbar-control-group"],
181
+ "accepts": ["toolbar-control-group", "wa-button"],
182
182
  "cardinality": { "min": 0, "max": 1 },
183
183
  "exclusiveWith": []
184
184
  }
@@ -85,6 +85,8 @@ export interface CatalogCompositionEntry {
85
85
  wiring: {
86
86
  required: boolean;
87
87
  helpers: string[];
88
+ /** Defining source files for application-owned helpers, relative to the catalog package root. */
89
+ sources?: Array<{ export: string; source: string }>;
88
90
  obligations: string[];
89
91
  /** Wiring-owned `data-*` attributes a helper writes at runtime. Optional
90
92
  * for extension catalogs; first-party entries always list it. */
@@ -3024,14 +3024,14 @@
3024
3024
  {
3025
3025
  "id": "trailing",
3026
3026
  "jsx": { "prop": "trailing" },
3027
- "accepts": ["toolbar-control-group"],
3027
+ "accepts": ["toolbar-control-group", "wa-button"],
3028
3028
  "cardinality": { "min": 0, "max": 1 },
3029
3029
  "exclusiveWith": []
3030
3030
  },
3031
3031
  {
3032
3032
  "id": "end",
3033
3033
  "jsx": { "prop": "end" },
3034
- "accepts": ["toolbar-control-group"],
3034
+ "accepts": ["toolbar-control-group", "wa-button"],
3035
3035
  "cardinality": { "min": 0, "max": 1 },
3036
3036
  "exclusiveWith": []
3037
3037
  }
@@ -159,6 +159,16 @@
159
159
  },
160
160
  "sourceLinks": { "$ref": "#/$defs/stringList" },
161
161
  "source": { "type": "string", "minLength": 1 },
162
+ "styleSources": {
163
+ "description": "Stylesheets owned by this component, relative to the catalog package root. Required for an entry to own its CSS in opt-in component ownership mode when no selection catalog supplies it.",
164
+ "type": "array",
165
+ "items": {
166
+ "type": "string",
167
+ "pattern": "^(?!.*(?:^|/)\\.{1,2}(?:/|$))[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+)*\\.css$"
168
+ },
169
+ "minItems": 1,
170
+ "uniqueItems": true
171
+ },
162
172
  "rendersAs": {
163
173
  "description": "For a wrapper component: the cataloged roots it renders (package-qualified keys), any one of them or nothing. Composition checks treat an element of this entry as each declared root: a zone accepts it only if it accepts every root, and each root's parent contract applies to where it is placed.",
164
174
  "type": "array",
@@ -214,7 +224,21 @@
214
224
  "required": ["required", "helpers", "obligations"],
215
225
  "properties": {
216
226
  "required": { "type": "boolean" },
227
+ "scope": { "enum": ["application", "module"] },
217
228
  "helpers": { "$ref": "#/$defs/stringList" },
229
+ "sources": {
230
+ "description": "Application-owned helper exports and their defining source files, relative to the catalog package root.",
231
+ "type": "array",
232
+ "items": {
233
+ "type": "object",
234
+ "required": ["export", "source"],
235
+ "properties": {
236
+ "export": { "type": "string", "minLength": 1 },
237
+ "source": { "type": "string", "minLength": 1 }
238
+ },
239
+ "additionalProperties": false
240
+ }
241
+ },
218
242
  "obligations": { "$ref": "#/$defs/stringList" },
219
243
  "stateAttributes": {
220
244
  "description": "Wiring-owned state attributes: data-* attributes a wiring helper writes at runtime, which the application must not render, remove, or treat as its own state. Optional for extension catalogs; first-party entries always list it, empty when their helpers write none.",
@@ -34,6 +34,7 @@ Exact prop names and types: [`@kerfjs/ui/loading-spinner`](../public-api-signatu
34
34
 
35
35
  - labeled
36
36
  - decorative
37
+ - sized
37
38
 
38
39
  ## Composition
39
40
 
@@ -45,6 +45,7 @@ Exact prop names and types: [`@kerfjs/ui/tab-bar`](../public-api-signatures-v1.m
45
45
  - intrinsic or fill allocation
46
46
  - separate or adjacent trailing action
47
47
  - far-edge end action
48
+ - standalone Web Awesome action in trailing or end
48
49
  - overflow dividers on each side with tabs scrolled out of view (wired by wireScrollDividers)
49
50
 
50
51
  ## Composition
@@ -52,14 +53,14 @@ Exact prop names and types: [`@kerfjs/ui/tab-bar`](../public-api-signatures-v1.m
52
53
  - Parents: any.
53
54
  - Contexts: `tabpanel-navigation`.
54
55
  - Children: only `app-tab` (required: `app-tab`).
55
- - Built from: [AppTab](./tabs.md), [ToolbarControlGroup](./toolbar-control-group.md).
56
+ - Built from: [AppTab](./tabs.md), [ToolbarControlGroup](./toolbar-control-group.md), [Button](./wa-button.md).
56
57
 
57
58
  Zones (a zone is bound to JSX only through its listed prop; never assume the zone id is a prop):
58
59
 
59
60
  - `tabs` — JSX prop `children`; accepts [AppTab](./tabs.md); at least 1.
60
61
  - `leading` — JSX prop `leading`; accepts [ToolbarControlGroup](./toolbar-control-group.md); 0–1.
61
- - `trailing` — JSX prop `trailing`; accepts [ToolbarControlGroup](./toolbar-control-group.md); 0–1.
62
- - `end` — JSX prop `end`; accepts [ToolbarControlGroup](./toolbar-control-group.md); 0–1.
62
+ - `trailing` — JSX prop `trailing`; accepts [ToolbarControlGroup](./toolbar-control-group.md), [Button](./wa-button.md); 0–1.
63
+ - `end` — JSX prop `end`; accepts [ToolbarControlGroup](./toolbar-control-group.md), [Button](./wa-button.md); 0–1.
63
64
 
64
65
  ## State and wiring
65
66
 
@@ -107,6 +108,6 @@ Everything else is internal: `kerf-ui-analyze` reports private or unknown `.kui-
107
108
 
108
109
  ## Related
109
110
 
110
- - Components: [AppTab](./tabs.md), [ToolbarControlGroup](./toolbar-control-group.md), [SegmentedControl](./segmented-control.md), [Select](./select.md), [Tab Group](./wa-tab-group.md).
111
+ - Components: [AppTab](./tabs.md), [ToolbarControlGroup](./toolbar-control-group.md), [Button](./wa-button.md), [SegmentedControl](./segmented-control.md), [Select](./select.md), [Tab Group](./wa-tab-group.md).
111
112
  - Docs: [`docs/component-selection.md`](../../docs/component-selection.md), [`docs/accessibility.md#tabs`](../../docs/accessibility.md#tabs).
112
113
  - UX catalog route: `?component=tab-bar`.
@@ -12,7 +12,7 @@ Provide complete toolbar action geometry through configuration, including semant
12
12
 
13
13
  - Group related controls inside a toolbar with a supported visual treatment.
14
14
  - Use ToolbarActionLink for navigation or external resources that must retain native anchor semantics inside a group.
15
- - Fill it with plain \<button> controls (the group styles > button fully); use wa-button only for a Web Awesome feature. A popup menu is a PopupMenu in a single group with nestedDropdown; it renders its own trigger, so never hand-write wa-dropdown markup. Nothing wraps inside a group: an icon sits beside its label on one row and no trigger gets a fixed width.
15
+ - Fill it with plain \<button> controls (the group styles > button fully); use wa-button only for a Web Awesome feature. A popup menu is a PopupMenu in a single group with nestedDropdown; it renders its own trigger, so never hand-write wa-dropdown markup. Each control stays whole: an icon sits beside its label on one row and no trigger gets a fixed width. Use overflow=wrap for independent links that should move as whole actions to another row on narrow surfaces.
16
16
  - For an avatar control, set content=avatar and avatarImage instead of inserting an img; one button paints the group and multiple buttons paint only the pressed highlight.
17
17
  - In a Workbench or CollapsiblePanel toolbar, mark a group relocateOnCollapse to keep it available in the work-area toolbar when the panel closes; place it directly or in an array in any toolbar zone.
18
18
 
@@ -49,7 +49,7 @@ Exact prop names and types: [`@kerfjs/ui/toolbar-control-group`](../public-api-s
49
49
  - brand, neutral, or pop selected tone
50
50
  - nested dropdown trigger
51
51
  - standard, compact, or list-zero dropdown menu inset
52
- - visible or horizontally scrollable overflow
52
+ - visible, horizontally scrollable, or whole-action wrapping overflow
53
53
  - always visible or compact-only responsive visibility
54
54
  - relocate on panel collapse
55
55
  - avatar scrim
@@ -1,6 +1,6 @@
1
1
  # Public API signatures for the UI authoring corpus
2
2
 
3
- Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.67` and `kerfjs@5.0.0-beta.67`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
3
+ Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.69` and `kerfjs@5.0.0-beta.69`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
4
4
 
5
5
  ## `@kerfjs/ui/css-values`
6
6
 
@@ -275,9 +275,11 @@ type ToolbarControlGroupContent = 'icon' | 'text' | 'mixed' | 'avatar' | 'search
275
275
  type ToolbarControlGroupFocusRing = 'control' | 'outline' | 'halo';
276
276
  type ToolbarControlGroupSelectedChrome = 'raised' | 'filled' | 'outline';
277
277
  type ToolbarControlGroupSelectedTone = 'brand' | 'neutral' | 'pop';
278
- type ToolbarControlGroupOverflow = 'visible' | 'scroll';
278
+ type ToolbarControlGroupOverflow = 'visible' | 'scroll' | 'wrap';
279
279
  type ToolbarControlGroupMenuInset = 'standard' | 'compact' | 'list-zero';
280
- type ToolbarControlGroupVisibility = 'always' | 'compact-only';
280
+ type ToolbarControlGroupVisibility = 'always' | 'compact-only' | 'hide-collapsed-tiny' | 'yield-to-expanded-sibling';
281
+ type ToolbarControlGroupSizing = 'content' | 'grow' | 'fill';
282
+ type ToolbarControlGroupPlacement = 'start' | 'end';
281
283
  interface ToolbarActionLinkProps {
282
284
  href: string;
283
285
  label: string;
@@ -316,10 +318,16 @@ interface ToolbarControlGroupProps {
316
318
  nestedDropdown?: boolean;
317
319
  /** Configure the nested dropdown menu inset without consumer ::part() CSS. */
318
320
  menuInset?: ToolbarControlGroupMenuInset;
319
- /** Keep an overlong row of actions inside the available width with horizontal scrolling. */
321
+ /** Keep an overlong row inside the available width by scrolling or wrapping. */
320
322
  overflow?: ToolbarControlGroupOverflow;
321
323
  /** Responsive visibility owned by the enclosing Toolbar container. */
322
324
  visibility?: ToolbarControlGroupVisibility;
325
+ /** Intrinsic (default), grow from a basis, or occupy a full wrapping row. */
326
+ sizing?: ToolbarControlGroupSizing;
327
+ /** Place this group at the end of its flex row in a wrapping Toolbar zone. */
328
+ placement?: ToolbarControlGroupPlacement;
329
+ /** CSS length used as the minimum width and flex basis for `sizing="grow"`; defaults to 19rem. */
330
+ growBasis?: string;
323
331
  /** Move this group into the work-area toolbar when its panel collapses. */
324
332
  relocateOnCollapse?: boolean;
325
333
  /** Add contrast behind photo-backed avatar content. */
@@ -336,9 +344,9 @@ interface ToolbarControlGroupProps {
336
344
  /** Native named-slot assignment when composed inside a web component. */
337
345
  slot?: string;
338
346
  }
339
- declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance, shape, size, density, content, focusRing, selectedChrome, selectedTone, nestedDropdown, menuInset, overflow, visibility, relocateOnCollapse, scrim, busy, busyLabel, avatarImage, slot, }: ToolbarControlGroupProps): kerfjs.SafeHtml;
347
+ declare function ToolbarControlGroup({ children, label, className, expanded, single, appearance, tone, buttonAppearance, shape, size, density, content, focusRing, selectedChrome, selectedTone, nestedDropdown, menuInset, overflow, visibility, sizing, placement, growBasis, relocateOnCollapse, scrim, busy, busyLabel, avatarImage, slot, }: ToolbarControlGroupProps): kerfjs.SafeHtml;
340
348
 
341
- export { ToolbarActionLink, type ToolbarActionLinkProps, ToolbarControlGroup, type ToolbarControlGroupAppearance, type ToolbarControlGroupButtonAppearance, type ToolbarControlGroupContent, type ToolbarControlGroupDensity, type ToolbarControlGroupFocusRing, type ToolbarControlGroupMenuInset, type ToolbarControlGroupOverflow, type ToolbarControlGroupProps, type ToolbarControlGroupSelectedChrome, type ToolbarControlGroupSelectedTone, type ToolbarControlGroupShape, type ToolbarControlGroupSize, type ToolbarControlGroupTone, type ToolbarControlGroupVisibility };
349
+ export { ToolbarActionLink, type ToolbarActionLinkProps, ToolbarControlGroup, type ToolbarControlGroupAppearance, type ToolbarControlGroupButtonAppearance, type ToolbarControlGroupContent, type ToolbarControlGroupDensity, type ToolbarControlGroupFocusRing, type ToolbarControlGroupMenuInset, type ToolbarControlGroupOverflow, type ToolbarControlGroupPlacement, type ToolbarControlGroupProps, type ToolbarControlGroupSelectedChrome, type ToolbarControlGroupSelectedTone, type ToolbarControlGroupShape, type ToolbarControlGroupSize, type ToolbarControlGroupSizing, type ToolbarControlGroupTone, type ToolbarControlGroupVisibility };
342
350
  ```
343
351
 
344
352
  ## `@kerfjs/ui/floating-toolbar`
@@ -1078,10 +1086,12 @@ interface TabBarProps {
1078
1086
  label: string;
1079
1087
  children: KerfUiContent;
1080
1088
  leading?: KerfUiContent;
1089
+ /** Tab-local action: a ToolbarControlGroup or a standalone Web Awesome button. */
1081
1090
  trailing?: KerfUiContent;
1082
1091
  /**
1083
1092
  * Action pinned to the far edge of the bar. Combine with an adjacent `trailing`
1084
1093
  * action when the tab-local and workspace-level actions must remain distinct.
1094
+ * Accepts a ToolbarControlGroup or a standalone Web Awesome button.
1085
1095
  */
1086
1096
  end?: KerfUiContent;
1087
1097
  className?: string;
@@ -1186,6 +1196,7 @@ interface WireScrollDividersOptions {
1186
1196
  */
1187
1197
  targets?: readonly ScrollDividerTarget[];
1188
1198
  }
1199
+ type Root = HTMLElement | Document;
1189
1200
  /**
1190
1201
  * Wire scroll dividers below `root`: a divider between pinned chrome and the
1191
1202
  * content that scrolls beside it shows only while content is scrolled away
@@ -1213,11 +1224,13 @@ interface WireScrollDividersOptions {
1213
1224
  *
1214
1225
  * Structure is re-read after every DOM change below root (a re-render that
1215
1226
  * drops the attributes gets them back before paint), scroll is tracked with
1216
- * one capturing listener, and size changes of each scroller and its children
1217
- * with a `ResizeObserver`. Returns a disposer that removes every attribute it
1218
- * wrote. See `docs/23-app-layouts.md` §3.7.
1227
+ * one capturing listener per registered root, and size changes of each
1228
+ * scroller and its children with a `ResizeObserver`. Overlapping registrations
1229
+ * share one attribute writer; each disposer removes its registration, and the
1230
+ * final disposer removes the wiring-owned attributes. See
1231
+ * `docs/23-app-layouts.md` §3.7.
1219
1232
  */
1220
- declare function wireScrollDividers(root: HTMLElement | Document, { targets }?: WireScrollDividersOptions): () => void;
1233
+ declare function wireScrollDividers(root: Root, { targets }?: WireScrollDividersOptions): () => void;
1221
1234
 
1222
1235
  export { type ScrollDividerTarget, type WireScrollDividersOptions, wireScrollDividers };
1223
1236
  ```
@@ -2698,7 +2711,7 @@ type SelectAccessibleName = {
2698
2711
  label?: never;
2699
2712
  ariaLabel: string;
2700
2713
  };
2701
- type SelectPresentation = 'form' | 'toolbar-borderless' | 'navigation';
2714
+ type SelectPresentation = 'form' | 'toolbar-borderless' | 'navigation' | 'inline' | 'title';
2702
2715
  type SelectSize = 'default' | 'compact';
2703
2716
  type SelectSelectedPresentation = 'label' | 'icon-only';
2704
2717
  type SelectFocusRingOwner = 'select' | 'group';
@@ -2715,7 +2728,7 @@ interface SelectBaseProps<Value extends string = string> {
2715
2728
  fitMenu?: boolean;
2716
2729
  /** Render as an unanimated loading skeleton: the label above a static, empty control box. */
2717
2730
  placeholder?: boolean;
2718
- /** Form (default), borderless toolbar, or intrinsic navigation chrome. */
2731
+ /** Form (default), borderless toolbar, intrinsic navigation, inline, or view-title chrome. */
2719
2732
  presentation?: SelectPresentation;
2720
2733
  size?: SelectSize;
2721
2734
  /** Closed trigger width; omit to keep the presentation's default. */
@@ -2724,6 +2737,8 @@ interface SelectBaseProps<Value extends string = string> {
2724
2737
  caret?: boolean;
2725
2738
  /** Let an enclosing ToolbarControlGroup paint the composed focus ring. */
2726
2739
  focusRingOwner?: SelectFocusRingOwner;
2740
+ /** Keep the trigger's focus ring inside its box when an ancestor clips overflow. */
2741
+ focusRingInset?: boolean;
2727
2742
  /** Maximum closed-control label width in CSS pixels before ellipsis. */
2728
2743
  labelMaxWidth?: number;
2729
2744
  /** Native named-slot assignment when composed inside a web component. */
@@ -2844,17 +2859,22 @@ export { EmptyState, type EmptyStateProps };
2844
2859
 
2845
2860
  ```ts
2846
2861
  import * as kerfjs from 'kerfjs';
2862
+ import { LucideIconSize } from './lucide-icon.js';
2863
+ import 'lucide';
2847
2864
 
2865
+ type LoadingSpinnerSize = LucideIconSize;
2848
2866
  interface LoadingSpinnerProps {
2849
2867
  className?: string;
2850
2868
  label?: string;
2869
+ /** Named icon scale or positive pixel size, converted to rem. Omit for 1em. */
2870
+ size?: LoadingSpinnerSize;
2851
2871
  /** Native named-slot assignment when composed inside a web component. */
2852
2872
  slot?: string;
2853
2873
  }
2854
2874
  /** Stable viewBox-centered progress ring based on svg-spinners' MIT-licensed 180-ring. */
2855
- declare function LoadingSpinner({ className, label, slot, }: LoadingSpinnerProps): kerfjs.SafeHtml;
2875
+ declare function LoadingSpinner({ className, label, size, slot, }: LoadingSpinnerProps): kerfjs.SafeHtml;
2856
2876
 
2857
- export { LoadingSpinner, type LoadingSpinnerProps };
2877
+ export { LoadingSpinner, type LoadingSpinnerProps, type LoadingSpinnerSize };
2858
2878
  ```
2859
2879
 
2860
2880
  ## `@kerfjs/ui/skeleton`