@kerfjs/ui 5.0.0-beta.17 → 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.
Files changed (274) hide show
  1. package/README.md +221 -81
  2. package/ai/application-ui-diagnostic-ids-v1.json +92 -0
  3. package/ai/application-ui-profile-sync.cjs +529 -0
  4. package/ai/application-ui-profile.d.mts +1 -0
  5. package/ai/application-ui-profile.d.ts +102 -0
  6. package/ai/application-ui-profile.defaults.json +41 -0
  7. package/ai/application-ui-profile.mjs +626 -0
  8. package/ai/application-ui-profile.schema.json +130 -0
  9. package/ai/catalog-authoring.json +25 -0
  10. package/ai/catalog-authoring.schema.json +61 -0
  11. package/ai/compile-time-contracts-v1.json +101 -0
  12. package/ai/compile-time-contracts-v1.schema.json +49 -0
  13. package/ai/component-catalog-extension-v2.d.ts +7 -0
  14. package/ai/component-catalog-extension-v2.schema.json +7 -0
  15. package/ai/component-catalog-extension.schema.json +79 -0
  16. package/ai/component-catalog-v2-overrides.json +326 -0
  17. package/ai/component-catalog-v2.d.ts +67 -0
  18. package/ai/component-catalog-v2.json +4441 -0
  19. package/ai/component-catalog-v2.schema.json +246 -0
  20. package/ai/component-catalog.json +649 -6
  21. package/ai/component-catalog.schema.json +15 -0
  22. package/ai/public-api-signatures-v1.md +357 -91
  23. package/ai/skill.md +95 -50
  24. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  25. package/analyzer/cli.mjs +46 -0
  26. package/analyzer/index.d.mts +45 -0
  27. package/analyzer/index.mjs +743 -0
  28. package/analyzer/report.schema.json +66 -0
  29. package/dist/app-tab.d.ts +1 -1
  30. package/dist/app-tab.js +1 -1
  31. package/dist/browser/pane.js +3 -0
  32. package/dist/browser/sunken-panel.js +3 -0
  33. package/dist/catalog-resources.d.ts +34 -0
  34. package/dist/catalog-resources.js +22 -0
  35. package/dist/catalog-resources.js.map +1 -0
  36. package/dist/catalog.d.ts +29 -4
  37. package/dist/catalog.js +302 -84
  38. package/dist/catalog.js.map +1 -1
  39. package/dist/chunk-5VTB2BKP.js +35 -0
  40. package/dist/chunk-5VTB2BKP.js.map +1 -0
  41. package/dist/chunk-6WTETUZ6.js +21 -0
  42. package/dist/chunk-6WTETUZ6.js.map +1 -0
  43. package/dist/chunk-7KEUJIIC.js +218 -0
  44. package/dist/chunk-7KEUJIIC.js.map +1 -0
  45. package/dist/chunk-7L6EHUMV.js +31 -0
  46. package/dist/chunk-7L6EHUMV.js.map +1 -0
  47. package/dist/{chunk-XXMOARTH.js → chunk-AASLYRGE.js} +57 -16
  48. package/dist/chunk-AASLYRGE.js.map +1 -0
  49. package/dist/chunk-BBRXX6PF.js +108 -0
  50. package/dist/chunk-BBRXX6PF.js.map +1 -0
  51. package/dist/{chunk-6NLRBSVX.js → chunk-CN2XNTAM.js} +129 -72
  52. package/dist/chunk-CN2XNTAM.js.map +1 -0
  53. package/dist/chunk-DPDBVKNA.js +113 -0
  54. package/dist/chunk-DPDBVKNA.js.map +1 -0
  55. package/dist/chunk-DSNIYOZ3.js +114 -0
  56. package/dist/chunk-DSNIYOZ3.js.map +1 -0
  57. package/dist/chunk-ELSJX4W7.js +68 -0
  58. package/dist/chunk-ELSJX4W7.js.map +1 -0
  59. package/dist/{chunk-AHQI52QO.js → chunk-FEY65TBV.js} +6 -2
  60. package/dist/chunk-FEY65TBV.js.map +1 -0
  61. package/dist/chunk-JRKSK2HX.js +79 -0
  62. package/dist/chunk-JRKSK2HX.js.map +1 -0
  63. package/dist/chunk-K57KRAOV.js +117 -0
  64. package/dist/chunk-K57KRAOV.js.map +1 -0
  65. package/dist/chunk-LPOMRC4C.js +25 -0
  66. package/dist/chunk-LPOMRC4C.js.map +1 -0
  67. package/dist/chunk-M7JBER5U.js +41 -0
  68. package/dist/chunk-M7JBER5U.js.map +1 -0
  69. package/dist/chunk-NENBUYX5.js +79 -0
  70. package/dist/chunk-NENBUYX5.js.map +1 -0
  71. package/dist/chunk-OHU6CI22.js +83 -0
  72. package/dist/chunk-OHU6CI22.js.map +1 -0
  73. package/dist/{chunk-YGN7OMLT.js → chunk-OZWU5ZPZ.js} +2 -2
  74. package/dist/chunk-OZWU5ZPZ.js.map +1 -0
  75. package/dist/chunk-PIDTEQ3U.js +105 -0
  76. package/dist/chunk-PIDTEQ3U.js.map +1 -0
  77. package/dist/chunk-PX3YSS5C.js +32 -0
  78. package/dist/chunk-PX3YSS5C.js.map +1 -0
  79. package/dist/chunk-PZQVSDLH.js +61 -0
  80. package/dist/chunk-PZQVSDLH.js.map +1 -0
  81. package/dist/chunk-QO55FN2Y.js +20 -0
  82. package/dist/chunk-QO55FN2Y.js.map +1 -0
  83. package/dist/chunk-QQFJMRIB.js +45 -0
  84. package/dist/chunk-QQFJMRIB.js.map +1 -0
  85. package/dist/chunk-RYRIFZCT.js +58 -0
  86. package/dist/chunk-RYRIFZCT.js.map +1 -0
  87. package/dist/{chunk-7BR3DO2A.js → chunk-SRSJO5QE.js} +13 -4
  88. package/dist/chunk-SRSJO5QE.js.map +1 -0
  89. package/dist/chunk-VLYV65LK.js +38 -0
  90. package/dist/chunk-VLYV65LK.js.map +1 -0
  91. package/dist/chunk-W4CXQYSG.js +23 -0
  92. package/dist/chunk-W4CXQYSG.js.map +1 -0
  93. package/dist/{chunk-WXMGNX2Y.js → chunk-W7YD6E4N.js} +2 -2
  94. package/dist/chunk-W7YD6E4N.js.map +1 -0
  95. package/dist/{chunk-56RJXPHG.js → chunk-Y6LYKFXY.js} +1 -1
  96. package/dist/{chunk-56RJXPHG.js.map → chunk-Y6LYKFXY.js.map} +1 -1
  97. package/dist/collapsible-panel.d.ts +2 -2
  98. package/dist/collapsible-panel.js +47 -6
  99. package/dist/collapsible-panel.js.map +1 -1
  100. package/dist/device-class.js +33 -5
  101. package/dist/device-class.js.map +1 -1
  102. package/dist/disclosure-arrow.js +1 -1
  103. package/dist/empty-state.d.ts +1 -1
  104. package/dist/empty-state.js +1 -1
  105. package/dist/floating-toolbar.d.ts +1 -1
  106. package/dist/floating-toolbar.js +1 -1
  107. package/dist/index.d.ts +6 -4
  108. package/dist/index.js +27 -25
  109. package/dist/list-action-row.d.ts +1 -1
  110. package/dist/list-action-row.js +1 -1
  111. package/dist/list-header.d.ts +30 -9
  112. package/dist/list-header.js +1 -1
  113. package/dist/list-inset-control.d.ts +1 -1
  114. package/dist/list-inset-control.js +1 -1
  115. package/dist/list-inset-text.d.ts +1 -1
  116. package/dist/list-inset-text.js +1 -1
  117. package/dist/list-item.d.ts +1 -1
  118. package/dist/list-item.js +1 -1
  119. package/dist/loading-spinner.js +1 -1
  120. package/dist/nav-stack.d.ts +1 -1
  121. package/dist/nav-stack.js +1 -1
  122. package/dist/pane.d.ts +49 -0
  123. package/dist/pane.js +4 -0
  124. package/dist/pane.js.map +1 -0
  125. package/dist/panel-header.d.ts +10 -4
  126. package/dist/panel-header.js +1 -1
  127. package/dist/resizable-region.d.ts +1 -1
  128. package/dist/resizable-region.js +1 -1
  129. package/dist/segmented-control.d.ts +6 -6
  130. package/dist/segmented-control.js +1 -1
  131. package/dist/select.d.ts +11 -5
  132. package/dist/select.js +1 -1
  133. package/dist/skeleton.d.ts +1 -1
  134. package/dist/skeleton.js +1 -1
  135. package/dist/split-view.d.ts +1 -1
  136. package/dist/split-view.js +70 -10
  137. package/dist/split-view.js.map +1 -1
  138. package/dist/state-banner.d.ts +4 -3
  139. package/dist/state-banner.js +1 -1
  140. package/dist/styles/catalog.css +32 -6
  141. package/dist/styles/layout.css +2 -20
  142. package/dist/styles/pane.css +62 -0
  143. package/dist/styles/segmented-control.css +3 -7
  144. package/dist/styles/sunken-panel.css +18 -0
  145. package/dist/sunken-panel.d.ts +16 -0
  146. package/dist/sunken-panel.js +3 -0
  147. package/dist/sunken-panel.js.map +1 -0
  148. package/dist/tab-bar.d.ts +4 -3
  149. package/dist/tab-bar.js +1 -1
  150. package/dist/tab-scaffold.d.ts +6 -6
  151. package/dist/tab-scaffold.js +43 -8
  152. package/dist/tab-scaffold.js.map +1 -1
  153. package/dist/token-search-field.d.ts +25 -9
  154. package/dist/token-search-field.js +1 -1
  155. package/dist/toolbar-control-group.d.ts +10 -6
  156. package/dist/toolbar-control-group.js +1 -1
  157. package/dist/toolbar-text.d.ts +10 -11
  158. package/dist/toolbar-text.js +1 -1
  159. package/dist/toolbar.d.ts +1 -1
  160. package/dist/toolbar.js +1 -1
  161. package/dist/value-table.d.ts +2 -2
  162. package/dist/value-table.js +1 -1
  163. package/dist/wire-catalog.d.ts +31 -2
  164. package/dist/wire-catalog.js +203 -8
  165. package/dist/wire-catalog.js.map +1 -1
  166. package/dist/wire-nav-stack.js +22 -7
  167. package/dist/wire-nav-stack.js.map +1 -1
  168. package/dist/wire-resizable-regions.d.ts +1 -1
  169. package/dist/wire-resizable-regions.js +1 -1
  170. package/dist/wire-sidebar.js +52 -41
  171. package/dist/wire-sidebar.js.map +1 -1
  172. package/dist/wire-tab-bars.d.ts +5 -2
  173. package/dist/wire-tab-bars.js +1 -1
  174. package/dist/wire-tab-scaffold.js +9 -4
  175. package/dist/wire-tab-scaffold.js.map +1 -1
  176. package/dist/wire-token-search-fields.d.ts +12 -7
  177. package/dist/wire-token-search-fields.js +1 -1
  178. package/dist/workbench.d.ts +1 -1
  179. package/dist/workbench.js +56 -9
  180. package/dist/workbench.js.map +1 -1
  181. package/docs/app-layouts.md +7 -7
  182. package/docs/catalog.md +249 -46
  183. package/docs/collapsible-panel.md +38 -11
  184. package/docs/component-contract.md +132 -7
  185. package/docs/component-selection.md +79 -67
  186. package/docs/design/templates.md +1 -1
  187. package/docs/design-philosophy.md +7 -9
  188. package/docs/device-class.md +8 -8
  189. package/docs/examples/application-ui-profile.json +60 -0
  190. package/docs/examples/command-palette-adapter.tsx +67 -17
  191. package/docs/examples/component-catalog-extension-v2.json +81 -0
  192. package/docs/examples/component-catalog-extension.json +45 -0
  193. package/docs/layout.md +51 -32
  194. package/docs/nav-stack.md +15 -6
  195. package/docs/recipes.md +4 -4
  196. package/docs/split-view.md +3 -3
  197. package/docs/sunken-panel.md +43 -0
  198. package/docs/tab-scaffold.md +16 -7
  199. package/docs/type-contracts.md +99 -0
  200. package/docs/ui-analyzer.md +81 -0
  201. package/docs/ui-doctor.md +77 -0
  202. package/docs/ui-evaluator.md +122 -0
  203. package/docs/webawesome-theme.md +8 -8
  204. package/docs/workbench.md +18 -5
  205. package/doctor/cli.mjs +115 -0
  206. package/doctor/config.schema.json +68 -0
  207. package/doctor/index.d.mts +116 -0
  208. package/doctor/index.mjs +1256 -0
  209. package/doctor/report.schema.json +118 -0
  210. package/evaluator/cli.mjs +89 -0
  211. package/evaluator/index.d.mts +90 -0
  212. package/evaluator/index.mjs +1138 -0
  213. package/evaluator/report.schema.json +152 -0
  214. package/llms.txt +58 -15
  215. package/package.json +70 -10
  216. package/ux-demo/recipes/app-shell.tsx +206 -10
  217. package/ux-demo/recipes/collapsible-sidebar.tsx +167 -44
  218. package/ux-demo/recipes/compact-toolbar.tsx +114 -12
  219. package/ux-demo/recipes/composer-form.tsx +106 -18
  220. package/ux-demo/recipes/list-detail-dialog.tsx +144 -8
  221. package/ux-demo/recipes/list-workspace-states.tsx +165 -8
  222. package/ux-demo/recipes/loaders.ts +5 -2
  223. package/ux-demo/recipes/loading-inspector.tsx +124 -41
  224. package/ux-demo/recipes/mount-recipe.ts +27 -6
  225. package/ux-demo/recipes/navigation-sidebar.tsx +120 -9
  226. package/ux-demo/recipes/navigation-stack.tsx +61 -19
  227. package/ux-demo/recipes/types.ts +3 -1
  228. package/ux-demo/recipes/workspace-header.tsx +57 -2
  229. package/dist/chunk-2Z5TWHKQ.js +0 -34
  230. package/dist/chunk-2Z5TWHKQ.js.map +0 -1
  231. package/dist/chunk-37T3VNIZ.js +0 -19
  232. package/dist/chunk-37T3VNIZ.js.map +0 -1
  233. package/dist/chunk-55DATV3A.js +0 -10
  234. package/dist/chunk-55DATV3A.js.map +0 -1
  235. package/dist/chunk-5ATCDSQS.js +0 -42
  236. package/dist/chunk-5ATCDSQS.js.map +0 -1
  237. package/dist/chunk-5E2HVJ33.js +0 -10
  238. package/dist/chunk-5E2HVJ33.js.map +0 -1
  239. package/dist/chunk-6NLRBSVX.js.map +0 -1
  240. package/dist/chunk-7BR3DO2A.js.map +0 -1
  241. package/dist/chunk-7JB7VPRI.js +0 -34
  242. package/dist/chunk-7JB7VPRI.js.map +0 -1
  243. package/dist/chunk-7LDCTU7D.js +0 -134
  244. package/dist/chunk-7LDCTU7D.js.map +0 -1
  245. package/dist/chunk-AHQI52QO.js.map +0 -1
  246. package/dist/chunk-AQNKJRFH.js +0 -79
  247. package/dist/chunk-AQNKJRFH.js.map +0 -1
  248. package/dist/chunk-AVZHJN6C.js +0 -14
  249. package/dist/chunk-AVZHJN6C.js.map +0 -1
  250. package/dist/chunk-B24C4A6T.js +0 -22
  251. package/dist/chunk-B24C4A6T.js.map +0 -1
  252. package/dist/chunk-CH4SQ6H5.js +0 -39
  253. package/dist/chunk-CH4SQ6H5.js.map +0 -1
  254. package/dist/chunk-DAFFDO33.js +0 -14
  255. package/dist/chunk-DAFFDO33.js.map +0 -1
  256. package/dist/chunk-I7INRJO4.js +0 -17
  257. package/dist/chunk-I7INRJO4.js.map +0 -1
  258. package/dist/chunk-L4OKFDJY.js +0 -24
  259. package/dist/chunk-L4OKFDJY.js.map +0 -1
  260. package/dist/chunk-M5JRI6MN.js +0 -27
  261. package/dist/chunk-M5JRI6MN.js.map +0 -1
  262. package/dist/chunk-RNPGFQ33.js +0 -10
  263. package/dist/chunk-RNPGFQ33.js.map +0 -1
  264. package/dist/chunk-VPCTCUED.js +0 -10
  265. package/dist/chunk-VPCTCUED.js.map +0 -1
  266. package/dist/chunk-WRTAQ7BZ.js +0 -23
  267. package/dist/chunk-WRTAQ7BZ.js.map +0 -1
  268. package/dist/chunk-WVPYVDXY.js +0 -15
  269. package/dist/chunk-WVPYVDXY.js.map +0 -1
  270. package/dist/chunk-WXMGNX2Y.js.map +0 -1
  271. package/dist/chunk-XXMOARTH.js.map +0 -1
  272. package/dist/chunk-YGN7OMLT.js.map +0 -1
  273. package/dist/chunk-ZQQ6P4SV.js +0 -20
  274. package/dist/chunk-ZQQ6P4SV.js.map +0 -1
@@ -26,17 +26,57 @@ export function mountCommandPaletteAdapter(
26
26
  const query = signal('');
27
27
  const render = () => {
28
28
  const needle = query.value.trim().toLocaleLowerCase();
29
- const matches = commands.filter((command) => !needle ||
30
- [command.label, ...(command.keywords ?? [])].some((value) => value.toLocaleLowerCase().includes(needle)));
31
- return <section class="app-command-palette kui-content" aria-label="Command palette">
32
- <div class="kui-content-item"><label for="app-command-query">Search commands</label><input id="app-command-query" type="search" value={query.value} autocomplete="off" data-command-query /></div>
33
- <p class="kui-content-item" aria-live="polite">{matches.length} matching commands</p>
34
- <ul class="kui-content-item" aria-label="Matching commands">{matches.map((command) => <li><button type="button" data-action="run-command" data-command-id={command.id}>{command.label}</button></li>)}</ul>
35
- <footer class="kui-control-cluster kui-content-item" aria-label="Command palette actions">
36
- <button type="button" data-action="clear-query">Clear search</button>
37
- <button type="button" data-action="close-palette">Close</button>
38
- </footer>
39
- </section>;
29
+ const matches = commands.filter(
30
+ (command) =>
31
+ !needle ||
32
+ [command.label, ...(command.keywords ?? [])].some((value) =>
33
+ value.toLocaleLowerCase().includes(needle),
34
+ ),
35
+ );
36
+ return (
37
+ <section
38
+ class="app-command-palette kui-content"
39
+ aria-label="Command palette"
40
+ >
41
+ <div class="kui-content-item">
42
+ <label for="app-command-query">Search commands</label>
43
+ <input
44
+ id="app-command-query"
45
+ type="search"
46
+ value={query.value}
47
+ autocomplete="off"
48
+ data-command-query
49
+ />
50
+ </div>
51
+ <p class="kui-content-item" aria-live="polite">
52
+ {matches.length} matching commands
53
+ </p>
54
+ <ul class="kui-content-item" aria-label="Matching commands">
55
+ {matches.map((command) => (
56
+ <li>
57
+ <button
58
+ type="button"
59
+ data-action="run-command"
60
+ data-command-id={command.id}
61
+ >
62
+ {command.label}
63
+ </button>
64
+ </li>
65
+ ))}
66
+ </ul>
67
+ <footer
68
+ class="kui-control-cluster kui-content-item"
69
+ aria-label="Command palette actions"
70
+ >
71
+ <button type="button" data-action="clear-query">
72
+ Clear search
73
+ </button>
74
+ <button type="button" data-action="close-palette">
75
+ Close
76
+ </button>
77
+ </footer>
78
+ </section>
79
+ );
40
80
  };
41
81
 
42
82
  const stopMount = mount(root, render);
@@ -52,12 +92,22 @@ export function mountCommandPaletteAdapter(
52
92
  },
53
93
  'close-palette': onClose,
54
94
  });
55
- const stopInput = delegate<HTMLInputElement>(root, 'input', '[data-command-query]', (_event, input) => {
56
- query.value = input.value;
57
- });
58
- const stopEscape = delegate(root, 'keydown', '[data-command-query]', (event) => {
59
- if ((event as KeyboardEvent).key === 'Escape') onClose();
60
- });
95
+ const stopInput = delegate<HTMLInputElement>(
96
+ root,
97
+ 'input',
98
+ '[data-command-query]',
99
+ (_event, input) => {
100
+ query.value = input.value;
101
+ },
102
+ );
103
+ const stopEscape = delegate(
104
+ root,
105
+ 'keydown',
106
+ '[data-command-query]',
107
+ (event) => {
108
+ if ((event as KeyboardEvent).key === 'Escape') onClose();
109
+ },
110
+ );
61
111
  let disposed = false;
62
112
  return () => {
63
113
  if (disposed) return;
@@ -0,0 +1,81 @@
1
+ {
2
+ "$schema": "../../ai/component-catalog-extension-v2.schema.json",
3
+ "schemaVersion": 2,
4
+ "package": "@acme/ui",
5
+ "compatibility": {
6
+ "v1Catalog": "./component-catalog-extension.json",
7
+ "identity": "package:id"
8
+ },
9
+ "entries": [
10
+ {
11
+ "key": "@acme/ui:inspector",
12
+ "package": "@acme/ui",
13
+ "id": "inspector",
14
+ "name": "Inspector",
15
+ "kind": "composition",
16
+ "source": "application",
17
+ "parents": { "mode": "listed", "entries": ["@kerfjs/ui:layout"] },
18
+ "contexts": ["record-detail"],
19
+ "zones": [
20
+ {
21
+ "id": "header",
22
+ "accepts": ["@kerfjs/ui:panel-header"],
23
+ "cardinality": { "min": 1, "max": 1 },
24
+ "exclusiveWith": []
25
+ },
26
+ {
27
+ "id": "facts",
28
+ "accepts": ["@kerfjs/ui:value-table"],
29
+ "cardinality": { "min": 1, "max": 1 },
30
+ "exclusiveWith": []
31
+ }
32
+ ],
33
+ "children": {
34
+ "mode": "listed",
35
+ "concepts": ["@kerfjs/ui:panel-header", "@kerfjs/ui:value-table"],
36
+ "requiredConcepts": [
37
+ "@kerfjs/ui:panel-header",
38
+ "@kerfjs/ui:value-table"
39
+ ]
40
+ },
41
+ "state": [
42
+ { "id": "selected-record", "owner": "application", "required": true }
43
+ ],
44
+ "wiring": { "required": false, "helpers": [], "obligations": [] },
45
+ "responsive": {
46
+ "owner": "application",
47
+ "behaviors": [
48
+ "May move between inline-end pane and dialog at the application breakpoint."
49
+ ]
50
+ },
51
+ "layout": {
52
+ "roles": ["inspector"],
53
+ "geometry": {
54
+ "margin": "parent",
55
+ "border": "child",
56
+ "padding": "child"
57
+ }
58
+ },
59
+ "accessibility": {
60
+ "obligations": ["The header names the inspector region."]
61
+ },
62
+ "boundaries": {
63
+ "rootClass": "acme-inspector",
64
+ "publicClasses": ["acme-inspector"],
65
+ "publicTokens": []
66
+ },
67
+ "diagnostics": [
68
+ {
69
+ "id": "KUI-C901",
70
+ "severity": "error",
71
+ "when": "the header or facts zone is absent",
72
+ "message": "Inspector requires exactly one header and one facts region."
73
+ }
74
+ ],
75
+ "provenance": {
76
+ "selection": "docs/inspector.md",
77
+ "composition": "docs/inspector.md#composition-contract"
78
+ }
79
+ }
80
+ ]
81
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "$schema": "../../ai/component-catalog-extension.schema.json",
3
+ "schemaVersion": 1,
4
+ "package": "@acme/ui",
5
+ "description": "Application-owned components composed with Kerf UI.",
6
+ "entries": [
7
+ {
8
+ "id": "acme-filter-chip",
9
+ "name": "FilterChip",
10
+ "kind": "component",
11
+ "purpose": "A removable application filter displayed inside a token-search workflow.",
12
+ "useWhen": [
13
+ "A saved application filter needs a compact visible label and remove action."
14
+ ],
15
+ "avoidWhen": [
16
+ "The value belongs to TokenSearchField's own controlled token collection."
17
+ ],
18
+ "publicClasses": ["acme-filter-chip"],
19
+ "publicTokens": ["--acme-filter-chip-accent"],
20
+ "geometry": {
21
+ "margin": "none",
22
+ "border": "self",
23
+ "padding": "self"
24
+ },
25
+ "documentation": "docs/filter-chip.md"
26
+ },
27
+ {
28
+ "id": "acme-inspector",
29
+ "name": "Inspector",
30
+ "kind": "composition",
31
+ "purpose": "An application inspector composed from Kerf panel, content, and value primitives.",
32
+ "useWhen": ["A selected record needs a titled fact-and-action region."],
33
+ "avoidWhen": ["A ValueTable alone expresses the complete interaction."],
34
+ "geometry": {
35
+ "margin": "parent",
36
+ "border": "child",
37
+ "padding": "child",
38
+ "notes": [
39
+ "The embedding pane positions the inspector; PanelHeader, ValueTable, and action groups retain their own geometry."
40
+ ]
41
+ },
42
+ "documentation": "docs/inspector.md"
43
+ }
44
+ ]
45
+ }
package/docs/layout.md CHANGED
@@ -1,26 +1,39 @@
1
1
  # Pane and content layout
2
2
 
3
- Import `@kerfjs/ui/layout.css` for the shared geometry used by sidebars, main
4
- areas, inspectors, and dialogs. The vocabulary deliberately describes structure
5
- rather than location: the same pane can be placed anywhere.
3
+ Import `Pane` from `@kerfjs/ui/pane`. Browser-aware bundlers receive its CSS
4
+ automatically; other consumers can import `@kerfjs/ui/pane.css`. Import
5
+ `@kerfjs/ui/layout.css` as well when using the related content-item and control
6
+ layout classes. The vocabulary deliberately describes structure rather than
7
+ location: the same pane can be placed anywhere.
6
8
 
7
9
  ## Anatomy
8
10
 
9
11
  ```tsx
10
- <aside class="kui-pane">
11
- <div class="kui-pane__toolbar"><Toolbar label="Workspace" ... /></div>
12
- <div class="kui-pane__content kui-content">
12
+ <Pane
13
+ element="aside"
14
+ label="Workspace"
15
+ contentElement="nav"
16
+ contentLabel="Workspace pages"
17
+ separators={["inline-end"]}
18
+ header={<Toolbar label="Workspace" ... />}
19
+ footer={<Toolbar label="Actions" ... />}
20
+ >
13
21
  <section>...</section>
14
22
  <section>...</section>
15
- </div>
16
- <footer class="kui-pane__footer"><Toolbar label="Actions" ... /></footer>
17
- </aside>
23
+ </Pane>
18
24
  ```
19
25
 
20
- `.kui-pane` has no padding. It reserves rows for an optional toolbar, one
21
- scrolling content area, and an optional footer. A main area or dialog often
22
- omits the footer; a navigation pane commonly uses all three. Fixed chrome stays
23
- outside `.kui-pane__content`, which is the pane's scroll owner.
26
+ `Pane` has no padding. It reserves rows for an optional vertical header, one
27
+ scrolling vertical content area, and an optional footer. A header may contain a
28
+ top toolbar followed by secondary toolbar or status rows. A main area or dialog
29
+ often omits the footer; a navigation pane commonly uses all three. Fixed chrome
30
+ stays outside `.kui-pane__content`, which is the pane's only scroll owner.
31
+
32
+ Pass any combination of logical sides to `separators`: `block-start`,
33
+ `block-end`, `inline-start`, and `inline-end`. Every line is off by default and
34
+ each enabled side uses `--kui-pane-separator-width` (1px) and
35
+ `--kui-pane-separator-color` (`--kui-color-border`). Logical sides keep pane
36
+ boundaries correct in both left-to-right and right-to-left layouts.
24
37
 
25
38
  `.kui-content` is a vertical stack with a 24px gap between major children.
26
39
  Sections may contain adjacent `ListItem` rows without adding another major gap.
@@ -37,6 +50,11 @@ Use `.kui-content-item--pill` for the 22px pill radius expressed as
37
50
  changing its geometry by setting `--kui-content-item-border` and
38
51
  `--kui-content-item-background`.
39
52
 
53
+ Nested rounded edges stay concentric by subtracting their full inset from the
54
+ outer radius. For example, `SegmentedControl` places each item behind a 1px
55
+ control border and 1px control padding, so its default rounded-rectangle radius
56
+ is 10px inside the control's 12px radius.
57
+
40
58
  A visible parent surface does not make every child another visible card. The
41
59
  composer recipe keeps its form as the single surface, uses `PanelHeader` for
42
60
  its task hierarchy, and places field and action control edges on the shared 8px
@@ -46,16 +64,17 @@ semantic status.
46
64
 
47
65
  ## Public roles and tokens
48
66
 
49
- | Need | Class | Token / default |
50
- | --- | --- | --- |
51
- | Unpadded toolbar/content/footer structure | `.kui-pane` | — |
52
- | Scrolling pane content | `.kui-pane__content` | — |
53
- | Major vertical rhythm | `.kui-content` | `--kui-layout-content-gap: 24px` |
54
- | Self-contained child geometry | `.kui-content-item` | 8px margin + 1px border + 8px padding |
55
- | Pill child | `.kui-content-item--pill` | `--kui-layout-pill-radius: 22px` |
56
- | Related controls | `.kui-control-cluster` | `--kui-layout-control-gap: 8px` |
57
- | Inline metadata | `.kui-inline-metadata` | `--kui-layout-metadata-gap: 4px` |
58
- | Explicit scroll owner outside a pane | `.kui-scroll-owner` | `overflow: auto` |
67
+ | Need | Class | Token / default |
68
+ | ---------------------------------------- | ------------------------- | ------------------------------------- |
69
+ | Unpadded header/content/footer structure | `Pane`, `.kui-pane` | — |
70
+ | Scrolling pane content | `.kui-pane__content` | — |
71
+ | Optional logical-edge separators | `Pane.separators` | `--kui-pane-separator-width: 1px` |
72
+ | Major vertical rhythm | `.kui-content` | `--kui-layout-content-gap: 24px` |
73
+ | Self-contained child geometry | `.kui-content-item` | 8px margin + 1px border + 8px padding |
74
+ | Pill child | `.kui-content-item--pill` | `--kui-layout-pill-radius: 22px` |
75
+ | Related controls | `.kui-control-cluster` | `--kui-layout-control-gap: 8px` |
76
+ | Inline metadata | `.kui-inline-metadata` | `--kui-layout-metadata-gap: 4px` |
77
+ | Explicit scroll owner outside a pane | `.kui-scroll-owner` | `overflow: auto` |
59
78
 
60
79
  The component layer applies the same contract to `Toolbar`, `ListHeader`,
61
80
  `ListItem`, `PanelHeader`, `StateBanner`, `ValueTable`,
@@ -81,16 +100,16 @@ expressing exactly one relationship — pick the step by **how connected two
81
100
  elements are**, not by eye. Every scalable value is `remify`-authored against the
82
101
  fixed 16px baseline, so it delivers as `rem`.
83
102
 
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 `ListItem` 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. |
103
+ | Value | Token | Relationship — when to use |
104
+ | ----- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
105
+ | 0px | `--kui-space-none` | No separation. The elements read as a single unit (a control and its own affordance, adjacent `ListItem` rows). |
106
+ | 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). |
107
+ | 8px | `--kui-space-xs` | Standard. Between elements **within a group** — the content-item padding/gutter, gaps between toolbar controls in a group. |
108
+ | 16px | `--kui-space-m` | Minor. Between **homogeneous groups** — two lists, two sibling sections of the same kind. |
109
+ | 24px | `--kui-space-l` | Major. Between **heterogeneous groups** — the `.kui-content` rhythm between major, differing regions. |
91
110
 
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
111
+ The two relationships that get confused most are 8px vs 24px: 8px is _inside_ a
112
+ group, 24px is _between_ major, differing regions. 16px sits between them for
94
113
  same-kind groups.
95
114
 
96
115
  `--kui-space-s` (12px) and `--kui-space-xl` (32px) exist but are **off the
package/docs/nav-stack.md CHANGED
@@ -8,9 +8,9 @@ a `NavStack` with one entry**. It is one of the opt-in app layouts (see
8
8
  Import the component and its companion stylesheet:
9
9
 
10
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';
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
14
  ```
15
15
 
16
16
  ## State lives in the app
@@ -20,16 +20,25 @@ stack as a `signal<NavStackView[]>`, `NavStack({ views })` renders it, and
20
20
  `wireNavStack` animates the transitions.
21
21
 
22
22
  ```tsx
23
- const views = signal<NavStackView[]>([{ key: 'inbox', title: 'Inbox', content: <InboxView /> }]);
23
+ const views = signal<NavStackView[]>([
24
+ { key: "inbox", title: "Inbox", content: <InboxView /> },
25
+ ]);
24
26
 
25
27
  // render inside mount():
26
28
  <NavStack id="mail" label="Mail" views={views.value} />;
27
29
 
28
30
  // once, after first render:
29
- const dispose = wireNavStack(root, { onBack: () => { views.value = views.value.slice(0, -1); } });
31
+ const dispose = wireNavStack(root, {
32
+ onBack: () => {
33
+ views.value = views.value.slice(0, -1);
34
+ },
35
+ });
30
36
 
31
37
  // push / pop by editing the signal:
32
- views.value = [...views.value, { key: id, title: 'Message', content: <MessageView id={id} /> }];
38
+ views.value = [
39
+ ...views.value,
40
+ { key: id, title: "Message", content: <MessageView id={id} /> },
41
+ ];
33
42
  ```
34
43
 
35
44
  `NavStack` renders every entry stacked, the last one active and the rest kept
package/docs/recipes.md CHANGED
@@ -14,12 +14,12 @@ the public `onCommit` callback, retains every disposer, and returns one
14
14
  idempotent disposer:
15
15
 
16
16
  ```ts
17
- import { createRecipe } from './navigation-sidebar.js';
18
- import { mountRecipe } from './mount-recipe.js';
17
+ import { createRecipe } from "./navigation-sidebar.js";
18
+ import { mountRecipe } from "./mount-recipe.js";
19
19
 
20
- const root = document.querySelector<HTMLElement>('#navigation')!;
20
+ const root = document.querySelector<HTMLElement>("#navigation")!;
21
21
  const stopRecipe = mountRecipe(root, createRecipe(announce));
22
- window.addEventListener('pagehide', stopRecipe, { once: true });
22
+ window.addEventListener("pagehide", stopRecipe, { once: true });
23
23
  ```
24
24
 
25
25
  The adapter is delivered as reference source, not a new package runtime export.
@@ -6,9 +6,9 @@ on compact ones. One of the opt-in app layouts (see
6
6
  [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
7
7
 
8
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';
9
+ import { SplitView } from "@kerfjs/ui/split-view";
10
+ import { deviceClass } from "@kerfjs/ui/device-class";
11
+ import "@kerfjs/ui/split-view.css";
12
12
  // plus nav-stack.css when the compact path is reachable, and
13
13
  // wireResizableRegions / wireNavStack for the interactive behavior.
14
14
  ```
@@ -0,0 +1,43 @@
1
+ # SunkenPanel
2
+
3
+ `SunkenPanel` is a visually lowered application surface with one compact inset
4
+ and a vertical content stack. Use it for a main work area or nested panel whose
5
+ background should sit behind ordinary content, such as the primary stage in an
6
+ issue tracker or component catalog.
7
+
8
+ ```tsx
9
+ import { SunkenPanel } from '@kerfjs/ui/sunken-panel';
10
+
11
+ <SunkenPanel ariaLabel="Release workspace">
12
+ <ReleaseSummary />
13
+ <ReleaseChecks />
14
+ </SunkenPanel>;
15
+ ```
16
+
17
+ ## Ownership
18
+
19
+ The root owns its lowered background, 8px padding, and 8px vertical gap.
20
+ Children own their borders and internal geometry. The application owns child
21
+ order, responsive placement, and scrolling; `SunkenPanel` deliberately does not
22
+ create another scroll container.
23
+
24
+ Use `ariaLabel` only when the surface is a distinct region people need to find
25
+ by name. With a label, the root receives `role="region"`; without one it remains
26
+ a non-landmark grouping.
27
+
28
+ Do not use `SunkenPanel` merely to add padding, as a substitute for pane
29
+ header/content/footer anatomy, or around a child that already owns the same
30
+ outer surface.
31
+
32
+ ## Public styling boundary
33
+
34
+ Override the public properties at the composition boundary:
35
+
36
+ - `--kui-sunken-panel-background`
37
+ - `--kui-sunken-panel-foreground`
38
+ - `--kui-sunken-panel-padding`
39
+ - `--kui-sunken-panel-gap`
40
+ - `--kui-sunken-panel-radius`
41
+
42
+ The public root class is `.kui-sunken-panel`. Prefer the properties above over
43
+ styling descendants.
@@ -7,9 +7,9 @@ opt-in app layouts (see [`../../docs/23-app-layouts.md`](../../docs/23-app-layou
7
7
  It is distinct from `TabBar` (document-oriented, reorderable strips).
8
8
 
9
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';
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
13
  ```
14
14
 
15
15
  ## Controlled selection
@@ -19,20 +19,29 @@ The app owns the active tab (a signal); `TabScaffold` renders every tab's scene
19
19
  clicks.
20
20
 
21
21
  ```tsx
22
- const active = signal('home');
22
+ const active = signal("home");
23
23
 
24
24
  <TabScaffold
25
25
  id="app"
26
26
  label="Sections"
27
27
  active={active.value}
28
28
  tabs={[
29
- { id: 'home', label: 'Home', icon: <HomeIcon />, content: <HomeStack /> },
30
- { id: 'search', label: 'Search', icon: <SearchIcon />, content: <SearchStack /> },
29
+ { id: "home", label: "Home", icon: <HomeIcon />, content: <HomeStack /> },
30
+ {
31
+ id: "search",
32
+ label: "Search",
33
+ icon: <SearchIcon />,
34
+ content: <SearchStack />,
35
+ },
31
36
  ]}
32
37
  />;
33
38
 
34
39
  // once, after first render:
35
- const dispose = wireTabScaffold(root, { onSelect: (id) => { active.value = id; } });
40
+ const dispose = wireTabScaffold(root, {
41
+ onSelect: (id) => {
42
+ active.value = id;
43
+ },
44
+ });
36
45
  ```
37
46
 
38
47
  Each `TabScaffoldTab` has an `id`, `label`, optional `icon`, and `content`. The
@@ -0,0 +1,99 @@
1
+ # Compile-time contracts
2
+
3
+ Kerf UI rejects invalid integrations at compile time only when TypeScript can
4
+ prove the relationship from one call. The versioned
5
+ [`compile-time-contracts-v1.json`](../ai/compile-time-contracts-v1.json)
6
+ artifact names those guarantees with stable `KUI-T###` ids. Its source fixture
7
+ is compiled twice: against `src/` and against declarations extracted from the
8
+ actual `npm pack` tarball. `npm run check:catalog` also verifies that every
9
+ contract still points to a real catalog entry/import and a symbol in the emitted
10
+ public-signature artifact.
11
+
12
+ ## Strengthened contracts
13
+
14
+ | ID | Compile-time guarantee |
15
+ | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
+ | `KUI-T001` | `ListHeader` is a passive heading, a fully named/icon-bearing trailing action, or a controlled disclosure with `action` + `expanded`. Props ignored by the selected mode are rejected. |
17
+ | `KUI-T002` | `Select` has a visible `label` or an `ariaLabel`. |
18
+ | `KUI-T003` | A literal `Select.value` belongs to its literal `choices`. |
19
+ | `KUI-T004` | A literal `SegmentedControl.value` belongs to its literal `choices`. |
20
+ | `KUI-T005` | `expanded`, `expandAction`, and `expandLabel` exist only on `TokenSearchField({ collapsible: true })`. |
21
+ | `KUI-T006` | `TokenSearchEditorAttributes` accepts application `data-*` metadata but rejects component-owned identity, morph, count, and placeholder attributes. |
22
+ | `KUI-T007` | `PanelHeader.summaryId` cannot exist without rendered `summary` content. |
23
+ | `KUI-T008` | `ToolbarText.maxLines` requires `wrap: true`; it is never silently ignored in typed code. |
24
+ | `KUI-T009` | A literal `TabScaffold.active` id belongs to its literal `tabs`. |
25
+ | `KUI-T010` | Adjacent-token keyboard removal requires `onRemoveToken`; disabling removal rejects the now-meaningless callback. |
26
+ | `KUI-T011` | Finite public variants have named exported union types, including tab activation, banner urgency, and all ToolbarControlGroup presentation axes. |
27
+
28
+ Literal identity inference is intentionally additive for dynamic applications.
29
+ When choices or ids arrive at runtime and the state signal is correctly typed as
30
+ `string`, widen the component explicitly:
31
+
32
+ ```tsx
33
+ <Select<string> value={loadedValue.value} choices={loadedChoices.value} ... />
34
+ <SegmentedControl<string> value={mode.value} choices={loadedModes.value} ... />
35
+ <TabScaffold<string> active={active.value} tabs={loadedTabs.value} ... />
36
+ ```
37
+
38
+ The widening is an honest statement that TypeScript cannot know the runtime
39
+ dataset. Do not cast a known literal typo merely to bypass the relationship.
40
+
41
+ ## Complete public-surface audit
42
+
43
+ The following existing contracts were already exact and remain covered by the
44
+ ordinary source/type gates:
45
+
46
+ - required identity or accessible-name inputs on `AppTab`, `FloatingToolbar`,
47
+ `NavStack`, `SplitView`, `Workbench`, `TabBar`, `ResizableRegion`,
48
+ `SegmentedControl`, `TokenSearchField`, `PanelHeader`, and `ValueTable`;
49
+ - finite variants exported or represented as literal props on `DisclosureArrow`,
50
+ `FloatingToolbar`, `Pane`, `ResizableRegion`, `SegmentedControl`, `StateBanner`,
51
+ `ToolbarText`, `CollapsiblePanel`, `Catalog`, and the wire helpers;
52
+ - required controlled-state callbacks on `wireResizableRegions`, `wireTabBars`,
53
+ and `wireTabScaffold`;
54
+ - mutually exclusive count/badge metadata on `ListHeader`; and
55
+ - protected application metadata boundaries on `AppTab`, `ListItem`,
56
+ `ListActionRow`, `ListHeader`, `Pane`, `CatalogExample`, and
57
+ `CatalogExampleStack`.
58
+
59
+ The remaining components (`LucideIcon`, `DisclosureArrow`, `Toolbar`,
60
+ `ToolbarControlGroup`, `ListInsetControl`, `ListInsetText`, `LoadingSpinner`,
61
+ `Skeleton`, `SunkenPanel`, `EmptyState`, `StateBanner`, `ValueTableRow`, and the
62
+ pure helpers) have independent props with no further conditional invalid state
63
+ that TypeScript can truthfully remove. Optional labels on decorative icons and
64
+ unnamed non-landmark surfaces are intentional variants, not missing contracts.
65
+
66
+ ## Boundaries TypeScript cannot prove
67
+
68
+ Types do not claim to validate:
69
+
70
+ - the contents of `SafeHtml` slots (for example, “only AppTab children” or “no
71
+ interactive descendants”);
72
+ - nonempty arrays, unique ids, numeric min/max ordering, or a controlled value
73
+ loaded from dynamic data;
74
+ - equality between ids rendered in separate calls, elements discovered later in
75
+ the DOM, or an ARIA relationship's live target;
76
+ - that a caller retained and invoked a wiring disposer; or
77
+ - CSS class/token use inside arbitrary strings.
78
+
79
+ Those remain catalog `KUI-C###` diagnostics, runtime filtering/guards, lint or
80
+ browser assertions. Encoding them as permissive-looking TypeScript brands would
81
+ move errors into casts without making the integration safer.
82
+
83
+ ## Migration from the earlier declarations
84
+
85
+ - Add `actionLabel` and `actionIcon` to a non-toggle `ListHeader` action. For a
86
+ disclosure, set literal `toggle: true`, provide `action` and controlled
87
+ `expanded`, and remove trailing-action-only props.
88
+ - Give every `Select` either `label` or `ariaLabel`. Use an explicit `<string>`
89
+ generic only for genuinely dynamic choice data.
90
+ - Put `expanded`/expand-action props behind `collapsible: true`.
91
+ - Replace `wireTokenSearchFields({ keyboard: true })` with either
92
+ `{ keyboard: { onRemoveToken } }` or
93
+ `{ keyboard: { removeAdjacentToken: false } }` when only caret navigation is
94
+ wanted.
95
+ - Remove orphaned `summaryId`, or render the matching `summary`; add
96
+ `wrap: true` anywhere `ToolbarText.maxLines` is used.
97
+
98
+ JavaScript runtime behavior remains defensive for previously emitted calls, but
99
+ new TypeScript builds report these invalid or ignored combinations.