basalt-ui 1.40.3 → 2.0.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 (923) hide show
  1. package/AGENTS.md +50 -45
  2. package/CHANGELOG.md +543 -513
  3. package/MIGRATING.md +969 -37
  4. package/README.md +172 -367
  5. package/agent/rules/basalt-batteries.md +19 -74
  6. package/agent/rules/basalt-charts.md +21 -95
  7. package/agent/rules/basalt-controls.md +23 -131
  8. package/agent/rules/basalt-mantine.md +26 -99
  9. package/agent/rules/basalt-state.md +21 -103
  10. package/agent/rules/basalt-tokens.md +33 -114
  11. package/agent/skills/basalt-app/SKILL.md +229 -72
  12. package/agent/skills/basalt-charts/SKILL.md +23 -57
  13. package/agent/skills/basalt-design/SKILL.md +12 -70
  14. package/agent/templates/AGENTS-block.md.tpl +30 -0
  15. package/agent/templates/DESIGN.md.tpl +2 -2
  16. package/agent/templates/__root.tpl +9 -16
  17. package/agent/templates/main.tpl +21 -0
  18. package/agent/templates/nav.tpl +18 -0
  19. package/configs/check.yml +1 -1
  20. package/configs/oxlint-basalt.json +4 -10
  21. package/configs/oxlint-plugin.js +468 -847
  22. package/configs/oxlint-plugin.test.ts +662 -710
  23. package/configs/oxlint.json +10 -2
  24. package/dist/agent/ai-sdk-transport.d.ts +19 -7
  25. package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
  26. package/dist/agent/ai-sdk-transport.js +10 -7
  27. package/dist/{agent-chat → agent/chat}/composer.d.ts +16 -16
  28. package/dist/agent/chat/composer.d.ts.map +1 -0
  29. package/dist/{agent-chat → agent/chat}/composer.js +10 -9
  30. package/dist/{agent-chat → agent/chat}/message-affordances.d.ts +7 -6
  31. package/dist/agent/chat/message-affordances.d.ts.map +1 -0
  32. package/dist/agent/chat/relative-time.d.ts.map +1 -0
  33. package/dist/{agent-chat → agent/chat}/thread-feed-row.d.ts +18 -16
  34. package/dist/agent/chat/thread-feed-row.d.ts.map +1 -0
  35. package/dist/{agent-chat → agent/chat}/thread-feed-row.js +12 -11
  36. package/dist/{agent-chat → agent/chat}/thread-feed.d.ts +8 -8
  37. package/dist/agent/chat/thread-feed.d.ts.map +1 -0
  38. package/dist/{agent-chat → agent/chat}/thread-feed.js +12 -10
  39. package/dist/{agent-chat → agent/chat}/thread-message.d.ts +12 -9
  40. package/dist/agent/chat/thread-message.d.ts.map +1 -0
  41. package/dist/{agent-chat → agent/chat}/thread-message.js +15 -13
  42. package/dist/{agent-chat → agent/chat}/thread-outcome-card.d.ts +2 -2
  43. package/dist/agent/chat/thread-outcome-card.d.ts.map +1 -0
  44. package/dist/{agent-chat → agent/chat}/thread-outcome-card.js +3 -3
  45. package/dist/{agent-chat → agent/chat}/tool-chip.d.ts +5 -5
  46. package/dist/agent/chat/tool-chip.d.ts.map +1 -0
  47. package/dist/{agent-chat → agent/chat}/tool-chip.js +4 -4
  48. package/dist/agent/chat/use-keyboard-inset.d.ts.map +1 -0
  49. package/dist/{agent-chat → agent/chat}/use-keyboard-inset.js +1 -1
  50. package/dist/{agent-chat → agent/chat}/virtualize.d.ts +7 -7
  51. package/dist/agent/chat/virtualize.d.ts.map +1 -0
  52. package/dist/agent/error.d.ts +31 -0
  53. package/dist/agent/error.d.ts.map +1 -0
  54. package/dist/agent/error.js +18 -0
  55. package/dist/agent/foreign.d.ts +3 -2
  56. package/dist/agent/foreign.d.ts.map +1 -1
  57. package/dist/agent/history.d.ts +2 -36
  58. package/dist/agent/history.d.ts.map +1 -1
  59. package/dist/agent/history.js +0 -32
  60. package/dist/agent/id.d.ts +4 -5
  61. package/dist/agent/id.d.ts.map +1 -1
  62. package/dist/agent/index.d.ts +19 -52
  63. package/dist/agent/index.d.ts.map +1 -1
  64. package/dist/agent/index.js +10 -32
  65. package/dist/agent/outcome.d.ts +3 -14
  66. package/dist/agent/outcome.d.ts.map +1 -1
  67. package/dist/agent/outcome.js +0 -30
  68. package/dist/agent/part-list.d.ts +3 -3
  69. package/dist/agent/part-list.d.ts.map +1 -1
  70. package/dist/agent/parts.d.ts +23 -24
  71. package/dist/agent/parts.d.ts.map +1 -1
  72. package/dist/agent/run-committer.d.ts +23 -0
  73. package/dist/agent/run-committer.d.ts.map +1 -0
  74. package/dist/agent/run-committer.js +41 -0
  75. package/dist/agent/run-records.d.ts +77 -0
  76. package/dist/agent/run-records.d.ts.map +1 -0
  77. package/dist/agent/run-records.js +69 -0
  78. package/dist/agent/thread.d.ts +23 -47
  79. package/dist/agent/thread.d.ts.map +1 -1
  80. package/dist/agent/thread.js +3 -134
  81. package/dist/agent/transport.d.ts +6 -69
  82. package/dist/agent/transport.d.ts.map +1 -1
  83. package/dist/agent/transport.js +0 -18
  84. package/dist/agent/use-agent-thread-runs.d.ts +63 -26
  85. package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
  86. package/dist/agent/use-agent-thread-runs.js +202 -66
  87. package/dist/charts/BandStrip.d.ts +46 -0
  88. package/dist/charts/BandStrip.d.ts.map +1 -0
  89. package/dist/charts/BandStrip.js +151 -0
  90. package/dist/charts/Bars.d.ts +19 -0
  91. package/dist/charts/Bars.d.ts.map +1 -0
  92. package/dist/charts/Bars.js +77 -0
  93. package/dist/charts/CartesianChart.d.ts +57 -0
  94. package/dist/charts/CartesianChart.d.ts.map +1 -0
  95. package/dist/charts/CartesianChart.js +121 -0
  96. package/dist/charts/ChartCard.d.ts +34 -0
  97. package/dist/charts/ChartCard.d.ts.map +1 -0
  98. package/dist/charts/ChartCard.js +115 -0
  99. package/dist/charts/Donut.d.ts +25 -0
  100. package/dist/charts/Donut.d.ts.map +1 -0
  101. package/dist/charts/Donut.js +191 -0
  102. package/dist/charts/DualPanel.d.ts +47 -0
  103. package/dist/charts/DualPanel.d.ts.map +1 -0
  104. package/dist/charts/DualPanel.js +214 -0
  105. package/dist/charts/Heatmap.d.ts +41 -0
  106. package/dist/charts/Heatmap.d.ts.map +1 -0
  107. package/dist/charts/Heatmap.js +216 -0
  108. package/dist/charts/Lines.d.ts +11 -0
  109. package/dist/charts/Lines.d.ts.map +1 -0
  110. package/dist/charts/Lines.js +114 -0
  111. package/dist/charts/MirroredBars.d.ts +44 -0
  112. package/dist/charts/MirroredBars.d.ts.map +1 -0
  113. package/dist/charts/MirroredBars.js +190 -0
  114. package/dist/charts/Sparkline.d.ts +22 -0
  115. package/dist/charts/Sparkline.d.ts.map +1 -0
  116. package/dist/charts/Sparkline.js +129 -0
  117. package/dist/charts/StackedArea.d.ts +15 -0
  118. package/dist/charts/StackedArea.d.ts.map +1 -0
  119. package/dist/charts/StackedArea.js +46 -0
  120. package/dist/charts/box.d.ts +38 -0
  121. package/dist/charts/box.d.ts.map +1 -0
  122. package/dist/charts/box.js +72 -0
  123. package/dist/charts/card-context.d.ts +3 -0
  124. package/dist/charts/card-context.d.ts.map +1 -0
  125. package/dist/charts/card-context.js +7 -0
  126. package/dist/charts/chart-box.module.css +64 -0
  127. package/{src/content/article-card.module.css.d.ts → dist/charts/chart-box.module.css.d.ts} +5 -9
  128. package/dist/charts/chart-card.module.css +25 -0
  129. package/dist/{dashboard/widget-grid.module.css.d.ts → charts/chart-card.module.css.d.ts} +1 -3
  130. package/dist/charts/chart-legend.module.css +38 -0
  131. package/{src/dashboard/widget-grid.module.css.d.ts → dist/charts/chart-legend.module.css.d.ts} +3 -3
  132. package/dist/charts/compile.d.ts +62 -0
  133. package/dist/charts/compile.d.ts.map +1 -0
  134. package/dist/charts/compile.js +386 -0
  135. package/dist/charts/cursor.d.ts +50 -0
  136. package/dist/charts/cursor.d.ts.map +1 -0
  137. package/dist/charts/cursor.js +157 -0
  138. package/dist/charts/curves.d.ts +12 -0
  139. package/dist/charts/curves.d.ts.map +1 -0
  140. package/dist/charts/curves.js +86 -0
  141. package/dist/charts/donut.module.css +54 -0
  142. package/dist/{content/article-card.module.css.d.ts → charts/donut.module.css.d.ts} +2 -9
  143. package/dist/charts/host.d.ts +17 -0
  144. package/dist/charts/host.d.ts.map +1 -0
  145. package/dist/charts/host.js +35 -0
  146. package/dist/charts/index.d.ts +18 -53
  147. package/dist/charts/index.d.ts.map +1 -1
  148. package/dist/charts/index.js +17 -189
  149. package/dist/charts/legend.d.ts +23 -0
  150. package/dist/charts/legend.d.ts.map +1 -0
  151. package/dist/charts/legend.js +160 -0
  152. package/dist/charts/marks.d.ts +25 -0
  153. package/dist/charts/marks.d.ts.map +1 -0
  154. package/dist/charts/marks.js +128 -0
  155. package/dist/charts/panel.d.ts +56 -0
  156. package/dist/charts/panel.d.ts.map +1 -0
  157. package/dist/charts/panel.js +123 -0
  158. package/dist/charts/scales.d.ts +44 -0
  159. package/dist/charts/scales.d.ts.map +1 -0
  160. package/dist/charts/scales.js +163 -0
  161. package/dist/charts/sizing.d.ts +12 -0
  162. package/dist/charts/sizing.d.ts.map +1 -0
  163. package/dist/charts/sizing.js +33 -0
  164. package/dist/charts/state.d.ts +17 -0
  165. package/dist/charts/state.d.ts.map +1 -0
  166. package/dist/charts/state.js +53 -0
  167. package/dist/charts/theme.d.ts +38 -19
  168. package/dist/charts/theme.d.ts.map +1 -1
  169. package/dist/charts/theme.js +48 -26
  170. package/dist/charts/types.d.ts +227 -0
  171. package/dist/charts/types.d.ts.map +1 -0
  172. package/dist/charts/types.js +4 -0
  173. package/dist/cli/audit-allows.d.ts +4 -17
  174. package/dist/cli/audit-allows.d.ts.map +1 -1
  175. package/dist/cli/config.d.ts +14 -43
  176. package/dist/cli/config.d.ts.map +1 -1
  177. package/dist/cli/doctor-upgrade.d.ts +31 -0
  178. package/dist/cli/doctor-upgrade.d.ts.map +1 -0
  179. package/dist/cli/doctor-upgrade.js +269 -0
  180. package/dist/cli/doctor.d.ts +13 -26
  181. package/dist/cli/doctor.d.ts.map +1 -1
  182. package/dist/cli/doctor.js +79 -8
  183. package/dist/cli/help.d.ts.map +1 -1
  184. package/dist/cli/help.js +2 -4
  185. package/dist/cli/index.d.ts +68 -135
  186. package/dist/cli/index.d.ts.map +1 -1
  187. package/dist/cli/index.js +89 -76
  188. package/dist/cli/init.d.ts.map +1 -1
  189. package/dist/cli/init.js +43 -27
  190. package/dist/cli/sync.d.ts +5 -21
  191. package/dist/cli/sync.d.ts.map +1 -1
  192. package/dist/cli/sync.js +4 -6
  193. package/dist/cli/tokens-css.d.ts +7 -28
  194. package/dist/cli/tokens-css.d.ts.map +1 -1
  195. package/dist/commands/ShortcutsHelp.d.ts.map +1 -1
  196. package/dist/commands/ShortcutsHelp.js +4 -5
  197. package/dist/commands/define-commands.d.ts +15 -31
  198. package/dist/commands/define-commands.d.ts.map +1 -1
  199. package/dist/commands/define-commands.js +1 -11
  200. package/dist/commands/define-overlays.d.ts +5 -19
  201. package/dist/commands/define-overlays.d.ts.map +1 -1
  202. package/dist/commands/define-overlays.js +3 -11
  203. package/dist/commands/index.d.ts +10 -40
  204. package/dist/commands/index.d.ts.map +1 -1
  205. package/dist/commands/index.js +9 -46
  206. package/dist/{notifications → commands/notifications}/bell.d.ts +1 -1
  207. package/dist/commands/notifications/bell.d.ts.map +1 -0
  208. package/dist/{notifications → commands/notifications}/center.d.ts +2 -2
  209. package/dist/commands/notifications/center.d.ts.map +1 -0
  210. package/dist/{notifications → commands/notifications}/center.js +2 -2
  211. package/dist/{notifications → commands/notifications}/define-notifications.d.ts +3 -3
  212. package/dist/commands/notifications/define-notifications.d.ts.map +1 -0
  213. package/dist/commands/notifications/notify.d.ts +44 -0
  214. package/dist/commands/notifications/notify.d.ts.map +1 -0
  215. package/dist/commands/notifications/notify.js +79 -0
  216. package/dist/{notifications → commands/notifications}/store.d.ts +2 -0
  217. package/dist/commands/notifications/store.d.ts.map +1 -0
  218. package/dist/{notifications → commands/notifications}/store.js +4 -0
  219. package/dist/commands/projectors.d.ts +1 -131
  220. package/dist/commands/projectors.d.ts.map +1 -1
  221. package/dist/commands/projectors.js +2 -73
  222. package/dist/commands/shell-bridge.d.ts +3 -3
  223. package/dist/commands/shortcut-format.d.ts +5 -4
  224. package/dist/commands/shortcut-format.d.ts.map +1 -1
  225. package/dist/common/errors.d.ts +3 -3
  226. package/dist/common/use-measured-widths.d.ts.map +1 -1
  227. package/dist/common/use-measured-widths.js +5 -3
  228. package/dist/common/validate.d.ts +2 -2
  229. package/dist/content/article-layout.d.ts.map +1 -1
  230. package/dist/content/article-layout.js +3 -3
  231. package/dist/content/article-model.d.ts +1 -46
  232. package/dist/content/article-model.d.ts.map +1 -1
  233. package/dist/content/article-model.js +1 -29
  234. package/dist/content/copy-action.d.ts +1 -1
  235. package/dist/content/glyphs.d.ts +1 -1
  236. package/dist/content/index.d.ts +11 -41
  237. package/dist/content/index.d.ts.map +1 -1
  238. package/dist/content/index.js +3 -35
  239. package/dist/content/markdown.d.ts +1 -1
  240. package/dist/content/markdown.d.ts.map +1 -1
  241. package/dist/content/markdown.js +1 -1
  242. package/dist/content/mermaid.d.ts.map +1 -1
  243. package/dist/content/mermaid.js +7 -7
  244. package/dist/content/slug.d.ts +1 -8
  245. package/dist/content/slug.d.ts.map +1 -1
  246. package/dist/content/slug.js +1 -8
  247. package/dist/content/url-hardening.d.ts +2 -2
  248. package/dist/content/url-hardening.d.ts.map +1 -1
  249. package/dist/content/url-hardening.js +2 -0
  250. package/dist/controls/actions.d.ts +16 -1
  251. package/dist/controls/actions.d.ts.map +1 -1
  252. package/dist/controls/enum-filter.d.ts +1 -1
  253. package/dist/controls/enum-filter.d.ts.map +1 -1
  254. package/dist/controls/filter-context.d.ts.map +1 -1
  255. package/dist/controls/filter-context.js +3 -1
  256. package/dist/controls/filter-set.js +4 -6
  257. package/dist/controls/form-layout.d.ts +1 -1
  258. package/dist/controls/form-layout.d.ts.map +1 -1
  259. package/dist/controls/form-layout.js +1 -1
  260. package/dist/controls/glyphs.d.ts +0 -2
  261. package/dist/controls/glyphs.d.ts.map +1 -1
  262. package/dist/controls/glyphs.js +0 -7
  263. package/dist/controls/index.d.ts +12 -17
  264. package/dist/controls/index.d.ts.map +1 -1
  265. package/dist/controls/index.js +5 -39
  266. package/dist/controls/multi-select-filter.d.ts +4 -3
  267. package/dist/controls/multi-select-filter.d.ts.map +1 -1
  268. package/dist/controls/number-filter.d.ts +4 -3
  269. package/dist/controls/number-filter.d.ts.map +1 -1
  270. package/dist/controls/number-filter.js +3 -1
  271. package/dist/controls/range-filter.d.ts +4 -3
  272. package/dist/controls/range-filter.d.ts.map +1 -1
  273. package/dist/controls/search-filter.d.ts +4 -3
  274. package/dist/controls/search-filter.d.ts.map +1 -1
  275. package/dist/controls/search-filter.js +19 -14
  276. package/dist/controls/select-filter.d.ts +4 -3
  277. package/dist/controls/select-filter.d.ts.map +1 -1
  278. package/dist/controls/sync-button.d.ts.map +1 -1
  279. package/dist/controls/sync-button.js +12 -15
  280. package/dist/controls/toggle-filter.d.ts +4 -3
  281. package/dist/controls/toggle-filter.d.ts.map +1 -1
  282. package/dist/controls/uncontrolled.d.ts +12 -0
  283. package/dist/controls/uncontrolled.d.ts.map +1 -0
  284. package/dist/controls/view-tabs.d.ts +2 -2
  285. package/dist/controls/view-tabs.d.ts.map +1 -1
  286. package/dist/controls-dates/index.d.ts +0 -1
  287. package/dist/controls-dates/index.d.ts.map +1 -1
  288. package/dist/dashboard/index.d.ts +7 -9
  289. package/dist/dashboard/index.d.ts.map +1 -1
  290. package/dist/dashboard/index.js +3 -23
  291. package/dist/dashboard/section.d.ts +4 -6
  292. package/dist/dashboard/section.d.ts.map +1 -1
  293. package/dist/dashboard/section.js +4 -10
  294. package/dist/dashboard/stat-card.d.ts +4 -4
  295. package/dist/dashboard/stat-card.d.ts.map +1 -1
  296. package/dist/dashboard/stat-card.js +3 -3
  297. package/dist/dashboard/widget-header.module.css +9 -7
  298. package/dist/dev-dock.d.ts.map +1 -1
  299. package/dist/dev-dock.js +6 -1
  300. package/dist/format/date.d.ts +0 -34
  301. package/dist/format/date.d.ts.map +1 -1
  302. package/dist/format/date.js +0 -54
  303. package/dist/format/index.d.ts +7 -22
  304. package/dist/format/index.d.ts.map +1 -1
  305. package/dist/format/index.js +2 -43
  306. package/dist/format/number.d.ts +3 -14
  307. package/dist/format/number.d.ts.map +1 -1
  308. package/dist/format/number.js +0 -13
  309. package/dist/format/shared.d.ts +2 -3
  310. package/dist/format/shared.d.ts.map +1 -1
  311. package/dist/forms/index.d.ts +0 -11
  312. package/dist/forms/index.d.ts.map +1 -1
  313. package/dist/forms/index.js +1 -14
  314. package/dist/guard/index.d.ts +6 -21
  315. package/dist/guard/index.d.ts.map +1 -1
  316. package/dist/guard/index.js +27 -115
  317. package/dist/guard/types.d.ts +7 -18
  318. package/dist/guard/types.d.ts.map +1 -1
  319. package/dist/index.d.ts +16 -48
  320. package/dist/index.d.ts.map +1 -1
  321. package/dist/index.js +21 -117
  322. package/dist/provider/create-router.d.ts +44 -0
  323. package/dist/provider/create-router.d.ts.map +1 -0
  324. package/dist/provider/create-router.js +62 -0
  325. package/dist/provider/hotkeys.d.ts +8 -0
  326. package/dist/provider/hotkeys.d.ts.map +1 -0
  327. package/dist/provider/hotkeys.js +50 -0
  328. package/dist/provider/index.d.ts +33 -110
  329. package/dist/provider/index.d.ts.map +1 -1
  330. package/dist/provider/index.js +182 -82
  331. package/dist/provider/lab-theme.d.ts.map +1 -1
  332. package/dist/provider/route-transition.d.ts +76 -0
  333. package/dist/provider/route-transition.d.ts.map +1 -0
  334. package/dist/provider/route-transition.js +98 -0
  335. package/dist/provider/spotlight-actions.d.ts +10 -0
  336. package/dist/provider/spotlight-actions.d.ts.map +1 -0
  337. package/dist/provider/spotlight-actions.js +55 -0
  338. package/dist/provider/test-router.d.ts +6 -0
  339. package/dist/provider/test-router.d.ts.map +1 -0
  340. package/dist/provider/test-router.js +18 -0
  341. package/dist/query-client.d.ts +4 -20
  342. package/dist/query-client.d.ts.map +1 -1
  343. package/dist/query-client.js +2 -2
  344. package/dist/router-tanstack/index.d.ts +18 -64
  345. package/dist/router-tanstack/index.d.ts.map +1 -1
  346. package/dist/router-tanstack/index.js +2 -16
  347. package/dist/router-tanstack/nav.d.ts +12 -1
  348. package/dist/router-tanstack/nav.d.ts.map +1 -1
  349. package/dist/router-tanstack/nav.js +8 -1
  350. package/dist/router-tanstack/search-store.d.ts +1 -1
  351. package/dist/router-tanstack/search-store.d.ts.map +1 -1
  352. package/dist/router-tanstack/use-nav.d.ts +8 -9
  353. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  354. package/dist/router-tanstack/use-nav.js +20 -1
  355. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  356. package/dist/shell/app-sidebar.js +1 -1
  357. package/dist/shell/index.d.ts +31 -10
  358. package/dist/shell/index.d.ts.map +1 -1
  359. package/dist/shell/index.js +14 -3
  360. package/dist/shell/page-aside.d.ts +2 -2
  361. package/dist/shell/page-aside.d.ts.map +1 -1
  362. package/dist/shell/page-aside.js +31 -12
  363. package/dist/shell/page-bar.d.ts +2 -2
  364. package/dist/shell/page-bar.d.ts.map +1 -1
  365. package/dist/shell/page-bar.js +1 -1
  366. package/dist/state.d.ts +10 -12
  367. package/dist/state.d.ts.map +1 -1
  368. package/dist/state.js +4 -6
  369. package/dist/styles.css +107 -7
  370. package/dist/surfaces.d.ts +28 -103
  371. package/dist/surfaces.d.ts.map +1 -1
  372. package/dist/surfaces.js +54 -143
  373. package/dist/{data → table}/data-table-state.d.ts +1 -1
  374. package/dist/table/data-table-state.d.ts.map +1 -0
  375. package/dist/{data → table}/data-table-state.js +4 -3
  376. package/dist/{data → table}/data-table.d.ts +28 -49
  377. package/dist/table/data-table.d.ts.map +1 -0
  378. package/dist/{data → table}/data-table.js +74 -60
  379. package/dist/table/index.d.ts +18 -0
  380. package/dist/table/index.d.ts.map +1 -0
  381. package/dist/{data/table.js → table/index.js} +1 -1
  382. package/dist/table/query-branch.d.ts.map +1 -0
  383. package/dist/table/table-features.d.ts +69 -0
  384. package/dist/table/table-features.d.ts.map +1 -0
  385. package/dist/table/table-features.js +49 -0
  386. package/dist/telemetry.d.ts +77 -0
  387. package/dist/telemetry.d.ts.map +1 -0
  388. package/dist/telemetry.js +33 -0
  389. package/dist/theme/index.d.ts +10 -21
  390. package/dist/theme/index.d.ts.map +1 -1
  391. package/dist/theme/index.js +3 -14
  392. package/dist/theme/theme-toggle.d.ts.map +1 -1
  393. package/dist/theme/theme-toggle.js +7 -33
  394. package/dist/theme/theme-toggle.module.css +30 -0
  395. package/dist/theme/theme-toggle.module.css.d.ts +10 -0
  396. package/dist/theme-lab/derive-controls.d.ts.map +1 -1
  397. package/dist/theme-lab/derive-controls.js +5 -3
  398. package/dist/theme-lab/derive-state.d.ts.map +1 -1
  399. package/dist/theme-lab/derive-state.js +2 -1
  400. package/dist/tokens/core.d.ts +360 -0
  401. package/dist/tokens/core.d.ts.map +1 -0
  402. package/dist/tokens/core.js +708 -0
  403. package/dist/tokens/derive.d.ts +1 -1
  404. package/dist/tokens/derive.d.ts.map +1 -1
  405. package/dist/tokens/index.d.ts +5 -373
  406. package/dist/tokens/index.d.ts.map +1 -1
  407. package/dist/tokens/index.js +8 -700
  408. package/dist/tokens/palette.d.ts +2 -6
  409. package/dist/tokens/palette.d.ts.map +1 -1
  410. package/dist/tokens/palette.js +1 -4
  411. package/dist/vite.d.ts +4 -3
  412. package/dist/vite.d.ts.map +1 -1
  413. package/llms.txt +43 -85
  414. package/package.json +77 -170
  415. package/src/agent/ai-sdk-transport.ts +57 -37
  416. package/src/{agent-chat → agent/chat}/composer.tsx +29 -30
  417. package/src/{agent-chat → agent/chat}/message-affordances.ts +8 -5
  418. package/src/{agent-chat → agent/chat}/thread-feed-row.tsx +30 -35
  419. package/src/{agent-chat → agent/chat}/thread-feed.tsx +40 -35
  420. package/src/{agent-chat → agent/chat}/thread-message.tsx +37 -35
  421. package/src/{agent-chat → agent/chat}/thread-outcome-card.tsx +5 -5
  422. package/src/{agent-chat → agent/chat}/tool-chip.tsx +12 -12
  423. package/src/{agent-chat → agent/chat}/use-keyboard-inset.ts +1 -1
  424. package/src/{agent-chat → agent/chat}/virtualize.ts +5 -5
  425. package/src/agent/error.ts +43 -0
  426. package/src/agent/foreign.ts +3 -2
  427. package/src/agent/history.ts +2 -107
  428. package/src/agent/id.ts +4 -5
  429. package/src/agent/index.ts +23 -112
  430. package/src/agent/outcome.ts +3 -56
  431. package/src/agent/part-list.tsx +3 -3
  432. package/src/agent/parts.ts +23 -22
  433. package/src/agent/run-committer.ts +64 -0
  434. package/src/agent/run-records.ts +147 -0
  435. package/src/agent/thread.ts +24 -231
  436. package/src/agent/transport.ts +6 -96
  437. package/src/agent/use-agent-thread-runs.ts +364 -199
  438. package/src/charts/BandStrip.tsx +276 -0
  439. package/src/charts/Bars.tsx +141 -0
  440. package/src/charts/CartesianChart.tsx +206 -0
  441. package/src/charts/ChartCard.tsx +178 -0
  442. package/src/charts/Donut.tsx +256 -0
  443. package/src/charts/DualPanel.tsx +326 -0
  444. package/src/charts/Heatmap.tsx +282 -0
  445. package/src/charts/Lines.tsx +151 -0
  446. package/src/charts/MirroredBars.tsx +303 -0
  447. package/src/charts/Sparkline.tsx +176 -0
  448. package/src/charts/StackedArea.tsx +80 -0
  449. package/src/charts/box.tsx +96 -0
  450. package/src/charts/card-context.ts +11 -0
  451. package/src/charts/chart-box.module.css +64 -0
  452. package/src/charts/chart-box.module.css.d.ts +12 -0
  453. package/src/charts/chart-card.module.css +25 -0
  454. package/src/charts/chart-card.module.css.d.ts +8 -0
  455. package/src/charts/chart-legend.module.css +38 -0
  456. package/src/charts/chart-legend.module.css.d.ts +10 -0
  457. package/src/charts/compile.ts +585 -0
  458. package/src/charts/cursor.ts +282 -0
  459. package/src/charts/curves.ts +111 -0
  460. package/src/charts/donut.module.css +54 -0
  461. package/src/charts/donut.module.css.d.ts +9 -0
  462. package/src/charts/host.tsx +60 -0
  463. package/src/charts/index.ts +41 -178
  464. package/src/charts/legend.tsx +204 -0
  465. package/src/charts/marks.ts +201 -0
  466. package/src/charts/panel.tsx +227 -0
  467. package/src/charts/scales.ts +237 -0
  468. package/src/charts/sizing.ts +47 -0
  469. package/src/charts/state.tsx +80 -0
  470. package/src/charts/theme.ts +65 -0
  471. package/src/charts/types.ts +242 -0
  472. package/src/cli/audit-allows.ts +8 -30
  473. package/src/cli/config.ts +14 -43
  474. package/src/cli/doctor-upgrade.ts +392 -0
  475. package/src/cli/doctor.ts +120 -68
  476. package/src/cli/help.ts +2 -4
  477. package/src/cli/index.ts +161 -276
  478. package/src/cli/init.ts +60 -37
  479. package/src/cli/sync.ts +13 -45
  480. package/src/cli/tokens-css.ts +7 -28
  481. package/src/commands/ShortcutsHelp.tsx +8 -9
  482. package/src/commands/define-commands.ts +16 -43
  483. package/src/commands/define-overlays.ts +12 -36
  484. package/src/commands/index.ts +11 -94
  485. package/src/{notifications → commands/notifications}/bell.tsx +2 -2
  486. package/src/{notifications → commands/notifications}/center.tsx +7 -5
  487. package/src/{notifications → commands/notifications}/define-notifications.ts +3 -3
  488. package/src/commands/notifications/notify.tsx +158 -0
  489. package/src/{notifications → commands/notifications}/store.ts +10 -2
  490. package/src/commands/projectors.ts +5 -225
  491. package/src/commands/shell-bridge.ts +5 -5
  492. package/src/commands/shortcut-format.ts +5 -4
  493. package/src/common/errors.ts +3 -3
  494. package/src/common/use-measured-widths.ts +6 -3
  495. package/src/common/validate.ts +2 -2
  496. package/src/content/article-layout.tsx +37 -38
  497. package/src/content/article-model.ts +1 -84
  498. package/src/content/copy-action.tsx +1 -1
  499. package/src/content/glyphs.tsx +1 -1
  500. package/src/content/index.ts +12 -83
  501. package/src/content/markdown.tsx +4 -3
  502. package/src/content/mermaid.tsx +9 -7
  503. package/src/content/slug.ts +1 -16
  504. package/src/content/url-hardening.ts +8 -2
  505. package/src/controls/actions.tsx +24 -1
  506. package/src/controls/enum-filter.tsx +1 -1
  507. package/src/controls/filter-context.tsx +3 -1
  508. package/src/controls/filter-set.tsx +5 -7
  509. package/src/controls/form-layout.tsx +2 -2
  510. package/src/controls/glyphs.tsx +0 -10
  511. package/src/controls/index.ts +13 -64
  512. package/src/controls/multi-select-filter.tsx +5 -4
  513. package/src/controls/number-filter.tsx +8 -4
  514. package/src/controls/range-filter.tsx +5 -4
  515. package/src/controls/search-filter.tsx +25 -19
  516. package/src/controls/select-filter.tsx +5 -4
  517. package/src/controls/sync-button.tsx +18 -20
  518. package/src/controls/toggle-filter.tsx +5 -4
  519. package/src/controls/uncontrolled.ts +12 -0
  520. package/src/controls/view-tabs.tsx +3 -3
  521. package/src/controls-dates/date-range-picker.tsx +1 -1
  522. package/src/controls-dates/index.ts +0 -1
  523. package/src/dashboard/index.ts +7 -48
  524. package/src/dashboard/section.tsx +11 -40
  525. package/src/dashboard/stat-card.tsx +14 -16
  526. package/src/dashboard/widget-header.module.css +9 -7
  527. package/src/dev-dock.tsx +14 -27
  528. package/src/format/date.ts +2 -92
  529. package/src/format/index.ts +7 -53
  530. package/src/format/number.ts +3 -26
  531. package/src/format/shared.ts +2 -3
  532. package/src/forms/index.ts +0 -23
  533. package/src/guard/index.ts +37 -192
  534. package/src/guard/types.ts +6 -19
  535. package/src/index.ts +30 -282
  536. package/src/provider/create-router.tsx +138 -0
  537. package/src/provider/hotkeys.ts +70 -0
  538. package/src/provider/index.tsx +260 -230
  539. package/src/provider/lab-theme.ts +3 -2
  540. package/src/provider/route-transition.ts +199 -0
  541. package/src/provider/spotlight-actions.tsx +69 -0
  542. package/src/provider/test-router.tsx +25 -0
  543. package/src/query-client.ts +4 -22
  544. package/src/router-tanstack/index.ts +18 -112
  545. package/src/router-tanstack/nav.ts +28 -2
  546. package/src/router-tanstack/search-store.ts +9 -2
  547. package/src/router-tanstack/use-nav.ts +47 -13
  548. package/src/shell/app-mobile-nav.tsx +3 -2
  549. package/src/shell/app-sidebar.tsx +1 -1
  550. package/src/shell/aside-docking.ts +1 -1
  551. package/src/shell/index.tsx +61 -20
  552. package/src/shell/page-aside.tsx +47 -23
  553. package/src/shell/page-bar.tsx +3 -3
  554. package/src/shell/page-title.tsx +1 -1
  555. package/src/state.ts +11 -37
  556. package/src/styles.css +107 -7
  557. package/src/surfaces.ts +56 -151
  558. package/src/{data → table}/data-table-state.ts +5 -4
  559. package/src/{data → table}/data-table.tsx +170 -161
  560. package/src/table/index.ts +18 -0
  561. package/src/table/table-features.ts +102 -0
  562. package/src/telemetry.ts +110 -0
  563. package/src/theme/index.ts +22 -47
  564. package/src/theme/theme-toggle.module.css +30 -0
  565. package/src/theme/theme-toggle.module.css.d.ts +10 -0
  566. package/src/theme/theme-toggle.tsx +15 -43
  567. package/src/theme-lab/derive-controls.tsx +5 -3
  568. package/src/theme-lab/derive-state.ts +2 -1
  569. package/src/tokens/core.ts +1163 -0
  570. package/src/tokens/derive.ts +1 -1
  571. package/src/tokens/index.ts +14 -1181
  572. package/src/tokens/palette.ts +3 -7
  573. package/src/vite.ts +3 -3
  574. package/agent/templates/CLAUDE-block.md.tpl +0 -40
  575. package/agent/templates/query-client.ts.tpl +0 -26
  576. package/dist/agent/adapter.d.ts +0 -150
  577. package/dist/agent/adapter.d.ts.map +0 -1
  578. package/dist/agent/adapter.js +0 -482
  579. package/dist/agent/use-agent-stream.d.ts +0 -35
  580. package/dist/agent/use-agent-stream.d.ts.map +0 -1
  581. package/dist/agent/use-agent-stream.js +0 -59
  582. package/dist/agent-chat/composer.d.ts.map +0 -1
  583. package/dist/agent-chat/index.d.ts +0 -31
  584. package/dist/agent-chat/index.d.ts.map +0 -1
  585. package/dist/agent-chat/index.js +0 -19
  586. package/dist/agent-chat/message-affordances.d.ts.map +0 -1
  587. package/dist/agent-chat/relative-time.d.ts.map +0 -1
  588. package/dist/agent-chat/thread-detail-panel.d.ts +0 -28
  589. package/dist/agent-chat/thread-detail-panel.d.ts.map +0 -1
  590. package/dist/agent-chat/thread-detail-panel.js +0 -115
  591. package/dist/agent-chat/thread-feed-row.d.ts.map +0 -1
  592. package/dist/agent-chat/thread-feed.d.ts.map +0 -1
  593. package/dist/agent-chat/thread-message.d.ts.map +0 -1
  594. package/dist/agent-chat/thread-outcome-card.d.ts.map +0 -1
  595. package/dist/agent-chat/thread-workspace.d.ts +0 -44
  596. package/dist/agent-chat/thread-workspace.d.ts.map +0 -1
  597. package/dist/agent-chat/thread-workspace.js +0 -153
  598. package/dist/agent-chat/tool-chip.d.ts.map +0 -1
  599. package/dist/agent-chat/use-keyboard-inset.d.ts.map +0 -1
  600. package/dist/agent-chat/virtualize.d.ts.map +0 -1
  601. package/dist/charts/cursor/resolve.d.ts +0 -75
  602. package/dist/charts/cursor/resolve.d.ts.map +0 -1
  603. package/dist/charts/cursor/resolve.js +0 -91
  604. package/dist/charts/cursor/scope.d.ts +0 -20
  605. package/dist/charts/cursor/scope.d.ts.map +0 -1
  606. package/dist/charts/cursor/scope.js +0 -20
  607. package/dist/charts/cursor/store.d.ts +0 -35
  608. package/dist/charts/cursor/store.d.ts.map +0 -1
  609. package/dist/charts/cursor/store.js +0 -25
  610. package/dist/charts/cursor/touch-pin.d.ts +0 -45
  611. package/dist/charts/cursor/touch-pin.d.ts.map +0 -1
  612. package/dist/charts/cursor/touch-pin.js +0 -79
  613. package/dist/charts/hooks/use-discrete-cursor.d.ts +0 -66
  614. package/dist/charts/hooks/use-discrete-cursor.d.ts.map +0 -1
  615. package/dist/charts/hooks/use-discrete-cursor.js +0 -198
  616. package/dist/charts/hooks/use-hysteresis.d.ts +0 -18
  617. package/dist/charts/hooks/use-hysteresis.d.ts.map +0 -1
  618. package/dist/charts/hooks/use-hysteresis.js +0 -12
  619. package/dist/charts/hooks/useBandPlot.d.ts +0 -145
  620. package/dist/charts/hooks/useBandPlot.d.ts.map +0 -1
  621. package/dist/charts/hooks/useBandPlot.js +0 -178
  622. package/dist/charts/hooks/useChartCursor.d.ts +0 -75
  623. package/dist/charts/hooks/useChartCursor.d.ts.map +0 -1
  624. package/dist/charts/hooks/useChartCursor.js +0 -200
  625. package/dist/charts/hooks/useChartSize.d.ts +0 -29
  626. package/dist/charts/hooks/useChartSize.d.ts.map +0 -1
  627. package/dist/charts/hooks/useChartSize.js +0 -8
  628. package/dist/charts/kinds/BandStrip.d.ts +0 -132
  629. package/dist/charts/kinds/BandStrip.d.ts.map +0 -1
  630. package/dist/charts/kinds/BandStrip.js +0 -241
  631. package/dist/charts/kinds/Bars.d.ts +0 -141
  632. package/dist/charts/kinds/Bars.d.ts.map +0 -1
  633. package/dist/charts/kinds/Bars.js +0 -343
  634. package/dist/charts/kinds/Donut.d.ts +0 -77
  635. package/dist/charts/kinds/Donut.d.ts.map +0 -1
  636. package/dist/charts/kinds/Donut.js +0 -245
  637. package/dist/charts/kinds/DualPanel.d.ts +0 -111
  638. package/dist/charts/kinds/DualPanel.d.ts.map +0 -1
  639. package/dist/charts/kinds/DualPanel.js +0 -471
  640. package/dist/charts/kinds/Heatmap.d.ts +0 -85
  641. package/dist/charts/kinds/Heatmap.d.ts.map +0 -1
  642. package/dist/charts/kinds/Heatmap.js +0 -243
  643. package/dist/charts/kinds/MirroredBars.d.ts +0 -98
  644. package/dist/charts/kinds/MirroredBars.d.ts.map +0 -1
  645. package/dist/charts/kinds/MirroredBars.js +0 -292
  646. package/dist/charts/kinds/MultiLine.d.ts +0 -87
  647. package/dist/charts/kinds/MultiLine.d.ts.map +0 -1
  648. package/dist/charts/kinds/MultiLine.js +0 -176
  649. package/dist/charts/kinds/StackedArea.d.ts +0 -73
  650. package/dist/charts/kinds/StackedArea.d.ts.map +0 -1
  651. package/dist/charts/kinds/StackedArea.js +0 -135
  652. package/dist/charts/kinds/ZonedLine.d.ts +0 -97
  653. package/dist/charts/kinds/ZonedLine.d.ts.map +0 -1
  654. package/dist/charts/kinds/ZonedLine.js +0 -155
  655. package/dist/charts/kinds/band-chrome.d.ts +0 -31
  656. package/dist/charts/kinds/band-chrome.d.ts.map +0 -1
  657. package/dist/charts/kinds/band-chrome.js +0 -71
  658. package/dist/charts/kinds/donut-layout.d.ts +0 -47
  659. package/dist/charts/kinds/donut-layout.d.ts.map +0 -1
  660. package/dist/charts/kinds/donut-layout.js +0 -52
  661. package/dist/charts/layout/auto-margin.d.ts +0 -122
  662. package/dist/charts/layout/auto-margin.d.ts.map +0 -1
  663. package/dist/charts/layout/auto-margin.js +0 -81
  664. package/dist/charts/layout/end-labels.d.ts +0 -64
  665. package/dist/charts/layout/end-labels.d.ts.map +0 -1
  666. package/dist/charts/layout/end-labels.js +0 -76
  667. package/dist/charts/layout/log-ticks.d.ts +0 -27
  668. package/dist/charts/layout/log-ticks.d.ts.map +0 -1
  669. package/dist/charts/layout/log-ticks.js +0 -42
  670. package/dist/charts/primitives/AreaGradient.d.ts +0 -18
  671. package/dist/charts/primitives/AreaGradient.d.ts.map +0 -1
  672. package/dist/charts/primitives/AreaGradient.js +0 -13
  673. package/dist/charts/primitives/Axes.d.ts +0 -83
  674. package/dist/charts/primitives/Axes.d.ts.map +0 -1
  675. package/dist/charts/primitives/Axes.js +0 -168
  676. package/dist/charts/primitives/CartesianChart.d.ts +0 -257
  677. package/dist/charts/primitives/CartesianChart.d.ts.map +0 -1
  678. package/dist/charts/primitives/CartesianChart.js +0 -549
  679. package/dist/charts/primitives/ChartCard.d.ts +0 -66
  680. package/dist/charts/primitives/ChartCard.d.ts.map +0 -1
  681. package/dist/charts/primitives/ChartCard.js +0 -143
  682. package/dist/charts/primitives/ChartFrame.d.ts +0 -156
  683. package/dist/charts/primitives/ChartFrame.d.ts.map +0 -1
  684. package/dist/charts/primitives/ChartFrame.js +0 -241
  685. package/dist/charts/primitives/ChartLegend.d.ts +0 -67
  686. package/dist/charts/primitives/ChartLegend.d.ts.map +0 -1
  687. package/dist/charts/primitives/ChartLegend.js +0 -292
  688. package/dist/charts/primitives/ChartPending.d.ts +0 -113
  689. package/dist/charts/primitives/ChartPending.d.ts.map +0 -1
  690. package/dist/charts/primitives/ChartPending.js +0 -117
  691. package/dist/charts/primitives/ChartTooltip.d.ts +0 -59
  692. package/dist/charts/primitives/ChartTooltip.d.ts.map +0 -1
  693. package/dist/charts/primitives/ChartTooltip.js +0 -197
  694. package/dist/charts/primitives/Crosshair.d.ts +0 -26
  695. package/dist/charts/primitives/Crosshair.d.ts.map +0 -1
  696. package/dist/charts/primitives/Crosshair.js +0 -23
  697. package/dist/charts/primitives/HatchPattern.d.ts +0 -31
  698. package/dist/charts/primitives/HatchPattern.d.ts.map +0 -1
  699. package/dist/charts/primitives/HatchPattern.js +0 -31
  700. package/dist/charts/primitives/HoverOverlay.d.ts +0 -45
  701. package/dist/charts/primitives/HoverOverlay.d.ts.map +0 -1
  702. package/dist/charts/primitives/HoverOverlay.js +0 -72
  703. package/dist/charts/primitives/HoverOverlay.module.css +0 -13
  704. package/dist/charts/primitives/HoverOverlay.module.css.d.ts +0 -8
  705. package/dist/charts/primitives/XZoneRects.d.ts +0 -54
  706. package/dist/charts/primitives/XZoneRects.d.ts.map +0 -1
  707. package/dist/charts/primitives/XZoneRects.js +0 -65
  708. package/dist/charts/primitives/ZoneRects.d.ts +0 -38
  709. package/dist/charts/primitives/ZoneRects.d.ts.map +0 -1
  710. package/dist/charts/primitives/ZoneRects.js +0 -30
  711. package/dist/charts/primitives/chart-card-context.d.ts +0 -33
  712. package/dist/charts/primitives/chart-card-context.d.ts.map +0 -1
  713. package/dist/charts/primitives/chart-card-context.js +0 -18
  714. package/dist/charts/primitives/chart-frame-layout.d.ts +0 -185
  715. package/dist/charts/primitives/chart-frame-layout.d.ts.map +0 -1
  716. package/dist/charts/primitives/chart-frame-layout.js +0 -175
  717. package/dist/charts/primitives/chart-layout.d.ts +0 -112
  718. package/dist/charts/primitives/chart-layout.d.ts.map +0 -1
  719. package/dist/charts/primitives/chart-layout.js +0 -175
  720. package/dist/charts/primitives/chart-tier.d.ts +0 -16
  721. package/dist/charts/primitives/chart-tier.d.ts.map +0 -1
  722. package/dist/charts/primitives/chart-tier.js +0 -44
  723. package/dist/charts/primitives/legend-disclosure.d.ts +0 -27
  724. package/dist/charts/primitives/legend-disclosure.d.ts.map +0 -1
  725. package/dist/charts/primitives/legend-disclosure.js +0 -153
  726. package/dist/charts/series.d.ts +0 -189
  727. package/dist/charts/series.d.ts.map +0 -1
  728. package/dist/charts/series.js +0 -84
  729. package/dist/charts/sparklines/BarSparkline.d.ts +0 -37
  730. package/dist/charts/sparklines/BarSparkline.d.ts.map +0 -1
  731. package/dist/charts/sparklines/BarSparkline.js +0 -53
  732. package/dist/charts/sparklines/LineSparkline.d.ts +0 -15
  733. package/dist/charts/sparklines/LineSparkline.d.ts.map +0 -1
  734. package/dist/charts/sparklines/LineSparkline.js +0 -51
  735. package/dist/charts/sparklines/index.d.ts +0 -3
  736. package/dist/charts/sparklines/index.d.ts.map +0 -1
  737. package/dist/charts/sparklines/index.js +0 -6
  738. package/dist/charts/utils/domain.d.ts +0 -35
  739. package/dist/charts/utils/domain.d.ts.map +0 -1
  740. package/dist/charts/utils/domain.js +0 -12
  741. package/dist/charts/utils/format.d.ts +0 -8
  742. package/dist/charts/utils/format.d.ts.map +0 -1
  743. package/dist/charts/utils/format.js +0 -20
  744. package/dist/charts/utils/measure-text.d.ts +0 -25
  745. package/dist/charts/utils/measure-text.d.ts.map +0 -1
  746. package/dist/charts/utils/measure-text.js +0 -64
  747. package/dist/charts/utils/ticks.d.ts +0 -103
  748. package/dist/charts/utils/ticks.d.ts.map +0 -1
  749. package/dist/charts/utils/ticks.js +0 -92
  750. package/dist/commands/overlays-mount.d.ts +0 -122
  751. package/dist/commands/overlays-mount.d.ts.map +0 -1
  752. package/dist/commands/overlays-mount.js +0 -125
  753. package/dist/commands/useCommandHotkeys.d.ts +0 -14
  754. package/dist/commands/useCommandHotkeys.d.ts.map +0 -1
  755. package/dist/commands/useCommandHotkeys.js +0 -45
  756. package/dist/content/article-actions.d.ts +0 -39
  757. package/dist/content/article-actions.d.ts.map +0 -1
  758. package/dist/content/article-actions.js +0 -28
  759. package/dist/content/article-card.d.ts +0 -58
  760. package/dist/content/article-card.d.ts.map +0 -1
  761. package/dist/content/article-card.js +0 -79
  762. package/dist/content/article-card.module.css +0 -111
  763. package/dist/content/mdx.d.ts +0 -20
  764. package/dist/content/mdx.d.ts.map +0 -1
  765. package/dist/content/mdx.js +0 -64
  766. package/dist/controls/compare-filter.d.ts +0 -42
  767. package/dist/controls/compare-filter.d.ts.map +0 -1
  768. package/dist/controls/compare-filter.js +0 -38
  769. package/dist/controls/slider-control.d.ts +0 -36
  770. package/dist/controls/slider-control.d.ts.map +0 -1
  771. package/dist/controls/slider-control.js +0 -64
  772. package/dist/dashboard/widget-grid.d.ts +0 -27
  773. package/dist/dashboard/widget-grid.d.ts.map +0 -1
  774. package/dist/dashboard/widget-grid.js +0 -40
  775. package/dist/dashboard/widget-grid.module.css +0 -69
  776. package/dist/data/data-table-state.d.ts.map +0 -1
  777. package/dist/data/data-table.d.ts.map +0 -1
  778. package/dist/data/query-branch.d.ts.map +0 -1
  779. package/dist/data/table.d.ts +0 -24
  780. package/dist/data/table.d.ts.map +0 -1
  781. package/dist/data/virtual-list.d.ts +0 -133
  782. package/dist/data/virtual-list.d.ts.map +0 -1
  783. package/dist/data/virtual-list.js +0 -157
  784. package/dist/data/virtual.d.ts +0 -21
  785. package/dist/data/virtual.d.ts.map +0 -1
  786. package/dist/data/virtual.js +0 -4
  787. package/dist/forms/FormErrorSummary.d.ts +0 -30
  788. package/dist/forms/FormErrorSummary.d.ts.map +0 -1
  789. package/dist/forms/FormErrorSummary.js +0 -30
  790. package/dist/forms/use-field-array.d.ts +0 -60
  791. package/dist/forms/use-field-array.d.ts.map +0 -1
  792. package/dist/forms/use-field-array.js +0 -20
  793. package/dist/forms/use-form-draft.d.ts +0 -76
  794. package/dist/forms/use-form-draft.d.ts.map +0 -1
  795. package/dist/forms/use-form-draft.js +0 -92
  796. package/dist/forms/use-form-submit.d.ts +0 -88
  797. package/dist/forms/use-form-submit.d.ts.map +0 -1
  798. package/dist/forms/use-form-submit.js +0 -63
  799. package/dist/notifications/bell.d.ts.map +0 -1
  800. package/dist/notifications/center.d.ts.map +0 -1
  801. package/dist/notifications/define-notifications.d.ts.map +0 -1
  802. package/dist/notifications/index.d.ts +0 -40
  803. package/dist/notifications/index.d.ts.map +0 -1
  804. package/dist/notifications/index.js +0 -54
  805. package/dist/notifications/mount-guard.d.ts +0 -8
  806. package/dist/notifications/mount-guard.d.ts.map +0 -1
  807. package/dist/notifications/mount-guard.js +0 -18
  808. package/dist/notifications/notify.d.ts +0 -149
  809. package/dist/notifications/notify.d.ts.map +0 -1
  810. package/dist/notifications/notify.js +0 -171
  811. package/dist/notifications/store.d.ts.map +0 -1
  812. package/dist/provider/connectivity-indicator.d.ts +0 -13
  813. package/dist/provider/connectivity-indicator.d.ts.map +0 -1
  814. package/dist/provider/connectivity-indicator.js +0 -205
  815. package/dist/provider/connectivity-provider.d.ts +0 -7
  816. package/dist/provider/connectivity-provider.d.ts.map +0 -1
  817. package/dist/provider/connectivity-provider.js +0 -128
  818. package/dist/provider/connectivity-types.d.ts +0 -36
  819. package/dist/provider/connectivity-types.d.ts.map +0 -1
  820. package/dist/provider/use-connectivity.d.ts +0 -3
  821. package/dist/provider/use-connectivity.d.ts.map +0 -1
  822. package/dist/provider/use-connectivity.js +0 -14
  823. package/dist/query-devtools.d.ts +0 -21
  824. package/dist/query-devtools.d.ts.map +0 -1
  825. package/dist/query-devtools.js +0 -13
  826. package/src/agent/adapter.ts +0 -945
  827. package/src/agent/use-agent-stream.ts +0 -154
  828. package/src/agent-chat/index.ts +0 -54
  829. package/src/agent-chat/thread-detail-panel.tsx +0 -201
  830. package/src/agent-chat/thread-workspace.tsx +0 -279
  831. package/src/charts/cursor/resolve.ts +0 -198
  832. package/src/charts/cursor/scope.tsx +0 -35
  833. package/src/charts/cursor/store.ts +0 -58
  834. package/src/charts/cursor/touch-pin.ts +0 -138
  835. package/src/charts/hooks/use-discrete-cursor.ts +0 -335
  836. package/src/charts/hooks/use-hysteresis.ts +0 -26
  837. package/src/charts/hooks/useBandPlot.ts +0 -383
  838. package/src/charts/hooks/useChartCursor.ts +0 -367
  839. package/src/charts/hooks/useChartSize.ts +0 -35
  840. package/src/charts/kinds/BandStrip.tsx +0 -449
  841. package/src/charts/kinds/Bars.tsx +0 -589
  842. package/src/charts/kinds/Donut.tsx +0 -368
  843. package/src/charts/kinds/DualPanel.tsx +0 -712
  844. package/src/charts/kinds/Heatmap.tsx +0 -396
  845. package/src/charts/kinds/MirroredBars.tsx +0 -469
  846. package/src/charts/kinds/MultiLine.tsx +0 -322
  847. package/src/charts/kinds/StackedArea.tsx +0 -267
  848. package/src/charts/kinds/ZonedLine.tsx +0 -301
  849. package/src/charts/kinds/band-chrome.tsx +0 -91
  850. package/src/charts/kinds/donut-layout.ts +0 -82
  851. package/src/charts/layout/auto-margin.ts +0 -242
  852. package/src/charts/layout/end-labels.ts +0 -135
  853. package/src/charts/layout/log-ticks.ts +0 -90
  854. package/src/charts/primitives/AreaGradient.tsx +0 -26
  855. package/src/charts/primitives/Axes.tsx +0 -271
  856. package/src/charts/primitives/CartesianChart.tsx +0 -1002
  857. package/src/charts/primitives/ChartCard.tsx +0 -280
  858. package/src/charts/primitives/ChartFrame.tsx +0 -493
  859. package/src/charts/primitives/ChartLegend.tsx +0 -422
  860. package/src/charts/primitives/ChartPending.tsx +0 -243
  861. package/src/charts/primitives/ChartTooltip.tsx +0 -265
  862. package/src/charts/primitives/Crosshair.tsx +0 -43
  863. package/src/charts/primitives/HatchPattern.tsx +0 -55
  864. package/src/charts/primitives/HoverOverlay.module.css +0 -13
  865. package/src/charts/primitives/HoverOverlay.module.css.d.ts +0 -8
  866. package/src/charts/primitives/HoverOverlay.tsx +0 -109
  867. package/src/charts/primitives/XZoneRects.tsx +0 -132
  868. package/src/charts/primitives/ZoneRects.tsx +0 -67
  869. package/src/charts/primitives/chart-card-context.ts +0 -46
  870. package/src/charts/primitives/chart-frame-layout.ts +0 -430
  871. package/src/charts/primitives/chart-layout.ts +0 -318
  872. package/src/charts/primitives/chart-tier.tsx +0 -76
  873. package/src/charts/primitives/legend-disclosure.tsx +0 -178
  874. package/src/charts/series.ts +0 -276
  875. package/src/charts/sparklines/BarSparkline.tsx +0 -94
  876. package/src/charts/sparklines/LineSparkline.tsx +0 -71
  877. package/src/charts/sparklines/index.ts +0 -2
  878. package/src/charts/theme.tsx +0 -53
  879. package/src/charts/utils/domain.ts +0 -41
  880. package/src/charts/utils/format.ts +0 -19
  881. package/src/charts/utils/measure-text.ts +0 -109
  882. package/src/charts/utils/ticks.ts +0 -239
  883. package/src/commands/overlays-mount.tsx +0 -333
  884. package/src/commands/useCommandHotkeys.ts +0 -114
  885. package/src/content/article-actions.ts +0 -72
  886. package/src/content/article-card.module.css +0 -111
  887. package/src/content/article-card.tsx +0 -174
  888. package/src/content/mdx.tsx +0 -127
  889. package/src/controls/compare-filter.tsx +0 -81
  890. package/src/controls/slider-control.tsx +0 -159
  891. package/src/dashboard/widget-grid.module.css +0 -69
  892. package/src/dashboard/widget-grid.tsx +0 -146
  893. package/src/data/table.ts +0 -39
  894. package/src/data/virtual-list.tsx +0 -356
  895. package/src/data/virtual.ts +0 -26
  896. package/src/forms/FormErrorSummary.tsx +0 -71
  897. package/src/forms/use-field-array.ts +0 -94
  898. package/src/forms/use-form-draft.ts +0 -262
  899. package/src/forms/use-form-submit.ts +0 -199
  900. package/src/notifications/index.ts +0 -88
  901. package/src/notifications/mount-guard.ts +0 -38
  902. package/src/notifications/notify.tsx +0 -379
  903. package/src/provider/connectivity-indicator.tsx +0 -267
  904. package/src/provider/connectivity-provider.tsx +0 -189
  905. package/src/provider/connectivity-types.ts +0 -42
  906. package/src/provider/use-connectivity.ts +0 -26
  907. package/src/query-devtools.tsx +0 -40
  908. /package/dist/{agent-chat → agent/chat}/message-affordances.js +0 -0
  909. /package/dist/{agent-chat → agent/chat}/relative-time.d.ts +0 -0
  910. /package/dist/{agent-chat → agent/chat}/relative-time.js +0 -0
  911. /package/dist/{agent-chat → agent/chat}/use-keyboard-inset.d.ts +0 -0
  912. /package/dist/{agent-chat → agent/chat}/virtualize.js +0 -0
  913. /package/dist/{notifications → commands/notifications}/bell.js +0 -0
  914. /package/dist/{notifications → commands/notifications}/define-notifications.js +0 -0
  915. /package/dist/{provider/connectivity-types.js → controls/uncontrolled.js} +0 -0
  916. /package/dist/{data → table}/data-table.module.css +0 -0
  917. /package/dist/{data → table}/data-table.module.css.d.ts +0 -0
  918. /package/dist/{data → table}/query-branch.d.ts +0 -0
  919. /package/dist/{data → table}/query-branch.js +0 -0
  920. /package/src/{agent-chat → agent/chat}/relative-time.ts +0 -0
  921. /package/src/{data → table}/data-table.module.css +0 -0
  922. /package/src/{data → table}/data-table.module.css.d.ts +0 -0
  923. /package/src/{data → table}/query-branch.ts +0 -0
@@ -15,9 +15,10 @@ import { dirname, resolve } from 'node:path'
15
15
  import basaltPlugin, {
16
16
  CTL_THEME_TAGS,
17
17
  WAIVER_ID_ALIASES,
18
- DEPRECATED_EXPORTS,
19
18
  KNOWN_RULE_IDS,
20
- PLUGIN_RULE_ADVISORY,
19
+ CHART_ENGINE_ALLOWED_PREFIXES,
20
+ CHART_ENGINE_ALLOWED_SUBPATHS,
21
+ isAllowedChartEngineSubpath,
21
22
  PLUGIN_RULE_GRACE,
22
23
  RAW_BREAKPOINT_RECIPE,
23
24
  RETIRED_RULE_IDS,
@@ -45,16 +46,12 @@ beforeEach(() => {
45
46
  plugins: [],
46
47
  jsPlugins: [PLUGIN_PATH],
47
48
  rules: {
48
- 'basalt/provider-above-router': 'error',
49
49
  'basalt/query-fn-unwrap': 'error',
50
- 'basalt/deprecated-export': 'error',
51
50
  'basalt/forms-field-key': 'error',
52
51
  'basalt/no-raw-font-size': 'error',
53
52
  'basalt/raw-size-literal': 'error',
54
53
  'basalt/card-inset': 'error',
55
54
  'basalt/chart-in-raw-surface': 'error',
56
- 'basalt/hand-rolled-plot': 'error',
57
- 'basalt/chart-legend-literal': 'error',
58
55
  'basalt/shadow-basalt-export': 'error',
59
56
  'basalt/hand-rolled-shell': 'error',
60
57
  'basalt/raw-scroll-container': 'error',
@@ -62,18 +59,18 @@ beforeEach(() => {
62
59
  'basalt/control-outside-home': 'error',
63
60
  'basalt/bound-control-outside-home': 'error',
64
61
  'basalt/control-size-literal': 'error',
65
- 'basalt/page-bar-budget': 'error',
66
62
  'basalt/in-body-page-title': 'error',
67
63
  'basalt/responsive-twin': 'error',
68
64
  'basalt/search-literal-link': 'error',
69
65
  'basalt/use-search-from-literal': 'error',
70
- 'basalt/visx-boundary': 'error',
71
- 'basalt/visx-tooltip': 'error',
66
+ 'basalt/chart-engine-boundary': 'error',
67
+ 'basalt/chart-host-single': 'error',
72
68
  'basalt/token-layer-boundary': 'error',
73
- 'basalt/agent-resume-guard': 'error',
74
69
  'basalt/agent-no-raw-usechat': 'error',
75
70
  'basalt/ai-sdk-major': 'error',
76
71
  'basalt/raw-breakpoint': 'error',
72
+ 'basalt/no-dev-helper': 'error',
73
+ 'basalt/route-view-transition': 'error',
77
74
  },
78
75
  }),
79
76
  )
@@ -245,88 +242,357 @@ describe('basalt/card-inset', () => {
245
242
 
246
243
  describe('basalt/chart-in-raw-surface', () => {
247
244
  it('flags a chart-kind element inside a raw Card', () => {
248
- const { code, rules } = run(`export const C = () => <Card><ZonedLine data={[]} /></Card>\n`)
245
+ const { code, rules } = run(`export const C = () => <Card><Lines data={[]} /></Card>\n`)
249
246
  expect(code).toBe(1)
250
247
  expect(rules).toContain('chart-in-raw-surface')
251
248
  })
252
249
 
253
250
  it('does NOT flag a chart passed as a prop value (not a Card/Paper subtree)', () => {
254
251
  const { code, rules } = run(
255
- `export const C = () => <Card><StatCard sparkline={<LineSparkline />} /></Card>\n`,
252
+ `export const C = () => <Card><StatCard sparkline={<Sparkline />} /></Card>\n`,
256
253
  )
257
254
  expect(code).toBe(0)
258
255
  expect(rules).not.toContain('chart-in-raw-surface')
259
256
  })
260
257
  })
261
258
 
262
- // ── visx-boundary ────────────────────────────────────────────────────────────
259
+ // ── chart-engine-boundary ────────────────────────────────────────────────────
263
260
 
264
- describe('basalt/visx-boundary', () => {
265
- it('flags a @visx/* import outside charts', () => {
266
- const { code, rules } = run(`import { scaleLinear } from '@visx/scale'\n`, 'lib.ts')
261
+ describe('basalt/chart-engine-boundary', () => {
262
+ it.each([
263
+ ['@visx/scale', `import { scaleLinear } from '@visx/scale'\n`],
264
+ ['@visx/tooltip', `import { Tooltip } from '@visx/tooltip'\n`],
265
+ ['a source-bearing named re-export', `export { scaleLinear } from '@visx/scale'\n`],
266
+ ['a wildcard re-export', `export * from '@visx/scale'\n`],
267
+ ['a dynamic import', `export const load = () => import('@visx/shape')\n`],
268
+ ])('bans %s outside basalt', (_label, source) => {
269
+ const { code, rules } = run(source, 'lib.ts')
267
270
  expect(code).toBe(1)
268
- expect(rules).toContain('visx-boundary')
271
+ expect(rules).toContain('chart-engine-boundary')
269
272
  })
270
273
 
271
- it('does NOT flag a @visx/* import inside charts', () => {
272
- const { code, rules } = run(`import { scaleLinear } from '@visx/scale'\n`, 'charts/lib.ts')
273
- expect(code).toBe(0)
274
- expect(rules).not.toContain('visx-boundary')
274
+ it('bans @visx/* inside basalt source and a charts/ directory too', () => {
275
+ for (const file of ['charts/lib.tsx', 'packages/basalt-ui/src/charts/lib.tsx']) {
276
+ const { code, rules } = run(`import { Bar } from '@visx/shape'\n`, file)
277
+ expect([file, code]).toEqual([file, 1])
278
+ expect(rules).toContain('chart-engine-boundary')
279
+ }
275
280
  })
276
281
 
277
- it('does NOT flag a @mantine/* import (out of scope for this rule)', () => {
278
- const { code, rules } = run(`import { Button } from '@mantine/core'\n`, 'lib.tsx')
282
+ it('still bans @visx/* under a theme-allow comment (no escape hatch)', () => {
283
+ const { rules } = run(
284
+ `// theme-allow chart-engine-boundary — legacy\nimport { scaleLinear } from '@visx/scale'\n`,
285
+ 'lib.ts',
286
+ )
287
+ expect(rules).toContain('chart-engine-boundary')
288
+ })
289
+
290
+ it.each([
291
+ '@tanstack/charts/line',
292
+ '@tanstack/charts/area-x',
293
+ '@tanstack/charts/mark/decorative',
294
+ '@tanstack/charts/d3/shape',
295
+ '@tanstack/charts/scales/linear',
296
+ '@tanstack/charts/transform/bin',
297
+ '@tanstack/charts/types',
298
+ ])('allows %s outside basalt', (specifier) => {
299
+ const { code, rules } = run(`import * as m from '${specifier}'\nexport { m }\n`, 'lib.ts')
300
+ expect([specifier, code]).toEqual([specifier, 0])
301
+ expect(rules).not.toContain('chart-engine-boundary')
302
+ })
303
+
304
+ it.each([
305
+ '@tanstack/charts',
306
+ '@tanstack/charts/react',
307
+ '@tanstack/charts/react/tooltip',
308
+ '@tanstack/charts/svg',
309
+ '@tanstack/charts/mark/composite',
310
+ '@tanstack/charts/transform',
311
+ '@tanstack/charts/scales/',
312
+ '@tanstack/charts/line/extra',
313
+ ])('bans %s outside basalt and names the allowed alternative', (specifier) => {
314
+ const { code, rules, output } = run(
315
+ `import * as m from '${specifier}'\nexport { m }\n`,
316
+ 'lib.ts',
317
+ )
318
+ expect([specifier, code]).toEqual([specifier, 1])
319
+ expect(rules).toContain('chart-engine-boundary')
320
+ expect(output).toContain('basalt-ui/charts')
321
+ })
322
+
323
+ it.each([
324
+ "const m = require('@visx/scale')\n",
325
+ "const m = require('@tanstack/charts')\n",
326
+ "const m = require('@tanstack/charts/react')\n",
327
+ "const m = require('@tanstack/charts/scales/../react')\n",
328
+ ])('bans a literal require: %s', (source) => {
329
+ const { code, rules } = run(`${source}export { m }\n`, 'lib.ts')
330
+ expect([source, code]).toEqual([source, 1])
331
+ expect(rules).toContain('chart-engine-boundary')
332
+ })
333
+
334
+ it('allows a literal require of an allowed subpath and ignores computed ones', () => {
335
+ const { code } = run(
336
+ `const a = require('@tanstack/charts/line')\nconst b = require(name)\nexport { a, b }\n`,
337
+ 'lib.ts',
338
+ )
279
339
  expect(code).toBe(0)
280
- expect(rules).not.toContain('visx-boundary')
281
340
  })
282
341
 
283
- it('still flags a @visx/* import outside charts carrying a theme-allow comment (no escape hatch)', () => {
284
- const { code, rules } = run(
285
- `// theme-allow: legacy\nimport { scaleLinear } from '@visx/scale'\n`,
342
+ it('bans a traversal out of an allowed family', () => {
343
+ const { rules } = run(
344
+ `import * as m from '@tanstack/charts/scales/../react'\nexport { m }\n`,
286
345
  'lib.ts',
287
346
  )
288
- expect(code).toBe(1)
289
- expect(rules).toContain('visx-boundary')
347
+ expect(rules).toContain('chart-engine-boundary')
290
348
  })
291
349
 
292
- it('flags a source-bearing named re-export of @visx/* outside charts', () => {
293
- const { code, rules } = run(`export { scaleLinear } from '@visx/scale'\n`, 'lib.ts')
294
- expect(code).toBe(1)
295
- expect(rules).toContain('visx-boundary')
350
+ it('bans a re-export of the bare root outside basalt', () => {
351
+ const { rules } = run(`export { defineChart } from '@tanstack/charts'\n`, 'lib.ts')
352
+ expect(rules).toContain('chart-engine-boundary')
296
353
  })
297
354
 
298
- it('flags a wildcard re-export of @visx/* outside charts', () => {
299
- const { code, rules } = run(`export * from '@visx/scale'\n`, 'lib.ts')
300
- expect(code).toBe(1)
301
- expect(rules).toContain('visx-boundary')
355
+ it('does NOT apply the allowlist inside basalt source', () => {
356
+ const { code, rules } = run(
357
+ `import { ChartRuntime } from '@tanstack/charts/react'\nexport { ChartRuntime }\n`,
358
+ 'packages/basalt-ui/src/charts/host.tsx',
359
+ )
360
+ expect(code).toBe(0)
361
+ expect(rules).not.toContain('chart-engine-boundary')
302
362
  })
303
363
 
304
- it('does NOT flag a plain local named export (no source)', () => {
305
- const { code, rules } = run(`const x = 1\nexport { x }\n`, 'lib.ts')
364
+ it('does not touch unrelated @tanstack packages', () => {
365
+ const { code } = run(
366
+ `import { useQuery } from '@tanstack/react-query'\nexport { useQuery }\n`,
367
+ 'lib.ts',
368
+ )
306
369
  expect(code).toBe(0)
307
- expect(rules).not.toContain('visx-boundary')
308
370
  })
309
371
  })
310
372
 
311
- // ── visx-tooltip ─────────────────────────────────────────────────────────────
373
+ describe('isAllowedChartEngineSubpath', () => {
374
+ it.each([...CHART_ENGINE_ALLOWED_SUBPATHS, 'scales/linear', 'transform/bin-time'])(
375
+ 'allows %s',
376
+ (subpath) => expect(isAllowedChartEngineSubpath(subpath)).toBe(true),
377
+ )
312
378
 
313
- describe('basalt/visx-tooltip', () => {
314
- it('flags @visx/tooltip even inside charts', () => {
315
- const { code, rules } = run(`import { Tooltip } from '@visx/tooltip'\n`, 'charts/lib.tsx')
316
- expect(code).toBe(1)
317
- expect(rules).toContain('visx-tooltip')
379
+ it.each([
380
+ '',
381
+ 'scales',
382
+ 'scales/',
383
+ 'transform',
384
+ 'transform/',
385
+ 'react',
386
+ 'line/extra',
387
+ 'scales/../react',
388
+ 'scales/./linear',
389
+ 'scales//linear',
390
+ '../line',
391
+ 'line/..',
392
+ ])('rejects %j', (subpath) => expect(isAllowedChartEngineSubpath(subpath)).toBe(false))
393
+ })
394
+
395
+ // ── chart-host-single ────────────────────────────────────────────────────────
396
+
397
+ describe('basalt/chart-host-single', () => {
398
+ const REACT_IMPORT = `import { Chart } from '@tanstack/charts/react'\nexport { Chart }\n`
399
+
400
+ it('allows @tanstack/charts/react in packages/basalt-ui/src/charts/host.tsx', () => {
401
+ const { rules } = run(REACT_IMPORT, 'packages/basalt-ui/src/charts/host.tsx')
402
+ expect(rules).not.toContain('chart-host-single')
318
403
  })
319
404
 
320
- it('flags @visx/tooltip outside charts too', () => {
321
- const { code, rules } = run(`import { Tooltip } from '@visx/tooltip'\n`, 'lib.tsx')
322
- expect(code).toBe(1)
323
- expect(rules).toContain('visx-tooltip')
405
+ it.each([
406
+ 'packages/basalt-ui/src/charts/kinds/lines.tsx',
407
+ 'packages/basalt-ui/src/charts/host.ts',
408
+ 'packages/basalt-ui/src/chart.tsx',
409
+ ])('flags it in %s', (file) => {
410
+ const { code, rules } = run(REACT_IMPORT, file)
411
+ expect([file, code]).toEqual([file, 1])
412
+ expect(rules).toContain('chart-host-single')
413
+ })
414
+
415
+ it('stays silent outside basalt source — chart-engine-boundary owns that one diagnostic', () => {
416
+ for (const file of ['apps/playground/src/chart.tsx', 'host.tsx']) {
417
+ const { rules } = run(REACT_IMPORT, file)
418
+ expect([file, rules.has('chart-host-single')]).toEqual([file, false])
419
+ }
420
+ })
421
+
422
+ it('is not waived by theme-allow', () => {
423
+ const { rules } = run(
424
+ `// theme-allow chart-host-single — just this once\n${REACT_IMPORT}`,
425
+ 'packages/basalt-ui/src/charts/kinds/lines.tsx',
426
+ )
427
+ expect(rules).toContain('chart-host-single')
428
+ })
429
+
430
+ it('flags require() of the react entry', () => {
431
+ const { rules } = run(
432
+ `const { Chart } = require('@tanstack/charts/react')\nexport { Chart }\n`,
433
+ 'packages/basalt-ui/src/charts/a.ts',
434
+ )
435
+ expect(rules).toContain('chart-host-single')
436
+ })
437
+
438
+ it('flags the react sub-entries and a dynamic import too', () => {
439
+ expect(
440
+ run(
441
+ `import 'x'\nexport * from '@tanstack/charts/react/tooltip'\n`,
442
+ 'packages/basalt-ui/src/a.ts',
443
+ ).rules,
444
+ ).toContain('chart-host-single')
445
+ expect(
446
+ run(
447
+ `export const l = () => import('@tanstack/charts/react')\n`,
448
+ 'packages/basalt-ui/src/b.ts',
449
+ ).rules,
450
+ ).toContain('chart-host-single')
451
+ })
452
+
453
+ it('leaves the mark subpaths alone', () => {
454
+ const { rules } = run(`import { line } from '@tanstack/charts/line'\nexport { line }\n`, 'a.ts')
455
+ expect(rules).not.toContain('chart-host-single')
456
+ })
457
+ })
458
+
459
+ // ── chart-engine-boundary ↔ the engine's `exports` (V2-SPEC § 9.8) ──────────────────────────────
460
+
461
+ /**
462
+ * Every `@tanstack/charts` subpath that is deliberately NOT importable outside basalt, classified
463
+ * against 1.1.0's `exports` ('' is the bare root). A new engine subpath is in neither this list nor
464
+ * the allowlist, so the test below fails until someone decides which it is.
465
+ */
466
+ const BANNED_ENGINE_SUBPATHS: readonly string[] = [
467
+ '',
468
+ 'adapter',
469
+ 'adapter/renderer',
470
+ 'alpine',
471
+ 'angular',
472
+ 'band',
473
+ 'box',
474
+ 'canvas',
475
+ 'crosshair',
476
+ 'cursor',
477
+ 'cursor/host',
478
+ 'd3/area-x',
479
+ 'dodge',
480
+ 'dom',
481
+ 'export',
482
+ 'facet',
483
+ 'focus',
484
+ 'focus/disabled',
485
+ 'focus/guide',
486
+ 'focus/mark',
487
+ 'frame',
488
+ 'geo',
489
+ 'group',
490
+ 'hexagon',
491
+ 'hierarchy/sunburst',
492
+ 'hierarchy/tree',
493
+ 'hierarchy/treemap',
494
+ 'interaction/brush',
495
+ 'interaction/cursor',
496
+ 'interaction/handle',
497
+ 'interaction/signal',
498
+ 'interaction/zoom',
499
+ 'legend',
500
+ 'lit',
501
+ 'mark/composite',
502
+ 'mark/scale-values',
503
+ 'motion',
504
+ 'motion/definition',
505
+ 'network/force',
506
+ 'network/sankey',
507
+ 'octane',
508
+ 'octane/canvas',
509
+ 'octane/core',
510
+ 'polar',
511
+ 'preact',
512
+ 'react',
513
+ 'react-native',
514
+ 'react-native/tooltip',
515
+ 'react/canvas',
516
+ 'react/core',
517
+ 'react/tooltip',
518
+ 'reconcile',
519
+ 'regression',
520
+ 'renderer',
521
+ 'renderer/rect',
522
+ 'ridgeline',
523
+ 'runtime',
524
+ 'scene',
525
+ 'selection',
526
+ 'solid',
527
+ 'spatial/contour',
528
+ 'spatial/delaunay',
529
+ 'spatial/density',
530
+ 'spatial/hexbin',
531
+ 'spatial/voronoi',
532
+ 'spring',
533
+ 'svelte',
534
+ 'svg',
535
+ 'svg/renderer',
536
+ 'svg/resources',
537
+ 'tooltip',
538
+ 'tooltip/model',
539
+ 'tooltip/portal',
540
+ 'transform',
541
+ 'types/core',
542
+ 'universal',
543
+ 'view',
544
+ 'violin',
545
+ 'vue',
546
+ 'waffle',
547
+ ]
548
+
549
+ // The package that declares the peer: bun's isolated linker installs it there, never at the root.
550
+ const ENGINE_PACKAGE_JSON = resolve(
551
+ import.meta.dirname,
552
+ '..',
553
+ 'node_modules',
554
+ '@tanstack',
555
+ 'charts',
556
+ 'package.json',
557
+ )
558
+
559
+ describe('chart-engine-boundary ↔ @tanstack/charts exports', () => {
560
+ const exportKeys = (): string[] => {
561
+ const manifest = JSON.parse(readFileSync(ENGINE_PACKAGE_JSON, 'utf8')) as {
562
+ exports: Record<string, unknown>
563
+ }
564
+ return Object.keys(manifest.exports).map((key) => (key === '.' ? '' : key.replace(/^\.\//, '')))
565
+ }
566
+
567
+ it('every exact allowlisted subpath exists in the engine', () => {
568
+ const exports = new Set(exportKeys())
569
+ for (const subpath of CHART_ENGINE_ALLOWED_SUBPATHS) {
570
+ expect([subpath, exports.has(subpath)]).toEqual([subpath, true])
571
+ }
324
572
  })
325
573
 
326
- it('takes precedence over basalt/visx-boundary for @visx/tooltip specifically', () => {
327
- const { rules } = run(`import { Tooltip } from '@visx/tooltip'\n`, 'lib.tsx')
328
- expect(rules).toContain('visx-tooltip')
329
- expect(rules).not.toContain('visx-boundary')
574
+ it('every allowlisted family matches at least one engine subpath', () => {
575
+ for (const prefix of CHART_ENGINE_ALLOWED_PREFIXES) {
576
+ expect([prefix, exportKeys().some((key) => key.startsWith(prefix))]).toEqual([prefix, true])
577
+ }
578
+ })
579
+
580
+ it('every engine subpath is allowlisted or deliberately banned', () => {
581
+ const unclassified = exportKeys().filter(
582
+ (key) => !isAllowedChartEngineSubpath(key) && !BANNED_ENGINE_SUBPATHS.includes(key),
583
+ )
584
+ expect(unclassified).toEqual([])
585
+ })
586
+
587
+ it('the banned list has no stale or allowlisted entries', () => {
588
+ const exports = new Set(exportKeys())
589
+ for (const subpath of BANNED_ENGINE_SUBPATHS) {
590
+ expect([subpath, exports.has(subpath), isAllowedChartEngineSubpath(subpath)]).toEqual([
591
+ subpath,
592
+ true,
593
+ false,
594
+ ])
595
+ }
330
596
  })
331
597
  })
332
598
 
@@ -353,80 +619,29 @@ describe('basalt/token-layer-boundary', () => {
353
619
  })
354
620
 
355
621
  // ── rule independence ──────────────────────────────────────────────────────────
356
- // The three rules used to be one bundled `import-boundary` rule with a single on/off toggle — a
357
- // consumer disabling the one check they disagreed with silently dropped the other two. Proves
358
- // disabling one of the three leaves the other two enforced.
622
+ // Each boundary rule has its own id, so disabling one leaves the others enforced.
359
623
 
360
624
  describe('rule independence', () => {
361
- it('disabling basalt/visx-boundary leaves basalt/visx-tooltip enforced', () => {
625
+ it('disabling basalt/chart-engine-boundary leaves basalt/chart-host-single enforced', () => {
362
626
  writeFileSync(
363
627
  resolve(dir, '.oxlintrc.json'),
364
628
  JSON.stringify({
365
629
  plugins: [],
366
630
  jsPlugins: [PLUGIN_PATH],
367
631
  rules: {
368
- 'basalt/visx-boundary': 'off',
369
- 'basalt/visx-tooltip': 'error',
632
+ 'basalt/chart-engine-boundary': 'off',
633
+ 'basalt/chart-host-single': 'error',
370
634
  'basalt/token-layer-boundary': 'error',
371
635
  },
372
636
  }),
373
637
  )
374
638
  const { code, rules } = run(
375
- `import { scaleLinear } from '@visx/scale'\nimport { Tooltip } from '@visx/tooltip'\n`,
376
- 'lib.ts',
639
+ `import { scaleLinear } from '@visx/scale'\nimport { Chart } from '@tanstack/charts/react'\n`,
640
+ 'packages/basalt-ui/src/lib.ts',
377
641
  )
378
642
  expect(code).toBe(1)
379
- expect(rules).not.toContain('visx-boundary')
380
- expect(rules).toContain('visx-tooltip')
381
- })
382
- })
383
-
384
- // ── agent-resume-guard ───────────────────────────────────────────────────────
385
- // The two agent rules honour a SEPARATE escape token, `basalt-agent-allow`, not `theme-allow` — a
386
- // color exemption must never be able to switch off a streaming guard. The `theme-allow` fixture
387
- // below proves the wrong token does not suppress the finding.
388
-
389
- describe('basalt/agent-resume-guard', () => {
390
- it('flags useChat({ resume: true })', () => {
391
- const { code, rules } = run(`const c = useChat({ id, resume: true })\n`)
392
- expect(code).toBe(1)
393
- expect(rules).toContain('agent-resume-guard')
394
- })
395
-
396
- it('does NOT flag useChat({ resume: false })', () => {
397
- const { code, rules } = run(`const c = useChat({ id, resume: false })\n`)
398
- expect(code).toBe(0)
399
- expect(rules).not.toContain('agent-resume-guard')
400
- })
401
-
402
- it('does NOT flag a bare useChat({ id }) with no resume key', () => {
403
- const { code, rules } = run(`const c = useChat({ id })\n`)
404
- expect(code).toBe(0)
405
- expect(rules).not.toContain('agent-resume-guard')
406
- })
407
-
408
- it('flags chat.resumeStream() called from an effect', () => {
409
- const { code, rules } = run(`useEffect(() => { chat.resumeStream() }, [])\n`)
410
- expect(code).toBe(1)
411
- expect(rules).toContain('agent-resume-guard')
412
- })
413
-
414
- it('does NOT flag chat.resumeStream() marked basalt-agent-allow', () => {
415
- const { code, rules } = run(`// basalt-agent-allow\nchat.resumeStream()\n`)
416
- expect(code).toBe(0)
417
- expect(rules).not.toContain('agent-resume-guard')
418
- })
419
-
420
- it('STILL flags chat.resumeStream() marked only theme-allow (wrong escape token)', () => {
421
- const { code, rules } = run(`// theme-allow\nchat.resumeStream()\n`)
422
- expect(code).toBe(1)
423
- expect(rules).toContain('agent-resume-guard')
424
- })
425
-
426
- it('does NOT flag an unrelated call like runs.start(id, text)', () => {
427
- const { code, rules } = run(`runs.start(id, text)\n`)
428
- expect(code).toBe(0)
429
- expect(rules).not.toContain('agent-resume-guard')
643
+ expect(rules).not.toContain('chart-engine-boundary')
644
+ expect(rules).toContain('chart-host-single')
430
645
  })
431
646
  })
432
647
 
@@ -560,7 +775,7 @@ describe('basalt/ai-sdk-major', () => {
560
775
  })
561
776
 
562
777
  // Unlike the other rules in this file, ai-sdk-major honours `basalt-agent-allow` — the SAME
563
- // token as its two agent-chat siblings above — not `theme-allow`. A skewed producer/consumer
778
+ // token as its agent-chat sibling above — not `theme-allow`. A skewed producer/consumer
564
779
  // import that is genuinely intentional (see doctor's `aiMajorSkewReason`) can mark the line.
565
780
  it('does NOT flag an import marked basalt-agent-allow, even with a skewed nearest package.json', () => {
566
781
  writeFileSync(
@@ -671,14 +886,14 @@ describe('basalt/raw-scroll-container promotion', () => {
671
886
  })
672
887
 
673
888
  // ── agent rules — promotion (warn → error shipped) ──────────────────────────
674
- // The three agent-chat rules were promoted warn → error in the SHIPPED preset alongside
889
+ // The two agent-chat rules were promoted warn → error in the SHIPPED preset alongside
675
890
  // raw-scroll-container above, but had no equivalent lock — `scripts/gen-oxlint.ts` only regenerates
676
891
  // the `overrides` array; this top-level `rules` block is hand-maintained and
677
892
  // `tests/oxlint-preset-sync.test.ts` only checks `overrides` against `projectBanList('shipped')`, so
678
- // a later accidental revert of one of these three back to "warn" would pass the suite silently.
893
+ // a later accidental revert of one of these back to "warn" would pass the suite silently.
679
894
 
680
895
  describe('basalt agent rules promotion', () => {
681
- it.each(['basalt/agent-resume-guard', 'basalt/agent-no-raw-usechat', 'basalt/ai-sdk-major'])(
896
+ it.each(['basalt/agent-no-raw-usechat', 'basalt/ai-sdk-major'])(
682
897
  '%s ships "error" (not "warn"/"off") in the consumer preset',
683
898
  (rule) => {
684
899
  const shipped = JSON.parse(
@@ -688,7 +903,7 @@ describe('basalt agent rules promotion', () => {
688
903
  },
689
904
  )
690
905
 
691
- it.each(['basalt/agent-resume-guard', 'basalt/agent-no-raw-usechat', 'basalt/ai-sdk-major'])(
906
+ it.each(['basalt/agent-no-raw-usechat', 'basalt/ai-sdk-major'])(
692
907
  '%s stays at the shipped level ("error") repo-local',
693
908
  (rule) => {
694
909
  const repoLocal = JSON.parse(
@@ -699,189 +914,71 @@ describe('basalt agent rules promotion', () => {
699
914
  )
700
915
  })
701
916
 
702
- // ── hand-rolled-plot ─────────────────────────────────────────────────────────
703
-
704
- const CHARTS_IMPORT = `import { AxisLeftNumeric, CartesianChart, ChartLegend, Crosshair, HoverOverlay } from 'basalt-ui/charts'\n`
705
-
706
- describe('basalt/hand-rolled-plot', () => {
707
- it('flags an axis primitive in a file that never composes CartesianChart', () => {
708
- const { code, rules } = run(
709
- `${CHARTS_IMPORT}export const C = () => <svg><AxisLeftNumeric scale={s} /></svg>\n`,
710
- )
711
- expect(code).toBe(1)
712
- expect(rules).toContain('hand-rolled-plot')
713
- })
714
-
715
- it('does NOT flag the same primitive when CartesianChart is composed', () => {
716
- const { code, rules } = run(
717
- `${CHARTS_IMPORT}export const C = () => <CartesianChart>{() => <AxisLeftNumeric scale={s} />}</CartesianChart>\n`,
718
- )
719
- expect(code).toBe(0)
720
- expect(rules).not.toContain('hand-rolled-plot')
721
- })
722
-
723
- // `theme-allow-file` is the written declaration about the file, and it covers every assembly node
724
- // in it — the DualPanel shape.
725
- it('a theme-allow-file declaration covers the whole file', () => {
726
- const { code, rules } = run(
727
- `${CHARTS_IMPORT}export const C = () => (
728
- <svg>
729
- {/* theme-allow-file basalt/hand-rolled-plot: multi-pane shape, not a single cartesian plot */}
730
- <AxisLeftNumeric scale={a} />
731
- <HoverOverlay width={1} height={1} />
732
- <Crosshair x={0} top={0} bottom={1} />
733
- </svg>
734
- )\n`,
735
- )
736
- expect(code).toBe(0)
737
- expect(rules).not.toContain('hand-rolled-plot')
738
- })
739
-
740
- // The other half of the 1.20.0 fix. Naming the rule AND giving a reason used to promote the
741
- // waiver to the whole file, which made whole-file the ONLY expressible scope: the shape the rule's
742
- // own message asks for was the shape that granted immunity. It is now what it reads as — one node.
743
- it('a named-with-a-reason theme-allow waives its own node ONLY, not the file', () => {
744
- const { code, rules } = run(
745
- `${CHARTS_IMPORT}export const C = () => (
746
- <svg>
747
- {/* theme-allow basalt/hand-rolled-plot — this one axis is drawn against a second scale */}
748
- <AxisLeftNumeric scale={a} />
749
- <HoverOverlay width={1} height={1} />
750
- </svg>
751
- )\n`,
752
- )
753
- expect(code).toBe(1)
754
- expect(rules).toContain('hand-rolled-plot')
755
- })
756
-
757
- // The linewatch defect: a BARE comment used to buy the whole file permanent immunity, because
758
- // only the first node was reported and the waiver was tested there. It now waives its own node
759
- // and nothing else, so everything added to the file afterwards is still reported.
760
- it('a bare theme-allow waives only the node it sits above, not the file', () => {
761
- const { code, rules } = run(
762
- `${CHARTS_IMPORT}export const C = () => (
763
- <svg>
764
- {/* theme-allow */}
765
- <AxisLeftNumeric scale={a} />
766
- <HoverOverlay width={1} height={1} />
767
- </svg>
768
- )\n`,
769
- )
770
- expect(code).toBe(1)
771
- expect(rules).toContain('hand-rolled-plot')
772
- })
773
-
774
- // A comment written about something else must not double as a licence to hand-assemble a plot.
775
- it('a theme-allow scoped to a DIFFERENT rule does not waive this one', () => {
776
- const { code, rules } = run(
777
- `${CHARTS_IMPORT}export const C = () => (
778
- <svg>
779
- {/* theme-allow raw-hex — the swatch below is a measured pixel */}
780
- <AxisLeftNumeric scale={a} />
781
- </svg>
782
- )\n`,
783
- )
784
- expect(code).toBe(1)
785
- expect(rules).toContain('hand-rolled-plot')
786
- })
787
-
788
- it('does NOT flag a same-named component that is NOT a basalt chart primitive', () => {
789
- const { code, rules } = run(
790
- `import { Crosshair } from './my-own-widgets'
791
- export const C = () => <svg><Crosshair x={0} /></svg>\n`,
792
- )
793
- expect(code).toBe(0)
794
- expect(rules).not.toContain('hand-rolled-plot')
795
- })
796
-
797
- // `code` is 1 in both of these and that is correct: a CONSUMER file declaring `CartesianChart`
798
- // shadows a basalt export, which `shadow-basalt-export` now sees since it reads the charts barrel
799
- // too. The assertion under test is that `hand-rolled-plot` stays silent — a rule saying "compose
800
- // X" cannot fire inside X.
801
- it('does NOT flag the module that DEFINES CartesianChart (function form)', () => {
802
- const { rules } = run(
803
- `import { AxisBottomDate } from '../primitives/Axes'
804
- export function CartesianChart() {
805
- return <svg><AxisBottomDate scale={s} top={0} tickValues={[]} /></svg>
806
- }\n`,
807
- )
808
- expect(rules).not.toContain('hand-rolled-plot')
809
- })
810
-
811
- it('does NOT flag the module that DEFINES CartesianChart (const form)', () => {
812
- const { rules } = run(
813
- `import { AxisBottomDate } from '../primitives/Axes'
814
- export const CartesianChart = () => <svg><AxisBottomDate scale={s} top={0} tickValues={[]} /></svg>\n`,
815
- )
816
- expect(rules).not.toContain('hand-rolled-plot')
817
- })
818
- })
819
-
820
917
  // ── theme-allow grammar — the accepted and rejected shapes, exhaustively ─────
821
918
  //
822
919
  // Two rounds produced three holes in this one contract, so it gets a matrix rather than a case.
823
- // `hand-rolled-plot` is the vehicle: it is the only rule with BOTH scopes, so one fixture shape
920
+ // `card-inset` is the vehicle: a node-level rule honouring both scopes, so one fixture shape
824
921
  // exercises the node waiver, the file declaration and the prefix rule at once.
825
922
 
826
923
  describe('theme-allow grammar', () => {
827
- /** Two assembly nodes, with `annotation` placed as a comment-only line above the first. */
924
+ /** Two flagged nodes, with `annotation` placed as a comment-only line above the first. */
828
925
  function twoNodes(annotation: string): { code: number; rules: Set<string> } {
829
926
  return run(
830
- `${CHARTS_IMPORT}export const C = () => (
831
- <svg>
927
+ `export const C = () => (
928
+ <div>
832
929
  ${annotation}
833
- <AxisLeftNumeric scale={a} />
834
- <HoverOverlay width={1} height={1} />
835
- </svg>
930
+ <Card p="lg" />
931
+ <Card p="lg" />
932
+ </div>
836
933
  )\n`,
837
934
  )
838
935
  }
839
936
 
840
937
  const waivesFirstNodeOnly = (annotation: string) => {
841
938
  const { rules } = twoNodes(annotation)
842
- expect([annotation, rules.has('hand-rolled-plot')]).toEqual([annotation, true])
939
+ expect([annotation, rules.has('card-inset')]).toEqual([annotation, true])
843
940
  }
844
941
  const waivesWholeFile = (annotation: string) => {
845
942
  const { rules } = twoNodes(annotation)
846
- expect([annotation, rules.has('hand-rolled-plot')]).toEqual([annotation, false])
943
+ expect([annotation, rules.has('card-inset')]).toEqual([annotation, false])
847
944
  }
848
945
 
849
946
  // ── ACCEPTED as a NODE waiver (the second node still reports) ──────────────
850
947
 
851
948
  it.each([
852
949
  '{/* theme-allow */}',
853
- '{/* theme-allow hand-rolled-plot */}',
854
- '{/* theme-allow hand-rolled-plot — a reason */}',
855
- '{/* theme-allow basalt/hand-rolled-plot: a reason */}',
856
- '{/* theme-allow hand-rolled-plot, raw-hex — two ids */}',
857
- '{/*theme-allow hand-rolled-plot — no space after the opener*/}',
950
+ '{/* theme-allow card-inset */}',
951
+ '{/* theme-allow card-inset — a reason */}',
952
+ '{/* theme-allow basalt/card-inset: a reason */}',
953
+ '{/* theme-allow card-inset, raw-hex — two ids */}',
954
+ '{/*theme-allow card-inset — no space after the opener*/}',
858
955
  ])('%s waives its own node and leaves the rest policed', waivesFirstNodeOnly)
859
956
 
860
957
  // ── ACCEPTED as a FILE declaration (nothing in the file reports) ───────────
861
958
 
862
959
  it.each([
863
- '{/* theme-allow-file hand-rolled-plot — a reason */}',
864
- '{/* theme-allow-file basalt/hand-rolled-plot: a reason */}',
865
- '{/* theme-allow-file hand-rolled-plot */}',
866
- '{/* theme-allow-file hand-rolled-plot, chart-legend-literal — two ids */}',
960
+ '{/* theme-allow-file card-inset — a reason */}',
961
+ '{/* theme-allow-file basalt/card-inset: a reason */}',
962
+ '{/* theme-allow-file card-inset */}',
963
+ '{/* theme-allow-file card-inset, raw-hex — two ids */}',
867
964
  ])('%s declares the whole file', waivesWholeFile)
868
965
 
869
966
  it('a theme-allow-file inside a docblock, detached from any node, declares the file', () => {
870
967
  const { code, rules } = run(
871
- `${CHARTS_IMPORT}/**
872
- * A two-pane shape.
968
+ `/**
969
+ * A bespoke shape.
873
970
  *
874
- * theme-allow-file hand-rolled-plot — two panes over one x scale is not a single plot
971
+ * theme-allow-file card-inset — the inset is dictated by a third-party embed
875
972
  */
876
973
  export const C = () => (
877
- <svg>
878
- <AxisLeftNumeric scale={a} />
879
- <HoverOverlay width={1} height={1} />
880
- </svg>
974
+ <div>
975
+ <Card p="lg" />
976
+ <Card p="lg" />
977
+ </div>
881
978
  )\n`,
882
979
  )
883
980
  expect(code).toBe(0)
884
- expect(rules).not.toContain('hand-rolled-plot')
981
+ expect(rules).not.toContain('card-inset')
885
982
  })
886
983
 
887
984
  // ── REJECTED — prose that merely MENTIONS the token (the linewatch defect) ─
@@ -897,14 +994,14 @@ export const C = () => (
897
994
  ])('%s is prose, not an annotation — it waives nothing', (annotation) => {
898
995
  const { code, rules } = twoNodes(annotation)
899
996
  expect([annotation, code]).toEqual([annotation, 1])
900
- expect([annotation, rules.has('hand-rolled-plot')]).toEqual([annotation, true])
997
+ expect([annotation, rules.has('card-inset')]).toEqual([annotation, true])
901
998
  })
902
999
 
903
1000
  it('a prose docblock line mentioning theme-allow-file does not declare the file', () => {
904
1001
  const { rules } = twoNodes(
905
- '{/* the shape to write here is a theme-allow-file hand-rolled-plot — see the docs */}',
1002
+ '{/* the shape to write here is a theme-allow-file card-inset — see the docs */}',
906
1003
  )
907
- expect(rules).toContain('hand-rolled-plot')
1004
+ expect(rules).toContain('card-inset')
908
1005
  })
909
1006
 
910
1007
  // ── REJECTED — a longer word that merely STARTS with the token ─────────────
@@ -913,7 +1010,7 @@ export const C = () => (
913
1010
  // fall through to the empty-`rules` branch, and read as a blanket waiver.
914
1011
  it('a comment starting with the kind name `theme-allow-unscoped` is not an annotation', () => {
915
1012
  const { rules } = twoNodes('{/* theme-allow-unscoped is what reports a bare waiver */}')
916
- expect(rules).toContain('hand-rolled-plot')
1013
+ expect(rules).toContain('card-inset')
917
1014
  })
918
1015
 
919
1016
  // ── REJECTED — a bare file declaration, and a typo'd id ────────────────────
@@ -921,63 +1018,24 @@ export const C = () => (
921
1018
  it('a BARE theme-allow-file waives nothing — file scope must name its ids', () => {
922
1019
  const { code, rules } = twoNodes('{/* theme-allow-file — everything in here is bespoke */}')
923
1020
  expect(code).toBe(1)
924
- expect(rules).toContain('hand-rolled-plot')
1021
+ expect(rules).toContain('card-inset')
925
1022
  })
926
1023
 
927
1024
  it("a theme-allow-file naming a typo'd id waives nothing", () => {
928
- const { rules } = twoNodes('{/* theme-allow-file hand-rolled-plott — a reason */}')
929
- expect(rules).toContain('hand-rolled-plot')
1025
+ const { rules } = twoNodes('{/* theme-allow-file card-insett — a reason */}')
1026
+ expect(rules).toContain('card-inset')
930
1027
  })
931
1028
 
932
1029
  it('a theme-allow-file scoped to a DIFFERENT rule does not waive this one', () => {
933
1030
  const { rules } = twoNodes('{/* theme-allow-file raw-hex — the swatch is a measured pixel */}')
934
- expect(rules).toContain('hand-rolled-plot')
1031
+ expect(rules).toContain('card-inset')
935
1032
  })
936
1033
 
937
1034
  // ── REJECTED — the token inside a string literal, not a comment ────────────
938
1035
 
939
1036
  it('the token inside a string literal is code, not an annotation', () => {
940
- const { rules } = twoNodes("{'theme-allow hand-rolled-plot — a reason'}")
941
- expect(rules).toContain('hand-rolled-plot')
942
- })
943
- })
944
-
945
- // ── chart-legend-literal ─────────────────────────────────────────────────────
946
-
947
- describe('basalt/chart-legend-literal', () => {
948
- it('flags a hand-authored items array', () => {
949
- const { code, rules } = run(
950
- `${CHARTS_IMPORT}export const C = () => <ChartLegend items={[{ key: 'a', label: 'A', color: 'red' }]} />\n`,
951
- )
952
- expect(code).toBe(1)
953
- expect(rules).toContain('chart-legend-literal')
954
- })
955
-
956
- it('does NOT flag items derived from the series descriptor', () => {
957
- const { code, rules } = run(
958
- `${CHARTS_IMPORT}export const C = () => <ChartLegend items={deriveLegend(series)} />\n`,
959
- )
960
- expect(code).toBe(0)
961
- expect(rules).not.toContain('chart-legend-literal')
962
- })
963
-
964
- it('does NOT flag a derived-and-extended list — that is legitimate composition', () => {
965
- const { code, rules } = run(
966
- `${CHARTS_IMPORT}export const C = () => <ChartLegend items={[...deriveLegend(series), note]} />\n`,
967
- )
968
- expect(code).toBe(0)
969
- expect(rules).not.toContain('chart-legend-literal')
970
- })
971
-
972
- it('honors a theme-allow escape', () => {
973
- const { code, rules } = run(
974
- `${CHARTS_IMPORT}export const C = () => (
975
- // theme-allow: static reference legend, no series behind it
976
- <ChartLegend items={[{ key: 'a', label: 'A', color: 'red' }]} />
977
- )\n`,
978
- )
979
- expect(code).toBe(0)
980
- expect(rules).not.toContain('chart-legend-literal')
1037
+ const { rules } = twoNodes("{'theme-allow card-inset — a reason'}")
1038
+ expect(rules).toContain('card-inset')
981
1039
  })
982
1040
  })
983
1041
 
@@ -1012,7 +1070,7 @@ describe('basalt/shadow-basalt-export', () => {
1012
1070
  })
1013
1071
 
1014
1072
  it('sees the content layer too', () => {
1015
- const { rules } = run(`export function ReadingProgress() { return null }\n`)
1073
+ const { rules } = run(`export function Callout() { return null }\n`)
1016
1074
  expect(rules).toContain('shadow-basalt-export')
1017
1075
  })
1018
1076
 
@@ -1413,32 +1471,26 @@ describe('PLUGIN_RULE_GRACE', () => {
1413
1471
  }
1414
1472
  ).version
1415
1473
  const graceEntries = Object.entries(PLUGIN_RULE_GRACE) as [string, GraceEntry][]
1416
- const advisoryEntries = Object.entries(PLUGIN_RULE_ADVISORY) as [
1417
- string,
1418
- { since: string; why: string },
1419
- ][]
1420
1474
  const graceIds = graceEntries.map(([id]) => id)
1421
- const advisoryIds = advisoryEntries.map(([id]) => id)
1422
1475
  const shippedBasaltIds = Object.keys(shipped.rules).filter((id) => id.startsWith('basalt/'))
1423
1476
 
1424
1477
  // The mechanism whose ABSENCE let three rules sit at `warn` for up to twelve minors with nothing
1425
1478
  // tracking them. Deleting a ledger entry IS the promotion, and this test makes flipping the
1426
1479
  // shipped level part of the same commit.
1427
- it('every grace and advisory entry is warn in the shipped preset', () => {
1428
- for (const id of [...graceIds, ...advisoryIds])
1429
- expect(shipped.rules[`basalt/${id}`]).toBe('warn')
1480
+ it('every grace entry is warn in the shipped preset', () => {
1481
+ for (const id of graceIds) expect(shipped.rules[`basalt/${id}`]).toBe('warn')
1430
1482
  })
1431
1483
 
1432
- it('every shipped rule NOT in either ledger is error', () => {
1484
+ it('every shipped rule NOT in the ledger is error', () => {
1433
1485
  for (const id of shippedBasaltIds) {
1434
1486
  const bareId = id.slice('basalt/'.length)
1435
- if (graceIds.includes(bareId) || advisoryIds.includes(bareId)) continue
1487
+ if (graceIds.includes(bareId)) continue
1436
1488
  expect([id, shipped.rules[id]]).toEqual([id, 'error'])
1437
1489
  }
1438
1490
  })
1439
1491
 
1440
1492
  it('every entry carries a written why', () => {
1441
- for (const [id, entry] of [...graceEntries, ...advisoryEntries]) {
1493
+ for (const [id, entry] of graceEntries) {
1442
1494
  if (entry.why.length <= 40)
1443
1495
  throw new Error(`${id}: \`why\` must be longer than 40 characters.`)
1444
1496
  expect(entry.why.length).toBeGreaterThan(40)
@@ -1446,8 +1498,8 @@ describe('PLUGIN_RULE_GRACE', () => {
1446
1498
  })
1447
1499
 
1448
1500
  // The C16 version gate, run against the REAL ledger and REAL package version — covers `since` <
1449
- // `promote` and "no entry has reached its promote version" in one call. ADVISORY entries are
1450
- // exempt by design (see `assertGraceLedger`'s synthetic coverage above for the gate itself).
1501
+ // `promote` and "no entry has reached its promote version" in one call.
1502
+ // (see `assertGraceLedger`'s synthetic coverage above for the gate itself).
1451
1503
  it('passes the C16 gate against the real ledger and package version', () => {
1452
1504
  expect(() => assertGraceLedger(Object.fromEntries(graceEntries), pkgVersion)).not.toThrow()
1453
1505
  })
@@ -1477,47 +1529,6 @@ describe('PLUGIN_RULE_GRACE', () => {
1477
1529
  })
1478
1530
  })
1479
1531
 
1480
- // ── chart-legend-literal: the .map() half ────────────────────────────────────
1481
-
1482
- describe('basalt/chart-legend-literal — derived-from-series', () => {
1483
- const IMPORT = `import { ChartLegend } from 'basalt-ui/charts'\n`
1484
-
1485
- // Deriving from *an* array is not deriving from *the* series: every field of a
1486
- // `refLines.map(...)` / `PACE_ZONES.map(...)` legend is authored at the call site, and the list
1487
- // can keep naming a band the plot no longer draws exactly like a `[...]` literal can.
1488
- it('flags a .map() over a non-series array', () => {
1489
- const { code, rules } = run(
1490
- `${IMPORT}export const C = () => <ChartLegend items={refLines.map((r) => ({ key: r.k, label: r.l }))} />\n`,
1491
- )
1492
- expect(code).toBe(1)
1493
- expect(rules).toContain('chart-legend-literal')
1494
- })
1495
-
1496
- it('does NOT flag a .map() over the series', () => {
1497
- const { code, rules } = run(
1498
- `${IMPORT}export const C = () => <ChartLegend items={series.map((s) => ({ key: s.key }))} />\n`,
1499
- )
1500
- expect(code).toBe(0)
1501
- expect(rules).not.toContain('chart-legend-literal')
1502
- })
1503
-
1504
- it('does NOT flag a .map() over a derived series binding', () => {
1505
- const { code, rules } = run(
1506
- `${IMPORT}export const C = () => <ChartLegend items={visibleSeries.map((s) => ({ key: s.key }))} />\n`,
1507
- )
1508
- expect(code).toBe(0)
1509
- expect(rules).not.toContain('chart-legend-literal')
1510
- })
1511
-
1512
- it('does NOT flag deriveLegend(series)', () => {
1513
- const { code, rules } = run(
1514
- `${IMPORT}export const C = () => <ChartLegend items={deriveLegend(series)} />\n`,
1515
- )
1516
- expect(code).toBe(0)
1517
- expect(rules).not.toContain('chart-legend-literal')
1518
- })
1519
- })
1520
-
1521
1532
  // ── no-raw-font-size: style-context scoping ──────────────────────────────────
1522
1533
 
1523
1534
  describe('basalt/no-raw-font-size — style context', () => {
@@ -2089,7 +2100,7 @@ describe('basalt/control-outside-home', () => {
2089
2100
  })
2090
2101
 
2091
2102
  // The SECOND dialect: a repo mandating `PascalCase.tsx` for component files (basalt's own root
2092
- // CLAUDE.md included) can never write `foo-panel.tsx`, so the kebab form alone exempted nothing
2103
+ // AGENTS.md included) can never write `foo-panel.tsx`, so the kebab form alone exempted nothing
2093
2104
  // there — which is how the CBBI panel collected four warns for a `<PageAside>` in its parent.
2094
2105
  it.each(['src/EditSessionModal.tsx', 'src/FiltersDrawer.tsx', 'src/CbbiPanel.tsx'])(
2095
2106
  'does NOT flag %s — the PascalCase dialect of the same convention',
@@ -2602,118 +2613,6 @@ describe('CTL_THEME_TAGS ↔ CTL_THEME', () => {
2602
2613
  })
2603
2614
  })
2604
2615
 
2605
- describe('basalt/page-bar-budget', () => {
2606
- it('flags two PageBars in the SAME returned tree — the pair that mounts together', () => {
2607
- const { code, rules } = run(
2608
- `${BASALT_IMPORT}export const C = () => (\n <>\n <PageBar title="A" />\n <PageBar title="B" />\n </>\n)\n`,
2609
- )
2610
- expect(code).toBe(1)
2611
- expect(rules).toContain('page-bar-budget')
2612
- })
2613
-
2614
- // The two false positives the per-FILE count produced. Neither renders two bars at once, so
2615
- // nothing races for the portal node — and the only escape was a theme-allow on correct code.
2616
- it('does NOT flag an early-return loading state and the loaded bar', () => {
2617
- const { code, rules } = run(
2618
- `${BASALT_IMPORT}export const C = ({ data }) => {\n if (!data) return <PageBar title="Jobs" />\n return <PageBar title="Jobs" actions={{ primary: { key: 'r', label: 'Run' } }} />\n}\n`,
2619
- )
2620
- expect(code).toBe(0)
2621
- expect(rules).not.toContain('page-bar-budget')
2622
- })
2623
-
2624
- it('does NOT flag two page components exported from one file', () => {
2625
- const { code, rules } = run(
2626
- `${BASALT_IMPORT}export const ListPage = () => <PageBar title="Jobs" />\nexport const DetailPage = () => <PageBar title="Job" />\n`,
2627
- )
2628
- expect(code).toBe(0)
2629
- expect(rules).not.toContain('page-bar-budget')
2630
- })
2631
-
2632
- it("does NOT flag a consumer's own PageBar-named component", () => {
2633
- const { code, rules } = run(
2634
- `import { PageBar } from './chrome/page-bar'\nexport const C = () => (\n <>\n <PageBar title="A" />\n <PageBar title="B" />\n </>\n)\n`,
2635
- )
2636
- expect(code).toBe(0)
2637
- expect(rules).not.toContain('page-bar-budget')
2638
- })
2639
-
2640
- it('flags a 5-entry actions.secondary array', () => {
2641
- const five = [1, 2, 3, 4, 5].map((n) => `{ key: '${n}', label: '${n}' }`).join(', ')
2642
- const { code, rules } = run(
2643
- `${BASALT_IMPORT}export const C = () => <PageBar actions={{ secondary: [${five}] }} />\n`,
2644
- )
2645
- expect(code).toBe(1)
2646
- expect(rules).toContain('page-bar-budget')
2647
- })
2648
-
2649
- it('does NOT flag a 4-entry actions.secondary array', () => {
2650
- const four = [1, 2, 3, 4].map((n) => `{ key: '${n}', label: '${n}' }`).join(', ')
2651
- const { code, rules } = run(
2652
- `${BASALT_IMPORT}export const C = () => <PageBar actions={{ secondary: [${four}] }} />\n`,
2653
- )
2654
- expect(code).toBe(0)
2655
- expect(rules).not.toContain('page-bar-budget')
2656
- })
2657
-
2658
- it('flags a 4-entry Section actions array (budget 3)', () => {
2659
- const four = [1, 2, 3, 4].map((n) => `{ key: '${n}', label: '${n}' }`).join(', ')
2660
- const { rules } = run(
2661
- `${BASALT_IMPORT}export const C = () => <Section actions={[${four}]} />\n`,
2662
- )
2663
- expect(rules).toContain('page-bar-budget')
2664
- })
2665
-
2666
- it('flags a second variant="filled" inside one slot', () => {
2667
- const { rules } = run(
2668
- `${BASALT_IMPORT}${MANTINE_IMPORT}export const C = () => (\n <PageBar actions={<><Button variant="filled">A</Button><Button variant="filled">B</Button></>} />\n)\n`,
2669
- )
2670
- expect(rules).toContain('page-bar-budget')
2671
- })
2672
-
2673
- it('does NOT flag one filled action beside a default one', () => {
2674
- const { code, rules } = run(
2675
- `${BASALT_IMPORT}${MANTINE_IMPORT}export const C = () => (\n <PageBar actions={<><Button variant="filled">A</Button><Button variant="default">B</Button></>} />\n)\n`,
2676
- )
2677
- expect(code).toBe(0)
2678
- expect(rules).not.toContain('page-bar-budget')
2679
- })
2680
-
2681
- // The `filled` count is a PRIMARY-ACTION count: a filled Badge is Mantine's default Badge
2682
- // variant and the ordinary way a header states a count (law C11 asks for one), and an overlay's
2683
- // submit button is not in the bar at all.
2684
- it('does NOT flag a filled Badge beside the one filled Button', () => {
2685
- const { code, rules } = run(
2686
- `${BASALT_IMPORT}import { Badge, Button } from '@mantine/core'\n` +
2687
- `export const C = () => (\n <Section title="T" actions={<><Button variant="filled">Run</Button><Badge variant="filled">3</Badge></>} />\n)\n`,
2688
- )
2689
- expect(code).toBe(0)
2690
- expect(rules).not.toContain('page-bar-budget')
2691
- })
2692
-
2693
- it('does NOT flag a filled ThemeIcon beside the one filled Button', () => {
2694
- const { rules } = run(
2695
- `${BASALT_IMPORT}import { Button, ThemeIcon } from '@mantine/core'\n` +
2696
- `export const C = () => (\n <Section title="T" actions={<><Button variant="filled">Run</Button><ThemeIcon variant="filled" /></>} />\n)\n`,
2697
- )
2698
- expect(rules).not.toContain('page-bar-budget')
2699
- })
2700
-
2701
- it("does NOT flag a Modal's own filled submit button colocated in the slot", () => {
2702
- const { rules } = run(
2703
- `${BASALT_IMPORT}import { Button, Modal } from '@mantine/core'\n` +
2704
- `export const C = () => (\n <PageBar actions={<><Button variant="filled">New</Button><Modal opened><Button variant="filled">Save</Button></Modal></>} />\n)\n`,
2705
- )
2706
- expect(rules).not.toContain('page-bar-budget')
2707
- })
2708
-
2709
- it('honors a scoped theme-allow', () => {
2710
- const { rules } = run(
2711
- `${BASALT_IMPORT}export const C = () => (\n <>\n <PageBar title="A" />\n {/* theme-allow page-bar-budget — the print-only variant */}\n <PageBar title="B" />\n </>\n)\n`,
2712
- )
2713
- expect(rules).not.toContain('page-bar-budget')
2714
- })
2715
- })
2716
-
2717
2616
  describe('basalt/in-body-page-title', () => {
2718
2617
  it.each([1, 2])('flags <Title order={%i}> in the body', (order) => {
2719
2618
  const { code, rules } = run(`export const C = () => <Title order={${order}}>Page</Title>\n`)
@@ -3200,95 +3099,6 @@ describe('PLUGIN_RULE_ID_LIST ↔ the plugin', () => {
3200
3099
  })
3201
3100
  })
3202
3101
 
3203
- // ── provider-above-router (F5) ───────────────────────────────────────────────
3204
-
3205
- describe('basalt/provider-above-router', () => {
3206
- const IMPORTS =
3207
- `import { BasaltProvider } from 'basalt-ui'\n` +
3208
- `import { RouterProvider } from '@tanstack/react-router'\n`
3209
-
3210
- it('flags a BasaltProvider rendered inside a RouterProvider', () => {
3211
- const { code, rules } = run(
3212
- `${IMPORTS}export const App = () => (\n` +
3213
- ` <RouterProvider router={r}>\n <BasaltProvider>{null}</BasaltProvider>\n` +
3214
- ` </RouterProvider>\n)\n`,
3215
- )
3216
- expect(code).toBe(1)
3217
- expect(rules).toContain('provider-above-router')
3218
- })
3219
-
3220
- it('does NOT flag the correct order — provider above router', () => {
3221
- const { code, rules } = run(
3222
- `${IMPORTS}export const App = () => (\n` +
3223
- ` <BasaltProvider>\n <RouterProvider router={r} />\n </BasaltProvider>\n)\n`,
3224
- )
3225
- expect(code).toBe(0)
3226
- expect(rules).not.toContain('provider-above-router')
3227
- })
3228
-
3229
- // Provenance both ways: the tag names alone are not the finding.
3230
- it("does NOT flag a consumer's own BasaltProvider (no basalt import)", () => {
3231
- const { code, rules } = run(
3232
- `import { RouterProvider } from '@tanstack/react-router'\n` +
3233
- `import { BasaltProvider } from './my-provider'\n` +
3234
- `export const App = () => (\n <RouterProvider router={r}>\n` +
3235
- ` <BasaltProvider>{null}</BasaltProvider>\n </RouterProvider>\n)\n`,
3236
- )
3237
- expect(code).toBe(0)
3238
- expect(rules).not.toContain('provider-above-router')
3239
- })
3240
-
3241
- it('does NOT flag a RouterProvider that is not TanStack s', () => {
3242
- const { code, rules } = run(
3243
- `import { BasaltProvider } from 'basalt-ui'\n` +
3244
- `import { RouterProvider } from 'other-router'\n` +
3245
- `export const App = () => (\n <RouterProvider router={r}>\n` +
3246
- ` <BasaltProvider>{null}</BasaltProvider>\n </RouterProvider>\n)\n`,
3247
- )
3248
- expect(code).toBe(0)
3249
- expect(rules).not.toContain('provider-above-router')
3250
- })
3251
-
3252
- // The alias is why the ancestry is read by BINDING and not by written tag name.
3253
- it('flags through an aliased RouterProvider import', () => {
3254
- const { code, rules } = run(
3255
- `import { BasaltProvider } from 'basalt-ui'\n` +
3256
- `import { RouterProvider as Router } from '@tanstack/react-router'\n` +
3257
- `export const App = () => (\n <Router router={r}>\n` +
3258
- ` <BasaltProvider>{null}</BasaltProvider>\n </Router>\n)\n`,
3259
- )
3260
- expect(code).toBe(1)
3261
- expect(rules).toContain('provider-above-router')
3262
- })
3263
-
3264
- it('honours a theme-allow naming the rule', () => {
3265
- const { code, rules } = run(
3266
- `${IMPORTS}export const App = () => (\n <RouterProvider router={r}>\n` +
3267
- ` {/* theme-allow provider-above-router — legacy bootstrap, ported next minor */}\n` +
3268
- ` <BasaltProvider>{null}</BasaltProvider>\n </RouterProvider>\n)\n`,
3269
- )
3270
- expect(code).toBe(0)
3271
- expect(rules).not.toContain('provider-above-router')
3272
- })
3273
-
3274
- // The documented limit of the heuristic — stated in the rule's own JSDoc, pinned here so a
3275
- // future widening has to move this test rather than discover the gap.
3276
- it('does NOT see a cross-file composition (static JSX ancestry only)', () => {
3277
- const { code, rules } = run(
3278
- `import { BasaltProvider } from 'basalt-ui'\n` +
3279
- `import { Routes } from './routes'\n` +
3280
- `export const App = () => (\n <Routes>\n <BasaltProvider>{null}</BasaltProvider>\n` +
3281
- ` </Routes>\n)\n`,
3282
- )
3283
- expect(code).toBe(0)
3284
- expect(rules).not.toContain('provider-above-router')
3285
- })
3286
- })
3287
-
3288
- // `basalt/query-dual-import` is RETIRED (`RETIRED_RULE_IDS`) — its own describe block was removed
3289
- // along with the rule; see `KNOWN_RULE_IDS` below for the coverage that remains: it is still a
3290
- // valid (dead) `theme-allow` id, never a typo.
3291
-
3292
3102
  // ── query-fn-unwrap (F5) ─────────────────────────────────────────────────────
3293
3103
 
3294
3104
  describe('basalt/query-fn-unwrap', () => {
@@ -3358,71 +3168,6 @@ describe('basalt/query-fn-unwrap', () => {
3358
3168
  })
3359
3169
  })
3360
3170
 
3361
- // ── deprecated-export (B4) ───────────────────────────────────────────────────
3362
-
3363
- describe('basalt/deprecated-export', () => {
3364
- it('reports a removed row as gone, never as "still resolves"', () => {
3365
- const { code, rules, output } = run(
3366
- `import { useBreakpoint } from 'basalt-ui'\nexport const x = useBreakpoint\n`,
3367
- )
3368
- expect(code).toBe(1)
3369
- expect(rules).toContain('deprecated-export')
3370
- expect(output).toContain('was removed in 1.34.0')
3371
- expect(output).not.toContain('still resolves')
3372
- })
3373
-
3374
- // The row no longer says `fix: false`; `removed` alone must suppress the rewrite, because a
3375
- // removed row's replacement is prose (`useSizeClass() for shell chrome; …`), not a name.
3376
- it('never autofixes a removed row', () => {
3377
- const source = `import { useBreakpoint } from 'basalt-ui'\nexport const x = useBreakpoint\n`
3378
- writeFileSync(resolve(dir, 'removed-fix.tsx'), source)
3379
- Bun.spawnSync([OXLINT_BIN, '-c', '.oxlintrc.json', '--fix', 'removed-fix.tsx'], { cwd: dir })
3380
- expect(readFileSync(resolve(dir, 'removed-fix.tsx'), 'utf8')).toBe(source)
3381
- })
3382
- })
3383
-
3384
- // 1.36.0: the form layout moved `basalt-ui/forms` → `basalt-ui/controls`. A move BETWEEN subpaths is
3385
- // not a drop-in rename — the autofix only rewrites the specifier inside its own import statement —
3386
- // so these rows carry `fix: false` and the message names the new home.
3387
- describe('basalt/deprecated-export — the form layout move', () => {
3388
- it.each(['FormSection', 'FormRow', 'FormGroup', 'FormActions'])(
3389
- 'reports %s from basalt-ui/forms and names basalt-ui/controls',
3390
- (name) => {
3391
- const { rules, output } = run(
3392
- `import { ${name} } from 'basalt-ui/forms'\nexport const x = ${name}\n`,
3393
- )
3394
- expect(rules).toContain('deprecated-export')
3395
- expect(output).toContain(`'basalt-ui/controls'`)
3396
- expect(output).toContain('was removed in 1.39.0')
3397
- },
3398
- )
3399
-
3400
- it.each(['useForm', 'schemaResolver'])('reports %s from basalt-ui/forms', (name) => {
3401
- const { rules, output } = run(
3402
- `import { ${name} } from 'basalt-ui/forms'\nexport const x = ${name}\n`,
3403
- )
3404
- expect(rules).toContain('deprecated-export')
3405
- expect(output).toContain(`'@mantine/form'`)
3406
- expect(output).toContain('was removed in 1.39.0')
3407
- })
3408
-
3409
- it('does NOT report FormRow imported from basalt-ui/controls', () => {
3410
- const { rules } = run(
3411
- `import { FormRow } from 'basalt-ui/controls'\nexport const x = FormRow\n`,
3412
- )
3413
- expect(rules).not.toContain('deprecated-export')
3414
- })
3415
-
3416
- it('never autofixes a cross-subpath row', () => {
3417
- const source = `import { FormRow } from 'basalt-ui/forms'\nexport const x = FormRow\n`
3418
- writeFileSync(resolve(dir, 'form-layout-fix.tsx'), source)
3419
- Bun.spawnSync([OXLINT_BIN, '-c', '.oxlintrc.json', '--fix', 'form-layout-fix.tsx'], {
3420
- cwd: dir,
3421
- })
3422
- expect(readFileSync(resolve(dir, 'form-layout-fix.tsx'), 'utf8')).toBe(source)
3423
- })
3424
- })
3425
-
3426
3171
  // ── forms-field-key ──────────────────────────────────────────────────────────
3427
3172
 
3428
3173
  describe('basalt/forms-field-key', () => {
@@ -4069,90 +3814,302 @@ describe('control homes — lane parity', () => {
4069
3814
  })
4070
3815
  })
4071
3816
 
4072
- // ── the deprecation ledger (B4) ──────────────────────────────────────────────
3817
+ // ── agent-no-raw-usechat — the `Chat` class ──────────────────────────────────
4073
3818
 
4074
- describe('DEPRECATED_EXPORTS', () => {
4075
- const pkgVersion = (
4076
- JSON.parse(readFileSync(resolve(import.meta.dirname, '..', 'package.json'), 'utf8')) as {
4077
- version: string
4078
- }
4079
- ).version
3819
+ describe('basalt/agent-no-raw-usechat — Chat', () => {
3820
+ it.each(['@ai-sdk/react', 'ai/react'])('flags `Chat` imported from %s', (source) => {
3821
+ const { code, rules } = run(`import { Chat } from '${source}'\n`, 'lib.ts')
3822
+ expect(code).toBe(1)
3823
+ expect(rules).toContain('agent-no-raw-usechat')
3824
+ })
4080
3825
 
4081
- const [major, minor] = pkgVersion.split('.').map(Number)
4082
- const floor = `${major}.${(minor as number) + 1}.0`
4083
- const surface = JSON.parse(
4084
- readFileSync(resolve(import.meta.dirname, '..', 'scripts', 'export-surface.json'), 'utf8'),
4085
- ) as Record<string, string[]>
4086
- const shipped = (row: (typeof DEPRECATED_EXPORTS)[number]) =>
4087
- surface[row.subpath === 'basalt-ui' ? '.' : row.subpath.replace('basalt-ui', '.')]?.includes(
4088
- row.name,
4089
- ) === true
4090
-
4091
- it('carries a replacement and a well-formed removeIn on every row', () => {
4092
- for (const row of DEPRECATED_EXPORTS) {
4093
- expect([row.name, row.replacement.length > 0]).toEqual([row.name, true])
4094
- expect([row.name, /^\d+\.\d+\.\d+$/.test(row.removeIn)]).toEqual([row.name, true])
4095
- }
3826
+ it('does NOT flag a type-only `Chat` import', () => {
3827
+ const { code } = run(`import type { Chat } from '@ai-sdk/react'\n`, 'lib.ts')
3828
+ expect(code).toBe(0)
4096
3829
  })
4097
3830
 
4098
- // A live row still ships, so its removal is still ahead: at least the next minor.
4099
- it('dates every live row at least one minor out', () => {
4100
- for (const row of DEPRECATED_EXPORTS.filter((r) => r.removed !== true)) {
4101
- expect([row.name, compareSemver(row.removeIn, floor) >= 0]).toEqual([row.name, true])
4102
- }
3831
+ it.each([
3832
+ ['AI.useChat()', `const c = AI.useChat({ id })\n`],
3833
+ ['AI.Chat as a value', `const C = AI.Chat\n`],
3834
+ ['new AI.Chat()', `const c = new AI.Chat({ id })\n`],
3835
+ ])('flags %s through a namespace import', (_name, use) => {
3836
+ const { code, rules } = run(`import * as AI from '@ai-sdk/react'\n${use}`, 'lib.ts')
3837
+ expect(code).toBe(1)
3838
+ expect(rules).toContain('agent-no-raw-usechat')
3839
+ })
3840
+
3841
+ it('flags a namespace import of ai/react', () => {
3842
+ const { code } = run(`import * as AI from 'ai/react'\nAI.useChat()\n`, 'lib.ts')
3843
+ expect(code).toBe(1)
3844
+ })
3845
+
3846
+ it('does NOT flag a namespace member that is not a chat hook, a local namespace, or a type import', () => {
3847
+ expect(run(`import * as AI from '@ai-sdk/react'\nAI.somethingElse()\n`, 'lib.ts').code).toBe(0)
3848
+ expect(run(`import * as AI from './mine'\nAI.useChat()\n`, 'lib.ts').code).toBe(0)
3849
+ expect(run(`import type * as AI from '@ai-sdk/react'\nAI.useChat()\n`, 'lib.ts').code).toBe(0)
3850
+ })
3851
+
3852
+ it.each([
3853
+ `export { Chat } from '@ai-sdk/react'\n`,
3854
+ `export { useChat as chat } from 'ai/react'\n`,
3855
+ `export * from '@ai-sdk/react'\n`,
3856
+ ])('flags the re-export %s', (source) => {
3857
+ const { code, rules } = run(source, 'lib.ts')
3858
+ expect(code).toBe(1)
3859
+ expect(rules).toContain('agent-no-raw-usechat')
3860
+ })
3861
+
3862
+ it('does NOT flag a type re-export or an unrelated name', () => {
3863
+ expect(run(`export type { UIMessage } from '@ai-sdk/react'\n`, 'lib.ts').code).toBe(0)
3864
+ expect(run(`export { Other } from '@ai-sdk/react'\n`, 'lib.ts').code).toBe(0)
3865
+ })
3866
+ })
3867
+
3868
+ // ── no-dev-helper ────────────────────────────────────────────────────────────
3869
+
3870
+ describe('basalt/no-dev-helper', () => {
3871
+ const LAZY = `const Dock = lazy(() => import('../dev-dock'))\n`
3872
+ it.each([
3873
+ ['an unguarded devtools import()', `const m = import('@tanstack/react-router-devtools')\n`],
3874
+ ['an unguarded theme-lab import()', `export const load = () => import('./theme-lab')\n`],
3875
+ ['an isDev() gate', `if (isDev()) import('./dev-dock')\n`],
3876
+ ['an eager const import() with no gate', `const m = import('./theme-lab')\n`],
3877
+ [
3878
+ 'an eager const import() even when a later use is gated',
3879
+ `const m = import('./theme-lab')\nif (process.env.NODE_ENV !== 'production') use(m)\n`,
3880
+ ],
3881
+ [
3882
+ 'an import() inside an anonymous callback',
3883
+ `useEffect(() => { void import('./dev-dock') }, [])\n`,
3884
+ ],
3885
+ ['a lazy binding rendered without a gate', `${LAZY}export const C = () => <Dock />\n`],
3886
+ [
3887
+ 'a lazy binding with one ungated use',
3888
+ `${LAZY}export const C = () => (\n <>\n {process.env.NODE_ENV !== 'production' && <Dock />}\n <Dock />\n </>\n)\n`,
3889
+ ],
3890
+ ['a TS-wrapped ungated use', `const load = () => import('./dev-dock')\nload!()\n`],
3891
+ [
3892
+ 'a gate that only holds in production',
3893
+ `const load = () => import('./dev-dock')\nif (ok && process.env.NODE_ENV === 'production') load()\n`,
3894
+ ],
3895
+ [
3896
+ 'a component that merely contains the import (its uses are not the dev uses)',
3897
+ `const App = () => { useEffect(() => { void import('./dev-dock') }, []); return null }\nexport const C = () => <App />\n`,
3898
+ ],
3899
+ ])('flags %s', (_name, source) => {
3900
+ const { code, rules } = run(source, 'lib.tsx')
3901
+ expect(code).toBe(1)
3902
+ expect(rules).toContain('no-dev-helper')
3903
+ })
3904
+
3905
+ it.each([
3906
+ [
3907
+ 'an if-gate',
3908
+ `if (process.env.NODE_ENV !== 'production') {\n void import('@tanstack/react-query-devtools')\n}\n`,
3909
+ ],
3910
+ [
3911
+ 'the bracketed spelling',
3912
+ `if (process.env['NODE_ENV'] !== 'production') {\n void import('./dev-dock')\n}\n`,
3913
+ ],
3914
+ [
3915
+ '=== development',
3916
+ `const m = process.env.NODE_ENV === 'development' ? import('./theme-lab') : null\n`,
3917
+ ],
3918
+ [
3919
+ 'the production alternate',
3920
+ `const m = process.env.NODE_ENV === 'production' ? null : import('./dev-dock')\n`,
3921
+ ],
3922
+ [
3923
+ 'a lazy binding used only under the gate',
3924
+ `${LAZY}export const C = () => process.env.NODE_ENV !== 'production' && <Dock />\n`,
3925
+ ],
3926
+ [
3927
+ 'a lazy binding whose name also appears as a property key, attribute and type',
3928
+ `${LAZY}type T = typeof Dock\nconst o = { Dock: 1 }\nexport const C = () => <X Dock="a" y={o.Dock}>{process.env.NODE_ENV !== 'production' && <Dock />}</X>\n`,
3929
+ ],
3930
+ [
3931
+ 'another binding that shadows the lazy name',
3932
+ `${LAZY}export const A = () => process.env.NODE_ENV !== 'production' && <Dock />\nexport const B = (Dock: unknown) => String(Dock)\n`,
3933
+ ],
3934
+ [
3935
+ 'a plain loader called under the gate',
3936
+ `const load = () => import('./dev-dock')\nif (process.env.NODE_ENV !== 'production') load()\n`,
3937
+ ],
3938
+ [
3939
+ 'a gate inside an && chain',
3940
+ `const load = () => import('./dev-dock')\nexport const f = (ok: boolean) => ok && process.env.NODE_ENV !== 'production' && load()\n`,
3941
+ ],
3942
+ [
3943
+ 'a negated production test',
3944
+ `const load = () => import('./dev-dock')\nif (!(process.env.NODE_ENV === 'production')) load()\n`,
3945
+ ],
3946
+ [
3947
+ 'a negated || test',
3948
+ `const load = () => import('./dev-dock')\nif (!(ok || process.env.NODE_ENV === 'production')) load()\n`,
3949
+ ],
3950
+ [
3951
+ 'the literal on the left',
3952
+ `const load = () => import('./dev-dock')\nif ('production' !== process.env.NODE_ENV) load()\n`,
3953
+ ],
3954
+ [
3955
+ 'TS-wrapped uses under the gate plus a class member of the same name',
3956
+ `${LAZY}class K { Dock = 1 }\nexport const C = () => process.env.NODE_ENV !== 'production' && (Dock as unknown as () => null)\n`,
3957
+ ],
3958
+ ['an unrelated import()', `const m = import('rehype-sanitize')\n`],
3959
+ ])('does NOT flag %s', (_name, source) => {
3960
+ const { code, rules } = run(source, 'lib.tsx')
3961
+ expect(code).toBe(0)
3962
+ expect(rules).not.toContain('no-dev-helper')
4103
3963
  })
4104
3964
 
4105
- // A removed row is kept past the removal only for its message: the export must really be gone,
4106
- // and the removing minor is the next one at the latest — never a date still ahead.
4107
- it('pins every removed row to an export that no longer ships', () => {
4108
- for (const row of DEPRECATED_EXPORTS.filter((r) => r.removed === true)) {
4109
- expect([row.name, compareSemver(row.removeIn, floor) <= 0]).toEqual([row.name, true])
4110
- if (row.prop === undefined) expect([row.name, shipped(row)]).toEqual([row.name, false])
3965
+ it('exempts the dev modules themselves', () => {
3966
+ const { code } = run(`const m = import('./theme-lab')\n`, 'src/dev-dock.tsx')
3967
+ expect(code).toBe(0)
3968
+ })
3969
+ })
3970
+
3971
+ // ── no-import-meta-env — scopes itself to basalt's own source ────────────────
3972
+
3973
+ describe('basalt/no-import-meta-env', () => {
3974
+ const PKG_ROOT = resolve(import.meta.dirname, '..')
3975
+ const visit = (filename: string): number => {
3976
+ const reports: unknown[] = []
3977
+ const visitor = basaltPlugin.rules['no-import-meta-env'].create({
3978
+ filename,
3979
+ sourceCode: { getAllComments: () => [] },
3980
+ report: (r: unknown) => reports.push(r),
3981
+ })
3982
+ const node = {
3983
+ type: 'MemberExpression',
3984
+ computed: false,
3985
+ property: { name: 'env' },
3986
+ object: { type: 'MetaProperty', meta: { name: 'import' }, property: { name: 'meta' } },
3987
+ loc: { start: { line: 1 } },
4111
3988
  }
3989
+ visitor.MemberExpression?.(node)
3990
+ return reports.length
3991
+ }
3992
+
3993
+ it('reports inside packages/basalt-ui/src', () => {
3994
+ expect(visit(`${PKG_ROOT}/src/x.ts`)).toBe(1)
3995
+ })
3996
+
3997
+ it('is silent elsewhere (the playground is a consumer) and in tests', () => {
3998
+ expect(visit(`${PKG_ROOT}/../../apps/playground/src/main.tsx`)).toBe(0)
3999
+ expect(visit(`${PKG_ROOT}/src/x.test.ts`)).toBe(0)
4000
+ })
4001
+ })
4002
+
4003
+ // ── route-view-transition ────────────────────────────────────────────────────
4004
+
4005
+ describe('basalt/route-view-transition', () => {
4006
+ const RR = `import { Outlet } from '@tanstack/react-router'\n`
4007
+ it.each([
4008
+ [
4009
+ 'a named import',
4010
+ `import { ViewTransition } from 'react'\n${RR}export const C = () => <ViewTransition><Outlet /></ViewTransition>\n`,
4011
+ ],
4012
+ [
4013
+ 'an aliased import',
4014
+ `import { ViewTransition as VT } from 'react'\n${RR}export const C = () => <VT><Outlet /></VT>\n`,
4015
+ ],
4016
+ [
4017
+ 'React.ViewTransition',
4018
+ `import React from 'react'\n${RR}export const C = () => <React.ViewTransition><Outlet /></React.ViewTransition>\n`,
4019
+ ],
4020
+ [
4021
+ 'a namespace import',
4022
+ `import * as R from 'react'\n${RR}export const C = () => <R.ViewTransition><Outlet /></R.ViewTransition>\n`,
4023
+ ],
4024
+ [
4025
+ 'an aliased Outlet, nested deep',
4026
+ `import { ViewTransition } from 'react'\nimport { Outlet as Page } from '@tanstack/react-router'\nexport const C = () => <ViewTransition><div><main><Page /></main></div></ViewTransition>\n`,
4027
+ ],
4028
+ [
4029
+ 'a nested ViewTransition (both are inert)',
4030
+ `import { ViewTransition } from 'react'\n${RR}export const C = () => <ViewTransition><section><ViewTransition><Outlet /></ViewTransition></section></ViewTransition>\n`,
4031
+ ],
4032
+ [
4033
+ 'a namespace Outlet',
4034
+ `import { ViewTransition } from 'react'\nimport * as Router from '@tanstack/react-router'\nexport const C = () => <ViewTransition><Router.Outlet /></ViewTransition>\n`,
4035
+ ],
4036
+ ])('flags %s', (_name, source) => {
4037
+ const { code, rules } = run(source, 'lib.tsx')
4038
+ expect(code).toBe(1)
4039
+ expect(rules).toContain('route-view-transition')
4040
+ })
4041
+
4042
+ it.each([
4043
+ [
4044
+ 'a ViewTransition with no Outlet',
4045
+ `import { ViewTransition } from 'react'\nexport const C = () => <ViewTransition><p /></ViewTransition>\n`,
4046
+ ],
4047
+ [
4048
+ 'an Outlet beside, not inside, a ViewTransition',
4049
+ `import { ViewTransition } from 'react'\n${RR}export const C = () => <><ViewTransition><p /></ViewTransition><Outlet /></>\n`,
4050
+ ],
4051
+ [
4052
+ 'a ViewTransition not from react',
4053
+ `import { ViewTransition } from './mine'\n${RR}export const C = () => <ViewTransition><Outlet /></ViewTransition>\n`,
4054
+ ],
4055
+ [
4056
+ 'an Outlet not from the router',
4057
+ `import { ViewTransition } from 'react'\nimport { Outlet } from './mine'\nexport const C = () => <ViewTransition><Outlet /></ViewTransition>\n`,
4058
+ ],
4059
+ [
4060
+ 'a type-only Outlet import',
4061
+ `import { ViewTransition } from 'react'\nimport type { Outlet } from '@tanstack/react-router'\nexport const C = () => <ViewTransition><Outlet /></ViewTransition>\n`,
4062
+ ],
4063
+ ])('does NOT flag %s', (_name, source) => {
4064
+ const { rules } = run(source, 'lib.tsx')
4065
+ expect(rules).not.toContain('route-view-transition')
4112
4066
  })
4113
4067
 
4114
- it('names a real published subpath on every row', () => {
4115
- const exports = Object.keys(
4116
- (
4117
- JSON.parse(readFileSync(resolve(import.meta.dirname, '..', 'package.json'), 'utf8')) as {
4118
- exports: Record<string, unknown>
4119
- }
4120
- ).exports,
4068
+ it('reports each enclosing ViewTransition once', () => {
4069
+ const { output } = run(
4070
+ `import { ViewTransition } from 'react'\n${RR}export const C = () => <ViewTransition><ViewTransition><Outlet /></ViewTransition></ViewTransition>\n`,
4071
+ 'lib.tsx',
4121
4072
  )
4122
- for (const row of DEPRECATED_EXPORTS) {
4123
- const subpath = row.subpath === 'basalt-ui' ? '.' : row.subpath.replace('basalt-ui', '.')
4124
- expect([row.subpath, exports.includes(subpath)]).toEqual([row.subpath, true])
4125
- }
4073
+ expect(output.match(/basalt\/route-view-transition/g)).toHaveLength(2)
4126
4074
  })
4127
4075
 
4128
- /**
4129
- * The barrel half of the lifecycle: a JSDoc `@deprecated` on a published barrel with no ledger
4130
- * row is an export nothing nudges anyone off. Deliberately scoped to `src/**' + '/index.ts(x)` —
4131
- * the files a consumer's import resolves through. To add one: write the `@deprecated` JSDoc, add
4132
- * the `DEPRECATED_EXPORTS` row, add the `MIGRATING.md` row.
4133
- */
4134
- it('has a row for every @deprecated JSDoc tag on a published barrel', () => {
4135
- const barrels = [
4136
- ...new Bun.Glob('src/**/index.{ts,tsx}').scanSync({
4137
- cwd: resolve(import.meta.dirname, '..'),
4138
- absolute: true,
4139
- }),
4140
- ]
4141
- const names = new Set(DEPRECATED_EXPORTS.map((row) => row.prop ?? row.name))
4142
- for (const file of barrels) {
4143
- const lines = readFileSync(file, 'utf8').split('\n')
4144
- for (const [index, line] of lines.entries()) {
4145
- if (!/^\s*\*\s*@deprecated\b/.test(line)) continue
4146
- const declared = lines
4147
- .slice(index, index + 12)
4148
- .map((l) => /^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\??\s*[:(]/.exec(l)?.[1])
4149
- .find((name) => name !== undefined)
4150
- expect([file, declared, declared !== undefined && names.has(declared)]).toEqual([
4151
- file,
4152
- declared,
4153
- true,
4154
- ])
4155
- }
4076
+ it('does not apply in test files', () => {
4077
+ const source = `import { ViewTransition } from 'react'\n${RR}export const C = () => <ViewTransition><Outlet /></ViewTransition>\n`
4078
+ expect(run(source, 'lib.test.tsx').rules).not.toContain('route-view-transition')
4079
+ })
4080
+
4081
+ it('honours a theme-allow annotation naming the rule', () => {
4082
+ const source = `import { ViewTransition } from 'react'\n${RR}export const C = () => (\n // theme-allow route-view-transition: legacy page under migration\n <ViewTransition><Outlet /></ViewTransition>\n)\n`
4083
+ expect(run(source, 'lib.tsx').rules).not.toContain('route-view-transition')
4084
+ })
4085
+ })
4086
+
4087
+ describe('rule level locks', () => {
4088
+ const levels = (path: string) =>
4089
+ (JSON.parse(readFileSync(path, 'utf8')) as { rules: Record<string, unknown> }).rules
4090
+ const shipped = levels(resolve(import.meta.dirname, 'oxlint-basalt.json'))
4091
+ const repo = levels(resolve(import.meta.dirname, '..', '..', '..', '.oxlintrc.json'))
4092
+
4093
+ it('no-dev-helper is repo-local only, at error', () => {
4094
+ expect(shipped).not.toHaveProperty('basalt/no-dev-helper')
4095
+ expect(repo['basalt/no-dev-helper']).toBe('error')
4096
+ })
4097
+ })
4098
+
4099
+ // ── dogfood — /.oxlintrc.json ⊇ the shipped basalt/* levels ──────────────────
4100
+
4101
+ describe('/.oxlintrc.json dogfoods oxlint-basalt.json', () => {
4102
+ const rulesOf = (path: string) =>
4103
+ (JSON.parse(readFileSync(path, 'utf8')) as { rules: Record<string, unknown> }).rules
4104
+ const shipped = rulesOf(resolve(import.meta.dirname, 'oxlint-basalt.json'))
4105
+ const repo = rulesOf(resolve(import.meta.dirname, '..', '..', '..', '.oxlintrc.json'))
4106
+
4107
+ // Repo-local is the strict side: `error` for every shipped id, grace entries included (those
4108
+ // ship `warn`); every other id ships the very level the repo runs.
4109
+ it.each(Object.entries(shipped))('%s is error repo-locally', (id, level) => {
4110
+ expect([id, repo[id]]).toEqual([id, 'error'])
4111
+ if (!Object.hasOwn(PLUGIN_RULE_GRACE, id.slice('basalt/'.length))) {
4112
+ expect([id, level]).toEqual([id, repo[id]])
4156
4113
  }
4157
4114
  })
4158
4115
  })
@@ -4198,11 +4155,6 @@ describe('the `Ships:` line above each rule', () => {
4198
4155
  expect([id, level]).toEqual([id, 'warn'])
4199
4156
  return
4200
4157
  }
4201
- if (Object.hasOwn(PLUGIN_RULE_ADVISORY, id)) {
4202
- expect([id, claim]).toEqual([id, 'warn (advisory)'])
4203
- expect([id, level]).toEqual([id, 'warn'])
4204
- return
4205
- }
4206
4158
  expect([id, claim]).toEqual([id, 'error'])
4207
4159
  expect([id, level]).toEqual([id, 'error'])
4208
4160
  })