basalt-ui 1.28.0 → 1.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (457) hide show
  1. package/AGENTS.md +12 -19
  2. package/CHANGELOG.md +23 -0
  3. package/MIGRATING.md +171 -7
  4. package/README.md +66 -105
  5. package/agent/rules/basalt-batteries.md +60 -183
  6. package/agent/rules/basalt-charts.md +56 -75
  7. package/agent/rules/basalt-controls.md +38 -69
  8. package/agent/rules/basalt-mantine.md +58 -103
  9. package/agent/rules/basalt-state.md +42 -60
  10. package/agent/rules/basalt-tokens.md +46 -67
  11. package/agent/skills/basalt-app/SKILL.md +2 -0
  12. package/agent/templates/CLAUDE-block.md.tpl +14 -14
  13. package/agent/templates/query-client.ts.tpl +1 -1
  14. package/configs/oxlint-plugin.js +84 -254
  15. package/configs/oxlint-plugin.test.ts +23 -266
  16. package/configs/oxlint.json +0 -2
  17. package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
  18. package/dist/agent/ai-sdk-transport.js +7 -4
  19. package/dist/agent/coalesce.js +1 -1
  20. package/dist/agent/merge.js +1 -1
  21. package/dist/agent/transport.d.ts +13 -2
  22. package/dist/agent/transport.d.ts.map +1 -1
  23. package/dist/agent/use-agent-thread-runs.d.ts +12 -1
  24. package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
  25. package/dist/agent/use-agent-thread-runs.js +20 -6
  26. package/dist/agent-chat/index.d.ts +1 -1
  27. package/dist/agent-chat/index.d.ts.map +1 -1
  28. package/dist/agent-chat/relative-time.js +1 -1
  29. package/dist/agent-chat/thread-detail-panel.js +1 -1
  30. package/dist/agent-chat/thread-feed-row.d.ts +32 -7
  31. package/dist/agent-chat/thread-feed-row.d.ts.map +1 -1
  32. package/dist/agent-chat/thread-feed-row.js +121 -86
  33. package/dist/agent-chat/thread-feed.d.ts +11 -1
  34. package/dist/agent-chat/thread-feed.d.ts.map +1 -1
  35. package/dist/agent-chat/thread-feed.js +10 -6
  36. package/dist/agent-chat/thread-message.js +1 -1
  37. package/dist/agent-chat/virtualize.d.ts +39 -0
  38. package/dist/agent-chat/virtualize.d.ts.map +1 -1
  39. package/dist/agent-chat/virtualize.js +15 -0
  40. package/dist/charts/index.d.ts +3 -3
  41. package/dist/charts/index.d.ts.map +1 -1
  42. package/dist/charts/index.js +8 -4
  43. package/dist/charts/kinds/BandStrip.js +1 -1
  44. package/dist/charts/kinds/Bars.d.ts +1 -3
  45. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  46. package/dist/charts/kinds/Bars.js +1 -1
  47. package/dist/charts/kinds/DualPanel.d.ts +3 -1
  48. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  49. package/dist/charts/kinds/DualPanel.js +1 -1
  50. package/dist/charts/kinds/StackedArea.js +1 -1
  51. package/dist/charts/kinds/ZonedLine.d.ts +1 -3
  52. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  53. package/dist/charts/primitives/Axes.d.ts +21 -2
  54. package/dist/charts/primitives/Axes.d.ts.map +1 -1
  55. package/dist/charts/primitives/Axes.js +34 -2
  56. package/dist/charts/primitives/ChartCard.d.ts +20 -4
  57. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  58. package/dist/charts/primitives/ChartCard.js +62 -33
  59. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  60. package/dist/charts/primitives/ChartFrame.js +8 -1
  61. package/dist/charts/primitives/ChartPending.d.ts +12 -6
  62. package/dist/charts/primitives/ChartPending.d.ts.map +1 -1
  63. package/dist/charts/primitives/ChartPending.js +1 -1
  64. package/dist/charts/utils/format.d.ts +5 -92
  65. package/dist/charts/utils/format.d.ts.map +1 -1
  66. package/dist/charts/utils/format.js +10 -80
  67. package/dist/cli/audit-allows.d.ts +24 -0
  68. package/dist/cli/audit-allows.d.ts.map +1 -0
  69. package/dist/cli/audit-allows.js +240 -0
  70. package/dist/cli/check-theme.d.ts +10 -0
  71. package/dist/cli/check-theme.d.ts.map +1 -0
  72. package/dist/cli/check-theme.js +139 -0
  73. package/dist/cli/doctor.d.ts +47 -0
  74. package/dist/cli/doctor.d.ts.map +1 -0
  75. package/dist/cli/doctor.js +197 -0
  76. package/dist/cli/guard-hook.d.ts +9 -0
  77. package/dist/cli/guard-hook.d.ts.map +1 -0
  78. package/dist/cli/guard-hook.js +93 -0
  79. package/dist/cli/help.d.ts +9 -0
  80. package/dist/cli/help.d.ts.map +1 -0
  81. package/dist/cli/help.js +53 -0
  82. package/dist/cli/index.d.ts +360 -270
  83. package/dist/cli/index.d.ts.map +1 -1
  84. package/dist/cli/index.js +85 -1901
  85. package/dist/cli/init.d.ts +4 -0
  86. package/dist/cli/init.d.ts.map +1 -0
  87. package/dist/cli/init.js +165 -0
  88. package/dist/cli/sync.d.ts +43 -0
  89. package/dist/cli/sync.d.ts.map +1 -0
  90. package/dist/cli/sync.js +206 -0
  91. package/dist/cli/tokens-css.d.ts +51 -0
  92. package/dist/cli/tokens-css.d.ts.map +1 -0
  93. package/dist/cli/tokens-css.js +165 -0
  94. package/dist/commands/define-commands.js +1 -1
  95. package/dist/commands/define-overlays.js +1 -1
  96. package/dist/commands/index.d.ts +2 -1
  97. package/dist/commands/index.d.ts.map +1 -1
  98. package/dist/commands/index.js +6 -0
  99. package/dist/commands/overlays-mount.d.ts +7 -9
  100. package/dist/commands/overlays-mount.d.ts.map +1 -1
  101. package/dist/commands/shell-bridge.d.ts +57 -0
  102. package/dist/commands/shell-bridge.d.ts.map +1 -0
  103. package/dist/commands/shell-bridge.js +32 -0
  104. package/dist/common/errors.d.ts +35 -0
  105. package/dist/common/errors.d.ts.map +1 -1
  106. package/dist/common/errors.js +29 -1
  107. package/dist/common/index.d.ts +2 -0
  108. package/dist/common/index.d.ts.map +1 -1
  109. package/dist/common/index.js +6 -0
  110. package/dist/common/is-dev.d.ts.map +1 -0
  111. package/dist/{motion/index.d.ts → common/motion.d.ts} +1 -1
  112. package/dist/common/motion.d.ts.map +1 -0
  113. package/dist/common/props.d.ts +1 -1
  114. package/dist/common/props.d.ts.map +1 -1
  115. package/dist/common/validate.js +1 -1
  116. package/dist/content/markdown.d.ts +3 -4
  117. package/dist/content/markdown.d.ts.map +1 -1
  118. package/dist/content/markdown.js +1 -4
  119. package/dist/controls/actions.d.ts +1 -1
  120. package/dist/controls/actions.d.ts.map +1 -1
  121. package/dist/controls/panel-row.js +1 -1
  122. package/dist/controls/range-filter.js +1 -1
  123. package/dist/dashboard/delta-badge.d.ts +18 -10
  124. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  125. package/dist/dashboard/delta-badge.js +33 -1
  126. package/dist/dashboard/empty-state.d.ts +1 -4
  127. package/dist/dashboard/empty-state.d.ts.map +1 -1
  128. package/dist/dashboard/empty-state.js +1 -2
  129. package/dist/dashboard/index.d.ts +4 -2
  130. package/dist/dashboard/index.d.ts.map +1 -1
  131. package/dist/dashboard/index.js +7 -1
  132. package/dist/dashboard/query-state.d.ts +4 -13
  133. package/dist/dashboard/query-state.d.ts.map +1 -1
  134. package/dist/dashboard/query-state.js +4 -7
  135. package/dist/{section → dashboard}/section.d.ts +2 -2
  136. package/dist/{section → dashboard}/section.d.ts.map +1 -1
  137. package/dist/{section → dashboard}/section.js +2 -2
  138. package/dist/dashboard/settings-section.d.ts +1 -1
  139. package/dist/dashboard/settings-section.d.ts.map +1 -1
  140. package/dist/dashboard/settings-section.js +1 -1
  141. package/dist/dashboard/stat-card.d.ts +1 -1
  142. package/dist/dashboard/stat-card.d.ts.map +1 -1
  143. package/dist/dashboard/stat-card.js +1 -1
  144. package/dist/dashboard/widget-header.d.ts.map +1 -0
  145. package/dist/data/data-table.js +2 -2
  146. package/dist/data/table.d.ts +1 -1
  147. package/dist/data/table.d.ts.map +1 -1
  148. package/dist/data/table.js +2 -16
  149. package/dist/data/virtual.d.ts +3 -3
  150. package/dist/dev-dock.d.ts +26 -0
  151. package/dist/dev-dock.d.ts.map +1 -0
  152. package/dist/dev-dock.js +85 -0
  153. package/dist/format/date.d.ts +62 -0
  154. package/dist/format/date.d.ts.map +1 -0
  155. package/dist/format/date.js +106 -0
  156. package/dist/format/index.d.ts +39 -0
  157. package/dist/format/index.d.ts.map +1 -0
  158. package/dist/format/index.js +54 -0
  159. package/dist/format/measure.d.ts +15 -0
  160. package/dist/format/measure.d.ts.map +1 -0
  161. package/dist/format/measure.js +16 -0
  162. package/dist/format/number.d.ts +69 -0
  163. package/dist/format/number.d.ts.map +1 -0
  164. package/dist/format/number.js +62 -0
  165. package/dist/format/shared.d.ts +27 -0
  166. package/dist/format/shared.d.ts.map +1 -0
  167. package/dist/format/shared.js +29 -0
  168. package/dist/forms/field.d.ts +4 -20
  169. package/dist/forms/field.d.ts.map +1 -1
  170. package/dist/forms/field.js +0 -4
  171. package/dist/forms/form-layout.d.ts +1 -1
  172. package/dist/forms/form-layout.d.ts.map +1 -1
  173. package/dist/forms/form-layout.js +1 -1
  174. package/dist/forms/index.d.ts +1 -1
  175. package/dist/forms/index.d.ts.map +1 -1
  176. package/dist/forms/index.js +1 -2
  177. package/dist/forms/use-form-submit.d.ts +1 -1
  178. package/dist/forms/use-form-submit.js +1 -1
  179. package/dist/guard/index.d.ts +2 -9
  180. package/dist/guard/index.d.ts.map +1 -1
  181. package/dist/guard/index.js +53 -32
  182. package/dist/guard/types.d.ts +8 -10
  183. package/dist/guard/types.d.ts.map +1 -1
  184. package/dist/index.d.ts +16 -11
  185. package/dist/index.d.ts.map +1 -1
  186. package/dist/index.js +23 -29
  187. package/dist/notifications/index.d.ts +5 -5
  188. package/dist/notifications/index.d.ts.map +1 -1
  189. package/dist/notifications/index.js +0 -2
  190. package/dist/notifications/mount-guard.d.ts.map +1 -1
  191. package/dist/notifications/mount-guard.js +2 -2
  192. package/dist/provider/connectivity-indicator.d.ts.map +1 -0
  193. package/dist/{connectivity → provider}/connectivity-provider.d.ts +1 -1
  194. package/dist/provider/connectivity-provider.d.ts.map +1 -0
  195. package/dist/{connectivity/types.d.ts → provider/connectivity-types.d.ts} +1 -1
  196. package/dist/provider/connectivity-types.d.ts.map +1 -0
  197. package/dist/provider/error-fallback.module.css +21 -0
  198. package/dist/provider/error-fallback.module.css.d.ts +9 -0
  199. package/dist/provider/index.d.ts +20 -36
  200. package/dist/provider/index.d.ts.map +1 -1
  201. package/dist/provider/index.js +42 -32
  202. package/dist/provider/lab-theme.js +1 -1
  203. package/dist/provider/use-connectivity.d.ts +3 -0
  204. package/dist/provider/use-connectivity.d.ts.map +1 -0
  205. package/dist/query-client.d.ts +67 -0
  206. package/dist/query-client.d.ts.map +1 -0
  207. package/dist/query-client.js +35 -0
  208. package/dist/{query/devtools.d.ts → query-devtools.d.ts} +2 -2
  209. package/dist/query-devtools.d.ts.map +1 -0
  210. package/dist/register.d.ts +17 -3
  211. package/dist/register.d.ts.map +1 -1
  212. package/dist/router-tanstack/index.d.ts +4 -7
  213. package/dist/router-tanstack/index.d.ts.map +1 -1
  214. package/dist/router-tanstack/index.js +1 -9
  215. package/dist/router-tanstack/nav.d.ts +1 -1
  216. package/dist/router-tanstack/nav.d.ts.map +1 -1
  217. package/dist/router-tanstack/search-store.d.ts +3 -6
  218. package/dist/router-tanstack/search-store.d.ts.map +1 -1
  219. package/dist/router-tanstack/use-nav.d.ts +1 -1
  220. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  221. package/dist/shell/app-breadcrumbs.d.ts +1 -1
  222. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  223. package/dist/shell/app-main.module.css +11 -0
  224. package/dist/shell/app-mobile-nav.d.ts +1 -1
  225. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  226. package/dist/shell/app-mobile-nav.js +24 -2
  227. package/dist/shell/app-sidebar.d.ts +1 -1
  228. package/dist/shell/app-sidebar.d.ts.map +1 -1
  229. package/dist/shell/app-sidebar.js +31 -2
  230. package/dist/shell/app-sidebar.module.css +10 -0
  231. package/dist/shell/index.d.ts +10 -4
  232. package/dist/shell/index.d.ts.map +1 -1
  233. package/dist/shell/index.js +13 -17
  234. package/dist/shell/mobile-nav-model.d.ts +1 -1
  235. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  236. package/dist/{nav/types.d.ts → shell/nav-types.d.ts} +1 -1
  237. package/dist/shell/nav-types.d.ts.map +1 -0
  238. package/dist/shell/page-aside.d.ts.map +1 -1
  239. package/dist/shell/page-aside.js +2 -29
  240. package/dist/shell/page-title.d.ts +14 -0
  241. package/dist/shell/page-title.d.ts.map +1 -0
  242. package/dist/shell/page-title.js +25 -0
  243. package/dist/shell/page-title.module.css +33 -0
  244. package/dist/shell/page-title.module.css.d.ts +11 -0
  245. package/dist/shell/sidebar-block-model.d.ts +1 -1
  246. package/dist/shell/sidebar-block-model.d.ts.map +1 -1
  247. package/dist/shell/sidebar-blocks.d.ts +1 -1
  248. package/dist/shell/sidebar-blocks.d.ts.map +1 -1
  249. package/dist/shell/use-breakpoint.d.ts +11 -0
  250. package/dist/shell/use-breakpoint.d.ts.map +1 -0
  251. package/dist/shell/use-breakpoint.js +33 -0
  252. package/dist/state/fields.d.ts +3 -19
  253. package/dist/state/fields.d.ts.map +1 -1
  254. package/dist/state/fields.js +6 -16
  255. package/dist/styles.css +36 -0
  256. package/dist/surfaces.d.ts +18 -41
  257. package/dist/surfaces.d.ts.map +1 -1
  258. package/dist/surfaces.js +48 -60
  259. package/dist/theme/index.d.ts.map +1 -1
  260. package/dist/{theme-toggle/index.d.ts → theme/theme-toggle.d.ts} +1 -1
  261. package/dist/theme/theme-toggle.d.ts.map +1 -0
  262. package/dist/{theme-toggle/index.js → theme/theme-toggle.js} +1 -1
  263. package/dist/tokens/index.d.ts +1 -0
  264. package/dist/tokens/index.d.ts.map +1 -1
  265. package/dist/tokens/index.js +10 -0
  266. package/dist/tokens/palette.d.ts +12 -0
  267. package/dist/tokens/palette.d.ts.map +1 -1
  268. package/dist/tokens/palette.js +13 -1
  269. package/dist/tokens.css +1 -0
  270. package/llms.txt +9 -24
  271. package/package.json +12 -16
  272. package/src/agent/ai-sdk-transport.ts +11 -4
  273. package/src/agent/coalesce.ts +1 -1
  274. package/src/agent/merge.ts +1 -1
  275. package/src/agent/transport.ts +15 -2
  276. package/src/agent/use-agent-thread-runs.ts +65 -15
  277. package/src/agent-chat/index.ts +6 -4
  278. package/src/agent-chat/relative-time.ts +1 -1
  279. package/src/agent-chat/thread-detail-panel.tsx +1 -1
  280. package/src/agent-chat/thread-feed-row.tsx +105 -30
  281. package/src/agent-chat/thread-feed.tsx +51 -25
  282. package/src/agent-chat/thread-message.tsx +1 -1
  283. package/src/agent-chat/virtualize.ts +48 -0
  284. package/src/charts/index.ts +7 -10
  285. package/src/charts/kinds/BandStrip.tsx +1 -1
  286. package/src/charts/kinds/Bars.tsx +2 -5
  287. package/src/charts/kinds/DualPanel.tsx +4 -3
  288. package/src/charts/kinds/StackedArea.tsx +1 -1
  289. package/src/charts/kinds/ZonedLine.tsx +1 -4
  290. package/src/charts/primitives/Axes.tsx +49 -0
  291. package/src/charts/primitives/ChartCard.tsx +49 -6
  292. package/src/charts/primitives/ChartFrame.tsx +5 -1
  293. package/src/charts/primitives/ChartPending.tsx +14 -8
  294. package/src/charts/utils/format.ts +17 -186
  295. package/src/cli/audit-allows.ts +371 -0
  296. package/src/cli/check-theme.ts +197 -0
  297. package/src/cli/doctor.ts +331 -0
  298. package/src/cli/guard-hook.ts +115 -0
  299. package/src/cli/help.ts +101 -0
  300. package/src/cli/index.ts +269 -3439
  301. package/src/cli/init.ts +247 -0
  302. package/src/cli/sync.ts +324 -0
  303. package/src/cli/tokens-css.ts +252 -0
  304. package/src/commands/define-commands.ts +1 -1
  305. package/src/commands/define-overlays.ts +1 -1
  306. package/src/commands/index.ts +10 -1
  307. package/src/commands/overlays-mount.tsx +8 -10
  308. package/src/commands/shell-bridge.ts +86 -0
  309. package/src/common/errors.ts +70 -0
  310. package/src/common/index.ts +2 -0
  311. package/src/common/props.ts +1 -1
  312. package/src/common/validate.ts +1 -1
  313. package/src/content/markdown.tsx +4 -8
  314. package/src/controls/actions.tsx +1 -1
  315. package/src/controls/panel-row.tsx +1 -1
  316. package/src/controls/range-filter.tsx +1 -1
  317. package/src/dashboard/delta-badge.tsx +74 -7
  318. package/src/dashboard/empty-state.tsx +2 -6
  319. package/src/dashboard/index.ts +11 -2
  320. package/src/dashboard/query-state.tsx +5 -18
  321. package/src/{section → dashboard}/section.tsx +4 -4
  322. package/src/dashboard/settings-section.tsx +2 -2
  323. package/src/dashboard/stat-card.tsx +2 -2
  324. package/src/data/data-table.tsx +3 -3
  325. package/src/data/table.ts +6 -25
  326. package/src/data/virtual-list.tsx +1 -1
  327. package/src/data/virtual.ts +3 -3
  328. package/src/dev-dock.tsx +162 -0
  329. package/src/format/date.ts +184 -0
  330. package/src/format/index.ts +69 -0
  331. package/src/format/measure.ts +31 -0
  332. package/src/format/number.ts +129 -0
  333. package/src/format/shared.ts +63 -0
  334. package/src/forms/field.ts +4 -24
  335. package/src/forms/form-layout.tsx +2 -2
  336. package/src/forms/index.ts +1 -2
  337. package/src/forms/use-form-submit.ts +2 -2
  338. package/src/guard/index.ts +80 -69
  339. package/src/guard/types.ts +7 -10
  340. package/src/index.ts +38 -29
  341. package/src/notifications/index.ts +5 -7
  342. package/src/notifications/mount-guard.ts +7 -9
  343. package/src/{connectivity → provider}/connectivity-indicator.tsx +1 -1
  344. package/src/{connectivity → provider}/connectivity-provider.tsx +1 -1
  345. package/src/provider/error-fallback.module.css +21 -0
  346. package/src/provider/error-fallback.module.css.d.ts +9 -0
  347. package/src/provider/index.tsx +71 -71
  348. package/src/provider/lab-theme.ts +1 -1
  349. package/src/{connectivity → provider}/use-connectivity.ts +3 -3
  350. package/src/query-client.ts +105 -0
  351. package/src/{query/devtools.tsx → query-devtools.tsx} +5 -4
  352. package/src/register.ts +20 -4
  353. package/src/router-tanstack/index.ts +4 -15
  354. package/src/router-tanstack/nav.ts +1 -1
  355. package/src/router-tanstack/search-store.ts +3 -4
  356. package/src/router-tanstack/use-nav.ts +1 -1
  357. package/src/shell/app-breadcrumbs.tsx +1 -1
  358. package/src/shell/app-main.module.css +11 -0
  359. package/src/shell/app-mobile-nav.tsx +28 -1
  360. package/src/shell/app-sidebar.module.css +10 -0
  361. package/src/shell/app-sidebar.tsx +31 -2
  362. package/src/shell/index.tsx +23 -28
  363. package/src/shell/mobile-nav-model.ts +1 -1
  364. package/src/shell/page-aside.tsx +2 -52
  365. package/src/shell/page-title.module.css +33 -0
  366. package/src/shell/page-title.module.css.d.ts +11 -0
  367. package/src/shell/page-title.tsx +57 -0
  368. package/src/shell/sidebar-block-model.ts +1 -1
  369. package/src/shell/sidebar-blocks.tsx +1 -1
  370. package/src/shell/use-breakpoint.ts +73 -0
  371. package/src/state/fields.ts +7 -43
  372. package/src/styles.css +36 -0
  373. package/src/surfaces.ts +47 -61
  374. package/src/theme/index.ts +7 -0
  375. package/src/{theme-toggle/index.tsx → theme/theme-toggle.tsx} +1 -1
  376. package/src/tokens/index.ts +10 -0
  377. package/src/tokens/palette.ts +12 -0
  378. package/dist/connectivity/connectivity-indicator.d.ts.map +0 -1
  379. package/dist/connectivity/connectivity-provider.d.ts.map +0 -1
  380. package/dist/connectivity/index.d.ts +0 -14
  381. package/dist/connectivity/index.d.ts.map +0 -1
  382. package/dist/connectivity/index.js +0 -8
  383. package/dist/connectivity/types.d.ts.map +0 -1
  384. package/dist/connectivity/use-connectivity.d.ts +0 -3
  385. package/dist/connectivity/use-connectivity.d.ts.map +0 -1
  386. package/dist/data/index.d.ts +0 -38
  387. package/dist/data/index.d.ts.map +0 -1
  388. package/dist/data/index.js +0 -24
  389. package/dist/motion/index.d.ts.map +0 -1
  390. package/dist/nav/types.d.ts.map +0 -1
  391. package/dist/notifications/overlay.d.ts +0 -20
  392. package/dist/notifications/overlay.d.ts.map +0 -1
  393. package/dist/notifications/overlay.js +0 -14
  394. package/dist/query/devtools.d.ts.map +0 -1
  395. package/dist/query/error-message.d.ts +0 -41
  396. package/dist/query/error-message.d.ts.map +0 -1
  397. package/dist/query/error-message.js +0 -30
  398. package/dist/query/index.d.ts +0 -47
  399. package/dist/query/index.d.ts.map +0 -1
  400. package/dist/query/index.js +0 -56
  401. package/dist/router-tanstack/field.d.ts +0 -14
  402. package/dist/router-tanstack/field.d.ts.map +0 -1
  403. package/dist/router-tanstack/field.js +0 -4
  404. package/dist/router-tanstack/multi-search-param-store.d.ts +0 -43
  405. package/dist/router-tanstack/multi-search-param-store.d.ts.map +0 -1
  406. package/dist/router-tanstack/multi-search-param-store.js +0 -30
  407. package/dist/router-tanstack/search-param-store.d.ts +0 -42
  408. package/dist/router-tanstack/search-param-store.d.ts.map +0 -1
  409. package/dist/router-tanstack/search-param-store.js +0 -25
  410. package/dist/section/index.d.ts +0 -2
  411. package/dist/section/index.d.ts.map +0 -1
  412. package/dist/section/index.js +0 -4
  413. package/dist/theme-toggle/index.d.ts.map +0 -1
  414. package/dist/utils/is-dev.d.ts.map +0 -1
  415. package/dist/widget-header/delta-badge.d.ts +0 -19
  416. package/dist/widget-header/delta-badge.d.ts.map +0 -1
  417. package/dist/widget-header/delta-badge.js +0 -36
  418. package/dist/widget-header/index.d.ts +0 -3
  419. package/dist/widget-header/index.d.ts.map +0 -1
  420. package/dist/widget-header/index.js +0 -8
  421. package/dist/widget-header/widget-header.d.ts.map +0 -1
  422. package/src/connectivity/index.ts +0 -19
  423. package/src/data/index.ts +0 -69
  424. package/src/notifications/overlay.tsx +0 -66
  425. package/src/query/error-message.ts +0 -70
  426. package/src/query/index.ts +0 -97
  427. package/src/router-tanstack/field.ts +0 -36
  428. package/src/router-tanstack/multi-search-param-store.ts +0 -106
  429. package/src/router-tanstack/search-param-store.ts +0 -100
  430. package/src/section/index.ts +0 -1
  431. package/src/widget-header/delta-badge.tsx +0 -77
  432. package/src/widget-header/index.ts +0 -10
  433. /package/dist/{utils → common}/is-dev.d.ts +0 -0
  434. /package/dist/{utils → common}/is-dev.js +0 -0
  435. /package/dist/{motion/index.js → common/motion.js} +0 -0
  436. /package/dist/{section → dashboard}/section.module.css +0 -0
  437. /package/dist/{section → dashboard}/section.module.css.d.ts +0 -0
  438. /package/dist/{widget-header → dashboard}/widget-header.d.ts +0 -0
  439. /package/dist/{widget-header → dashboard}/widget-header.js +0 -0
  440. /package/dist/{widget-header → dashboard}/widget-header.module.css +0 -0
  441. /package/dist/{widget-header → dashboard}/widget-header.module.css.d.ts +0 -0
  442. /package/dist/{connectivity → provider}/connectivity-indicator.d.ts +0 -0
  443. /package/dist/{connectivity → provider}/connectivity-indicator.js +0 -0
  444. /package/dist/{connectivity → provider}/connectivity-provider.js +0 -0
  445. /package/dist/{connectivity/types.js → provider/connectivity-types.js} +0 -0
  446. /package/dist/{connectivity → provider}/use-connectivity.js +0 -0
  447. /package/dist/{query/devtools.js → query-devtools.js} +0 -0
  448. /package/dist/{nav/types.js → shell/nav-types.js} +0 -0
  449. /package/src/{utils → common}/is-dev.ts +0 -0
  450. /package/src/{motion/index.ts → common/motion.ts} +0 -0
  451. /package/src/{section → dashboard}/section.module.css +0 -0
  452. /package/src/{section → dashboard}/section.module.css.d.ts +0 -0
  453. /package/src/{widget-header → dashboard}/widget-header.module.css +0 -0
  454. /package/src/{widget-header → dashboard}/widget-header.module.css.d.ts +0 -0
  455. /package/src/{widget-header → dashboard}/widget-header.tsx +0 -0
  456. /package/src/{connectivity/types.ts → provider/connectivity-types.ts} +0 -0
  457. /package/src/{nav/types.ts → shell/nav-types.ts} +0 -0
@@ -0,0 +1,24 @@
1
+ import type { GuardConfig } from '../guard/index.js';
2
+ import type { BasaltConfig } from './index.js';
3
+ /**
4
+ * `check-theme --audit-allows` — list every active waiver and prove, per waiver, whether it still
5
+ * suppresses anything.
6
+ *
7
+ * The accountability release shipped `theme-allow-unscoped`, which reports a waiver that names no
8
+ * rule — but a waiver that is perfectly well written and covers a finding that no longer exists is
9
+ * invisible to it, and to everything else. Two consumers found five of those by hand, by editing
10
+ * the token out and re-running the guard. This is that method, automated: for each annotation the
11
+ * file is re-checked with THAT occurrence neutralized, and the findings that appear are exactly
12
+ * what it suppresses. Nothing is inferred from the annotation's text, so the audit cannot disagree
13
+ * with the scan — it IS the scan, run twice.
14
+ *
15
+ * `basalt.exemptRules` entries are audited the same way, one pattern at a time. That also answers
16
+ * the question the key could never answer for itself: a pattern that matches no scanned file (a
17
+ * real relative path, say, where the matcher takes whole path SEGMENTS) suppresses nothing and is
18
+ * reported as dead rather than passing silently.
19
+ *
20
+ * Exit 1 when anything is dead, so it can be wired into CI as a gate. A waiver nobody re-checks is
21
+ * how a rule quietly stops applying to the file that needed the exception most.
22
+ */
23
+ export declare function auditAllows(cwd: string, cfg: BasaltConfig, guardCfg: GuardConfig, scanned: string[]): number;
24
+ //# sourceMappingURL=audit-allows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-allows.d.ts","sourceRoot":"","sources":["../../src/cli/audit-allows.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAW,WAAW,EAAa,MAAM,UAAU,CAAA;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAmB,MAAM,SAAS,CAAA;AAqJ5D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,YAAY,EACjB,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,MAAM,EAAE,GAChB,MAAM,CAqLR"}
@@ -0,0 +1,240 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import {
5
+ checkSource,
6
+ findAllowAnnotations,
7
+ neutralizeAllowAnnotation,
8
+ unmatchedExemptPatterns
9
+ } from "../guard/index.js";
10
+ import { exemptRulePaths, exemptRuleReason, resolveRoots } from "./index.js";
11
+ function findingKey(f) {
12
+ return `${f.relPath}:${f.line}:${f.kind}`;
13
+ }
14
+ function resolveToolBin(dir, name) {
15
+ let current = dir;
16
+ for (; ; ) {
17
+ const candidate = resolve(current, "node_modules/.bin", name);
18
+ if (existsSync(candidate)) return candidate;
19
+ const parent = resolve(current, "..");
20
+ if (parent === current) break;
21
+ current = parent;
22
+ }
23
+ const probe = spawnSync(name, ["--version"], { stdio: "ignore", timeout: 1e4 });
24
+ return probe.error === void 0 && probe.status === 0 ? name : null;
25
+ }
26
+ function diagnosticCounts(diagnostics, filename) {
27
+ const counts = /* @__PURE__ */ new Map();
28
+ for (const d of diagnostics) {
29
+ if (d.filename !== filename) continue;
30
+ const code = typeof d.code === "string" ? d.code : "";
31
+ const rule = /^basalt\((.+)\)$/.exec(code)?.[1];
32
+ if (rule === void 0) continue;
33
+ const line = d.labels?.[0]?.span?.line;
34
+ const key = `${typeof line === "number" ? line : 0}:${rule}`;
35
+ counts.set(key, (counts.get(key) ?? 0) + 1);
36
+ }
37
+ return counts;
38
+ }
39
+ function probePluginRules(cwd, guardCfg, probes, sources) {
40
+ if (probes.length === 0) return /* @__PURE__ */ new Map();
41
+ const bin = resolveToolBin(cwd, "oxlint");
42
+ if (bin === null) return null;
43
+ const written = [];
44
+ const probePaths = /* @__PURE__ */ new Map();
45
+ try {
46
+ for (const probe of probes) {
47
+ const probed = neutralizeAllowAnnotation(sources.get(probe.rel) ?? "", probe.line, guardCfg);
48
+ const dot = probe.rel.lastIndexOf(".");
49
+ const rel = dot === -1 ? `${probe.rel}.basalt-audit-${probe.slot}` : `${probe.rel.slice(0, dot)}.basalt-audit-${probe.slot}${probe.rel.slice(dot)}`;
50
+ const abs = resolve(cwd, rel);
51
+ if (existsSync(abs)) continue;
52
+ writeFileSync(abs, probed, "utf8");
53
+ written.push(abs);
54
+ probePaths.set(probe.slot, rel);
55
+ }
56
+ if (probePaths.size === 0) return null;
57
+ const targets = [...new Set(probes.map((p) => p.rel)), ...probePaths.values()];
58
+ const run = spawnSync(bin, ["--format=json", "--no-error-on-unmatched-pattern", ...targets], {
59
+ cwd,
60
+ encoding: "utf8",
61
+ timeout: 12e4,
62
+ stdio: ["ignore", "pipe", "ignore"]
63
+ });
64
+ if (run.error !== void 0 || typeof run.stdout !== "string") return null;
65
+ let diagnostics;
66
+ try {
67
+ diagnostics = JSON.parse(run.stdout).diagnostics ?? [];
68
+ } catch {
69
+ return null;
70
+ }
71
+ const verdicts = /* @__PURE__ */ new Map();
72
+ for (const probe of probes) {
73
+ const probeRel = probePaths.get(probe.slot);
74
+ if (probeRel === void 0) continue;
75
+ const before = diagnosticCounts(diagnostics, probe.rel);
76
+ const after = diagnosticCounts(diagnostics, probeRel);
77
+ const revealed = [];
78
+ for (const [key, count] of after) {
79
+ if (count <= (before.get(key) ?? 0)) continue;
80
+ const [line = "", rule = ""] = key.split(":");
81
+ revealed.push(`${rule}@${line}`);
82
+ }
83
+ verdicts.set(probe.slot, revealed.toSorted());
84
+ }
85
+ return verdicts;
86
+ } finally {
87
+ for (const abs of written) {
88
+ try {
89
+ unlinkSync(abs);
90
+ } catch {
91
+ }
92
+ }
93
+ }
94
+ }
95
+ function auditAllows(cwd, cfg, guardCfg, scanned) {
96
+ const token = guardCfg.allowComment;
97
+ const lines = [`
98
+ basalt-ui check-theme --audit-allows \u2014 ${cwd}
99
+ `];
100
+ let dead = 0;
101
+ let live = 0;
102
+ let outOfReach = 0;
103
+ let unaccountable = 0;
104
+ const waiverLines = [];
105
+ const pluginProbes = [];
106
+ const sources = /* @__PURE__ */ new Map();
107
+ for (const rel of scanned) sources.set(rel, readFileSync(resolve(cwd, rel), "utf8"));
108
+ for (const rel of scanned) {
109
+ const text = sources.get(rel) ?? "";
110
+ if (!text.includes(token)) continue;
111
+ const baseline = checkSource(text, rel, guardCfg);
112
+ const baselineKeys = new Set(
113
+ baseline.filter((f) => f.kind !== "theme-allow-unscoped").map(findingKey)
114
+ );
115
+ const unscopedAt = new Map(
116
+ baseline.filter((f) => f.kind === "theme-allow-unscoped").map((f) => [f.line, f.token])
117
+ );
118
+ for (const site of findAllowAnnotations(text, rel, guardCfg)) {
119
+ const revealed = checkSource(
120
+ neutralizeAllowAnnotation(text, site.line, guardCfg),
121
+ rel,
122
+ guardCfg
123
+ ).filter((f) => f.kind !== "theme-allow-unscoped" && !baselineKeys.has(findingKey(f))).map((f) => `${f.kind}@${f.line}`);
124
+ const note = unscopedAt.get(site.line);
125
+ if (note !== void 0) unaccountable++;
126
+ const label = ` ${rel}:${site.line}`.padEnd(52);
127
+ const suffix = note === void 0 ? "" : ` [${note}]`;
128
+ if (revealed.length > 0) {
129
+ live++;
130
+ waiverLines.push(`${label} suppresses ${[...new Set(revealed)].join(", ")}${suffix}`);
131
+ continue;
132
+ }
133
+ if (site.pluginRules.length > 0 && site.guardKinds.length === 0) {
134
+ pluginProbes.push({
135
+ slot: waiverLines.length,
136
+ rel,
137
+ line: site.line,
138
+ ids: site.pluginRules,
139
+ site: label,
140
+ suffix
141
+ });
142
+ waiverLines.push("");
143
+ continue;
144
+ }
145
+ if (site.guardKinds.length === 0 && site.unknownRules.length > 0) {
146
+ outOfReach++;
147
+ waiverLines.push(
148
+ `${label} scoped to ${site.unknownRules.join(", ")} \u2014 names no guard kind and no plugin rule, so this audit cannot judge it (most likely a typo, which theme-allow-unscoped reports)${suffix}`
149
+ );
150
+ continue;
151
+ }
152
+ dead++;
153
+ waiverLines.push(`${label} SUPPRESSES NOTHING \u2014 dead, delete it${suffix}`);
154
+ }
155
+ }
156
+ const pluginVerdicts = probePluginRules(cwd, guardCfg, pluginProbes, sources);
157
+ for (const probe of pluginProbes) {
158
+ const revealed = pluginVerdicts?.get(probe.slot);
159
+ if (revealed === void 0) {
160
+ outOfReach++;
161
+ waiverLines[probe.slot] = `${probe.site} scoped to ${probe.ids.join(", ")} \u2014 an oxlint plugin rule, and oxlint could not be run here, so this audit cannot judge it${probe.suffix}`;
162
+ continue;
163
+ }
164
+ if (revealed.length > 0) {
165
+ live++;
166
+ waiverLines[probe.slot] = `${probe.site} suppresses ${revealed.join(", ")} (oxlint)${probe.suffix}`;
167
+ continue;
168
+ }
169
+ dead++;
170
+ waiverLines[probe.slot] = `${probe.site} SUPPRESSES NOTHING \u2014 dead, delete it (proved by re-running oxlint over the file with this annotation neutralized)${probe.suffix}`;
171
+ }
172
+ lines.push(`${token} annotations (${waiverLines.length}):`);
173
+ lines.push(...waiverLines.length === 0 ? [" (none)"] : waiverLines);
174
+ const exemptLines = [];
175
+ let exemptCount = 0;
176
+ const declared = Object.entries(cfg.exemptRules ?? {});
177
+ if (declared.length > 0) {
178
+ const baselineKeys = /* @__PURE__ */ new Set();
179
+ for (const rel of scanned) {
180
+ for (const f of checkSource(sources.get(rel) ?? "", rel, guardCfg))
181
+ baselineKeys.add(findingKey(f));
182
+ }
183
+ for (const [kind, entry] of declared) {
184
+ const reason = exemptRuleReason(entry);
185
+ for (const pattern of exemptRulePaths(entry)) {
186
+ exemptCount++;
187
+ const probeCfg = {
188
+ ...guardCfg,
189
+ exemptRules: {
190
+ ...guardCfg.exemptRules,
191
+ [kind]: exemptRulePaths(entry).filter((p) => p !== pattern)
192
+ }
193
+ };
194
+ const revealed = /* @__PURE__ */ new Set();
195
+ for (const rel of scanned) {
196
+ for (const f of checkSource(sources.get(rel) ?? "", rel, probeCfg)) {
197
+ if (!baselineKeys.has(findingKey(f))) revealed.add(f.relPath);
198
+ }
199
+ }
200
+ const site = ` ${kind}: "${pattern}"`.padEnd(52);
201
+ const why = reason === null ? " [no reason recorded]" : ` \u2014 ${reason}`;
202
+ if (revealed.size === 0) {
203
+ dead++;
204
+ const unmatched = unmatchedExemptPatterns(guardCfg, scanned).find(
205
+ (u) => u.kind === kind && u.pattern === pattern
206
+ );
207
+ exemptLines.push(
208
+ `${site} SUPPRESSES NOTHING \u2014 ` + (unmatched === void 0 ? "the files it matches have no such finding; delete it" : unmatched.reason === "unknown-kind" ? "it names no guard kind (a typo, or a renamed kind)" : "it matches no scanned file at all") + why
209
+ );
210
+ } else {
211
+ live++;
212
+ exemptLines.push(
213
+ `${site} suppresses findings in ${[...revealed].toSorted().join(", ")}${why}`
214
+ );
215
+ }
216
+ }
217
+ }
218
+ }
219
+ lines.push(`
220
+ basalt.exemptRules entries (${exemptCount}):`);
221
+ lines.push(...exemptLines.length === 0 ? [" (none)"] : exemptLines);
222
+ lines.push(
223
+ `
224
+ ${live} live, ${dead} dead, ${outOfReach} unjudgeable, ${unaccountable} unaccountable (reported as theme-allow-unscoped by a normal run).`
225
+ );
226
+ const includes = cfg.include ?? [];
227
+ lines.push(
228
+ `Scope: the ${scanned.length} file(s) check-theme scans \u2014 everything under basalt.roots (${resolveRoots(cfg).join(", ")}), plus each root's sibling index.html and public/ tree, ` + (includes.length === 0 ? "plus anything named in basalt.include (none). " : `plus basalt.include (${includes.join(", ")}). `) + "A waiver in a file none of those reach is not audited \u2014 widen roots, or name it in basalt.include, or accept that nothing polices that file."
229
+ );
230
+ if (dead > 0) {
231
+ lines.push(
232
+ "A dead waiver is not harmless: it is an exception nobody re-checked, and it will silently cover the next real finding on that line."
233
+ );
234
+ }
235
+ console.log(lines.join("\n"));
236
+ return dead > 0 ? 1 : 0;
237
+ }
238
+ export {
239
+ auditAllows
240
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Theme guard — thin FS walker over the headless `../guard` core. Reads BasaltConfig, builds a
3
+ * GuardConfig, walks roots, calls checkSource per file, collects Finding[], groups/reports, returns
4
+ * 0 (clean) / 1 (violations). A `theme-allow` comment exempts a line.
5
+ *
6
+ * Runs against `resolveProjectDir(cwd)`, not `cwd` — see that type's doc for why a root-invoked
7
+ * hook or CI step has to find the package the config lives in rather than scan nothing and pass.
8
+ */
9
+ export declare function checkTheme(invocationCwd?: string, flags?: readonly string[]): number;
10
+ //# sourceMappingURL=check-theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-theme.d.ts","sourceRoot":"","sources":["../../src/cli/check-theme.ts"],"names":[],"mappings":"AA8BA;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,aAAa,GAAE,MAAsB,EACrC,KAAK,GAAE,SAAS,MAAM,EAAO,GAC5B,MAAM,CA2JR"}
@@ -0,0 +1,139 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import {
4
+ checkSource,
5
+ DEFAULT_GUARD_CONFIG,
6
+ guardKindRemedy,
7
+ TOKENS_ONLY_DISABLED_KINDS,
8
+ unmatchedExemptPatterns
9
+ } from "../guard/index.js";
10
+ import { auditAllows } from "./audit-allows.js";
11
+ import {
12
+ conflictingProfileFlags,
13
+ declaredProfile,
14
+ DEFAULT_ROOTS,
15
+ readBasaltConfig,
16
+ resolveExemptRules,
17
+ resolveProjectDir,
18
+ resolveRoots,
19
+ scannableFiles,
20
+ SERIES_MODULE_HINT_RE,
21
+ waiverHintFor
22
+ } from "./index.js";
23
+ function checkTheme(invocationCwd = process.cwd(), flags = []) {
24
+ if (conflictingProfileFlags(flags)) {
25
+ console.error(
26
+ "basalt-ui check-theme: --tokens-only and --framework are alternatives \u2014 pass one."
27
+ );
28
+ return 1;
29
+ }
30
+ const project = resolveProjectDir(invocationCwd);
31
+ const cwd = project.dir;
32
+ if (project.relocatedFrom !== null) {
33
+ console.log(
34
+ `basalt-ui check-theme: BASALT_CWD relocated from ${project.relocatedFrom} to ${cwd}.`
35
+ );
36
+ }
37
+ const cfg = readBasaltConfig(cwd);
38
+ const roots = resolveRoots(cfg);
39
+ const profile = declaredProfile(cfg, flags);
40
+ if (profile === "tokens-only") {
41
+ console.log(
42
+ `basalt-ui check-theme: tokens-only profile \u2014 ${TOKENS_ONLY_DISABLED_KINDS.size} Mantine-coupled kinds are off; the color and typography kinds still apply. Pass --framework to force the full set.`
43
+ );
44
+ }
45
+ const guardCfg = {
46
+ spacingSteps: cfg.spacingSteps ?? DEFAULT_GUARD_CONFIG.spacingSteps,
47
+ rawRadius: cfg.rawRadius ?? DEFAULT_GUARD_CONFIG.rawRadius,
48
+ forbiddenAccents: cfg.forbiddenAccents ?? DEFAULT_GUARD_CONFIG.forbiddenAccents,
49
+ mantineShadeIndex: cfg.mantineShadeIndex ?? DEFAULT_GUARD_CONFIG.mantineShadeIndex,
50
+ rawSurface: cfg.rawSurface ?? DEFAULT_GUARD_CONFIG.rawSurface,
51
+ cardWithBorder: cfg.cardWithBorder ?? DEFAULT_GUARD_CONFIG.cardWithBorder,
52
+ offSystemSurfaceVar: cfg.offSystemSurfaceVar ?? DEFAULT_GUARD_CONFIG.offSystemSurfaceVar,
53
+ rawHtmlLayout: cfg.rawHtmlLayout ?? DEFAULT_GUARD_CONFIG.rawHtmlLayout,
54
+ inlineSpacing: cfg.inlineSpacing ?? DEFAULT_GUARD_CONFIG.inlineSpacing,
55
+ inlineDisplay: cfg.inlineDisplay ?? DEFAULT_GUARD_CONFIG.inlineDisplay,
56
+ rawVisxAxis: cfg.rawVisxAxis ?? DEFAULT_GUARD_CONFIG.rawVisxAxis,
57
+ rawMotionValue: cfg.rawMotionValue ?? DEFAULT_GUARD_CONFIG.rawMotionValue,
58
+ chartMissingAriaLabel: cfg.chartMissingAriaLabel ?? DEFAULT_GUARD_CONFIG.chartMissingAriaLabel,
59
+ rawFormControl: cfg.rawFormControl ?? DEFAULT_GUARD_CONFIG.rawFormControl,
60
+ sub16InputFont: cfg.sub16InputFont ?? DEFAULT_GUARD_CONFIG.sub16InputFont,
61
+ allowComment: "theme-allow",
62
+ exemptRules: resolveExemptRules(cfg),
63
+ severity: cfg.severity ?? DEFAULT_GUARD_CONFIG.severity,
64
+ ...profile === "tokens-only" ? { profile: "tokens-only" } : {}
65
+ };
66
+ const findings = [];
67
+ const scanned = scannableFiles(cwd, cfg);
68
+ for (const rel of scanned) {
69
+ findings.push(...checkSource(readFileSync(resolve(cwd, rel), "utf8"), rel, guardCfg));
70
+ }
71
+ if (scanned.length === 0) {
72
+ if (cfg.roots === void 0) {
73
+ console.error(
74
+ `\u2716 basalt-ui check-theme: 0 files scanned \u2014 no "basalt.roots" configured in package.json, and the built-in default roots (${DEFAULT_ROOTS.join(", ")}) matched zero files. Set "basalt": { "roots": [...] } in package.json to point at your source directories.`
75
+ );
76
+ } else {
77
+ console.error(
78
+ `\u2716 basalt-ui check-theme: 0 files scanned \u2014 the configured "basalt.roots" (${roots.join(", ")}) matched zero files. Check the paths in "basalt.roots" in package.json.`
79
+ );
80
+ }
81
+ return 1;
82
+ }
83
+ if (flags.includes("--audit-allows")) return auditAllows(cwd, cfg, guardCfg, scanned);
84
+ for (const unmatched of unmatchedExemptPatterns(guardCfg, scanned)) {
85
+ console.error(
86
+ `\u26A0 basalt-ui check-theme: "basalt.exemptRules" entry ${unmatched.kind}: "${unmatched.pattern}" ` + (unmatched.reason === "unknown-kind" ? "names no guard kind \u2014 it exempts nothing at all (a typo, or a kind that was renamed)." : "matched none of the scanned files \u2014 it suppresses nothing, and reads as coverage in a config review while enforcing as much as an empty object.")
87
+ );
88
+ }
89
+ if (findings.length === 0) {
90
+ console.log("\u2713 Theme guard: no off-palette colors.");
91
+ return 0;
92
+ }
93
+ const errors = findings.filter((f) => f.severity === "error");
94
+ const warnings = findings.filter((f) => f.severity === "warn");
95
+ const report = (group, heading) => {
96
+ const byFile = /* @__PURE__ */ new Map();
97
+ for (const f of group) {
98
+ const list = byFile.get(f.relPath) ?? [];
99
+ list.push(f);
100
+ byFile.set(f.relPath, list);
101
+ }
102
+ console.error(heading);
103
+ for (const [file, vs] of [...byFile].toSorted()) {
104
+ console.error(file);
105
+ for (const v of vs.toSorted((a, b) => a.line - b.line)) {
106
+ console.error(` ${String(v.line).padStart(4)} ${v.kind.padEnd(22)} ${v.text}`);
107
+ }
108
+ console.error("");
109
+ }
110
+ };
111
+ if (warnings.length > 0) {
112
+ report(
113
+ warnings,
114
+ // Deliberately neutral about WHY a kind is a warning. Two origins reach this block — a kind
115
+ // in its grace minor, and a consumer's own `basalt.severity` override — and the finding
116
+ // doesn't carry which. Promising promotion next minor would be wrong for the second.
117
+ `\u26A0 Theme guard: ${warnings.length} warning(s) \u2014 reported, not fatal. A kind warns while it is in its grace minor, or because \`basalt.severity\` turned it down; fix on your own schedule.
118
+ `
119
+ );
120
+ }
121
+ if (errors.length > 0) {
122
+ report(errors, `\u2716 Theme guard: ${errors.length} off-palette / off-identity violation(s)
123
+ `);
124
+ }
125
+ const presentKinds = [...new Set(findings.map((f) => f.kind))].toSorted();
126
+ const waiverHints = [
127
+ ...new Set(findings.map((f) => waiverHintFor(f.relPath, profile)))
128
+ ].toSorted();
129
+ console.error(["Fix:", ...presentKinds.map(guardKindRemedy), ...waiverHints].join(" "));
130
+ if (findings.some((f) => SERIES_MODULE_HINT_RE.test(f.relPath))) {
131
+ console.error(
132
+ 'Hint: if a flagged file IS your palette/series source, exempt it via "basalt": { "exempt": ["<path>"] } in package.json.'
133
+ );
134
+ }
135
+ return errors.length > 0 ? 1 : 0;
136
+ }
137
+ export {
138
+ checkTheme
139
+ };
@@ -0,0 +1,47 @@
1
+ export type DoctorResult = {
2
+ /** Number of hard failures (exit non-zero). */
3
+ hardFailures: number;
4
+ /** Number of warnings (informational only). */
5
+ warnings: number;
6
+ /**
7
+ * Number of checks that could not RUN (as opposed to ran and passed). Counted separately and
8
+ * exits non-zero on its own: under bun's isolated linker `doctor` used to drop two of its five
9
+ * checks and still print "All checks passed", which is the same false-green this whole surface
10
+ * exists to remove. A check that cannot run is not a check that passed.
11
+ */
12
+ skipped: number;
13
+ };
14
+ /**
15
+ * Check a consumer repo's basalt integration and print a pass/warn report.
16
+ *
17
+ * Narrowed to what a single-directory-scoped run can actually verify — no workspace-glob walking,
18
+ * no ascend/descend project discovery (see {@link resolveProjectDir}). Four checks, numbered in
19
+ * the order they run and print:
20
+ *
21
+ * Hard failures (exit non-zero):
22
+ * 1. `.basalt/manifest.json` exists (`basalt-ui init` was run).
23
+ * 2. `oxlint-preset` ("rules in sync"): the consumer's `.oxlintrc.json` extends the shipped
24
+ * preset AND that path resolves. `init` keeps an existing config, so the framework's whole
25
+ * lint half can be silently off — one repo carried six real violations invisibly across five
26
+ * minors this way.
27
+ * 4. `ai-major-parity`: within THIS package.json, `dependencies`/`devDependencies`/
28
+ * `peerDependencies` agree on the `ai` package's major. `basalt/ai-sdk-major` (the oxlint
29
+ * plugin rule) already catches this per linted FILE against its nearest package.json; this
30
+ * check exists for the shape a lint run cannot see — a package whose own manifest asks its
31
+ * dependents for one major while it itself runs another. A detected skew is exempt-able via
32
+ * `basalt.aiMajorSkewReason` (a non-empty reason string, not a bare `true`); missing or
33
+ * invalid still hard-fails as if the key were absent. A declared reason when the majors
34
+ * already agree warns that the exemption is stale.
35
+ *
36
+ * Warning (non-fatal):
37
+ * 3. `lefthook-preset` ("lefthook wired"): a pre-commit command runs `check-theme`, read from
38
+ * `lefthook dump --format json` (resolves `extends`/`include`/`root:` the way lefthook itself
39
+ * does — see {@link inspectLefthookGate}). No lefthook config at all is a pass (a consumer may
40
+ * gate elsewhere); a config that resolves but wires no guard command warns; a config lefthook
41
+ * dump could not read at all (binary missing, or the dump failed) warns and says so, rather
42
+ * than inventing a verdict.
43
+ *
44
+ * Returns the exit code: 0 = all good, 1 = one or more hard failures.
45
+ */
46
+ export declare function doctor(invocationCwd?: string, flags?: string[]): number;
47
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"AA0BA,MAAM,MAAM,YAAY,GAAG;IACzB,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAA;IACpB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AA8DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,MAAM,CAAC,aAAa,GAAE,MAAsB,EAAE,KAAK,GAAE,MAAM,EAAO,GAAG,MAAM,CAsM1F"}
@@ -0,0 +1,197 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import {
4
+ MANIFEST_PATH,
5
+ basaltBinCommand,
6
+ basaltPresetEntry,
7
+ conflictingProfileFlags,
8
+ declaredProfile,
9
+ findBasaltInstall,
10
+ findRepoRoot,
11
+ inspectLefthookGate,
12
+ parseJsonc,
13
+ readBasaltConfig,
14
+ readIfExists,
15
+ resolveProjectDir,
16
+ shippedAssetPath
17
+ } from "./index.js";
18
+ import { findManifestAbove, parentInstallAdvice } from "./sync.js";
19
+ function majorOf(range) {
20
+ if (typeof range !== "string") return null;
21
+ const match = range.match(/\d+/);
22
+ return match === null ? null : Number(match[0]);
23
+ }
24
+ const AI_MAJOR_FIELDS = ["dependencies", "devDependencies", "peerDependencies"];
25
+ function aiMajorsAt(dir) {
26
+ const raw = readIfExists(resolve(dir, "package.json"));
27
+ if (raw === null) return {};
28
+ try {
29
+ const pkg = JSON.parse(raw);
30
+ const out = {};
31
+ for (const field of AI_MAJOR_FIELDS) {
32
+ const major = majorOf(pkg[field]?.["ai"]);
33
+ if (major !== null) out[field] = major;
34
+ }
35
+ return out;
36
+ } catch {
37
+ return {};
38
+ }
39
+ }
40
+ function resolveAiMajorSkewReason(cfg) {
41
+ const raw = cfg.aiMajorSkewReason;
42
+ if (raw === void 0) return { reason: null, presentButInvalid: false };
43
+ if (typeof raw === "string" && raw.trim().length > 0) {
44
+ return { reason: raw, presentButInvalid: false };
45
+ }
46
+ return { reason: null, presentButInvalid: true };
47
+ }
48
+ function doctor(invocationCwd = process.cwd(), flags = []) {
49
+ if (conflictingProfileFlags(flags)) {
50
+ console.error("basalt-ui doctor: --tokens-only and --framework are alternatives \u2014 pass one.");
51
+ return 1;
52
+ }
53
+ const project = resolveProjectDir(invocationCwd);
54
+ const cwd = project.dir;
55
+ const cfg = readBasaltConfig(cwd);
56
+ const result = { hardFailures: 0, warnings: 0, skipped: 0 };
57
+ const lines = [`
58
+ basalt-ui doctor \u2014 ${cwd}
59
+ `];
60
+ function pass(msg) {
61
+ lines.push(` \u2713 ${msg}`);
62
+ }
63
+ function warn(msg) {
64
+ lines.push(` \u26A0 ${msg}`);
65
+ result.warnings++;
66
+ }
67
+ function fail(msg) {
68
+ lines.push(` \u2716 ${msg}`);
69
+ result.hardFailures++;
70
+ }
71
+ if (project.relocatedFrom !== null) {
72
+ lines.push(` \u2192 BASALT_CWD relocated from ${project.relocatedFrom} to ${cwd}.
73
+ `);
74
+ }
75
+ const profile = declaredProfile(cfg, flags);
76
+ if (profile === "tokens-only") {
77
+ lines.push(
78
+ " profile: tokens-only \u2014 checking the token layer only. `basalt-ui init` is NOT the fix\n here, it places a Mantine doctrine you have no use for. Pass --framework to force the\n full profile.\n"
79
+ );
80
+ }
81
+ const manifestAbs = resolve(cwd, MANIFEST_PATH);
82
+ const manifestExists = existsSync(manifestAbs);
83
+ const parentInstall = manifestExists ? null : findManifestAbove(cwd);
84
+ if (profile === "tokens-only") {
85
+ pass(`${MANIFEST_PATH}: n/a \u2014 a tokens-only consumer has no scaffold to reconcile`);
86
+ } else if (manifestExists) {
87
+ pass(`${MANIFEST_PATH} exists`);
88
+ } else if (parentInstall !== null) {
89
+ fail(
90
+ `${MANIFEST_PATH} missing here, but this is not an unscaffolded consumer. ${parentInstallAdvice(cwd, parentInstall, "doctor")} \`basalt-ui init\` is NOT the fix: it would scaffold a SECOND consumer beside the real one.`
91
+ );
92
+ } else {
93
+ fail(`${MANIFEST_PATH} missing \u2014 run \`basalt-ui init\` to scaffold the consumer repo`);
94
+ }
95
+ const install = findBasaltInstall(cwd);
96
+ if (profile === "tokens-only") {
97
+ pass("oxlint-preset: n/a \u2014 the shipped preset is a Mantine/React preset");
98
+ } else {
99
+ const oxlintrcRaw = readIfExists(resolve(cwd, ".oxlintrc.json"));
100
+ if (oxlintrcRaw === null) {
101
+ fail(
102
+ ".oxlintrc.json missing \u2014 the shipped oxlint preset (the basalt/* design rules) is not active. " + (parentInstall === null ? "Run `basalt-ui init` to seed it." : `${parentInstallAdvice(cwd, parentInstall, "doctor")} The preset is seeded once, at the install \u2014 not per package.`)
103
+ );
104
+ } else {
105
+ const parsed = parseJsonc(oxlintrcRaw);
106
+ if (parsed === null) {
107
+ fail(
108
+ ".oxlintrc.json does not parse as JSON/JSONC \u2014 cannot tell whether it extends the preset"
109
+ );
110
+ } else {
111
+ const entry = basaltPresetEntry(parsed["extends"]);
112
+ const correct = shippedAssetPath(install, cwd, "configs/oxlint.json");
113
+ if (entry === null) {
114
+ fail(
115
+ `.oxlintrc.json does NOT extend the shipped preset \u2014 every basalt/* design rule is off and oxlint reports green without them. Add "extends": ["${correct}"], or re-run \`basalt-ui init --merge-lint\` to splice it in.`
116
+ );
117
+ } else if (!existsSync(resolve(cwd, entry))) {
118
+ fail(
119
+ `.oxlintrc.json extends "${entry}", which does not exist (${resolve(cwd, entry)}) \u2014 oxlint refuses to start on a missing extends target (\`NotFound\`), so nothing is linted at all. Repoint it at "${correct}", where basalt-ui actually resolves.`
120
+ );
121
+ } else {
122
+ pass(`.oxlintrc.json extends the shipped basalt-ui oxlint preset (${entry})`);
123
+ }
124
+ }
125
+ }
126
+ }
127
+ const repoRoot = findRepoRoot(cwd);
128
+ const lefthookGate = profile === "tokens-only" ? { kind: "n/a" } : inspectLefthookGate(repoRoot);
129
+ const lefthookCorrect = shippedAssetPath(install, repoRoot, "configs/lefthook.yml");
130
+ if (lefthookGate.kind === "n/a") {
131
+ pass("lefthook-preset: n/a \u2014 a tokens-only consumer wires its own hooks");
132
+ } else if (lefthookGate.kind === "no-file") {
133
+ pass(`lefthook-preset: n/a \u2014 no lefthook config at ${repoRoot}`);
134
+ } else if (lefthookGate.kind === "wired") {
135
+ pass(`${lefthookGate.file}: \`lefthook dump\` shows a pre-commit command running check-theme.`);
136
+ } else if (lefthookGate.kind === "absent") {
137
+ warn(
138
+ `${lefthookGate.file}: \`lefthook dump\` resolves the merged config and NO pre-commit command runs check-theme \u2014 the theme guard is not gating commits. Add "extends: [${lefthookCorrect}]", or your own command running \`${basaltBinCommand(install, cwd)} check-theme\`.`
139
+ );
140
+ } else {
141
+ warn(
142
+ `${lefthookGate.file}: \`lefthook dump\` could not be run or parsed here (lefthook may not be installed) \u2014 treat this check as advisory. Verify by running \`lefthook dump\` yourself.`
143
+ );
144
+ }
145
+ const aiMajors = aiMajorsAt(cwd);
146
+ const aiEntries = AI_MAJOR_FIELDS.filter((field) => aiMajors[field] !== void 0).map(
147
+ (field) => ({ field, major: aiMajors[field] })
148
+ );
149
+ const { reason: aiMajorSkewReason, presentButInvalid: aiMajorSkewReasonInvalid } = resolveAiMajorSkewReason(cfg);
150
+ if (aiEntries.length > 0) {
151
+ const distinctMajors = new Set(aiEntries.map((entry) => entry.major));
152
+ if (distinctMajors.size > 1) {
153
+ const summary = aiEntries.map((entry) => `${entry.field}@ai${entry.major}`).join(", ");
154
+ if (aiMajorSkewReason !== null) {
155
+ pass(
156
+ `ai package major version mismatch within this package.json: ${summary} \u2014 exempted via basalt.aiMajorSkewReason: "${aiMajorSkewReason}"`
157
+ );
158
+ } else if (aiMajorSkewReasonInvalid) {
159
+ fail(
160
+ `ai package major version mismatch within this package.json: ${summary} \u2014 basalt.aiMajorSkewReason is present but is not a non-empty string (a bare \`true\` is not accepted); the exemption must carry a written reason.`
161
+ );
162
+ } else {
163
+ fail(`ai package major version mismatch within this package.json: ${summary}`);
164
+ }
165
+ } else {
166
+ const [major] = distinctMajors;
167
+ if (aiMajorSkewReason !== null) {
168
+ warn(
169
+ `basalt.aiMajorSkewReason ("${aiMajorSkewReason}") is declared but the ai package major already agrees within this package.json (ai@${major}) \u2014 the exemption is no longer needed and can be deleted.`
170
+ );
171
+ } else {
172
+ pass(`ai package major is consistent within this package.json (ai@${major})`);
173
+ }
174
+ }
175
+ } else {
176
+ pass("ai-major-parity: this package.json declares no ai dependency \u2014 nothing to compare");
177
+ }
178
+ lines.push("");
179
+ if (result.hardFailures === 0 && result.warnings === 0) {
180
+ lines.push("All checks passed.");
181
+ } else {
182
+ const parts = [];
183
+ if (result.hardFailures > 0) parts.push(`${result.hardFailures} hard failure(s)`);
184
+ parts.push(`${result.warnings} warning(s)`);
185
+ lines.push(`${parts.join(", ")}.`);
186
+ }
187
+ lines.push("");
188
+ if (result.hardFailures > 0) {
189
+ console.error(lines.join("\n"));
190
+ return 1;
191
+ }
192
+ console.log(lines.join("\n"));
193
+ return 0;
194
+ }
195
+ export {
196
+ doctor
197
+ };