@kerfjs/ui 4.4.1 → 4.5.0-beta.1

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 (499) hide show
  1. package/README.md +425 -42
  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 +102 -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 +319 -0
  17. package/ai/component-catalog-v2.d.ts +67 -0
  18. package/ai/component-catalog-v2.json +4675 -0
  19. package/ai/component-catalog-v2.schema.json +246 -0
  20. package/ai/component-catalog.json +5826 -0
  21. package/ai/component-catalog.schema.json +95 -0
  22. package/ai/public-api-signatures-v1.md +1719 -0
  23. package/ai/skill.md +129 -21
  24. package/ai/webawesome-jsx-signatures-v1.md +106 -0
  25. package/analyzer/cli.mjs +46 -0
  26. package/analyzer/index.d.mts +45 -0
  27. package/analyzer/index.mjs +737 -0
  28. package/analyzer/report.schema.json +66 -0
  29. package/dist/app-tab.d.ts +14 -2
  30. package/dist/app-tab.js +3 -1
  31. package/dist/browser/app-tab.js +3 -2
  32. package/dist/browser/disclosure-arrow.js +4 -0
  33. package/dist/browser/empty-state.js +3 -3
  34. package/dist/browser/floating-toolbar.js +3 -0
  35. package/dist/browser/list-action-row.js +4 -0
  36. package/dist/browser/list-header.js +6 -0
  37. package/dist/browser/list-inset-control.js +3 -0
  38. package/dist/browser/list-inset-text.js +3 -0
  39. package/dist/browser/list-item.js +4 -0
  40. package/dist/browser/list.js +3 -0
  41. package/dist/browser/loading-spinner.js +2 -2
  42. package/dist/browser/lucide-icon.js +2 -2
  43. package/dist/browser/pane.js +3 -0
  44. package/dist/browser/panel-header.js +7 -0
  45. package/dist/browser/resizable-region.js +2 -2
  46. package/dist/browser/segmented-control.js +3 -2
  47. package/dist/browser/select.js +4 -3
  48. package/dist/browser/skeleton.js +3 -0
  49. package/dist/browser/state-banner.js +3 -2
  50. package/dist/browser/sunken-panel.js +3 -0
  51. package/dist/browser/tab-bar.js +2 -2
  52. package/dist/browser/token-search-field.js +4 -0
  53. package/dist/browser/toolbar-control-group.js +2 -2
  54. package/dist/browser/toolbar-text.js +3 -2
  55. package/dist/browser/toolbar.js +2 -2
  56. package/dist/browser/value-table.js +3 -2
  57. package/dist/catalog-resources.d.ts +34 -0
  58. package/dist/catalog-resources.js +22 -0
  59. package/dist/catalog-resources.js.map +1 -0
  60. package/dist/catalog.d.ts +150 -0
  61. package/dist/catalog.js +377 -0
  62. package/dist/catalog.js.map +1 -0
  63. package/dist/chunk-5VTB2BKP.js +35 -0
  64. package/dist/chunk-5VTB2BKP.js.map +1 -0
  65. package/dist/chunk-6WTETUZ6.js +21 -0
  66. package/dist/chunk-6WTETUZ6.js.map +1 -0
  67. package/dist/chunk-7KEUJIIC.js +218 -0
  68. package/dist/chunk-7KEUJIIC.js.map +1 -0
  69. package/dist/chunk-7XRDUG4Y.js +58 -0
  70. package/dist/chunk-7XRDUG4Y.js.map +1 -0
  71. package/dist/chunk-B3A3IVO5.js +83 -0
  72. package/dist/chunk-B3A3IVO5.js.map +1 -0
  73. package/dist/chunk-DPDBVKNA.js +113 -0
  74. package/dist/chunk-DPDBVKNA.js.map +1 -0
  75. package/dist/chunk-FEAMR7S4.js +68 -0
  76. package/dist/chunk-FEAMR7S4.js.map +1 -0
  77. package/dist/chunk-FEY65TBV.js +15 -0
  78. package/dist/chunk-FEY65TBV.js.map +1 -0
  79. package/dist/{chunk-TRNBHPNW.js → chunk-IJBSZ4NX.js} +11 -8
  80. package/dist/chunk-IJBSZ4NX.js.map +1 -0
  81. package/dist/chunk-J5BFYY7Q.js +35 -0
  82. package/dist/chunk-J5BFYY7Q.js.map +1 -0
  83. package/dist/chunk-JRKSK2HX.js +79 -0
  84. package/dist/chunk-JRKSK2HX.js.map +1 -0
  85. package/dist/chunk-KWWR5VMS.js +45 -0
  86. package/dist/chunk-KWWR5VMS.js.map +1 -0
  87. package/dist/chunk-LPOMRC4C.js +25 -0
  88. package/dist/chunk-LPOMRC4C.js.map +1 -0
  89. package/dist/chunk-M7JBER5U.js +41 -0
  90. package/dist/chunk-M7JBER5U.js.map +1 -0
  91. package/dist/chunk-NENBUYX5.js +79 -0
  92. package/dist/chunk-NENBUYX5.js.map +1 -0
  93. package/dist/chunk-NWSEP5RT.js +117 -0
  94. package/dist/chunk-NWSEP5RT.js.map +1 -0
  95. package/dist/chunk-OJWWHFH3.js +426 -0
  96. package/dist/chunk-OJWWHFH3.js.map +1 -0
  97. package/dist/chunk-PSKN344N.js +114 -0
  98. package/dist/chunk-PSKN344N.js.map +1 -0
  99. package/dist/chunk-PX3YSS5C.js +32 -0
  100. package/dist/chunk-PX3YSS5C.js.map +1 -0
  101. package/dist/chunk-QO55FN2Y.js +20 -0
  102. package/dist/chunk-QO55FN2Y.js.map +1 -0
  103. package/dist/chunk-R7ZWUN64.js +38 -0
  104. package/dist/chunk-R7ZWUN64.js.map +1 -0
  105. package/dist/chunk-RKVQEH4J.js +61 -0
  106. package/dist/chunk-RKVQEH4J.js.map +1 -0
  107. package/dist/chunk-SRSJO5QE.js +40 -0
  108. package/dist/chunk-SRSJO5QE.js.map +1 -0
  109. package/dist/chunk-SYDMYBPG.js +31 -0
  110. package/dist/chunk-SYDMYBPG.js.map +1 -0
  111. package/dist/chunk-TB6DY7H5.js +25 -0
  112. package/dist/chunk-TB6DY7H5.js.map +1 -0
  113. package/dist/chunk-UFZKLBCF.js +108 -0
  114. package/dist/chunk-UFZKLBCF.js.map +1 -0
  115. package/dist/{chunk-MQIZAD4A.js → chunk-UKKSMZYT.js} +76 -22
  116. package/dist/chunk-UKKSMZYT.js.map +1 -0
  117. package/dist/chunk-W5L2JSBY.js +40 -0
  118. package/dist/chunk-W5L2JSBY.js.map +1 -0
  119. package/dist/chunk-Y45PPBDV.js +105 -0
  120. package/dist/chunk-Y45PPBDV.js.map +1 -0
  121. package/dist/chunk-Y6LYKFXY.js +43 -0
  122. package/dist/chunk-Y6LYKFXY.js.map +1 -0
  123. package/dist/collapsible-panel.d.ts +66 -0
  124. package/dist/collapsible-panel.js +63 -0
  125. package/dist/collapsible-panel.js.map +1 -0
  126. package/dist/device-class.d.ts +62 -0
  127. package/dist/device-class.js +103 -0
  128. package/dist/device-class.js.map +1 -0
  129. package/dist/disclosure-arrow.d.ts +14 -0
  130. package/dist/disclosure-arrow.js +4 -0
  131. package/dist/disclosure-arrow.js.map +1 -0
  132. package/dist/divider-sides-267FA7sY.d.ts +4 -0
  133. package/dist/empty-state.d.ts +1 -1
  134. package/dist/empty-state.js +1 -1
  135. package/dist/floating-toolbar.d.ts +28 -0
  136. package/dist/floating-toolbar.js +3 -0
  137. package/dist/floating-toolbar.js.map +1 -0
  138. package/dist/index.d.ts +20 -9
  139. package/dist/index.js +29 -18
  140. package/dist/list-action-row.d.ts +52 -0
  141. package/dist/list-action-row.js +5 -0
  142. package/dist/list-action-row.js.map +1 -0
  143. package/dist/list-header.d.ts +64 -0
  144. package/dist/list-header.js +7 -0
  145. package/dist/list-header.js.map +1 -0
  146. package/dist/list-inset-control.d.ts +16 -0
  147. package/dist/list-inset-control.js +3 -0
  148. package/dist/list-inset-control.js.map +1 -0
  149. package/dist/list-inset-text.d.ts +25 -0
  150. package/dist/list-inset-text.js +3 -0
  151. package/dist/list-inset-text.js.map +1 -0
  152. package/dist/list-item.d.ts +33 -0
  153. package/dist/list-item.js +5 -0
  154. package/dist/list-item.js.map +1 -0
  155. package/dist/list.d.ts +19 -0
  156. package/dist/list.js +3 -0
  157. package/dist/list.js.map +1 -0
  158. package/dist/loading-spinner.js +1 -1
  159. package/dist/nav-stack.d.ts +38 -0
  160. package/dist/nav-stack.js +5 -0
  161. package/dist/nav-stack.js.map +1 -0
  162. package/dist/pane.d.ts +49 -0
  163. package/dist/pane.js +4 -0
  164. package/dist/pane.js.map +1 -0
  165. package/dist/panel-header.d.ts +42 -0
  166. package/dist/panel-header.js +7 -0
  167. package/dist/panel-header.js.map +1 -0
  168. package/dist/resizable-region.d.ts +3 -1
  169. package/dist/resizable-region.js +1 -1
  170. package/dist/segmented-control.d.ts +8 -6
  171. package/dist/segmented-control.js +2 -1
  172. package/dist/select-register.d.ts +5 -1
  173. package/dist/select-register.js +3 -0
  174. package/dist/select-register.js.map +1 -1
  175. package/dist/select.d.ts +15 -6
  176. package/dist/select.js +2 -1
  177. package/dist/skeleton.d.ts +24 -0
  178. package/dist/skeleton.js +3 -0
  179. package/dist/skeleton.js.map +1 -0
  180. package/dist/split-view.d.ts +42 -0
  181. package/dist/split-view.js +81 -0
  182. package/dist/split-view.js.map +1 -0
  183. package/dist/state-banner.d.ts +8 -3
  184. package/dist/state-banner.js +2 -1
  185. package/dist/styles/app-tab.css +152 -0
  186. package/dist/styles/catalog.css +575 -0
  187. package/dist/styles/collapsible-panel.css +146 -0
  188. package/dist/styles/disclosure-arrow.css +23 -0
  189. package/dist/styles/empty-state.css +47 -0
  190. package/dist/styles/floating-toolbar.css +38 -0
  191. package/dist/styles/foundation.css +362 -0
  192. package/dist/styles/layout.css +55 -0
  193. package/dist/styles/list-action-row.css +160 -0
  194. package/dist/styles/list-header.css +160 -0
  195. package/dist/styles/list-inset-control.css +7 -0
  196. package/dist/styles/list-inset-text.css +14 -0
  197. package/dist/styles/list-item.css +123 -0
  198. package/dist/styles/list.css +43 -0
  199. package/dist/styles/loading-spinner.css +21 -0
  200. package/dist/styles/lucide-icon.css +6 -0
  201. package/dist/styles/nav-stack.css +111 -0
  202. package/dist/styles/pane.css +62 -0
  203. package/dist/styles/panel-header.css +64 -0
  204. package/dist/styles/resizable-region.css +111 -0
  205. package/{src → dist/styles}/segmented-control.css +58 -31
  206. package/dist/styles/select.css +140 -0
  207. package/dist/styles/skeleton.css +31 -0
  208. package/dist/styles/split-view.css +35 -0
  209. package/dist/styles/state-banner.css +198 -0
  210. package/dist/styles/styles.css +24 -0
  211. package/dist/styles/sunken-panel.css +22 -0
  212. package/dist/styles/tab-bar.css +45 -0
  213. package/dist/styles/tab-scaffold.css +84 -0
  214. package/dist/styles/token-search-field.css +289 -0
  215. package/dist/styles/toolbar-control-group.css +225 -0
  216. package/dist/styles/toolbar-text.css +92 -0
  217. package/dist/styles/toolbar.css +97 -0
  218. package/dist/styles/value-table.css +75 -0
  219. package/dist/styles/webawesome.css +399 -0
  220. package/dist/styles/workbench.css +94 -0
  221. package/dist/sunken-panel.d.ts +19 -0
  222. package/dist/sunken-panel.js +3 -0
  223. package/dist/sunken-panel.js.map +1 -0
  224. package/dist/tab-bar.d.ts +10 -2
  225. package/dist/tab-bar.js +1 -1
  226. package/dist/tab-scaffold.d.ts +30 -0
  227. package/dist/tab-scaffold.js +51 -0
  228. package/dist/tab-scaffold.js.map +1 -0
  229. package/dist/token-search-field.d.ts +59 -0
  230. package/dist/token-search-field.js +4 -0
  231. package/dist/token-search-field.js.map +1 -0
  232. package/dist/toolbar-control-group.d.ts +11 -5
  233. package/dist/toolbar-control-group.js +1 -1
  234. package/dist/toolbar-text.d.ts +31 -4
  235. package/dist/toolbar-text.js +2 -1
  236. package/dist/toolbar.d.ts +5 -3
  237. package/dist/toolbar.js +1 -1
  238. package/dist/value-table.d.ts +13 -3
  239. package/dist/value-table.js +2 -1
  240. package/dist/webawesome.d.ts +100 -0
  241. package/dist/webawesome.js +3 -0
  242. package/dist/webawesome.js.map +1 -0
  243. package/dist/wire-catalog.d.ts +57 -0
  244. package/dist/wire-catalog.js +302 -0
  245. package/dist/wire-catalog.js.map +1 -0
  246. package/dist/wire-nav-stack.d.ts +15 -0
  247. package/dist/wire-nav-stack.js +103 -0
  248. package/dist/wire-nav-stack.js.map +1 -0
  249. package/dist/wire-resizable-regions.d.ts +1 -1
  250. package/dist/wire-resizable-regions.js +1 -1
  251. package/dist/wire-sidebar.d.ts +43 -0
  252. package/dist/wire-sidebar.js +128 -0
  253. package/dist/wire-sidebar.js.map +1 -0
  254. package/dist/wire-tab-bars.d.ts +18 -2
  255. package/dist/wire-tab-bars.js +1 -1
  256. package/dist/wire-tab-scaffold.d.ts +11 -0
  257. package/dist/wire-tab-scaffold.js +21 -0
  258. package/dist/wire-tab-scaffold.js.map +1 -0
  259. package/dist/wire-token-search-fields.d.ts +111 -0
  260. package/dist/wire-token-search-fields.js +5 -0
  261. package/dist/wire-token-search-fields.js.map +1 -0
  262. package/dist/workbench.d.ts +33 -0
  263. package/dist/workbench.js +64 -0
  264. package/dist/workbench.js.map +1 -0
  265. package/docs/accessibility.md +124 -4
  266. package/docs/app-layouts.md +61 -0
  267. package/docs/catalog.md +364 -0
  268. package/docs/collapsible-panel.md +129 -0
  269. package/docs/component-contract.md +312 -5
  270. package/docs/component-selection.md +219 -0
  271. package/docs/design/templates/empty-state/filtered-dark.svg +1 -0
  272. package/docs/design/templates/empty-state/filtered.svg +1 -0
  273. package/docs/design/templates/empty-state/with-action-dark.svg +1 -0
  274. package/docs/design/templates/empty-state/with-action.svg +1 -0
  275. package/docs/design/templates/empty-state-dark.svg +1 -0
  276. package/docs/design/templates/empty-state.svg +1 -0
  277. package/docs/design/templates/list/compact-dark.svg +1 -0
  278. package/docs/design/templates/list/compact.svg +1 -0
  279. package/docs/design/templates/list/stack-dark.svg +1 -0
  280. package/docs/design/templates/list/stack.svg +1 -0
  281. package/docs/design/templates/list-action-row/default-dark.svg +1 -0
  282. package/docs/design/templates/list-action-row/default.svg +1 -0
  283. package/docs/design/templates/list-action-row/selected-dark.svg +1 -0
  284. package/docs/design/templates/list-action-row/selected.svg +1 -0
  285. package/docs/design/templates/list-action-row-dark.svg +1 -0
  286. package/docs/design/templates/list-action-row.svg +1 -0
  287. package/docs/design/templates/list-dark.svg +1 -0
  288. package/docs/design/templates/list-header/action-dark.svg +1 -0
  289. package/docs/design/templates/list-header/action.svg +1 -0
  290. package/docs/design/templates/list-header/count-dark.svg +1 -0
  291. package/docs/design/templates/list-header/count.svg +1 -0
  292. package/docs/design/templates/list-header/toggle-collapsed-dark.svg +1 -0
  293. package/docs/design/templates/list-header/toggle-collapsed.svg +1 -0
  294. package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -0
  295. package/docs/design/templates/list-header/toggle-expanded.svg +1 -0
  296. package/docs/design/templates/list-header-dark.svg +1 -0
  297. package/docs/design/templates/list-header.svg +1 -0
  298. package/docs/design/templates/list-item/default-dark.svg +1 -0
  299. package/docs/design/templates/list-item/default.svg +1 -0
  300. package/docs/design/templates/list-item/multiline-dark.svg +1 -0
  301. package/docs/design/templates/list-item/multiline.svg +1 -0
  302. package/docs/design/templates/list-item/selected-dark.svg +1 -0
  303. package/docs/design/templates/list-item/selected.svg +1 -0
  304. package/docs/design/templates/list-item/trailing-dark.svg +1 -0
  305. package/docs/design/templates/list-item/trailing.svg +1 -0
  306. package/docs/design/templates/list-item-dark.svg +1 -0
  307. package/docs/design/templates/list-item.svg +1 -0
  308. package/docs/design/templates/list.svg +1 -0
  309. package/docs/design/templates/panel-header/icon-actions-dark.svg +1 -0
  310. package/docs/design/templates/panel-header/icon-actions.svg +1 -0
  311. package/docs/design/templates/panel-header/icon-summary-actions-dark.svg +1 -0
  312. package/docs/design/templates/panel-header/icon-summary-actions.svg +1 -0
  313. package/docs/design/templates/panel-header/no-icon-dark.svg +1 -0
  314. package/docs/design/templates/panel-header/no-icon.svg +1 -0
  315. package/docs/design/templates/panel-header/page-heading-dark.svg +1 -0
  316. package/docs/design/templates/panel-header/page-heading.svg +1 -0
  317. package/docs/design/templates/panel-header/title-only-dark.svg +1 -0
  318. package/docs/design/templates/panel-header/title-only.svg +1 -0
  319. package/docs/design/templates/panel-header-dark.svg +1 -0
  320. package/docs/design/templates/panel-header.svg +1 -0
  321. package/docs/design/templates/segmented-control/equal-dark.svg +1 -0
  322. package/docs/design/templates/segmented-control/equal.svg +1 -0
  323. package/docs/design/templates/segmented-control/pill-small-dark.svg +1 -0
  324. package/docs/design/templates/segmented-control/pill-small.svg +1 -0
  325. package/docs/design/templates/segmented-control-dark.svg +1 -0
  326. package/docs/design/templates/segmented-control.svg +1 -0
  327. package/docs/design/templates/skeleton/avatar-dark.svg +1 -0
  328. package/docs/design/templates/skeleton/avatar.svg +1 -0
  329. package/docs/design/templates/skeleton/block-dark.svg +1 -0
  330. package/docs/design/templates/skeleton/block.svg +1 -0
  331. package/docs/design/templates/skeleton/lines-dark.svg +1 -0
  332. package/docs/design/templates/skeleton/lines.svg +1 -0
  333. package/docs/design/templates/skeleton-dark.svg +1 -0
  334. package/docs/design/templates/skeleton.svg +1 -0
  335. package/docs/design/templates/state-banner/danger-dark.svg +1 -0
  336. package/docs/design/templates/state-banner/danger.svg +1 -0
  337. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -0
  338. package/docs/design/templates/state-banner/info-badge.svg +1 -0
  339. package/docs/design/templates/state-banner/info-dark.svg +1 -0
  340. package/docs/design/templates/state-banner/info.svg +1 -0
  341. package/docs/design/templates/state-banner/success-dark.svg +1 -0
  342. package/docs/design/templates/state-banner/success.svg +1 -0
  343. package/docs/design/templates/state-banner/warning-dark.svg +1 -0
  344. package/docs/design/templates/state-banner/warning.svg +1 -0
  345. package/docs/design/templates/state-banner-dark.svg +1 -0
  346. package/docs/design/templates/state-banner.svg +1 -0
  347. package/docs/design/templates/tab-bar/workspace-tabs-dark.svg +1 -0
  348. package/docs/design/templates/tab-bar/workspace-tabs.svg +1 -0
  349. package/docs/design/templates/tab-bar-dark.svg +1 -0
  350. package/docs/design/templates/tab-bar.svg +1 -0
  351. package/docs/design/templates/token-search-field/empty-dark.svg +1 -0
  352. package/docs/design/templates/token-search-field/empty.svg +1 -0
  353. package/docs/design/templates/token-search-field/tokens-dark.svg +1 -0
  354. package/docs/design/templates/token-search-field/tokens.svg +1 -0
  355. package/docs/design/templates/token-search-field-dark.svg +1 -0
  356. package/docs/design/templates/token-search-field.svg +1 -0
  357. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -0
  358. package/docs/design/templates/toolbar/title-and-actions.svg +1 -0
  359. package/docs/design/templates/toolbar-control-group/borderless-single-dark.svg +1 -0
  360. package/docs/design/templates/toolbar-control-group/borderless-single.svg +1 -0
  361. package/docs/design/templates/toolbar-control-group/icon-buttons-dark.svg +1 -0
  362. package/docs/design/templates/toolbar-control-group/icon-buttons.svg +1 -0
  363. package/docs/design/templates/toolbar-control-group/push-buttons-dark.svg +1 -0
  364. package/docs/design/templates/toolbar-control-group/push-buttons.svg +1 -0
  365. package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -0
  366. package/docs/design/templates/toolbar-control-group/with-text.svg +1 -0
  367. package/docs/design/templates/toolbar-control-group-dark.svg +1 -0
  368. package/docs/design/templates/toolbar-control-group.svg +1 -0
  369. package/docs/design/templates/toolbar-dark.svg +1 -0
  370. package/docs/design/templates/toolbar-text/default-dark.svg +1 -0
  371. package/docs/design/templates/toolbar-text/default.svg +1 -0
  372. package/docs/design/templates/toolbar-text/large-dark.svg +1 -0
  373. package/docs/design/templates/toolbar-text/large.svg +1 -0
  374. package/docs/design/templates/toolbar-text/small-dark.svg +1 -0
  375. package/docs/design/templates/toolbar-text/small.svg +1 -0
  376. package/docs/design/templates/toolbar-text-dark.svg +1 -0
  377. package/docs/design/templates/toolbar-text.svg +1 -0
  378. package/docs/design/templates/toolbar.svg +1 -0
  379. package/docs/design/templates/value-table/details-dark.svg +1 -0
  380. package/docs/design/templates/value-table/details.svg +1 -0
  381. package/docs/design/templates/value-table-dark.svg +1 -0
  382. package/docs/design/templates/value-table.svg +1 -0
  383. package/docs/design/templates.md +130 -0
  384. package/docs/design-philosophy.md +59 -1
  385. package/docs/device-class.md +54 -0
  386. package/docs/examples/application-ui-profile.json +60 -0
  387. package/docs/examples/command-palette-adapter.tsx +120 -0
  388. package/docs/examples/component-catalog-extension-v2.json +81 -0
  389. package/docs/examples/component-catalog-extension.json +45 -0
  390. package/docs/layout.md +150 -0
  391. package/docs/nav-stack.md +56 -0
  392. package/docs/recipes.md +167 -0
  393. package/docs/split-view.md +49 -0
  394. package/docs/sunken-panel.md +53 -0
  395. package/docs/tab-scaffold.md +50 -0
  396. package/docs/type-contracts.md +102 -0
  397. package/docs/ui-analyzer.md +86 -0
  398. package/docs/ui-doctor.md +84 -0
  399. package/docs/ui-evaluator.md +122 -0
  400. package/docs/ux-demo.md +23 -9
  401. package/docs/webawesome-theme.md +87 -7
  402. package/docs/workbench.md +73 -0
  403. package/doctor/cli.mjs +115 -0
  404. package/doctor/config.schema.json +68 -0
  405. package/doctor/eslint-diagnostics.d.mts +8 -0
  406. package/doctor/eslint-diagnostics.mjs +7 -0
  407. package/doctor/index.d.mts +116 -0
  408. package/doctor/index.mjs +1257 -0
  409. package/doctor/report.schema.json +118 -0
  410. package/evaluator/cli.mjs +89 -0
  411. package/evaluator/index.d.mts +90 -0
  412. package/evaluator/index.mjs +1138 -0
  413. package/evaluator/report.schema.json +152 -0
  414. package/llms.txt +197 -14
  415. package/package.json +228 -46
  416. package/traversal-exclusions.mjs +27 -0
  417. package/ux-demo/recipes/app-shell.tsx +228 -0
  418. package/ux-demo/recipes/collapsible-sidebar.tsx +223 -0
  419. package/ux-demo/recipes/compact-toolbar.tsx +145 -0
  420. package/ux-demo/recipes/composer-form.tsx +151 -0
  421. package/ux-demo/recipes/list-detail-dialog.tsx +181 -0
  422. package/ux-demo/recipes/list-workspace-states.tsx +183 -0
  423. package/ux-demo/recipes/loaders.ts +36 -0
  424. package/ux-demo/recipes/loading-inspector.tsx +164 -0
  425. package/ux-demo/recipes/mount-recipe.ts +64 -0
  426. package/ux-demo/recipes/navigation-sidebar.tsx +137 -0
  427. package/ux-demo/recipes/navigation-stack.tsx +118 -0
  428. package/ux-demo/recipes/recipes.css +448 -0
  429. package/ux-demo/recipes/types.ts +19 -0
  430. package/ux-demo/recipes/workspace-header.tsx +67 -0
  431. package/dist/browser/dialog-header.js +0 -3
  432. package/dist/browser/menu-header.js +0 -3
  433. package/dist/browser/menu-item.js +0 -3
  434. package/dist/browser/page-header.js +0 -3
  435. package/dist/chunk-2PES33HS.js +0 -13
  436. package/dist/chunk-2PES33HS.js.map +0 -1
  437. package/dist/chunk-3O7ZEUW2.js +0 -27
  438. package/dist/chunk-3O7ZEUW2.js.map +0 -1
  439. package/dist/chunk-4HHCYSGT.js +0 -79
  440. package/dist/chunk-4HHCYSGT.js.map +0 -1
  441. package/dist/chunk-55DATV3A.js +0 -10
  442. package/dist/chunk-55DATV3A.js.map +0 -1
  443. package/dist/chunk-723YNJBH.js +0 -17
  444. package/dist/chunk-723YNJBH.js.map +0 -1
  445. package/dist/chunk-DAFFDO33.js +0 -14
  446. package/dist/chunk-DAFFDO33.js.map +0 -1
  447. package/dist/chunk-GY5WH7TO.js +0 -10
  448. package/dist/chunk-GY5WH7TO.js.map +0 -1
  449. package/dist/chunk-MBR3TYNL.js +0 -14
  450. package/dist/chunk-MBR3TYNL.js.map +0 -1
  451. package/dist/chunk-MFKMHK5W.js +0 -10
  452. package/dist/chunk-MFKMHK5W.js.map +0 -1
  453. package/dist/chunk-MQIZAD4A.js.map +0 -1
  454. package/dist/chunk-NHXX4JI5.js +0 -28
  455. package/dist/chunk-NHXX4JI5.js.map +0 -1
  456. package/dist/chunk-OPCJRCYO.js +0 -19
  457. package/dist/chunk-OPCJRCYO.js.map +0 -1
  458. package/dist/chunk-T5XNMVOI.js +0 -24
  459. package/dist/chunk-T5XNMVOI.js.map +0 -1
  460. package/dist/chunk-TRNBHPNW.js.map +0 -1
  461. package/dist/chunk-VHW5YNIB.js +0 -17
  462. package/dist/chunk-VHW5YNIB.js.map +0 -1
  463. package/dist/chunk-VKH2JLDZ.js +0 -10
  464. package/dist/chunk-VKH2JLDZ.js.map +0 -1
  465. package/dist/chunk-WVPYVDXY.js +0 -15
  466. package/dist/chunk-WVPYVDXY.js.map +0 -1
  467. package/dist/chunk-WY4CAEPG.js +0 -14
  468. package/dist/chunk-WY4CAEPG.js.map +0 -1
  469. package/dist/dialog-header.d.ts +0 -14
  470. package/dist/dialog-header.js +0 -3
  471. package/dist/dialog-header.js.map +0 -1
  472. package/dist/menu-header.d.ts +0 -15
  473. package/dist/menu-header.js +0 -3
  474. package/dist/menu-header.js.map +0 -1
  475. package/dist/menu-item.d.ts +0 -22
  476. package/dist/menu-item.js +0 -3
  477. package/dist/menu-item.js.map +0 -1
  478. package/dist/page-header.d.ts +0 -9
  479. package/dist/page-header.js +0 -3
  480. package/dist/page-header.js.map +0 -1
  481. package/src/app-tab.css +0 -20
  482. package/src/dialog-header.css +0 -9
  483. package/src/empty-state.css +0 -6
  484. package/src/foundation.css +0 -106
  485. package/src/loading-spinner.css +0 -4
  486. package/src/lucide-icon.css +0 -1
  487. package/src/menu-header.css +0 -13
  488. package/src/menu-item.css +0 -12
  489. package/src/page-header.css +0 -3
  490. package/src/resizable-region.css +0 -19
  491. package/src/select.css +0 -18
  492. package/src/state-banner.css +0 -31
  493. package/src/styles.css +0 -18
  494. package/src/tab-bar.css +0 -4
  495. package/src/toolbar-control-group.css +0 -25
  496. package/src/toolbar-text.css +0 -4
  497. package/src/toolbar.css +0 -9
  498. package/src/value-table.css +0 -5
  499. package/src/webawesome.css +0 -178
@@ -0,0 +1,737 @@
1
+ import { readFile, readdir, stat } from 'node:fs/promises';
2
+ import {
3
+ basename,
4
+ dirname,
5
+ extname,
6
+ isAbsolute,
7
+ relative,
8
+ resolve,
9
+ } from 'node:path';
10
+ import process from 'node:process';
11
+
12
+ import postcss from 'postcss';
13
+ import ts from 'typescript';
14
+
15
+ import { loadApplicationUiProfile } from '../ai/application-ui-profile.mjs';
16
+ import { isUiTraversalExcluded } from '../traversal-exclusions.mjs';
17
+
18
+ export const UI_ANALYSIS_SCHEMA_VERSION = 1;
19
+
20
+ export const UI_ANALYSIS_RULES = Object.freeze({
21
+ 'KUI-L001': { severity: 'error', title: 'Private or unknown Kerf selector' },
22
+ 'KUI-L002': { severity: 'error', title: 'Unknown Kerf token' },
23
+ 'KUI-L003': { severity: 'error', title: 'Competing geometry owners' },
24
+ 'KUI-L004': { severity: 'review', title: 'Repeated content inset' },
25
+ 'KUI-L005': { severity: 'review', title: 'Forced component dimension' },
26
+ 'KUI-L006': {
27
+ severity: 'review',
28
+ title: 'Hard-coded spacing outside the approved scale',
29
+ },
30
+ 'KUI-L007': { severity: 'error', title: 'Nested scroll owners' },
31
+ 'KUI-L008': { severity: 'review', title: 'Dynamic class requires review' },
32
+ 'KUI-L009': { severity: 'error', title: 'Stylesheet could not be parsed' },
33
+ });
34
+
35
+ const sourceExtensions = new Set(['.tsx', '.jsx']);
36
+ const spacingProperties = /^(?:margin|padding|gap|inset)(?:-|$)/;
37
+ const dimensionProperties =
38
+ /^(?:width|height|min-width|max-width|min-height|max-height)$/;
39
+ const approvedSpacing = new Set([0, 4, 8, 16, 24]);
40
+ async function collectFiles(root, paths) {
41
+ const files = [];
42
+ const visit = async (path) => {
43
+ if (isUiTraversalExcluded(root, path)) return;
44
+ const details = await stat(path);
45
+ if (details.isFile()) {
46
+ if (sourceExtensions.has(extname(path)) || path.endsWith('.css'))
47
+ files.push(path);
48
+ return;
49
+ }
50
+ const entries = await readdir(path, { withFileTypes: true });
51
+ for (const entry of entries) {
52
+ const child = resolve(path, entry.name);
53
+ if (entry.isDirectory()) await visit(child);
54
+ else if (
55
+ sourceExtensions.has(extname(entry.name)) ||
56
+ entry.name.endsWith('.css')
57
+ )
58
+ files.push(child);
59
+ }
60
+ };
61
+ for (const path of paths?.length ? paths : [root])
62
+ await visit(resolve(root, path));
63
+ return files.sort();
64
+ }
65
+
66
+ function location(file, node, source) {
67
+ if (!source) {
68
+ const point = node.source?.start;
69
+ return {
70
+ file,
71
+ line: point?.line ?? 1,
72
+ column: point?.column ?? point?.offset ?? 1,
73
+ };
74
+ }
75
+ const point = source.getLineAndCharacterOfPosition(node.getStart(source));
76
+ return {
77
+ file,
78
+ line: point.line + 1,
79
+ column: point.character + 1,
80
+ };
81
+ }
82
+
83
+ function diagnostic(ruleId, at, message, evidence, chain) {
84
+ const rule = UI_ANALYSIS_RULES[ruleId];
85
+ return {
86
+ ruleId,
87
+ severity: rule.severity,
88
+ message,
89
+ location: at,
90
+ evidence,
91
+ chain,
92
+ };
93
+ }
94
+
95
+ function isSuppressed(item, profile, root) {
96
+ const target = relative(root, item.location.file).replaceAll('\\', '/');
97
+ return (profile?.exceptions ?? []).some(
98
+ (exception) =>
99
+ exception.rules?.includes(item.ruleId) && exception.target === target,
100
+ );
101
+ }
102
+
103
+ function relativeStyleImports(file, source) {
104
+ const imports = [];
105
+ if (file.endsWith('.css')) {
106
+ let stylesheet;
107
+ try {
108
+ stylesheet = postcss.parse(source, { from: file });
109
+ } catch {
110
+ return imports;
111
+ }
112
+ stylesheet.walkAtRules('import', (rule) => {
113
+ const match = rule.params.match(
114
+ /^\s*(?:["']([^"']+)["']|url\(\s*(?:["']([^"']+)["']|([^\s)]+))\s*\))/,
115
+ );
116
+ const specifier = match?.[1] ?? match?.[2] ?? match?.[3];
117
+ if (specifier?.startsWith('.'))
118
+ imports.push(resolve(dirname(file), specifier));
119
+ });
120
+ return imports;
121
+ }
122
+ const syntax = file.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.JSX;
123
+ const module = ts.createSourceFile(
124
+ file,
125
+ source,
126
+ ts.ScriptTarget.Latest,
127
+ true,
128
+ syntax,
129
+ );
130
+ for (const statement of module.statements) {
131
+ if (
132
+ !ts.isImportDeclaration(statement) ||
133
+ !ts.isStringLiteral(statement.moduleSpecifier)
134
+ )
135
+ continue;
136
+ const specifier = statement.moduleSpecifier.text;
137
+ if (specifier.startsWith('.') && specifier.endsWith('.css'))
138
+ imports.push(resolve(dirname(file), specifier));
139
+ }
140
+ return imports;
141
+ }
142
+
143
+ function reachableStyleFacts(file, imports, styleFacts) {
144
+ const result = new Map();
145
+ for (const style of reachableStyleFiles(file, imports, styleFacts)) {
146
+ for (const [className, facts] of styleFacts.get(style)) {
147
+ const current = result.get(className) ?? { scroll: false, inset: false };
148
+ current.scroll ||= facts.scroll;
149
+ current.inset ||= facts.inset;
150
+ result.set(className, current);
151
+ }
152
+ }
153
+ return result;
154
+ }
155
+
156
+ function reachableStyleFiles(file, imports, styleFacts) {
157
+ const visited = new Set();
158
+ const visit = (style) => {
159
+ if (visited.has(style) || !styleFacts.has(style)) return;
160
+ visited.add(style);
161
+ for (const dependency of imports.get(style) ?? []) visit(dependency);
162
+ };
163
+ for (const style of imports.get(file) ?? []) visit(style);
164
+ return visited;
165
+ }
166
+
167
+ async function loadCatalogs(profileResult) {
168
+ const entries = [];
169
+ for (const catalog of profileResult.profile?.catalogs ?? []) {
170
+ const owner = profileResult.provenance[`$catalogs.${catalog.package}`];
171
+ if (!owner) continue;
172
+ try {
173
+ const artifact = JSON.parse(
174
+ await readFile(
175
+ resolve(dirname(owner), catalog.composition.path),
176
+ 'utf8',
177
+ ),
178
+ );
179
+ entries.push(...(artifact.entries ?? []));
180
+ } catch {
181
+ // Profile validation reports the precise catalog load failure.
182
+ }
183
+ }
184
+ return entries;
185
+ }
186
+
187
+ function catalogFacts(entries) {
188
+ const publicClasses = new Set();
189
+ const publicTokens = new Set();
190
+ const classEntries = new Map();
191
+ const exportEntries = new Map();
192
+ for (const entry of entries) {
193
+ exportEntries.set(entry.name, entry);
194
+ for (const item of entry.publicExports ?? [])
195
+ exportEntries.set(typeof item === 'string' ? item : item.name, entry);
196
+ for (const className of entry.boundaries?.publicClasses ?? []) {
197
+ publicClasses.add(className);
198
+ const owners = classEntries.get(className) ?? [];
199
+ owners.push(entry);
200
+ classEntries.set(className, owners);
201
+ }
202
+ for (const token of entry.boundaries?.publicTokens ?? [])
203
+ publicTokens.add(token);
204
+ }
205
+ return { publicClasses, publicTokens, classEntries, exportEntries };
206
+ }
207
+
208
+ function spacingValues(value) {
209
+ const results = [];
210
+ for (const match of value.matchAll(/(-?\d*\.?\d+)(px|rem)\b/g)) {
211
+ const numeric = Number(match[1]) * (match[2] === 'rem' ? 16 : 1);
212
+ results.push({ text: match[0], pixels: numeric });
213
+ }
214
+ return results;
215
+ }
216
+
217
+ async function inspectCss(file, source, facts, diagnostics, cssFacts) {
218
+ let root;
219
+ try {
220
+ root = postcss.parse(source, { from: file });
221
+ } catch (error) {
222
+ diagnostics.push(
223
+ diagnostic(
224
+ 'KUI-L009',
225
+ { file, line: error.line ?? 1, column: error.column ?? 1 },
226
+ error.reason ?? error.message,
227
+ ),
228
+ );
229
+ return;
230
+ }
231
+ root.walkRules((rule) => {
232
+ const classes = [
233
+ ...rule.selector.matchAll(/\.([_a-zA-Z]+[_a-zA-Z0-9-]*)/g),
234
+ ].map((match) => match[1]);
235
+ for (const className of classes) {
236
+ if (className.startsWith('kui-') && !facts.publicClasses.has(className))
237
+ diagnostics.push(
238
+ diagnostic(
239
+ 'KUI-L001',
240
+ location(file, rule),
241
+ `.${className} is not a cataloged public Kerf class.`,
242
+ { selector: rule.selector, className },
243
+ ),
244
+ );
245
+ }
246
+ rule.walkDecls((decl) => {
247
+ const at = location(file, decl);
248
+ const tokens = [
249
+ ...(decl.prop.match(/--kui-[a-z0-9-]+/g) ?? []),
250
+ ...(decl.value.match(/--kui-[a-z0-9-]+/g) ?? []),
251
+ ];
252
+ for (const token of new Set(tokens))
253
+ if (!facts.publicTokens.has(token))
254
+ diagnostics.push(
255
+ diagnostic(
256
+ 'KUI-L002',
257
+ at,
258
+ `${token} is not a cataloged public Kerf token.`,
259
+ { property: decl.prop, value: decl.value, token },
260
+ ),
261
+ );
262
+ if (
263
+ spacingProperties.test(decl.prop) &&
264
+ !/var\(|calc\(|remify\(/.test(decl.value)
265
+ )
266
+ for (const value of spacingValues(decl.value))
267
+ if (!approvedSpacing.has(value.pixels))
268
+ diagnostics.push(
269
+ diagnostic(
270
+ 'KUI-L006',
271
+ at,
272
+ `${decl.prop}: ${value.text} is outside the approved Kerf spacing scale.`,
273
+ {
274
+ property: decl.prop,
275
+ value: decl.value,
276
+ pixels: value.pixels,
277
+ },
278
+ ),
279
+ );
280
+ for (const className of classes) {
281
+ const record = cssFacts.get(className) ?? {
282
+ scroll: false,
283
+ inset: false,
284
+ };
285
+ if (
286
+ /^overflow(?:-|$)/.test(decl.prop) &&
287
+ /\b(?:auto|scroll)\b/.test(decl.value)
288
+ )
289
+ record.scroll = true;
290
+ if (/^padding(?:-|$)/.test(decl.prop) && decl.value !== '0')
291
+ record.inset = true;
292
+ cssFacts.set(className, record);
293
+ if (
294
+ facts.publicClasses.has(className) &&
295
+ dimensionProperties.test(decl.prop)
296
+ )
297
+ diagnostics.push(
298
+ diagnostic(
299
+ 'KUI-L005',
300
+ at,
301
+ `.${className} forces ${decl.prop}; prefer the component's public sizing contract.`,
302
+ {
303
+ selector: rule.selector,
304
+ property: decl.prop,
305
+ value: decl.value,
306
+ },
307
+ ),
308
+ );
309
+ }
310
+ });
311
+ });
312
+ }
313
+
314
+ function literalClasses(attribute) {
315
+ if (!attribute?.initializer) return { values: [], dynamic: false };
316
+ const value = attribute.initializer;
317
+ if (ts.isStringLiteral(value))
318
+ return { values: value.text.split(/\s+/).filter(Boolean), dynamic: false };
319
+ if (
320
+ ts.isJsxExpression(value) &&
321
+ value.expression &&
322
+ ts.isStringLiteralLike(value.expression)
323
+ )
324
+ return {
325
+ values: value.expression.text.split(/\s+/).filter(Boolean),
326
+ dynamic: false,
327
+ };
328
+ return { values: [], dynamic: true };
329
+ }
330
+
331
+ function inspectTsx(file, sourceText, facts, cssFacts, diagnostics) {
332
+ const source = ts.createSourceFile(
333
+ file,
334
+ sourceText,
335
+ ts.ScriptTarget.Latest,
336
+ true,
337
+ ts.ScriptKind.TSX,
338
+ );
339
+ const imports = new Map();
340
+ for (const statement of source.statements) {
341
+ if (
342
+ !ts.isImportDeclaration(statement) ||
343
+ !ts.isStringLiteral(statement.moduleSpecifier)
344
+ )
345
+ continue;
346
+ const module = statement.moduleSpecifier.text;
347
+ if (!module.includes('kerf') && !module.startsWith('@')) continue;
348
+ const bindings = statement.importClause?.namedBindings;
349
+ if (!bindings || !ts.isNamedImports(bindings)) continue;
350
+ for (const item of bindings.elements) {
351
+ const exported = item.propertyName?.text ?? item.name.text;
352
+ const entry = facts.exportEntries.get(exported);
353
+ if (entry) imports.set(item.name.text, entry);
354
+ }
355
+ }
356
+ const stack = [];
357
+ const visit = (node) => {
358
+ if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
359
+ const opening = ts.isJsxElement(node) ? node.openingElement : node;
360
+ const tag = opening.tagName.getText(source);
361
+ const entry = imports.get(tag);
362
+ const classAttribute = opening.attributes.properties.find(
363
+ (item) =>
364
+ ts.isJsxAttribute(item) &&
365
+ ['class', 'className'].includes(item.name.getText(source)),
366
+ );
367
+ const classes = literalClasses(classAttribute);
368
+ const at = location(file, opening, source);
369
+ if (classes.dynamic)
370
+ diagnostics.push(
371
+ diagnostic(
372
+ 'KUI-L008',
373
+ at,
374
+ 'Dynamic class expression was left unclassified; review it against cataloged public classes.',
375
+ { tag },
376
+ ),
377
+ );
378
+ const owners = classes.values.flatMap(
379
+ (className) => facts.classEntries.get(className) ?? [],
380
+ );
381
+ for (const property of ['margin', 'border', 'padding']) {
382
+ const selfOwners = owners.filter(
383
+ (owner) => owner.layout?.geometry?.[property] === 'self',
384
+ );
385
+ if (new Set(selfOwners.map((owner) => owner.key)).size > 1)
386
+ diagnostics.push(
387
+ diagnostic(
388
+ 'KUI-L003',
389
+ at,
390
+ `One element combines multiple ${property} owners: ${selfOwners.map((owner) => owner.key).join(', ')}.`,
391
+ { property, classes: classes.values },
392
+ selfOwners.map((owner) => owner.key),
393
+ ),
394
+ );
395
+ }
396
+ const scroll = classes.values.some(
397
+ (className) => cssFacts.get(className)?.scroll,
398
+ );
399
+ const inset = classes.values.some(
400
+ (className) => cssFacts.get(className)?.inset,
401
+ );
402
+ const parent = stack.at(-1);
403
+ if (scroll && parent?.scroll)
404
+ diagnostics.push(
405
+ diagnostic(
406
+ 'KUI-L007',
407
+ at,
408
+ 'A scroll-owning element is nested inside another declared scroll owner.',
409
+ { classes: classes.values },
410
+ [parent.label, entry?.key ?? tag],
411
+ ),
412
+ );
413
+ if (inset && parent?.inset)
414
+ diagnostics.push(
415
+ diagnostic(
416
+ 'KUI-L004',
417
+ at,
418
+ 'Nested elements both add content padding; confirm that the repeated inset is intentional.',
419
+ { classes: classes.values },
420
+ [parent.label, entry?.key ?? tag],
421
+ ),
422
+ );
423
+ stack.push({
424
+ scroll: scroll || parent?.scroll,
425
+ inset: inset || parent?.inset,
426
+ label: entry?.key ?? tag,
427
+ });
428
+ if (ts.isJsxElement(node))
429
+ for (const child of node.children) visit(child);
430
+ stack.pop();
431
+ return;
432
+ }
433
+ ts.forEachChild(node, visit);
434
+ };
435
+ visit(source);
436
+ }
437
+
438
+ export async function analyzeUiProject({
439
+ root: requestedRoot = process.cwd(),
440
+ paths,
441
+ knownRules = [],
442
+ profile: packageProfile = resolve(
443
+ import.meta.dirname,
444
+ '../ai/application-ui-profile.defaults.json',
445
+ ),
446
+ } = {}) {
447
+ const root = resolve(requestedRoot);
448
+ const files = await collectFiles(root, paths);
449
+ const diagnosticDecisions = new Map();
450
+ const contents = new Map(
451
+ await Promise.all(
452
+ files.map(async (file) => [file, await readFile(file, 'utf8')]),
453
+ ),
454
+ );
455
+ const discoveredFiles = new Set(files);
456
+ for (let index = 0; index < files.length; index += 1) {
457
+ const file = files[index];
458
+ for (const dependency of relativeStyleImports(file, contents.get(file))) {
459
+ const projectPath = relative(root, dependency);
460
+ if (
461
+ discoveredFiles.has(dependency) ||
462
+ projectPath === '..' ||
463
+ projectPath.startsWith('../')
464
+ )
465
+ continue;
466
+ try {
467
+ const source = await readFile(dependency, 'utf8');
468
+ discoveredFiles.add(dependency);
469
+ files.push(dependency);
470
+ contents.set(dependency, source);
471
+ } catch {
472
+ // TypeScript or the CSS toolchain reports missing imports; analysis stays read-only.
473
+ }
474
+ }
475
+ }
476
+ files.sort();
477
+ const profileContexts = new Map();
478
+ const loadContext = async (file) => {
479
+ const startDirectory = file === root ? root : dirname(file);
480
+ if (!profileContexts.has(startDirectory))
481
+ profileContexts.set(
482
+ startDirectory,
483
+ (async () => {
484
+ const profileResult = await loadApplicationUiProfile({
485
+ workspaceRoot: root,
486
+ startDirectory,
487
+ packageProfile,
488
+ knownRules: [...Object.keys(UI_ANALYSIS_RULES), ...knownRules],
489
+ });
490
+ const entries = await loadCatalogs(profileResult);
491
+ return {
492
+ profileResult,
493
+ facts: catalogFacts(entries),
494
+ startDirectory,
495
+ };
496
+ })(),
497
+ );
498
+ return profileContexts.get(startDirectory);
499
+ };
500
+ const contexts = new Map(
501
+ await Promise.all(
502
+ files.map(async (file) => [file, await loadContext(file)]),
503
+ ),
504
+ );
505
+ if (!files.length) await loadContext(root);
506
+ const imports = new Map(
507
+ files.map((file) => [file, relativeStyleImports(file, contents.get(file))]),
508
+ );
509
+ const styleFacts = new Map();
510
+ const recordDiagnostics = (items, context) => {
511
+ for (const item of items) {
512
+ const key = JSON.stringify([
513
+ item.ruleId,
514
+ item.location,
515
+ item.message,
516
+ item.evidence,
517
+ item.chain,
518
+ ]);
519
+ const decision = diagnosticDecisions.get(key) ?? {
520
+ item,
521
+ active: false,
522
+ suppressed: false,
523
+ activeConsumers: new Set(),
524
+ suppressedConsumers: new Set(),
525
+ };
526
+ const consumer =
527
+ relative(root, context.startDirectory).replaceAll('\\', '/') || '.';
528
+ if (isSuppressed(item, context.profileResult.profile, root)) {
529
+ decision.suppressed = true;
530
+ decision.suppressedConsumers.add(consumer);
531
+ } else {
532
+ decision.active = true;
533
+ decision.activeConsumers.add(consumer);
534
+ }
535
+ diagnosticDecisions.set(key, decision);
536
+ }
537
+ };
538
+ for (const file of files.filter((item) => item.endsWith('.css'))) {
539
+ const context = contexts.get(file);
540
+ const fileFacts = new Map();
541
+ await inspectCss(file, contents.get(file), context.facts, [], fileFacts);
542
+ styleFacts.set(file, fileFacts);
543
+ }
544
+ const styleConsumers = new Map(
545
+ [...styleFacts.keys()].map((file) => [file, new Set()]),
546
+ );
547
+ for (const file of files.filter((item) =>
548
+ sourceExtensions.has(extname(item)),
549
+ )) {
550
+ const context = contexts.get(file);
551
+ for (const style of reachableStyleFiles(file, imports, styleFacts))
552
+ styleConsumers.get(style).add(context);
553
+ const fileDiagnostics = [];
554
+ inspectTsx(
555
+ file,
556
+ contents.get(file),
557
+ context.facts,
558
+ reachableStyleFacts(file, imports, styleFacts),
559
+ fileDiagnostics,
560
+ );
561
+ recordDiagnostics(fileDiagnostics, context);
562
+ }
563
+ for (const [file, consumers] of styleConsumers) {
564
+ const applicableContexts = consumers.size
565
+ ? consumers
566
+ : new Set([contexts.get(file)]);
567
+ for (const context of applicableContexts) {
568
+ const fileDiagnostics = [];
569
+ await inspectCss(
570
+ file,
571
+ contents.get(file),
572
+ context.facts,
573
+ fileDiagnostics,
574
+ new Map(),
575
+ );
576
+ recordDiagnostics(fileDiagnostics, context);
577
+ }
578
+ }
579
+
580
+ const active = [...diagnosticDecisions.values()]
581
+ .filter((decision) => decision.active)
582
+ .map((decision) => {
583
+ if (
584
+ !decision.suppressedConsumers.size &&
585
+ decision.activeConsumers.size <= 1
586
+ )
587
+ return decision.item;
588
+ return {
589
+ ...decision.item,
590
+ evidence: {
591
+ ...decision.item.evidence,
592
+ consumers: {
593
+ active: [...decision.activeConsumers].sort(),
594
+ suppressed: [...decision.suppressedConsumers].sort(),
595
+ },
596
+ },
597
+ };
598
+ });
599
+ const suppressed = [...diagnosticDecisions.values()].filter(
600
+ (decision) => !decision.active && decision.suppressed,
601
+ ).length;
602
+ active.sort(
603
+ (a, b) =>
604
+ a.location.file.localeCompare(b.location.file) ||
605
+ a.location.line - b.location.line ||
606
+ a.ruleId.localeCompare(b.ruleId),
607
+ );
608
+ const portablePath = (file) => {
609
+ if (!file || !isAbsolute(file)) return file;
610
+ const path = relative(root, file).replaceAll('\\', '/');
611
+ return path === '..' || path.startsWith('../')
612
+ ? `<external>/${basename(file)}`
613
+ : path;
614
+ };
615
+ const resolvedContexts = await Promise.all(profileContexts.values());
616
+ const profileFiles = [
617
+ ...new Set(
618
+ resolvedContexts.flatMap(
619
+ ({ profileResult }) => profileResult.files ?? [],
620
+ ),
621
+ ),
622
+ ];
623
+ const profilePackageRoot = dirname(profileFiles[0] ?? root);
624
+ const portableMessage = (message) =>
625
+ String(message)
626
+ .replaceAll(root, '<repo-root>')
627
+ .replaceAll(profilePackageRoot, '<package-root>');
628
+ const profileDiagnostics = [
629
+ ...new Map(
630
+ resolvedContexts
631
+ .flatMap(({ profileResult }) => profileResult.diagnostics ?? [])
632
+ .map((item) => [
633
+ JSON.stringify([item.code, item.source, item.path, item.message]),
634
+ {
635
+ ...item,
636
+ source: portablePath(item.source),
637
+ message: portableMessage(item.message),
638
+ },
639
+ ]),
640
+ ).values(),
641
+ ];
642
+ return {
643
+ schemaVersion: UI_ANALYSIS_SCHEMA_VERSION,
644
+ tool: { name: '@kerfjs/ui analyzer', version: 1 },
645
+ root: '.',
646
+ files: files.map((file) => relative(root, file).replaceAll('\\', '/')),
647
+ profile: {
648
+ files: profileFiles.map(portablePath),
649
+ diagnostics: profileDiagnostics,
650
+ },
651
+ diagnostics: active.map((item) => ({
652
+ ...item,
653
+ location: {
654
+ ...item.location,
655
+ file: portablePath(item.location.file),
656
+ },
657
+ })),
658
+ summary: {
659
+ errors:
660
+ active.filter((item) => item.severity === 'error').length +
661
+ profileDiagnostics.length,
662
+ review: active.filter((item) => item.severity === 'review').length,
663
+ suppressed,
664
+ },
665
+ };
666
+ }
667
+
668
+ export function formatUiAnalysisText(report) {
669
+ const lines = report.profile.diagnostics.map(
670
+ (item) =>
671
+ `${item.source ?? '<profile>'}:1:1 error ${item.code} ${item.message}`,
672
+ );
673
+ lines.push(
674
+ ...report.diagnostics.map(
675
+ (item) =>
676
+ `${item.location.file}:${item.location.line}:${item.location.column} ${item.severity} ${item.ruleId} ${item.message}`,
677
+ ),
678
+ );
679
+ lines.push(
680
+ `Kerf UI analysis: ${report.summary.errors} error(s), ${report.summary.review} review finding(s), ${report.summary.suppressed} suppressed.`,
681
+ );
682
+ return lines.join('\n');
683
+ }
684
+
685
+ export function formatUiAnalysisSarif(report) {
686
+ return {
687
+ version: '2.1.0',
688
+ $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
689
+ runs: [
690
+ {
691
+ tool: {
692
+ driver: {
693
+ name: report.tool.name,
694
+ version: String(report.tool.version),
695
+ rules: Object.entries(UI_ANALYSIS_RULES).map(([id, rule]) => ({
696
+ id,
697
+ shortDescription: { text: rule.title },
698
+ })),
699
+ },
700
+ },
701
+ results: [
702
+ ...report.profile.diagnostics.map((item) => ({
703
+ ruleId: item.code,
704
+ level: 'error',
705
+ message: { text: item.message },
706
+ locations: [
707
+ {
708
+ physicalLocation: {
709
+ artifactLocation: { uri: item.source ?? '<profile>' },
710
+ region: { startLine: 1, startColumn: 1 },
711
+ },
712
+ },
713
+ ],
714
+ properties: { path: item.path },
715
+ })),
716
+ ...report.diagnostics.map((item) => ({
717
+ ruleId: item.ruleId,
718
+ level: item.severity === 'error' ? 'error' : 'warning',
719
+ message: { text: item.message },
720
+ locations: [
721
+ {
722
+ physicalLocation: {
723
+ artifactLocation: { uri: item.location.file },
724
+ region: {
725
+ startLine: item.location.line,
726
+ startColumn: item.location.column,
727
+ },
728
+ },
729
+ },
730
+ ],
731
+ properties: { evidence: item.evidence, chain: item.chain },
732
+ })),
733
+ ],
734
+ },
735
+ ],
736
+ };
737
+ }