@kerfjs/ui 4.5.0-beta.1 → 5.0.0-beta.100

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 (980) hide show
  1. package/README.md +461 -116
  2. package/ai/application-ui-diagnostic-ids-v1.json +23 -1
  3. package/ai/application-ui-profile-sync.cjs +21 -2
  4. package/ai/application-ui-profile.d.ts +4 -0
  5. package/ai/application-ui-profile.defaults.json +63 -8
  6. package/ai/application-ui-profile.mjs +28 -1
  7. package/ai/application-ui-profile.schema.json +10 -0
  8. package/ai/catalog-authoring.json +13 -4
  9. package/ai/catalog-authoring.schema.json +52 -8
  10. package/ai/compile-time-contracts-v1.json +63 -16
  11. package/ai/compile-time-contracts-v1.schema.json +4 -1
  12. package/ai/component-catalog-extension.schema.json +25 -0
  13. package/ai/component-catalog.json +5585 -1175
  14. package/ai/component-catalog.schema.json +78 -1
  15. package/ai/component-class-owners.cjs +83 -0
  16. package/ai/component-composition-extension.d.ts +7 -0
  17. package/ai/component-composition-extension.schema.json +7 -0
  18. package/ai/{component-catalog-v2-overrides.json → component-composition-rules.json} +281 -49
  19. package/ai/component-composition.d.ts +132 -0
  20. package/ai/component-composition.json +8729 -0
  21. package/ai/component-composition.schema.json +412 -0
  22. package/ai/components/README.md +185 -0
  23. package/ai/components/anchored-surface.md +99 -0
  24. package/ai/components/application-tabs.md +65 -0
  25. package/ai/components/badge.md +76 -0
  26. package/ai/components/chip.md +76 -0
  27. package/ai/components/choice-chips.md +87 -0
  28. package/ai/components/collapsible-panel.md +103 -0
  29. package/ai/components/column-track.md +78 -0
  30. package/ai/components/combobox.md +88 -0
  31. package/ai/components/composer.md +83 -0
  32. package/ai/components/content-item.md +98 -0
  33. package/ai/components/content-swap.md +78 -0
  34. package/ai/components/corner-status.md +88 -0
  35. package/ai/components/disclosure-arrow.md +77 -0
  36. package/ai/components/document-baseline.md +77 -0
  37. package/ai/components/edge-activity-indicator.md +81 -0
  38. package/ai/components/empty-state.md +80 -0
  39. package/ai/components/feedback.md +71 -0
  40. package/ai/components/file-picker.md +88 -0
  41. package/ai/components/floating-toolbar.md +82 -0
  42. package/ai/components/foundation.md +65 -0
  43. package/ai/components/framed-list.md +80 -0
  44. package/ai/components/grid.md +101 -0
  45. package/ai/components/headers.md +63 -0
  46. package/ai/components/icon-button.md +82 -0
  47. package/ai/components/icon-toggle-button.md +80 -0
  48. package/ai/components/layout.md +75 -0
  49. package/ai/components/list-action-row.md +108 -0
  50. package/ai/components/list-header.md +91 -0
  51. package/ai/components/list-inset-control.md +74 -0
  52. package/ai/components/list-inset-text.md +72 -0
  53. package/ai/components/list-item.md +108 -0
  54. package/ai/components/list.md +125 -0
  55. package/ai/components/loading-spinner.md +73 -0
  56. package/ai/components/lucide-icon.md +97 -0
  57. package/ai/components/nav-stack.md +115 -0
  58. package/ai/components/pane.md +115 -0
  59. package/ai/components/popup-menu.md +171 -0
  60. package/ai/components/recipe-app-shell.md +57 -0
  61. package/ai/components/recipe-collapsible-sidebar.md +57 -0
  62. package/ai/components/recipe-compact-toolbar.md +57 -0
  63. package/ai/components/recipe-composer-form.md +57 -0
  64. package/ai/components/recipe-list-detail-dialog.md +57 -0
  65. package/ai/components/recipe-list-workspace-states.md +57 -0
  66. package/ai/components/recipe-loading-inspector.md +57 -0
  67. package/ai/components/recipe-navigation-sidebar.md +57 -0
  68. package/ai/components/recipe-navigation-stack.md +57 -0
  69. package/ai/components/recipe-workspace-header.md +57 -0
  70. package/ai/components/resize.md +106 -0
  71. package/ai/components/row.md +96 -0
  72. package/ai/components/segmented-control.md +82 -0
  73. package/ai/components/select.md +116 -0
  74. package/ai/components/skeleton.md +84 -0
  75. package/ai/components/spacer.md +86 -0
  76. package/ai/components/split-button.md +80 -0
  77. package/ai/components/split-view.md +105 -0
  78. package/ai/components/state-banner.md +96 -0
  79. package/ai/components/summary-row.md +85 -0
  80. package/ai/components/sunken-panel.md +79 -0
  81. package/ai/components/surface-scaffold.md +105 -0
  82. package/ai/components/tab-bar.md +114 -0
  83. package/ai/components/tab-navigator.md +100 -0
  84. package/ai/components/tabs.md +95 -0
  85. package/ai/components/text.md +90 -0
  86. package/ai/components/token-search-field.md +107 -0
  87. package/ai/components/toolbar-control-group.md +125 -0
  88. package/ai/components/toolbar-text.md +100 -0
  89. package/ai/components/toolbar.md +103 -0
  90. package/ai/components/value-table.md +80 -0
  91. package/ai/components/wa-accordion-item.md +55 -0
  92. package/ai/components/wa-accordion.md +65 -0
  93. package/ai/components/wa-animated-image.md +55 -0
  94. package/ai/components/wa-animation.md +55 -0
  95. package/ai/components/wa-avatar.md +55 -0
  96. package/ai/components/wa-badge.md +55 -0
  97. package/ai/components/wa-breadcrumb-item.md +53 -0
  98. package/ai/components/wa-breadcrumb.md +55 -0
  99. package/ai/components/wa-button-group.md +60 -0
  100. package/ai/components/wa-button.md +59 -0
  101. package/ai/components/wa-callout.md +58 -0
  102. package/ai/components/wa-card.md +59 -0
  103. package/ai/components/wa-carousel-item.md +53 -0
  104. package/ai/components/wa-carousel.md +55 -0
  105. package/ai/components/wa-checkbox-group.md +55 -0
  106. package/ai/components/wa-checkbox.md +55 -0
  107. package/ai/components/wa-color-picker.md +55 -0
  108. package/ai/components/wa-comparison.md +55 -0
  109. package/ai/components/wa-copy-button.md +55 -0
  110. package/ai/components/wa-details.md +61 -0
  111. package/ai/components/wa-dialog.md +59 -0
  112. package/ai/components/wa-divider.md +53 -0
  113. package/ai/components/wa-drawer.md +55 -0
  114. package/ai/components/wa-dropdown-item.md +60 -0
  115. package/ai/components/wa-dropdown.md +60 -0
  116. package/ai/components/wa-format-bytes.md +53 -0
  117. package/ai/components/wa-format-date.md +53 -0
  118. package/ai/components/wa-format-number.md +53 -0
  119. package/ai/components/wa-icon.md +58 -0
  120. package/ai/components/wa-include.md +53 -0
  121. package/ai/components/wa-input.md +59 -0
  122. package/ai/components/wa-intersection-observer.md +53 -0
  123. package/ai/components/wa-known-date.md +53 -0
  124. package/ai/components/wa-markdown.md +53 -0
  125. package/ai/components/wa-mutation-observer.md +53 -0
  126. package/ai/components/wa-number-input.md +55 -0
  127. package/ai/components/wa-option.md +59 -0
  128. package/ai/components/wa-otp-input.md +53 -0
  129. package/ai/components/wa-page.md +53 -0
  130. package/ai/components/wa-pagination.md +55 -0
  131. package/ai/components/wa-popover.md +55 -0
  132. package/ai/components/wa-popup.md +53 -0
  133. package/ai/components/wa-progress-bar.md +53 -0
  134. package/ai/components/wa-progress-ring.md +53 -0
  135. package/ai/components/wa-qr-code.md +53 -0
  136. package/ai/components/wa-radio-group.md +59 -0
  137. package/ai/components/wa-radio.md +55 -0
  138. package/ai/components/wa-random-content.md +53 -0
  139. package/ai/components/wa-rating.md +55 -0
  140. package/ai/components/wa-relative-time.md +53 -0
  141. package/ai/components/wa-resize-observer.md +53 -0
  142. package/ai/components/wa-scroller.md +53 -0
  143. package/ai/components/wa-select.md +63 -0
  144. package/ai/components/wa-skeleton.md +53 -0
  145. package/ai/components/wa-slider.md +55 -0
  146. package/ai/components/wa-spinner.md +58 -0
  147. package/ai/components/wa-split-panel.md +58 -0
  148. package/ai/components/wa-switch.md +53 -0
  149. package/ai/components/wa-tab-group.md +69 -0
  150. package/ai/components/wa-tab-panel.md +60 -0
  151. package/ai/components/wa-tab.md +59 -0
  152. package/ai/components/wa-tag.md +59 -0
  153. package/ai/components/wa-textarea.md +53 -0
  154. package/ai/components/wa-time-input.md +55 -0
  155. package/ai/components/wa-toast-item.md +60 -0
  156. package/ai/components/wa-toast.md +60 -0
  157. package/ai/components/wa-tooltip.md +55 -0
  158. package/ai/components/wa-tree-item.md +55 -0
  159. package/ai/components/wa-tree.md +55 -0
  160. package/ai/components/wa-zoomable-frame.md +55 -0
  161. package/ai/components/workbench.md +120 -0
  162. package/ai/public-api-signatures-v1.md +3851 -441
  163. package/ai/skill.md +402 -70
  164. package/ai/webawesome-jsx-signatures-v1.md +9 -2
  165. package/analyzer/cli.mjs +5 -2
  166. package/analyzer/component-ownership.d.mts +72 -0
  167. package/analyzer/component-ownership.mjs +429 -0
  168. package/analyzer/index.d.mts +8 -0
  169. package/analyzer/index.mjs +1687 -49
  170. package/analyzer/loud-pairs.d.mts +49 -0
  171. package/analyzer/loud-pairs.mjs +357 -0
  172. package/analyzer/selectors.d.mts +21 -0
  173. package/analyzer/selectors.mjs +212 -0
  174. package/catalog-projection-cli.mjs +96 -0
  175. package/catalog-projection.d.mts +55 -0
  176. package/catalog-projection.mjs +62 -0
  177. package/dist/anchored-surface.d.ts +69 -0
  178. package/dist/anchored-surface.js +3 -0
  179. package/dist/anchored-surface.js.map +1 -0
  180. package/dist/app-tab.d.ts +39 -4
  181. package/dist/app-tab.js +2 -1
  182. package/dist/badge.d.ts +83 -0
  183. package/dist/badge.js +4 -0
  184. package/dist/badge.js.map +1 -0
  185. package/dist/browser/anchored-surface.js +3 -0
  186. package/dist/browser/badge.js +3 -0
  187. package/dist/browser/catalog.js +28 -0
  188. package/dist/browser/chip.js +3 -0
  189. package/dist/browser/choice-chips.js +3 -0
  190. package/dist/browser/collapsible-panel.js +9 -0
  191. package/dist/browser/column-track.js +3 -0
  192. package/dist/browser/combobox.js +3 -0
  193. package/dist/browser/composer.js +4 -0
  194. package/dist/browser/content-item.js +3 -0
  195. package/dist/browser/content-swap.js +3 -0
  196. package/dist/browser/corner-status.js +3 -0
  197. package/dist/browser/edge-activity-indicator.js +3 -0
  198. package/dist/browser/file-picker.js +3 -0
  199. package/dist/browser/framed-list.js +3 -0
  200. package/dist/browser/grid.js +3 -0
  201. package/dist/browser/icon-button.js +4 -0
  202. package/dist/browser/icon-toggle-button.js +4 -0
  203. package/dist/browser/list-action-row.js +2 -0
  204. package/dist/browser/list-header.js +3 -1
  205. package/dist/browser/list-item.js +1 -0
  206. package/dist/browser/nav-stack.js +11 -0
  207. package/dist/browser/popup-menu.js +3 -0
  208. package/dist/browser/row.js +3 -0
  209. package/dist/browser/segmented-control.js +0 -1
  210. package/dist/browser/select.js +2 -1
  211. package/dist/browser/spacer.js +3 -0
  212. package/dist/browser/split-button.js +5 -0
  213. package/dist/browser/split-view.js +13 -0
  214. package/dist/browser/state-banner.js +1 -0
  215. package/dist/browser/summary-row.js +3 -0
  216. package/dist/browser/surface-scaffold.js +3 -0
  217. package/dist/browser/tab-navigator.js +4 -0
  218. package/dist/browser/text.js +3 -0
  219. package/dist/browser/toolbar-control-group.js +1 -0
  220. package/dist/browser/workbench.js +14 -0
  221. package/dist/catalog-resources.d.ts +17 -1
  222. package/dist/catalog-resources.js +5 -5
  223. package/dist/catalog-resources.js.map +1 -1
  224. package/dist/catalog.d.ts +123 -121
  225. package/dist/catalog.js +598 -269
  226. package/dist/catalog.js.map +1 -1
  227. package/dist/chip.d.ts +69 -0
  228. package/dist/chip.js +3 -0
  229. package/dist/chip.js.map +1 -0
  230. package/dist/choice-chips.d.ts +82 -0
  231. package/dist/choice-chips.js +3 -0
  232. package/dist/choice-chips.js.map +1 -0
  233. package/dist/chunk-2GOCOLQL.js +270 -0
  234. package/dist/chunk-2GOCOLQL.js.map +1 -0
  235. package/dist/chunk-2MSPNRWJ.js +82 -0
  236. package/dist/chunk-2MSPNRWJ.js.map +1 -0
  237. package/dist/chunk-34U4ANDJ.js +119 -0
  238. package/dist/chunk-34U4ANDJ.js.map +1 -0
  239. package/dist/chunk-3GLYIIFR.js +230 -0
  240. package/dist/chunk-3GLYIIFR.js.map +1 -0
  241. package/dist/{chunk-Y6LYKFXY.js → chunk-3VXDSIBJ.js} +7 -4
  242. package/dist/chunk-3VXDSIBJ.js.map +1 -0
  243. package/dist/chunk-3WYREXW4.js +120 -0
  244. package/dist/chunk-3WYREXW4.js.map +1 -0
  245. package/dist/chunk-4UB5Z3ZZ.js +68 -0
  246. package/dist/chunk-4UB5Z3ZZ.js.map +1 -0
  247. package/dist/chunk-6C6DLP3W.js +31 -0
  248. package/dist/chunk-6C6DLP3W.js.map +1 -0
  249. package/dist/chunk-7IUEBNCO.js +108 -0
  250. package/dist/chunk-7IUEBNCO.js.map +1 -0
  251. package/dist/{chunk-QO55FN2Y.js → chunk-AN5IWMTM.js} +7 -3
  252. package/dist/chunk-AN5IWMTM.js.map +1 -0
  253. package/dist/chunk-AV7CPAPT.js +82 -0
  254. package/dist/chunk-AV7CPAPT.js.map +1 -0
  255. package/dist/{chunk-PX3YSS5C.js → chunk-B53PS2LE.js} +5 -3
  256. package/dist/chunk-B53PS2LE.js.map +1 -0
  257. package/dist/chunk-CA7LNX3J.js +44 -0
  258. package/dist/chunk-CA7LNX3J.js.map +1 -0
  259. package/dist/chunk-CEJ5FK6G.js +288 -0
  260. package/dist/chunk-CEJ5FK6G.js.map +1 -0
  261. package/dist/chunk-CMETHPKR.js +175 -0
  262. package/dist/chunk-CMETHPKR.js.map +1 -0
  263. package/dist/chunk-CMLPFIC5.js +46 -0
  264. package/dist/chunk-CMLPFIC5.js.map +1 -0
  265. package/dist/{chunk-RKVQEH4J.js → chunk-CVD5IZGB.js} +10 -5
  266. package/dist/chunk-CVD5IZGB.js.map +1 -0
  267. package/dist/chunk-CXZXHZTR.js +514 -0
  268. package/dist/chunk-CXZXHZTR.js.map +1 -0
  269. package/dist/chunk-D5RGM3XA.js +38 -0
  270. package/dist/chunk-D5RGM3XA.js.map +1 -0
  271. package/dist/{chunk-KWWR5VMS.js → chunk-DZJ4GBSP.js} +19 -5
  272. package/dist/chunk-DZJ4GBSP.js.map +1 -0
  273. package/dist/chunk-EIET4QLK.js +51 -0
  274. package/dist/chunk-EIET4QLK.js.map +1 -0
  275. package/dist/chunk-ELCMSQNJ.js +49 -0
  276. package/dist/chunk-ELCMSQNJ.js.map +1 -0
  277. package/dist/chunk-EOO6PYGF.js +162 -0
  278. package/dist/chunk-EOO6PYGF.js.map +1 -0
  279. package/dist/chunk-ETCWQLJV.js +85 -0
  280. package/dist/chunk-ETCWQLJV.js.map +1 -0
  281. package/dist/chunk-FDZNYD4C.js +138 -0
  282. package/dist/chunk-FDZNYD4C.js.map +1 -0
  283. package/dist/chunk-FR76ZJQC.js +90 -0
  284. package/dist/chunk-FR76ZJQC.js.map +1 -0
  285. package/dist/chunk-G3J7JDP6.js +29 -0
  286. package/dist/chunk-G3J7JDP6.js.map +1 -0
  287. package/dist/chunk-GS2QD3N7.js +14 -0
  288. package/dist/chunk-GS2QD3N7.js.map +1 -0
  289. package/dist/chunk-HGV7J4T6.js +86 -0
  290. package/dist/chunk-HGV7J4T6.js.map +1 -0
  291. package/dist/chunk-HLDRH23D.js +84 -0
  292. package/dist/chunk-HLDRH23D.js.map +1 -0
  293. package/dist/chunk-HQZYKJ56.js +302 -0
  294. package/dist/chunk-HQZYKJ56.js.map +1 -0
  295. package/dist/chunk-HZPHCSCS.js +95 -0
  296. package/dist/chunk-HZPHCSCS.js.map +1 -0
  297. package/dist/chunk-IKS72C6P.js +53 -0
  298. package/dist/chunk-IKS72C6P.js.map +1 -0
  299. package/dist/chunk-IM3GKYOG.js +202 -0
  300. package/dist/chunk-IM3GKYOG.js.map +1 -0
  301. package/dist/{chunk-JRKSK2HX.js → chunk-INI4WDWC.js} +53 -7
  302. package/dist/chunk-INI4WDWC.js.map +1 -0
  303. package/dist/chunk-IQSRHXZR.js +164 -0
  304. package/dist/chunk-IQSRHXZR.js.map +1 -0
  305. package/dist/{chunk-M7JBER5U.js → chunk-JOR5LGYE.js} +18 -4
  306. package/dist/chunk-JOR5LGYE.js.map +1 -0
  307. package/dist/chunk-K4AT3V3J.js +545 -0
  308. package/dist/chunk-K4AT3V3J.js.map +1 -0
  309. package/dist/chunk-KCS6NJ3O.js +82 -0
  310. package/dist/chunk-KCS6NJ3O.js.map +1 -0
  311. package/dist/{chunk-SRSJO5QE.js → chunk-KH4KEMSP.js} +34 -3
  312. package/dist/chunk-KH4KEMSP.js.map +1 -0
  313. package/dist/chunk-LL45C7BB.js +122 -0
  314. package/dist/chunk-LL45C7BB.js.map +1 -0
  315. package/dist/chunk-LOSRXZAH.js +100 -0
  316. package/dist/chunk-LOSRXZAH.js.map +1 -0
  317. package/dist/{chunk-7KEUJIIC.js → chunk-MF6ZUUJZ.js} +93 -11
  318. package/dist/chunk-MF6ZUUJZ.js.map +1 -0
  319. package/dist/chunk-MHMWQ7RB.js +19 -0
  320. package/dist/chunk-MHMWQ7RB.js.map +1 -0
  321. package/dist/chunk-MJ5RU6XT.js +173 -0
  322. package/dist/chunk-MJ5RU6XT.js.map +1 -0
  323. package/dist/chunk-MXNIVEVL.js +48 -0
  324. package/dist/chunk-MXNIVEVL.js.map +1 -0
  325. package/dist/{chunk-IJBSZ4NX.js → chunk-NS2TBYM4.js} +8 -5
  326. package/dist/chunk-NS2TBYM4.js.map +1 -0
  327. package/dist/chunk-PIVY32AE.js +140 -0
  328. package/dist/chunk-PIVY32AE.js.map +1 -0
  329. package/dist/chunk-PYBBYSZ3.js +526 -0
  330. package/dist/chunk-PYBBYSZ3.js.map +1 -0
  331. package/dist/chunk-QDSZTQXL.js +28 -0
  332. package/dist/chunk-QDSZTQXL.js.map +1 -0
  333. package/dist/chunk-QH5KOFNS.js +39 -0
  334. package/dist/chunk-QH5KOFNS.js.map +1 -0
  335. package/dist/chunk-QIG4IGUP.js +139 -0
  336. package/dist/chunk-QIG4IGUP.js.map +1 -0
  337. package/dist/chunk-QLA25V2K.js +493 -0
  338. package/dist/chunk-QLA25V2K.js.map +1 -0
  339. package/dist/chunk-QROYYGWU.js +52 -0
  340. package/dist/chunk-QROYYGWU.js.map +1 -0
  341. package/dist/chunk-QYMJHS4K.js +36 -0
  342. package/dist/chunk-QYMJHS4K.js.map +1 -0
  343. package/dist/chunk-RHD7RKGI.js +176 -0
  344. package/dist/chunk-RHD7RKGI.js.map +1 -0
  345. package/dist/chunk-S2XULQEK.js +52 -0
  346. package/dist/chunk-S2XULQEK.js.map +1 -0
  347. package/dist/{chunk-LPOMRC4C.js → chunk-S3ECMF23.js} +11 -3
  348. package/dist/chunk-S3ECMF23.js.map +1 -0
  349. package/dist/chunk-S4Y3YTAI.js +35 -0
  350. package/dist/chunk-S4Y3YTAI.js.map +1 -0
  351. package/dist/chunk-S4ZGTDSG.js +18 -0
  352. package/dist/chunk-S4ZGTDSG.js.map +1 -0
  353. package/dist/{chunk-PSKN344N.js → chunk-T4WJ5VGP.js} +63 -12
  354. package/dist/chunk-T4WJ5VGP.js.map +1 -0
  355. package/dist/chunk-TDMHA7C3.js +251 -0
  356. package/dist/chunk-TDMHA7C3.js.map +1 -0
  357. package/dist/{chunk-Y45PPBDV.js → chunk-TGLUDQP2.js} +47 -14
  358. package/dist/chunk-TGLUDQP2.js.map +1 -0
  359. package/dist/chunk-TLMMFQWM.js +222 -0
  360. package/dist/chunk-TLMMFQWM.js.map +1 -0
  361. package/dist/chunk-U6QYTMDB.js +85 -0
  362. package/dist/chunk-U6QYTMDB.js.map +1 -0
  363. package/dist/chunk-UMKV7YVQ.js +20 -0
  364. package/dist/chunk-UMKV7YVQ.js.map +1 -0
  365. package/dist/chunk-VFYO3UGZ.js +45 -0
  366. package/dist/chunk-VFYO3UGZ.js.map +1 -0
  367. package/dist/chunk-W4IK7AWD.js +73 -0
  368. package/dist/chunk-W4IK7AWD.js.map +1 -0
  369. package/dist/chunk-WVS6CUCT.js +48 -0
  370. package/dist/chunk-WVS6CUCT.js.map +1 -0
  371. package/dist/chunk-Y6KT7ZTR.js +109 -0
  372. package/dist/chunk-Y6KT7ZTR.js.map +1 -0
  373. package/dist/chunk-YMUIQRWK.js +44 -0
  374. package/dist/chunk-YMUIQRWK.js.map +1 -0
  375. package/dist/chunk-YOA5W5ZH.js +807 -0
  376. package/dist/chunk-YOA5W5ZH.js.map +1 -0
  377. package/dist/chunk-YQ2AC4E2.js +59 -0
  378. package/dist/chunk-YQ2AC4E2.js.map +1 -0
  379. package/dist/chunk-ZKKO4MHC.js +25 -0
  380. package/dist/chunk-ZKKO4MHC.js.map +1 -0
  381. package/dist/chunk-ZNROKDSZ.js +54 -0
  382. package/dist/chunk-ZNROKDSZ.js.map +1 -0
  383. package/dist/collapsible-panel.d.ts +96 -17
  384. package/dist/collapsible-panel.js +104 -27
  385. package/dist/collapsible-panel.js.map +1 -1
  386. package/dist/column-track.d.ts +41 -0
  387. package/dist/column-track.js +4 -0
  388. package/dist/column-track.js.map +1 -0
  389. package/dist/combobox.d.ts +40 -0
  390. package/dist/combobox.js +3 -0
  391. package/dist/combobox.js.map +1 -0
  392. package/dist/composer.d.ts +62 -0
  393. package/dist/composer.js +5 -0
  394. package/dist/composer.js.map +1 -0
  395. package/dist/content-item.d.ts +116 -0
  396. package/dist/content-item.js +4 -0
  397. package/dist/content-item.js.map +1 -0
  398. package/dist/content-swap.d.ts +34 -0
  399. package/dist/content-swap.js +3 -0
  400. package/dist/content-swap.js.map +1 -0
  401. package/dist/corner-status.d.ts +36 -0
  402. package/dist/corner-status.js +3 -0
  403. package/dist/corner-status.js.map +1 -0
  404. package/dist/css-values.d.ts +127 -0
  405. package/dist/css-values.js +3 -0
  406. package/dist/css-values.js.map +1 -0
  407. package/dist/device-class.js +1 -1
  408. package/dist/device-class.js.map +1 -1
  409. package/dist/disclosure-arrow.d.ts +6 -1
  410. package/dist/disclosure-arrow.js +2 -1
  411. package/dist/edge-activity-indicator.d.ts +28 -0
  412. package/dist/edge-activity-indicator.js +3 -0
  413. package/dist/edge-activity-indicator.js.map +1 -0
  414. package/dist/empty-state.d.ts +5 -2
  415. package/dist/empty-state.js +2 -1
  416. package/dist/extension-attributes-mu8MFUl0.d.ts +13 -0
  417. package/dist/file-picker.d.ts +47 -0
  418. package/dist/file-picker.js +3 -0
  419. package/dist/file-picker.js.map +1 -0
  420. package/dist/flex-alignment-4ms8ZbV8.d.ts +5 -0
  421. package/dist/floating-toolbar.d.ts +29 -6
  422. package/dist/floating-toolbar.js +1 -1
  423. package/dist/framed-list.d.ts +47 -0
  424. package/dist/framed-list.js +4 -0
  425. package/dist/framed-list.js.map +1 -0
  426. package/dist/grid.d.ts +58 -0
  427. package/dist/grid.js +6 -0
  428. package/dist/grid.js.map +1 -0
  429. package/dist/help-tags-register.d.ts +13 -0
  430. package/dist/help-tags-register.js +9 -0
  431. package/dist/help-tags-register.js.map +1 -0
  432. package/dist/icon-button.d.ts +46 -0
  433. package/dist/icon-button.js +6 -0
  434. package/dist/icon-button.js.map +1 -0
  435. package/dist/icon-toggle-button.d.ts +42 -0
  436. package/dist/icon-toggle-button.js +6 -0
  437. package/dist/icon-toggle-button.js.map +1 -0
  438. package/dist/index.d.ts +59 -20
  439. package/dist/index.js +66 -29
  440. package/dist/list-action-row.d.ts +69 -12
  441. package/dist/list-action-row.js +5 -1
  442. package/dist/list-header.d.ts +36 -2
  443. package/dist/list-header.js +4 -1
  444. package/dist/list-icon-tile-B8dr97OW.d.ts +18 -0
  445. package/dist/list-inset-control.d.ts +10 -4
  446. package/dist/list-inset-control.js +1 -1
  447. package/dist/list-inset-text.d.ts +17 -12
  448. package/dist/list-inset-text.js +2 -1
  449. package/dist/list-item.d.ts +49 -5
  450. package/dist/list-item.js +4 -1
  451. package/dist/list.d.ts +121 -12
  452. package/dist/list.js +5 -1
  453. package/dist/loading-spinner.d.ts +12 -2
  454. package/dist/loading-spinner.js +2 -1
  455. package/dist/lucide-icon.d.ts +15 -3
  456. package/dist/lucide-icon.js +2 -1
  457. package/dist/nav-stack-VXTumzKa.d.ts +158 -0
  458. package/dist/nav-stack.d.ts +15 -38
  459. package/dist/nav-stack.js +13 -1
  460. package/dist/pane.d.ts +111 -7
  461. package/dist/pane.js +2 -1
  462. package/dist/panel-toolbar-vg6Ir5Dy.d.ts +77 -0
  463. package/dist/popup-menu-register.d.ts +10 -0
  464. package/dist/popup-menu-register.js +336 -0
  465. package/dist/popup-menu-register.js.map +1 -0
  466. package/dist/popup-menu.d.ts +125 -0
  467. package/dist/popup-menu.js +3 -0
  468. package/dist/popup-menu.js.map +1 -0
  469. package/dist/resizable-region.d.ts +32 -3
  470. package/dist/resizable-region.js +1 -1
  471. package/dist/row.d.ts +53 -0
  472. package/dist/row.js +7 -0
  473. package/dist/row.js.map +1 -0
  474. package/dist/scroll-clearance-BikHljTc.d.ts +14 -0
  475. package/dist/segmented-control.d.ts +7 -4
  476. package/dist/segmented-control.js +1 -2
  477. package/dist/select-register.js +255 -3
  478. package/dist/select-register.js.map +1 -1
  479. package/dist/select.d.ts +95 -6
  480. package/dist/select.js +4 -1
  481. package/dist/semantic-content-BbzjvSu9.d.ts +14 -0
  482. package/dist/semantic-tone-CdW013KF.d.ts +4 -0
  483. package/dist/sides-BPSWde0A.d.ts +4 -0
  484. package/dist/skeleton.d.ts +14 -7
  485. package/dist/skeleton.js +2 -1
  486. package/dist/spacer.d.ts +18 -0
  487. package/dist/spacer.js +4 -0
  488. package/dist/spacer.js.map +1 -0
  489. package/dist/split-button.d.ts +57 -0
  490. package/dist/split-button.js +7 -0
  491. package/dist/split-button.js.map +1 -0
  492. package/dist/split-view.d.ts +63 -6
  493. package/dist/split-view.js +88 -9
  494. package/dist/split-view.js.map +1 -1
  495. package/dist/state-banner.d.ts +37 -6
  496. package/dist/state-banner.js +4 -1
  497. package/dist/styles/anchored-surface.css +41 -0
  498. package/dist/styles/app-tab.css +232 -0
  499. package/dist/styles/badge.css +145 -0
  500. package/dist/styles/catalog/components/catalog-example-stack.css +14 -0
  501. package/dist/styles/catalog/components/catalog-example.css +204 -0
  502. package/dist/styles/catalog/components/catalog-stage.css +61 -0
  503. package/dist/styles/catalog/components/catalog.css +26 -0
  504. package/dist/styles/catalog.css +5 -575
  505. package/dist/styles/chip.css +208 -0
  506. package/dist/styles/choice-chips.css +154 -0
  507. package/dist/styles/collapsible-panel.css +450 -16
  508. package/dist/styles/column-track.css +57 -0
  509. package/dist/styles/combobox.css +113 -0
  510. package/dist/styles/components/actions/floating-toolbar/floating-toolbar.css +119 -0
  511. package/dist/styles/components/actions/icon-button/icon-button.css +22 -0
  512. package/dist/styles/components/actions/icon-toggle-button/icon-toggle-button.css +83 -0
  513. package/dist/styles/components/actions/popup-menu/popup-menu.css +370 -0
  514. package/dist/styles/components/actions/split-button/split-button.css +95 -0
  515. package/dist/styles/components/actions/toolbar/toolbar.css +443 -0
  516. package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +1069 -0
  517. package/dist/styles/components/actions/toolbar-text/toolbar-text.css +269 -0
  518. package/dist/styles/components/collections/framed-list/framed-list.css +47 -0
  519. package/dist/styles/components/collections/list/list.css +250 -0
  520. package/dist/styles/components/collections/list-action-row/list-action-row.css +466 -0
  521. package/dist/styles/components/collections/list-header/list-header.css +229 -0
  522. package/dist/styles/components/collections/list-inset-control/list-inset-control.css +30 -0
  523. package/dist/styles/components/collections/list-inset-text/list-inset-text.css +29 -0
  524. package/dist/styles/components/collections/list-item/list-item.css +461 -0
  525. package/dist/styles/components/collections/summary-row/summary-row.css +161 -0
  526. package/dist/styles/components/data-display/value-table/value-table.css +183 -0
  527. package/dist/styles/components/feedback/anchored-surface/anchored-surface.css +41 -0
  528. package/dist/styles/components/feedback/badge/badge.css +145 -0
  529. package/dist/styles/components/feedback/chip/chip.css +208 -0
  530. package/dist/styles/components/feedback/corner-status/corner-status.css +60 -0
  531. package/dist/styles/components/feedback/edge-activity-indicator/edge-activity-indicator.css +127 -0
  532. package/dist/styles/components/feedback/empty-state/empty-state.css +47 -0
  533. package/dist/styles/components/feedback/loading-spinner/loading-spinner.css +25 -0
  534. package/dist/styles/components/feedback/skeleton/skeleton.css +92 -0
  535. package/dist/styles/components/feedback/state-banner/state-banner.css +297 -0
  536. package/dist/styles/components/forms/choice-chips/choice-chips.css +154 -0
  537. package/dist/styles/components/forms/combobox/combobox.css +113 -0
  538. package/dist/styles/components/forms/composer/composer.css +100 -0
  539. package/dist/styles/components/forms/file-picker/file-picker.css +81 -0
  540. package/dist/styles/components/forms/segmented-control/segmented-control.css +196 -0
  541. package/dist/styles/components/forms/select/select.css +455 -0
  542. package/dist/styles/components/forms/token-search-field/token-search-field.css +432 -0
  543. package/dist/styles/components/layout/collapsible-panel/collapsible-panel.css +580 -0
  544. package/dist/styles/components/layout/column-track/column-track.css +57 -0
  545. package/dist/styles/components/layout/content-swap/content-swap.css +154 -0
  546. package/dist/styles/components/layout/grid/grid.css +88 -0
  547. package/dist/styles/components/layout/pane/pane.css +382 -0
  548. package/dist/styles/components/layout/resizable-region/resizable-region.css +545 -0
  549. package/dist/styles/components/layout/row/row.css +114 -0
  550. package/dist/styles/components/layout/spacer/spacer.css +11 -0
  551. package/dist/styles/components/layout/split-view/split-view.css +146 -0
  552. package/dist/styles/components/layout/workbench/workbench.css +1086 -0
  553. package/dist/styles/components/media/lucide-icon/lucide-icon.css +15 -0
  554. package/dist/styles/components/navigation/app-tab/app-tab.css +384 -0
  555. package/dist/styles/components/navigation/disclosure-arrow/disclosure-arrow.css +23 -0
  556. package/dist/styles/components/navigation/nav-stack/nav-stack.css +342 -0
  557. package/dist/styles/components/navigation/tab-bar/tab-bar.css +197 -0
  558. package/dist/styles/components/navigation/tab-navigator/tab-navigator.css +325 -0
  559. package/dist/styles/components/surfaces/content-item/content-item.css +138 -0
  560. package/dist/styles/components/surfaces/sunken-panel/sunken-panel.css +31 -0
  561. package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +425 -0
  562. package/dist/styles/components/typography/text/text.css +220 -0
  563. package/dist/styles/composer.css +100 -0
  564. package/dist/styles/content-item.css +138 -0
  565. package/dist/styles/content-swap.css +154 -0
  566. package/dist/styles/corner-status.css +60 -0
  567. package/dist/styles/document.css +98 -0
  568. package/dist/styles/edge-activity-indicator.css +127 -0
  569. package/dist/styles/file-picker.css +81 -0
  570. package/dist/styles/floating-toolbar.css +87 -6
  571. package/dist/styles/foundation.css +254 -53
  572. package/dist/styles/framed-list.css +47 -0
  573. package/dist/styles/grid.css +88 -0
  574. package/dist/styles/icon-button.css +22 -0
  575. package/dist/styles/icon-toggle-button.css +83 -0
  576. package/dist/styles/layout.css +1 -21
  577. package/dist/styles/list-action-row.css +323 -17
  578. package/dist/styles/list-header.css +128 -59
  579. package/dist/styles/list-inset-control.css +25 -2
  580. package/dist/styles/list-inset-text.css +27 -12
  581. package/dist/styles/list-item.css +371 -33
  582. package/dist/styles/list.css +214 -7
  583. package/dist/styles/loading-spinner.css +4 -0
  584. package/dist/styles/lucide-icon.css +9 -0
  585. package/dist/styles/nav-stack.css +280 -49
  586. package/dist/styles/pane.css +322 -2
  587. package/dist/styles/popup-menu.css +370 -0
  588. package/dist/styles/resizable-region.css +438 -4
  589. package/dist/styles/row.css +114 -0
  590. package/dist/styles/segmented-control.css +34 -3
  591. package/dist/styles/select.css +338 -23
  592. package/dist/styles/skeleton.css +64 -3
  593. package/dist/styles/spacer.css +11 -0
  594. package/dist/styles/split-button.css +95 -0
  595. package/dist/styles/split-view.css +114 -3
  596. package/dist/styles/state-banner.css +141 -42
  597. package/dist/styles/styles.css +22 -1
  598. package/dist/styles/summary-row.css +161 -0
  599. package/dist/styles/sunken-panel.css +13 -4
  600. package/dist/styles/surface-scaffold.css +425 -0
  601. package/dist/styles/tab-bar.css +161 -9
  602. package/dist/styles/tab-navigator.css +325 -0
  603. package/dist/styles/text.css +220 -0
  604. package/dist/styles/token-search-field.css +202 -59
  605. package/dist/styles/toolbar-control-group.css +886 -42
  606. package/dist/styles/toolbar-text.css +184 -7
  607. package/dist/styles/toolbar.css +363 -17
  608. package/dist/styles/value-table.css +115 -7
  609. package/dist/styles/webawesome.css +282 -20
  610. package/dist/styles/workbench.css +999 -7
  611. package/dist/summary-row.d.ts +56 -0
  612. package/dist/summary-row.js +4 -0
  613. package/dist/summary-row.js.map +1 -0
  614. package/dist/sunken-panel.d.ts +23 -4
  615. package/dist/sunken-panel.js +2 -1
  616. package/dist/surface-scaffold-register.d.ts +9 -0
  617. package/dist/surface-scaffold-register.js +151 -0
  618. package/dist/surface-scaffold-register.js.map +1 -0
  619. package/dist/surface-scaffold.d.ts +170 -0
  620. package/dist/surface-scaffold.js +4 -0
  621. package/dist/surface-scaffold.js.map +1 -0
  622. package/dist/tab-bar.d.ts +31 -6
  623. package/dist/tab-bar.js +1 -1
  624. package/dist/tab-navigator.d.ts +114 -0
  625. package/dist/tab-navigator.js +102 -0
  626. package/dist/tab-navigator.js.map +1 -0
  627. package/dist/text.d.ts +98 -0
  628. package/dist/text.js +3 -0
  629. package/dist/text.js.map +1 -0
  630. package/dist/token-search-field.d.ts +3 -59
  631. package/dist/token-search-field.js +2 -1
  632. package/dist/token-search-model-aYR0YAei.d.ts +184 -0
  633. package/dist/token-search-model.d.ts +3 -0
  634. package/dist/token-search-model.js +3 -0
  635. package/dist/token-search-model.js.map +1 -0
  636. package/dist/toolbar-control-group.d.ts +87 -4
  637. package/dist/toolbar-control-group.js +3 -1
  638. package/dist/toolbar-text.d.ts +46 -12
  639. package/dist/toolbar-text.js +2 -1
  640. package/dist/toolbar.d.ts +71 -9
  641. package/dist/toolbar.js +1 -1
  642. package/dist/types-CVgIwSdq.d.ts +189 -0
  643. package/dist/value-table.d.ts +30 -4
  644. package/dist/value-table.js +2 -1
  645. package/dist/webawesome.d.ts +8 -1
  646. package/dist/wire-catalog.d.ts +88 -13
  647. package/dist/wire-catalog.js +240 -206
  648. package/dist/wire-catalog.js.map +1 -1
  649. package/dist/wire-comboboxes.d.ts +26 -0
  650. package/dist/wire-comboboxes.js +3 -0
  651. package/dist/wire-comboboxes.js.map +1 -0
  652. package/dist/wire-composers.d.ts +16 -0
  653. package/dist/wire-composers.js +3 -0
  654. package/dist/wire-composers.js.map +1 -0
  655. package/dist/wire-content-items.d.ts +11 -0
  656. package/dist/wire-content-items.js +139 -0
  657. package/dist/wire-content-items.js.map +1 -0
  658. package/dist/wire-corner-status.d.ts +11 -0
  659. package/dist/wire-corner-status.js +3 -0
  660. package/dist/wire-corner-status.js.map +1 -0
  661. package/dist/wire-file-pickers.d.ts +26 -0
  662. package/dist/wire-file-pickers.js +3 -0
  663. package/dist/wire-file-pickers.js.map +1 -0
  664. package/dist/wire-list-focus-scroll.d.ts +16 -0
  665. package/dist/wire-list-focus-scroll.js +3 -0
  666. package/dist/wire-list-focus-scroll.js.map +1 -0
  667. package/dist/wire-list-reorder.d.ts +41 -0
  668. package/dist/wire-list-reorder.js +3 -0
  669. package/dist/wire-list-reorder.js.map +1 -0
  670. package/dist/wire-nav-stack.d.ts +4 -1
  671. package/dist/wire-nav-stack.js +312 -16
  672. package/dist/wire-nav-stack.js.map +1 -1
  673. package/dist/wire-resizable-regions.d.ts +2 -1
  674. package/dist/wire-resizable-regions.js +2 -1
  675. package/dist/wire-scroll-dividers.d.ts +87 -0
  676. package/dist/wire-scroll-dividers.js +3 -0
  677. package/dist/wire-scroll-dividers.js.map +1 -0
  678. package/dist/wire-sidebar.d.ts +24 -4
  679. package/dist/wire-sidebar.js +122 -19
  680. package/dist/wire-sidebar.js.map +1 -1
  681. package/dist/wire-tab-bars.d.ts +1 -0
  682. package/dist/wire-tab-bars.js +1 -1
  683. package/dist/wire-tab-navigator.d.ts +13 -0
  684. package/dist/wire-tab-navigator.js +153 -0
  685. package/dist/wire-tab-navigator.js.map +1 -0
  686. package/dist/wire-token-search-fields.d.ts +5 -1
  687. package/dist/wire-token-search-fields.js +2 -1
  688. package/dist/wire-toolbar-visibility.d.ts +13 -0
  689. package/dist/wire-toolbar-visibility.js +3 -0
  690. package/dist/wire-toolbar-visibility.js.map +1 -0
  691. package/dist/wire-workbench.d.ts +116 -0
  692. package/dist/wire-workbench.js +6 -0
  693. package/dist/wire-workbench.js.map +1 -0
  694. package/dist/workbench.d.ts +253 -10
  695. package/dist/workbench.js +20 -62
  696. package/dist/workbench.js.map +1 -1
  697. package/docs/accessibility.md +617 -32
  698. package/docs/anchored-surface.md +101 -0
  699. package/docs/app-layouts.md +159 -10
  700. package/docs/apple-hig.md +2 -0
  701. package/docs/catalog.md +403 -110
  702. package/docs/collapsible-panel.md +272 -16
  703. package/docs/component-contract.md +700 -79
  704. package/docs/component-integration.md +75 -0
  705. package/docs/component-selection.md +908 -79
  706. package/docs/composer.md +143 -0
  707. package/docs/design/templates/choice-chips/color-palette-dark.svg +1 -0
  708. package/docs/design/templates/choice-chips/color-palette.svg +1 -0
  709. package/docs/design/templates/choice-chips/compact-icons-dark.svg +1 -0
  710. package/docs/design/templates/choice-chips/compact-icons.svg +1 -0
  711. package/docs/design/templates/choice-chips/multiple-dark.svg +1 -0
  712. package/docs/design/templates/choice-chips/multiple.svg +1 -0
  713. package/docs/design/templates/choice-chips-dark.svg +1 -0
  714. package/docs/design/templates/choice-chips.svg +1 -0
  715. package/docs/design/templates/composer/chat-dark.svg +1 -0
  716. package/docs/design/templates/composer/chat.svg +1 -0
  717. package/docs/design/templates/composer/prompt-dark.svg +1 -0
  718. package/docs/design/templates/composer/prompt.svg +1 -0
  719. package/docs/design/templates/composer-dark.svg +1 -0
  720. package/docs/design/templates/composer.svg +1 -0
  721. package/docs/design/templates/empty-state/filtered-dark.svg +1 -1
  722. package/docs/design/templates/empty-state/filtered.svg +1 -1
  723. package/docs/design/templates/empty-state/with-action-dark.svg +1 -1
  724. package/docs/design/templates/empty-state/with-action.svg +1 -1
  725. package/docs/design/templates/empty-state-dark.svg +1 -1
  726. package/docs/design/templates/empty-state.svg +1 -1
  727. package/docs/design/templates/list/compact-dark.svg +1 -1
  728. package/docs/design/templates/list/compact.svg +1 -1
  729. package/docs/design/templates/list/stack-dark.svg +1 -1
  730. package/docs/design/templates/list/stack.svg +1 -1
  731. package/docs/design/templates/list-action-row/default-dark.svg +1 -1
  732. package/docs/design/templates/list-action-row/default.svg +1 -1
  733. package/docs/design/templates/list-action-row/icon-tile-dark.svg +1 -0
  734. package/docs/design/templates/list-action-row/icon-tile.svg +1 -0
  735. package/docs/design/templates/list-action-row/selected-dark.svg +1 -1
  736. package/docs/design/templates/list-action-row/selected.svg +1 -1
  737. package/docs/design/templates/list-action-row/trailing-menu-dark.svg +1 -0
  738. package/docs/design/templates/list-action-row/trailing-menu.svg +1 -0
  739. package/docs/design/templates/list-action-row-dark.svg +1 -1
  740. package/docs/design/templates/list-action-row.svg +1 -1
  741. package/docs/design/templates/list-dark.svg +1 -1
  742. package/docs/design/templates/list-header/action-dark.svg +1 -1
  743. package/docs/design/templates/list-header/action.svg +1 -1
  744. package/docs/design/templates/list-header/count-dark.svg +1 -1
  745. package/docs/design/templates/list-header/count.svg +1 -1
  746. package/docs/design/templates/list-header/inline-action-dark.svg +1 -0
  747. package/docs/design/templates/list-header/inline-action.svg +1 -0
  748. package/docs/design/templates/list-header/size-l-dark.svg +1 -0
  749. package/docs/design/templates/list-header/size-l.svg +1 -0
  750. package/docs/design/templates/list-header/size-m-dark.svg +1 -0
  751. package/docs/design/templates/list-header/size-m.svg +1 -0
  752. package/docs/design/templates/list-header/size-xl-dark.svg +1 -0
  753. package/docs/design/templates/list-header/size-xl.svg +1 -0
  754. package/docs/design/templates/list-header/size-xs-dark.svg +1 -0
  755. package/docs/design/templates/list-header/size-xs.svg +1 -0
  756. package/docs/design/templates/list-header/toggle-collapsed-dark.svg +1 -1
  757. package/docs/design/templates/list-header/toggle-collapsed.svg +1 -1
  758. package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -1
  759. package/docs/design/templates/list-header/toggle-expanded.svg +1 -1
  760. package/docs/design/templates/list-header-dark.svg +1 -1
  761. package/docs/design/templates/list-header.svg +1 -1
  762. package/docs/design/templates/list-item/default-dark.svg +1 -1
  763. package/docs/design/templates/list-item/default.svg +1 -1
  764. package/docs/design/templates/list-item/icon-tile-dark.svg +1 -0
  765. package/docs/design/templates/list-item/icon-tile.svg +1 -0
  766. package/docs/design/templates/list-item/multiline-dark.svg +1 -1
  767. package/docs/design/templates/list-item/multiline.svg +1 -1
  768. package/docs/design/templates/list-item/selected-dark.svg +1 -1
  769. package/docs/design/templates/list-item/selected.svg +1 -1
  770. package/docs/design/templates/list-item/trailing-dark.svg +1 -1
  771. package/docs/design/templates/list-item/trailing.svg +1 -1
  772. package/docs/design/templates/list-item-dark.svg +1 -1
  773. package/docs/design/templates/list-item.svg +1 -1
  774. package/docs/design/templates/list.svg +1 -1
  775. package/docs/design/templates/segmented-control/equal-dark.svg +1 -1
  776. package/docs/design/templates/segmented-control/equal.svg +1 -1
  777. package/docs/design/templates/segmented-control/pill-small-dark.svg +1 -1
  778. package/docs/design/templates/segmented-control/pill-small.svg +1 -1
  779. package/docs/design/templates/segmented-control-dark.svg +1 -1
  780. package/docs/design/templates/segmented-control.svg +1 -1
  781. package/docs/design/templates/skeleton-dark.svg +1 -1
  782. package/docs/design/templates/skeleton.svg +1 -1
  783. package/docs/design/templates/state-banner/danger-dark.svg +1 -1
  784. package/docs/design/templates/state-banner/danger.svg +1 -1
  785. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -1
  786. package/docs/design/templates/state-banner/info-badge.svg +1 -1
  787. package/docs/design/templates/state-banner/info-dark.svg +1 -1
  788. package/docs/design/templates/state-banner/info.svg +1 -1
  789. package/docs/design/templates/state-banner/strip-dark.svg +1 -0
  790. package/docs/design/templates/state-banner/strip.svg +1 -0
  791. package/docs/design/templates/state-banner/success-dark.svg +1 -1
  792. package/docs/design/templates/state-banner/success.svg +1 -1
  793. package/docs/design/templates/state-banner/warning-dark.svg +1 -1
  794. package/docs/design/templates/state-banner/warning.svg +1 -1
  795. package/docs/design/templates/state-banner-dark.svg +1 -1
  796. package/docs/design/templates/state-banner.svg +1 -1
  797. package/docs/design/templates/tab-bar/workspace-tabs-dark.svg +1 -1
  798. package/docs/design/templates/tab-bar/workspace-tabs.svg +1 -1
  799. package/docs/design/templates/tab-bar-dark.svg +1 -1
  800. package/docs/design/templates/tab-bar.svg +1 -1
  801. package/docs/design/templates/token-search-field/empty-dark.svg +1 -1
  802. package/docs/design/templates/token-search-field/empty.svg +1 -1
  803. package/docs/design/templates/token-search-field/tokens-dark.svg +1 -1
  804. package/docs/design/templates/token-search-field/tokens.svg +1 -1
  805. package/docs/design/templates/token-search-field-dark.svg +1 -1
  806. package/docs/design/templates/token-search-field.svg +1 -1
  807. package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -1
  808. package/docs/design/templates/toolbar/title-and-actions.svg +1 -1
  809. package/docs/design/templates/toolbar/width-visibility-320-dark.svg +1 -0
  810. package/docs/design/templates/toolbar/width-visibility-320.svg +1 -0
  811. package/docs/design/templates/toolbar/width-visibility-600-dark.svg +1 -0
  812. package/docs/design/templates/toolbar/width-visibility-600.svg +1 -0
  813. package/docs/design/templates/toolbar-control-group/borderless-single-dark.svg +1 -1
  814. package/docs/design/templates/toolbar-control-group/borderless-single.svg +1 -1
  815. package/docs/design/templates/toolbar-control-group/icon-buttons-dark.svg +1 -1
  816. package/docs/design/templates/toolbar-control-group/icon-buttons.svg +1 -1
  817. package/docs/design/templates/toolbar-control-group/icon-tile-brand-dark.svg +1 -0
  818. package/docs/design/templates/toolbar-control-group/icon-tile-brand.svg +1 -0
  819. package/docs/design/templates/toolbar-control-group/icon-tile-danger-dark.svg +1 -0
  820. package/docs/design/templates/toolbar-control-group/icon-tile-danger.svg +1 -0
  821. package/docs/design/templates/toolbar-control-group/icon-tile-neutral-dark.svg +1 -0
  822. package/docs/design/templates/toolbar-control-group/icon-tile-neutral.svg +1 -0
  823. package/docs/design/templates/toolbar-control-group/icon-tile-success-dark.svg +1 -0
  824. package/docs/design/templates/toolbar-control-group/icon-tile-success.svg +1 -0
  825. package/docs/design/templates/toolbar-control-group/icon-tile-warning-dark.svg +1 -0
  826. package/docs/design/templates/toolbar-control-group/icon-tile-warning.svg +1 -0
  827. package/docs/design/templates/toolbar-control-group/push-buttons-dark.svg +1 -1
  828. package/docs/design/templates/toolbar-control-group/push-buttons.svg +1 -1
  829. package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -1
  830. package/docs/design/templates/toolbar-control-group/with-text.svg +1 -1
  831. package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
  832. package/docs/design/templates/toolbar-control-group.svg +1 -1
  833. package/docs/design/templates/toolbar-dark.svg +1 -1
  834. package/docs/design/templates/toolbar-text/dark-tone-dark.svg +1 -0
  835. package/docs/design/templates/toolbar-text/dark-tone.svg +1 -0
  836. package/docs/design/templates/toolbar-text/default-dark.svg +1 -1
  837. package/docs/design/templates/toolbar-text/default.svg +1 -1
  838. package/docs/design/templates/toolbar-text/large-dark.svg +1 -1
  839. package/docs/design/templates/toolbar-text/large.svg +1 -1
  840. package/docs/design/templates/toolbar-text/small-dark.svg +1 -1
  841. package/docs/design/templates/toolbar-text/small.svg +1 -1
  842. package/docs/design/templates/toolbar-text-dark.svg +1 -1
  843. package/docs/design/templates/toolbar-text.svg +1 -1
  844. package/docs/design/templates/toolbar.svg +1 -1
  845. package/docs/design/templates/value-table/details-dark.svg +1 -1
  846. package/docs/design/templates/value-table/details.svg +1 -1
  847. package/docs/design/templates/value-table-dark.svg +1 -1
  848. package/docs/design/templates/value-table.svg +1 -1
  849. package/docs/design/templates.md +19 -9
  850. package/docs/design-philosophy.md +58 -11
  851. package/docs/device-class.md +2 -1
  852. package/docs/document-baseline.md +93 -0
  853. package/docs/examples/application-ui-profile.json +8 -8
  854. package/docs/examples/command-palette-adapter.tsx +9 -7
  855. package/docs/examples/component-catalog-extension.json +8 -1
  856. package/docs/examples/{component-catalog-extension-v2.json → component-composition-extension.json} +8 -9
  857. package/docs/layout.md +764 -40
  858. package/docs/nav-stack.md +275 -11
  859. package/docs/recipes.md +125 -69
  860. package/docs/split-view.md +94 -5
  861. package/docs/sunken-panel.md +54 -1
  862. package/docs/surface-scaffold.md +270 -0
  863. package/docs/tab-navigator.md +255 -0
  864. package/docs/type-contracts.md +57 -22
  865. package/docs/ui-analyzer.md +334 -14
  866. package/docs/ui-doctor.md +207 -10
  867. package/docs/ui-evaluator.md +17 -0
  868. package/docs/ux-demo.md +47 -16
  869. package/docs/webawesome-theme.md +148 -14
  870. package/docs/workbench.md +600 -11
  871. package/docs/writing.md +152 -0
  872. package/doctor/cli.mjs +5 -0
  873. package/doctor/component-style-checks.mjs +457 -0
  874. package/doctor/config.schema.json +26 -0
  875. package/doctor/copy-capitalization-checks.mjs +591 -0
  876. package/doctor/eslint-diagnostics.d.mts +16 -0
  877. package/doctor/eslint-diagnostics.mjs +17 -0
  878. package/doctor/index.d.mts +6 -0
  879. package/doctor/index.mjs +214 -47
  880. package/evaluator/index.mjs +73 -8
  881. package/llms.txt +702 -56
  882. package/package.json +256 -25
  883. package/scripts/component-catalog-extension-validation.mjs +28 -0
  884. package/traversal-exclusions.mjs +60 -1
  885. package/ux-demo/recipes/app-shell.tsx +181 -189
  886. package/ux-demo/recipes/collapsible-sidebar.tsx +177 -139
  887. package/ux-demo/recipes/compact-toolbar.tsx +120 -101
  888. package/ux-demo/recipes/composer-form.tsx +86 -62
  889. package/ux-demo/recipes/list-detail-dialog.tsx +219 -114
  890. package/ux-demo/recipes/list-workspace-states.tsx +95 -80
  891. package/ux-demo/recipes/loaders.ts +5 -2
  892. package/ux-demo/recipes/loading-inspector.tsx +157 -117
  893. package/ux-demo/recipes/mount-recipe.ts +0 -5
  894. package/ux-demo/recipes/navigation-sidebar.tsx +98 -83
  895. package/ux-demo/recipes/navigation-stack.tsx +42 -29
  896. package/ux-demo/recipes/types.ts +14 -1
  897. package/ux-demo/recipes/workspace-header.tsx +77 -51
  898. package/vite-ux-review.d.mts +83 -0
  899. package/vite-ux-review.mjs +120 -0
  900. package/ai/component-catalog-extension-v2.d.ts +0 -7
  901. package/ai/component-catalog-extension-v2.schema.json +0 -7
  902. package/ai/component-catalog-v2.d.ts +0 -67
  903. package/ai/component-catalog-v2.json +0 -4675
  904. package/ai/component-catalog-v2.schema.json +0 -246
  905. package/dist/browser/panel-header.js +0 -7
  906. package/dist/chunk-5VTB2BKP.js +0 -35
  907. package/dist/chunk-5VTB2BKP.js.map +0 -1
  908. package/dist/chunk-6WTETUZ6.js +0 -21
  909. package/dist/chunk-6WTETUZ6.js.map +0 -1
  910. package/dist/chunk-7KEUJIIC.js.map +0 -1
  911. package/dist/chunk-7XRDUG4Y.js +0 -58
  912. package/dist/chunk-7XRDUG4Y.js.map +0 -1
  913. package/dist/chunk-B3A3IVO5.js +0 -83
  914. package/dist/chunk-B3A3IVO5.js.map +0 -1
  915. package/dist/chunk-DPDBVKNA.js +0 -113
  916. package/dist/chunk-DPDBVKNA.js.map +0 -1
  917. package/dist/chunk-FEAMR7S4.js +0 -68
  918. package/dist/chunk-FEAMR7S4.js.map +0 -1
  919. package/dist/chunk-FEY65TBV.js +0 -15
  920. package/dist/chunk-FEY65TBV.js.map +0 -1
  921. package/dist/chunk-IJBSZ4NX.js.map +0 -1
  922. package/dist/chunk-J5BFYY7Q.js +0 -35
  923. package/dist/chunk-J5BFYY7Q.js.map +0 -1
  924. package/dist/chunk-JRKSK2HX.js.map +0 -1
  925. package/dist/chunk-KWWR5VMS.js.map +0 -1
  926. package/dist/chunk-LPOMRC4C.js.map +0 -1
  927. package/dist/chunk-M7JBER5U.js.map +0 -1
  928. package/dist/chunk-NENBUYX5.js +0 -79
  929. package/dist/chunk-NENBUYX5.js.map +0 -1
  930. package/dist/chunk-NWSEP5RT.js +0 -117
  931. package/dist/chunk-NWSEP5RT.js.map +0 -1
  932. package/dist/chunk-OJWWHFH3.js +0 -426
  933. package/dist/chunk-OJWWHFH3.js.map +0 -1
  934. package/dist/chunk-PSKN344N.js.map +0 -1
  935. package/dist/chunk-PX3YSS5C.js.map +0 -1
  936. package/dist/chunk-QO55FN2Y.js.map +0 -1
  937. package/dist/chunk-R7ZWUN64.js +0 -38
  938. package/dist/chunk-R7ZWUN64.js.map +0 -1
  939. package/dist/chunk-RKVQEH4J.js.map +0 -1
  940. package/dist/chunk-SRSJO5QE.js.map +0 -1
  941. package/dist/chunk-SYDMYBPG.js +0 -31
  942. package/dist/chunk-SYDMYBPG.js.map +0 -1
  943. package/dist/chunk-TB6DY7H5.js +0 -25
  944. package/dist/chunk-TB6DY7H5.js.map +0 -1
  945. package/dist/chunk-UFZKLBCF.js +0 -108
  946. package/dist/chunk-UFZKLBCF.js.map +0 -1
  947. package/dist/chunk-UKKSMZYT.js +0 -281
  948. package/dist/chunk-UKKSMZYT.js.map +0 -1
  949. package/dist/chunk-W2FK7ZNY.js +0 -26
  950. package/dist/chunk-W2FK7ZNY.js.map +0 -1
  951. package/dist/chunk-W5L2JSBY.js +0 -40
  952. package/dist/chunk-W5L2JSBY.js.map +0 -1
  953. package/dist/chunk-Y45PPBDV.js.map +0 -1
  954. package/dist/chunk-Y6LYKFXY.js.map +0 -1
  955. package/dist/divider-sides-267FA7sY.d.ts +0 -4
  956. package/dist/panel-header.d.ts +0 -42
  957. package/dist/panel-header.js +0 -7
  958. package/dist/panel-header.js.map +0 -1
  959. package/dist/styles/panel-header.css +0 -64
  960. package/dist/styles/tab-scaffold.css +0 -84
  961. package/dist/tab-scaffold.d.ts +0 -30
  962. package/dist/tab-scaffold.js +0 -51
  963. package/dist/tab-scaffold.js.map +0 -1
  964. package/dist/wire-tab-scaffold.d.ts +0 -11
  965. package/dist/wire-tab-scaffold.js +0 -21
  966. package/dist/wire-tab-scaffold.js.map +0 -1
  967. package/docs/design/templates/panel-header/icon-actions-dark.svg +0 -1
  968. package/docs/design/templates/panel-header/icon-actions.svg +0 -1
  969. package/docs/design/templates/panel-header/icon-summary-actions-dark.svg +0 -1
  970. package/docs/design/templates/panel-header/icon-summary-actions.svg +0 -1
  971. package/docs/design/templates/panel-header/no-icon-dark.svg +0 -1
  972. package/docs/design/templates/panel-header/no-icon.svg +0 -1
  973. package/docs/design/templates/panel-header/page-heading-dark.svg +0 -1
  974. package/docs/design/templates/panel-header/page-heading.svg +0 -1
  975. package/docs/design/templates/panel-header/title-only-dark.svg +0 -1
  976. package/docs/design/templates/panel-header/title-only.svg +0 -1
  977. package/docs/design/templates/panel-header-dark.svg +0 -1
  978. package/docs/design/templates/panel-header.svg +0 -1
  979. package/docs/tab-scaffold.md +0 -50
  980. package/ux-demo/recipes/recipes.css +0 -448
@@ -13,7 +13,26 @@ import postcss from 'postcss';
13
13
  import ts from 'typescript';
14
14
 
15
15
  import { loadApplicationUiProfile } from '../ai/application-ui-profile.mjs';
16
- import { isUiTraversalExcluded } from '../traversal-exclusions.mjs';
16
+ import { createUiTraversalFilter } from '../traversal-exclusions.mjs';
17
+ import {
18
+ classOwnerFor,
19
+ componentLabel,
20
+ componentName,
21
+ componentOwnershipFacts,
22
+ contextualComponents,
23
+ configurationFor,
24
+ ownsClassBlock,
25
+ privateVariableOwner,
26
+ reportGap,
27
+ restyledComponents,
28
+ typedPropForToken,
29
+ } from './component-ownership.mjs';
30
+ import { collectLoudDeclarations, inspectLoudPairs } from './loud-pairs.mjs';
31
+ import {
32
+ classNames,
33
+ complexSelectorParts,
34
+ withoutRelationalArguments,
35
+ } from './selectors.mjs';
17
36
 
18
37
  export const UI_ANALYSIS_SCHEMA_VERSION = 1;
19
38
 
@@ -22,7 +41,10 @@ export const UI_ANALYSIS_RULES = Object.freeze({
22
41
  'KUI-L002': { severity: 'error', title: 'Unknown Kerf token' },
23
42
  'KUI-L003': { severity: 'error', title: 'Competing geometry owners' },
24
43
  'KUI-L004': { severity: 'review', title: 'Repeated content inset' },
25
- 'KUI-L005': { severity: 'review', title: 'Forced component dimension' },
44
+ // KUI-L005 ('Forced component dimension') is retired: KUI-L019 reports any
45
+ // restyle of a cataloged component, dimensions included, and L005's only
46
+ // other shape (a public class as an ancestor) sizes an application element.
47
+ // The id stays in the diagnostic registry so existing exceptions still load.
26
48
  'KUI-L006': {
27
49
  severity: 'review',
28
50
  title: 'Hard-coded spacing outside the approved scale',
@@ -30,17 +52,84 @@ export const UI_ANALYSIS_RULES = Object.freeze({
30
52
  'KUI-L007': { severity: 'error', title: 'Nested scroll owners' },
31
53
  'KUI-L008': { severity: 'review', title: 'Dynamic class requires review' },
32
54
  'KUI-L009': { severity: 'error', title: 'Stylesheet could not be parsed' },
55
+ 'KUI-L010': {
56
+ severity: 'error',
57
+ title: 'Private Kerf descendant override',
58
+ },
59
+ 'KUI-L011': { severity: 'error', title: 'Uncataloged shadow part override' },
60
+ 'KUI-L012': { severity: 'error', title: 'Private Kerf token assignment' },
61
+ 'KUI-L013': { severity: 'error', title: 'Uncataloged CSS value literal' },
62
+ 'KUI-L014': { severity: 'error', title: 'Wrong CSS value helper dimension' },
63
+ 'KUI-L015': { severity: 'error', title: 'Non-standalone CSS expression' },
64
+ 'KUI-L016': { severity: 'error', title: 'Forbidden declaration-list escape' },
65
+ 'KUI-L017': { severity: 'review', title: 'Exceptional spacing shorthand' },
66
+ 'KUI-L018': {
67
+ severity: 'review',
68
+ title: 'Loud fill override without its on-loud pair',
69
+ },
70
+ 'KUI-L019': {
71
+ severity: 'error',
72
+ title: 'Application CSS restyles a cataloged component',
73
+ },
74
+ 'KUI-L020': {
75
+ severity: 'error',
76
+ title: "Another component's private variable",
77
+ },
78
+ 'KUI-L021': {
79
+ severity: 'error',
80
+ title: 'Component token overridden where a typed prop exists',
81
+ },
82
+ 'KUI-L022': {
83
+ severity: 'error',
84
+ title: "Hook class restyles a cataloged component's root",
85
+ },
86
+ 'KUI-L023': {
87
+ severity: 'error',
88
+ title: "Another component's public class is rendered as markup",
89
+ },
90
+ });
91
+
92
+ const ownershipAction =
93
+ "Configure the component through its typed props, variants, or public tokens; style only your own elements (in the component's context when needed). If no configuration covers the need, report the component gap to its package instead of overriding it.";
94
+
95
+ /** The doctor's per-rule repair action, where it is more specific than the default. */
96
+ export const UI_ANALYSIS_ACTIONS = Object.freeze({
97
+ 'KUI-L019': ownershipAction,
98
+ 'KUI-L020': ownershipAction,
99
+ 'KUI-L021':
100
+ 'Set the typed prop the diagnostic names on the component instead of overriding its token.',
101
+ 'KUI-L022': ownershipAction,
102
+ 'KUI-L023': ownershipAction,
33
103
  });
34
104
 
35
- const sourceExtensions = new Set(['.tsx', '.jsx']);
105
+ const adoptionRules = new Set([
106
+ 'KUI-L001',
107
+ 'KUI-L002',
108
+ 'KUI-L010',
109
+ 'KUI-L011',
110
+ 'KUI-L012',
111
+ 'KUI-L019',
112
+ 'KUI-L020',
113
+ 'KUI-L021',
114
+ 'KUI-L022',
115
+ 'KUI-L023',
116
+ ]);
117
+
118
+ const sourceExtensions = new Set(['.js', '.jsx', '.mjs', '.ts', '.tsx']);
119
+ function isTestModule(file) {
120
+ const path = file.replaceAll('\\', '/');
121
+ return (
122
+ /(?:^|\/)__tests__\//.test(path) ||
123
+ /\.(?:test|spec)\.(?:js|jsx|mjs|ts|tsx)$/.test(path)
124
+ );
125
+ }
36
126
  const spacingProperties = /^(?:margin|padding|gap|inset)(?:-|$)/;
37
- const dimensionProperties =
38
- /^(?:width|height|min-width|max-width|min-height|max-height)$/;
39
127
  const approvedSpacing = new Set([0, 4, 8, 16, 24]);
40
128
  async function collectFiles(root, paths) {
41
129
  const files = [];
130
+ const excluded = await createUiTraversalFilter(root);
42
131
  const visit = async (path) => {
43
- if (isUiTraversalExcluded(root, path)) return;
132
+ if (excluded(path)) return;
44
133
  const details = await stat(path);
45
134
  if (details.isFile()) {
46
135
  if (sourceExtensions.has(extname(path)) || path.endsWith('.css'))
@@ -51,6 +140,7 @@ async function collectFiles(root, paths) {
51
140
  for (const entry of entries) {
52
141
  const child = resolve(path, entry.name);
53
142
  if (entry.isDirectory()) await visit(child);
143
+ else if (excluded(child)) continue;
54
144
  else if (
55
145
  sourceExtensions.has(extname(entry.name)) ||
56
146
  entry.name.endsWith('.css')
@@ -80,11 +170,11 @@ function location(file, node, source) {
80
170
  };
81
171
  }
82
172
 
83
- function diagnostic(ruleId, at, message, evidence, chain) {
173
+ function diagnostic(ruleId, at, message, evidence, chain, adoption = false) {
84
174
  const rule = UI_ANALYSIS_RULES[ruleId];
85
175
  return {
86
176
  ruleId,
87
- severity: rule.severity,
177
+ severity: adoption && adoptionRules.has(ruleId) ? 'review' : rule.severity,
88
178
  message,
89
179
  location: at,
90
180
  evidence,
@@ -119,7 +209,13 @@ function relativeStyleImports(file, source) {
119
209
  });
120
210
  return imports;
121
211
  }
122
- const syntax = file.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.JSX;
212
+ const syntax = file.endsWith('.tsx')
213
+ ? ts.ScriptKind.TSX
214
+ : file.endsWith('.jsx')
215
+ ? ts.ScriptKind.JSX
216
+ : file.endsWith('.ts')
217
+ ? ts.ScriptKind.TS
218
+ : ts.ScriptKind.JS;
123
219
  const module = ts.createSourceFile(
124
220
  file,
125
221
  source,
@@ -147,6 +243,7 @@ function reachableStyleFacts(file, imports, styleFacts) {
147
243
  const current = result.get(className) ?? { scroll: false, inset: false };
148
244
  current.scroll ||= facts.scroll;
149
245
  current.inset ||= facts.inset;
246
+ current.styled ??= facts.styled;
150
247
  result.set(className, current);
151
248
  }
152
249
  }
@@ -164,7 +261,7 @@ function reachableStyleFiles(file, imports, styleFacts) {
164
261
  return visited;
165
262
  }
166
263
 
167
- async function loadCatalogs(profileResult) {
264
+ async function loadCatalogs(profileResult, ownership) {
168
265
  const entries = [];
169
266
  for (const catalog of profileResult.profile?.catalogs ?? []) {
170
267
  const owner = profileResult.provenance[`$catalogs.${catalog.package}`];
@@ -176,7 +273,51 @@ async function loadCatalogs(profileResult) {
176
273
  'utf8',
177
274
  ),
178
275
  );
179
- entries.push(...(artifact.entries ?? []));
276
+ const selection = await loadSelectionFacts(owner, catalog);
277
+ const joined = new Set();
278
+ for (const entry of artifact.entries ?? []) {
279
+ const selected = selection.get(entry.key);
280
+ joined.add(entry.key);
281
+ const componentSource =
282
+ selected?.componentSource ??
283
+ (/\.[cm]?[jt]sx?$/.test(entry.source) ? entry.source : undefined);
284
+ entries.push(
285
+ selected
286
+ ? {
287
+ ...entry,
288
+ publicExports: entry.publicExports ?? selected.publicExports,
289
+ moduleImports: selected.moduleImports,
290
+ componentSource,
291
+ styleSources: entry.styleSources ?? selected.styleSources,
292
+ catalogDirectory: dirname(owner),
293
+ }
294
+ : { ...entry, componentSource, catalogDirectory: dirname(owner) },
295
+ );
296
+ }
297
+ for (const [key, selected] of selection) {
298
+ if (
299
+ ownership !== 'component' ||
300
+ joined.has(key) ||
301
+ !selected.componentSource
302
+ )
303
+ continue;
304
+ entries.push({
305
+ key,
306
+ package: catalog.package,
307
+ id: selected.id,
308
+ name: selected.name,
309
+ kind: selected.kind,
310
+ componentSource: selected.componentSource,
311
+ styleSources: selected.styleSources,
312
+ moduleImports: selected.moduleImports,
313
+ publicExports: selected.publicExports,
314
+ boundaries: {
315
+ publicClasses: selected.publicClasses ?? [],
316
+ rootElement: selected.rootElement,
317
+ },
318
+ catalogDirectory: dirname(owner),
319
+ });
320
+ }
180
321
  } catch {
181
322
  // Profile validation reports the precise catalog load failure.
182
323
  }
@@ -184,25 +325,342 @@ async function loadCatalogs(profileResult) {
184
325
  return entries;
185
326
  }
186
327
 
328
+ // The composition (v2) catalog carries no import facts, and several of its
329
+ // entries share a display name (the Kerf `Select` and the Web Awesome
330
+ // `wa-select` are both "Select"). The selection (v1) catalog is where each
331
+ // entry's real public exports and import subpaths live, so join it by key.
332
+ async function loadSelectionFacts(owner, catalog) {
333
+ const facts = new Map();
334
+ if (!catalog.selection?.path) return facts;
335
+ let artifact;
336
+ try {
337
+ artifact = JSON.parse(
338
+ await readFile(resolve(dirname(owner), catalog.selection.path), 'utf8'),
339
+ );
340
+ } catch {
341
+ return facts;
342
+ }
343
+ const packageName = artifact.package ?? catalog.package;
344
+ for (const entry of artifact.entries ?? []) {
345
+ const delivery = entry.delivery ?? {};
346
+ facts.set(`${packageName}:${entry.id}`, {
347
+ id: entry.id,
348
+ name: entry.name,
349
+ kind: entry.kind,
350
+ publicExports: entry.publicExports,
351
+ publicClasses: entry.publicClasses,
352
+ rootElement: entry.rootElement,
353
+ componentSource: entry.source,
354
+ styleSources: entry.styleSources,
355
+ moduleImports: [delivery.browserImport, delivery.moduleImport].filter(
356
+ Boolean,
357
+ ),
358
+ });
359
+ }
360
+ return facts;
361
+ }
362
+
363
+ // A catalog entry's public exports mix the component render function with
364
+ // helpers its props consume (`Select` ships `uiColor`, `List` ships
365
+ // `px`/`rem`/`flex`). Only the render function stands for the entry, so only
366
+ // it may carry the entry's contracts. A component export is one whose name
367
+ // starts with an uppercase letter: JSX itself treats a lowercase tag as an
368
+ // intrinsic element, so a lowercase export can never be a component tag, and
369
+ // scripts/check-component-catalog.mjs enforces the converse for this package
370
+ // (every uppercase runtime export returns SafeHtml; no lowercase one does).
371
+ // eslint-plugin-kerfjs applies the same rule.
372
+ function isComponentExport(name) {
373
+ return /^[A-Z]/.test(name);
374
+ }
375
+
376
+ // Pick the catalog entry an imported (or namespace-accessed) export name
377
+ // refers to. Names are not unique across a catalog, so candidates are ranked:
378
+ // an entry whose own import subpath is the module wins, then an entry that
379
+ // declares the name as a public export, then catalog order. A helper export
380
+ // never resolves to an entry.
381
+ function resolveExportEntry(
382
+ candidates,
383
+ name,
384
+ module,
385
+ file,
386
+ packageDirectory,
387
+ aliasSource,
388
+ ) {
389
+ if (!isComponentExport(name)) return undefined;
390
+ const localSource =
391
+ packageDirectory && (module?.startsWith('.') || aliasSource)
392
+ ? relative(
393
+ packageDirectory,
394
+ aliasSource ?? resolve(dirname(file), module),
395
+ )
396
+ .replaceAll('\\', '/')
397
+ .replace(/\.[cm]?[jt]sx?$/, '')
398
+ : undefined;
399
+ const eligible = (candidates ?? []).filter(
400
+ (entry) =>
401
+ module === entry.package ||
402
+ module?.startsWith(`${entry.package}/`) ||
403
+ (localSource &&
404
+ entry.componentSource?.replace(/\.[cm]?[jt]sx?$/, '') === localSource),
405
+ );
406
+ const exported = (entry) =>
407
+ (entry.publicExports ?? []).some(
408
+ (item) => (typeof item === 'string' ? item : item.name) === name,
409
+ );
410
+ return (
411
+ eligible.find((entry) => entry.moduleImports?.includes(module)) ??
412
+ eligible.find(exported) ??
413
+ eligible[0]
414
+ );
415
+ }
416
+
417
+ // Use the nearest project tsconfig without walking into a parent repository.
418
+ // TypeScript expands `extends` and resolves exact and wildcard `paths` here.
419
+ function compilerOptionsFor(file, root, cache) {
420
+ let directory = dirname(file);
421
+ const visited = [];
422
+ while (directory === root || !relative(root, directory).startsWith('..')) {
423
+ if (cache.has(directory)) break;
424
+ visited.push(directory);
425
+ const configPath = resolve(directory, 'tsconfig.json');
426
+ if (ts.sys.fileExists(configPath)) {
427
+ const config = ts.readConfigFile(configPath, ts.sys.readFile);
428
+ cache.set(
429
+ directory,
430
+ config.error
431
+ ? undefined
432
+ : ts.parseJsonConfigFileContent(
433
+ config.config,
434
+ ts.sys,
435
+ directory,
436
+ undefined,
437
+ configPath,
438
+ ).options,
439
+ );
440
+ break;
441
+ }
442
+ if (directory === root) break;
443
+ directory = dirname(directory);
444
+ }
445
+ const options = cache.get(directory);
446
+ for (const path of visited) cache.set(path, options);
447
+ return options;
448
+ }
449
+
187
450
  function catalogFacts(entries) {
188
451
  const publicClasses = new Set();
189
452
  const publicTokens = new Set();
453
+ const publicParts = new Map();
190
454
  const classEntries = new Map();
455
+ const blockEntries = new Map();
191
456
  const exportEntries = new Map();
457
+ const addExport = (name, entry) => {
458
+ const candidates = exportEntries.get(name) ?? [];
459
+ if (!candidates.includes(entry)) candidates.push(entry);
460
+ exportEntries.set(name, candidates);
461
+ };
192
462
  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 ?? []) {
463
+ addExport(entry.name, entry);
464
+ for (const item of entry.publicExports ?? []) {
465
+ const name = typeof item === 'string' ? item : item.name;
466
+ if (isComponentExport(name)) addExport(name, entry);
467
+ }
468
+ for (const className of new Set(
469
+ [
470
+ entry.boundaries?.rootClass,
471
+ ...(entry.boundaries?.publicClasses ?? []),
472
+ ].filter(Boolean),
473
+ )) {
197
474
  publicClasses.add(className);
198
475
  const owners = classEntries.get(className) ?? [];
199
476
  owners.push(entry);
200
477
  classEntries.set(className, owners);
478
+ const block = bemBlock(className);
479
+ const blockOwners = blockEntries.get(block) ?? [];
480
+ if (!blockOwners.includes(entry)) blockOwners.push(entry);
481
+ blockEntries.set(block, blockOwners);
201
482
  }
202
483
  for (const token of entry.boundaries?.publicTokens ?? [])
203
484
  publicTokens.add(token);
485
+ for (const part of entry.boundaries?.publicParts ?? []) {
486
+ const roots = publicParts.get(part) ?? new Set();
487
+ if (entry.boundaries?.rootClass) roots.add(entry.boundaries.rootClass);
488
+ publicParts.set(part, roots);
489
+ }
204
490
  }
205
- return { publicClasses, publicTokens, classEntries, exportEntries };
491
+ return {
492
+ entries,
493
+ publicClasses,
494
+ publicTokens,
495
+ publicParts,
496
+ classEntries,
497
+ blockEntries,
498
+ exportEntries,
499
+ ownership: componentOwnershipFacts(entries),
500
+ };
501
+ }
502
+
503
+ const bemBlock = (name) => name.split(/__|--/, 1)[0];
504
+
505
+ function renderedLiteralClasses(file, source) {
506
+ const names = new Set();
507
+ const parsed = ts.createSourceFile(
508
+ file,
509
+ source,
510
+ ts.ScriptTarget.Latest,
511
+ true,
512
+ file.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.JSX,
513
+ );
514
+ const visit = (node) => {
515
+ if (
516
+ ts.isJsxAttribute(node) &&
517
+ ['class', 'className'].includes(node.name.text)
518
+ ) {
519
+ const value =
520
+ node.initializer && ts.isStringLiteral(node.initializer)
521
+ ? node.initializer.text
522
+ : node.initializer &&
523
+ ts.isJsxExpression(node.initializer) &&
524
+ node.initializer.expression &&
525
+ ts.isStringLiteral(node.initializer.expression)
526
+ ? node.initializer.expression.text
527
+ : undefined;
528
+ for (const name of value?.split(/\s+/) ?? []) if (name) names.add(name);
529
+ }
530
+ ts.forEachChild(node, visit);
531
+ };
532
+ visit(parsed);
533
+ return names;
534
+ }
535
+
536
+ function inferOwnedClasses(entries, contents, imports) {
537
+ return entries.map((entry) => {
538
+ if (!entry.componentSource || !entry.catalogDirectory) return entry;
539
+ const sourceFile = resolve(entry.catalogDirectory, entry.componentSource);
540
+ const source = contents.get(sourceFile);
541
+ if (!source) return entry;
542
+ const styleFiles = entry.styleSources?.length
543
+ ? entry.styleSources.map((path) => resolve(entry.catalogDirectory, path))
544
+ : [...(imports.get(sourceFile) ?? [])];
545
+ const styleSources = styleFiles.map((file) =>
546
+ relative(entry.catalogDirectory, file).replaceAll('\\', '/'),
547
+ );
548
+ const styledBlocks = new Set();
549
+ for (const style of styleFiles) {
550
+ const css = contents.get(style);
551
+ if (!css) continue;
552
+ try {
553
+ postcss.parse(css, { from: style }).walkRules((rule) => {
554
+ for (const name of subjectClasses(rule.selector))
555
+ styledBlocks.add(bemBlock(name));
556
+ });
557
+ } catch {
558
+ // The stylesheet parser reports malformed CSS in the normal pass.
559
+ }
560
+ }
561
+ const inferred = [...renderedLiteralClasses(sourceFile, source)].filter(
562
+ (name) => styledBlocks.has(bemBlock(name)),
563
+ );
564
+ if (!inferred.length) return { ...entry, styleSources };
565
+ return {
566
+ ...entry,
567
+ styleSources,
568
+ boundaries: {
569
+ ...entry.boundaries,
570
+ publicClasses: [
571
+ ...new Set([...(entry.boundaries?.publicClasses ?? []), ...inferred]),
572
+ ],
573
+ },
574
+ };
575
+ });
576
+ }
577
+
578
+ async function ownershipGroupEntries(groups, root, contents) {
579
+ if (!groups?.length) return [];
580
+ const manifest = JSON.parse(
581
+ await readFile(resolve(root, 'package.json'), 'utf8'),
582
+ );
583
+ return Promise.all(
584
+ groups.map(async (group, index) => {
585
+ const classes = new Set();
586
+ for (const style of group.styleSources) {
587
+ const file = resolve(root, style);
588
+ let css = contents.get(file);
589
+ if (css === undefined)
590
+ try {
591
+ css = await readFile(file, 'utf8');
592
+ } catch {
593
+ // A missing configured file owns no classes.
594
+ }
595
+ if (!css) continue;
596
+ try {
597
+ postcss.parse(css, { from: file }).walkRules((rule) => {
598
+ for (const name of subjectClasses(rule.selector)) classes.add(name);
599
+ });
600
+ } catch {
601
+ // The normal stylesheet pass reports malformed CSS.
602
+ }
603
+ }
604
+ return {
605
+ key: `${manifest.name}:ownership-group-${index + 1}`,
606
+ package: manifest.name,
607
+ id: `ownership-group-${index + 1}`,
608
+ name: `Ownership group ${index + 1}`,
609
+ catalogDirectory: root,
610
+ styleSources: group.styleSources,
611
+ ownershipGroupSources: group.sources,
612
+ boundaries: { publicClasses: [...classes] },
613
+ };
614
+ }),
615
+ );
616
+ }
617
+
618
+ async function implicitOwnershipEntries(files, imports, root) {
619
+ const found = [];
620
+ const packages = new Map();
621
+ const packageAt = async (directory) => {
622
+ if (packages.has(directory)) return packages.get(directory);
623
+ let result;
624
+ try {
625
+ const manifest = JSON.parse(
626
+ await readFile(resolve(directory, 'package.json'), 'utf8'),
627
+ );
628
+ if (typeof manifest.name === 'string')
629
+ result = { name: manifest.name, directory };
630
+ } catch {
631
+ // Search the containing directory for a package manifest.
632
+ }
633
+ if (!result && directory !== root) {
634
+ const parent = dirname(directory);
635
+ if (!relative(root, parent).startsWith('..'))
636
+ result = await packageAt(parent);
637
+ }
638
+ packages.set(directory, result);
639
+ return result;
640
+ };
641
+ for (const file of files) {
642
+ if (!sourceExtensions.has(extname(file))) continue;
643
+ const styles = (imports.get(file) ?? []).filter((path) =>
644
+ files.includes(path),
645
+ );
646
+ if (!styles.length) continue;
647
+ const owner = await packageAt(dirname(file));
648
+ if (!owner) continue;
649
+ const source = relative(owner.directory, file).replaceAll('\\', '/');
650
+ const id = `module:${source}`;
651
+ found.push({
652
+ key: `${owner.name}:${id}`,
653
+ package: owner.name,
654
+ id,
655
+ name: basename(file).replace(/\.[^.]+$/, ''),
656
+ componentSource: source,
657
+ catalogDirectory: owner.directory,
658
+ styleSources: styles.map((style) =>
659
+ relative(owner.directory, style).replaceAll('\\', '/'),
660
+ ),
661
+ });
662
+ }
663
+ return found;
206
664
  }
207
665
 
208
666
  function spacingValues(value) {
@@ -214,7 +672,180 @@ function spacingValues(value) {
214
672
  return results;
215
673
  }
216
674
 
217
- async function inspectCss(file, source, facts, diagnostics, cssFacts) {
675
+ function escapeRegExp(value) {
676
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
677
+ }
678
+
679
+ // A declaration that restyles, as opposed to one that only sets a custom
680
+ // property: setting a component's public token on its host is configuration,
681
+ // which the token rules (KUI-L002, KUI-L012, KUI-L020, KUI-L021) judge.
682
+ function restylingDeclarations(rule) {
683
+ return (rule.nodes ?? []).filter(
684
+ (node) => node.type === 'decl' && !node.prop.startsWith('--'),
685
+ );
686
+ }
687
+
688
+ function inKeyframes(rule) {
689
+ return rule.parent?.type === 'atrule' && /keyframes$/i.test(rule.parent.name);
690
+ }
691
+
692
+ // The classes a rule makes its subject (the rightmost compound of each complex
693
+ // selector, outside :has()/:not()).
694
+ function subjectClasses(selector) {
695
+ return complexSelectorParts(selector).flatMap((parts) => {
696
+ const subject = parts.at(-1)?.compound;
697
+ return subject ? classNames(withoutRelationalArguments(subject)) : [];
698
+ });
699
+ }
700
+
701
+ function inspectComponentOwnership(
702
+ file,
703
+ rule,
704
+ facts,
705
+ diagnostics,
706
+ adoption,
707
+ isForeign,
708
+ ownershipEvidence,
709
+ ownershipContext,
710
+ ) {
711
+ if (inKeyframes(rule)) return;
712
+ const restyling = restylingDeclarations(rule);
713
+ if (restyling.length === 0) return;
714
+ const properties = [...new Set(restyling.map((decl) => decl.prop))];
715
+ const restyled = restyledComponents(
716
+ rule.selector,
717
+ facts.ownership,
718
+ isForeign,
719
+ ownershipEvidence,
720
+ );
721
+ for (const { entry, coOwners, via, name } of restyled)
722
+ diagnostics.push(
723
+ diagnostic(
724
+ 'KUI-L019',
725
+ location(file, rule),
726
+ coOwners
727
+ ? `\`${name}\` makes a class co-owned by ${coOwners.map((owner) => componentLabel(owner)).join(', ')} the subject of an application rule (${properties.join(', ')}); those components own their styles. Configure an owning component or style your own element instead.`
728
+ : via === 'descendant'
729
+ ? `\`${name}\` reaches an unclassed descendant inside ${componentLabel(entry)} (${properties.join(', ')}); components own their styles. Use a child combinator or an own class to target your element, or configure the child through ${configurationFor(entry)}. ${reportGap(entry)}`
730
+ : `\`${name}\` makes ${componentLabel(entry)} the subject of an application rule (${properties.join(', ')}); components own their styles. Configure it through ${configurationFor(entry)}. To place your own content in its context, style your own element (\`${name} > .your-element\`). ${reportGap(entry)}`,
731
+ {
732
+ selector: rule.selector,
733
+ ...(coOwners
734
+ ? { components: coOwners.map((owner) => owner.key) }
735
+ : { component: entry.key }),
736
+ via,
737
+ target: name,
738
+ properties,
739
+ },
740
+ undefined,
741
+ adoption,
742
+ ),
743
+ );
744
+ if (ownershipContext === 'subject' || !ownershipEvidence?.ownPackage) return;
745
+ for (const { entry, name, position } of contextualComponents(
746
+ rule.selector,
747
+ facts.ownership,
748
+ isForeign,
749
+ ownershipEvidence.ownPackage,
750
+ ownershipContext === 'any-package',
751
+ )) {
752
+ if (position === 'subject') continue;
753
+ if (
754
+ restyled.some(
755
+ (item) => item.entry.key === entry.key && item.name === name,
756
+ )
757
+ )
758
+ continue;
759
+ diagnostics.push(
760
+ diagnostic(
761
+ 'KUI-L019',
762
+ location(file, rule),
763
+ `\`${name}\` uses ${componentLabel(entry)} as ${position} selector context (${properties.join(', ')}); foreign component handles cannot key this stylesheet in this ownership mode. Configure the component through ${configurationFor(entry)}. ${reportGap(entry)}`,
764
+ {
765
+ selector: rule.selector,
766
+ component: entry.key,
767
+ via: 'context',
768
+ position,
769
+ target: name,
770
+ properties,
771
+ },
772
+ undefined,
773
+ adoption,
774
+ ),
775
+ );
776
+ }
777
+ }
778
+
779
+ function inspectVariableOwnership(
780
+ file,
781
+ decl,
782
+ facts,
783
+ diagnostics,
784
+ adoption,
785
+ isForeign,
786
+ ) {
787
+ const at = location(file, decl);
788
+ const privates = new Set([
789
+ ...(decl.prop.match(/--_[a-z0-9_-]+/gi) ?? []),
790
+ ...(decl.value.match(/--_[a-z0-9_-]+/gi) ?? []),
791
+ ]);
792
+ for (const variable of privates) {
793
+ const owner = privateVariableOwner(variable, facts.ownership);
794
+ if (owner === undefined || (owner && !isForeign(owner))) continue;
795
+ const written = decl.prop === variable;
796
+ diagnostics.push(
797
+ diagnostic(
798
+ 'KUI-L020',
799
+ at,
800
+ `\`${variable}\` is ${owner ? `${componentName(owner)}'s` : "a Kerf component's"} private variable; ${written ? 'writing it overrides' : 'reading it couples to'} that component's implementation. Configure it through ${configurationFor(owner)}. ${reportGap(owner)}`,
801
+ {
802
+ property: decl.prop,
803
+ value: decl.value,
804
+ variable,
805
+ ...(owner ? { component: owner.key } : {}),
806
+ access: written ? 'write' : 'read',
807
+ },
808
+ undefined,
809
+ adoption,
810
+ ),
811
+ );
812
+ }
813
+ if (!decl.prop.startsWith('--')) return;
814
+ const typed = typedPropForToken(decl.prop, facts.ownership);
815
+ if (!typed || !isForeign(typed.entry)) return;
816
+ const contract = (typed.entry.cssValueProps ?? []).find(
817
+ ({ path }) => path === typed.path,
818
+ );
819
+ const example = contract?.examples?.[0];
820
+ diagnostics.push(
821
+ diagnostic(
822
+ 'KUI-L021',
823
+ at,
824
+ `\`${decl.prop}\` is what ${typed.entry.name}'s typed \`${typed.path}\` prop sets; set the prop on ${typed.entry.name}${example ? ` (\`<${typed.entry.name} ${example} />\`)` : ''} instead of overriding the token. ${reportGap(typed.entry)}`,
825
+ {
826
+ property: decl.prop,
827
+ value: decl.value,
828
+ component: typed.entry.key,
829
+ prop: typed.path,
830
+ },
831
+ undefined,
832
+ adoption,
833
+ ),
834
+ );
835
+ }
836
+
837
+ async function inspectCss(
838
+ file,
839
+ source,
840
+ facts,
841
+ diagnostics,
842
+ cssFacts,
843
+ adoption = false,
844
+ siblingOnLoud = [],
845
+ isForeign = () => true,
846
+ ownershipEvidence,
847
+ ownershipContext = 'subject',
848
+ ) {
218
849
  let root;
219
850
  try {
220
851
  root = postcss.parse(source, { from: file });
@@ -228,37 +859,139 @@ async function inspectCss(file, source, facts, diagnostics, cssFacts) {
228
859
  );
229
860
  return;
230
861
  }
862
+ inspectLoudPairs(
863
+ file,
864
+ root,
865
+ (decl, message, evidence) =>
866
+ diagnostics.push(
867
+ diagnostic('KUI-L018', location(file, decl), message, evidence),
868
+ ),
869
+ siblingOnLoud,
870
+ );
231
871
  root.walkRules((rule) => {
872
+ const subjects = subjectClasses(rule.selector);
873
+ inspectComponentOwnership(
874
+ file,
875
+ rule,
876
+ facts,
877
+ diagnostics,
878
+ adoption,
879
+ isForeign,
880
+ ownershipEvidence,
881
+ ownershipContext,
882
+ );
883
+ if (!inKeyframes(rule) && restylingDeclarations(rule).length > 0)
884
+ for (const className of subjects) {
885
+ const record = cssFacts.get(className) ?? {
886
+ scroll: false,
887
+ inset: false,
888
+ };
889
+ record.styled ??= {
890
+ stylesheet: file,
891
+ line: rule.source?.start?.line ?? 1,
892
+ selector: rule.selector,
893
+ };
894
+ cssFacts.set(className, record);
895
+ }
232
896
  const classes = [
233
897
  ...rule.selector.matchAll(/\.([_a-zA-Z]+[_a-zA-Z0-9-]*)/g),
234
898
  ].map((match) => match[1]);
899
+ const publicRootPattern = [...facts.publicClasses]
900
+ .map(
901
+ (className) => `\\.${className.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`,
902
+ )
903
+ .join('|');
904
+ const privateDescendants = new Set();
905
+ if (publicRootPattern)
906
+ for (const selector of rule.selectors ?? [rule.selector]) {
907
+ const rootMatch = new RegExp(
908
+ `(?:${publicRootPattern})(?:[.#:[\\w-]|\\s)*(?:>|\\s)`,
909
+ ).exec(selector);
910
+ if (!rootMatch) continue;
911
+ const descendant = selector.slice(
912
+ rootMatch.index + rootMatch[0].length,
913
+ );
914
+ for (const match of descendant.matchAll(/\.(kui-[\w-]+)/g))
915
+ if (!facts.publicClasses.has(match[1]))
916
+ privateDescendants.add(match[1]);
917
+ }
235
918
  for (const className of classes) {
236
- if (className.startsWith('kui-') && !facts.publicClasses.has(className))
919
+ if (privateDescendants.has(className))
920
+ diagnostics.push(
921
+ diagnostic(
922
+ 'KUI-L010',
923
+ location(file, rule),
924
+ `.${className} is a private Kerf descendant; prefer the owning component's prop or variant, or catalog a deliberate public extension point.`,
925
+ { selector: rule.selector, className },
926
+ undefined,
927
+ adoption,
928
+ ),
929
+ );
930
+ else if (
931
+ className.startsWith('kui-') &&
932
+ !facts.publicClasses.has(className)
933
+ )
237
934
  diagnostics.push(
238
935
  diagnostic(
239
936
  'KUI-L001',
240
937
  location(file, rule),
241
- `.${className} is not a cataloged public Kerf class.`,
938
+ `.${className} is not a cataloged public Kerf class; prefer a component prop or variant, or catalog the extension point.`,
242
939
  { selector: rule.selector, className },
940
+ undefined,
941
+ adoption,
942
+ ),
943
+ );
944
+ }
945
+ for (const match of rule.selector.matchAll(/::part\(\s*([\w-]+)\s*\)/g)) {
946
+ const part = match[1];
947
+ const roots = facts.publicParts.get(part) ?? new Set();
948
+ const allowed = [...roots].some((className) =>
949
+ new RegExp(`\\.${escapeRegExp(className)}(?:[^\\w-]|$)`).test(
950
+ rule.selector.slice(0, match.index),
951
+ ),
952
+ );
953
+ if (!allowed)
954
+ diagnostics.push(
955
+ diagnostic(
956
+ 'KUI-L011',
957
+ location(file, rule),
958
+ `::part(${part}) is not a cataloged extension point; prefer the component's prop or variant, or add ${part} to boundaries.publicParts.`,
959
+ { selector: rule.selector, part },
960
+ undefined,
961
+ adoption,
243
962
  ),
244
963
  );
245
964
  }
246
965
  rule.walkDecls((decl) => {
966
+ inspectVariableOwnership(
967
+ file,
968
+ decl,
969
+ facts,
970
+ diagnostics,
971
+ adoption,
972
+ isForeign,
973
+ );
247
974
  const at = location(file, decl);
248
975
  const tokens = [
249
976
  ...(decl.prop.match(/--kui-[a-z0-9-]+/g) ?? []),
250
977
  ...(decl.value.match(/--kui-[a-z0-9-]+/g) ?? []),
251
978
  ];
252
979
  for (const token of new Set(tokens))
253
- if (!facts.publicTokens.has(token))
980
+ if (!facts.publicTokens.has(token)) {
981
+ const assignment = decl.prop === token;
254
982
  diagnostics.push(
255
983
  diagnostic(
256
- 'KUI-L002',
984
+ assignment ? 'KUI-L012' : 'KUI-L002',
257
985
  at,
258
- `${token} is not a cataloged public Kerf token.`,
986
+ assignment
987
+ ? `${token} has no public configuration contract; prefer the component's prop or variant, or catalog the token explicitly.`
988
+ : `${token} is not a cataloged public Kerf token; prefer a documented token or component configuration.`,
259
989
  { property: decl.prop, value: decl.value, token },
990
+ undefined,
991
+ adoption,
260
992
  ),
261
993
  );
994
+ }
262
995
  if (
263
996
  spacingProperties.test(decl.prop) &&
264
997
  !/var\(|calc\(|remify\(/.test(decl.value)
@@ -277,7 +1010,7 @@ async function inspectCss(file, source, facts, diagnostics, cssFacts) {
277
1010
  },
278
1011
  ),
279
1012
  );
280
- for (const className of classes) {
1013
+ for (const className of subjects) {
281
1014
  const record = cssFacts.get(className) ?? {
282
1015
  scroll: false,
283
1016
  inset: false,
@@ -290,22 +1023,6 @@ async function inspectCss(file, source, facts, diagnostics, cssFacts) {
290
1023
  if (/^padding(?:-|$)/.test(decl.prop) && decl.value !== '0')
291
1024
  record.inset = true;
292
1025
  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
1026
  }
310
1027
  });
311
1028
  });
@@ -328,7 +1045,480 @@ function literalClasses(attribute) {
328
1045
  return { values: [], dynamic: true };
329
1046
  }
330
1047
 
331
- function inspectTsx(file, sourceText, facts, cssFacts, diagnostics) {
1048
+ function literalClassValues(expression) {
1049
+ if (!expression) return [];
1050
+ if (ts.isStringLiteralLike(expression))
1051
+ return expression.text.split(/\s+/).filter(Boolean);
1052
+ if (ts.isParenthesizedExpression(expression))
1053
+ return literalClassValues(expression.expression);
1054
+ if (ts.isConditionalExpression(expression))
1055
+ return [
1056
+ ...literalClassValues(expression.whenTrue),
1057
+ ...literalClassValues(expression.whenFalse),
1058
+ ];
1059
+ return [];
1060
+ }
1061
+
1062
+ function jsxOwnershipEvidence(style, facts, contents, root, compilerCache) {
1063
+ const hooks = new Map();
1064
+ const composedChildren = new Map();
1065
+ const composedDescendants = new Map();
1066
+ const ambiguousHooks = new Set();
1067
+ const ambiguousChildren = new Set();
1068
+ const ambiguousDescendants = new Set();
1069
+ const record = (map, ambiguous, key, entry) => {
1070
+ if (ambiguous.has(key)) return;
1071
+ if (map.has(key) && map.get(key).key !== entry.key) {
1072
+ map.delete(key);
1073
+ ambiguous.add(key);
1074
+ } else map.set(key, entry);
1075
+ };
1076
+ for (const owner of facts.entries) {
1077
+ if (!owner.componentSource || !owner.catalogDirectory) continue;
1078
+ const relativeStyle = relative(owner.catalogDirectory, style).replaceAll(
1079
+ '\\',
1080
+ '/',
1081
+ );
1082
+ if (!owner.styleSources?.includes(relativeStyle)) continue;
1083
+ const file = resolve(owner.catalogDirectory, owner.componentSource);
1084
+ const sourceText = contents.get(file);
1085
+ if (!sourceText) continue;
1086
+ const source = ts.createSourceFile(
1087
+ file,
1088
+ sourceText,
1089
+ ts.ScriptTarget.Latest,
1090
+ true,
1091
+ ts.ScriptKind.TSX,
1092
+ );
1093
+ const localComponents = new Map();
1094
+ for (const statement of source.statements) {
1095
+ if (
1096
+ ts.isFunctionDeclaration(statement) &&
1097
+ statement.name &&
1098
+ statement.body
1099
+ )
1100
+ localComponents.set(statement.name.text, statement.body);
1101
+ if (!ts.isVariableStatement(statement)) continue;
1102
+ for (const declaration of statement.declarationList.declarations)
1103
+ if (
1104
+ ts.isIdentifier(declaration.name) &&
1105
+ declaration.initializer &&
1106
+ (ts.isArrowFunction(declaration.initializer) ||
1107
+ ts.isFunctionExpression(declaration.initializer))
1108
+ )
1109
+ localComponents.set(
1110
+ declaration.name.text,
1111
+ declaration.initializer.body,
1112
+ );
1113
+ }
1114
+ const imports = new Map();
1115
+ const namespaces = new Map();
1116
+ const compilerOptions = compilerOptionsFor(file, root, compilerCache);
1117
+ const aliasSource = (module) =>
1118
+ module && !module.startsWith('.') && compilerOptions?.paths
1119
+ ? ts.resolveModuleName(module, file, compilerOptions, ts.sys)
1120
+ .resolvedModule?.resolvedFileName
1121
+ : undefined;
1122
+ for (const statement of source.statements) {
1123
+ if (
1124
+ !ts.isImportDeclaration(statement) ||
1125
+ !ts.isStringLiteral(statement.moduleSpecifier)
1126
+ )
1127
+ continue;
1128
+ const module = statement.moduleSpecifier.text;
1129
+ const bindings = statement.importClause?.namedBindings;
1130
+ if (bindings && ts.isNamespaceImport(bindings)) {
1131
+ namespaces.set(bindings.name.text, module);
1132
+ continue;
1133
+ }
1134
+ if (!bindings || !ts.isNamedImports(bindings)) continue;
1135
+ for (const item of bindings.elements) {
1136
+ const exported = item.propertyName?.text ?? item.name.text;
1137
+ const entry = resolveExportEntry(
1138
+ facts.exportEntries.get(exported),
1139
+ exported,
1140
+ module,
1141
+ file,
1142
+ owner.catalogDirectory,
1143
+ aliasSource(module),
1144
+ );
1145
+ if (entry) imports.set(item.name.text, entry);
1146
+ }
1147
+ }
1148
+ const openingOf = (node) =>
1149
+ ts.isJsxElement(node) ? node.openingElement : node;
1150
+ const entryFor = (opening) => {
1151
+ const tag = opening.tagName.getText(source);
1152
+ if (imports.has(tag)) return imports.get(tag);
1153
+ if (
1154
+ ts.isPropertyAccessExpression(opening.tagName) &&
1155
+ ts.isIdentifier(opening.tagName.expression)
1156
+ ) {
1157
+ const module = namespaces.get(opening.tagName.expression.text);
1158
+ return resolveExportEntry(
1159
+ facts.exportEntries.get(opening.tagName.name.text),
1160
+ opening.tagName.name.text,
1161
+ module,
1162
+ file,
1163
+ owner.catalogDirectory,
1164
+ aliasSource(module),
1165
+ );
1166
+ }
1167
+ return undefined;
1168
+ };
1169
+ const classesOf = (opening) =>
1170
+ literalClasses(
1171
+ opening.attributes.properties.find(
1172
+ (item) =>
1173
+ ts.isJsxAttribute(item) &&
1174
+ ['class', 'className'].includes(item.name.getText(source)),
1175
+ ),
1176
+ ).values;
1177
+ const returnedJsx = (body) => {
1178
+ if (!ts.isBlock(body)) return [body];
1179
+ const expressions = [];
1180
+ const visitReturns = (node) => {
1181
+ if (ts.isReturnStatement(node)) {
1182
+ if (node.expression) expressions.push(node.expression);
1183
+ return;
1184
+ }
1185
+ if (
1186
+ node !== body &&
1187
+ (ts.isFunctionDeclaration(node) ||
1188
+ ts.isFunctionExpression(node) ||
1189
+ ts.isArrowFunction(node))
1190
+ )
1191
+ return;
1192
+ ts.forEachChild(node, visitReturns);
1193
+ };
1194
+ visitReturns(body);
1195
+ return expressions;
1196
+ };
1197
+ const localReturns = (opening, seen) => {
1198
+ const name = opening.tagName.getText(source);
1199
+ const body = localComponents.get(name);
1200
+ if (!body || seen.has(name)) return undefined;
1201
+ return { name, expressions: returnedJsx(body) };
1202
+ };
1203
+ const composedBelow = (node, found, seen = new Set()) => {
1204
+ if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
1205
+ const opening = openingOf(node);
1206
+ const entry = entryFor(opening);
1207
+ if (entry) {
1208
+ for (const type of [
1209
+ entry.boundaries?.rootElement,
1210
+ ...(entry.boundaries?.descendantElements ?? []),
1211
+ ]) {
1212
+ if (!type) continue;
1213
+ const entries = found.get(type) ?? new Set();
1214
+ entries.add(entry);
1215
+ found.set(type, entries);
1216
+ }
1217
+ return;
1218
+ }
1219
+ const local = localReturns(opening, seen);
1220
+ if (local) {
1221
+ const next = new Set([...seen, local.name]);
1222
+ for (const expression of local.expressions)
1223
+ composedBelow(expression, found, next);
1224
+ return;
1225
+ }
1226
+ if (ts.isJsxElement(node))
1227
+ for (const child of node.children) composedBelow(child, found, seen);
1228
+ return;
1229
+ }
1230
+ if (ts.isJsxFragment(node)) {
1231
+ for (const child of node.children) composedBelow(child, found, seen);
1232
+ return;
1233
+ }
1234
+ ts.forEachChild(node, (child) => composedBelow(child, found, seen));
1235
+ };
1236
+ const expandedRoots = (node, seen = new Set()) => {
1237
+ if (ts.isJsxFragment(node))
1238
+ return node.children.flatMap((child) => expandedRoots(child, seen));
1239
+ if (!ts.isJsxElement(node) && !ts.isJsxSelfClosingElement(node)) {
1240
+ const roots = [];
1241
+ ts.forEachChild(node, (child) =>
1242
+ roots.push(...expandedRoots(child, seen)),
1243
+ );
1244
+ return roots;
1245
+ }
1246
+ const local = localReturns(openingOf(node), seen);
1247
+ if (!local) return [node];
1248
+ const next = new Set([...seen, local.name]);
1249
+ return local.expressions.flatMap((expression) =>
1250
+ expandedRoots(expression, next),
1251
+ );
1252
+ };
1253
+ const visit = (node) => {
1254
+ if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
1255
+ const opening = openingOf(node);
1256
+ const entry = entryFor(opening);
1257
+ if (entry)
1258
+ for (const name of classesOf(opening))
1259
+ record(hooks, ambiguousHooks, name, entry);
1260
+ if (ts.isJsxElement(node)) {
1261
+ const parentClasses = classesOf(opening);
1262
+ if (parentClasses.length) {
1263
+ const descendants = new Map();
1264
+ for (const child of node.children)
1265
+ composedBelow(child, descendants);
1266
+ for (const [type, entries] of descendants)
1267
+ for (const entry of entries)
1268
+ for (const name of parentClasses)
1269
+ record(
1270
+ composedDescendants,
1271
+ ambiguousDescendants,
1272
+ `${name}|${type}`,
1273
+ entry,
1274
+ );
1275
+ }
1276
+ const possible = new Map();
1277
+ let dynamic = false;
1278
+ for (const child of node.children) {
1279
+ if (ts.isJsxExpression(child) && child.expression) dynamic = true;
1280
+ if (!ts.isJsxElement(child) && !ts.isJsxSelfClosingElement(child))
1281
+ continue;
1282
+ for (const root of expandedRoots(child)) {
1283
+ const childOpening = openingOf(root);
1284
+ const childEntry = entryFor(childOpening);
1285
+ const type =
1286
+ childEntry?.boundaries?.rootElement ??
1287
+ (childEntry
1288
+ ? undefined
1289
+ : childOpening.tagName.getText(source).toLowerCase());
1290
+ if (!type) continue;
1291
+ const candidates = possible.get(type) ?? [];
1292
+ candidates.push(childEntry);
1293
+ possible.set(type, candidates);
1294
+ }
1295
+ }
1296
+ if (!dynamic)
1297
+ for (const [type, candidates] of possible)
1298
+ if (
1299
+ candidates.length > 0 &&
1300
+ candidates.every(
1301
+ (candidate) => candidate?.key === candidates[0]?.key,
1302
+ ) &&
1303
+ candidates[0]
1304
+ )
1305
+ for (const name of parentClasses)
1306
+ record(
1307
+ composedChildren,
1308
+ ambiguousChildren,
1309
+ `${name}|${type}`,
1310
+ candidates[0],
1311
+ );
1312
+ }
1313
+ }
1314
+ ts.forEachChild(node, visit);
1315
+ };
1316
+ visit(source);
1317
+ }
1318
+ return { componentMode: true, hooks, composedChildren, composedDescendants };
1319
+ }
1320
+
1321
+ function cssPreferred(contract) {
1322
+ return (
1323
+ [
1324
+ ...(contract.canonicalShorthands ?? []).map((item) => `\`${item}\``),
1325
+ ...(contract.helpers ?? []).map((item) => `\`${item}()\``),
1326
+ ].join(' or ') || 'a cataloged component prop'
1327
+ );
1328
+ }
1329
+
1330
+ function objectProperty(object, name) {
1331
+ return object?.properties.find(
1332
+ (property) =>
1333
+ ts.isPropertyAssignment(property) &&
1334
+ property.name.getText().replaceAll(/["']/g, '') === name,
1335
+ );
1336
+ }
1337
+
1338
+ // A catalog `cssValueProps` path is dot-separated object members, each
1339
+ // optionally suffixed with `[]` to visit every array element: `gap`,
1340
+ // `choices[].swatch`, `iconTile.color`. A member that does not resolve to a
1341
+ // literal object or array (a tone string, a variable, a spread) is skipped:
1342
+ // dynamic values are the type system's job.
1343
+ function cssPathSegments(path) {
1344
+ return path
1345
+ .split('.')
1346
+ .map((segment) =>
1347
+ segment.endsWith('[]')
1348
+ ? { name: segment.slice(0, -2), each: true }
1349
+ : { name: segment, each: false },
1350
+ );
1351
+ }
1352
+
1353
+ function expandCssElements(values, each) {
1354
+ if (!each) return values;
1355
+ return values.flatMap((value) =>
1356
+ ts.isArrayLiteralExpression(value) ? value.elements : [],
1357
+ );
1358
+ }
1359
+
1360
+ function nestedCssValues(value, segments) {
1361
+ let values = expandCssElements(value ? [value] : [], segments[0].each);
1362
+ for (const { name, each } of segments.slice(1))
1363
+ values = expandCssElements(
1364
+ values.flatMap((item) => {
1365
+ if (!ts.isObjectLiteralExpression(item)) return [];
1366
+ const property = objectProperty(item, name);
1367
+ return property ? [property.initializer] : [];
1368
+ }),
1369
+ each,
1370
+ );
1371
+ return values;
1372
+ }
1373
+
1374
+ function jsxCssValues(opening, path) {
1375
+ const segments = cssPathSegments(path);
1376
+ const attribute = opening.attributes.properties.find(
1377
+ (item) =>
1378
+ ts.isJsxAttribute(item) && item.name.getText() === segments[0].name,
1379
+ );
1380
+ if (!attribute?.initializer) return [];
1381
+ const value = ts.isJsxExpression(attribute.initializer)
1382
+ ? attribute.initializer.expression
1383
+ : attribute.initializer;
1384
+ return nestedCssValues(value, segments);
1385
+ }
1386
+
1387
+ function callCssValues(call, path) {
1388
+ const options = call.arguments[0];
1389
+ if (!options || !ts.isObjectLiteralExpression(options)) return [];
1390
+ const segments = cssPathSegments(path);
1391
+ const property = objectProperty(options, segments[0].name);
1392
+ return property ? nestedCssValues(property.initializer, segments) : [];
1393
+ }
1394
+
1395
+ function cssLiteral(value) {
1396
+ if (
1397
+ ts.isStringLiteralLike(value) ||
1398
+ ts.isNoSubstitutionTemplateLiteral(value)
1399
+ )
1400
+ return value.text;
1401
+ return undefined;
1402
+ }
1403
+
1404
+ function importedName(expression, helperImports, namespaces, packageName) {
1405
+ let helper;
1406
+ if (ts.isIdentifier(expression)) helper = helperImports.get(expression.text);
1407
+ else if (
1408
+ ts.isPropertyAccessExpression(expression) &&
1409
+ ts.isIdentifier(expression.expression) &&
1410
+ namespaces.has(expression.expression.text)
1411
+ )
1412
+ helper = {
1413
+ imported: expression.name.text,
1414
+ source: namespaces.get(expression.expression.text),
1415
+ };
1416
+ else helper = undefined;
1417
+ if (!helper) return undefined;
1418
+ return helper.source === packageName ||
1419
+ helper.source.startsWith(`${packageName}/`)
1420
+ ? helper.imported
1421
+ : undefined;
1422
+ }
1423
+
1424
+ function inspectCssValues(
1425
+ file,
1426
+ source,
1427
+ entry,
1428
+ valuesFor,
1429
+ helperImports,
1430
+ namespaces,
1431
+ diagnostics,
1432
+ ) {
1433
+ for (const contract of entry.cssValueProps ?? []) {
1434
+ const preferred = cssPreferred(contract);
1435
+ for (const value of valuesFor(contract.path)) {
1436
+ const at = location(file, value, source);
1437
+ if (
1438
+ contract.grammar === 'declarations' &&
1439
+ contract.rawPolicy !== 'allow'
1440
+ ) {
1441
+ diagnostics.push(
1442
+ diagnostic(
1443
+ 'KUI-L016',
1444
+ at,
1445
+ `\`${entry.name}.${contract.path}\` is a forbidden declaration-list escape hatch; use \`className\`, public tokens, or cataloged props.`,
1446
+ { component: entry.key, path: contract.path },
1447
+ ),
1448
+ );
1449
+ continue;
1450
+ }
1451
+ const literal = cssLiteral(value);
1452
+ if (literal !== undefined) {
1453
+ if (contract.exceptionalShorthands?.includes(literal)) {
1454
+ diagnostics.push(
1455
+ diagnostic(
1456
+ 'KUI-L017',
1457
+ at,
1458
+ `\`${literal}\` is an exceptional shorthand for \`${entry.name}.${contract.path}\`; prefer ${preferred} unless the off-scale choice is deliberate.`,
1459
+ { component: entry.key, path: contract.path, value: literal },
1460
+ ),
1461
+ );
1462
+ } else if (
1463
+ !contract.shorthands?.includes(literal) &&
1464
+ contract.rawPolicy !== 'allow'
1465
+ ) {
1466
+ diagnostics.push(
1467
+ diagnostic(
1468
+ 'KUI-L013',
1469
+ at,
1470
+ `\`${entry.name}.${contract.path}\` uses ${contract.grammar} grammar; replace raw \`${literal}\` with ${preferred}.`,
1471
+ { component: entry.key, path: contract.path, value: literal },
1472
+ ),
1473
+ );
1474
+ }
1475
+ continue;
1476
+ }
1477
+ if (!ts.isCallExpression(value)) continue;
1478
+ const helper = importedName(
1479
+ value.expression,
1480
+ helperImports,
1481
+ namespaces,
1482
+ entry.package,
1483
+ );
1484
+ if (!helper) continue;
1485
+ if (contract.nonStandaloneHelpers?.includes(helper))
1486
+ diagnostics.push(
1487
+ diagnostic(
1488
+ 'KUI-L015',
1489
+ at,
1490
+ `\`${helper}()\` is not a standalone CSS value for \`${entry.name}.${contract.path}\`; wrap it with an accepted composer such as \`calc()\`.`,
1491
+ { component: entry.key, path: contract.path, helper },
1492
+ ),
1493
+ );
1494
+ else if (
1495
+ !contract.helpers?.includes(helper) &&
1496
+ contract.unsafeHelper !== helper
1497
+ )
1498
+ diagnostics.push(
1499
+ diagnostic(
1500
+ 'KUI-L014',
1501
+ at,
1502
+ `\`${helper}()\` produces the wrong grammar for \`${entry.name}.${contract.path}\`; use ${preferred}.`,
1503
+ { component: entry.key, path: contract.path, helper },
1504
+ ),
1505
+ );
1506
+ }
1507
+ }
1508
+ }
1509
+
1510
+ function inspectTsx(
1511
+ file,
1512
+ sourceText,
1513
+ facts,
1514
+ cssFacts,
1515
+ diagnostics,
1516
+ adoption = false,
1517
+ isForeign = () => true,
1518
+ packageDirectory,
1519
+ compilerOptions,
1520
+ componentMode = false,
1521
+ ) {
332
1522
  const source = ts.createSourceFile(
333
1523
  file,
334
1524
  sourceText,
@@ -337,6 +1527,20 @@ function inspectTsx(file, sourceText, facts, cssFacts, diagnostics) {
337
1527
  ts.ScriptKind.TSX,
338
1528
  );
339
1529
  const imports = new Map();
1530
+ const helperImports = new Map();
1531
+ const namespaces = new Map();
1532
+ const aliasSources = new Map();
1533
+ const aliasSource = (module) => {
1534
+ if (!module || module.startsWith('.') || !compilerOptions?.paths)
1535
+ return undefined;
1536
+ if (!aliasSources.has(module))
1537
+ aliasSources.set(
1538
+ module,
1539
+ ts.resolveModuleName(module, file, compilerOptions, ts.sys)
1540
+ .resolvedModule?.resolvedFileName,
1541
+ );
1542
+ return aliasSources.get(module);
1543
+ };
340
1544
  for (const statement of source.statements) {
341
1545
  if (
342
1546
  !ts.isImportDeclaration(statement) ||
@@ -344,21 +1548,96 @@ function inspectTsx(file, sourceText, facts, cssFacts, diagnostics) {
344
1548
  )
345
1549
  continue;
346
1550
  const module = statement.moduleSpecifier.text;
347
- if (!module.includes('kerf') && !module.startsWith('@')) continue;
1551
+ const importClause = statement.importClause;
1552
+ if (importClause?.name)
1553
+ helperImports.set(importClause.name.text, {
1554
+ imported: 'default',
1555
+ source: module,
1556
+ });
348
1557
  const bindings = statement.importClause?.namedBindings;
1558
+ if (bindings && ts.isNamespaceImport(bindings)) {
1559
+ namespaces.set(bindings.name.text, module);
1560
+ continue;
1561
+ }
349
1562
  if (!bindings || !ts.isNamedImports(bindings)) continue;
350
1563
  for (const item of bindings.elements) {
351
1564
  const exported = item.propertyName?.text ?? item.name.text;
352
- const entry = facts.exportEntries.get(exported);
1565
+ helperImports.set(item.name.text, { imported: exported, source: module });
1566
+ const entry = resolveExportEntry(
1567
+ facts.exportEntries.get(exported),
1568
+ exported,
1569
+ module,
1570
+ file,
1571
+ packageDirectory,
1572
+ aliasSource(module),
1573
+ );
353
1574
  if (entry) imports.set(item.name.text, entry);
354
1575
  }
355
1576
  }
356
1577
  const stack = [];
1578
+ const inspectBorrowedMarkup = !isTestModule(file);
1579
+ const borrowed = (className, node, via) => {
1580
+ if (!inspectBorrowedMarkup) return;
1581
+ const owners =
1582
+ facts.classEntries.get(className) ??
1583
+ (componentMode && bemBlock(className) !== className
1584
+ ? facts.blockEntries.get(bemBlock(className))
1585
+ : undefined) ??
1586
+ [];
1587
+ for (const owner of owners) {
1588
+ if (
1589
+ !isForeign(owner, className) ||
1590
+ owner.boundaries?.placeableClasses?.includes(className)
1591
+ )
1592
+ continue;
1593
+ diagnostics.push(
1594
+ diagnostic(
1595
+ 'KUI-L023',
1596
+ location(file, node, source),
1597
+ `\`.${className}\` is rendered in this module's ${via} but belongs to ${componentLabel(owner)}. Render that component or configure it through ${configurationFor(owner)}. ${reportGap(owner)}`,
1598
+ { className, component: owner.key, via },
1599
+ undefined,
1600
+ adoption,
1601
+ ),
1602
+ );
1603
+ }
1604
+ };
1605
+ const borrowedValues = (values, node, via) => {
1606
+ for (const className of new Set(values)) borrowed(className, node, via);
1607
+ };
1608
+ const templateGap = '\uE000';
1609
+ const rawHtmlClasses = (value) => {
1610
+ const classes = [];
1611
+ for (const match of value.matchAll(
1612
+ /<[a-z][^>]*\sclass\s*=\s*["']([^"']+)["']/gi,
1613
+ )) {
1614
+ const pieces = match[1].split(templateGap);
1615
+ for (const [index, piece] of pieces.entries()) {
1616
+ const words = piece.split(/\s+/).filter(Boolean);
1617
+ if (index > 0 && !/^\s/.test(piece)) words.shift();
1618
+ if (index < pieces.length - 1 && !/\s$/.test(piece)) words.pop();
1619
+ classes.push(...words);
1620
+ }
1621
+ }
1622
+ return classes;
1623
+ };
357
1624
  const visit = (node) => {
358
1625
  if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
359
1626
  const opening = ts.isJsxElement(node) ? node.openingElement : node;
360
1627
  const tag = opening.tagName.getText(source);
361
- const entry = imports.get(tag);
1628
+ const namespaceEntry =
1629
+ ts.isPropertyAccessExpression(opening.tagName) &&
1630
+ ts.isIdentifier(opening.tagName.expression)
1631
+ ? resolveExportEntry(
1632
+ facts.exportEntries.get(opening.tagName.name.text),
1633
+ opening.tagName.name.text,
1634
+ namespaces.get(opening.tagName.expression.text),
1635
+ file,
1636
+ packageDirectory,
1637
+ aliasSource(namespaces.get(opening.tagName.expression.text)),
1638
+ )
1639
+ : undefined;
1640
+ const entry = imports.get(tag) ?? namespaceEntry;
362
1641
  const classAttribute = opening.attributes.properties.find(
363
1642
  (item) =>
364
1643
  ts.isJsxAttribute(item) &&
@@ -366,6 +1645,55 @@ function inspectTsx(file, sourceText, facts, cssFacts, diagnostics) {
366
1645
  );
367
1646
  const classes = literalClasses(classAttribute);
368
1647
  const at = location(file, opening, source);
1648
+ if ((!entry && /^[a-z]/.test(tag)) || (componentMode && entry))
1649
+ borrowedValues(
1650
+ classAttribute?.initializer &&
1651
+ ts.isJsxExpression(classAttribute.initializer)
1652
+ ? literalClassValues(classAttribute.initializer.expression)
1653
+ : classes.values,
1654
+ classAttribute ?? opening,
1655
+ 'JSX markup',
1656
+ );
1657
+ if (entry)
1658
+ inspectCssValues(
1659
+ file,
1660
+ source,
1661
+ entry,
1662
+ (path) => jsxCssValues(opening, path),
1663
+ helperImports,
1664
+ namespaces,
1665
+ diagnostics,
1666
+ );
1667
+ if (entry && isForeign(entry))
1668
+ for (const className of classes.values) {
1669
+ // A class owned by the surrounding application is still a hook when
1670
+ // placed on another component's root. Only the component's own
1671
+ // public class is safe to style here.
1672
+ if (
1673
+ classOwnerFor(facts.ownership, className, componentMode)?.key ===
1674
+ entry.key
1675
+ )
1676
+ continue;
1677
+ const styled = cssFacts.get(className)?.styled;
1678
+ if (!styled) continue;
1679
+ diagnostics.push(
1680
+ diagnostic(
1681
+ 'KUI-L022',
1682
+ at,
1683
+ `\`${className}\` is placed on ${entry.name}'s root and styled by ${basename(styled.stylesheet)}:${styled.line} (\`${styled.selector}\`), which restyles ${componentLabel(entry)}; components own their styles. Configure it through ${configurationFor(entry)}, or wrap it in your own element and style that. ${reportGap(entry)}`,
1684
+ {
1685
+ tag,
1686
+ component: entry.key,
1687
+ className,
1688
+ stylesheet: styled.stylesheet,
1689
+ line: styled.line,
1690
+ selector: styled.selector,
1691
+ },
1692
+ undefined,
1693
+ adoption,
1694
+ ),
1695
+ );
1696
+ }
369
1697
  if (classes.dynamic)
370
1698
  diagnostics.push(
371
1699
  diagnostic(
@@ -425,11 +1753,92 @@ function inspectTsx(file, sourceText, facts, cssFacts, diagnostics) {
425
1753
  inset: inset || parent?.inset,
426
1754
  label: entry?.key ?? tag,
427
1755
  });
1756
+ // JSX supplied through a prop (for example Toolbar.leading) is still
1757
+ // rendered markup and must be checked like ordinary children.
1758
+ ts.forEachChild(opening.attributes, visit);
428
1759
  if (ts.isJsxElement(node))
429
1760
  for (const child of node.children) visit(child);
430
1761
  stack.pop();
431
1762
  return;
432
1763
  }
1764
+ if (ts.isCallExpression(node)) {
1765
+ if (ts.isPropertyAccessExpression(node.expression)) {
1766
+ const method = node.expression.name.text;
1767
+ const receiver = node.expression.expression;
1768
+ if (
1769
+ ts.isPropertyAccessExpression(receiver) &&
1770
+ receiver.name.text === 'classList'
1771
+ ) {
1772
+ const written =
1773
+ method === 'add'
1774
+ ? node.arguments
1775
+ : method === 'toggle'
1776
+ ? node.arguments.slice(0, 1)
1777
+ : method === 'replace'
1778
+ ? node.arguments.slice(1, 2)
1779
+ : [];
1780
+ for (const value of written)
1781
+ borrowedValues(literalClassValues(value), value, 'classList write');
1782
+ }
1783
+ if (
1784
+ method === 'setAttribute' &&
1785
+ node.arguments[0] &&
1786
+ ts.isStringLiteralLike(node.arguments[0]) &&
1787
+ node.arguments[0].text === 'class'
1788
+ )
1789
+ borrowedValues(
1790
+ literalClassValues(node.arguments[1]),
1791
+ node.arguments[1] ?? node,
1792
+ 'setAttribute write',
1793
+ );
1794
+ }
1795
+ const entry = ts.isIdentifier(node.expression)
1796
+ ? imports.get(node.expression.text)
1797
+ : ts.isPropertyAccessExpression(node.expression) &&
1798
+ ts.isIdentifier(node.expression.expression)
1799
+ ? resolveExportEntry(
1800
+ facts.exportEntries.get(node.expression.name.text),
1801
+ node.expression.name.text,
1802
+ namespaces.get(node.expression.expression.text),
1803
+ file,
1804
+ packageDirectory,
1805
+ aliasSource(namespaces.get(node.expression.expression.text)),
1806
+ )
1807
+ : undefined;
1808
+ if (entry)
1809
+ inspectCssValues(
1810
+ file,
1811
+ source,
1812
+ entry,
1813
+ (path) => callCssValues(node, path),
1814
+ helperImports,
1815
+ namespaces,
1816
+ diagnostics,
1817
+ );
1818
+ }
1819
+ if (
1820
+ ts.isBinaryExpression(node) &&
1821
+ [ts.SyntaxKind.EqualsToken, ts.SyntaxKind.PlusEqualsToken].includes(
1822
+ node.operatorToken.kind,
1823
+ ) &&
1824
+ ts.isPropertyAccessExpression(node.left) &&
1825
+ node.left.name.text === 'className'
1826
+ )
1827
+ borrowedValues(
1828
+ literalClassValues(node.right),
1829
+ node.right,
1830
+ 'className assignment',
1831
+ );
1832
+ if (ts.isStringLiteralLike(node) && node.text.includes('<'))
1833
+ borrowedValues(rawHtmlClasses(node.text), node, 'raw HTML');
1834
+ if (ts.isTemplateExpression(node)) {
1835
+ const staticHtml = [
1836
+ node.head.text,
1837
+ ...node.templateSpans.map((span) => span.literal.text),
1838
+ ].join(templateGap);
1839
+ if (staticHtml.includes('<'))
1840
+ borrowedValues(rawHtmlClasses(staticHtml), node, 'raw HTML');
1841
+ }
433
1842
  ts.forEachChild(node, visit);
434
1843
  };
435
1844
  visit(source);
@@ -439,6 +1848,11 @@ export async function analyzeUiProject({
439
1848
  root: requestedRoot = process.cwd(),
440
1849
  paths,
441
1850
  knownRules = [],
1851
+ adoption = false,
1852
+ ownership = 'package',
1853
+ ownershipContext = 'subject',
1854
+ implicitComponentOwnership = false,
1855
+ ownershipGroups = [],
442
1856
  profile: packageProfile = resolve(
443
1857
  import.meta.dirname,
444
1858
  '../ai/application-ui-profile.defaults.json',
@@ -474,6 +1888,22 @@ export async function analyzeUiProject({
474
1888
  }
475
1889
  }
476
1890
  files.sort();
1891
+ const imports = new Map(
1892
+ files.map((file) => [file, relativeStyleImports(file, contents.get(file))]),
1893
+ );
1894
+ const implicitEntries =
1895
+ ownership === 'component' && implicitComponentOwnership
1896
+ ? await implicitOwnershipEntries(files, imports, root)
1897
+ : [];
1898
+ const groupEntries =
1899
+ ownership === 'component'
1900
+ ? await ownershipGroupEntries(ownershipGroups, root, contents)
1901
+ : [];
1902
+ const groupStyles = new Set(
1903
+ groupEntries.flatMap((entry) =>
1904
+ entry.styleSources.map((style) => resolve(entry.catalogDirectory, style)),
1905
+ ),
1906
+ );
477
1907
  const profileContexts = new Map();
478
1908
  const loadContext = async (file) => {
479
1909
  const startDirectory = file === root ? root : dirname(file);
@@ -487,10 +1917,51 @@ export async function analyzeUiProject({
487
1917
  packageProfile,
488
1918
  knownRules: [...Object.keys(UI_ANALYSIS_RULES), ...knownRules],
489
1919
  });
490
- const entries = await loadCatalogs(profileResult);
1920
+ const entries = await loadCatalogs(profileResult, ownership);
1921
+ const catalogedSources = new Set(
1922
+ entries
1923
+ .filter((entry) => entry.componentSource)
1924
+ .map((entry) =>
1925
+ resolve(entry.catalogDirectory, entry.componentSource),
1926
+ ),
1927
+ );
1928
+ const ownershipEntries = [
1929
+ ...entries,
1930
+ ...groupEntries,
1931
+ ...implicitEntries.flatMap((entry) => {
1932
+ const source = resolve(
1933
+ entry.catalogDirectory,
1934
+ entry.componentSource,
1935
+ );
1936
+ if (catalogedSources.has(source)) return [];
1937
+ if (
1938
+ groupEntries.some((group) => {
1939
+ const relativeSource = relative(
1940
+ group.catalogDirectory,
1941
+ source,
1942
+ ).replaceAll('\\', '/');
1943
+ return group.ownershipGroupSources.some((path) =>
1944
+ path.endsWith('/')
1945
+ ? relativeSource.startsWith(path)
1946
+ : relativeSource === path,
1947
+ );
1948
+ })
1949
+ )
1950
+ return [];
1951
+ const styleSources = entry.styleSources.filter(
1952
+ (style) =>
1953
+ !groupStyles.has(resolve(entry.catalogDirectory, style)),
1954
+ );
1955
+ return styleSources.length ? [{ ...entry, styleSources }] : [];
1956
+ }),
1957
+ ];
491
1958
  return {
492
1959
  profileResult,
493
- facts: catalogFacts(entries),
1960
+ facts: catalogFacts(
1961
+ ownership === 'component'
1962
+ ? inferOwnedClasses(ownershipEntries, contents, imports)
1963
+ : entries,
1964
+ ),
494
1965
  startDirectory,
495
1966
  };
496
1967
  })(),
@@ -503,9 +1974,106 @@ export async function analyzeUiProject({
503
1974
  ),
504
1975
  );
505
1976
  if (!files.length) await loadContext(root);
506
- const imports = new Map(
507
- files.map((file) => [file, relativeStyleImports(file, contents.get(file))]),
508
- );
1977
+ // A stylesheet or source file belongs to the package whose manifest is
1978
+ // nearest it; entries of every other catalog package are foreign to it.
1979
+ const packages = new Map();
1980
+ const compilerOptions = new Map();
1981
+ const packageOf = async (directory) => {
1982
+ if (!packages.has(directory))
1983
+ packages.set(
1984
+ directory,
1985
+ (async () => {
1986
+ try {
1987
+ const manifest = JSON.parse(
1988
+ await readFile(resolve(directory, 'package.json'), 'utf8'),
1989
+ );
1990
+ if (typeof manifest.name === 'string')
1991
+ return { name: manifest.name, directory };
1992
+ } catch {
1993
+ // No readable manifest here; keep walking up.
1994
+ }
1995
+ const parent = dirname(directory);
1996
+ const inside = relative(root, parent);
1997
+ if (
1998
+ parent === directory ||
1999
+ inside === '..' ||
2000
+ inside.startsWith('../')
2001
+ )
2002
+ return undefined;
2003
+ return packageOf(parent);
2004
+ })(),
2005
+ );
2006
+ return packages.get(directory);
2007
+ };
2008
+ const styledBlocksByFile = new Map();
2009
+ const styledBlocksIn = (style) => {
2010
+ if (!styledBlocksByFile.has(style)) {
2011
+ const blocks = new Set();
2012
+ const css = contents.get(style);
2013
+ if (css)
2014
+ try {
2015
+ postcss.parse(css, { from: style }).walkRules((rule) => {
2016
+ for (const name of subjectClasses(rule.selector))
2017
+ blocks.add(bemBlock(name));
2018
+ });
2019
+ } catch {
2020
+ // Malformed CSS is reported by the normal inspection pass.
2021
+ }
2022
+ styledBlocksByFile.set(style, blocks);
2023
+ }
2024
+ return styledBlocksByFile.get(style);
2025
+ };
2026
+ const foreignTo = async (file, facts) => {
2027
+ const own = await packageOf(dirname(file));
2028
+ const source = own && relative(own.directory, file).replaceAll('\\', '/');
2029
+ const ownedStyles = new Set(imports.get(file) ?? []);
2030
+ if (ownership === 'component' && !file.endsWith('.css'))
2031
+ for (const entry of facts.entries) {
2032
+ if (
2033
+ entry.package !== own?.name ||
2034
+ !entry.componentSource ||
2035
+ !entry.catalogDirectory ||
2036
+ resolve(entry.catalogDirectory, entry.componentSource) !== file
2037
+ )
2038
+ continue;
2039
+ for (const style of entry.styleSources ?? [])
2040
+ ownedStyles.add(resolve(entry.catalogDirectory, style));
2041
+ }
2042
+ return (entry, className) => {
2043
+ if (entry.package !== own?.name) return true;
2044
+ if (ownership !== 'component') return false;
2045
+ if (entry.ownershipGroupSources) {
2046
+ const relativeFile = relative(entry.catalogDirectory, file).replaceAll(
2047
+ '\\',
2048
+ '/',
2049
+ );
2050
+ if (file.endsWith('.css'))
2051
+ return !entry.styleSources?.includes(relativeFile);
2052
+ return !entry.ownershipGroupSources.some((path) =>
2053
+ path.endsWith('/')
2054
+ ? relativeFile.startsWith(path)
2055
+ : relativeFile === path,
2056
+ );
2057
+ }
2058
+ if (
2059
+ !file.endsWith('.css') &&
2060
+ className &&
2061
+ entry.catalogDirectory &&
2062
+ entry.styleSources?.some((style) => {
2063
+ const path = resolve(entry.catalogDirectory, style);
2064
+ return (
2065
+ ownedStyles.has(path) &&
2066
+ styledBlocksIn(path).has(bemBlock(className))
2067
+ );
2068
+ })
2069
+ )
2070
+ return false;
2071
+ const ownedSources = file.endsWith('.css')
2072
+ ? entry.styleSources
2073
+ : [entry.componentSource];
2074
+ return !ownedSources?.includes(source);
2075
+ };
2076
+ };
509
2077
  const styleFacts = new Map();
510
2078
  const recordDiagnostics = (items, context) => {
511
2079
  for (const item of items) {
@@ -538,16 +2106,56 @@ export async function analyzeUiProject({
538
2106
  for (const file of files.filter((item) => item.endsWith('.css'))) {
539
2107
  const context = contexts.get(file);
540
2108
  const fileFacts = new Map();
541
- await inspectCss(file, contents.get(file), context.facts, [], fileFacts);
2109
+ await inspectCss(
2110
+ file,
2111
+ contents.get(file),
2112
+ context.facts,
2113
+ [],
2114
+ fileFacts,
2115
+ adoption,
2116
+ );
542
2117
  styleFacts.set(file, fileFacts);
543
2118
  }
544
2119
  const styleConsumers = new Map(
545
2120
  [...styleFacts.keys()].map((file) => [file, new Set()]),
546
2121
  );
2122
+ // KUI-L018 accepts an on-loud from another stylesheet the same entry loads:
2123
+ // every stylesheet reachable from one file (a source module's CSS imports,
2124
+ // or a stylesheet with its @imports) is loaded together.
2125
+ const onLoudByStyle = new Map();
2126
+ for (const style of styleFacts.keys()) {
2127
+ let parsed;
2128
+ try {
2129
+ parsed = postcss.parse(contents.get(style), { from: style });
2130
+ } catch {
2131
+ continue;
2132
+ }
2133
+ onLoudByStyle.set(
2134
+ style,
2135
+ collectLoudDeclarations(style, parsed).filter(
2136
+ ({ kind }) => kind === 'on-loud',
2137
+ ),
2138
+ );
2139
+ }
2140
+ const coLoadedStyles = new Map(
2141
+ [...styleFacts.keys()].map((style) => [style, new Set()]),
2142
+ );
2143
+ for (const file of files) {
2144
+ const loaded = new Set(reachableStyleFiles(file, imports, styleFacts));
2145
+ if (styleFacts.has(file)) loaded.add(file);
2146
+ for (const style of loaded)
2147
+ for (const sibling of loaded)
2148
+ if (sibling !== style) coLoadedStyles.get(style).add(sibling);
2149
+ }
2150
+ const siblingOnLoud = (style) =>
2151
+ [...coLoadedStyles.get(style)]
2152
+ .sort()
2153
+ .flatMap((sibling) => onLoudByStyle.get(sibling) ?? []);
547
2154
  for (const file of files.filter((item) =>
548
2155
  sourceExtensions.has(extname(item)),
549
2156
  )) {
550
2157
  const context = contexts.get(file);
2158
+ const own = await packageOf(dirname(file));
551
2159
  for (const style of reachableStyleFiles(file, imports, styleFacts))
552
2160
  styleConsumers.get(style).add(context);
553
2161
  const fileDiagnostics = [];
@@ -557,6 +2165,11 @@ export async function analyzeUiProject({
557
2165
  context.facts,
558
2166
  reachableStyleFacts(file, imports, styleFacts),
559
2167
  fileDiagnostics,
2168
+ adoption,
2169
+ await foreignTo(file, context.facts),
2170
+ own?.directory,
2171
+ compilerOptionsFor(file, root, compilerOptions),
2172
+ ownership === 'component',
560
2173
  );
561
2174
  recordDiagnostics(fileDiagnostics, context);
562
2175
  }
@@ -566,12 +2179,29 @@ export async function analyzeUiProject({
566
2179
  : new Set([contexts.get(file)]);
567
2180
  for (const context of applicableContexts) {
568
2181
  const fileDiagnostics = [];
2182
+ const own = await packageOf(dirname(file));
569
2183
  await inspectCss(
570
2184
  file,
571
2185
  contents.get(file),
572
2186
  context.facts,
573
2187
  fileDiagnostics,
574
2188
  new Map(),
2189
+ adoption,
2190
+ siblingOnLoud(file),
2191
+ await foreignTo(file, context.facts),
2192
+ ownership === 'component'
2193
+ ? {
2194
+ ...jsxOwnershipEvidence(
2195
+ file,
2196
+ context.facts,
2197
+ contents,
2198
+ root,
2199
+ compilerOptions,
2200
+ ),
2201
+ ownPackage: own?.name,
2202
+ }
2203
+ : undefined,
2204
+ ownershipContext,
575
2205
  );
576
2206
  recordDiagnostics(fileDiagnostics, context);
577
2207
  }
@@ -654,6 +2284,14 @@ export async function analyzeUiProject({
654
2284
  ...item.location,
655
2285
  file: portablePath(item.location.file),
656
2286
  },
2287
+ ...(item.evidence?.stylesheet
2288
+ ? {
2289
+ evidence: {
2290
+ ...item.evidence,
2291
+ stylesheet: portablePath(item.evidence.stylesheet),
2292
+ },
2293
+ }
2294
+ : {}),
657
2295
  })),
658
2296
  summary: {
659
2297
  errors: