basalt-ui 1.27.0 → 1.29.0

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 (1245) hide show
  1. package/AGENTS.md +28 -35
  2. package/CHANGELOG.md +72 -0
  3. package/MIGRATING.md +759 -22
  4. package/README.md +56 -69
  5. package/agent/rules/basalt-batteries.md +60 -168
  6. package/agent/rules/basalt-charts.md +56 -75
  7. package/agent/rules/basalt-controls.md +81 -87
  8. package/agent/rules/basalt-mantine.md +64 -98
  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/configs/oxlint-plugin.js +964 -33
  14. package/configs/oxlint-plugin.test.ts +902 -2
  15. package/configs/oxlint.json +6 -0
  16. package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
  17. package/dist/agent/ai-sdk-transport.js +7 -4
  18. package/dist/agent/coalesce.d.ts.map +1 -1
  19. package/dist/agent/coalesce.js +2 -1
  20. package/dist/agent/merge.d.ts.map +1 -1
  21. package/dist/agent/merge.js +2 -1
  22. package/dist/agent/part-list.d.ts.map +1 -1
  23. package/dist/agent/part-list.js +2 -0
  24. package/dist/agent/stick-to-bottom.d.ts +3 -6
  25. package/dist/agent/stick-to-bottom.d.ts.map +1 -1
  26. package/dist/agent/stick-to-bottom.js +6 -3
  27. package/dist/agent/transport.d.ts +13 -2
  28. package/dist/agent/transport.d.ts.map +1 -1
  29. package/dist/agent/use-agent-thread-runs.d.ts +12 -1
  30. package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
  31. package/dist/agent/use-agent-thread-runs.js +20 -6
  32. package/dist/agent-chat/composer.d.ts +6 -2
  33. package/dist/agent-chat/composer.d.ts.map +1 -1
  34. package/dist/agent-chat/composer.js +49 -33
  35. package/dist/agent-chat/index.d.ts +1 -1
  36. package/dist/agent-chat/index.d.ts.map +1 -1
  37. package/dist/agent-chat/relative-time.d.ts +0 -9
  38. package/dist/agent-chat/relative-time.d.ts.map +1 -1
  39. package/dist/agent-chat/relative-time.js +2 -1
  40. package/dist/agent-chat/thread-detail-panel.d.ts +3 -2
  41. package/dist/agent-chat/thread-detail-panel.d.ts.map +1 -1
  42. package/dist/agent-chat/thread-detail-panel.js +9 -6
  43. package/dist/agent-chat/thread-feed-row.d.ts +32 -6
  44. package/dist/agent-chat/thread-feed-row.d.ts.map +1 -1
  45. package/dist/agent-chat/thread-feed-row.js +61 -26
  46. package/dist/agent-chat/thread-feed.d.ts +13 -2
  47. package/dist/agent-chat/thread-feed.d.ts.map +1 -1
  48. package/dist/agent-chat/thread-feed.js +16 -7
  49. package/dist/agent-chat/thread-message.d.ts +4 -3
  50. package/dist/agent-chat/thread-message.d.ts.map +1 -1
  51. package/dist/agent-chat/thread-message.js +70 -31
  52. package/dist/agent-chat/thread-outcome-card.d.ts +3 -2
  53. package/dist/agent-chat/thread-outcome-card.d.ts.map +1 -1
  54. package/dist/agent-chat/thread-outcome-card.js +9 -2
  55. package/dist/agent-chat/thread-workspace.d.ts +7 -2
  56. package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
  57. package/dist/agent-chat/thread-workspace.js +62 -29
  58. package/dist/agent-chat/tool-chip.d.ts +3 -2
  59. package/dist/agent-chat/tool-chip.d.ts.map +1 -1
  60. package/dist/agent-chat/tool-chip.js +7 -2
  61. package/dist/agent-chat/virtualize.d.ts +39 -0
  62. package/dist/agent-chat/virtualize.d.ts.map +1 -1
  63. package/dist/agent-chat/virtualize.js +15 -0
  64. package/dist/charts/hooks/useBandPlot.d.ts.map +1 -1
  65. package/dist/charts/hooks/useBandPlot.js +15 -8
  66. package/dist/charts/index.d.ts +11 -10
  67. package/dist/charts/index.d.ts.map +1 -1
  68. package/dist/charts/index.js +53 -9
  69. package/dist/charts/kinds/BandStrip.d.ts +10 -7
  70. package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
  71. package/dist/charts/kinds/BandStrip.js +12 -7
  72. package/dist/charts/kinds/Bars.d.ts +13 -9
  73. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  74. package/dist/charts/kinds/Bars.js +37 -11
  75. package/dist/charts/kinds/Donut.d.ts +6 -5
  76. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  77. package/dist/charts/kinds/Donut.js +19 -2
  78. package/dist/charts/kinds/DualPanel.d.ts +9 -6
  79. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  80. package/dist/charts/kinds/DualPanel.js +37 -9
  81. package/dist/charts/kinds/Heatmap.d.ts +10 -6
  82. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  83. package/dist/charts/kinds/Heatmap.js +58 -31
  84. package/dist/charts/kinds/MirroredBars.d.ts +6 -5
  85. package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
  86. package/dist/charts/kinds/MirroredBars.js +9 -2
  87. package/dist/charts/kinds/MultiLine.d.ts +11 -6
  88. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  89. package/dist/charts/kinds/MultiLine.js +24 -15
  90. package/dist/charts/kinds/StackedArea.d.ts +18 -8
  91. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  92. package/dist/charts/kinds/StackedArea.js +29 -4
  93. package/dist/charts/kinds/ZonedLine.d.ts +13 -11
  94. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  95. package/dist/charts/kinds/ZonedLine.js +33 -23
  96. package/dist/charts/layout/auto-margin.d.ts +65 -8
  97. package/dist/charts/layout/auto-margin.d.ts.map +1 -1
  98. package/dist/charts/layout/auto-margin.js +45 -10
  99. package/dist/charts/layout/log-ticks.d.ts +27 -0
  100. package/dist/charts/layout/log-ticks.d.ts.map +1 -0
  101. package/dist/charts/layout/log-ticks.js +42 -0
  102. package/dist/charts/primitives/Axes.d.ts +32 -4
  103. package/dist/charts/primitives/Axes.d.ts.map +1 -1
  104. package/dist/charts/primitives/Axes.js +49 -7
  105. package/dist/charts/primitives/CartesianChart.d.ts +45 -7
  106. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
  107. package/dist/charts/primitives/CartesianChart.js +86 -28
  108. package/dist/charts/primitives/ChartCard.d.ts +45 -4
  109. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  110. package/dist/charts/primitives/ChartCard.js +70 -21
  111. package/dist/charts/primitives/ChartFrame.d.ts +30 -4
  112. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  113. package/dist/charts/primitives/ChartFrame.js +52 -15
  114. package/dist/charts/primitives/ChartLegend.d.ts +13 -2
  115. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  116. package/dist/charts/primitives/ChartLegend.js +33 -13
  117. package/dist/charts/primitives/ChartPending.d.ts +85 -6
  118. package/dist/charts/primitives/ChartPending.d.ts.map +1 -1
  119. package/dist/charts/primitives/ChartPending.js +100 -6
  120. package/dist/charts/primitives/ChartTooltip.d.ts +7 -0
  121. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  122. package/dist/charts/primitives/ChartTooltip.js +18 -5
  123. package/dist/charts/primitives/Crosshair.d.ts +3 -0
  124. package/dist/charts/primitives/Crosshair.d.ts.map +1 -1
  125. package/dist/charts/primitives/Crosshair.js +3 -1
  126. package/dist/charts/primitives/chart-frame-layout.d.ts +95 -0
  127. package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -0
  128. package/dist/charts/primitives/chart-frame-layout.js +89 -0
  129. package/dist/charts/primitives/chart-tier.d.ts +16 -0
  130. package/dist/charts/primitives/chart-tier.d.ts.map +1 -0
  131. package/dist/charts/primitives/chart-tier.js +20 -0
  132. package/dist/charts/series.d.ts +55 -1
  133. package/dist/charts/series.d.ts.map +1 -1
  134. package/dist/charts/series.js +27 -1
  135. package/dist/charts/sparklines/BarSparkline.d.ts +3 -3
  136. package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
  137. package/dist/charts/sparklines/BarSparkline.js +9 -3
  138. package/dist/charts/sparklines/LineSparkline.d.ts +3 -3
  139. package/dist/charts/sparklines/LineSparkline.d.ts.map +1 -1
  140. package/dist/charts/sparklines/LineSparkline.js +15 -3
  141. package/dist/charts/sparklines/index.d.ts +2 -2
  142. package/dist/charts/sparklines/index.d.ts.map +1 -1
  143. package/dist/charts/utils/format.d.ts +7 -4
  144. package/dist/charts/utils/format.d.ts.map +1 -1
  145. package/dist/charts/utils/format.js +17 -24
  146. package/dist/charts/utils/ticks.d.ts +81 -2
  147. package/dist/charts/utils/ticks.d.ts.map +1 -1
  148. package/dist/charts/utils/ticks.js +54 -4
  149. package/dist/cli/audit-allows.d.ts +24 -0
  150. package/dist/cli/audit-allows.d.ts.map +1 -0
  151. package/dist/cli/audit-allows.js +240 -0
  152. package/dist/cli/check-theme.d.ts +10 -0
  153. package/dist/cli/check-theme.d.ts.map +1 -0
  154. package/dist/cli/check-theme.js +139 -0
  155. package/dist/cli/doctor.d.ts +47 -0
  156. package/dist/cli/doctor.d.ts.map +1 -0
  157. package/dist/cli/doctor.js +197 -0
  158. package/dist/cli/guard-hook.d.ts +9 -0
  159. package/dist/cli/guard-hook.d.ts.map +1 -0
  160. package/dist/cli/guard-hook.js +93 -0
  161. package/dist/cli/help.d.ts +9 -0
  162. package/dist/cli/help.d.ts.map +1 -0
  163. package/dist/cli/help.js +53 -0
  164. package/dist/cli/index.d.ts +360 -270
  165. package/dist/cli/index.d.ts.map +1 -1
  166. package/dist/cli/index.js +85 -1901
  167. package/dist/cli/init.d.ts +4 -0
  168. package/dist/cli/init.d.ts.map +1 -0
  169. package/dist/cli/init.js +165 -0
  170. package/dist/cli/sync.d.ts +43 -0
  171. package/dist/cli/sync.d.ts.map +1 -0
  172. package/dist/cli/sync.js +206 -0
  173. package/dist/cli/tokens-css.d.ts +51 -0
  174. package/dist/cli/tokens-css.d.ts.map +1 -0
  175. package/dist/cli/tokens-css.js +165 -0
  176. package/dist/commands/ShortcutsHelp.d.ts +3 -2
  177. package/dist/commands/ShortcutsHelp.d.ts.map +1 -1
  178. package/dist/commands/ShortcutsHelp.js +8 -2
  179. package/dist/commands/define-commands.d.ts.map +1 -1
  180. package/dist/commands/define-commands.js +2 -2
  181. package/dist/commands/define-overlays.d.ts +102 -0
  182. package/dist/commands/define-overlays.d.ts.map +1 -1
  183. package/dist/commands/define-overlays.js +102 -2
  184. package/dist/commands/index.d.ts +12 -2
  185. package/dist/commands/index.d.ts.map +1 -1
  186. package/dist/commands/index.js +9 -1
  187. package/dist/commands/overlays-mount.d.ts +16 -11
  188. package/dist/commands/overlays-mount.d.ts.map +1 -1
  189. package/dist/commands/overlays-mount.js +23 -14
  190. package/dist/commands/shell-bridge.d.ts +57 -0
  191. package/dist/commands/shell-bridge.d.ts.map +1 -0
  192. package/dist/commands/shell-bridge.js +32 -0
  193. package/dist/common/errors.d.ts +69 -0
  194. package/dist/common/errors.d.ts.map +1 -0
  195. package/dist/common/errors.js +64 -0
  196. package/dist/common/index.d.ts +17 -0
  197. package/dist/common/index.d.ts.map +1 -0
  198. package/dist/common/index.js +35 -0
  199. package/dist/common/is-dev.d.ts +3 -0
  200. package/dist/common/is-dev.d.ts.map +1 -0
  201. package/dist/common/is-dev.js +6 -0
  202. package/dist/{motion/index.d.ts → common/motion.d.ts} +1 -1
  203. package/dist/common/motion.d.ts.map +1 -0
  204. package/dist/common/props.d.ts +63 -0
  205. package/dist/common/props.d.ts.map +1 -0
  206. package/dist/common/props.js +6 -0
  207. package/dist/common/query-state-like.d.ts +43 -0
  208. package/dist/common/query-state-like.d.ts.map +1 -0
  209. package/dist/common/query-state-like.js +20 -0
  210. package/dist/common/refs.d.ts +29 -0
  211. package/dist/common/refs.d.ts.map +1 -0
  212. package/dist/common/refs.js +29 -0
  213. package/dist/common/scroll-parent.d.ts +23 -0
  214. package/dist/common/scroll-parent.d.ts.map +1 -0
  215. package/dist/common/scroll-parent.js +21 -0
  216. package/dist/common/validate.d.ts +38 -0
  217. package/dist/common/validate.d.ts.map +1 -0
  218. package/dist/common/validate.js +36 -0
  219. package/dist/content/article-layout.module.css +11 -4
  220. package/dist/content/callout.d.ts +8 -4
  221. package/dist/content/callout.d.ts.map +1 -1
  222. package/dist/content/callout.js +15 -8
  223. package/dist/content/guide.d.ts +5 -4
  224. package/dist/content/guide.d.ts.map +1 -1
  225. package/dist/content/guide.js +13 -4
  226. package/dist/content/heading-anchor.d.ts +24 -2
  227. package/dist/content/heading-anchor.d.ts.map +1 -1
  228. package/dist/content/heading-anchor.js +23 -9
  229. package/dist/content/index.d.ts.map +1 -1
  230. package/dist/content/markdown.d.ts +3 -4
  231. package/dist/content/markdown.d.ts.map +1 -1
  232. package/dist/content/markdown.js +3 -4
  233. package/dist/content/prose.module.css +9 -7
  234. package/dist/content/reading-progress.d.ts +10 -4
  235. package/dist/content/reading-progress.d.ts.map +1 -1
  236. package/dist/content/reading-progress.js +26 -10
  237. package/dist/content/slug.d.ts +4 -0
  238. package/dist/content/slug.d.ts.map +1 -1
  239. package/dist/content/toc.d.ts +11 -0
  240. package/dist/content/toc.d.ts.map +1 -1
  241. package/dist/content/toc.js +23 -10
  242. package/dist/controls/actions.d.ts +50 -7
  243. package/dist/controls/actions.d.ts.map +1 -1
  244. package/dist/controls/actions.js +39 -8
  245. package/dist/controls/compare-filter.d.ts +3 -2
  246. package/dist/controls/compare-filter.d.ts.map +1 -1
  247. package/dist/controls/compare-filter.js +17 -2
  248. package/dist/controls/control-group.d.ts +3 -3
  249. package/dist/controls/control-group.d.ts.map +1 -1
  250. package/dist/controls/control-group.js +9 -2
  251. package/dist/controls/controls.module.css +169 -186
  252. package/dist/controls/controls.module.css.d.ts +9 -11
  253. package/dist/controls/enum-filter.d.ts +3 -2
  254. package/dist/controls/enum-filter.d.ts.map +1 -1
  255. package/dist/controls/enum-filter.js +28 -13
  256. package/dist/controls/filter-context.d.ts +8 -4
  257. package/dist/controls/filter-context.d.ts.map +1 -1
  258. package/dist/controls/filter-pill.d.ts +3 -3
  259. package/dist/controls/filter-pill.d.ts.map +1 -1
  260. package/dist/controls/filter-pill.js +4 -1
  261. package/dist/controls/filter-set.d.ts +3 -2
  262. package/dist/controls/filter-set.d.ts.map +1 -1
  263. package/dist/controls/filter-set.js +66 -41
  264. package/dist/controls/filter-sheet.d.ts +20 -80
  265. package/dist/controls/filter-sheet.d.ts.map +1 -1
  266. package/dist/controls/filter-sheet.js +9 -95
  267. package/dist/controls/index.d.ts +2 -0
  268. package/dist/controls/index.d.ts.map +1 -1
  269. package/dist/controls/index.js +4 -0
  270. package/dist/controls/multi-select-filter.d.ts +18 -2
  271. package/dist/controls/multi-select-filter.d.ts.map +1 -1
  272. package/dist/controls/multi-select-filter.js +122 -20
  273. package/dist/controls/number-filter.d.ts +7 -5
  274. package/dist/controls/number-filter.d.ts.map +1 -1
  275. package/dist/controls/number-filter.js +40 -11
  276. package/dist/controls/panel-row.d.ts +132 -0
  277. package/dist/controls/panel-row.d.ts.map +1 -0
  278. package/dist/controls/panel-row.js +173 -0
  279. package/dist/controls/panel-row.module.css +86 -0
  280. package/dist/controls/panel-row.module.css.d.ts +13 -0
  281. package/dist/controls/range-filter.d.ts +2 -1
  282. package/dist/controls/range-filter.d.ts.map +1 -1
  283. package/dist/controls/range-filter.js +57 -35
  284. package/dist/controls/search-filter.d.ts +3 -2
  285. package/dist/controls/search-filter.d.ts.map +1 -1
  286. package/dist/controls/search-filter.js +25 -10
  287. package/dist/controls/select-filter.d.ts +2 -1
  288. package/dist/controls/select-filter.d.ts.map +1 -1
  289. package/dist/controls/select-filter.js +4 -0
  290. package/dist/controls/slider-control.d.ts +36 -0
  291. package/dist/controls/slider-control.d.ts.map +1 -0
  292. package/dist/controls/slider-control.js +64 -0
  293. package/dist/controls/sync-button.d.ts +3 -2
  294. package/dist/controls/sync-button.d.ts.map +1 -1
  295. package/dist/controls/sync-button.js +16 -3
  296. package/dist/controls/toggle-filter.d.ts +3 -2
  297. package/dist/controls/toggle-filter.d.ts.map +1 -1
  298. package/dist/controls/toggle-filter.js +28 -11
  299. package/dist/controls/view-tabs.d.ts +3 -2
  300. package/dist/controls/view-tabs.d.ts.map +1 -1
  301. package/dist/controls/view-tabs.js +43 -11
  302. package/dist/controls-dates/date-range-picker.d.ts +1 -1
  303. package/dist/controls-dates/date-range-picker.d.ts.map +1 -1
  304. package/dist/controls-dates/date-range-picker.js +6 -1
  305. package/dist/dashboard/delta-badge.d.ts +18 -6
  306. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  307. package/dist/dashboard/delta-badge.js +33 -1
  308. package/dist/dashboard/empty-state.d.ts +8 -4
  309. package/dist/dashboard/empty-state.d.ts.map +1 -1
  310. package/dist/dashboard/empty-state.js +36 -6
  311. package/dist/dashboard/index.d.ts +9 -5
  312. package/dist/dashboard/index.d.ts.map +1 -1
  313. package/dist/dashboard/index.js +13 -1
  314. package/dist/dashboard/query-state.d.ts +39 -27
  315. package/dist/dashboard/query-state.d.ts.map +1 -1
  316. package/dist/dashboard/query-state.js +109 -58
  317. package/dist/dashboard/section.d.ts +58 -0
  318. package/dist/dashboard/section.d.ts.map +1 -0
  319. package/dist/dashboard/section.js +118 -0
  320. package/dist/{section → dashboard}/section.module.css +11 -0
  321. package/dist/dashboard/settings-section.d.ts +19 -9
  322. package/dist/dashboard/settings-section.d.ts.map +1 -1
  323. package/dist/dashboard/settings-section.js +75 -26
  324. package/dist/dashboard/stat-card.d.ts +44 -38
  325. package/dist/dashboard/stat-card.d.ts.map +1 -1
  326. package/dist/dashboard/stat-card.js +56 -43
  327. package/dist/dashboard/stat-card.module.css +46 -31
  328. package/dist/dashboard/stat-card.module.css.d.ts +1 -0
  329. package/dist/dashboard/stat-group.d.ts +41 -0
  330. package/dist/dashboard/stat-group.d.ts.map +1 -0
  331. package/dist/dashboard/stat-group.js +30 -0
  332. package/dist/dashboard/stat-group.module.css +66 -0
  333. package/dist/dashboard/stat-group.module.css.d.ts +8 -0
  334. package/dist/dashboard/widget-grid.d.ts +27 -0
  335. package/dist/dashboard/widget-grid.d.ts.map +1 -0
  336. package/dist/dashboard/widget-grid.js +41 -0
  337. package/dist/dashboard/widget-grid.module.css +53 -0
  338. package/dist/dashboard/widget-grid.module.css.d.ts +9 -0
  339. package/dist/dashboard/widget-header.d.ts +132 -0
  340. package/dist/dashboard/widget-header.d.ts.map +1 -0
  341. package/dist/{widget-header → dashboard}/widget-header.js +42 -27
  342. package/dist/{widget-header → dashboard}/widget-header.module.css +72 -7
  343. package/dist/data/data-table.d.ts +151 -12
  344. package/dist/data/data-table.d.ts.map +1 -1
  345. package/dist/data/data-table.js +385 -139
  346. package/dist/data/data-table.module.css +64 -0
  347. package/dist/data/data-table.module.css.d.ts +11 -0
  348. package/dist/data/query-branch.d.ts +28 -0
  349. package/dist/data/query-branch.d.ts.map +1 -0
  350. package/dist/data/query-branch.js +10 -0
  351. package/dist/data/table.d.ts +2 -2
  352. package/dist/data/table.d.ts.map +1 -1
  353. package/dist/data/table.js +2 -16
  354. package/dist/data/virtual-list.d.ts +65 -6
  355. package/dist/data/virtual-list.d.ts.map +1 -1
  356. package/dist/data/virtual-list.js +89 -38
  357. package/dist/data/virtual.d.ts +4 -4
  358. package/dist/data/virtual.d.ts.map +1 -1
  359. package/dist/dev-dock.d.ts +28 -0
  360. package/dist/dev-dock.d.ts.map +1 -0
  361. package/dist/dev-dock.js +85 -0
  362. package/dist/format/date.d.ts +62 -0
  363. package/dist/format/date.d.ts.map +1 -0
  364. package/dist/format/date.js +106 -0
  365. package/dist/format/index.d.ts +39 -0
  366. package/dist/format/index.d.ts.map +1 -0
  367. package/dist/format/index.js +54 -0
  368. package/dist/format/measure.d.ts +15 -0
  369. package/dist/format/measure.d.ts.map +1 -0
  370. package/dist/format/measure.js +16 -0
  371. package/dist/format/number.d.ts +69 -0
  372. package/dist/format/number.d.ts.map +1 -0
  373. package/dist/format/number.js +62 -0
  374. package/dist/format/shared.d.ts +27 -0
  375. package/dist/format/shared.d.ts.map +1 -0
  376. package/dist/format/shared.js +29 -0
  377. package/dist/forms/FormErrorSummary.d.ts +4 -3
  378. package/dist/forms/FormErrorSummary.d.ts.map +1 -1
  379. package/dist/forms/FormErrorSummary.js +6 -4
  380. package/dist/forms/create-form.d.ts +2 -2
  381. package/dist/forms/field.d.ts +33 -9
  382. package/dist/forms/field.d.ts.map +1 -1
  383. package/dist/forms/field.js +7 -6
  384. package/dist/forms/form-layout.d.ts +142 -0
  385. package/dist/forms/form-layout.d.ts.map +1 -0
  386. package/dist/forms/form-layout.js +110 -0
  387. package/dist/forms/form-layout.module.css +145 -0
  388. package/dist/forms/form-layout.module.css.d.ts +20 -0
  389. package/dist/forms/form-state.d.ts +42 -0
  390. package/dist/forms/form-state.d.ts.map +1 -0
  391. package/dist/forms/form-state.js +26 -0
  392. package/dist/forms/index.d.ts +22 -6
  393. package/dist/forms/index.d.ts.map +1 -1
  394. package/dist/forms/index.js +19 -4
  395. package/dist/forms/use-field-array.d.ts +60 -0
  396. package/dist/forms/use-field-array.d.ts.map +1 -0
  397. package/dist/forms/use-field-array.js +20 -0
  398. package/dist/forms/use-form-draft.d.ts +25 -10
  399. package/dist/forms/use-form-draft.d.ts.map +1 -1
  400. package/dist/forms/use-form-draft.js +55 -18
  401. package/dist/forms/use-form-submit.d.ts +88 -0
  402. package/dist/forms/use-form-submit.d.ts.map +1 -0
  403. package/dist/forms/use-form-submit.js +63 -0
  404. package/dist/guard/index.d.ts +2 -9
  405. package/dist/guard/index.d.ts.map +1 -1
  406. package/dist/guard/index.js +38 -28
  407. package/dist/guard/types.d.ts +8 -10
  408. package/dist/guard/types.d.ts.map +1 -1
  409. package/dist/index.d.ts +21 -14
  410. package/dist/index.d.ts.map +1 -1
  411. package/dist/index.js +40 -6
  412. package/dist/notifications/bell.d.ts +3 -2
  413. package/dist/notifications/bell.d.ts.map +1 -1
  414. package/dist/notifications/bell.js +73 -56
  415. package/dist/notifications/center.d.ts +15 -4
  416. package/dist/notifications/center.d.ts.map +1 -1
  417. package/dist/notifications/center.js +5 -2
  418. package/dist/notifications/define-notifications.d.ts +56 -14
  419. package/dist/notifications/define-notifications.d.ts.map +1 -1
  420. package/dist/notifications/define-notifications.js +2 -1
  421. package/dist/notifications/index.d.ts +11 -7
  422. package/dist/notifications/index.d.ts.map +1 -1
  423. package/dist/notifications/index.js +5 -3
  424. package/dist/notifications/mount-guard.d.ts +8 -0
  425. package/dist/notifications/mount-guard.d.ts.map +1 -0
  426. package/dist/notifications/mount-guard.js +19 -0
  427. package/dist/notifications/notify.d.ts +58 -1
  428. package/dist/notifications/notify.d.ts.map +1 -1
  429. package/dist/notifications/notify.js +46 -2
  430. package/dist/notifications/store.d.ts +7 -0
  431. package/dist/notifications/store.d.ts.map +1 -1
  432. package/dist/provider/connectivity-indicator.d.ts +13 -0
  433. package/dist/provider/connectivity-indicator.d.ts.map +1 -0
  434. package/dist/{connectivity → provider}/connectivity-indicator.js +3 -1
  435. package/dist/{connectivity → provider}/connectivity-provider.d.ts +1 -1
  436. package/dist/provider/connectivity-provider.d.ts.map +1 -0
  437. package/dist/{connectivity → provider}/connectivity-provider.js +4 -1
  438. package/dist/{connectivity/types.d.ts → provider/connectivity-types.d.ts} +3 -3
  439. package/dist/provider/connectivity-types.d.ts.map +1 -0
  440. package/dist/provider/error-fallback.module.css +21 -0
  441. package/dist/provider/error-fallback.module.css.d.ts +9 -0
  442. package/dist/provider/index.d.ts +29 -13
  443. package/dist/provider/index.d.ts.map +1 -1
  444. package/dist/provider/index.js +82 -34
  445. package/dist/provider/lab-theme.d.ts.map +1 -1
  446. package/dist/provider/lab-theme.js +2 -1
  447. package/dist/provider/use-connectivity.d.ts +3 -0
  448. package/dist/provider/use-connectivity.d.ts.map +1 -0
  449. package/dist/query-client.d.ts +63 -0
  450. package/dist/query-client.d.ts.map +1 -0
  451. package/dist/query-client.js +33 -0
  452. package/dist/{query/devtools.d.ts → query-devtools.d.ts} +2 -2
  453. package/dist/query-devtools.d.ts.map +1 -0
  454. package/dist/{query/devtools.js → query-devtools.js} +1 -0
  455. package/dist/register.d.ts +17 -3
  456. package/dist/register.d.ts.map +1 -1
  457. package/dist/router-tanstack/index.d.ts +4 -7
  458. package/dist/router-tanstack/index.d.ts.map +1 -1
  459. package/dist/router-tanstack/index.js +1 -9
  460. package/dist/router-tanstack/nav.d.ts +1 -1
  461. package/dist/router-tanstack/nav.d.ts.map +1 -1
  462. package/dist/router-tanstack/search-store.d.ts +3 -6
  463. package/dist/router-tanstack/search-store.d.ts.map +1 -1
  464. package/dist/router-tanstack/search-store.js +1 -1
  465. package/dist/router-tanstack/use-nav.d.ts +1 -1
  466. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  467. package/dist/router-tanstack/use-nav.js +1 -1
  468. package/dist/shell/account-types.d.ts +5 -2
  469. package/dist/shell/account-types.d.ts.map +1 -1
  470. package/dist/shell/app-brand.d.ts +13 -0
  471. package/dist/shell/app-brand.d.ts.map +1 -0
  472. package/dist/shell/app-brand.js +94 -0
  473. package/dist/shell/app-brand.module.css +109 -0
  474. package/dist/shell/app-brand.module.css.d.ts +14 -0
  475. package/dist/shell/app-breadcrumbs.d.ts +5 -3
  476. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  477. package/dist/shell/app-breadcrumbs.js +33 -22
  478. package/dist/shell/app-header.module.css +10 -5
  479. package/dist/shell/app-main.module.css +118 -0
  480. package/dist/shell/app-main.module.css.d.ts +11 -0
  481. package/dist/shell/app-mobile-nav.d.ts +4 -3
  482. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  483. package/dist/shell/app-mobile-nav.js +53 -17
  484. package/dist/shell/app-mobile-nav.module.css +48 -27
  485. package/dist/shell/app-mobile-nav.module.css.d.ts +0 -1
  486. package/dist/shell/app-sidebar-account.d.ts +2 -1
  487. package/dist/shell/app-sidebar-account.d.ts.map +1 -1
  488. package/dist/shell/app-sidebar-account.js +57 -22
  489. package/dist/shell/app-sidebar.d.ts +10 -4
  490. package/dist/shell/app-sidebar.d.ts.map +1 -1
  491. package/dist/shell/app-sidebar.js +78 -103
  492. package/dist/shell/app-sidebar.module.css +63 -96
  493. package/dist/shell/app-sidebar.module.css.d.ts +0 -5
  494. package/dist/shell/index.d.ts +24 -10
  495. package/dist/shell/index.d.ts.map +1 -1
  496. package/dist/shell/index.js +60 -29
  497. package/dist/shell/isomorphic-layout-effect.d.ts +14 -0
  498. package/dist/shell/isomorphic-layout-effect.d.ts.map +1 -0
  499. package/dist/shell/isomorphic-layout-effect.js +5 -0
  500. package/dist/shell/mobile-nav-model.d.ts +1 -1
  501. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  502. package/dist/shell/mobile-nav-model.js +27 -6
  503. package/dist/shell/nav-count-badge.d.ts +5 -3
  504. package/dist/shell/nav-count-badge.d.ts.map +1 -1
  505. package/dist/shell/nav-count-badge.js +3 -1
  506. package/dist/{nav/types.d.ts → shell/nav-types.d.ts} +16 -7
  507. package/dist/shell/nav-types.d.ts.map +1 -0
  508. package/dist/shell/page-aside.d.ts +43 -0
  509. package/dist/shell/page-aside.d.ts.map +1 -0
  510. package/dist/shell/page-aside.js +175 -0
  511. package/dist/shell/page-aside.module.css +174 -0
  512. package/dist/shell/page-aside.module.css.d.ts +14 -0
  513. package/dist/shell/page-bar.d.ts +55 -15
  514. package/dist/shell/page-bar.d.ts.map +1 -1
  515. package/dist/shell/page-bar.js +130 -41
  516. package/dist/shell/page-bar.module.css +107 -13
  517. package/dist/shell/page-bar.module.css.d.ts +4 -1
  518. package/dist/shell/page-title.d.ts +14 -0
  519. package/dist/shell/page-title.d.ts.map +1 -0
  520. package/dist/shell/page-title.js +25 -0
  521. package/dist/shell/page-title.module.css +33 -0
  522. package/dist/shell/page-title.module.css.d.ts +11 -0
  523. package/dist/shell/sidebar-block-model.d.ts +1 -1
  524. package/dist/shell/sidebar-block-model.d.ts.map +1 -1
  525. package/dist/shell/sidebar-blocks.d.ts +1 -1
  526. package/dist/shell/sidebar-blocks.d.ts.map +1 -1
  527. package/dist/shell/sidebar-search.d.ts +4 -2
  528. package/dist/shell/sidebar-search.d.ts.map +1 -1
  529. package/dist/shell/sidebar-search.js +9 -4
  530. package/dist/shell/sidebar-search.module.css +1 -1
  531. package/dist/shell/use-breakpoint.d.ts +11 -0
  532. package/dist/shell/use-breakpoint.d.ts.map +1 -0
  533. package/dist/shell/use-breakpoint.js +33 -0
  534. package/dist/state/fields.d.ts +26 -22
  535. package/dist/state/fields.d.ts.map +1 -1
  536. package/dist/state/fields.js +19 -18
  537. package/dist/state/persisted-or-local.d.ts +9 -0
  538. package/dist/state/persisted-or-local.d.ts.map +1 -0
  539. package/dist/state/persisted-or-local.js +24 -0
  540. package/dist/state.d.ts +1 -1
  541. package/dist/state.d.ts.map +1 -1
  542. package/dist/state.js +2 -6
  543. package/dist/styles.css +36 -0
  544. package/dist/surfaces.d.ts +25 -47
  545. package/dist/surfaces.d.ts.map +1 -1
  546. package/dist/surfaces.js +72 -55
  547. package/dist/theme/ctl-theme.d.ts.map +1 -1
  548. package/dist/theme/index.d.ts.map +1 -1
  549. package/dist/theme/index.js +102 -17
  550. package/dist/theme/nav-link.module.css +37 -26
  551. package/dist/theme/segmented-control.module.css +22 -0
  552. package/dist/theme/segmented-control.module.css.d.ts +1 -0
  553. package/dist/theme/theme-toggle.d.ts +9 -0
  554. package/dist/theme/theme-toggle.d.ts.map +1 -0
  555. package/dist/{theme-toggle/index.js → theme/theme-toggle.js} +9 -3
  556. package/dist/theme-lab/boot.d.ts +66 -0
  557. package/dist/theme-lab/boot.d.ts.map +1 -0
  558. package/dist/theme-lab/boot.js +82 -0
  559. package/dist/theme-lab/index.d.ts +4 -43
  560. package/dist/theme-lab/index.d.ts.map +1 -1
  561. package/dist/theme-lab/index.js +9 -69
  562. package/dist/tokens/index.d.ts +43 -0
  563. package/dist/tokens/index.d.ts.map +1 -1
  564. package/dist/tokens/index.js +45 -6
  565. package/dist/tokens/palette.d.ts +57 -25
  566. package/dist/tokens/palette.d.ts.map +1 -1
  567. package/dist/tokens/palette.js +44 -15
  568. package/dist/tokens.css +5 -5
  569. package/llms.txt +11 -26
  570. package/package.json +54 -34
  571. package/src/agent/ai-sdk-transport.ts +11 -4
  572. package/src/agent/coalesce.ts +2 -1
  573. package/src/agent/merge.ts +2 -1
  574. package/src/agent/part-list.tsx +2 -0
  575. package/src/agent/stick-to-bottom.tsx +9 -11
  576. package/src/agent/transport.ts +15 -2
  577. package/src/agent/use-agent-thread-runs.ts +65 -15
  578. package/src/agent-chat/composer.tsx +124 -103
  579. package/src/agent-chat/index.ts +6 -4
  580. package/src/agent-chat/relative-time.ts +2 -1
  581. package/src/agent-chat/thread-detail-panel.tsx +10 -6
  582. package/src/agent-chat/thread-feed-row.tsx +117 -34
  583. package/src/agent-chat/thread-feed.tsx +59 -27
  584. package/src/agent-chat/thread-message.tsx +48 -13
  585. package/src/agent-chat/thread-outcome-card.tsx +9 -1
  586. package/src/agent-chat/thread-workspace.tsx +72 -33
  587. package/src/agent-chat/tool-chip.tsx +8 -2
  588. package/src/agent-chat/virtualize.ts +48 -0
  589. package/src/charts/hooks/useBandPlot.ts +26 -8
  590. package/src/charts/index.ts +46 -14
  591. package/src/charts/kinds/BandStrip.tsx +36 -14
  592. package/src/charts/kinds/Bars.tsx +68 -20
  593. package/src/charts/kinds/Donut.tsx +30 -3
  594. package/src/charts/kinds/DualPanel.tsx +60 -13
  595. package/src/charts/kinds/Heatmap.tsx +90 -33
  596. package/src/charts/kinds/MirroredBars.tsx +21 -3
  597. package/src/charts/kinds/MultiLine.tsx +61 -18
  598. package/src/charts/kinds/StackedArea.tsx +77 -6
  599. package/src/charts/kinds/ZonedLine.tsx +72 -35
  600. package/src/charts/layout/auto-margin.ts +134 -21
  601. package/src/charts/layout/log-ticks.ts +90 -0
  602. package/src/charts/primitives/Axes.tsx +82 -5
  603. package/src/charts/primitives/CartesianChart.tsx +194 -41
  604. package/src/charts/primitives/ChartCard.tsx +111 -27
  605. package/src/charts/primitives/ChartFrame.tsx +111 -26
  606. package/src/charts/primitives/ChartLegend.tsx +43 -13
  607. package/src/charts/primitives/ChartPending.tsx +194 -10
  608. package/src/charts/primitives/ChartTooltip.tsx +37 -5
  609. package/src/charts/primitives/Crosshair.tsx +6 -3
  610. package/src/charts/primitives/chart-frame-layout.ts +212 -0
  611. package/src/charts/primitives/chart-tier.tsx +37 -0
  612. package/src/charts/series.ts +77 -1
  613. package/src/charts/sparklines/BarSparkline.tsx +11 -3
  614. package/src/charts/sparklines/LineSparkline.tsx +18 -4
  615. package/src/charts/sparklines/index.ts +2 -2
  616. package/src/charts/utils/format.ts +19 -27
  617. package/src/charts/utils/ticks.ts +149 -4
  618. package/src/cli/audit-allows.ts +371 -0
  619. package/src/cli/check-theme.ts +197 -0
  620. package/src/cli/doctor.ts +331 -0
  621. package/src/cli/guard-hook.ts +115 -0
  622. package/src/cli/help.ts +101 -0
  623. package/src/cli/index.ts +269 -3434
  624. package/src/cli/init.ts +247 -0
  625. package/src/cli/sync.ts +324 -0
  626. package/src/cli/tokens-css.ts +252 -0
  627. package/src/commands/ShortcutsHelp.tsx +10 -3
  628. package/src/commands/define-commands.ts +2 -2
  629. package/src/commands/define-overlays.ts +216 -1
  630. package/src/commands/index.ts +22 -1
  631. package/src/commands/overlays-mount.tsx +63 -31
  632. package/src/commands/shell-bridge.ts +86 -0
  633. package/src/common/errors.ts +143 -0
  634. package/src/common/index.ts +23 -0
  635. package/src/common/is-dev.ts +4 -0
  636. package/src/common/props.ts +71 -0
  637. package/src/common/query-state-like.ts +64 -0
  638. package/src/common/refs.ts +58 -0
  639. package/src/common/scroll-parent.ts +42 -0
  640. package/src/common/validate.ts +94 -0
  641. package/src/content/article-layout.module.css +11 -4
  642. package/src/content/callout.tsx +13 -7
  643. package/src/content/guide.tsx +14 -4
  644. package/src/content/heading-anchor.tsx +14 -3
  645. package/src/content/index.ts +2 -0
  646. package/src/content/markdown.tsx +6 -8
  647. package/src/content/prose.module.css +9 -7
  648. package/src/content/reading-progress.tsx +40 -15
  649. package/src/content/slug.ts +4 -0
  650. package/src/content/toc.tsx +47 -10
  651. package/src/controls/actions.tsx +90 -10
  652. package/src/controls/compare-filter.tsx +20 -3
  653. package/src/controls/control-group.tsx +11 -4
  654. package/src/controls/controls.module.css +169 -186
  655. package/src/controls/controls.module.css.d.ts +9 -11
  656. package/src/controls/enum-filter.tsx +32 -16
  657. package/src/controls/filter-context.tsx +14 -6
  658. package/src/controls/filter-pill.tsx +6 -3
  659. package/src/controls/filter-set.tsx +32 -13
  660. package/src/controls/filter-sheet.tsx +44 -194
  661. package/src/controls/index.ts +7 -0
  662. package/src/controls/multi-select-filter.tsx +177 -27
  663. package/src/controls/number-filter.tsx +61 -23
  664. package/src/controls/panel-row.module.css +86 -0
  665. package/src/controls/panel-row.module.css.d.ts +13 -0
  666. package/src/controls/panel-row.tsx +392 -0
  667. package/src/controls/range-filter.tsx +76 -39
  668. package/src/controls/search-filter.tsx +30 -12
  669. package/src/controls/select-filter.tsx +11 -1
  670. package/src/controls/slider-control.tsx +159 -0
  671. package/src/controls/sync-button.tsx +13 -3
  672. package/src/controls/toggle-filter.tsx +36 -16
  673. package/src/controls/view-tabs.tsx +63 -11
  674. package/src/controls-dates/date-range-picker.tsx +8 -1
  675. package/src/dashboard/delta-badge.tsx +75 -4
  676. package/src/dashboard/empty-state.tsx +67 -24
  677. package/src/dashboard/index.ts +24 -3
  678. package/src/dashboard/query-state.tsx +142 -85
  679. package/src/{section → dashboard}/section.module.css +11 -0
  680. package/src/dashboard/section.tsx +287 -0
  681. package/src/dashboard/settings-section.tsx +88 -32
  682. package/src/dashboard/stat-card.module.css +46 -31
  683. package/src/dashboard/stat-card.module.css.d.ts +1 -0
  684. package/src/dashboard/stat-card.tsx +186 -128
  685. package/src/dashboard/stat-group.module.css +66 -0
  686. package/src/dashboard/stat-group.module.css.d.ts +8 -0
  687. package/src/dashboard/stat-group.tsx +74 -0
  688. package/src/dashboard/widget-grid.module.css +53 -0
  689. package/src/dashboard/widget-grid.module.css.d.ts +9 -0
  690. package/src/dashboard/widget-grid.tsx +127 -0
  691. package/src/{widget-header → dashboard}/widget-header.module.css +72 -7
  692. package/src/dashboard/widget-header.tsx +292 -0
  693. package/src/data/data-table.module.css +64 -0
  694. package/src/data/data-table.module.css.d.ts +11 -0
  695. package/src/data/data-table.tsx +921 -348
  696. package/src/data/query-branch.ts +53 -0
  697. package/src/data/table.ts +9 -23
  698. package/src/data/virtual-list.tsx +190 -56
  699. package/src/data/virtual.ts +8 -4
  700. package/src/dev-dock.tsx +159 -0
  701. package/src/format/date.ts +184 -0
  702. package/src/format/index.ts +69 -0
  703. package/src/format/measure.ts +31 -0
  704. package/src/format/number.ts +129 -0
  705. package/src/format/shared.ts +63 -0
  706. package/src/forms/FormErrorSummary.tsx +13 -6
  707. package/src/forms/create-form.ts +2 -2
  708. package/src/forms/field.ts +42 -13
  709. package/src/forms/form-layout.module.css +145 -0
  710. package/src/forms/form-layout.module.css.d.ts +20 -0
  711. package/src/forms/form-layout.tsx +305 -0
  712. package/src/forms/form-state.tsx +94 -0
  713. package/src/forms/index.ts +37 -7
  714. package/src/forms/use-field-array.ts +94 -0
  715. package/src/forms/use-form-draft.ts +145 -53
  716. package/src/forms/use-form-submit.ts +199 -0
  717. package/src/guard/index.ts +99 -70
  718. package/src/guard/types.ts +7 -10
  719. package/src/index.ts +92 -19
  720. package/src/notifications/bell.tsx +18 -4
  721. package/src/notifications/center.tsx +18 -5
  722. package/src/notifications/define-notifications.ts +64 -18
  723. package/src/notifications/index.ts +17 -8
  724. package/src/notifications/mount-guard.ts +39 -0
  725. package/src/notifications/notify.tsx +122 -3
  726. package/src/notifications/store.ts +7 -0
  727. package/src/{connectivity → provider}/connectivity-indicator.tsx +7 -2
  728. package/src/{connectivity → provider}/connectivity-provider.tsx +5 -2
  729. package/src/{connectivity/types.ts → provider/connectivity-types.ts} +5 -2
  730. package/src/provider/error-fallback.module.css +21 -0
  731. package/src/provider/error-fallback.module.css.d.ts +9 -0
  732. package/src/provider/index.tsx +116 -28
  733. package/src/provider/lab-theme.ts +2 -1
  734. package/src/{connectivity → provider}/use-connectivity.ts +4 -3
  735. package/src/query-client.ts +100 -0
  736. package/src/{query/devtools.tsx → query-devtools.tsx} +6 -4
  737. package/src/register.ts +20 -4
  738. package/src/router-tanstack/index.ts +4 -15
  739. package/src/router-tanstack/nav.ts +1 -1
  740. package/src/router-tanstack/search-store.ts +6 -14
  741. package/src/router-tanstack/use-nav.ts +19 -14
  742. package/src/shell/account-types.ts +5 -2
  743. package/src/shell/app-brand.module.css +109 -0
  744. package/src/shell/app-brand.module.css.d.ts +14 -0
  745. package/src/shell/app-brand.tsx +140 -0
  746. package/src/shell/app-breadcrumbs.tsx +20 -10
  747. package/src/shell/app-header.module.css +10 -5
  748. package/src/shell/app-main.module.css +118 -0
  749. package/src/shell/app-main.module.css.d.ts +11 -0
  750. package/src/shell/app-mobile-nav.module.css +48 -27
  751. package/src/shell/app-mobile-nav.module.css.d.ts +0 -1
  752. package/src/shell/app-mobile-nav.tsx +71 -12
  753. package/src/shell/app-sidebar-account.tsx +29 -6
  754. package/src/shell/app-sidebar.module.css +63 -96
  755. package/src/shell/app-sidebar.module.css.d.ts +0 -5
  756. package/src/shell/app-sidebar.tsx +61 -105
  757. package/src/shell/index.tsx +179 -93
  758. package/src/shell/isomorphic-layout-effect.ts +14 -0
  759. package/src/shell/mobile-nav-model.ts +67 -11
  760. package/src/shell/nav-count-badge.tsx +6 -1
  761. package/src/{nav/types.ts → shell/nav-types.ts} +15 -6
  762. package/src/shell/page-aside.module.css +174 -0
  763. package/src/shell/page-aside.module.css.d.ts +14 -0
  764. package/src/shell/page-aside.tsx +327 -0
  765. package/src/shell/page-bar.module.css +107 -13
  766. package/src/shell/page-bar.module.css.d.ts +4 -1
  767. package/src/shell/page-bar.tsx +294 -94
  768. package/src/shell/page-title.module.css +33 -0
  769. package/src/shell/page-title.module.css.d.ts +11 -0
  770. package/src/shell/page-title.tsx +57 -0
  771. package/src/shell/sidebar-block-model.ts +1 -1
  772. package/src/shell/sidebar-blocks.tsx +1 -1
  773. package/src/shell/sidebar-search.module.css +1 -1
  774. package/src/shell/sidebar-search.tsx +13 -4
  775. package/src/shell/use-breakpoint.ts +73 -0
  776. package/src/state/fields.ts +50 -53
  777. package/src/state/persisted-or-local.ts +50 -0
  778. package/src/state.ts +0 -9
  779. package/src/styles.css +36 -0
  780. package/src/surfaces.ts +72 -57
  781. package/src/theme/ctl-theme.tsx +10 -0
  782. package/src/theme/index.ts +136 -16
  783. package/src/theme/nav-link.module.css +37 -26
  784. package/src/theme/segmented-control.module.css +22 -0
  785. package/src/theme/segmented-control.module.css.d.ts +1 -0
  786. package/src/{theme-toggle/index.tsx → theme/theme-toggle.tsx} +11 -4
  787. package/src/theme-lab/boot.ts +140 -0
  788. package/src/theme-lab/index.tsx +21 -112
  789. package/src/tokens/index.ts +60 -6
  790. package/src/tokens/palette.ts +70 -30
  791. package/dist/agent/adapter.test.d.ts +0 -2
  792. package/dist/agent/adapter.test.d.ts.map +0 -1
  793. package/dist/agent/ai-sdk-transport.test.d.ts +0 -2
  794. package/dist/agent/ai-sdk-transport.test.d.ts.map +0 -1
  795. package/dist/agent/coalesce.test.d.ts +0 -2
  796. package/dist/agent/coalesce.test.d.ts.map +0 -1
  797. package/dist/agent/foreign.test.d.ts +0 -2
  798. package/dist/agent/foreign.test.d.ts.map +0 -1
  799. package/dist/agent/id.test.d.ts +0 -2
  800. package/dist/agent/id.test.d.ts.map +0 -1
  801. package/dist/agent/merge.test.d.ts +0 -2
  802. package/dist/agent/merge.test.d.ts.map +0 -1
  803. package/dist/agent/part-list.test.d.ts +0 -2
  804. package/dist/agent/part-list.test.d.ts.map +0 -1
  805. package/dist/agent/parts.test.d.ts +0 -2
  806. package/dist/agent/parts.test.d.ts.map +0 -1
  807. package/dist/agent/thread.test.d.ts +0 -2
  808. package/dist/agent/thread.test.d.ts.map +0 -1
  809. package/dist/agent/transport.test.d.ts +0 -2
  810. package/dist/agent/transport.test.d.ts.map +0 -1
  811. package/dist/agent/use-agent-stream.test.d.ts +0 -2
  812. package/dist/agent/use-agent-stream.test.d.ts.map +0 -1
  813. package/dist/agent/use-agent-thread-runs.foreign.test.d.ts +0 -2
  814. package/dist/agent/use-agent-thread-runs.foreign.test.d.ts.map +0 -1
  815. package/dist/agent/use-agent-thread-runs.resume.test.d.ts +0 -2
  816. package/dist/agent/use-agent-thread-runs.resume.test.d.ts.map +0 -1
  817. package/dist/agent/use-agent-thread-runs.test.d.ts +0 -2
  818. package/dist/agent/use-agent-thread-runs.test.d.ts.map +0 -1
  819. package/dist/agent/use-agent-thread-runs.wedge.test.d.ts +0 -2
  820. package/dist/agent/use-agent-thread-runs.wedge.test.d.ts.map +0 -1
  821. package/dist/agent-chat/composer.test.d.ts +0 -2
  822. package/dist/agent-chat/composer.test.d.ts.map +0 -1
  823. package/dist/agent-chat/relative-time.test.d.ts +0 -2
  824. package/dist/agent-chat/relative-time.test.d.ts.map +0 -1
  825. package/dist/agent-chat/thread-feed-row.test.d.ts +0 -2
  826. package/dist/agent-chat/thread-feed-row.test.d.ts.map +0 -1
  827. package/dist/agent-chat/thread-feed.test.d.ts +0 -2
  828. package/dist/agent-chat/thread-feed.test.d.ts.map +0 -1
  829. package/dist/agent-chat/thread-message.test.d.ts +0 -2
  830. package/dist/agent-chat/thread-message.test.d.ts.map +0 -1
  831. package/dist/agent-chat/thread-workspace.test.d.ts +0 -2
  832. package/dist/agent-chat/thread-workspace.test.d.ts.map +0 -1
  833. package/dist/agent-chat/tool-chip.test.d.ts +0 -2
  834. package/dist/agent-chat/tool-chip.test.d.ts.map +0 -1
  835. package/dist/agent-chat/virtualize.type-guard.test.d.ts +0 -2
  836. package/dist/agent-chat/virtualize.type-guard.test.d.ts.map +0 -1
  837. package/dist/charts/cursor/resolve.test.d.ts +0 -2
  838. package/dist/charts/cursor/resolve.test.d.ts.map +0 -1
  839. package/dist/charts/cursor/store.test.d.ts +0 -2
  840. package/dist/charts/cursor/store.test.d.ts.map +0 -1
  841. package/dist/charts/kinds/BandStrip.test.d.ts +0 -2
  842. package/dist/charts/kinds/BandStrip.test.d.ts.map +0 -1
  843. package/dist/charts/kinds/Bars.test.d.ts +0 -2
  844. package/dist/charts/kinds/Bars.test.d.ts.map +0 -1
  845. package/dist/charts/kinds/DualPanel.test.d.ts +0 -2
  846. package/dist/charts/kinds/DualPanel.test.d.ts.map +0 -1
  847. package/dist/charts/kinds/MirroredBars.test.d.ts +0 -2
  848. package/dist/charts/kinds/MirroredBars.test.d.ts.map +0 -1
  849. package/dist/charts/kinds/MultiLine.test.d.ts +0 -2
  850. package/dist/charts/kinds/MultiLine.test.d.ts.map +0 -1
  851. package/dist/charts/kinds/StackedArea.test.d.ts +0 -2
  852. package/dist/charts/kinds/StackedArea.test.d.ts.map +0 -1
  853. package/dist/charts/kinds/ZonedLine.test.d.ts +0 -2
  854. package/dist/charts/kinds/ZonedLine.test.d.ts.map +0 -1
  855. package/dist/charts/layout/auto-margin.test.d.ts +0 -2
  856. package/dist/charts/layout/auto-margin.test.d.ts.map +0 -1
  857. package/dist/charts/primitives/CartesianChart.test.d.ts +0 -2
  858. package/dist/charts/primitives/CartesianChart.test.d.ts.map +0 -1
  859. package/dist/charts/primitives/ChartCard.test.d.ts +0 -2
  860. package/dist/charts/primitives/ChartCard.test.d.ts.map +0 -1
  861. package/dist/charts/primitives/ChartFrame.test.d.ts +0 -2
  862. package/dist/charts/primitives/ChartFrame.test.d.ts.map +0 -1
  863. package/dist/charts/primitives/ChartLegend.test.d.ts +0 -2
  864. package/dist/charts/primitives/ChartLegend.test.d.ts.map +0 -1
  865. package/dist/charts/primitives/ChartTooltip.test.d.ts +0 -2
  866. package/dist/charts/primitives/ChartTooltip.test.d.ts.map +0 -1
  867. package/dist/charts/primitives/XZoneRects.test.d.ts +0 -2
  868. package/dist/charts/primitives/XZoneRects.test.d.ts.map +0 -1
  869. package/dist/charts/series.test.d.ts +0 -2
  870. package/dist/charts/series.test.d.ts.map +0 -1
  871. package/dist/charts/utils/domain.test.d.ts +0 -2
  872. package/dist/charts/utils/domain.test.d.ts.map +0 -1
  873. package/dist/charts/utils/measure-text.test.d.ts +0 -2
  874. package/dist/charts/utils/measure-text.test.d.ts.map +0 -1
  875. package/dist/cli/check-coverage.test.d.ts +0 -2
  876. package/dist/cli/check-coverage.test.d.ts.map +0 -1
  877. package/dist/cli/check-theme.test.d.ts +0 -2
  878. package/dist/cli/check-theme.test.d.ts.map +0 -1
  879. package/dist/cli/help.test.d.ts +0 -2
  880. package/dist/cli/help.test.d.ts.map +0 -1
  881. package/dist/cli/init-sync.test.d.ts +0 -2
  882. package/dist/cli/init-sync.test.d.ts.map +0 -1
  883. package/dist/cli/placement-engine.test.d.ts +0 -2
  884. package/dist/cli/placement-engine.test.d.ts.map +0 -1
  885. package/dist/cli/shipped-versions.test.d.ts +0 -2
  886. package/dist/cli/shipped-versions.test.d.ts.map +0 -1
  887. package/dist/cli/tarball-parity.test.d.ts +0 -2
  888. package/dist/cli/tarball-parity.test.d.ts.map +0 -1
  889. package/dist/cli/template-format.test.d.ts +0 -2
  890. package/dist/cli/template-format.test.d.ts.map +0 -1
  891. package/dist/cli/template-ledger.test.d.ts +0 -2
  892. package/dist/cli/template-ledger.test.d.ts.map +0 -1
  893. package/dist/cli/tokens-css.test.d.ts +0 -2
  894. package/dist/cli/tokens-css.test.d.ts.map +0 -1
  895. package/dist/cli/toolchain-wiring.test.d.ts +0 -2
  896. package/dist/cli/toolchain-wiring.test.d.ts.map +0 -1
  897. package/dist/cli/waiver-audit.test.d.ts +0 -2
  898. package/dist/cli/waiver-audit.test.d.ts.map +0 -1
  899. package/dist/connectivity/connectivity-indicator.d.ts +0 -11
  900. package/dist/connectivity/connectivity-indicator.d.ts.map +0 -1
  901. package/dist/connectivity/connectivity-provider.d.ts.map +0 -1
  902. package/dist/connectivity/index.d.ts +0 -13
  903. package/dist/connectivity/index.d.ts.map +0 -1
  904. package/dist/connectivity/index.js +0 -8
  905. package/dist/connectivity/types.d.ts.map +0 -1
  906. package/dist/connectivity/use-connectivity.d.ts +0 -3
  907. package/dist/connectivity/use-connectivity.d.ts.map +0 -1
  908. package/dist/content/article-actions.test.d.ts +0 -2
  909. package/dist/content/article-actions.test.d.ts.map +0 -1
  910. package/dist/content/article-model.test.d.ts +0 -2
  911. package/dist/content/article-model.test.d.ts.map +0 -1
  912. package/dist/content/block-split.test.d.ts +0 -2
  913. package/dist/content/block-split.test.d.ts.map +0 -1
  914. package/dist/content/heading-components.test.d.ts +0 -2
  915. package/dist/content/heading-components.test.d.ts.map +0 -1
  916. package/dist/content/markdown.test.d.ts +0 -2
  917. package/dist/content/markdown.test.d.ts.map +0 -1
  918. package/dist/content/prose.module.css.test.d.ts +0 -2
  919. package/dist/content/prose.module.css.test.d.ts.map +0 -1
  920. package/dist/content/sanitize.test.d.ts +0 -2
  921. package/dist/content/sanitize.test.d.ts.map +0 -1
  922. package/dist/content/slug.test.d.ts +0 -2
  923. package/dist/content/slug.test.d.ts.map +0 -1
  924. package/dist/controls/actions.test.d.ts +0 -2
  925. package/dist/controls/actions.test.d.ts.map +0 -1
  926. package/dist/controls/actions.type-guard.test.d.ts +0 -2
  927. package/dist/controls/actions.type-guard.test.d.ts.map +0 -1
  928. package/dist/controls/control-group.test.d.ts +0 -2
  929. package/dist/controls/control-group.test.d.ts.map +0 -1
  930. package/dist/controls/controls.router.test.d.ts +0 -2
  931. package/dist/controls/controls.router.test.d.ts.map +0 -1
  932. package/dist/controls/filter-set.test.d.ts +0 -2
  933. package/dist/controls/filter-set.test.d.ts.map +0 -1
  934. package/dist/controls/peer-boundary.test.d.ts +0 -2
  935. package/dist/controls/peer-boundary.test.d.ts.map +0 -1
  936. package/dist/controls/props-contract.test.d.ts +0 -2
  937. package/dist/controls/props-contract.test.d.ts.map +0 -1
  938. package/dist/controls/range-filter.type-guard.test.d.ts +0 -2
  939. package/dist/controls/range-filter.type-guard.test.d.ts.map +0 -1
  940. package/dist/controls/sync-button.test.d.ts +0 -2
  941. package/dist/controls/sync-button.test.d.ts.map +0 -1
  942. package/dist/dashboard/query-state.test.d.ts +0 -2
  943. package/dist/dashboard/query-state.test.d.ts.map +0 -1
  944. package/dist/dashboard/settings-section.test.d.ts +0 -2
  945. package/dist/dashboard/settings-section.test.d.ts.map +0 -1
  946. package/dist/dashboard/stat-card.test.d.ts +0 -2
  947. package/dist/dashboard/stat-card.test.d.ts.map +0 -1
  948. package/dist/data/data-table.test.d.ts +0 -2
  949. package/dist/data/data-table.test.d.ts.map +0 -1
  950. package/dist/data/index.d.ts +0 -38
  951. package/dist/data/index.d.ts.map +0 -1
  952. package/dist/data/index.js +0 -2
  953. package/dist/guard/check-source.test.d.ts +0 -2
  954. package/dist/guard/check-source.test.d.ts.map +0 -1
  955. package/dist/guard/grace.test.d.ts +0 -20
  956. package/dist/guard/grace.test.d.ts.map +0 -1
  957. package/dist/guard/guard-hook.test.d.ts +0 -2
  958. package/dist/guard/guard-hook.test.d.ts.map +0 -1
  959. package/dist/motion/index.d.ts.map +0 -1
  960. package/dist/nav/types.d.ts.map +0 -1
  961. package/dist/notifications/overlay.d.ts +0 -20
  962. package/dist/notifications/overlay.d.ts.map +0 -1
  963. package/dist/notifications/overlay.js +0 -12
  964. package/dist/provider/index.test.d.ts +0 -2
  965. package/dist/provider/index.test.d.ts.map +0 -1
  966. package/dist/provider/lab-theme.test.d.ts +0 -2
  967. package/dist/provider/lab-theme.test.d.ts.map +0 -1
  968. package/dist/query/devtools.d.ts.map +0 -1
  969. package/dist/query/error-message.d.ts +0 -41
  970. package/dist/query/error-message.d.ts.map +0 -1
  971. package/dist/query/error-message.js +0 -30
  972. package/dist/query/error-message.test.d.ts +0 -2
  973. package/dist/query/error-message.test.d.ts.map +0 -1
  974. package/dist/query/index.d.ts +0 -47
  975. package/dist/query/index.d.ts.map +0 -1
  976. package/dist/query/index.js +0 -56
  977. package/dist/router-tanstack/field.d.ts +0 -14
  978. package/dist/router-tanstack/field.d.ts.map +0 -1
  979. package/dist/router-tanstack/field.js +0 -4
  980. package/dist/router-tanstack/multi-search-param-store.d.ts +0 -43
  981. package/dist/router-tanstack/multi-search-param-store.d.ts.map +0 -1
  982. package/dist/router-tanstack/multi-search-param-store.js +0 -30
  983. package/dist/router-tanstack/multi-search-param-store.test.d.ts +0 -2
  984. package/dist/router-tanstack/multi-search-param-store.test.d.ts.map +0 -1
  985. package/dist/router-tanstack/nav.test.d.ts +0 -2
  986. package/dist/router-tanstack/nav.test.d.ts.map +0 -1
  987. package/dist/router-tanstack/search-param-store.d.ts +0 -42
  988. package/dist/router-tanstack/search-param-store.d.ts.map +0 -1
  989. package/dist/router-tanstack/search-param-store.js +0 -25
  990. package/dist/router-tanstack/search-param-store.test.d.ts +0 -2
  991. package/dist/router-tanstack/search-param-store.test.d.ts.map +0 -1
  992. package/dist/router-tanstack/search-store.router.test.d.ts +0 -2
  993. package/dist/router-tanstack/search-store.router.test.d.ts.map +0 -1
  994. package/dist/router-tanstack/search-store.test.d.ts +0 -2
  995. package/dist/router-tanstack/search-store.test.d.ts.map +0 -1
  996. package/dist/router-tanstack/use-nav.test.d.ts +0 -2
  997. package/dist/router-tanstack/use-nav.test.d.ts.map +0 -1
  998. package/dist/section/index.d.ts +0 -2
  999. package/dist/section/index.d.ts.map +0 -1
  1000. package/dist/section/index.js +0 -4
  1001. package/dist/section/section.d.ts +0 -24
  1002. package/dist/section/section.d.ts.map +0 -1
  1003. package/dist/section/section.js +0 -126
  1004. package/dist/section/section.test.d.ts +0 -2
  1005. package/dist/section/section.test.d.ts.map +0 -1
  1006. package/dist/shell/app-mobile-nav.test.d.ts +0 -2
  1007. package/dist/shell/app-mobile-nav.test.d.ts.map +0 -1
  1008. package/dist/shell/app-sidebar.test.d.ts +0 -2
  1009. package/dist/shell/app-sidebar.test.d.ts.map +0 -1
  1010. package/dist/shell/index.test.d.ts +0 -2
  1011. package/dist/shell/index.test.d.ts.map +0 -1
  1012. package/dist/shell/mobile-nav-model.test.d.ts +0 -2
  1013. package/dist/shell/mobile-nav-model.test.d.ts.map +0 -1
  1014. package/dist/shell/page-bar.test.d.ts +0 -2
  1015. package/dist/shell/page-bar.test.d.ts.map +0 -1
  1016. package/dist/state/fields.test.d.ts +0 -2
  1017. package/dist/state/fields.test.d.ts.map +0 -1
  1018. package/dist/state/persisted.test.d.ts +0 -2
  1019. package/dist/state/persisted.test.d.ts.map +0 -1
  1020. package/dist/styles.floor.test.d.ts +0 -2
  1021. package/dist/styles.floor.test.d.ts.map +0 -1
  1022. package/dist/theme/border-coverage.test.d.ts +0 -18
  1023. package/dist/theme/border-coverage.test.d.ts.map +0 -1
  1024. package/dist/theme/contrast.test.d.ts +0 -2
  1025. package/dist/theme/contrast.test.d.ts.map +0 -1
  1026. package/dist/theme/ctl-theme.test.d.ts +0 -2
  1027. package/dist/theme/ctl-theme.test.d.ts.map +0 -1
  1028. package/dist/theme/ctl-tier-coverage.test.d.ts +0 -2
  1029. package/dist/theme/ctl-tier-coverage.test.d.ts.map +0 -1
  1030. package/dist/theme/density-option.test.d.ts +0 -2
  1031. package/dist/theme/density-option.test.d.ts.map +0 -1
  1032. package/dist/theme/density-relations.test.d.ts +0 -2
  1033. package/dist/theme/density-relations.test.d.ts.map +0 -1
  1034. package/dist/theme/derive-theme.test.d.ts +0 -2
  1035. package/dist/theme/derive-theme.test.d.ts.map +0 -1
  1036. package/dist/theme/divider-law.test.d.ts +0 -2
  1037. package/dist/theme/divider-law.test.d.ts.map +0 -1
  1038. package/dist/theme/fonts-option.test.d.ts +0 -2
  1039. package/dist/theme/fonts-option.test.d.ts.map +0 -1
  1040. package/dist/theme/icon-slot.test.d.ts +0 -2
  1041. package/dist/theme/icon-slot.test.d.ts.map +0 -1
  1042. package/dist/theme/layout-rhythm-css.test.d.ts +0 -2
  1043. package/dist/theme/layout-rhythm-css.test.d.ts.map +0 -1
  1044. package/dist/theme/radius-option.test.d.ts +0 -2
  1045. package/dist/theme/radius-option.test.d.ts.map +0 -1
  1046. package/dist/theme/radius.test.d.ts +0 -2
  1047. package/dist/theme/radius.test.d.ts.map +0 -1
  1048. package/dist/theme/shadow-surfaces.test.d.ts +0 -26
  1049. package/dist/theme/shadow-surfaces.test.d.ts.map +0 -1
  1050. package/dist/theme/spacing.test.d.ts +0 -2
  1051. package/dist/theme/spacing.test.d.ts.map +0 -1
  1052. package/dist/theme/use-basalt-spacing.test.d.ts +0 -2
  1053. package/dist/theme/use-basalt-spacing.test.d.ts.map +0 -1
  1054. package/dist/theme-lab/derive-controls.test.d.ts +0 -2
  1055. package/dist/theme-lab/derive-controls.test.d.ts.map +0 -1
  1056. package/dist/theme-lab/derive-state.test.d.ts +0 -2
  1057. package/dist/theme-lab/derive-state.test.d.ts.map +0 -1
  1058. package/dist/theme-lab/theme-lab.test.d.ts +0 -2
  1059. package/dist/theme-lab/theme-lab.test.d.ts.map +0 -1
  1060. package/dist/theme-toggle/index.d.ts +0 -8
  1061. package/dist/theme-toggle/index.d.ts.map +0 -1
  1062. package/dist/tokens/build-fonts-css.test.d.ts +0 -2
  1063. package/dist/tokens/build-fonts-css.test.d.ts.map +0 -1
  1064. package/dist/tokens/build-radius-css.test.d.ts +0 -2
  1065. package/dist/tokens/build-radius-css.test.d.ts.map +0 -1
  1066. package/dist/tokens/chart-margin.test.d.ts +0 -2
  1067. package/dist/tokens/chart-margin.test.d.ts.map +0 -1
  1068. package/dist/tokens/density.test.d.ts +0 -2
  1069. package/dist/tokens/density.test.d.ts.map +0 -1
  1070. package/dist/tokens/derive-radius.test.d.ts +0 -2
  1071. package/dist/tokens/derive-radius.test.d.ts.map +0 -1
  1072. package/dist/tokens/derive.test.d.ts +0 -2
  1073. package/dist/tokens/derive.test.d.ts.map +0 -1
  1074. package/dist/tokens/palette.test.d.ts +0 -2
  1075. package/dist/tokens/palette.test.d.ts.map +0 -1
  1076. package/dist/tokens/text-scale.test.d.ts +0 -2
  1077. package/dist/tokens/text-scale.test.d.ts.map +0 -1
  1078. package/dist/vite-color-mix.test.d.ts +0 -2
  1079. package/dist/vite-color-mix.test.d.ts.map +0 -1
  1080. package/dist/vite.test.d.ts +0 -2
  1081. package/dist/vite.test.d.ts.map +0 -1
  1082. package/dist/widget-header/delta-badge.d.ts +0 -13
  1083. package/dist/widget-header/delta-badge.d.ts.map +0 -1
  1084. package/dist/widget-header/delta-badge.js +0 -29
  1085. package/dist/widget-header/delta-badge.test.d.ts +0 -2
  1086. package/dist/widget-header/delta-badge.test.d.ts.map +0 -1
  1087. package/dist/widget-header/index.d.ts +0 -3
  1088. package/dist/widget-header/index.d.ts.map +0 -1
  1089. package/dist/widget-header/index.js +0 -6
  1090. package/dist/widget-header/no-mantine.test.d.ts +0 -2
  1091. package/dist/widget-header/no-mantine.test.d.ts.map +0 -1
  1092. package/dist/widget-header/widget-header.d.ts +0 -67
  1093. package/dist/widget-header/widget-header.d.ts.map +0 -1
  1094. package/dist/widget-header/widget-header.test.d.ts +0 -2
  1095. package/dist/widget-header/widget-header.test.d.ts.map +0 -1
  1096. package/src/agent/adapter.test.ts +0 -1640
  1097. package/src/agent/ai-sdk-transport.test.ts +0 -658
  1098. package/src/agent/coalesce.test.ts +0 -387
  1099. package/src/agent/foreign.test.ts +0 -51
  1100. package/src/agent/id.test.ts +0 -169
  1101. package/src/agent/merge.test.ts +0 -206
  1102. package/src/agent/part-list.test.tsx +0 -112
  1103. package/src/agent/parts.test.ts +0 -720
  1104. package/src/agent/thread.test.ts +0 -220
  1105. package/src/agent/transport.test.ts +0 -70
  1106. package/src/agent/use-agent-stream.test.tsx +0 -287
  1107. package/src/agent/use-agent-thread-runs.foreign.test.tsx +0 -138
  1108. package/src/agent/use-agent-thread-runs.resume.test.tsx +0 -294
  1109. package/src/agent/use-agent-thread-runs.test.tsx +0 -1120
  1110. package/src/agent/use-agent-thread-runs.wedge.test.tsx +0 -387
  1111. package/src/agent-chat/composer.test.tsx +0 -924
  1112. package/src/agent-chat/relative-time.test.ts +0 -95
  1113. package/src/agent-chat/thread-feed-row.test.tsx +0 -510
  1114. package/src/agent-chat/thread-feed.test.tsx +0 -241
  1115. package/src/agent-chat/thread-message.test.tsx +0 -1285
  1116. package/src/agent-chat/thread-workspace.test.tsx +0 -169
  1117. package/src/agent-chat/tool-chip.test.tsx +0 -139
  1118. package/src/agent-chat/virtualize.type-guard.test.ts +0 -49
  1119. package/src/charts/cursor/resolve.test.ts +0 -151
  1120. package/src/charts/cursor/store.test.ts +0 -64
  1121. package/src/charts/kinds/BandStrip.test.tsx +0 -282
  1122. package/src/charts/kinds/Bars.test.tsx +0 -183
  1123. package/src/charts/kinds/DualPanel.test.tsx +0 -527
  1124. package/src/charts/kinds/MirroredBars.test.tsx +0 -232
  1125. package/src/charts/kinds/MultiLine.test.tsx +0 -218
  1126. package/src/charts/kinds/StackedArea.test.tsx +0 -114
  1127. package/src/charts/kinds/ZonedLine.test.tsx +0 -147
  1128. package/src/charts/layout/auto-margin.test.ts +0 -42
  1129. package/src/charts/primitives/CartesianChart.test.tsx +0 -647
  1130. package/src/charts/primitives/ChartCard.test.tsx +0 -66
  1131. package/src/charts/primitives/ChartFrame.test.tsx +0 -142
  1132. package/src/charts/primitives/ChartLegend.test.tsx +0 -78
  1133. package/src/charts/primitives/ChartTooltip.test.tsx +0 -44
  1134. package/src/charts/primitives/XZoneRects.test.tsx +0 -123
  1135. package/src/charts/series.test.ts +0 -112
  1136. package/src/charts/utils/domain.test.ts +0 -54
  1137. package/src/charts/utils/measure-text.test.ts +0 -36
  1138. package/src/cli/check-coverage.test.ts +0 -297
  1139. package/src/cli/check-theme.test.ts +0 -837
  1140. package/src/cli/help.test.ts +0 -170
  1141. package/src/cli/init-sync.test.ts +0 -343
  1142. package/src/cli/placement-engine.test.ts +0 -298
  1143. package/src/cli/shipped-versions.test.ts +0 -83
  1144. package/src/cli/tarball-parity.test.ts +0 -44
  1145. package/src/cli/template-format.test.ts +0 -64
  1146. package/src/cli/template-ledger.test.ts +0 -219
  1147. package/src/cli/tokens-css.test.ts +0 -249
  1148. package/src/cli/toolchain-wiring.test.ts +0 -1358
  1149. package/src/cli/waiver-audit.test.ts +0 -215
  1150. package/src/connectivity/index.ts +0 -18
  1151. package/src/content/article-actions.test.ts +0 -78
  1152. package/src/content/article-model.test.ts +0 -119
  1153. package/src/content/block-split.test.ts +0 -75
  1154. package/src/content/heading-components.test.tsx +0 -65
  1155. package/src/content/markdown.test.tsx +0 -603
  1156. package/src/content/prose.module.css.test.ts +0 -39
  1157. package/src/content/sanitize.test.ts +0 -659
  1158. package/src/content/slug.test.ts +0 -62
  1159. package/src/controls/actions.test.tsx +0 -252
  1160. package/src/controls/actions.type-guard.test.ts +0 -63
  1161. package/src/controls/control-group.test.tsx +0 -229
  1162. package/src/controls/controls.router.test.tsx +0 -1053
  1163. package/src/controls/filter-set.test.tsx +0 -472
  1164. package/src/controls/peer-boundary.test.ts +0 -85
  1165. package/src/controls/props-contract.test.tsx +0 -137
  1166. package/src/controls/range-filter.type-guard.test.tsx +0 -122
  1167. package/src/controls/sync-button.test.tsx +0 -183
  1168. package/src/dashboard/query-state.test.tsx +0 -152
  1169. package/src/dashboard/settings-section.test.tsx +0 -56
  1170. package/src/dashboard/stat-card.test.tsx +0 -385
  1171. package/src/data/data-table.test.tsx +0 -434
  1172. package/src/data/index.ts +0 -41
  1173. package/src/guard/check-source.test.ts +0 -3388
  1174. package/src/guard/grace.test.ts +0 -158
  1175. package/src/guard/guard-hook.test.ts +0 -203
  1176. package/src/notifications/overlay.tsx +0 -64
  1177. package/src/provider/index.test.tsx +0 -101
  1178. package/src/provider/lab-theme.test.ts +0 -147
  1179. package/src/query/error-message.test.ts +0 -67
  1180. package/src/query/error-message.ts +0 -70
  1181. package/src/query/index.ts +0 -97
  1182. package/src/router-tanstack/field.ts +0 -35
  1183. package/src/router-tanstack/multi-search-param-store.test.ts +0 -124
  1184. package/src/router-tanstack/multi-search-param-store.ts +0 -106
  1185. package/src/router-tanstack/nav.test.ts +0 -111
  1186. package/src/router-tanstack/search-param-store.test.ts +0 -135
  1187. package/src/router-tanstack/search-param-store.ts +0 -100
  1188. package/src/router-tanstack/search-store.router.test.tsx +0 -882
  1189. package/src/router-tanstack/search-store.test.ts +0 -578
  1190. package/src/router-tanstack/use-nav.test.tsx +0 -187
  1191. package/src/section/index.ts +0 -1
  1192. package/src/section/section.test.tsx +0 -204
  1193. package/src/section/section.tsx +0 -220
  1194. package/src/shell/app-mobile-nav.test.tsx +0 -403
  1195. package/src/shell/app-sidebar.test.tsx +0 -509
  1196. package/src/shell/index.test.tsx +0 -428
  1197. package/src/shell/mobile-nav-model.test.ts +0 -517
  1198. package/src/shell/page-bar.test.tsx +0 -316
  1199. package/src/state/fields.test.tsx +0 -512
  1200. package/src/state/persisted.test.ts +0 -91
  1201. package/src/styles.floor.test.ts +0 -36
  1202. package/src/theme/border-coverage.test.ts +0 -272
  1203. package/src/theme/contrast.test.ts +0 -354
  1204. package/src/theme/ctl-theme.test.tsx +0 -275
  1205. package/src/theme/ctl-tier-coverage.test.ts +0 -170
  1206. package/src/theme/density-option.test.ts +0 -96
  1207. package/src/theme/density-relations.test.ts +0 -506
  1208. package/src/theme/derive-theme.test.ts +0 -101
  1209. package/src/theme/divider-law.test.ts +0 -259
  1210. package/src/theme/fonts-option.test.ts +0 -75
  1211. package/src/theme/icon-slot.test.tsx +0 -275
  1212. package/src/theme/layout-rhythm-css.test.ts +0 -72
  1213. package/src/theme/radius-option.test.ts +0 -86
  1214. package/src/theme/radius.test.ts +0 -142
  1215. package/src/theme/shadow-surfaces.test.ts +0 -488
  1216. package/src/theme/spacing.test.ts +0 -495
  1217. package/src/theme/use-basalt-spacing.test.tsx +0 -55
  1218. package/src/theme-lab/derive-controls.test.ts +0 -94
  1219. package/src/theme-lab/derive-state.test.ts +0 -71
  1220. package/src/theme-lab/theme-lab.test.ts +0 -46
  1221. package/src/tokens/build-fonts-css.test.ts +0 -52
  1222. package/src/tokens/build-radius-css.test.ts +0 -25
  1223. package/src/tokens/chart-margin.test.ts +0 -39
  1224. package/src/tokens/density.test.ts +0 -259
  1225. package/src/tokens/derive-radius.test.ts +0 -62
  1226. package/src/tokens/derive.test.ts +0 -30
  1227. package/src/tokens/palette.test.ts +0 -114
  1228. package/src/tokens/text-scale.test.ts +0 -51
  1229. package/src/vite-color-mix.test.ts +0 -46
  1230. package/src/vite.test.ts +0 -423
  1231. package/src/widget-header/delta-badge.test.tsx +0 -60
  1232. package/src/widget-header/delta-badge.tsx +0 -57
  1233. package/src/widget-header/index.ts +0 -2
  1234. package/src/widget-header/no-mantine.test.ts +0 -33
  1235. package/src/widget-header/widget-header.test.tsx +0 -187
  1236. package/src/widget-header/widget-header.tsx +0 -213
  1237. /package/dist/{motion/index.js → common/motion.js} +0 -0
  1238. /package/dist/{section → dashboard}/section.module.css.d.ts +0 -0
  1239. /package/dist/{widget-header → dashboard}/widget-header.module.css.d.ts +0 -0
  1240. /package/dist/{connectivity/types.js → provider/connectivity-types.js} +0 -0
  1241. /package/dist/{connectivity → provider}/use-connectivity.js +0 -0
  1242. /package/dist/{nav/types.js → shell/nav-types.js} +0 -0
  1243. /package/src/{motion/index.ts → common/motion.ts} +0 -0
  1244. /package/src/{section → dashboard}/section.module.css.d.ts +0 -0
  1245. /package/src/{widget-header → dashboard}/widget-header.module.css.d.ts +0 -0
@@ -1,1358 +0,0 @@
1
- /**
2
- * Round-4 regression suite: the toolchain reported green while enforcing nothing.
3
- *
4
- * Five consumers hit one bug in five shapes — `doctor` counting checks it could not run as passes,
5
- * `init` scaffolding a guard that scans zero files, seeded configs whose `extends` resolved to
6
- * nothing, and `tokens:css` output basalt's own guard rejected. Every test below pins one of those
7
- * false-greens shut. The unit under test is always "does the tool TELL you", not "does it work".
8
- */
9
- import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
10
- import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
11
- import { tmpdir } from 'node:os'
12
- import { join, resolve } from 'node:path'
13
- import { fileURLToPath } from 'node:url'
14
-
15
- import { buildPaletteCss } from '../tokens'
16
- import {
17
- checkTheme,
18
- doctor,
19
- fontsCss,
20
- init,
21
- MANIFEST_PATH,
22
- normalizeColorFunctions,
23
- sync,
24
- tokensCss,
25
- } from './index.ts'
26
-
27
- const PKG_ROOT = fileURLToPath(new URL('../../', import.meta.url))
28
- const CLI_VERSION = (
29
- JSON.parse(readFileSync(resolve(PKG_ROOT, 'package.json'), 'utf8')) as { version: string }
30
- ).version
31
-
32
- let dir: string
33
-
34
- function write(relPath: string, content: string): void {
35
- const abs = join(dir, relPath)
36
- mkdirSync(resolve(abs, '..'), { recursive: true })
37
- writeFileSync(abs, content, 'utf8')
38
- }
39
-
40
- function read(relPath: string): string {
41
- return readFileSync(join(dir, relPath), 'utf8')
42
- }
43
-
44
- function readPkg(relPath = 'package.json'): Record<string, unknown> {
45
- return JSON.parse(read(relPath)) as Record<string, unknown>
46
- }
47
-
48
- /** Captures both streams — doctor/init/check-theme pick one or the other by outcome. */
49
- function capture(fn: () => number): { code: number; log: string } {
50
- const originalLog = console.log
51
- const originalError = console.error
52
- let log = ''
53
- const sink = (...args: unknown[]) => {
54
- log += `${args.join(' ')}\n`
55
- }
56
- console.log = sink
57
- console.error = sink
58
- try {
59
- return { code: fn(), log }
60
- } finally {
61
- console.log = originalLog
62
- console.error = originalError
63
- }
64
- }
65
-
66
- /** The install `doctor` should find — same version as the running CLI, so version checks pass. */
67
- function installBasalt(at = ''): void {
68
- write(
69
- join(at, 'node_modules/basalt-ui/package.json'),
70
- JSON.stringify({ name: 'basalt-ui', version: CLI_VERSION }),
71
- )
72
- // The shipped preset itself — `doctor`'s oxlint-preset check resolves the `extends` target now,
73
- // so an install without it is (correctly) a failure.
74
- write(join(at, 'node_modules/basalt-ui/configs/oxlint.json'), '{}')
75
- }
76
-
77
- /** A repo where every framework-profile doctor check passes. */
78
- function healthyFixture(): void {
79
- write('package.json', JSON.stringify({ name: 'fixture', basalt: { roots: ['src'] } }))
80
- write('src/app.tsx', 'export const App = () => null\n')
81
- write('.oxlintrc.json', '{ "extends": ["./node_modules/basalt-ui/configs/oxlint.json"] }')
82
- write(MANIFEST_PATH, JSON.stringify({ version: 1, files: {}, basaltVersion: CLI_VERSION }))
83
- installBasalt()
84
- }
85
-
86
- beforeEach(() => {
87
- dir = mkdtempSync(resolve(tmpdir(), 'basalt-wiring-'))
88
- })
89
-
90
- afterEach(() => {
91
- rmSync(dir, { recursive: true, force: true })
92
- })
93
-
94
- describe('doctor — a check that cannot run is not a check that passed', () => {
95
- it('is green on a fully-wired repo', () => {
96
- healthyFixture()
97
- const { code, log } = capture(() => doctor(dir))
98
- expect(log).toContain('All checks passed.')
99
- expect(code).toBe(0)
100
- })
101
-
102
- it('reports SKIPPED and exits non-zero when basalt-ui does not resolve, instead of dropping the checks', () => {
103
- healthyFixture()
104
- rmSync(join(dir, 'node_modules'), { recursive: true, force: true })
105
- const { code, log } = capture(() => doctor(dir))
106
- expect(log).toContain('SKIPPED')
107
- expect(log).toContain('does not resolve')
108
- expect(log).not.toContain('All checks passed.')
109
- expect(code).toBe(1)
110
- })
111
-
112
- it('finds an install that lives beside a workspace package rather than at the root', () => {
113
- healthyFixture()
114
- rmSync(join(dir, 'node_modules'), { recursive: true, force: true })
115
- write(
116
- 'package.json',
117
- JSON.stringify({ name: 'root', workspaces: ['packages/*'], basalt: { roots: ['src'] } }),
118
- )
119
- write('packages/lib/package.json', JSON.stringify({ name: 'lib' }))
120
- installBasalt('packages/lib')
121
- const { log } = capture(() => doctor(dir))
122
- expect(log).toContain('packages/lib/node_modules/basalt-ui')
123
- expect(log).not.toContain('SKIPPED')
124
- })
125
-
126
- it('hard-fails when check-theme would scan zero files — the two commands may not disagree', () => {
127
- healthyFixture()
128
- rmSync(join(dir, 'src'), { recursive: true, force: true })
129
- const { code, log } = capture(() => doctor(dir))
130
- expect(log).toContain('guard-scan: check-theme would scan 0 files')
131
- expect(code).toBe(1)
132
- })
133
-
134
- it('hard-fails when .oxlintrc.json does not extend the shipped preset', () => {
135
- healthyFixture()
136
- write('.oxlintrc.json', '{ "rules": {} }')
137
- const { code, log } = capture(() => doctor(dir))
138
- expect(log).toContain('does NOT extend the shipped preset')
139
- expect(log).toContain('--merge-lint')
140
- expect(code).toBe(1)
141
- })
142
-
143
- it('hard-fails when .oxlintrc.json is missing entirely', () => {
144
- healthyFixture()
145
- rmSync(join(dir, '.oxlintrc.json'), { force: true })
146
- const { code, log } = capture(() => doctor(dir))
147
- expect(log).toContain('.oxlintrc.json missing')
148
- expect(code).toBe(1)
149
- })
150
-
151
- it('relocates to the single workspace package carrying the basalt config', () => {
152
- write('package.json', JSON.stringify({ name: 'root', workspaces: ['apps/*'] }))
153
- write('apps/web/package.json', JSON.stringify({ name: 'web', basalt: { roots: ['src'] } }))
154
- write('apps/web/src/app.tsx', 'export const App = () => null\n')
155
- write(
156
- 'apps/web/.oxlintrc.json',
157
- '{ "extends": ["./node_modules/basalt-ui/configs/oxlint.json"] }',
158
- )
159
- write(
160
- join('apps/web', MANIFEST_PATH),
161
- JSON.stringify({ version: 1, files: {}, basaltVersion: CLI_VERSION }),
162
- )
163
- installBasalt('apps/web')
164
- const { code, log } = capture(() => doctor(dir))
165
- expect(log).toContain('reporting on ./apps/web')
166
- expect(code).toBe(0)
167
- })
168
- })
169
-
170
- describe('doctor — tokens-only profile', () => {
171
- function tokensOnlyFixture(): void {
172
- write('package.json', JSON.stringify({ name: 'static-site' }))
173
- installBasalt()
174
- }
175
-
176
- it('does not tell a Mantine-free consumer to run init, and passes on a matching version', () => {
177
- tokensOnlyFixture()
178
- const { code, log } = capture(() => doctor(dir))
179
- expect(log).toContain('profile: tokens-only')
180
- expect(log).not.toContain('run `basalt-ui init` to scaffold')
181
- expect(log).toContain('matches the installed basalt-ui')
182
- expect(code).toBe(0)
183
- })
184
-
185
- it('still reaches the CLI-vs-installed version check that the manifest failure used to shadow', () => {
186
- write('package.json', JSON.stringify({ name: 'static-site' }))
187
- write(
188
- 'node_modules/basalt-ui/package.json',
189
- JSON.stringify({ name: 'basalt-ui', version: '0.4.2' }),
190
- )
191
- const { code, log } = capture(() => doctor(dir))
192
- expect(log).toContain('0.4.2')
193
- expect(code).toBe(0) // a stale bunx fetch is a warning, not a hard failure
194
- })
195
-
196
- it('--framework forces the full profile on the same repo', () => {
197
- tokensOnlyFixture()
198
- const { code, log } = capture(() => doctor(dir, ['--framework']))
199
- expect(log).toContain(`${MANIFEST_PATH} missing`)
200
- expect(code).toBe(1)
201
- })
202
-
203
- it('--tokens-only forces the token profile on a Mantine repo', () => {
204
- healthyFixture()
205
- const { log } = capture(() => doctor(dir, ['--tokens-only']))
206
- expect(log).toContain('profile: tokens-only')
207
- expect(log).toContain('n/a')
208
- })
209
- })
210
-
211
- describe('init — describing the repo it is actually in', () => {
212
- it('writes basalt.roots from the bun workspaces layout', () => {
213
- write('package.json', JSON.stringify({ name: 'root', workspaces: ['apps/*', 'packages/*'] }))
214
- write(
215
- 'apps/web/package.json',
216
- JSON.stringify({ name: 'web', dependencies: { 'basalt-ui': '^1' } }),
217
- )
218
- write('apps/web/src/app.tsx', 'export const App = () => null\n')
219
- write('packages/api/package.json', JSON.stringify({ name: 'api' }))
220
- write('packages/api/src/server.ts', 'export const serve = () => null\n')
221
-
222
- const { log } = capture(() => init(dir))
223
- // Only the basalt-consuming package is a design-guard root — a backend `src/` is not.
224
- expect((readPkg()['basalt'] as { roots: string[] }).roots).toEqual(['apps/web/src'])
225
- expect(log).toContain('"roots"')
226
- expect(read('.github/workflows/check.yml')).toContain("'apps/web/src/**'")
227
- })
228
-
229
- it("writes basalt.roots ['src'] for a single-package repo", () => {
230
- write('package.json', JSON.stringify({ name: 'app' }))
231
- write('src/app.tsx', 'export const App = () => null\n')
232
- init(dir)
233
- expect((readPkg()['basalt'] as { roots: string[] }).roots).toEqual(['src'])
234
- })
235
-
236
- it('says so loudly when it cannot infer roots, instead of seeding one that matches nothing', () => {
237
- write('package.json', JSON.stringify({ name: 'app' }))
238
- const { log } = capture(() => init(dir))
239
- expect(log).toContain('could not infer "basalt.roots"')
240
- expect(readPkg()['basalt']).toBeUndefined()
241
- })
242
-
243
- it('never overwrites a roots the consumer already set', () => {
244
- write('package.json', JSON.stringify({ name: 'app', basalt: { roots: ['custom'] } }))
245
- write('src/app.tsx', 'export const App = () => null\n')
246
- init(dir)
247
- expect((readPkg()['basalt'] as { roots: string[] }).roots).toEqual(['custom'])
248
- })
249
-
250
- it('adds a lint:basalt script wiring oxlint and the guard together', () => {
251
- write('package.json', JSON.stringify({ name: 'app' }))
252
- write('src/app.tsx', 'export const App = () => null\n')
253
- init(dir)
254
- const scripts = readPkg()['scripts'] as Record<string, string>
255
- expect(scripts['lint:basalt']).toContain('oxlint .')
256
- expect(scripts['lint:basalt']).toContain('check-theme')
257
- })
258
-
259
- it('leaves an existing lint:basalt script alone', () => {
260
- write('package.json', JSON.stringify({ name: 'app', scripts: { 'lint:basalt': 'mine' } }))
261
- init(dir)
262
- expect((readPkg()['scripts'] as Record<string, string>)['lint:basalt']).toBe('mine')
263
- })
264
-
265
- it('names every kept file AND what keeping it costs', () => {
266
- write('package.json', JSON.stringify({ name: 'app' }))
267
- write('src/app.tsx', 'export const App = () => null\n')
268
- write('.oxlintrc.json', '{ "rules": {} }')
269
- const { log } = capture(() => init(dir))
270
- expect(log).toContain('Kept (already present')
271
- expect(log).toContain('.oxlintrc.json')
272
- expect(log).toContain('is NOT active')
273
- // The aggregate count stays, but it is no longer the only thing said.
274
- expect(log).toMatch(/\d+ written, \d+ kept/)
275
- })
276
-
277
- it('warns that adoption on an existing app is a lint-debt event, with the plugin count', () => {
278
- write('package.json', JSON.stringify({ name: 'app' }))
279
- const { log } = capture(() => init(dir))
280
- expect(log).toContain('Lint debt')
281
- expect(log).toContain('jsx-a11y')
282
- expect(log).toContain('lefthook install')
283
- })
284
-
285
- it('--merge-lint splices the shipped preset into an .oxlintrc.json init would otherwise keep', () => {
286
- write('package.json', JSON.stringify({ name: 'app' }))
287
- write('.oxlintrc.json', JSON.stringify({ rules: { 'no-debugger': 'error' } }, null, 2))
288
- const { log } = capture(() => init(dir, { mergeLint: true }))
289
- const cfg = JSON.parse(read('.oxlintrc.json')) as { extends: string[]; rules: unknown }
290
- expect(cfg.extends[0]).toContain('basalt-ui/configs/oxlint.json')
291
- expect(cfg.rules).toEqual({ 'no-debugger': 'error' })
292
- expect(log).toContain('--merge-lint')
293
- })
294
-
295
- it('--merge-lint is idempotent', () => {
296
- write('package.json', JSON.stringify({ name: 'app' }))
297
- write('.oxlintrc.json', '{ "extends": ["./node_modules/basalt-ui/configs/oxlint.json"] }')
298
- const { log } = capture(() => init(dir, { mergeLint: true }))
299
- expect(log).toContain('already extends')
300
- })
301
- })
302
-
303
- describe('init — seeds resolve basalt where it actually installed', () => {
304
- it('renders the extends paths and the CI bin from a workspace-level install, not the root', () => {
305
- write('package.json', JSON.stringify({ name: 'root', workspaces: ['packages/*'] }))
306
- write('packages/lib/package.json', JSON.stringify({ name: 'lib' }))
307
- write('packages/lib/src/index.ts', 'export const x = 1\n')
308
- installBasalt('packages/lib')
309
-
310
- capture(() => init(dir))
311
-
312
- expect(read('.oxlintrc.json')).toContain(
313
- './packages/lib/node_modules/basalt-ui/configs/oxlint.json',
314
- )
315
- expect(read('lefthook.yml')).toContain(
316
- 'packages/lib/node_modules/basalt-ui/configs/lefthook.yml',
317
- )
318
- // The CI step runs the LOCAL bin — bunx would silently fetch a different copy from npm.
319
- expect(read('.github/workflows/check.yml')).toContain(
320
- 'packages/lib/node_modules/.bin/basalt-ui check-theme',
321
- )
322
- expect(read('.github/workflows/check.yml')).not.toContain('{{BASALT_BIN}}')
323
- })
324
-
325
- it('warns loudly when basalt-ui resolves nowhere, rather than seeding a silent dead path', () => {
326
- write('package.json', JSON.stringify({ name: 'app' }))
327
- write('src/app.tsx', 'export const App = () => null\n')
328
- const { log } = capture(() => init(dir))
329
- expect(log).toContain('does not resolve from')
330
- })
331
- })
332
-
333
- describe('check-theme — finds the config a root-invoked hook cannot see', () => {
334
- it('relocates to the one workspace package carrying a basalt config', () => {
335
- write('package.json', JSON.stringify({ name: 'root', workspaces: ['apps/*'] }))
336
- write('apps/web/package.json', JSON.stringify({ name: 'web', basalt: { roots: ['src'] } }))
337
- write('apps/web/src/app.tsx', 'export const App = () => null\n')
338
- const { code, log } = capture(() => checkTheme(dir))
339
- expect(log).toContain('running in ./apps/web')
340
- expect(log).toContain('no off-palette colors')
341
- expect(code).toBe(0)
342
- })
343
-
344
- it('reports ambiguity rather than guessing when two packages carry a config', () => {
345
- ambiguousWorkspace()
346
- const { code, log } = capture(() => checkTheme(dir))
347
- expect(log).toContain('2 packages below it carry one')
348
- expect(log).toContain('BASALT_CWD')
349
- expect(code).toBe(1)
350
- })
351
- })
352
-
353
- /** A root with no basalt config and two workspace packages that each carry one. */
354
- function ambiguousWorkspace(): void {
355
- write('package.json', JSON.stringify({ name: 'root', workspaces: ['apps/*'] }))
356
- write('apps/a/package.json', JSON.stringify({ name: 'a', basalt: { roots: ['src'] } }))
357
- write('apps/a/src/app.tsx', 'export const A = () => null\n')
358
- write('apps/b/package.json', JSON.stringify({ name: 'b', basalt: { roots: ['src'] } }))
359
- write('apps/b/src/app.tsx', 'export const B = () => null\n')
360
- }
361
-
362
- describe('doctor — an ambiguous project is terminal, exactly as it is for check-theme', () => {
363
- it('short-circuits instead of running every check against the wrong root', () => {
364
- ambiguousWorkspace()
365
- const { code, log } = capture(() => doctor(dir))
366
- expect(log).toContain('2 packages below it carry one')
367
- expect(log).toContain('BASALT_CWD')
368
- expect(code).toBe(1)
369
- // The bug this pins shut: falling back to the invocation cwd ran the remaining checks against
370
- // a root that has no config, burying the real error under failures caused by looking there.
371
- expect(log).not.toContain('would scan 0 files')
372
- expect(log).not.toContain('.oxlintrc.json missing')
373
- expect(log).not.toContain('manifest.json missing')
374
- })
375
-
376
- it('agrees with check-theme on the same tree', () => {
377
- ambiguousWorkspace()
378
- expect(capture(() => doctor(dir)).code).toBe(capture(() => checkTheme(dir)).code)
379
- })
380
-
381
- it('still runs normally once BASALT_CWD picks a package', () => {
382
- ambiguousWorkspace()
383
- const { code, log } = capture(() => doctor(join(dir, 'apps/a')))
384
- expect(log).not.toContain('packages below it carry one')
385
- expect(code).toBe(1) // no install / no manifest there — but the checks RAN
386
- expect(log).toContain('guard-scan')
387
- })
388
- })
389
-
390
- describe('doctor — no check may vanish from the report', () => {
391
- it('SKIPS the spacing-scale check when there is no manifest, rather than printing nothing', () => {
392
- // A Mantine app that never ran `init`: framework profile, no manifest to compare against.
393
- write(
394
- 'package.json',
395
- JSON.stringify({
396
- name: 'app',
397
- dependencies: { '@mantine/core': '^9.0.0' },
398
- basalt: { roots: ['src'] },
399
- }),
400
- )
401
- write('src/app.tsx', 'export const App = () => null\n')
402
- write('.oxlintrc.json', '{ "extends": ["./node_modules/basalt-ui/configs/oxlint.json"] }')
403
- installBasalt()
404
- const { code, log } = capture(() => doctor(dir))
405
- expect(log).toContain('spacing scale')
406
- expect(log).toContain('SKIPPED')
407
- expect(code).toBe(1)
408
- })
409
-
410
- it('calls the spacing check n/a — not silent — for a tokens-only consumer', () => {
411
- write('package.json', JSON.stringify({ name: 'site', basalt: { profile: 'tokens-only' } }))
412
- installBasalt()
413
- const { log } = capture(() => doctor(dir))
414
- expect(log).toContain('spacing scale: n/a')
415
- })
416
- })
417
-
418
- describe('--tokens-only and --framework are alternatives, not a precedence question', () => {
419
- it('check-theme rejects both together', () => {
420
- write('package.json', JSON.stringify({ name: 'app', basalt: { roots: ['src'] } }))
421
- write('src/app.tsx', 'export const App = () => null\n')
422
- const { code, log } = capture(() => checkTheme(dir, ['--tokens-only', '--framework']))
423
- expect(code).toBe(1)
424
- expect(log).toContain('alternatives')
425
- })
426
-
427
- it('doctor rejects both together', () => {
428
- healthyFixture()
429
- const { code, log } = capture(() => doctor(dir, ['--tokens-only', '--framework']))
430
- expect(code).toBe(1)
431
- expect(log).toContain('alternatives')
432
- })
433
- })
434
-
435
- describe('check-theme — the markup scan reaches the app shell', () => {
436
- it('scans index.html and public/*.webmanifest beside a root, not just inside it', () => {
437
- // The exact argo shape: roots point at `apps/dashboard/src`, the raw hex lives one level up in
438
- // index.html and in public/site.webmanifest. Widening the extension filter alone missed both.
439
- write('package.json', JSON.stringify({ name: 'app', basalt: { roots: ['apps/web/src'] } }))
440
- write('apps/web/src/app.tsx', 'export const App = () => null\n')
441
- write('apps/web/index.html', '<meta name="theme-color" content="#EDEFF2" />\n')
442
- write('apps/web/public/site.webmanifest', '{ "theme_color": "#242424" }\n')
443
- const { code, log } = capture(() => checkTheme(dir))
444
- expect(log).toContain('apps/web/index.html')
445
- expect(log).toContain('apps/web/public/site.webmanifest')
446
- expect(code).toBe(1)
447
- })
448
-
449
- it('leaves a sibling docs/ full of throwaway HTML alone', () => {
450
- write('package.json', JSON.stringify({ name: 'app', basalt: { roots: ['src'] } }))
451
- write('src/app.tsx', 'export const App = () => null\n')
452
- write('docs/poc.html', '<div style="color: #ff0000">x</div>\n')
453
- const { code, log } = capture(() => checkTheme(dir))
454
- expect(log).not.toContain('docs/poc.html')
455
- expect(code).toBe(0)
456
- })
457
-
458
- it('never blanket-scans .json, but scans one named in basalt.include', () => {
459
- write('package.json', JSON.stringify({ name: 'app', basalt: { roots: ['src'] } }))
460
- write('src/app.tsx', 'export const App = () => null\n')
461
- write('src/fixtures/data.json', '{ "note": "#ff0000 in a test fixture" }\n')
462
- expect(capture(() => checkTheme(dir)).code).toBe(0)
463
-
464
- write(
465
- 'package.json',
466
- JSON.stringify({ name: 'app', basalt: { roots: ['src'], include: ['app/manifest.json'] } }),
467
- )
468
- write('app/manifest.json', '{ "theme_color": "#EDEFF2" }\n')
469
- const { code, log } = capture(() => checkTheme(dir))
470
- expect(log).toContain('app/manifest.json')
471
- expect(log).not.toContain('data.json')
472
- expect(code).toBe(1)
473
- })
474
- })
475
-
476
- describe('check-theme — tokens-only profile', () => {
477
- function mantineFreeApp(): void {
478
- write(
479
- 'package.json',
480
- JSON.stringify({ name: 'site', basalt: { roots: ['src'], profile: 'tokens-only' } }),
481
- )
482
- write('src/form.tsx', "export const F = () => <input />\nexport const C = '#ff0000'\n")
483
- }
484
-
485
- it('drops the Mantine-coupled kinds while the color kinds keep firing', () => {
486
- mantineFreeApp()
487
- const { log } = capture(() => checkTheme(dir))
488
- expect(log).toContain('tokens-only profile')
489
- expect(log).not.toContain('raw-form-control')
490
- expect(log).toContain('raw-hex')
491
- })
492
-
493
- it('--framework forces the full kind set back on', () => {
494
- mantineFreeApp()
495
- const { log } = capture(() => checkTheme(dir, ['--framework']))
496
- expect(log).not.toContain('tokens-only profile')
497
- expect(log).toContain('raw-form-control')
498
- })
499
-
500
- it('is never INFERRED — an undeclared Mantine-free repo keeps every kind live', () => {
501
- // Silencing 17 kinds off the mere absence of @mantine/core is this round's own failure mode:
502
- // a repo holding Mantine in a workspace package would switch off half its guard silently.
503
- write('package.json', JSON.stringify({ name: 'app', basalt: { roots: ['src'] } }))
504
- write('src/form.tsx', 'export const F = () => <input />\n')
505
- const { log } = capture(() => checkTheme(dir))
506
- expect(log).not.toContain('tokens-only profile')
507
- expect(log).toContain('raw-form-control')
508
- })
509
-
510
- it('doctor infers the shape and tells the consumer to declare it', () => {
511
- write('package.json', JSON.stringify({ name: 'site' }))
512
- installBasalt()
513
- const { log } = capture(() => doctor(dir))
514
- expect(log).toContain('profile: tokens-only')
515
- expect(log).toContain('"profile": "tokens-only"')
516
- })
517
- })
518
-
519
- describe('tokens:css — a committable artifact', () => {
520
- // Restated verbatim on purpose: the guard compares line 1 byte-for-byte, so a reword is a
521
- // contract change. It is command-NEUTRAL — it used to name `tokens:css`, which told anyone
522
- // reading a fonts:css file to overwrite it with the palette sheet.
523
- const GENERATED_FIRST_LINE =
524
- '/* @generated basalt-ui — do not edit; regenerate with the command on the next line */'
525
-
526
- function emit(flags: string[], out = 'out.css'): string {
527
- write('package.json', JSON.stringify({ name: 'fixture' }))
528
- expect(tokensCss([...flags, '--out', out], dir)).toBe(0)
529
- return read(out)
530
- }
531
-
532
- it('opens with the exact @generated marker the theme guard skips on', () => {
533
- const css = emit([])
534
- expect(css.split('\n')[0]).toBe(GENERATED_FIRST_LINE)
535
- expect(css.split('\n')[1]).toContain(`basalt-ui ${CLI_VERSION}`)
536
- })
537
-
538
- it('ends with a trailing newline and normalizes rgba() argument spacing', () => {
539
- const css = emit([])
540
- expect(css.endsWith('}\n')).toBe(true)
541
- expect(css).not.toMatch(/rgba\(\d+,\d/)
542
- expect(css).toContain('rgba(255, 255, 255, 0.6)')
543
- })
544
-
545
- it('emits no trailing-zero alpha — the last thing keeping the sheet out of a linter', () => {
546
- // prettier rewrites `rgba(28, 25, 23, 0.10)` → `0.1`, so the two light shadow tokens failed
547
- // `format/prettier` while `--fix` put the file straight into `tokens:css --check` drift. The
548
- // consumer's eslint-ignore entry for it survived two releases.
549
- const css = emit([])
550
- expect(css).not.toMatch(/rgba\([^)]*\d\.\d*0\)/)
551
- expect(css).toContain('rgba(28, 25, 23, 0.1)')
552
- expect(normalizeColorFunctions('a: rgba(1, 2, 3, 0.10); b: rgba(1, 2, 3, 1.0);')).toBe(
553
- 'a: rgba(1, 2, 3, 0.1); b: rgba(1, 2, 3, 1);',
554
- )
555
- })
556
-
557
- it('changes no token VALUE — the body is buildPaletteCss modulo the formatting', () => {
558
- const body = emit([]).split('\n').slice(2).join('\n')
559
- expect(body).toBe(`${normalizeColorFunctions(buildPaletteCss())}\n`)
560
- })
561
-
562
- it('--selector-class emits a class selector instead of an attribute one', () => {
563
- const css = emit(['--selector-class', 'dark', '--default-scheme', 'light'])
564
- expect(css).toContain(':root.dark {')
565
- expect(css).toContain(':root,\n:root.light {')
566
- expect(css).not.toContain('data-basalt-scheme-class')
567
- })
568
-
569
- it('rejects --selector-class together with --selector-attribute', () => {
570
- write('package.json', JSON.stringify({ name: 'fixture' }))
571
- const { code } = capture(() =>
572
- tokensCss(
573
- ['--selector-class', 'dark', '--selector-attribute', 'data-x', '--out', 'o.css'],
574
- dir,
575
- ),
576
- )
577
- expect(code).toBe(1)
578
- })
579
-
580
- it('rejects a --selector-class that is not a plain class name', () => {
581
- write('package.json', JSON.stringify({ name: 'fixture' }))
582
- expect(tokensCss(['--selector-class', "dark'] { x", '--out', 'o.css'], dir)).toBe(1)
583
- })
584
-
585
- it('--check passes on an up-to-date file and fails on drift', () => {
586
- emit(['--only', 'core'])
587
- expect(
588
- capture(() => tokensCss(['--only', 'core', '--out', 'out.css', '--check'], dir)).code,
589
- ).toBe(0)
590
- write('out.css', 'nope\n')
591
- const { code, log } = capture(() =>
592
- tokensCss(['--only', 'core', '--out', 'out.css', '--check'], dir),
593
- )
594
- expect(code).toBe(1)
595
- expect(log).toContain('differs from what')
596
- })
597
-
598
- it('--check fails when the file has never been generated', () => {
599
- write('package.json', JSON.stringify({ name: 'fixture' }))
600
- const { code, log } = capture(() => tokensCss(['--out', 'missing.css', '--check'], dir))
601
- expect(code).toBe(1)
602
- expect(log).toContain('does not exist')
603
- })
604
-
605
- it('--check without --out is an error, not a silent stdout dump', () => {
606
- write('package.json', JSON.stringify({ name: 'fixture' }))
607
- expect(capture(() => tokensCss(['--check'], dir)).code).toBe(1)
608
- })
609
- })
610
-
611
- describe('the @generated marker, end to end — emit, then scan', () => {
612
- /** A tokens-only consumer whose only CSS is the sheet `tokens:css` writes into its own src/. */
613
- function siteWithEmittedTokens(flags: string[] = []): void {
614
- write(
615
- 'package.json',
616
- JSON.stringify({ name: 'site', basalt: { roots: ['src'], profile: 'tokens-only' } }),
617
- )
618
- write('src/app.tsx', 'export const App = () => null\n')
619
- expect(tokensCss([...flags, '--out', 'src/tokens.css'], dir)).toBe(0)
620
- }
621
-
622
- it('the sheet tokens:css just wrote scans to zero findings — 116 was the round-4 bug', () => {
623
- siteWithEmittedTokens(['--only', 'core'])
624
- // Sanity: it really is full of the raw hex the guard would otherwise report.
625
- expect(read('src/tokens.css').match(/#[0-9a-f]{6}/gi)?.length ?? 0).toBeGreaterThan(50)
626
- const { code, log } = capture(() => checkTheme(dir))
627
- expect(log).not.toContain('src/tokens.css')
628
- expect(code).toBe(0)
629
- })
630
-
631
- it('holds for every emission shape, not just the default', () => {
632
- for (const flags of [
633
- [],
634
- ['--only', 'all'],
635
- ['--selector-class', 'dark'],
636
- ['--default-scheme', 'light'],
637
- ]) {
638
- rmSync(join(dir, 'src'), { recursive: true, force: true })
639
- siteWithEmittedTokens(flags)
640
- expect(capture(() => checkTheme(dir)).code).toBe(0)
641
- }
642
- })
643
-
644
- it('a HAND-FORGED marker on ordinary source suppresses nothing', () => {
645
- siteWithEmittedTokens(['--only', 'core'])
646
- const header = read('src/tokens.css').split('\n').slice(0, 2).join('\n')
647
- // Exactly what an agent (or anyone) could do: copy the two header lines onto a real file.
648
- write('src/forged.css', `${header}\n.btn { color: #ff0000; }\n`)
649
- const { code, log } = capture(() => checkTheme(dir))
650
- expect(log).toContain('src/forged.css')
651
- expect(log).toContain('raw-hex')
652
- expect(code).toBe(1)
653
- })
654
-
655
- it('a STRIPPED marker makes the emitted sheet ordinary source again', () => {
656
- siteWithEmittedTokens(['--only', 'core'])
657
- const stripped = read('src/tokens.css').split('\n').slice(2).join('\n')
658
- write('src/tokens.css', stripped)
659
- const { code, log } = capture(() => checkTheme(dir))
660
- expect(log).toContain('src/tokens.css')
661
- expect(code).toBe(1)
662
- })
663
-
664
- it('appending a real rule to the emitted sheet un-exempts it', () => {
665
- siteWithEmittedTokens(['--only', 'core'])
666
- write('src/tokens.css', `${read('src/tokens.css')}.btn { color: #ff0000; }\n`)
667
- const { code, log } = capture(() => checkTheme(dir))
668
- expect(log).toContain('src/tokens.css')
669
- expect(code).toBe(1)
670
- })
671
- })
672
-
673
- describe('fonts:css — the typeface half of the token layer', () => {
674
- it('emits the shipped --basalt-font-* stacks under the generated header', () => {
675
- write('package.json', JSON.stringify({ name: 'fixture' }))
676
- expect(fontsCss(['--out', 'fonts.css'], dir)).toBe(0)
677
- const css = read('fonts.css')
678
- expect(css.split('\n')[0]).toContain('@generated basalt-ui')
679
- expect(css).toContain('--basalt-font-sans:')
680
- expect(css).toContain('--basalt-font-head:')
681
- expect(css).toContain('--basalt-font-mono:')
682
- expect(css).toContain('Nunito Sans Variable')
683
- expect(css.endsWith('}\n')).toBe(true)
684
- })
685
-
686
- it('emits a stylesheet check-theme accepts end to end', () => {
687
- write('package.json', JSON.stringify({ name: 'site', basalt: { roots: ['src'] } }))
688
- write('src/app.tsx', 'export const App = () => null\n')
689
- expect(fontsCss(['--out', 'src/fonts.css'], dir)).toBe(0)
690
- expect(capture(() => checkTheme(dir)).code).toBe(0)
691
- })
692
-
693
- // The header used to hard-code `regenerate with \`bunx basalt-ui tokens:css\`` on every emitted
694
- // file — following it on a fonts sheet overwrote the typefaces with the palette.
695
- it('never tells the reader to regenerate a fonts sheet with tokens:css', () => {
696
- write('package.json', JSON.stringify({ name: 'fixture' }))
697
- expect(fontsCss(['--out', 'fonts.css'], dir)).toBe(0)
698
- const [marker, provenance] = read('fonts.css').split('\n')
699
- expect(marker).not.toContain('tokens:css')
700
- expect(provenance).toContain('`basalt-ui fonts:css')
701
- })
702
-
703
- it('has the same --check drift gate as tokens:css', () => {
704
- write('package.json', JSON.stringify({ name: 'fixture' }))
705
- fontsCss(['--out', 'fonts.css'], dir)
706
- expect(capture(() => fontsCss(['--out', 'fonts.css', '--check'], dir)).code).toBe(0)
707
- write('fonts.css', ':root {}\n')
708
- expect(capture(() => fontsCss(['--out', 'fonts.css', '--check'], dir)).code).toBe(1)
709
- })
710
- })
711
-
712
- // ──────────────────────────────────────────────────────────────────────────────
713
- // Round 5: the same false-green class, one layer in — inside the checks that
714
- // were added to prevent it.
715
- // ──────────────────────────────────────────────────────────────────────────────
716
-
717
- describe('doctor — the oxlint-preset check resolves the path, not just the string', () => {
718
- it('hard-fails when the extends entry names a path that does not exist', () => {
719
- healthyFixture()
720
- // Exactly the state a monorepo consumer upgrades into: a well-shaped root-relative entry, and
721
- // basalt installed beside the package that depends on it. `bunx oxlint .` dies with NotFound
722
- // here, and 1.20.0's own check printed ✓ in the same tree.
723
- rmSync(join(dir, 'node_modules/basalt-ui/configs/oxlint.json'))
724
- const { code, log } = capture(() => doctor(dir))
725
- expect(code).toBe(1)
726
- expect(log).toContain('does not exist')
727
- expect(log).toContain('NotFound')
728
- expect(log).not.toContain('✓ .oxlintrc.json extends')
729
- })
730
-
731
- it('names the RESOLVED install path as the remedy when the entry points somewhere else', () => {
732
- write(
733
- 'package.json',
734
- JSON.stringify({
735
- name: 'root',
736
- workspaces: ['packages/*'],
737
- basalt: { roots: ['packages/web/src'] },
738
- }),
739
- )
740
- write('packages/web/package.json', JSON.stringify({ name: 'web' }))
741
- write('packages/web/src/app.tsx', 'export const App = () => null\n')
742
- write(MANIFEST_PATH, JSON.stringify({ version: 1, files: {}, basaltVersion: CLI_VERSION }))
743
- write('.oxlintrc.json', '{ "extends": ["./node_modules/basalt-ui/configs/oxlint.json"] }')
744
- installBasalt('packages/web')
745
- const { code, log } = capture(() => doctor(dir))
746
- expect(code).toBe(1)
747
- expect(log).toContain('packages/web/node_modules/basalt-ui/configs/oxlint.json')
748
- })
749
-
750
- it('passes — and quotes the entry — when the target really is there', () => {
751
- healthyFixture()
752
- const { code, log } = capture(() => doctor(dir))
753
- expect(code).toBe(0)
754
- expect(log).toContain('extends the shipped basalt-ui oxlint preset')
755
- })
756
- })
757
-
758
- describe('doctor — the lefthook extends target, the seam that fails SILENTLY', () => {
759
- it('hard-fails on a missing target: lefthook merges it to zero commands and exits 0', () => {
760
- healthyFixture()
761
- write('lefthook.yml', 'extends:\n - node_modules/basalt-ui/configs/lefthook.yml\n')
762
- const { code, log } = capture(() => doctor(dir))
763
- expect(code).toBe(1)
764
- expect(log).toContain('ZERO commands')
765
- })
766
-
767
- it('passes when the target resolves', () => {
768
- healthyFixture()
769
- write('node_modules/basalt-ui/configs/lefthook.yml', 'pre-commit:\n commands: {}\n')
770
- write('lefthook.yml', 'extends:\n - node_modules/basalt-ui/configs/lefthook.yml\n')
771
- const { code, log } = capture(() => doctor(dir))
772
- expect(code).toBe(0)
773
- expect(log).toContain('extends the shipped lefthook preset')
774
- })
775
-
776
- it('passes a repo that spells the gate out instead of extending — the check is the GATE', () => {
777
- // linewatch's root lefthook.yml runs all three jobs with `root: 'web/'`, because `extends`
778
- // merges commands WITHOUT their working directory: the preset's `bunx oxlint` would run at the
779
- // repo root where there is no .oxlintrc.json. Testing for the extends STRING warned at a
780
- // correctly-wired repo and prescribed a change that would have broken it.
781
- healthyFixture()
782
- write(
783
- 'lefthook.yml',
784
- "pre-commit:\n commands:\n theme-guard:\n root: 'web/'\n" +
785
- ' run: bunx basalt-ui check-theme\n',
786
- )
787
- const { code, log } = capture(() => doctor(dir))
788
- expect(code).toBe(0)
789
- expect(log).toContain('a pre-commit command runs check-theme directly — the gate exists')
790
- })
791
-
792
- it('is advisory, never confident, when neither the text nor `lefthook dump` settles it', () => {
793
- // A check that is confidently wrong is worse than one that admits its limit: an `include:` or a
794
- // remote config carries commands this reader never sees.
795
- healthyFixture()
796
- write('lefthook.yml', 'pre-commit:\n commands:\n mine:\n run: echo hi\n')
797
- const { code, log } = capture(() => doctor(dir))
798
- expect(code).toBe(0)
799
- expect(log).toContain('cannot tell a missing gate from one wired through an `include:`')
800
- })
801
-
802
- it('is not applicable when the repo runs no lefthook at all', () => {
803
- healthyFixture()
804
- const { code, log } = capture(() => doctor(dir))
805
- expect(code).toBe(0)
806
- expect(log).toContain('lefthook-preset: n/a')
807
- })
808
- })
809
-
810
- describe('sync — basalt.roots is drift, and sync is the drift command', () => {
811
- it('backfills the inferred roots when the key was never written', () => {
812
- write('package.json', JSON.stringify({ name: 'fixture' }))
813
- write('src/app.tsx', 'export const App = () => null\n')
814
- capture(() => init(dir))
815
- // Undo init's write to reproduce a repo scaffolded before roots existed, then upgrade.
816
- write('package.json', JSON.stringify({ name: 'fixture' }))
817
- const { log } = capture(() => sync({}, dir))
818
- expect(log).toContain('"roots": ["src"]')
819
- expect((readPkg()['basalt'] as { roots: string[] }).roots).toEqual(['src'])
820
- })
821
-
822
- it('never overwrites a declared roots — it names what the declaration does not cover', () => {
823
- write(
824
- 'package.json',
825
- JSON.stringify({
826
- name: 'root',
827
- workspaces: ['packages/*'],
828
- basalt: { roots: ['packages/web/src'] },
829
- }),
830
- )
831
- write(MANIFEST_PATH, JSON.stringify({ version: 1, files: {} }))
832
- write(
833
- 'packages/web/package.json',
834
- JSON.stringify({ name: 'web', dependencies: { 'basalt-ui': '*' } }),
835
- )
836
- write('packages/web/src/app.tsx', 'export const App = () => null\n')
837
- write(
838
- 'packages/api/package.json',
839
- JSON.stringify({ name: 'api', dependencies: { 'basalt-ui': '*' } }),
840
- )
841
- write('packages/api/src/server.ts', 'export const server = null\n')
842
- const { log } = capture(() => sync({}, dir))
843
- expect((readPkg()['basalt'] as { roots: string[] }).roots).toEqual(['packages/web/src'])
844
- expect(log).toContain('keeping your "basalt.roots"')
845
- expect(log).toContain('packages/api/src')
846
- })
847
-
848
- it('--check writes nothing, but still says the key is missing', () => {
849
- write('package.json', JSON.stringify({ name: 'fixture' }))
850
- write(MANIFEST_PATH, JSON.stringify({ version: 1, files: {} }))
851
- write('src/app.tsx', 'export const App = () => null\n')
852
- const { log } = capture(() => sync({ check: true }, dir))
853
- expect(readPkg()['basalt']).toBeUndefined()
854
- expect(log).toContain('"basalt.roots" is not set')
855
- })
856
-
857
- it('reports a lefthook extends target that no longer resolves', () => {
858
- write('package.json', JSON.stringify({ name: 'fixture', basalt: { roots: ['src'] } }))
859
- write(MANIFEST_PATH, JSON.stringify({ version: 1, files: {} }))
860
- write('src/app.tsx', 'export const App = () => null\n')
861
- write('lefthook.yml', 'extends:\n - node_modules/basalt-ui/configs/lefthook.yml\n')
862
- const { log } = capture(() => sync({}, dir))
863
- expect(log).toContain('ZERO commands')
864
- })
865
- })
866
-
867
- describe('init — the seeded lefthook.yml pins the guard to the local bin', () => {
868
- it('renders a BASALT_BIN env override, the one key an extends target cannot eat', () => {
869
- write('package.json', JSON.stringify({ name: 'fixture' }))
870
- write('src/app.tsx', 'export const App = () => null\n')
871
- installBasalt()
872
- capture(() => init(dir))
873
- const seeded = read('lefthook.yml')
874
- expect(seeded).toContain('BASALT_BIN: node_modules/.bin/basalt-ui')
875
- expect(seeded).toContain('extends:')
876
- })
877
- })
878
-
879
- // ── sync must never silently behave as init ───────────────────────────────────────────────────
880
- //
881
- // Found independently in argo and rb. Run from `apps/dashboard` — the package that actually
882
- // depends on basalt-ui — `sync` printed `0 updated, 20 recreated`, wrote a second `basalt` key into
883
- // that package.json, and scaffolded a complete competing install (`.basalt/`, `.oxlintrc.json`,
884
- // `.oxfmtrc.json`, `DESIGN.md`, 13 rules, 3 skills, and in rb's case a `src/routes/__root.tsx`
885
- // into an app that hand-writes its router) beside the real one at the repo root. Nothing warned.
886
- // Both consumers reverted it by hand. `check-theme` and `doctor` already relocate; `sync` — the one
887
- // command that WRITES — did not.
888
-
889
- /** A workspace whose basalt install lives at the ROOT, with an app package below it. */
890
- function installAtRoot(): void {
891
- write(
892
- 'package.json',
893
- JSON.stringify({
894
- name: 'root',
895
- workspaces: ['apps/*'],
896
- basalt: { roots: ['apps/dashboard/src'] },
897
- }),
898
- )
899
- write(MANIFEST_PATH, JSON.stringify({ version: 1, files: {}, basaltVersion: CLI_VERSION }))
900
- write(
901
- 'apps/dashboard/package.json',
902
- JSON.stringify({ name: 'dashboard', dependencies: { 'basalt-ui': '*' } }),
903
- )
904
- write('apps/dashboard/src/app.tsx', 'export const App = () => null\n')
905
- }
906
-
907
- describe('sync — a scaffold is init’s decision, never a drift refresh’s', () => {
908
- it('refuses from a workspace sub-package and points at the install it found above', () => {
909
- installAtRoot()
910
- const { code, log } = capture(() => sync({}, join(dir, 'apps/dashboard')))
911
- expect(code).toBe(1)
912
- expect(log).toContain('refusing to scaffold')
913
- expect(log).toContain("This repo's install is at ../..")
914
- })
915
-
916
- it('writes NOTHING when it refuses — not the files, and not the second basalt key', () => {
917
- installAtRoot()
918
- capture(() => sync({}, join(dir, 'apps/dashboard')))
919
- // The `basalt.roots` backfill is half the damage: a second config site means check-theme
920
- // silently scans a different tree depending on which directory it was invoked from.
921
- expect(JSON.parse(read('apps/dashboard/package.json'))['basalt']).toBeUndefined()
922
- expect(readdirSync(join(dir, 'apps/dashboard')).toSorted()).toEqual(['package.json', 'src'])
923
- })
924
-
925
- it('refuses in a repo that was never a consumer, and names init as the command that is', () => {
926
- write('package.json', JSON.stringify({ name: 'fixture' }))
927
- const { code, log } = capture(() => sync({}, dir))
928
- expect(code).toBe(1)
929
- expect(log).toContain('No manifest exists at any ancestor either')
930
- expect(log).toContain('basalt-ui init')
931
- })
932
-
933
- it('relocates to the single workspace package carrying the install, exactly as doctor does', () => {
934
- write('package.json', JSON.stringify({ name: 'root', workspaces: ['apps/*'] }))
935
- write('apps/web/package.json', JSON.stringify({ name: 'web', basalt: { roots: ['src'] } }))
936
- write('apps/web/src/app.tsx', 'export const App = () => null\n')
937
- write(join('apps/web', MANIFEST_PATH), JSON.stringify({ version: 1, files: {} }))
938
- const { code, log } = capture(() => sync({ check: true }, dir))
939
- expect(log).toContain('running in ./apps/web, where it lives')
940
- // --check on an unsynced package is legitimately stale; what matters is WHERE it looked.
941
- expect(code).toBe(1)
942
- })
943
-
944
- it('short-circuits on an ambiguous workspace instead of picking one to scaffold', () => {
945
- ambiguousWorkspace()
946
- const { code, log } = capture(() => sync({}, dir))
947
- expect(code).toBe(1)
948
- expect(log).toContain('2 packages below it carry one')
949
- expect(log).toContain('BASALT_CWD')
950
- })
951
-
952
- it('says created, not recreated, for a file the ledger never recorded', () => {
953
- // `0 updated, 20 recreated` described a scaffold as a refresh. "Recreated" means the ledger
954
- // placed it once and it went missing; a file basalt never wrote here is CREATED.
955
- write('package.json', JSON.stringify({ name: 'fixture', basalt: { roots: ['src'] } }))
956
- write('src/app.tsx', 'export const App = () => null\n')
957
- write(MANIFEST_PATH, JSON.stringify({ version: 1, files: {} }))
958
- const { log } = capture(() => sync({}, dir))
959
- expect(log).toMatch(/0 updated, \d+ created, 0 recreated/)
960
- })
961
-
962
- it('says recreated for a managed file the ledger recorded and someone deleted', () => {
963
- write('package.json', JSON.stringify({ name: 'fixture', basalt: { roots: ['src'] } }))
964
- write('src/app.tsx', 'export const App = () => null\n')
965
- capture(() => init(dir))
966
- const placed = '.claude/rules/basalt-tokens.md'
967
- rmSync(join(dir, placed))
968
- const { log } = capture(() => sync({}, dir))
969
- expect(log).toContain('1 recreated')
970
- })
971
- })
972
-
973
- // ── doctor's exit status, pinned per outcome ──────────────────────────────────────────────────
974
- //
975
- // argo reported two hard failures exiting 0. It does not reproduce (see the report), but the gap
976
- // that let 1.20.0 ship a SKIPPED-exits-0 bug was that the tests asserted printed TEXT and never the
977
- // status. These assert the status for every outcome, including the combinations.
978
-
979
- describe('doctor — exit status per outcome, not per printed string', () => {
980
- it('pass-only exits 0 and is the only state that may print "All checks passed"', () => {
981
- healthyFixture()
982
- const { code, log } = capture(() => doctor(dir))
983
- expect([code, log.includes('All checks passed')]).toEqual([0, true])
984
- })
985
-
986
- it('warn-only exits 0 — a warning is a schedule, not a build break', () => {
987
- healthyFixture()
988
- write(
989
- 'node_modules/basalt-ui/package.json',
990
- JSON.stringify({ name: 'basalt-ui', version: '0.4.2' }),
991
- )
992
- const { code, log } = capture(() => doctor(dir))
993
- expect(log).toContain('warning(s)')
994
- expect(log).not.toContain('hard failure')
995
- expect(log).not.toContain('All checks passed')
996
- expect(code).toBe(0)
997
- })
998
-
999
- it('one hard failure exits 1', () => {
1000
- healthyFixture()
1001
- rmSync(join(dir, '.oxlintrc.json'))
1002
- const { code, log } = capture(() => doctor(dir))
1003
- expect(log).toContain('1 hard failure(s)')
1004
- expect(code).toBe(1)
1005
- })
1006
-
1007
- it('TWO hard failures exit 1 — the shape argo reported as exiting 0', () => {
1008
- // argo's exact pair: no manifest and no .oxlintrc.json, in a Mantine repo (so the profile
1009
- // stays `framework` and both checks are real rather than n/a).
1010
- healthyFixture()
1011
- write(
1012
- 'package.json',
1013
- JSON.stringify({
1014
- name: 'fixture',
1015
- dependencies: { '@mantine/core': '^9.3.0' },
1016
- basalt: { roots: ['src'] },
1017
- }),
1018
- )
1019
- rmSync(join(dir, '.oxlintrc.json'))
1020
- rmSync(join(dir, MANIFEST_PATH))
1021
- const { code, log } = capture(() => doctor(dir))
1022
- expect(log).toContain('2 hard failure(s)')
1023
- expect(code).toBe(1)
1024
- })
1025
-
1026
- it('a SKIPPED check exits 1 on its own, with no failure and no warning', () => {
1027
- write('package.json', JSON.stringify({ name: 'fixture', basalt: { roots: ['src'] } }))
1028
- write('src/app.tsx', 'export const App = () => null\n')
1029
- write('.oxlintrc.json', '{ "extends": ["./node_modules/basalt-ui/configs/oxlint.json"] }')
1030
- write(MANIFEST_PATH, JSON.stringify({ version: 1, files: {}, basaltVersion: CLI_VERSION }))
1031
- const { code, log } = capture(() => doctor(dir))
1032
- expect(log).toContain('SKIPPED')
1033
- expect(code).toBe(1)
1034
- })
1035
-
1036
- it('an ambiguous project exits 1 before any check runs', () => {
1037
- ambiguousWorkspace()
1038
- expect(capture(() => doctor(dir)).code).toBe(1)
1039
- })
1040
-
1041
- it('contradictory profile flags exit 1', () => {
1042
- healthyFixture()
1043
- expect(capture(() => doctor(dir, ['--tokens-only', '--framework'])).code).toBe(1)
1044
- })
1045
- })
1046
-
1047
- // ── the project resolver, for a repo that declares no workspaces ───────────────────────────────
1048
- //
1049
- // linewatch keeps its whole basalt consumer in `web/` and declares no `workspaces` field anywhere.
1050
- // Measured at its repo root, all three project-scoped commands got that wrong in a different way:
1051
- // `check-theme` printed `✓ no off-palette colors` having scanned ZERO files, `doctor` inferred
1052
- // `tokens-only` for a full Mantine consumer, and `sync` refused while naming `basalt-ui init` as
1053
- // the remedy — the one command that WOULD have written a competing install at the root. The
1054
- // workspace-declaration path already handled image-share's `apps/admin`; nothing handled this.
1055
-
1056
- /** linewatch's shape: no `workspaces` field, the whole consumer one directory down. */
1057
- function undeclaredChild(child = 'web'): void {
1058
- write('package.json', JSON.stringify({ name: 'root' }))
1059
- write(`${child}/package.json`, JSON.stringify({ name: child, basalt: { roots: ['src'] } }))
1060
- write(`${child}/src/app.tsx`, "export const c = '#ff0000'\n")
1061
- write(join(child, MANIFEST_PATH), JSON.stringify({ version: 1, files: {} }))
1062
- }
1063
-
1064
- describe('resolveProjectDir — a repo that declares no workspaces still has a layout', () => {
1065
- it('sync relocates into the undeclared child instead of refusing with init as the advice', () => {
1066
- undeclaredChild()
1067
- const { log } = capture(() => sync({ check: true }, dir))
1068
- expect(log).toContain('running in ./web, where it lives')
1069
- expect(log).not.toContain('basalt-ui init')
1070
- })
1071
-
1072
- it('sync writes NOTHING at the root it was invoked from', () => {
1073
- undeclaredChild()
1074
- capture(() => sync({}, dir))
1075
- expect(readdirSync(dir).toSorted()).toEqual(['package.json', 'web'])
1076
- expect(readPkg()['basalt']).toBeUndefined()
1077
- })
1078
-
1079
- it('check-theme scans the child rather than reporting green over zero files', () => {
1080
- undeclaredChild()
1081
- const { code, log } = capture(() => checkTheme(dir, []))
1082
- expect(log).toContain('running in ./web, where it lives')
1083
- expect(log).not.toContain('no off-palette colors')
1084
- expect(code).toBe(1)
1085
- })
1086
-
1087
- it('doctor reports on the child, so it cannot infer tokens-only from the empty root', () => {
1088
- undeclaredChild()
1089
- const { log } = capture(() => doctor(dir, []))
1090
- expect(log).toContain(join(dir, 'web'))
1091
- expect(log).not.toContain('profile: tokens-only')
1092
- })
1093
-
1094
- it('two undeclared children are ambiguous, named, and never guessed between', () => {
1095
- undeclaredChild('web')
1096
- undeclaredChild('admin')
1097
- const { code, log } = capture(() => sync({}, dir))
1098
- expect(code).toBe(1)
1099
- expect(log).toContain('2 packages below it carry one')
1100
- expect(log).toContain('./admin')
1101
- expect(log).toContain('./web')
1102
- expect(readdirSync(dir)).not.toContain('DESIGN.md')
1103
- })
1104
-
1105
- it('a declared workspaces field still wins — the layout scan is a fallback, not a union', () => {
1106
- // `stray/` carries a basalt key but is not a workspace package. Treating both as candidates
1107
- // would turn a working single-candidate repo into an ambiguity error on upgrade.
1108
- write('package.json', JSON.stringify({ name: 'root', workspaces: ['apps/*'] }))
1109
- write('apps/web/package.json', JSON.stringify({ name: 'web', basalt: { roots: ['src'] } }))
1110
- write('apps/web/src/app.tsx', 'export const App = () => null\n')
1111
- write(join('apps/web', MANIFEST_PATH), JSON.stringify({ version: 1, files: {} }))
1112
- write('stray/package.json', JSON.stringify({ name: 'stray', basalt: { roots: ['src'] } }))
1113
- const { log } = capture(() => sync({ check: true }, dir))
1114
- expect(log).toContain('running in ./apps/web, where it lives')
1115
- })
1116
- })
1117
-
1118
- // ── sync honours the profile doctor already reads ──────────────────────────────────────────────
1119
- //
1120
- // rollhook is tokens-only in both apps and declares it. `sync` told it to run `basalt-ui init` —
1121
- // the exact advice `doctor`, in the same directory at the same version, exists to prevent — and
1122
- // exited 1, which also put `sync --check` out of reach of the one CI drift gate every other
1123
- // consumer runs.
1124
-
1125
- describe('sync — tokens-only is a pass, not a refusal', () => {
1126
- function tokensOnly(): void {
1127
- write(
1128
- 'package.json',
1129
- JSON.stringify({ name: 'marketing', basalt: { roots: ['src'], profile: 'tokens-only' } }),
1130
- )
1131
- write('src/app.ts', 'export const c = 1\n')
1132
- }
1133
-
1134
- it('reports n/a and exits 0, mirroring doctor’s manifest row', () => {
1135
- tokensOnly()
1136
- const { code, log } = capture(() => sync({}, dir))
1137
- expect(code).toBe(0)
1138
- expect(log).toContain('n/a — a tokens-only consumer has no scaffold to reconcile')
1139
- expect(log).not.toContain('basalt-ui init')
1140
- })
1141
-
1142
- it('is wirable into CI: --check exits 0 instead of failing on a scaffold it must not have', () => {
1143
- tokensOnly()
1144
- expect(capture(() => sync({ check: true, flags: [] }, dir)).code).toBe(0)
1145
- })
1146
-
1147
- it('writes nothing — no manifest, no DESIGN.md, no basalt.roots backfill', () => {
1148
- tokensOnly()
1149
- capture(() => sync({}, dir))
1150
- expect(readdirSync(dir).toSorted()).toEqual(['package.json', 'src'])
1151
- })
1152
-
1153
- it('--framework forces the full profile back on', () => {
1154
- tokensOnly()
1155
- const { code, log } = capture(() => sync({ flags: ['--framework'] }, dir))
1156
- expect(code).toBe(1)
1157
- expect(log).toContain('refusing to scaffold')
1158
- })
1159
-
1160
- it('contradictory profile flags exit 1, exactly as they do for check-theme and doctor', () => {
1161
- tokensOnly()
1162
- expect(capture(() => sync({ flags: ['--tokens-only', '--framework'] }, dir)).code).toBe(1)
1163
- })
1164
- })
1165
-
1166
- // ── no shipped artifact carries a version number that goes stale ───────────────────────────────
1167
- //
1168
- // `DESIGN.md` is a seed: written once at init, then consumer-owned and never reconciled. Its
1169
- // opener stamped `{{BASALT_VERSION}}`, so across the seven consumers the same line read 1.0.0,
1170
- // 1.9.0, 1.21.0 and 1.22.0 under an identical 1.22.0 install — reported three rounds running as
1171
- // four separate doc bugs. It was one bug: a version number in a file nothing ever rewrites.
1172
-
1173
- describe('DESIGN.md carries no version, and sync heals the ones already written', () => {
1174
- function seeded(): void {
1175
- write('package.json', JSON.stringify({ name: 'fixture', basalt: { roots: ['src'] } }))
1176
- write('src/app.tsx', 'export const App = () => null\n')
1177
- capture(() => init(dir))
1178
- }
1179
-
1180
- it('a fresh scaffold stamps no version at all', () => {
1181
- seeded()
1182
- expect(read('DESIGN.md')).not.toMatch(/Managed by basalt-ui \(\d+\.\d+\.\d+\)/)
1183
- expect(read('DESIGN.md')).toContain('run `basalt-ui doctor` for the version')
1184
- })
1185
-
1186
- it('sync rewrites a stale opener in place and leaves the rest of the file alone', () => {
1187
- seeded()
1188
- const consumerOwned = '\n## Series\n\nOurs: hrv, rhr.\n'
1189
- write('DESIGN.md', `# app — Design\n\n> Managed by basalt-ui (1.9.0). Thin.\n${consumerOwned}`)
1190
- capture(() => sync({}, dir))
1191
- expect(read('DESIGN.md')).toContain(
1192
- '> Managed by basalt-ui — run `basalt-ui doctor` for the version. Thin.',
1193
- )
1194
- expect(read('DESIGN.md')).toContain(consumerOwned)
1195
- })
1196
-
1197
- it('--check writes nothing, including the heal', () => {
1198
- seeded()
1199
- const stale = '# app — Design\n\n> Managed by basalt-ui (1.9.0). Thin.\n'
1200
- write('DESIGN.md', stale)
1201
- capture(() => sync({ check: true }, dir))
1202
- expect(read('DESIGN.md')).toBe(stale)
1203
- })
1204
-
1205
- it('leaves an opener the consumer already rewrote by hand untouched', () => {
1206
- seeded()
1207
- const owned = '# app — Design\n\n> Our own words entirely.\n'
1208
- write('DESIGN.md', owned)
1209
- capture(() => sync({}, dir))
1210
- expect(read('DESIGN.md')).toBe(owned)
1211
- })
1212
- })
1213
-
1214
- // ── The ascend direction ──────────────────────────────────────────────────────
1215
- // Round 9, basalt-ui-obsidian: from `apps/demo` — a package with no `basalt` key of its own —
1216
- // `check-theme` invented `roots: ["src"]`, scanned 22 of the repo's 44 guarded files, printed
1217
- // `✓ no off-palette colors`, and reported the invention back as `basalt.roots (src)`. Its
1218
- // `--audit-allows` then reported `0 live` for a repo carrying one live waiver: a clean slate from
1219
- // the one command whose entire job is to deny one. `doctor`, standing in the same directory,
1220
- // already resolved the parent. The descend direction announced itself; only ascend was silent.
1221
- describe('check-theme / doctor — ascend to the parent config instead of fabricating one', () => {
1222
- /** Repo root owns the config and names TWO roots; neither package declares anything. */
1223
- function twoRootRepo(): void {
1224
- write('.git/HEAD', 'ref: refs/heads/master\n')
1225
- write(
1226
- 'package.json',
1227
- JSON.stringify({
1228
- name: 'fixture',
1229
- workspaces: ['packages/*', 'apps/*'],
1230
- basalt: { roots: ['packages/lib/src', 'apps/demo/src'] },
1231
- }),
1232
- )
1233
- write('packages/lib/package.json', JSON.stringify({ name: 'lib' }))
1234
- write('apps/demo/package.json', JSON.stringify({ name: 'demo' }))
1235
- write('apps/demo/src/App.tsx', 'export const App = () => null\n')
1236
- }
1237
-
1238
- it('scans the WHOLE declared surface from a package that declares nothing, and says so', () => {
1239
- twoRootRepo()
1240
- // The violation sits in the sibling root — the half the fabricated `src` default never read.
1241
- write('packages/lib/src/Card.tsx', `export const C = () => <div style={{ borderRadius: 8 }} />`)
1242
-
1243
- const { code, log } = capture(() => checkTheme(join(dir, 'apps/demo')))
1244
- expect(log).toContain('running in ../.., where it lives')
1245
- expect(code).toBe(1)
1246
- expect(log).toContain('raw-surface')
1247
- })
1248
-
1249
- it('--audit-allows reports the repo’s real waiver count, not zero', () => {
1250
- twoRootRepo()
1251
- write(
1252
- 'packages/lib/src/Card.tsx',
1253
- 'export const C = () => <div style={{ borderRadius: 8 }} /> // theme-allow: legacy widget\n',
1254
- )
1255
-
1256
- const { code, log } = capture(() => checkTheme(join(dir, 'apps/demo'), ['--audit-allows']))
1257
- expect(code).toBe(0)
1258
- expect(log).toContain('1 live, 0 dead')
1259
- expect(log).toContain('basalt.roots (packages/lib/src, apps/demo/src)')
1260
- })
1261
-
1262
- it('doctor and check-theme now answer with the SAME project dir', () => {
1263
- twoRootRepo()
1264
- write('packages/lib/src/Card.tsx', 'export const C = () => null\n')
1265
- write(MANIFEST_PATH, JSON.stringify({ version: 1, files: {}, basaltVersion: CLI_VERSION }))
1266
- write('.oxlintrc.json', '{ "extends": ["./node_modules/basalt-ui/configs/oxlint.json"] }')
1267
- installBasalt()
1268
-
1269
- const { log } = capture(() => doctor(join(dir, 'apps/demo')))
1270
- expect(log).toContain('reporting on ../.., where it lives')
1271
- expect(log).toContain(`basalt-ui doctor — ${dir}`)
1272
- })
1273
-
1274
- it('never ascends out of the repo — no .git above cwd means no ancestor to read', () => {
1275
- // No `.git` anywhere: the walk has no bound, so it refuses to walk at all and the built-in
1276
- // defaults apply exactly as they did before. A standalone consumer is untouched by this.
1277
- write('package.json', JSON.stringify({ name: 'root', basalt: { roots: ['packages/lib/src'] } }))
1278
- write('packages/lib/package.json', JSON.stringify({ name: 'lib' }))
1279
- write('packages/lib/src/App.tsx', 'export const App = () => null\n')
1280
-
1281
- const { code, log } = capture(() => checkTheme(join(dir, 'packages/lib')))
1282
- expect(log).not.toContain('where it lives')
1283
- expect(code).toBe(0)
1284
- })
1285
-
1286
- it('BASALT_CWD still wins over the ascend', () => {
1287
- twoRootRepo()
1288
- write('packages/lib/src/Card.tsx', 'export const C = () => null\n')
1289
- write('apps/demo/package.json', JSON.stringify({ name: 'demo', basalt: { roots: ['src'] } }))
1290
- process.env['BASALT_CWD'] = join(dir, 'apps/demo')
1291
- try {
1292
- const { code, log } = capture(() => checkTheme(dir, ['--audit-allows']))
1293
- expect(code).toBe(0)
1294
- expect(log).toContain('basalt.roots (src)')
1295
- } finally {
1296
- delete process.env['BASALT_CWD']
1297
- }
1298
- })
1299
- })
1300
-
1301
- // ── The icons check, reachable wherever doctor runs ───────────────────────────
1302
- // Round 9, rb and argo: the root run — the ONLY invocation that can exit 0 on a monorepo layout —
1303
- // omitted the icons check entirely, with no `⊘ SKIPPED` line. It read `cwd/public` and `cwd`'s
1304
- // vite config, and in a monorepo both live in the app package. A check that vanishes without a
1305
- // word is indistinguishable from one that passed.
1306
- describe('doctor — the icons check is reachable from the root, or says why not', () => {
1307
- /** A monorepo whose app package owns the vite config, the public/ tree and nothing else. */
1308
- function appPackageRepo(viteConfig: string): void {
1309
- write('.git/HEAD', 'ref: refs/heads/master\n')
1310
- write('package.json', JSON.stringify({ name: 'fixture', basalt: { roots: ['apps/web/src'] } }))
1311
- write('apps/web/src/app.tsx', 'export const App = () => null\n')
1312
- write('apps/web/vite.config.ts', viteConfig)
1313
- write('.oxlintrc.json', '{ "extends": ["./node_modules/basalt-ui/configs/oxlint.json"] }')
1314
- write(MANIFEST_PATH, JSON.stringify({ version: 1, files: {}, basaltVersion: CLI_VERSION }))
1315
- installBasalt()
1316
- }
1317
-
1318
- it('reads the app package’s `icons` array from a ROOT-invoked run', () => {
1319
- appPackageRepo("export default basaltAppPlugin({ icons: [{ src: '/favicon.svg' }] })\n")
1320
- write('apps/web/public/favicon.svg', '<svg/>')
1321
-
1322
- const { log } = capture(() => doctor(dir))
1323
- expect(log).toContain("apps/web/public/ has all 1 icon file(s) basaltAppPlugin's `icons`")
1324
- })
1325
-
1326
- it('reports a missing icon from the root too — it is not just reachable, it judges', () => {
1327
- appPackageRepo("export default basaltAppPlugin({ icons: [{ src: '/favicon.svg' }] })\n")
1328
- write('apps/web/public/.keep', '')
1329
-
1330
- const { log } = capture(() => doctor(dir))
1331
- expect(log).toContain('basaltAppPlugin declares icon(s) that are not in ./apps/web/public/')
1332
- })
1333
-
1334
- it('SKIPS loudly when the plugin is configured but has no public/ beside it', () => {
1335
- appPackageRepo("export default basaltAppPlugin({ icons: [{ src: '/favicon.svg' }] })\n")
1336
-
1337
- const { code, log } = capture(() => doctor(dir))
1338
- expect(log).toContain('⊘ SKIPPED — app icons — ./apps/web configures basaltAppPlugin')
1339
- expect(code).toBe(1)
1340
- })
1341
-
1342
- it('states the absence rather than omitting the line when nothing uses the plugin', () => {
1343
- write('.git/HEAD', 'ref: refs/heads/master\n')
1344
- healthyFixture()
1345
-
1346
- const { code, log } = capture(() => doctor(dir))
1347
- expect(log).toContain('no `basaltAppPlugin(` under this project')
1348
- expect(code).toBe(0)
1349
- })
1350
-
1351
- it('`icons: false` is nothing to check, from the root as from the package', () => {
1352
- appPackageRepo('export default basaltAppPlugin({ icons: false })\n')
1353
-
1354
- const { code, log } = capture(() => doctor(dir))
1355
- expect(log).toContain('basaltAppPlugin is configured with no icons')
1356
- expect(code).toBe(0)
1357
- })
1358
- })