basalt-ui 1.41.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (915) hide show
  1. package/AGENTS.md +46 -43
  2. package/CHANGELOG.md +542 -518
  3. package/MIGRATING.md +964 -37
  4. package/README.md +171 -366
  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 +16 -26
  15. package/agent/templates/DESIGN.md.tpl +1 -1
  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/oxlint-basalt.json +4 -10
  20. package/configs/oxlint-plugin.js +465 -844
  21. package/configs/oxlint-plugin.test.ts +661 -709
  22. package/configs/oxlint.json +10 -2
  23. package/dist/agent/ai-sdk-transport.d.ts +19 -7
  24. package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
  25. package/dist/agent/ai-sdk-transport.js +10 -7
  26. package/dist/{agent-chat → agent/chat}/composer.d.ts +16 -16
  27. package/dist/agent/chat/composer.d.ts.map +1 -0
  28. package/dist/{agent-chat → agent/chat}/composer.js +10 -9
  29. package/dist/{agent-chat → agent/chat}/message-affordances.d.ts +7 -6
  30. package/dist/agent/chat/message-affordances.d.ts.map +1 -0
  31. package/dist/agent/chat/relative-time.d.ts.map +1 -0
  32. package/dist/{agent-chat → agent/chat}/thread-feed-row.d.ts +18 -16
  33. package/dist/agent/chat/thread-feed-row.d.ts.map +1 -0
  34. package/dist/{agent-chat → agent/chat}/thread-feed-row.js +12 -11
  35. package/dist/{agent-chat → agent/chat}/thread-feed.d.ts +8 -8
  36. package/dist/agent/chat/thread-feed.d.ts.map +1 -0
  37. package/dist/{agent-chat → agent/chat}/thread-feed.js +12 -10
  38. package/dist/{agent-chat → agent/chat}/thread-message.d.ts +12 -9
  39. package/dist/agent/chat/thread-message.d.ts.map +1 -0
  40. package/dist/{agent-chat → agent/chat}/thread-message.js +15 -13
  41. package/dist/{agent-chat → agent/chat}/thread-outcome-card.d.ts +2 -2
  42. package/dist/agent/chat/thread-outcome-card.d.ts.map +1 -0
  43. package/dist/{agent-chat → agent/chat}/thread-outcome-card.js +3 -3
  44. package/dist/{agent-chat → agent/chat}/tool-chip.d.ts +5 -5
  45. package/dist/agent/chat/tool-chip.d.ts.map +1 -0
  46. package/dist/{agent-chat → agent/chat}/tool-chip.js +4 -4
  47. package/dist/agent/chat/use-keyboard-inset.d.ts.map +1 -0
  48. package/dist/{agent-chat → agent/chat}/use-keyboard-inset.js +1 -1
  49. package/dist/{agent-chat → agent/chat}/virtualize.d.ts +7 -7
  50. package/dist/agent/chat/virtualize.d.ts.map +1 -0
  51. package/dist/agent/error.d.ts +31 -0
  52. package/dist/agent/error.d.ts.map +1 -0
  53. package/dist/agent/error.js +18 -0
  54. package/dist/agent/history.d.ts +2 -36
  55. package/dist/agent/history.d.ts.map +1 -1
  56. package/dist/agent/history.js +0 -32
  57. package/dist/agent/id.d.ts +4 -5
  58. package/dist/agent/id.d.ts.map +1 -1
  59. package/dist/agent/index.d.ts +19 -52
  60. package/dist/agent/index.d.ts.map +1 -1
  61. package/dist/agent/index.js +10 -32
  62. package/dist/agent/outcome.d.ts +3 -14
  63. package/dist/agent/outcome.d.ts.map +1 -1
  64. package/dist/agent/outcome.js +0 -30
  65. package/dist/agent/part-list.d.ts +3 -3
  66. package/dist/agent/part-list.d.ts.map +1 -1
  67. package/dist/agent/parts.d.ts +23 -24
  68. package/dist/agent/parts.d.ts.map +1 -1
  69. package/dist/agent/run-committer.d.ts +23 -0
  70. package/dist/agent/run-committer.d.ts.map +1 -0
  71. package/dist/agent/run-committer.js +41 -0
  72. package/dist/agent/run-records.d.ts +77 -0
  73. package/dist/agent/run-records.d.ts.map +1 -0
  74. package/dist/agent/run-records.js +69 -0
  75. package/dist/agent/thread.d.ts +23 -47
  76. package/dist/agent/thread.d.ts.map +1 -1
  77. package/dist/agent/thread.js +3 -134
  78. package/dist/agent/transport.d.ts +6 -69
  79. package/dist/agent/transport.d.ts.map +1 -1
  80. package/dist/agent/transport.js +0 -18
  81. package/dist/agent/use-agent-thread-runs.d.ts +63 -26
  82. package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
  83. package/dist/agent/use-agent-thread-runs.js +202 -66
  84. package/dist/charts/BandStrip.d.ts +46 -0
  85. package/dist/charts/BandStrip.d.ts.map +1 -0
  86. package/dist/charts/BandStrip.js +151 -0
  87. package/dist/charts/Bars.d.ts +19 -0
  88. package/dist/charts/Bars.d.ts.map +1 -0
  89. package/dist/charts/Bars.js +77 -0
  90. package/dist/charts/CartesianChart.d.ts +57 -0
  91. package/dist/charts/CartesianChart.d.ts.map +1 -0
  92. package/dist/charts/CartesianChart.js +121 -0
  93. package/dist/charts/ChartCard.d.ts +34 -0
  94. package/dist/charts/ChartCard.d.ts.map +1 -0
  95. package/dist/charts/ChartCard.js +115 -0
  96. package/dist/charts/Donut.d.ts +25 -0
  97. package/dist/charts/Donut.d.ts.map +1 -0
  98. package/dist/charts/Donut.js +191 -0
  99. package/dist/charts/DualPanel.d.ts +47 -0
  100. package/dist/charts/DualPanel.d.ts.map +1 -0
  101. package/dist/charts/DualPanel.js +214 -0
  102. package/dist/charts/Heatmap.d.ts +41 -0
  103. package/dist/charts/Heatmap.d.ts.map +1 -0
  104. package/dist/charts/Heatmap.js +216 -0
  105. package/dist/charts/Lines.d.ts +11 -0
  106. package/dist/charts/Lines.d.ts.map +1 -0
  107. package/dist/charts/Lines.js +114 -0
  108. package/dist/charts/MirroredBars.d.ts +44 -0
  109. package/dist/charts/MirroredBars.d.ts.map +1 -0
  110. package/dist/charts/MirroredBars.js +190 -0
  111. package/dist/charts/Sparkline.d.ts +22 -0
  112. package/dist/charts/Sparkline.d.ts.map +1 -0
  113. package/dist/charts/Sparkline.js +129 -0
  114. package/dist/charts/StackedArea.d.ts +15 -0
  115. package/dist/charts/StackedArea.d.ts.map +1 -0
  116. package/dist/charts/StackedArea.js +46 -0
  117. package/dist/charts/box.d.ts +38 -0
  118. package/dist/charts/box.d.ts.map +1 -0
  119. package/dist/charts/box.js +72 -0
  120. package/dist/charts/card-context.d.ts +3 -0
  121. package/dist/charts/card-context.d.ts.map +1 -0
  122. package/dist/charts/card-context.js +7 -0
  123. package/dist/charts/chart-box.module.css +64 -0
  124. package/{src/content/article-card.module.css.d.ts → dist/charts/chart-box.module.css.d.ts} +5 -9
  125. package/dist/charts/chart-card.module.css +25 -0
  126. package/dist/{dashboard/widget-grid.module.css.d.ts → charts/chart-card.module.css.d.ts} +1 -3
  127. package/dist/charts/chart-legend.module.css +38 -0
  128. package/{src/dashboard/widget-grid.module.css.d.ts → dist/charts/chart-legend.module.css.d.ts} +3 -3
  129. package/dist/charts/compile.d.ts +62 -0
  130. package/dist/charts/compile.d.ts.map +1 -0
  131. package/dist/charts/compile.js +386 -0
  132. package/dist/charts/cursor.d.ts +50 -0
  133. package/dist/charts/cursor.d.ts.map +1 -0
  134. package/dist/charts/cursor.js +157 -0
  135. package/dist/charts/curves.d.ts +12 -0
  136. package/dist/charts/curves.d.ts.map +1 -0
  137. package/dist/charts/curves.js +86 -0
  138. package/dist/charts/donut.module.css +54 -0
  139. package/dist/{content/article-card.module.css.d.ts → charts/donut.module.css.d.ts} +2 -9
  140. package/dist/charts/host.d.ts +17 -0
  141. package/dist/charts/host.d.ts.map +1 -0
  142. package/dist/charts/host.js +35 -0
  143. package/dist/charts/index.d.ts +18 -53
  144. package/dist/charts/index.d.ts.map +1 -1
  145. package/dist/charts/index.js +17 -189
  146. package/dist/charts/legend.d.ts +23 -0
  147. package/dist/charts/legend.d.ts.map +1 -0
  148. package/dist/charts/legend.js +160 -0
  149. package/dist/charts/marks.d.ts +25 -0
  150. package/dist/charts/marks.d.ts.map +1 -0
  151. package/dist/charts/marks.js +128 -0
  152. package/dist/charts/panel.d.ts +56 -0
  153. package/dist/charts/panel.d.ts.map +1 -0
  154. package/dist/charts/panel.js +123 -0
  155. package/dist/charts/scales.d.ts +44 -0
  156. package/dist/charts/scales.d.ts.map +1 -0
  157. package/dist/charts/scales.js +163 -0
  158. package/dist/charts/sizing.d.ts +12 -0
  159. package/dist/charts/sizing.d.ts.map +1 -0
  160. package/dist/charts/sizing.js +33 -0
  161. package/dist/charts/state.d.ts +17 -0
  162. package/dist/charts/state.d.ts.map +1 -0
  163. package/dist/charts/state.js +53 -0
  164. package/dist/charts/theme.d.ts +38 -19
  165. package/dist/charts/theme.d.ts.map +1 -1
  166. package/dist/charts/theme.js +48 -26
  167. package/dist/charts/types.d.ts +227 -0
  168. package/dist/charts/types.d.ts.map +1 -0
  169. package/dist/charts/types.js +4 -0
  170. package/dist/cli/audit-allows.d.ts +4 -17
  171. package/dist/cli/audit-allows.d.ts.map +1 -1
  172. package/dist/cli/config.d.ts +18 -43
  173. package/dist/cli/config.d.ts.map +1 -1
  174. package/dist/cli/config.js +6 -0
  175. package/dist/cli/doctor-upgrade.d.ts +43 -0
  176. package/dist/cli/doctor-upgrade.d.ts.map +1 -0
  177. package/dist/cli/doctor-upgrade.js +314 -0
  178. package/dist/cli/doctor.d.ts +14 -29
  179. package/dist/cli/doctor.d.ts.map +1 -1
  180. package/dist/cli/doctor.js +58 -9
  181. package/dist/cli/help.d.ts.map +1 -1
  182. package/dist/cli/help.js +2 -4
  183. package/dist/cli/index.d.ts +48 -172
  184. package/dist/cli/index.d.ts.map +1 -1
  185. package/dist/cli/index.js +39 -132
  186. package/dist/cli/init.d.ts.map +1 -1
  187. package/dist/cli/init.js +43 -37
  188. package/dist/cli/sync.d.ts +5 -21
  189. package/dist/cli/sync.d.ts.map +1 -1
  190. package/dist/cli/sync.js +4 -23
  191. package/dist/cli/tokens-css.d.ts +7 -28
  192. package/dist/cli/tokens-css.d.ts.map +1 -1
  193. package/dist/commands/ShortcutsHelp.d.ts.map +1 -1
  194. package/dist/commands/ShortcutsHelp.js +4 -5
  195. package/dist/commands/define-commands.d.ts +15 -31
  196. package/dist/commands/define-commands.d.ts.map +1 -1
  197. package/dist/commands/define-commands.js +1 -11
  198. package/dist/commands/define-overlays.d.ts +5 -19
  199. package/dist/commands/define-overlays.d.ts.map +1 -1
  200. package/dist/commands/define-overlays.js +3 -11
  201. package/dist/commands/index.d.ts +10 -40
  202. package/dist/commands/index.d.ts.map +1 -1
  203. package/dist/commands/index.js +9 -46
  204. package/dist/{notifications → commands/notifications}/bell.d.ts +1 -1
  205. package/dist/commands/notifications/bell.d.ts.map +1 -0
  206. package/dist/{notifications → commands/notifications}/center.d.ts +2 -2
  207. package/dist/commands/notifications/center.d.ts.map +1 -0
  208. package/dist/{notifications → commands/notifications}/center.js +2 -2
  209. package/dist/{notifications → commands/notifications}/define-notifications.d.ts +3 -3
  210. package/dist/commands/notifications/define-notifications.d.ts.map +1 -0
  211. package/dist/commands/notifications/notify.d.ts +44 -0
  212. package/dist/commands/notifications/notify.d.ts.map +1 -0
  213. package/dist/commands/notifications/notify.js +79 -0
  214. package/dist/{notifications → commands/notifications}/store.d.ts +2 -0
  215. package/dist/commands/notifications/store.d.ts.map +1 -0
  216. package/dist/{notifications → commands/notifications}/store.js +4 -0
  217. package/dist/commands/projectors.d.ts +1 -131
  218. package/dist/commands/projectors.d.ts.map +1 -1
  219. package/dist/commands/projectors.js +2 -73
  220. package/dist/commands/shell-bridge.d.ts +3 -3
  221. package/dist/commands/shortcut-format.d.ts +5 -4
  222. package/dist/commands/shortcut-format.d.ts.map +1 -1
  223. package/dist/common/errors.d.ts +3 -3
  224. package/dist/common/use-measured-widths.d.ts.map +1 -1
  225. package/dist/common/use-measured-widths.js +5 -3
  226. package/dist/common/validate.d.ts +2 -2
  227. package/dist/content/article-layout.d.ts.map +1 -1
  228. package/dist/content/article-layout.js +3 -3
  229. package/dist/content/article-model.d.ts +1 -46
  230. package/dist/content/article-model.d.ts.map +1 -1
  231. package/dist/content/article-model.js +1 -29
  232. package/dist/content/index.d.ts +11 -41
  233. package/dist/content/index.d.ts.map +1 -1
  234. package/dist/content/index.js +3 -35
  235. package/dist/content/markdown.d.ts +1 -1
  236. package/dist/content/markdown.d.ts.map +1 -1
  237. package/dist/content/markdown.js +1 -1
  238. package/dist/content/mermaid.d.ts.map +1 -1
  239. package/dist/content/mermaid.js +7 -7
  240. package/dist/content/slug.d.ts +1 -8
  241. package/dist/content/slug.d.ts.map +1 -1
  242. package/dist/content/slug.js +1 -8
  243. package/dist/content/url-hardening.d.ts +2 -2
  244. package/dist/content/url-hardening.d.ts.map +1 -1
  245. package/dist/content/url-hardening.js +2 -0
  246. package/dist/controls/actions.d.ts +16 -1
  247. package/dist/controls/actions.d.ts.map +1 -1
  248. package/dist/controls/enum-filter.d.ts +1 -1
  249. package/dist/controls/enum-filter.d.ts.map +1 -1
  250. package/dist/controls/filter-context.d.ts.map +1 -1
  251. package/dist/controls/filter-context.js +3 -1
  252. package/dist/controls/filter-set.js +4 -6
  253. package/dist/controls/form-layout.d.ts +1 -1
  254. package/dist/controls/form-layout.d.ts.map +1 -1
  255. package/dist/controls/form-layout.js +1 -1
  256. package/dist/controls/glyphs.d.ts +0 -2
  257. package/dist/controls/glyphs.d.ts.map +1 -1
  258. package/dist/controls/glyphs.js +0 -7
  259. package/dist/controls/index.d.ts +12 -17
  260. package/dist/controls/index.d.ts.map +1 -1
  261. package/dist/controls/index.js +5 -39
  262. package/dist/controls/multi-select-filter.d.ts +4 -3
  263. package/dist/controls/multi-select-filter.d.ts.map +1 -1
  264. package/dist/controls/number-filter.d.ts +4 -3
  265. package/dist/controls/number-filter.d.ts.map +1 -1
  266. package/dist/controls/number-filter.js +3 -1
  267. package/dist/controls/range-filter.d.ts +4 -3
  268. package/dist/controls/range-filter.d.ts.map +1 -1
  269. package/dist/controls/search-filter.d.ts +4 -3
  270. package/dist/controls/search-filter.d.ts.map +1 -1
  271. package/dist/controls/search-filter.js +19 -14
  272. package/dist/controls/select-filter.d.ts +4 -3
  273. package/dist/controls/select-filter.d.ts.map +1 -1
  274. package/dist/controls/sync-button.d.ts.map +1 -1
  275. package/dist/controls/sync-button.js +12 -15
  276. package/dist/controls/toggle-filter.d.ts +4 -3
  277. package/dist/controls/toggle-filter.d.ts.map +1 -1
  278. package/dist/controls/uncontrolled.d.ts +12 -0
  279. package/dist/controls/uncontrolled.d.ts.map +1 -0
  280. package/dist/controls/view-tabs.d.ts +2 -2
  281. package/dist/controls/view-tabs.d.ts.map +1 -1
  282. package/dist/controls-dates/index.d.ts +0 -1
  283. package/dist/controls-dates/index.d.ts.map +1 -1
  284. package/dist/dashboard/index.d.ts +7 -9
  285. package/dist/dashboard/index.d.ts.map +1 -1
  286. package/dist/dashboard/index.js +3 -23
  287. package/dist/dashboard/section.d.ts +4 -6
  288. package/dist/dashboard/section.d.ts.map +1 -1
  289. package/dist/dashboard/section.js +4 -10
  290. package/dist/dashboard/stat-card.d.ts +4 -4
  291. package/dist/dashboard/stat-card.d.ts.map +1 -1
  292. package/dist/dashboard/stat-card.js +3 -3
  293. package/dist/dashboard/widget-header.module.css +9 -7
  294. package/dist/dev-dock.d.ts.map +1 -1
  295. package/dist/dev-dock.js +6 -1
  296. package/dist/format/date.d.ts +0 -34
  297. package/dist/format/date.d.ts.map +1 -1
  298. package/dist/format/date.js +0 -54
  299. package/dist/format/index.d.ts +7 -22
  300. package/dist/format/index.d.ts.map +1 -1
  301. package/dist/format/index.js +2 -43
  302. package/dist/format/number.d.ts +3 -14
  303. package/dist/format/number.d.ts.map +1 -1
  304. package/dist/format/number.js +0 -13
  305. package/dist/format/shared.d.ts +2 -3
  306. package/dist/format/shared.d.ts.map +1 -1
  307. package/dist/forms/index.d.ts +0 -11
  308. package/dist/forms/index.d.ts.map +1 -1
  309. package/dist/forms/index.js +1 -14
  310. package/dist/guard/index.d.ts +6 -21
  311. package/dist/guard/index.d.ts.map +1 -1
  312. package/dist/guard/index.js +27 -115
  313. package/dist/guard/types.d.ts +7 -18
  314. package/dist/guard/types.d.ts.map +1 -1
  315. package/dist/index.d.ts +16 -48
  316. package/dist/index.d.ts.map +1 -1
  317. package/dist/index.js +21 -117
  318. package/dist/provider/create-router.d.ts +44 -0
  319. package/dist/provider/create-router.d.ts.map +1 -0
  320. package/dist/provider/create-router.js +62 -0
  321. package/dist/provider/hotkeys.d.ts +8 -0
  322. package/dist/provider/hotkeys.d.ts.map +1 -0
  323. package/dist/provider/hotkeys.js +50 -0
  324. package/dist/provider/index.d.ts +33 -110
  325. package/dist/provider/index.d.ts.map +1 -1
  326. package/dist/provider/index.js +182 -82
  327. package/dist/provider/lab-theme.d.ts.map +1 -1
  328. package/dist/provider/route-transition.d.ts +76 -0
  329. package/dist/provider/route-transition.d.ts.map +1 -0
  330. package/dist/provider/route-transition.js +98 -0
  331. package/dist/provider/spotlight-actions.d.ts +10 -0
  332. package/dist/provider/spotlight-actions.d.ts.map +1 -0
  333. package/dist/provider/spotlight-actions.js +55 -0
  334. package/dist/provider/test-router.d.ts +6 -0
  335. package/dist/provider/test-router.d.ts.map +1 -0
  336. package/dist/provider/test-router.js +18 -0
  337. package/dist/query-client.d.ts +4 -20
  338. package/dist/query-client.d.ts.map +1 -1
  339. package/dist/query-client.js +2 -2
  340. package/dist/router-tanstack/index.d.ts +18 -64
  341. package/dist/router-tanstack/index.d.ts.map +1 -1
  342. package/dist/router-tanstack/index.js +2 -16
  343. package/dist/router-tanstack/nav.d.ts +12 -1
  344. package/dist/router-tanstack/nav.d.ts.map +1 -1
  345. package/dist/router-tanstack/nav.js +8 -1
  346. package/dist/router-tanstack/search-store.d.ts +1 -1
  347. package/dist/router-tanstack/search-store.d.ts.map +1 -1
  348. package/dist/router-tanstack/use-nav.d.ts +8 -9
  349. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  350. package/dist/router-tanstack/use-nav.js +20 -1
  351. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  352. package/dist/shell/app-sidebar.js +1 -1
  353. package/dist/shell/index.d.ts +31 -10
  354. package/dist/shell/index.d.ts.map +1 -1
  355. package/dist/shell/index.js +14 -3
  356. package/dist/shell/page-aside.d.ts +2 -2
  357. package/dist/shell/page-aside.d.ts.map +1 -1
  358. package/dist/shell/page-aside.js +31 -12
  359. package/dist/shell/page-bar.d.ts +2 -2
  360. package/dist/shell/page-bar.d.ts.map +1 -1
  361. package/dist/shell/page-bar.js +1 -1
  362. package/dist/state.d.ts +10 -12
  363. package/dist/state.d.ts.map +1 -1
  364. package/dist/state.js +4 -6
  365. package/dist/styles.css +107 -7
  366. package/dist/surfaces.d.ts +28 -103
  367. package/dist/surfaces.d.ts.map +1 -1
  368. package/dist/surfaces.js +54 -143
  369. package/dist/{data → table}/data-table-state.d.ts +1 -1
  370. package/dist/table/data-table-state.d.ts.map +1 -0
  371. package/dist/{data → table}/data-table-state.js +4 -3
  372. package/dist/{data → table}/data-table.d.ts +28 -49
  373. package/dist/table/data-table.d.ts.map +1 -0
  374. package/dist/{data → table}/data-table.js +74 -60
  375. package/dist/table/index.d.ts +18 -0
  376. package/dist/table/index.d.ts.map +1 -0
  377. package/dist/{data/table.js → table/index.js} +1 -1
  378. package/dist/table/query-branch.d.ts.map +1 -0
  379. package/dist/table/table-features.d.ts +69 -0
  380. package/dist/table/table-features.d.ts.map +1 -0
  381. package/dist/table/table-features.js +49 -0
  382. package/dist/telemetry.d.ts +77 -0
  383. package/dist/telemetry.d.ts.map +1 -0
  384. package/dist/telemetry.js +33 -0
  385. package/dist/theme/index.d.ts +10 -21
  386. package/dist/theme/index.d.ts.map +1 -1
  387. package/dist/theme/index.js +3 -14
  388. package/dist/theme/theme-toggle.d.ts.map +1 -1
  389. package/dist/theme/theme-toggle.js +7 -33
  390. package/dist/theme/theme-toggle.module.css +30 -0
  391. package/dist/theme/theme-toggle.module.css.d.ts +10 -0
  392. package/dist/theme-lab/derive-controls.d.ts.map +1 -1
  393. package/dist/theme-lab/derive-controls.js +5 -3
  394. package/dist/theme-lab/derive-state.d.ts.map +1 -1
  395. package/dist/theme-lab/derive-state.js +2 -1
  396. package/dist/tokens/core.d.ts +360 -0
  397. package/dist/tokens/core.d.ts.map +1 -0
  398. package/dist/tokens/core.js +708 -0
  399. package/dist/tokens/derive.d.ts +1 -1
  400. package/dist/tokens/derive.d.ts.map +1 -1
  401. package/dist/tokens/index.d.ts +5 -373
  402. package/dist/tokens/index.d.ts.map +1 -1
  403. package/dist/tokens/index.js +8 -700
  404. package/dist/tokens/palette.d.ts +2 -6
  405. package/dist/tokens/palette.d.ts.map +1 -1
  406. package/dist/tokens/palette.js +1 -4
  407. package/dist/vite.d.ts +4 -3
  408. package/dist/vite.d.ts.map +1 -1
  409. package/llms.txt +43 -85
  410. package/package.json +80 -173
  411. package/src/agent/ai-sdk-transport.ts +57 -37
  412. package/src/{agent-chat → agent/chat}/composer.tsx +29 -30
  413. package/src/{agent-chat → agent/chat}/message-affordances.ts +8 -5
  414. package/src/{agent-chat → agent/chat}/thread-feed-row.tsx +30 -35
  415. package/src/{agent-chat → agent/chat}/thread-feed.tsx +40 -35
  416. package/src/{agent-chat → agent/chat}/thread-message.tsx +37 -35
  417. package/src/{agent-chat → agent/chat}/thread-outcome-card.tsx +5 -5
  418. package/src/{agent-chat → agent/chat}/tool-chip.tsx +12 -12
  419. package/src/{agent-chat → agent/chat}/use-keyboard-inset.ts +1 -1
  420. package/src/{agent-chat → agent/chat}/virtualize.ts +5 -5
  421. package/src/agent/error.ts +43 -0
  422. package/src/agent/history.ts +2 -107
  423. package/src/agent/id.ts +4 -5
  424. package/src/agent/index.ts +23 -112
  425. package/src/agent/outcome.ts +3 -56
  426. package/src/agent/part-list.tsx +3 -3
  427. package/src/agent/parts.ts +23 -22
  428. package/src/agent/run-committer.ts +64 -0
  429. package/src/agent/run-records.ts +147 -0
  430. package/src/agent/thread.ts +24 -231
  431. package/src/agent/transport.ts +6 -96
  432. package/src/agent/use-agent-thread-runs.ts +364 -199
  433. package/src/charts/BandStrip.tsx +276 -0
  434. package/src/charts/Bars.tsx +141 -0
  435. package/src/charts/CartesianChart.tsx +206 -0
  436. package/src/charts/ChartCard.tsx +178 -0
  437. package/src/charts/Donut.tsx +256 -0
  438. package/src/charts/DualPanel.tsx +326 -0
  439. package/src/charts/Heatmap.tsx +282 -0
  440. package/src/charts/Lines.tsx +151 -0
  441. package/src/charts/MirroredBars.tsx +303 -0
  442. package/src/charts/Sparkline.tsx +176 -0
  443. package/src/charts/StackedArea.tsx +80 -0
  444. package/src/charts/box.tsx +96 -0
  445. package/src/charts/card-context.ts +11 -0
  446. package/src/charts/chart-box.module.css +64 -0
  447. package/src/charts/chart-box.module.css.d.ts +12 -0
  448. package/src/charts/chart-card.module.css +25 -0
  449. package/src/charts/chart-card.module.css.d.ts +8 -0
  450. package/src/charts/chart-legend.module.css +38 -0
  451. package/src/charts/chart-legend.module.css.d.ts +10 -0
  452. package/src/charts/compile.ts +585 -0
  453. package/src/charts/cursor.ts +282 -0
  454. package/src/charts/curves.ts +111 -0
  455. package/src/charts/donut.module.css +54 -0
  456. package/src/charts/donut.module.css.d.ts +9 -0
  457. package/src/charts/host.tsx +60 -0
  458. package/src/charts/index.ts +41 -178
  459. package/src/charts/legend.tsx +204 -0
  460. package/src/charts/marks.ts +201 -0
  461. package/src/charts/panel.tsx +227 -0
  462. package/src/charts/scales.ts +237 -0
  463. package/src/charts/sizing.ts +47 -0
  464. package/src/charts/state.tsx +80 -0
  465. package/src/charts/theme.ts +65 -0
  466. package/src/charts/types.ts +242 -0
  467. package/src/cli/audit-allows.ts +8 -30
  468. package/src/cli/config.ts +26 -43
  469. package/src/cli/doctor-upgrade.ts +457 -0
  470. package/src/cli/doctor.ts +89 -72
  471. package/src/cli/help.ts +2 -4
  472. package/src/cli/index.ts +100 -384
  473. package/src/cli/init.ts +60 -51
  474. package/src/cli/sync.ts +13 -68
  475. package/src/cli/tokens-css.ts +7 -28
  476. package/src/commands/ShortcutsHelp.tsx +8 -9
  477. package/src/commands/define-commands.ts +16 -43
  478. package/src/commands/define-overlays.ts +12 -36
  479. package/src/commands/index.ts +11 -94
  480. package/src/{notifications → commands/notifications}/bell.tsx +2 -2
  481. package/src/{notifications → commands/notifications}/center.tsx +7 -5
  482. package/src/{notifications → commands/notifications}/define-notifications.ts +3 -3
  483. package/src/commands/notifications/notify.tsx +158 -0
  484. package/src/{notifications → commands/notifications}/store.ts +10 -2
  485. package/src/commands/projectors.ts +5 -225
  486. package/src/commands/shell-bridge.ts +5 -5
  487. package/src/commands/shortcut-format.ts +5 -4
  488. package/src/common/errors.ts +3 -3
  489. package/src/common/use-measured-widths.ts +6 -3
  490. package/src/common/validate.ts +2 -2
  491. package/src/content/article-layout.tsx +37 -38
  492. package/src/content/article-model.ts +1 -84
  493. package/src/content/index.ts +12 -83
  494. package/src/content/markdown.tsx +4 -3
  495. package/src/content/mermaid.tsx +9 -7
  496. package/src/content/slug.ts +1 -16
  497. package/src/content/url-hardening.ts +8 -2
  498. package/src/controls/actions.tsx +24 -1
  499. package/src/controls/enum-filter.tsx +1 -1
  500. package/src/controls/filter-context.tsx +3 -1
  501. package/src/controls/filter-set.tsx +5 -7
  502. package/src/controls/form-layout.tsx +2 -2
  503. package/src/controls/glyphs.tsx +0 -10
  504. package/src/controls/index.ts +13 -64
  505. package/src/controls/multi-select-filter.tsx +5 -4
  506. package/src/controls/number-filter.tsx +8 -4
  507. package/src/controls/range-filter.tsx +5 -4
  508. package/src/controls/search-filter.tsx +25 -19
  509. package/src/controls/select-filter.tsx +5 -4
  510. package/src/controls/sync-button.tsx +18 -20
  511. package/src/controls/toggle-filter.tsx +5 -4
  512. package/src/controls/uncontrolled.ts +12 -0
  513. package/src/controls/view-tabs.tsx +3 -3
  514. package/src/controls-dates/date-range-picker.tsx +1 -1
  515. package/src/controls-dates/index.ts +0 -1
  516. package/src/dashboard/index.ts +7 -48
  517. package/src/dashboard/section.tsx +11 -40
  518. package/src/dashboard/stat-card.tsx +14 -16
  519. package/src/dashboard/widget-header.module.css +9 -7
  520. package/src/dev-dock.tsx +14 -27
  521. package/src/format/date.ts +2 -92
  522. package/src/format/index.ts +7 -53
  523. package/src/format/number.ts +3 -26
  524. package/src/format/shared.ts +2 -3
  525. package/src/forms/index.ts +0 -23
  526. package/src/guard/index.ts +36 -191
  527. package/src/guard/types.ts +6 -19
  528. package/src/index.ts +30 -282
  529. package/src/provider/create-router.tsx +138 -0
  530. package/src/provider/hotkeys.ts +70 -0
  531. package/src/provider/index.tsx +260 -230
  532. package/src/provider/lab-theme.ts +3 -2
  533. package/src/provider/route-transition.ts +199 -0
  534. package/src/provider/spotlight-actions.tsx +69 -0
  535. package/src/provider/test-router.tsx +25 -0
  536. package/src/query-client.ts +4 -22
  537. package/src/router-tanstack/index.ts +18 -112
  538. package/src/router-tanstack/nav.ts +28 -2
  539. package/src/router-tanstack/search-store.ts +9 -2
  540. package/src/router-tanstack/use-nav.ts +47 -13
  541. package/src/shell/app-mobile-nav.tsx +3 -2
  542. package/src/shell/app-sidebar.tsx +1 -1
  543. package/src/shell/aside-docking.ts +1 -1
  544. package/src/shell/index.tsx +61 -20
  545. package/src/shell/page-aside.tsx +47 -23
  546. package/src/shell/page-bar.tsx +3 -3
  547. package/src/shell/page-title.tsx +1 -1
  548. package/src/state.ts +11 -37
  549. package/src/styles.css +107 -7
  550. package/src/surfaces.ts +56 -151
  551. package/src/{data → table}/data-table-state.ts +5 -4
  552. package/src/{data → table}/data-table.tsx +170 -161
  553. package/src/table/index.ts +18 -0
  554. package/src/table/table-features.ts +102 -0
  555. package/src/telemetry.ts +110 -0
  556. package/src/theme/index.ts +22 -47
  557. package/src/theme/theme-toggle.module.css +30 -0
  558. package/src/theme/theme-toggle.module.css.d.ts +10 -0
  559. package/src/theme/theme-toggle.tsx +15 -43
  560. package/src/theme-lab/derive-controls.tsx +5 -3
  561. package/src/theme-lab/derive-state.ts +2 -1
  562. package/src/tokens/core.ts +1163 -0
  563. package/src/tokens/derive.ts +1 -1
  564. package/src/tokens/index.ts +14 -1181
  565. package/src/tokens/palette.ts +3 -7
  566. package/src/vite.ts +3 -3
  567. package/agent/templates/query-client.ts.tpl +0 -26
  568. package/dist/agent/adapter.d.ts +0 -150
  569. package/dist/agent/adapter.d.ts.map +0 -1
  570. package/dist/agent/adapter.js +0 -482
  571. package/dist/agent/use-agent-stream.d.ts +0 -35
  572. package/dist/agent/use-agent-stream.d.ts.map +0 -1
  573. package/dist/agent/use-agent-stream.js +0 -59
  574. package/dist/agent-chat/composer.d.ts.map +0 -1
  575. package/dist/agent-chat/index.d.ts +0 -31
  576. package/dist/agent-chat/index.d.ts.map +0 -1
  577. package/dist/agent-chat/index.js +0 -19
  578. package/dist/agent-chat/message-affordances.d.ts.map +0 -1
  579. package/dist/agent-chat/relative-time.d.ts.map +0 -1
  580. package/dist/agent-chat/thread-detail-panel.d.ts +0 -28
  581. package/dist/agent-chat/thread-detail-panel.d.ts.map +0 -1
  582. package/dist/agent-chat/thread-detail-panel.js +0 -115
  583. package/dist/agent-chat/thread-feed-row.d.ts.map +0 -1
  584. package/dist/agent-chat/thread-feed.d.ts.map +0 -1
  585. package/dist/agent-chat/thread-message.d.ts.map +0 -1
  586. package/dist/agent-chat/thread-outcome-card.d.ts.map +0 -1
  587. package/dist/agent-chat/thread-workspace.d.ts +0 -44
  588. package/dist/agent-chat/thread-workspace.d.ts.map +0 -1
  589. package/dist/agent-chat/thread-workspace.js +0 -153
  590. package/dist/agent-chat/tool-chip.d.ts.map +0 -1
  591. package/dist/agent-chat/use-keyboard-inset.d.ts.map +0 -1
  592. package/dist/agent-chat/virtualize.d.ts.map +0 -1
  593. package/dist/charts/cursor/resolve.d.ts +0 -75
  594. package/dist/charts/cursor/resolve.d.ts.map +0 -1
  595. package/dist/charts/cursor/resolve.js +0 -91
  596. package/dist/charts/cursor/scope.d.ts +0 -20
  597. package/dist/charts/cursor/scope.d.ts.map +0 -1
  598. package/dist/charts/cursor/scope.js +0 -20
  599. package/dist/charts/cursor/store.d.ts +0 -35
  600. package/dist/charts/cursor/store.d.ts.map +0 -1
  601. package/dist/charts/cursor/store.js +0 -25
  602. package/dist/charts/cursor/touch-pin.d.ts +0 -45
  603. package/dist/charts/cursor/touch-pin.d.ts.map +0 -1
  604. package/dist/charts/cursor/touch-pin.js +0 -79
  605. package/dist/charts/hooks/use-discrete-cursor.d.ts +0 -66
  606. package/dist/charts/hooks/use-discrete-cursor.d.ts.map +0 -1
  607. package/dist/charts/hooks/use-discrete-cursor.js +0 -198
  608. package/dist/charts/hooks/use-hysteresis.d.ts +0 -18
  609. package/dist/charts/hooks/use-hysteresis.d.ts.map +0 -1
  610. package/dist/charts/hooks/use-hysteresis.js +0 -12
  611. package/dist/charts/hooks/useBandPlot.d.ts +0 -145
  612. package/dist/charts/hooks/useBandPlot.d.ts.map +0 -1
  613. package/dist/charts/hooks/useBandPlot.js +0 -178
  614. package/dist/charts/hooks/useChartCursor.d.ts +0 -75
  615. package/dist/charts/hooks/useChartCursor.d.ts.map +0 -1
  616. package/dist/charts/hooks/useChartCursor.js +0 -200
  617. package/dist/charts/hooks/useChartSize.d.ts +0 -29
  618. package/dist/charts/hooks/useChartSize.d.ts.map +0 -1
  619. package/dist/charts/hooks/useChartSize.js +0 -8
  620. package/dist/charts/kinds/BandStrip.d.ts +0 -132
  621. package/dist/charts/kinds/BandStrip.d.ts.map +0 -1
  622. package/dist/charts/kinds/BandStrip.js +0 -241
  623. package/dist/charts/kinds/Bars.d.ts +0 -141
  624. package/dist/charts/kinds/Bars.d.ts.map +0 -1
  625. package/dist/charts/kinds/Bars.js +0 -343
  626. package/dist/charts/kinds/Donut.d.ts +0 -77
  627. package/dist/charts/kinds/Donut.d.ts.map +0 -1
  628. package/dist/charts/kinds/Donut.js +0 -245
  629. package/dist/charts/kinds/DualPanel.d.ts +0 -111
  630. package/dist/charts/kinds/DualPanel.d.ts.map +0 -1
  631. package/dist/charts/kinds/DualPanel.js +0 -471
  632. package/dist/charts/kinds/Heatmap.d.ts +0 -85
  633. package/dist/charts/kinds/Heatmap.d.ts.map +0 -1
  634. package/dist/charts/kinds/Heatmap.js +0 -243
  635. package/dist/charts/kinds/MirroredBars.d.ts +0 -98
  636. package/dist/charts/kinds/MirroredBars.d.ts.map +0 -1
  637. package/dist/charts/kinds/MirroredBars.js +0 -292
  638. package/dist/charts/kinds/MultiLine.d.ts +0 -87
  639. package/dist/charts/kinds/MultiLine.d.ts.map +0 -1
  640. package/dist/charts/kinds/MultiLine.js +0 -176
  641. package/dist/charts/kinds/StackedArea.d.ts +0 -73
  642. package/dist/charts/kinds/StackedArea.d.ts.map +0 -1
  643. package/dist/charts/kinds/StackedArea.js +0 -135
  644. package/dist/charts/kinds/ZonedLine.d.ts +0 -97
  645. package/dist/charts/kinds/ZonedLine.d.ts.map +0 -1
  646. package/dist/charts/kinds/ZonedLine.js +0 -155
  647. package/dist/charts/kinds/band-chrome.d.ts +0 -31
  648. package/dist/charts/kinds/band-chrome.d.ts.map +0 -1
  649. package/dist/charts/kinds/band-chrome.js +0 -71
  650. package/dist/charts/kinds/donut-layout.d.ts +0 -47
  651. package/dist/charts/kinds/donut-layout.d.ts.map +0 -1
  652. package/dist/charts/kinds/donut-layout.js +0 -52
  653. package/dist/charts/layout/auto-margin.d.ts +0 -122
  654. package/dist/charts/layout/auto-margin.d.ts.map +0 -1
  655. package/dist/charts/layout/auto-margin.js +0 -81
  656. package/dist/charts/layout/end-labels.d.ts +0 -64
  657. package/dist/charts/layout/end-labels.d.ts.map +0 -1
  658. package/dist/charts/layout/end-labels.js +0 -76
  659. package/dist/charts/layout/log-ticks.d.ts +0 -27
  660. package/dist/charts/layout/log-ticks.d.ts.map +0 -1
  661. package/dist/charts/layout/log-ticks.js +0 -42
  662. package/dist/charts/primitives/AreaGradient.d.ts +0 -18
  663. package/dist/charts/primitives/AreaGradient.d.ts.map +0 -1
  664. package/dist/charts/primitives/AreaGradient.js +0 -13
  665. package/dist/charts/primitives/Axes.d.ts +0 -83
  666. package/dist/charts/primitives/Axes.d.ts.map +0 -1
  667. package/dist/charts/primitives/Axes.js +0 -168
  668. package/dist/charts/primitives/CartesianChart.d.ts +0 -257
  669. package/dist/charts/primitives/CartesianChart.d.ts.map +0 -1
  670. package/dist/charts/primitives/CartesianChart.js +0 -549
  671. package/dist/charts/primitives/ChartCard.d.ts +0 -66
  672. package/dist/charts/primitives/ChartCard.d.ts.map +0 -1
  673. package/dist/charts/primitives/ChartCard.js +0 -143
  674. package/dist/charts/primitives/ChartFrame.d.ts +0 -156
  675. package/dist/charts/primitives/ChartFrame.d.ts.map +0 -1
  676. package/dist/charts/primitives/ChartFrame.js +0 -241
  677. package/dist/charts/primitives/ChartLegend.d.ts +0 -67
  678. package/dist/charts/primitives/ChartLegend.d.ts.map +0 -1
  679. package/dist/charts/primitives/ChartLegend.js +0 -292
  680. package/dist/charts/primitives/ChartPending.d.ts +0 -113
  681. package/dist/charts/primitives/ChartPending.d.ts.map +0 -1
  682. package/dist/charts/primitives/ChartPending.js +0 -117
  683. package/dist/charts/primitives/ChartTooltip.d.ts +0 -59
  684. package/dist/charts/primitives/ChartTooltip.d.ts.map +0 -1
  685. package/dist/charts/primitives/ChartTooltip.js +0 -197
  686. package/dist/charts/primitives/Crosshair.d.ts +0 -26
  687. package/dist/charts/primitives/Crosshair.d.ts.map +0 -1
  688. package/dist/charts/primitives/Crosshair.js +0 -23
  689. package/dist/charts/primitives/HatchPattern.d.ts +0 -31
  690. package/dist/charts/primitives/HatchPattern.d.ts.map +0 -1
  691. package/dist/charts/primitives/HatchPattern.js +0 -31
  692. package/dist/charts/primitives/HoverOverlay.d.ts +0 -45
  693. package/dist/charts/primitives/HoverOverlay.d.ts.map +0 -1
  694. package/dist/charts/primitives/HoverOverlay.js +0 -72
  695. package/dist/charts/primitives/HoverOverlay.module.css +0 -13
  696. package/dist/charts/primitives/HoverOverlay.module.css.d.ts +0 -8
  697. package/dist/charts/primitives/XZoneRects.d.ts +0 -54
  698. package/dist/charts/primitives/XZoneRects.d.ts.map +0 -1
  699. package/dist/charts/primitives/XZoneRects.js +0 -65
  700. package/dist/charts/primitives/ZoneRects.d.ts +0 -38
  701. package/dist/charts/primitives/ZoneRects.d.ts.map +0 -1
  702. package/dist/charts/primitives/ZoneRects.js +0 -30
  703. package/dist/charts/primitives/chart-card-context.d.ts +0 -33
  704. package/dist/charts/primitives/chart-card-context.d.ts.map +0 -1
  705. package/dist/charts/primitives/chart-card-context.js +0 -18
  706. package/dist/charts/primitives/chart-frame-layout.d.ts +0 -185
  707. package/dist/charts/primitives/chart-frame-layout.d.ts.map +0 -1
  708. package/dist/charts/primitives/chart-frame-layout.js +0 -175
  709. package/dist/charts/primitives/chart-layout.d.ts +0 -112
  710. package/dist/charts/primitives/chart-layout.d.ts.map +0 -1
  711. package/dist/charts/primitives/chart-layout.js +0 -175
  712. package/dist/charts/primitives/chart-tier.d.ts +0 -16
  713. package/dist/charts/primitives/chart-tier.d.ts.map +0 -1
  714. package/dist/charts/primitives/chart-tier.js +0 -44
  715. package/dist/charts/primitives/legend-disclosure.d.ts +0 -27
  716. package/dist/charts/primitives/legend-disclosure.d.ts.map +0 -1
  717. package/dist/charts/primitives/legend-disclosure.js +0 -153
  718. package/dist/charts/series.d.ts +0 -189
  719. package/dist/charts/series.d.ts.map +0 -1
  720. package/dist/charts/series.js +0 -84
  721. package/dist/charts/sparklines/BarSparkline.d.ts +0 -37
  722. package/dist/charts/sparklines/BarSparkline.d.ts.map +0 -1
  723. package/dist/charts/sparklines/BarSparkline.js +0 -53
  724. package/dist/charts/sparklines/LineSparkline.d.ts +0 -15
  725. package/dist/charts/sparklines/LineSparkline.d.ts.map +0 -1
  726. package/dist/charts/sparklines/LineSparkline.js +0 -51
  727. package/dist/charts/sparklines/index.d.ts +0 -3
  728. package/dist/charts/sparklines/index.d.ts.map +0 -1
  729. package/dist/charts/sparklines/index.js +0 -6
  730. package/dist/charts/utils/domain.d.ts +0 -35
  731. package/dist/charts/utils/domain.d.ts.map +0 -1
  732. package/dist/charts/utils/domain.js +0 -12
  733. package/dist/charts/utils/format.d.ts +0 -8
  734. package/dist/charts/utils/format.d.ts.map +0 -1
  735. package/dist/charts/utils/format.js +0 -20
  736. package/dist/charts/utils/measure-text.d.ts +0 -25
  737. package/dist/charts/utils/measure-text.d.ts.map +0 -1
  738. package/dist/charts/utils/measure-text.js +0 -64
  739. package/dist/charts/utils/ticks.d.ts +0 -103
  740. package/dist/charts/utils/ticks.d.ts.map +0 -1
  741. package/dist/charts/utils/ticks.js +0 -92
  742. package/dist/commands/overlays-mount.d.ts +0 -122
  743. package/dist/commands/overlays-mount.d.ts.map +0 -1
  744. package/dist/commands/overlays-mount.js +0 -125
  745. package/dist/commands/useCommandHotkeys.d.ts +0 -14
  746. package/dist/commands/useCommandHotkeys.d.ts.map +0 -1
  747. package/dist/commands/useCommandHotkeys.js +0 -45
  748. package/dist/content/article-actions.d.ts +0 -39
  749. package/dist/content/article-actions.d.ts.map +0 -1
  750. package/dist/content/article-actions.js +0 -28
  751. package/dist/content/article-card.d.ts +0 -58
  752. package/dist/content/article-card.d.ts.map +0 -1
  753. package/dist/content/article-card.js +0 -79
  754. package/dist/content/article-card.module.css +0 -111
  755. package/dist/content/mdx.d.ts +0 -20
  756. package/dist/content/mdx.d.ts.map +0 -1
  757. package/dist/content/mdx.js +0 -64
  758. package/dist/controls/compare-filter.d.ts +0 -42
  759. package/dist/controls/compare-filter.d.ts.map +0 -1
  760. package/dist/controls/compare-filter.js +0 -38
  761. package/dist/controls/slider-control.d.ts +0 -36
  762. package/dist/controls/slider-control.d.ts.map +0 -1
  763. package/dist/controls/slider-control.js +0 -64
  764. package/dist/dashboard/widget-grid.d.ts +0 -27
  765. package/dist/dashboard/widget-grid.d.ts.map +0 -1
  766. package/dist/dashboard/widget-grid.js +0 -40
  767. package/dist/dashboard/widget-grid.module.css +0 -69
  768. package/dist/data/data-table-state.d.ts.map +0 -1
  769. package/dist/data/data-table.d.ts.map +0 -1
  770. package/dist/data/query-branch.d.ts.map +0 -1
  771. package/dist/data/table.d.ts +0 -24
  772. package/dist/data/table.d.ts.map +0 -1
  773. package/dist/data/virtual-list.d.ts +0 -133
  774. package/dist/data/virtual-list.d.ts.map +0 -1
  775. package/dist/data/virtual-list.js +0 -157
  776. package/dist/data/virtual.d.ts +0 -21
  777. package/dist/data/virtual.d.ts.map +0 -1
  778. package/dist/data/virtual.js +0 -4
  779. package/dist/forms/FormErrorSummary.d.ts +0 -30
  780. package/dist/forms/FormErrorSummary.d.ts.map +0 -1
  781. package/dist/forms/FormErrorSummary.js +0 -30
  782. package/dist/forms/use-field-array.d.ts +0 -60
  783. package/dist/forms/use-field-array.d.ts.map +0 -1
  784. package/dist/forms/use-field-array.js +0 -20
  785. package/dist/forms/use-form-draft.d.ts +0 -76
  786. package/dist/forms/use-form-draft.d.ts.map +0 -1
  787. package/dist/forms/use-form-draft.js +0 -92
  788. package/dist/forms/use-form-submit.d.ts +0 -88
  789. package/dist/forms/use-form-submit.d.ts.map +0 -1
  790. package/dist/forms/use-form-submit.js +0 -63
  791. package/dist/notifications/bell.d.ts.map +0 -1
  792. package/dist/notifications/center.d.ts.map +0 -1
  793. package/dist/notifications/define-notifications.d.ts.map +0 -1
  794. package/dist/notifications/index.d.ts +0 -40
  795. package/dist/notifications/index.d.ts.map +0 -1
  796. package/dist/notifications/index.js +0 -54
  797. package/dist/notifications/mount-guard.d.ts +0 -8
  798. package/dist/notifications/mount-guard.d.ts.map +0 -1
  799. package/dist/notifications/mount-guard.js +0 -18
  800. package/dist/notifications/notify.d.ts +0 -149
  801. package/dist/notifications/notify.d.ts.map +0 -1
  802. package/dist/notifications/notify.js +0 -171
  803. package/dist/notifications/store.d.ts.map +0 -1
  804. package/dist/provider/connectivity-indicator.d.ts +0 -13
  805. package/dist/provider/connectivity-indicator.d.ts.map +0 -1
  806. package/dist/provider/connectivity-indicator.js +0 -205
  807. package/dist/provider/connectivity-provider.d.ts +0 -7
  808. package/dist/provider/connectivity-provider.d.ts.map +0 -1
  809. package/dist/provider/connectivity-provider.js +0 -128
  810. package/dist/provider/connectivity-types.d.ts +0 -36
  811. package/dist/provider/connectivity-types.d.ts.map +0 -1
  812. package/dist/provider/use-connectivity.d.ts +0 -3
  813. package/dist/provider/use-connectivity.d.ts.map +0 -1
  814. package/dist/provider/use-connectivity.js +0 -14
  815. package/dist/query-devtools.d.ts +0 -21
  816. package/dist/query-devtools.d.ts.map +0 -1
  817. package/dist/query-devtools.js +0 -13
  818. package/src/agent/adapter.ts +0 -945
  819. package/src/agent/use-agent-stream.ts +0 -154
  820. package/src/agent-chat/index.ts +0 -54
  821. package/src/agent-chat/thread-detail-panel.tsx +0 -201
  822. package/src/agent-chat/thread-workspace.tsx +0 -279
  823. package/src/charts/cursor/resolve.ts +0 -198
  824. package/src/charts/cursor/scope.tsx +0 -35
  825. package/src/charts/cursor/store.ts +0 -58
  826. package/src/charts/cursor/touch-pin.ts +0 -138
  827. package/src/charts/hooks/use-discrete-cursor.ts +0 -335
  828. package/src/charts/hooks/use-hysteresis.ts +0 -26
  829. package/src/charts/hooks/useBandPlot.ts +0 -383
  830. package/src/charts/hooks/useChartCursor.ts +0 -367
  831. package/src/charts/hooks/useChartSize.ts +0 -35
  832. package/src/charts/kinds/BandStrip.tsx +0 -449
  833. package/src/charts/kinds/Bars.tsx +0 -589
  834. package/src/charts/kinds/Donut.tsx +0 -368
  835. package/src/charts/kinds/DualPanel.tsx +0 -712
  836. package/src/charts/kinds/Heatmap.tsx +0 -396
  837. package/src/charts/kinds/MirroredBars.tsx +0 -469
  838. package/src/charts/kinds/MultiLine.tsx +0 -322
  839. package/src/charts/kinds/StackedArea.tsx +0 -267
  840. package/src/charts/kinds/ZonedLine.tsx +0 -301
  841. package/src/charts/kinds/band-chrome.tsx +0 -91
  842. package/src/charts/kinds/donut-layout.ts +0 -82
  843. package/src/charts/layout/auto-margin.ts +0 -242
  844. package/src/charts/layout/end-labels.ts +0 -135
  845. package/src/charts/layout/log-ticks.ts +0 -90
  846. package/src/charts/primitives/AreaGradient.tsx +0 -26
  847. package/src/charts/primitives/Axes.tsx +0 -271
  848. package/src/charts/primitives/CartesianChart.tsx +0 -1002
  849. package/src/charts/primitives/ChartCard.tsx +0 -280
  850. package/src/charts/primitives/ChartFrame.tsx +0 -493
  851. package/src/charts/primitives/ChartLegend.tsx +0 -422
  852. package/src/charts/primitives/ChartPending.tsx +0 -243
  853. package/src/charts/primitives/ChartTooltip.tsx +0 -265
  854. package/src/charts/primitives/Crosshair.tsx +0 -43
  855. package/src/charts/primitives/HatchPattern.tsx +0 -55
  856. package/src/charts/primitives/HoverOverlay.module.css +0 -13
  857. package/src/charts/primitives/HoverOverlay.module.css.d.ts +0 -8
  858. package/src/charts/primitives/HoverOverlay.tsx +0 -109
  859. package/src/charts/primitives/XZoneRects.tsx +0 -132
  860. package/src/charts/primitives/ZoneRects.tsx +0 -67
  861. package/src/charts/primitives/chart-card-context.ts +0 -46
  862. package/src/charts/primitives/chart-frame-layout.ts +0 -430
  863. package/src/charts/primitives/chart-layout.ts +0 -318
  864. package/src/charts/primitives/chart-tier.tsx +0 -76
  865. package/src/charts/primitives/legend-disclosure.tsx +0 -178
  866. package/src/charts/series.ts +0 -276
  867. package/src/charts/sparklines/BarSparkline.tsx +0 -94
  868. package/src/charts/sparklines/LineSparkline.tsx +0 -71
  869. package/src/charts/sparklines/index.ts +0 -2
  870. package/src/charts/theme.tsx +0 -53
  871. package/src/charts/utils/domain.ts +0 -41
  872. package/src/charts/utils/format.ts +0 -19
  873. package/src/charts/utils/measure-text.ts +0 -109
  874. package/src/charts/utils/ticks.ts +0 -239
  875. package/src/commands/overlays-mount.tsx +0 -333
  876. package/src/commands/useCommandHotkeys.ts +0 -114
  877. package/src/content/article-actions.ts +0 -72
  878. package/src/content/article-card.module.css +0 -111
  879. package/src/content/article-card.tsx +0 -174
  880. package/src/content/mdx.tsx +0 -127
  881. package/src/controls/compare-filter.tsx +0 -81
  882. package/src/controls/slider-control.tsx +0 -159
  883. package/src/dashboard/widget-grid.module.css +0 -69
  884. package/src/dashboard/widget-grid.tsx +0 -146
  885. package/src/data/table.ts +0 -39
  886. package/src/data/virtual-list.tsx +0 -356
  887. package/src/data/virtual.ts +0 -26
  888. package/src/forms/FormErrorSummary.tsx +0 -71
  889. package/src/forms/use-field-array.ts +0 -94
  890. package/src/forms/use-form-draft.ts +0 -262
  891. package/src/forms/use-form-submit.ts +0 -199
  892. package/src/notifications/index.ts +0 -88
  893. package/src/notifications/mount-guard.ts +0 -38
  894. package/src/notifications/notify.tsx +0 -379
  895. package/src/provider/connectivity-indicator.tsx +0 -267
  896. package/src/provider/connectivity-provider.tsx +0 -189
  897. package/src/provider/connectivity-types.ts +0 -42
  898. package/src/provider/use-connectivity.ts +0 -26
  899. package/src/query-devtools.tsx +0 -40
  900. /package/dist/{agent-chat → agent/chat}/message-affordances.js +0 -0
  901. /package/dist/{agent-chat → agent/chat}/relative-time.d.ts +0 -0
  902. /package/dist/{agent-chat → agent/chat}/relative-time.js +0 -0
  903. /package/dist/{agent-chat → agent/chat}/use-keyboard-inset.d.ts +0 -0
  904. /package/dist/{agent-chat → agent/chat}/virtualize.js +0 -0
  905. /package/dist/{notifications → commands/notifications}/bell.js +0 -0
  906. /package/dist/{notifications → commands/notifications}/define-notifications.js +0 -0
  907. /package/dist/{provider/connectivity-types.js → controls/uncontrolled.js} +0 -0
  908. /package/dist/{data → table}/data-table.module.css +0 -0
  909. /package/dist/{data → table}/data-table.module.css.d.ts +0 -0
  910. /package/dist/{data → table}/query-branch.d.ts +0 -0
  911. /package/dist/{data → table}/query-branch.js +0 -0
  912. /package/src/{agent-chat → agent/chat}/relative-time.ts +0 -0
  913. /package/src/{data → table}/data-table.module.css +0 -0
  914. /package/src/{data → table}/data-table.module.css.d.ts +0 -0
  915. /package/src/{data → table}/query-branch.ts +0 -0
@@ -17,9 +17,9 @@
17
17
  * scope has to be spelled. The three boundary rules deliberately honour neither — see their own
18
18
  * comment for why.
19
19
  *
20
- * ── The two agent-chat guard rules (`agent-resume-guard` / `agent-no-raw-usechat`) + `ai-sdk-major` ──
20
+ * ── The agent-chat guard rule (`agent-no-raw-usechat`) + `ai-sdk-major` ──
21
21
  * These are also correctness/architecture boundaries, not design guidance, so they do NOT honour
22
- * `theme-allow`. All three instead honour their own line-comment escape, `basalt-agent-allow`
22
+ * `theme-allow`. Both instead honour their own line-comment escape, `basalt-agent-allow`
23
23
  * (`hasAgentAllow`, a copy of `hasThemeAllow` with a different needle) — deliberately a separate
24
24
  * token, so a color exemption can never double as a license to switch off a streaming guard or an
25
25
  * intentional `ai` major skew. `ai-sdk-major`'s escape is line-level and per-import, distinct from
@@ -30,7 +30,7 @@
30
30
  * filesystem (reads ancestor `package.json`s, memoized per directory so a lint run does one walk
31
31
  * per directory, not per file) — every other rule here is pure AST + filename-string matching.
32
32
  *
33
- * ── The three boundary rules (`visx-boundary` / `visx-tooltip` / `token-layer-boundary`) ─────────
33
+ * ── The three boundary rules (`chart-engine-boundary` / `chart-host-single` / `token-layer-boundary`) ─────────
34
34
  * These used to be one bundled `import-boundary` rule sharing a single on/off toggle — that meant
35
35
  * a consumer disabling the one check they disagreed with silently dropped the other two as well.
36
36
  * Each is now its own rule id with its own `meta`/`create`/registration, so a consumer can only
@@ -66,8 +66,7 @@
66
66
  * per-glob last-writer-wins for `no-restricted-imports` — a base ban does NOT merge into an
67
67
  * override glob, so any ban that must also hold inside an override is duplicated into that
68
68
  * override, AND a consumer's own `no-restricted-imports` override on an overlapping glob silently
69
- * REPLACES basalt's ban rather than merging with it. That is exactly why the `@visx/*`-only-in-
70
- * charts and token-layer charts/tokens boundaries live in plugin rules below instead of
69
+ * REPLACES basalt's ban rather than merging with it. That is exactly why the chart-engine and token-layer charts/tokens boundaries live in plugin rules below instead of
71
70
  * `no-restricted-imports`: a jsPlugin rule has its own rule id, so a consumer can only turn it off
72
71
  * explicitly, by name — never silently. `configs/oxlint.json` is generated from `SURFACES` by
73
72
  * `scripts/gen-oxlint.ts` (`--check` is the CI drift gate) — its top-level keys are limited to what
@@ -86,8 +85,7 @@ import { fileURLToPath } from 'node:url'
86
85
  * `check-theme --audit-allows`, never as a typo.
87
86
  *
88
87
  * - `unframed-chart` (regex: a `<ChartLegend items={[...]}>` array literal) →
89
- * `basalt/chart-legend-literal`, which also catches the `.map()`-over-a-non-series form the
90
- * regex never could reach.
88
+ * `basalt/chart-legend-literal`, itself retired in 2.0 with the public `ChartLegend`.
91
89
  * - `duplicate-notifications-mount` — its only case, `<BasaltNotifications />` mounted beside
92
90
  * `<BasaltOverlays notifications>`, is unreachable now that `BasaltNotifications` no longer
93
91
  * exists as a standalone export; the rule had nothing left to fire on.
@@ -98,8 +96,21 @@ import { fileURLToPath } from 'node:url'
98
96
  */
99
97
  export const RETIRED_RULE_IDS = new Set([
100
98
  'unframed-chart',
99
+ // 2.0 (V2-SPEC § 9.8): the visx lint/guard layer, replaced by `chart-engine-boundary` or made
100
+ // unrepresentable by the rung.
101
+ 'visx-boundary',
102
+ 'visx-tooltip',
103
+ 'hand-rolled-plot',
104
+ 'chart-legend-literal',
105
+ 'raw-visx-axis',
106
+ 'chart-missing-aria-label',
101
107
  'duplicate-notifications-mount',
102
108
  'query-dual-import',
109
+ // 2.0 (V2-SPEC § 3): the concern is unrepresentable.
110
+ 'provider-above-router', // BasaltProvider mounts RouterProvider
111
+ 'page-bar-budget', // PageBarActions / SectionActions tuple types
112
+ 'agent-resume-guard', // resumeStream only exists on useChat/Chat, banned by agent-no-raw-usechat
113
+ 'deprecated-export', // 2.0.0 ships no deprecations; returns with the first 2.x one
103
114
  ])
104
115
 
105
116
  /**
@@ -119,8 +130,6 @@ export const KNOWN_RULE_IDS = new Set([
119
130
  'raw-size-literal',
120
131
  'card-inset',
121
132
  'chart-in-raw-surface',
122
- 'hand-rolled-plot',
123
- 'chart-legend-literal',
124
133
  'hand-rolled-shell',
125
134
  'shadow-basalt-export',
126
135
  'raw-scroll-container',
@@ -128,25 +137,23 @@ export const KNOWN_RULE_IDS = new Set([
128
137
  'control-outside-home',
129
138
  'bound-control-outside-home',
130
139
  'control-size-literal',
131
- 'page-bar-budget',
132
140
  'responsive-twin',
133
141
  'search-literal-link',
134
142
  'use-search-from-literal',
135
- 'provider-above-router',
136
143
  'query-dual-import',
137
144
  'query-fn-unwrap',
138
- 'deprecated-export',
139
145
  'forms-field-key',
140
146
  // `in-body-page-title` is BOTH a plugin rule and a guard kind — one id, two lanes, so one
141
147
  // annotation waives both. It is listed once, below, with the guard kinds.
142
- 'visx-boundary',
143
- 'visx-tooltip',
148
+ 'chart-engine-boundary',
149
+ 'chart-host-single',
144
150
  'token-layer-boundary',
145
151
  'no-import-meta-env',
146
152
  'raw-breakpoint',
147
- // These three honour `basalt-agent-allow`, never `theme-allow` — but they are real ids, so an
153
+ 'no-dev-helper',
154
+ 'route-view-transition',
155
+ // These two honour `basalt-agent-allow`, never `theme-allow` — but they are real ids, so an
148
156
  // annotation naming one must parse as a (useless) scoped annotation rather than as prose.
149
- 'agent-resume-guard',
150
157
  'agent-no-raw-usechat',
151
158
  'ai-sdk-major',
152
159
  // src/guard kinds
@@ -163,9 +170,7 @@ export const KNOWN_RULE_IDS = new Set([
163
170
  'raw-html-layout',
164
171
  'inline-spacing',
165
172
  'inline-display',
166
- 'raw-visx-axis',
167
173
  'raw-motion-value',
168
- 'chart-missing-aria-label',
169
174
  'raw-form-control',
170
175
  'sub-16-input-font',
171
176
  'raw-font-family',
@@ -665,11 +670,9 @@ const CHART_TAGS = new Set([
665
670
  'DualPanel',
666
671
  'Heatmap',
667
672
  'MirroredBars',
668
- 'MultiLine',
673
+ 'Lines',
669
674
  'StackedArea',
670
- 'ZonedLine',
671
- 'BarSparkline',
672
- 'LineSparkline',
675
+ 'Sparkline',
673
676
  ])
674
677
 
675
678
  const CHART_IN_RAW_SURFACE_MESSAGE =
@@ -767,7 +770,7 @@ const RAW_SCROLL_CONTAINER_HOME_MESSAGE =
767
770
  *
768
771
  * Scoped by ANCESTRY only, with no hoisted-binding lookup: a style object hoisted to a `const` has
769
772
  * no JSX ancestor at all, so a hoisted `{ overflowX: 'auto' }` handed to a slot is a known false
770
- * negative here — the same gap `chart-legend-literal` documents for a hoisted `items` array, and
773
+ * negative here — the same gap the retired `chart-legend-literal` had for a hoisted `items` array, and
771
774
  * for the same reason (following it means local flow analysis for a bypass nobody reaches by
772
775
  * accident).
773
776
  */
@@ -865,12 +868,6 @@ const rawScrollContainer = {
865
868
 
866
869
  // Message bodies mirror the wording that used to live in the shipped `no-restricted-imports`
867
870
  // overrides (configs/oxlint.json) verbatim, so the DX doesn't regress.
868
- const VISX_BOUNDARY_MESSAGE =
869
- 'Direct @visx/* imports are only allowed inside the charts boundary (**/charts/**). ' +
870
- '(basalt/visx-boundary)'
871
- const VISX_TOOLTIP_MESSAGE =
872
- 'Use ChartTooltip + TooltipHeader/Row/Body from basalt-ui charts primitives. ' +
873
- '(basalt/visx-tooltip)'
874
871
  const TOKEN_LAYER_BOUNDARY_MESSAGES = {
875
872
  charts:
876
873
  'charts must stay upstream-of-Mantine (no Mantine imports) — read color via --vx-* tokens, ' +
@@ -923,58 +920,141 @@ function boundaryVisitor(reportFor) {
923
920
  ExportAllDeclaration(node) {
924
921
  reportFor(node, exportSource(node))
925
922
  },
923
+ // CommonJS `require('<spec>')` with a literal specifier; a computed one cannot be judged.
924
+ CallExpression(node) {
925
+ const [arg] = node.arguments
926
+ if (node.callee?.type !== 'Identifier' || node.callee.name !== 'require') return
927
+ if (arg?.type === 'Literal') reportFor(node, arg.value)
928
+ },
926
929
  }
927
930
  }
928
931
 
929
- // Rule 5 — visx-boundary: @visx/* only allowed inside a `charts` path segment. Matching any path
930
- // segment named `charts` is deliberately permissive, not restrictive — it only ever widens where
931
- // visx is allowed, never narrows where it's flagged elsewhere. "Your visx lives in a charts/ dir"
932
- // is the intended convention.
932
+ // Rule 5 — chart-engine-boundary (V2-SPEC § 9.8): `@visx/*` is banned everywhere; outside basalt's
933
+ // own `src/`, `@tanstack/charts` is an ALLOWLIST of mark/scale/transform subpaths. The bare root
934
+ // re-exports `defineChart`, `createChartRuntime`, `mountChart`, `renderChartSvg` and `createMark`
935
+ // (a second chart runtime), `/react` is the host (basalt owns the one), and any NEW engine
936
+ // subpath is an error until `oxlint-plugin.test.ts` classifies it against the engine's `exports`.
937
+ // Inside `packages/basalt-ui/src/` only the visx ban applies — `basalt/chart-host-single` polices
938
+ // the one place `/react` may be imported. Deliberately NO `hasThemeAllow` escape (see
939
+ // token-layer-boundary below).
940
+ const visxBanned = (source) => source === '@visx' || source.startsWith('@visx/')
941
+
942
+ /** Exact `@tanstack/charts` subpaths a consumer may import. Kept in step with V2-SPEC § 9.8. */
943
+ export const CHART_ENGINE_ALLOWED_SUBPATHS = new Set([
944
+ 'line',
945
+ 'area',
946
+ 'area-x',
947
+ 'bar',
948
+ 'rect',
949
+ 'rule',
950
+ 'dot',
951
+ 'tick',
952
+ 'text',
953
+ 'vector',
954
+ 'link',
955
+ 'arrow',
956
+ 'difference',
957
+ 'stack',
958
+ 'mark/decorative',
959
+ 'd3/shape',
960
+ 'types',
961
+ ])
962
+
963
+ /** Allowed subpath FAMILIES: `scales/*` and `transform/*` (the bare `transform` is not one). */
964
+ export const CHART_ENGINE_ALLOWED_PREFIXES = ['scales/', 'transform/']
965
+
966
+ /**
967
+ * True when `subpath` (the part after `@tanstack/charts/`) is on the allowlist. A `.`, `..` or empty
968
+ * segment is rejected first: `scales/../react` starts with an allowed family but resolves outside
969
+ * it. Within a family the length guard rejects the bare prefix (`scales/`), which names no module.
970
+ */
971
+ export function isAllowedChartEngineSubpath(subpath) {
972
+ if (subpath.split('/').some((segment) => segment === '' || segment === '.' || segment === '..'))
973
+ return false
974
+ return (
975
+ CHART_ENGINE_ALLOWED_SUBPATHS.has(subpath) ||
976
+ CHART_ENGINE_ALLOWED_PREFIXES.some(
977
+ (prefix) => subpath.startsWith(prefix) && subpath.length > prefix.length,
978
+ )
979
+ )
980
+ }
981
+
982
+ const CHART_ENGINE_ALLOWED_LIST = [
983
+ ...CHART_ENGINE_ALLOWED_SUBPATHS,
984
+ ...CHART_ENGINE_ALLOWED_PREFIXES.map((p) => `${p}*`),
985
+ ].join(', ')
986
+ const CHART_ENGINE_MESSAGE =
987
+ "Import chart kinds from 'basalt-ui/charts', or the rung's `marks` (CartesianChart) for custom " +
988
+ 'marks — only these @tanstack/charts subpaths are allowed: ' +
989
+ CHART_ENGINE_ALLOWED_LIST +
990
+ ". The bare root, /react and every other subpath are basalt's to own. (basalt/chart-engine-boundary)"
991
+ const VISX_BANNED_MESSAGE =
992
+ "@visx/* is gone in 2.0 — use 'basalt-ui/charts' kinds, or the rung's `marks` built from the " +
993
+ 'allowed @tanstack/charts subpaths. (basalt/chart-engine-boundary)'
994
+
995
+ /** True for basalt's own package source — the one place the engine allowlist does not apply. */
996
+ function inBasaltSource(filename) {
997
+ return filename.replaceAll('\\', '/').includes('/packages/basalt-ui/src/')
998
+ }
999
+
933
1000
  // Ships: error
934
- const visxBoundary = {
1001
+ const chartEngineBoundary = {
935
1002
  meta: {
936
- type: 'suggestion',
1003
+ type: 'problem',
937
1004
  docs: {
938
- description: 'Disallow direct @visx/* imports outside a charts/ directory.',
1005
+ description:
1006
+ 'Ban @visx/* everywhere and restrict @tanstack/charts to the allowed mark subpaths.',
939
1007
  },
940
1008
  schema: [],
941
1009
  },
942
1010
  create(context) {
943
- const inCharts = hasPathSegment(getFilename(context), 'charts')
944
-
945
- // Deliberately NO `hasThemeAllow` escape here (unlike the four design-guard rules above).
946
- // `theme-allow` exists so the theme/palette source can opt out of DESIGN guidance (raw font
947
- // sizes, raw colors) — it is not a license to punch through an ARCHITECTURE boundary. The whole
948
- // point of moving this check into its own plugin rule instead of `no-restricted-imports` was
949
- // that it can only be turned off explicitly, by rule id (see the file-header comment); a stray
950
- // `theme-allow` comment (which also matches the line above the flagged node) must not silently
951
- // bypass it. Do not "restore consistency" by adding it back.
1011
+ const ownSource = inBasaltSource(getFilename(context))
1012
+
952
1013
  function reportFor(node, source) {
953
- if (typeof source !== 'string' || !source.startsWith('@visx/')) return
954
- if (source === '@visx/tooltip') return // basalt/visx-tooltip owns this one, everywhere
955
- if (!inCharts) context.report({ node, message: VISX_BOUNDARY_MESSAGE })
1014
+ if (typeof source !== 'string') return
1015
+ if (visxBanned(source)) {
1016
+ context.report({ node, message: VISX_BANNED_MESSAGE })
1017
+ return
1018
+ }
1019
+ if (ownSource) return
1020
+ if (source !== '@tanstack/charts' && !source.startsWith('@tanstack/charts/')) return
1021
+ const subpath = source.slice('@tanstack/charts/'.length)
1022
+ if (source !== '@tanstack/charts' && isAllowedChartEngineSubpath(subpath)) return
1023
+ context.report({ node, message: CHART_ENGINE_MESSAGE })
956
1024
  }
957
1025
 
958
1026
  return boundaryVisitor(reportFor)
959
1027
  },
960
1028
  }
961
1029
 
962
- // Rule 6 — visx-tooltip: @visx/tooltip banned everywhere, including inside charts. Fires ahead of
963
- // (and instead of) basalt/visx-boundary for that one specifier — basalt's own src/ never imports
964
- // @visx/tooltip, so no theme-allow-style exemption is needed here either.
965
- // Ships: error
966
- const visxTooltip = {
1030
+ // Rule 6 — chart-host-single (V2-SPEC § 9.8): `@tanstack/charts/react` is imported by
1031
+ // `packages/basalt-ui/src/charts/host.tsx` alone, so measured sizing, legend and state chrome live
1032
+ // in one component. Repo-local only, like token-layer-boundary — never in the consumer preset.
1033
+ const CHART_HOST_FILE = 'packages/basalt-ui/src/charts/host.tsx'
1034
+ const CHART_HOST_MESSAGE =
1035
+ '@tanstack/charts/react may only be imported by ' +
1036
+ CHART_HOST_FILE +
1037
+ ' — render charts through the host. (basalt/chart-host-single)'
1038
+ // Ships: repo-local only
1039
+ const chartHostSingle = {
967
1040
  meta: {
968
- type: 'suggestion',
1041
+ type: 'problem',
969
1042
  docs: {
970
- description: 'Disallow @visx/tooltip everywhere — use basalt-ui ChartTooltip instead.',
1043
+ description: 'Allow @tanstack/charts/react only in packages/basalt-ui/src/charts/host.tsx.',
971
1044
  },
972
1045
  schema: [],
973
1046
  },
974
1047
  create(context) {
1048
+ const filename = getFilename(context)
1049
+ // Scoped to basalt source: elsewhere `chart-engine-boundary` already bans /react, one diagnostic.
1050
+ const exempt =
1051
+ !inBasaltSource(filename) || filename.replaceAll('\\', '/').endsWith('/' + CHART_HOST_FILE)
1052
+
975
1053
  function reportFor(node, source) {
976
- if (source !== '@visx/tooltip') return
977
- context.report({ node, message: VISX_TOOLTIP_MESSAGE })
1054
+ if (typeof source !== 'string' || exempt) return
1055
+ if (source === '@tanstack/charts/react' || source.startsWith('@tanstack/charts/react/')) {
1056
+ context.report({ node, message: CHART_HOST_MESSAGE })
1057
+ }
978
1058
  }
979
1059
 
980
1060
  return boundaryVisitor(reportFor)
@@ -1000,7 +1080,7 @@ const tokenLayerBoundary = {
1000
1080
  const inCharts = hasPathSegment(filename, 'charts')
1001
1081
  const inTokens = hasPathSegment(filename, 'tokens')
1002
1082
 
1003
- // Deliberately NO `hasThemeAllow` escape — see basalt/visx-boundary's comment above for why.
1083
+ // Deliberately NO `hasThemeAllow` escape — see the chart-engine-boundary comment above for why.
1004
1084
  function reportFor(node, source) {
1005
1085
  if (typeof source !== 'string') return
1006
1086
  if (
@@ -1021,23 +1101,6 @@ const tokenLayerBoundary = {
1021
1101
  },
1022
1102
  }
1023
1103
 
1024
- // ── Rule 8 — agent-resume-guard ─────────────────────────────────────────────────────────────────
1025
-
1026
- const AGENT_RESUME_GUARD_MESSAGE =
1027
- 'Unguarded stream resume — useAgentThreadRuns owns single-consumer discipline and ' +
1028
- 'StrictMode-safe reconnection; a raw resume re-fires on every effect re-run (vercel/ai#7891, no ' +
1029
- "merged fix). Mark the line 'basalt-agent-allow' if you own the guard. (basalt/agent-resume-guard)"
1030
-
1031
- /** True for `resumeStream()` and `<anything>.resumeStream()` call expressions. */
1032
- function isResumeStreamCall(callee) {
1033
- if (callee.type === 'Identifier') return callee.name === 'resumeStream'
1034
- return (
1035
- callee.type === 'MemberExpression' &&
1036
- callee.property.type === 'Identifier' &&
1037
- callee.property.name === 'resumeStream'
1038
- )
1039
- }
1040
-
1041
1104
  /** The `key` of an object Property as a string, covering both Identifier and Literal keys. */
1042
1105
  function propertyKeyName(node) {
1043
1106
  const key = node.key
@@ -1046,53 +1109,15 @@ function propertyKeyName(node) {
1046
1109
  return undefined
1047
1110
  }
1048
1111
 
1049
- // Deliberately NO `hasThemeAllow` escape — see basalt/visx-boundary's comment for why these
1050
- // guards use their own token (`basalt-agent-allow`, see the file-header comment) instead.
1051
- // Ships: error
1052
- const agentResumeGuard = {
1053
- meta: {
1054
- type: 'problem',
1055
- docs: {
1056
- description:
1057
- 'Disallow unguarded useChat({ resume: true }) / resumeStream() calls outside useAgentThreadRuns.',
1058
- },
1059
- schema: [],
1060
- },
1061
- create(context) {
1062
- return {
1063
- CallExpression(node) {
1064
- const callee = node.callee
1065
-
1066
- if (callee.type === 'Identifier' && callee.name === 'useChat') {
1067
- const arg0 = node.arguments[0]
1068
- if (arg0 === undefined || arg0.type !== 'ObjectExpression') return
1069
- for (const prop of arg0.properties) {
1070
- if (prop.type !== 'Property') continue
1071
- if (propertyKeyName(prop) !== 'resume') continue
1072
- if (prop.value.type !== 'Literal' || prop.value.value !== true) continue
1073
- if (hasAgentAllow(context, prop)) continue
1074
- context.report({ node: prop, message: AGENT_RESUME_GUARD_MESSAGE })
1075
- }
1076
- return
1077
- }
1078
-
1079
- if (!isResumeStreamCall(callee)) return
1080
- if (hasAgentAllow(context, node)) return
1081
- context.report({ node, message: AGENT_RESUME_GUARD_MESSAGE })
1082
- },
1083
- }
1084
- },
1085
- }
1086
-
1087
1112
  // ── Rule 9 — agent-no-raw-usechat ───────────────────────────────────────────────────────────────
1088
1113
 
1089
1114
  const AGENT_NO_RAW_USECHAT_MESSAGE =
1090
- 'Raw @ai-sdk/react useChat — use useAgentStream / useAgentThreadRuns over aiSdkTransport ' +
1115
+ 'Raw @ai-sdk/react useChat / useCompletion / Chat — use useAgentStream / useAgentThreadRuns over aiSdkTransport ' +
1091
1116
  "(unmount abort, supersede guards, single-consumer resume). Mark the line 'basalt-agent-allow' to " +
1092
1117
  'opt out. (basalt/agent-no-raw-usechat)'
1093
1118
 
1094
1119
  const RAW_USE_CHAT_SOURCES = new Set(['@ai-sdk/react', 'ai/react'])
1095
- const RAW_USE_CHAT_NAMES = new Set(['useChat', 'useCompletion'])
1120
+ const RAW_USE_CHAT_NAMES = new Set(['useChat', 'useCompletion', 'Chat'])
1096
1121
 
1097
1122
  // Deliberately NO `hasThemeAllow` escape — see the file-header comment.
1098
1123
  // Ships: error
@@ -1101,11 +1126,18 @@ const agentNoRawUseChat = {
1101
1126
  type: 'problem',
1102
1127
  docs: {
1103
1128
  description:
1104
- 'Disallow importing useChat/useCompletion directly from @ai-sdk/react or ai/react.',
1129
+ 'Disallow importing useChat/useCompletion/Chat directly from @ai-sdk/react or ai/react.',
1105
1130
  },
1106
1131
  schema: [],
1107
1132
  },
1108
1133
  create(context) {
1134
+ const sourceCode = context.sourceCode ?? context.getSourceCode?.()
1135
+ const nameOf = (node) => (node.type === 'Identifier' ? node.name : node.value)
1136
+ const report = (node) => {
1137
+ if (!hasAgentAllow(context, node)) {
1138
+ context.report({ node, message: AGENT_NO_RAW_USECHAT_MESSAGE })
1139
+ }
1140
+ }
1109
1141
  return {
1110
1142
  ImportDeclaration(node) {
1111
1143
  const source = node.source?.value
@@ -1117,13 +1149,32 @@ const agentNoRawUseChat = {
1117
1149
  if (specifier.type !== 'ImportSpecifier') continue
1118
1150
  // `import { type useChat } from '…'` — the individual specifier is type-only.
1119
1151
  if (specifier.importKind === 'type') continue
1120
- const imported = specifier.imported
1121
- const importedName = imported.type === 'Identifier' ? imported.name : imported.value
1152
+ const importedName = nameOf(specifier.imported)
1122
1153
  if (typeof importedName !== 'string' || !RAW_USE_CHAT_NAMES.has(importedName)) continue
1123
- if (hasAgentAllow(context, specifier)) continue
1124
- context.report({ node: specifier, message: AGENT_NO_RAW_USECHAT_MESSAGE })
1154
+ report(specifier)
1155
+ }
1156
+ },
1157
+ // `import * as AI from '@ai-sdk/react'` then `AI.useChat` / `new AI.Chat`.
1158
+ MemberExpression(node) {
1159
+ if (node.computed || node.object?.type !== 'Identifier') return
1160
+ if (!RAW_USE_CHAT_NAMES.has(node.property?.name)) return
1161
+ const def = soleDefinition(sourceCode, node.object)
1162
+ if (def?.type !== 'ImportBinding' || def.node.type !== 'ImportNamespaceSpecifier') return
1163
+ if (def.parent?.importKind === 'type') return
1164
+ if (!RAW_USE_CHAT_SOURCES.has(def.parent?.source?.value)) return
1165
+ report(node)
1166
+ },
1167
+ // `export { Chat } from '@ai-sdk/react'` and `export * from '@ai-sdk/react'`.
1168
+ ExportNamedDeclaration(node) {
1169
+ if (!RAW_USE_CHAT_SOURCES.has(node.source?.value) || node.exportKind === 'type') return
1170
+ for (const specifier of node.specifiers ?? []) {
1171
+ if (specifier.exportKind === 'type') continue
1172
+ if (RAW_USE_CHAT_NAMES.has(nameOf(specifier.local))) report(specifier)
1125
1173
  }
1126
1174
  },
1175
+ ExportAllDeclaration(node) {
1176
+ if (RAW_USE_CHAT_SOURCES.has(node.source?.value) && node.exportKind !== 'type') report(node)
1177
+ },
1127
1178
  }
1128
1179
  },
1129
1180
  }
@@ -1287,7 +1338,7 @@ function aiSdkMajorMessage(consumerMajor) {
1287
1338
  )
1288
1339
  }
1289
1340
 
1290
- // Honours `basalt-agent-allow` (NOT `theme-allow`) like its two agent-chat siblings above — a
1341
+ // Honours `basalt-agent-allow` (NOT `theme-allow`) like `agent-no-raw-usechat` above — a
1291
1342
  // version skew is still a fact about two package.json files, but a written, intentional
1292
1343
  // producer/consumer pair (see `doctor`'s `aiMajorSkewReason`) needs a way to say so at the import
1293
1344
  // site too, not just be permanently blocked.
@@ -1323,227 +1374,6 @@ const aiSdkMajor = {
1323
1374
  },
1324
1375
  }
1325
1376
 
1326
- // ── Rule 12 — hand-rolled-plot ───────────────────────────────────────────────────────────────────
1327
-
1328
- /**
1329
- * The primitives `CartesianChart` assembles. Rendering one of these directly means the file is
1330
- * re-implementing the plot assembly — margins, scales, axes, cursor, crosshair, tooltip — that the
1331
- * primitive owns for every other chart, which is exactly how charts drift apart from each other.
1332
- */
1333
- const PLOT_ASSEMBLY_TAGS = new Set([
1334
- 'AxisLeftNumeric',
1335
- 'AxisRightNumeric',
1336
- 'AxisBottomDate',
1337
- 'HoverOverlay',
1338
- 'Crosshair',
1339
- ])
1340
-
1341
- /**
1342
- * KNOWN LIMIT, deliberately not closed: a chart drawn out of DOM instead of the chart layer is
1343
- * structurally invisible here.
1344
- *
1345
- * argo has two — a 3×3 Mantine `SimpleGrid` matrix and absolutely-positioned `Box` bullet bars —
1346
- * that import only `ChartCard` and `VX`, render no basalt primitive, and therefore cannot trip any
1347
- * tag-name check. The candidate widenings all fail on false positives in a rule that SHIPS to
1348
- * consumers: adding `ChartTooltipFloat`/`useChartSize` to this set flags `Donut` and `Heatmap`,
1349
- * which compose them legitimately; "renders `ChartCard` and a raw `<svg>`" flags an inline icon in
1350
- * a card header. Detecting "this Box grid is a chart" needs intent, not syntax. Naming the gap here
1351
- * is the honest answer — a noisy rule gets switched off, and then it guards nothing at all.
1352
- */
1353
-
1354
- /**
1355
- * Composing this means the file is USING the chart system rather than re-implementing it.
1356
- * `ChartFrame` deliberately does NOT count: composing the frame directly and then hand-assembling
1357
- * axes and a cursor on top of it IS the drift this rule exists to catch. A genuinely non-single-
1358
- * plot shape does exactly that — and declares it with a `theme-allow` comment, so the exception is
1359
- * a decision someone wrote down rather than a default anyone can fall into.
1360
- */
1361
- const PLOT_OWNER_TAGS = new Set(['CartesianChart'])
1362
-
1363
- /**
1364
- * Import sources that make a JSX tag one of BASALT's chart primitives rather than a component the
1365
- * consumer happens to have named `Crosshair`. Matching bare tag names would false-positive on any
1366
- * app with its own overlay component — and this rule ships to consumers, so that matters.
1367
- */
1368
- const CHART_IMPORT_SOURCE = /(^|\/)basalt-ui\/charts|(^|\/)charts(\/|$)|(^|\.\.\/)primitives\//
1369
-
1370
- /** Local names imported from a basalt charts entry — the only tags either chart rule considers. */
1371
- function collectChartImports(node, into) {
1372
- if (!CHART_IMPORT_SOURCE.test(node.source?.value ?? '')) return
1373
- for (const spec of node.specifiers ?? []) {
1374
- if (spec.local?.name !== undefined) into.add(spec.local.name)
1375
- }
1376
- }
1377
-
1378
- const HAND_ROLLED_PLOT_MESSAGE =
1379
- 'Chart assembly primitive rendered without composing CartesianChart — that primitive already ' +
1380
- 'owns the measured margins, both y scales, the axes, grid, shared cursor, crosshair and ' +
1381
- 'tooltip. Hand-assembling them is how two charts stop matching. Compose CartesianChart and ' +
1382
- 'render only marks. A genuinely non-single-plot shape (multi-pane, radial, matrix) composes ' +
1383
- 'ChartFrame instead — declare that with `theme-allow-file hand-rolled-plot — <why>` anywhere in ' +
1384
- 'the file. To waive just THIS node and stay policed on the rest, write `theme-allow ' +
1385
- 'hand-rolled-plot — <why>` on its own line above it. (basalt/hand-rolled-plot)'
1386
-
1387
- // Ships: error
1388
- const handRolledPlot = {
1389
- meta: {
1390
- type: 'suggestion',
1391
- docs: {
1392
- description:
1393
- 'Disallow assembling a cartesian plot by hand instead of composing CartesianChart.',
1394
- },
1395
- schema: [],
1396
- },
1397
- create(context) {
1398
- if (isTestFile(context)) return {}
1399
- // File-scoped, not node-scoped: the verdict depends on whether a plot OWNER appears anywhere
1400
- // in the file, which is only known once the whole file has been walked.
1401
- const assemblyNodes = []
1402
- const chartImports = new Set()
1403
- let ownsPlot = false
1404
- let definesOwner = false
1405
-
1406
- // A rule that says "compose CartesianChart" cannot fire inside the module that DEFINES
1407
- // CartesianChart — that file is the assembly. Detected by declaration rather than by path so
1408
- // it stays honest: this is definitional, not basalt exempting itself for convenience (the
1409
- // self-exemption habit is what let the 1.4.0 regression reach consumers — see docs/STATUS.md).
1410
- const notesOwnerDefinition = (name) => {
1411
- if (name === 'CartesianChart') definesOwner = true
1412
- }
1413
-
1414
- return {
1415
- ImportDeclaration(node) {
1416
- collectChartImports(node, chartImports)
1417
- },
1418
- FunctionDeclaration(node) {
1419
- notesOwnerDefinition(node.id?.name)
1420
- },
1421
- VariableDeclarator(node) {
1422
- notesOwnerDefinition(node.id?.name)
1423
- },
1424
- JSXOpeningElement(node) {
1425
- const tagName = node.name?.name
1426
- if (PLOT_OWNER_TAGS.has(tagName)) ownsPlot = true
1427
- else if (PLOT_ASSEMBLY_TAGS.has(tagName)) assemblyNodes.push(node)
1428
- },
1429
- 'Program:exit'() {
1430
- if (ownsPlot || definesOwner) return
1431
- // The CHECK stays file-scoped — forced by the shape of the correct pattern:
1432
- // `<CartesianChart>{() => <AxisLeftNumeric …/>}</CartesianChart>` puts the axis inside a
1433
- // render-prop arrow, a different function from the component that owns the chart, so
1434
- // component scope would flag the canonical usage.
1435
- //
1436
- // The SUPPRESSION no longer is. Reporting only the first node and testing the waiver there
1437
- // meant one comment — any comment, about anything — bought a whole file permanent immunity:
1438
- // every axis, overlay and crosshair added to that file afterwards went unreported forever
1439
- // (linewatch: three files, one 604 lines). Now every assembly node is reported and waived on
1440
- // its own, EXCEPT when the file carries a written declaration — a `theme-allow` that names
1441
- // `basalt/hand-rolled-plot` and gives a reason. That is the shape a genuinely non-single-plot
1442
- // kind already uses (`DualPanel`), and it is the difference between a line waiver and a
1443
- // decision about the file.
1444
- const nodes = assemblyNodes.filter((node) => chartImports.has(node.name?.name))
1445
- if (nodes.length === 0) return
1446
- if (hasFileDeclaration(context, 'hand-rolled-plot')) return
1447
- for (const node of nodes) {
1448
- if (hasThemeAllow(context, node, 'hand-rolled-plot')) continue
1449
- context.report({ node, message: HAND_ROLLED_PLOT_MESSAGE })
1450
- }
1451
- },
1452
- }
1453
- },
1454
- }
1455
-
1456
- // ── Rule 13 — chart-legend-literal ───────────────────────────────────────────────────────────────
1457
-
1458
- const CHART_LEGEND_LITERAL_MESSAGE =
1459
- 'Hand-authored ChartLegend items — the legend must be DERIVED from the same `series` array the ' +
1460
- 'chart draws (deriveLegend, or just let ChartFrame/CartesianChart do it). A hand-written list ' +
1461
- 'is a second source of truth that silently goes stale: it keeps naming a series after the plot ' +
1462
- 'stops drawing it. (basalt/chart-legend-literal)'
1463
-
1464
- /**
1465
- * Does this expression derive from the chart's own `series`?
1466
- *
1467
- * The contract is derivation from `series` — not derivation from *an* array. `items={refLines.map(
1468
- * (r) => ({ key, label, color, shape }))}` and `items={PACE_ZONES.map(…)}` are hand-authored
1469
- * legends by any reading: every field is written at the call site, and the list can name a band the
1470
- * plot no longer draws exactly like a `[...]` literal can. Walks the base of a member/call chain so
1471
- * `visibleSeries.filter(…).map(…)` and `deriveLegend(series)` both read as derived.
1472
- */
1473
- function derivesFromSeries(node, depth = 0) {
1474
- if (node === null || node === undefined || depth > 8) return false
1475
- if (node.type === 'Identifier') return /series$/i.test(node.name ?? '')
1476
- if (node.type === 'MemberExpression') {
1477
- return derivesFromSeries(node.object, depth + 1) || derivesFromSeries(node.property, depth + 1)
1478
- }
1479
- if (node.type === 'CallExpression') {
1480
- if (node.callee?.type === 'Identifier' && node.callee.name === 'deriveLegend') return true
1481
- return (
1482
- derivesFromSeries(node.callee, depth + 1) ||
1483
- (node.arguments ?? []).some((arg) => derivesFromSeries(arg, depth + 1))
1484
- )
1485
- }
1486
- return false
1487
- }
1488
-
1489
- /** `X.map(…)` — the derivation-shaped form that is only derivation when `X` is the series. */
1490
- function isMapCall(node) {
1491
- return (
1492
- node?.type === 'CallExpression' &&
1493
- node.callee?.type === 'MemberExpression' &&
1494
- node.callee.property?.name === 'map'
1495
- )
1496
- }
1497
-
1498
- // Ships: error
1499
- const chartLegendLiteral = {
1500
- meta: {
1501
- type: 'suggestion',
1502
- docs: { description: 'Require ChartLegend items to be derived from the series descriptor.' },
1503
- schema: [],
1504
- },
1505
- create(context) {
1506
- if (isTestFile(context)) return {}
1507
- const chartImports = new Set()
1508
-
1509
- return {
1510
- ImportDeclaration(node) {
1511
- collectChartImports(node, chartImports)
1512
- },
1513
- JSXOpeningElement(node) {
1514
- if (node.name?.name !== 'ChartLegend' || !chartImports.has('ChartLegend')) return
1515
- const items = node.attributes?.find(
1516
- (attr) => attr.type === 'JSXAttribute' && attr.name?.name === 'items',
1517
- )
1518
- const expression = items?.value?.expression
1519
- // The `.map()` half — a list built by mapping over something that is NOT the series. See
1520
- // `derivesFromSeries`. Reported under the same id as the array-literal half because it is
1521
- // the same defect (a second source of truth for the legend), just written as a call.
1522
- if (isMapCall(expression) && !derivesFromSeries(expression.callee?.object)) {
1523
- if (hasThemeAllow(context, node, 'chart-legend-literal')) return
1524
- context.report({ node, message: CHART_LEGEND_LITERAL_MESSAGE })
1525
- return
1526
- }
1527
- if (expression?.type !== 'ArrayExpression') return
1528
- // Only a list that is ENTIRELY object literals is a hand-authored legend. An array that
1529
- // spreads or calls something (`[...deriveLegend(series), note]`) is derived-and-extended,
1530
- // which is legitimate — flagging it would push people to disable the rule.
1531
- //
1532
- // Known and accepted gap: hoisting the literal to a `const` one line up
1533
- // (`const items = [...]; <ChartLegend items={items} />`) reads as an Identifier here and
1534
- // is not flagged. Following that would mean local flow analysis for a bypass nobody
1535
- // reaches by accident; the rule targets the shape people actually write.
1536
- const allObjectLiterals =
1537
- expression.elements.length > 0 &&
1538
- expression.elements.every((el) => el?.type === 'ObjectExpression')
1539
- if (!allObjectLiterals) return
1540
- if (hasThemeAllow(context, node, 'chart-legend-literal')) return
1541
- context.report({ node, message: CHART_LEGEND_LITERAL_MESSAGE })
1542
- },
1543
- }
1544
- },
1545
- }
1546
-
1547
1377
  // ── Rule 14 — shadow-basalt-export ───────────────────────────────────────────────────────────────
1548
1378
 
1549
1379
  /** The package root — one directory above `configs/`, repo-locally AND inside node_modules. */
@@ -1649,7 +1479,7 @@ const BASALT_EXPORTS = (() => {
1649
1479
  * barrel read, this table is hand-maintained by construction: there is no artifact to derive
1650
1480
  * "someone else's word for `Section`" from. Keep it in step with `docs/CONTROLS-SPEC.md` §6.
1651
1481
  *
1652
- * Advisory, permanently (see {@link PLUGIN_RULE_ADVISORY}): a name is evidence, never proof.
1482
+ * A name is evidence, never proof — which is why the table is exact, not fuzzy.
1653
1483
  */
1654
1484
  const SHADOW_ALIASES = {
1655
1485
  PageSection: 'Section',
@@ -1759,14 +1589,16 @@ function shadowBasaltExportMessage(name) {
1759
1589
  * The cheapest possible detector for the whole absorption class: argo shipped its own `EmptyState`
1760
1590
  * (a full duplicate of the shipped one) and rb a local `StatCard`, and both passed `check-theme`,
1761
1591
  * `oxlint` and `doctor` clean — off-palette code fails, a forked component that uses the correct
1762
- * tokens does not. `warn`, not `error`: a name collision is evidence, not proof, and a consumer
1763
- * with a genuinely different `Composer` should be told once, not blocked.
1592
+ * tokens does not. `error` since 2.0 (the major is the channel); a genuinely different `Composer`
1593
+ * takes a `theme-allow shadow-basalt-export — <why>`.
1764
1594
  */
1765
- // Ships: warn (advisory)
1595
+ // Ships: error
1766
1596
  const shadowBasaltExport = {
1767
1597
  meta: {
1768
1598
  type: 'suggestion',
1769
- docs: { description: 'Warn on a local component whose name collides with a basalt-ui export.' },
1599
+ docs: {
1600
+ description: 'Disallow a local component whose name collides with a basalt-ui export.',
1601
+ },
1770
1602
  schema: [],
1771
1603
  },
1772
1604
  create(context) {
@@ -1878,7 +1710,7 @@ const HAND_ROLLED_SHELL_MESSAGE =
1878
1710
  'Application shell assembled by hand — AppShell parts / Burger / NavLink in a file that does not ' +
1879
1711
  'render BasaltShell. The shell is the framework: BasaltShell owns the sidebar, the mobile tab ' +
1880
1712
  'bar and its More sheet, breadcrumbs, the page header and the collapse state, all driven by ONE ' +
1881
- 'typed nav definition (defineNav + useNav). A hand-rolled AppShell + burger drawer is the exact ' +
1713
+ 'typed nav definition (defineNav + BasaltShell nav={NAV}). A hand-rolled AppShell + burger drawer is the exact ' +
1882
1714
  'layer the 1.19 nav contract replaces, and no palette guard can see it — it uses the right ' +
1883
1715
  'tokens. Declare a deliberate exception with `theme-allow-file hand-rolled-shell — <why>`. ' +
1884
1716
  '(basalt/hand-rolled-shell)'
@@ -2399,20 +2231,13 @@ function hasAncestorTag(node, tags) {
2399
2231
  * {@link slotOwnerBinding} does: whether that `PanelRow` is basalt's is answered by the import list,
2400
2232
  * which is only complete at `Program:exit`, while the ancestry is only walkable during the visit.
2401
2233
  *
2402
- * `tags` is OPTIONAL, and omitting it is the second real use rather than a convenience: a rule
2403
- * whose ancestor set is not known until the import list is complete cannot filter by tag NAME at
2404
- * all (`RouterProvider as Router` is an ordinary thing to write), so it takes every binding and
2405
- * resolves them at exit. `provider-above-router` is that caller.
2406
2234
  */
2407
2235
  function ancestorTagBindings(node, tags) {
2408
2236
  const found = []
2409
2237
  let current = node.parent
2410
2238
  for (let depth = 0; current !== null && current !== undefined; depth++) {
2411
2239
  if (depth > ANCESTRY_MAX_DEPTH || current.type === 'Program') break
2412
- if (
2413
- current.type === 'JSXElement' &&
2414
- (tags === undefined || tags.has(jsxTagName(current.openingElement?.name) ?? ''))
2415
- ) {
2240
+ if (current.type === 'JSXElement' && tags.has(jsxTagName(current.openingElement?.name) ?? '')) {
2416
2241
  const root = jsxRootName(current.openingElement?.name)
2417
2242
  if (root !== undefined) found.push(root)
2418
2243
  }
@@ -2911,166 +2736,6 @@ const controlSizeLiteral = {
2911
2736
  },
2912
2737
  }
2913
2738
 
2914
- // ── Rule 19 — page-bar-budget ───────────────────────────────────────────────────────────────────
2915
-
2916
- /** `PageBar.actions.secondary` — 4, because the one `primary` makes 5 (law C6). */
2917
- const PAGE_BAR_SECONDARY_BUDGET = 4
2918
- /** A `Section`'s `actions` — 3 (law C6). */
2919
- const SECTION_ACTION_BUDGET = 3
2920
-
2921
- const PAGE_BAR_BUDGET_MESSAGES = {
2922
- duplicate:
2923
- 'Second PageBar in the same returned tree — a page has exactly one page bar, and row 1 portals ' +
2924
- 'into the app shell header, so two bars mounted TOGETHER race for the same node (law C6). An ' +
2925
- 'early-return loading state and two page components in one file each get their own tree and ' +
2926
- 'never report. (basalt/page-bar-budget)',
2927
- secondary:
2928
- `More than ${PAGE_BAR_SECONDARY_BUDGET} secondary PageBar actions — the bar holds ≤5 entries ` +
2929
- 'including the one `primary`, and everything past that folds into the `More` menu basalt ' +
2930
- 'computes from the typed BarAction[] (laws C6/C7). Move the rest into a `kind: "menu"` entry. ' +
2931
- '(basalt/page-bar-budget)',
2932
- section:
2933
- `More than ${SECTION_ACTION_BUDGET} Section actions — a section header holds ≤3 (law C6). ` +
2934
- '(basalt/page-bar-budget)',
2935
- filled:
2936
- 'Second filled Button/ActionIcon inside one slot — a home has exactly one primary action, and ' +
2937
- 'two filled buttons side by side name neither (law C6). A filled Badge/ThemeIcon (a count) and ' +
2938
- "an overlay's own submit button are not actions in the bar and never report. " +
2939
- '(basalt/page-bar-budget)',
2940
- }
2941
-
2942
- /** The two tags a `variant="filled"` makes a PRIMARY ACTION — a Badge is a count, not an action. */
2943
- const PRIMARY_ACTION_TAGS = new Set(['Button', 'ActionIcon'])
2944
-
2945
- /**
2946
- * The JSX TREE `node` is returned in — the nearest `ReturnStatement`, concise arrow body, or
2947
- * `const` binding, whichever the walk reaches first.
2948
- *
2949
- * This is what "two PageBars race for the same node" actually requires: they must MOUNT together.
2950
- * Counting per FILE made an `if (!data) return <PageBar title="Jobs"/>` loading state, and two page
2951
- * components exported from one file, an `error` on code that never renders two bars — with a
2952
- * `theme-allow` on correct code as the only escape.
2953
- */
2954
- function enclosingReturnedTree(node) {
2955
- let current = node.parent
2956
- for (let depth = 0; current !== null && current !== undefined; depth++) {
2957
- if (depth > ANCESTRY_MAX_DEPTH || current.type === 'Program') return undefined
2958
- if (current.type === 'ReturnStatement' || current.type === 'VariableDeclarator') return current
2959
- if (current.type === 'ArrowFunctionExpression' && current.body?.type !== 'BlockStatement') {
2960
- return current
2961
- }
2962
- current = current.parent
2963
- }
2964
- return undefined
2965
- }
2966
-
2967
- /** The `actions` attribute of a JSXOpeningElement, unwrapped, or undefined. */
2968
- function actionsAttributeValue(node) {
2969
- for (const attr of node.attributes ?? []) {
2970
- if (attr.type !== 'JSXAttribute' || attr.name?.name !== 'actions') continue
2971
- return { attr, value: unwrapExpressionContainer(attr.value) }
2972
- }
2973
- return undefined
2974
- }
2975
-
2976
- /**
2977
- * The four countable halves of C6. `error` from the start: every one of them is arithmetic over a
2978
- * literal the file wrote down, with no heuristic and no layout intent to guess at.
2979
- */
2980
- // Ships: error
2981
- const pageBarBudget = {
2982
- meta: {
2983
- type: 'problem',
2984
- docs: { description: 'Enforce the PageBar / Section action budgets and the single primary.' },
2985
- schema: [],
2986
- },
2987
- create(context) {
2988
- if (isTestFile(context)) return {}
2989
- const slots = createSlotContext(context)
2990
- const mantineImports = new Map()
2991
- const pageBars = []
2992
- const findings = []
2993
- /** Slot attribute node → its owner binding + the `variant="filled"` attributes inside it. */
2994
- const filledPerSlot = new Map()
2995
-
2996
- return {
2997
- ImportDeclaration(node) {
2998
- collectMantineImports(node, mantineImports)
2999
- slots.noteImport(node)
3000
- },
3001
- JSXOpeningElement(node) {
3002
- const tag = jsxTagName(node.name)
3003
- const owner = jsxRootName(node.name)
3004
- if (tag === 'PageBar') {
3005
- pageBars.push({ node, owner, tree: enclosingReturnedTree(node) })
3006
- const actions = actionsAttributeValue(node)
3007
- if (actions?.value?.type === 'ObjectExpression') {
3008
- for (const prop of actions.value.properties) {
3009
- if (prop.type !== 'Property' || propertyKeyName(prop) !== 'secondary') continue
3010
- if (prop.value?.type !== 'ArrayExpression') continue
3011
- if (prop.value.elements.length <= PAGE_BAR_SECONDARY_BUDGET) continue
3012
- findings.push({ node: prop, owner, message: PAGE_BAR_BUDGET_MESSAGES.secondary })
3013
- }
3014
- }
3015
- return
3016
- }
3017
- if (tag !== 'Section') return
3018
- const actions = actionsAttributeValue(node)
3019
- if (actions?.value?.type !== 'ArrayExpression') return
3020
- if (actions.value.elements.length <= SECTION_ACTION_BUDGET) return
3021
- findings.push({ node: actions.attr, owner, message: PAGE_BAR_BUDGET_MESSAGES.section })
3022
- },
3023
- // The object-spread spelling of a slot — see {@link spreadSlotOwnerBinding}. `slots.note`
3024
- // accepts either node shape; feeding both is what keeps ONE home model across the lanes.
3025
- Property(node) {
3026
- slots.note(node)
3027
- },
3028
- JSXAttribute(node) {
3029
- slots.note(node)
3030
- if (node.name?.name !== 'variant') return
3031
- const value = unwrapExpressionContainer(node.value)
3032
- if (!isStringLiteral(value) || value.value !== 'filled') return
3033
- // An overlay's own submit button is not an action in the bar — see hostedInsideSlot.
3034
- if (hostedInsideSlot(node)) return
3035
- const element = node.parent
3036
- if (element === null || element === undefined || element.type !== 'JSXOpeningElement')
3037
- return
3038
- const slot = enclosingSlotAttribute(node)
3039
- if (slot === undefined) return
3040
- const entry = filledPerSlot.get(slot) ?? { owner: slotOwnerBinding(slot), written: [] }
3041
- entry.written.push({ node, name: element.name })
3042
- filledPerSlot.set(slot, entry)
3043
- },
3044
- 'Program:exit'() {
3045
- /** Returned tree → the PageBars mounted in it. Two in one tree mount together; see C6. */
3046
- const barsPerTree = new Map()
3047
- for (const bar of pageBars) {
3048
- if (!slots.isHome(bar.owner)) continue
3049
- barsPerTree.set(bar.tree, [...(barsPerTree.get(bar.tree) ?? []), bar])
3050
- }
3051
- for (const bars of barsPerTree.values()) {
3052
- for (const { node, owner } of bars.slice(1)) {
3053
- findings.push({ node, owner, message: PAGE_BAR_BUDGET_MESSAGES.duplicate })
3054
- }
3055
- }
3056
- for (const { owner, written } of filledPerSlot.values()) {
3057
- const actions = written.filter(({ name }) =>
3058
- PRIMARY_ACTION_TAGS.has(resolveMantineTag(name, mantineImports) ?? ''),
3059
- )
3060
- for (const { node } of actions.slice(1)) {
3061
- findings.push({ node, owner, message: PAGE_BAR_BUDGET_MESSAGES.filled })
3062
- }
3063
- }
3064
- for (const { node, owner, message } of findings) {
3065
- if (!slots.isHome(owner)) continue
3066
- if (hasThemeAllow(context, node, 'page-bar-budget')) continue
3067
- context.report({ node, message })
3068
- }
3069
- },
3070
- }
3071
- },
3072
- }
3073
-
3074
2739
  // ── Rule 20 — in-body-page-title ────────────────────────────────────────────────────────────────
3075
2740
 
3076
2741
  /** Where an `order={1|2}` Title is a DOCUMENT heading rather than a page title. */
@@ -3462,78 +3127,6 @@ const boundControlOutsideHome = {
3462
3127
  },
3463
3128
  }
3464
3129
 
3465
- // ── Rule 25 — provider-above-router ─────────────────────────────────────────────────────────────
3466
-
3467
- /** `@tanstack/react-router` and its subpaths — never `@tanstack/react-router-devtools`. */
3468
- const TANSTACK_ROUTER_SOURCE = /^@tanstack\/react-router(?:\/|$)/
3469
-
3470
- const PROVIDER_ABOVE_ROUTER_MESSAGE =
3471
- '<BasaltProvider> is rendered INSIDE <RouterProvider> — invert them. Mantine context has to ' +
3472
- 'exist before any route renders, so the mount order is BasaltProvider → the data layer → ' +
3473
- 'RouterProvider (agent/rules/basalt-mantine.md). A route that renders a themed component ' +
3474
- 'before the provider mounts reads Mantine defaults, not the basalt theme. ' +
3475
- '(basalt/provider-above-router)'
3476
-
3477
- /**
3478
- * `BasaltProvider` mounted BELOW a TanStack `RouterProvider` — F5, the first of the three
3479
- * `basalt-mantine.md` laws that shipped under a `not guarded: —` banner.
3480
- *
3481
- * Both tags are provenance-gated: `BasaltProvider` through {@link collectBasaltImportMap} (so a
3482
- * consumer's own component of that name is not this one) and `RouterProvider` through an
3483
- * `@tanstack/react-router` import, by its LOCAL binding rather than its written name. The inverse
3484
- * nesting — the correct one — is not a finding, and neither is either tag alone.
3485
- *
3486
- * **STATIC JSX ancestry only, and that is the whole heuristic.** The two providers have to be
3487
- * written in one JSX tree in one file for this to see anything. The common real shape (`main.tsx`
3488
- * renders `<BasaltProvider><App/></BasaltProvider>` and `App` renders `<RouterProvider/>`) is
3489
- * INVERTED cross-file and correct; the inverted-and-wrong cross-file shape is equally invisible.
3490
- * A rule that followed the composition would need the module graph, which an oxlint rule does not
3491
- * have — so this catches the one-file case and the law stays advisory for the rest. That is why it
3492
- * lands at `warn`.
3493
- */
3494
- // Ships: error
3495
- const providerAboveRouter = {
3496
- meta: {
3497
- type: 'suggestion',
3498
- docs: { description: 'Warn on a BasaltProvider rendered inside a TanStack RouterProvider.' },
3499
- schema: [],
3500
- },
3501
- create(context) {
3502
- if (isTestFile(context)) return {}
3503
- const ownTree = isBasaltOwnSource(getFilename(context))
3504
- const basalt = new Map()
3505
- const routers = new Set()
3506
- const candidates = []
3507
-
3508
- return {
3509
- ImportDeclaration(node) {
3510
- collectBasaltImportMap(node, basalt, ownTree)
3511
- const source = node.source?.value
3512
- if (typeof source !== 'string' || !TANSTACK_ROUTER_SOURCE.test(source)) return
3513
- for (const spec of node.specifiers ?? []) {
3514
- const imported = spec.imported?.name ?? spec.imported?.value
3515
- if (imported === 'RouterProvider' && typeof spec.local?.name === 'string') {
3516
- routers.add(spec.local.name)
3517
- }
3518
- }
3519
- },
3520
- JSXOpeningElement(node) {
3521
- const root = jsxRootName(node.name)
3522
- if (root === undefined || !basalt.has(root)) return
3523
- candidates.push({ node, root, ancestors: ancestorTagBindings(node) })
3524
- },
3525
- 'Program:exit'() {
3526
- for (const { node, root, ancestors } of candidates) {
3527
- if (basalt.get(root) !== 'BasaltProvider') continue
3528
- if (!ancestors.some((name) => routers.has(name))) continue
3529
- if (hasThemeAllow(context, node, 'provider-above-router')) continue
3530
- context.report({ node, message: PROVIDER_ABOVE_ROUTER_MESSAGE })
3531
- }
3532
- },
3533
- }
3534
- },
3535
- }
3536
-
3537
3130
  // Rule 27 — query-dual-import — RETIRED. 1.29.0 dropped every raw TanStack re-export from
3538
3131
  // `basalt-ui/query` (the subpath itself is gone), so "dual-importing `@tanstack/react-query`
3539
3132
  // beside `basalt-ui/query`" is no longer a thing a consumer can even do — a consumer MUST import
@@ -3615,210 +3208,6 @@ const queryFnUnwrap = {
3615
3208
  },
3616
3209
  }
3617
3210
 
3618
- // ── Rule 29 — deprecated-export ─────────────────────────────────────────────────────────────────
3619
-
3620
- /**
3621
- * @typedef {{
3622
- * subpath: string
3623
- * name: string
3624
- * prop?: string
3625
- * replacement: string
3626
- * removeIn: string
3627
- * removed?: true
3628
- * fix?: false
3629
- * }} DeprecatedExport
3630
- */
3631
-
3632
- /**
3633
- * The deprecation ledger — every export that is `@deprecated` but still SHIPPED, with what to
3634
- * write instead and the minor it goes away in. B4: majors are banned here, so a rename cannot be
3635
- * signalled by a version number; the lifecycle is instead "ship the alias, lint the old name, write
3636
- * the MIGRATING row, remove it a minor later" (`../MAINTAINING.md`, "Deprecation lifecycle").
3637
- *
3638
- * A row without `prop` is a NAMED IMPORT from `subpath` — reported on the specifier, with an
3639
- * autofix that rewrites it to `{ <replacement> as <local> }` so no call site has to move in the
3640
- * same edit. **That autofix is the row's contract**: only a DROP-IN rename belongs here. A
3641
- * deprecation whose replacement takes a different shape (`createSearchParamStore` →
3642
- * `createSearchStore` + a `fields` object) would need the fix suppressed before it could be a row,
3643
- * and until then it lives in `MIGRATING.md` alone. A MOVE between subpaths (`FormRow` from
3644
- * `basalt-ui/forms` → `basalt-ui/controls`) is the same case: the fix only rewrites the specifier
3645
- * inside its own import statement and cannot change its module, so such a row says `fix: false` and
3646
- * reports without rewriting. A row WITH `prop` is a JSX ATTRIBUTE on the component `name`, imported from `subpath`
3647
- * — no autofix, because the three connectivity props collapse into ONE object prop and a
3648
- * per-attribute rewrite would produce three `connectivity=` attributes on one tag.
3649
- *
3650
- * **Adding a row is the whole job.** Deprecate the export in its JSDoc, add the row here with a
3651
- * `removeIn` at least one minor out, add the `MIGRATING.md` row, and this rule starts nudging.
3652
- * `oxlint-plugin.test.ts` scans every `src/**\/index.ts(x)` barrel for a JSDoc `@deprecated` and
3653
- * fails when one has no row here, so the barrel half cannot be forgotten. Removing the export is a
3654
- * separate commit that either deletes the row (the fix is manual from then on — say so in the
3655
- * removal's MIGRATING row) or keeps it one minor past the removal with `removed: true` and
3656
- * `removeIn` set to the removing minor, so the message reads "was removed in X" instead of "still
3657
- * resolves" — and never autofixes, whatever `fix` says. `scripts/check-grace.ts` refuses a release
3658
- * at or past a `removeIn` whose export still ships — the gap that let `useBreakpoint` outlive its
3659
- * date twice.
3660
- *
3661
- * @type {readonly DeprecatedExport[]}
3662
- */
3663
- export const DEPRECATED_EXPORTS = [
3664
- // The chart phone-tier API (`resolveChartTier`/`chartTierMetrics`/`useChartTier`/
3665
- // `useChartTierMetrics`/`ChartTierMetrics`/`phoneChartWidth`) is NOT here: MIGRATING.md's own
3666
- // Unreleased section states it a hard removal with no grace window (zero consumers), and these
3667
- // exports are no longer reachable from any public barrel in `src/charts`/`src/tokens` — a row
3668
- // here would nudge a rewrite to an import that no longer resolves, i.e. "it still resolves today"
3669
- // would be false.
3670
- {
3671
- subpath: 'basalt-ui/forms',
3672
- name: 'FormSection',
3673
- replacement: "FormSection from 'basalt-ui/controls'",
3674
- removeIn: '1.39.0',
3675
- removed: true,
3676
- fix: false,
3677
- },
3678
- {
3679
- subpath: 'basalt-ui/forms',
3680
- name: 'FormRow',
3681
- replacement: "FormRow from 'basalt-ui/controls'",
3682
- removeIn: '1.39.0',
3683
- removed: true,
3684
- fix: false,
3685
- },
3686
- {
3687
- subpath: 'basalt-ui/forms',
3688
- name: 'FormGroup',
3689
- replacement: "FormGroup from 'basalt-ui/controls'",
3690
- removeIn: '1.39.0',
3691
- removed: true,
3692
- fix: false,
3693
- },
3694
- {
3695
- subpath: 'basalt-ui/forms',
3696
- name: 'FormActions',
3697
- replacement: "FormActions from 'basalt-ui/controls'",
3698
- removeIn: '1.39.0',
3699
- removed: true,
3700
- fix: false,
3701
- },
3702
- {
3703
- subpath: 'basalt-ui/forms',
3704
- name: 'useForm',
3705
- replacement: "useForm from '@mantine/form'",
3706
- removeIn: '1.39.0',
3707
- removed: true,
3708
- fix: false,
3709
- },
3710
- {
3711
- subpath: 'basalt-ui/forms',
3712
- name: 'schemaResolver',
3713
- replacement: "schemaResolver from '@mantine/form'",
3714
- removeIn: '1.39.0',
3715
- removed: true,
3716
- fix: false,
3717
- },
3718
- {
3719
- subpath: 'basalt-ui',
3720
- name: 'useBreakpoint',
3721
- replacement:
3722
- 'useSizeClass() for shell chrome; a page’s own layout swap is a container decision — a type="container" SimpleGrid/Grid or an @container rule',
3723
- removeIn: '1.34.0',
3724
- removed: true,
3725
- },
3726
- ]
3727
-
3728
- function deprecatedExportMessage(row) {
3729
- const what =
3730
- row.prop === undefined
3731
- ? `\`${row.name}\` from ${row.subpath}`
3732
- : `\`${row.prop}\` on <${row.name}>`
3733
- if (row.removed === true) {
3734
- return (
3735
- `Removed basalt export — ${what} was removed in ${row.removeIn} and no longer resolves; use ` +
3736
- `\`${row.replacement}\` instead (packages/basalt-ui/MIGRATING.md carries the row). ` +
3737
- '(basalt/deprecated-export)'
3738
- )
3739
- }
3740
- return (
3741
- `Deprecated basalt export — ${what} is superseded by \`${row.replacement}\` and is removed in ` +
3742
- `${row.removeIn}. It still resolves today, so nothing is broken yet; migrate before that minor ` +
3743
- '(packages/basalt-ui/MIGRATING.md carries the row). ' +
3744
- '(basalt/deprecated-export)'
3745
- )
3746
- }
3747
-
3748
- /**
3749
- * A shipped-but-`@deprecated` export still being imported or written — B4, the sunset half of the
3750
- * lifecycle whose absence the blueprint audit found (§8): the grace mechanism covers a rule getting
3751
- * STRICTER, and nothing covered an export going away.
3752
- *
3753
- * Permanently `warn` ({@link PLUGIN_RULE_ADVISORY}), for the same reason `shadow-basalt-export` is:
3754
- * a deprecation is a schedule, not a defect. The code compiles and runs; promoting this to `error`
3755
- * would break a consumer's build for something that still works, which is the one thing the
3756
- * no-majors doctrine exists to avoid. The `removeIn` minor is where it becomes an error — by the
3757
- * export ceasing to exist.
3758
- */
3759
- // Ships: warn (advisory)
3760
- const deprecatedExport = {
3761
- meta: {
3762
- type: 'suggestion',
3763
- docs: { description: 'Warn on an import or prop that basalt has deprecated but still ships.' },
3764
- schema: [],
3765
- fixable: 'code',
3766
- },
3767
- create(context) {
3768
- if (isTestFile(context)) return {}
3769
- if (isBasaltOwnSource(getFilename(context))) return {}
3770
- const importRows = DEPRECATED_EXPORTS.filter((row) => row.prop === undefined)
3771
- const propRows = DEPRECATED_EXPORTS.filter((row) => row.prop !== undefined)
3772
- const components = new Map()
3773
- const candidates = []
3774
-
3775
- return {
3776
- ImportDeclaration(node) {
3777
- const source = node.source?.value
3778
- if (typeof source !== 'string') return
3779
- for (const spec of node.specifiers ?? []) {
3780
- const imported = spec.imported?.name ?? spec.imported?.value
3781
- const local = spec.local?.name
3782
- if (typeof imported !== 'string' || typeof local !== 'string') continue
3783
- for (const row of propRows) {
3784
- if (source === row.subpath && imported === row.name) components.set(local, row.name)
3785
- }
3786
- const row = importRows.find((r) => r.subpath === source && r.name === imported)
3787
- if (row === undefined) continue
3788
- if (hasThemeAllow(context, spec, 'deprecated-export')) continue
3789
- context.report({
3790
- node: spec,
3791
- message: deprecatedExportMessage(row),
3792
- // A removed row's replacement is prose for a gone import, never a drop-in name.
3793
- ...(row.fix === false || row.removed === true
3794
- ? {}
3795
- : { fix: (fixer) => fixer.replaceText(spec, `${row.replacement} as ${local}`) }),
3796
- })
3797
- }
3798
- },
3799
- JSXAttribute(node) {
3800
- const name = node.name?.name
3801
- if (typeof name !== 'string') return
3802
- const owner = node.parent
3803
- if (owner === null || owner === undefined || owner.type !== 'JSXOpeningElement') return
3804
- const root = jsxRootName(owner.name)
3805
- if (root === undefined) return
3806
- candidates.push({ node, root, name })
3807
- },
3808
- 'Program:exit'() {
3809
- for (const { node, root, name } of candidates) {
3810
- const component = components.get(root)
3811
- if (component === undefined) continue
3812
- const row = propRows.find((r) => r.name === component && r.prop === name)
3813
- if (row === undefined) continue
3814
- if (hasThemeAllow(context, node, 'deprecated-export')) continue
3815
- context.report({ node, message: deprecatedExportMessage(row) })
3816
- }
3817
- },
3818
- }
3819
- },
3820
- }
3821
-
3822
3211
  // ── Rule 30 — forms-field-key ───────────────────────────────────────────────────────────────────
3823
3212
 
3824
3213
  /** The subpath the forms idiom is imported from — the only provenance this rule accepts. */
@@ -3872,8 +3261,7 @@ function hasKeyAttribute(opening) {
3872
3261
  * - `{...field(…)}` — the pre-migration call site. Reported ALWAYS, key or no key, and deliberately
3873
3262
  * NOT autofixed: the honest edit rewrites the IMPORT as well as the call site, and a fixer that
3874
3263
  * only touched the spread would leave an unresolved `field` behind. This arm is the whole reason
3875
- * the rule still knows the name — 1.29.0 deleted `field`, and `basalt/deprecated-export`'s table
3876
- * went empty with it, so a consumer skipping 1.28.x reaches 1.29.x with a hard compile error and
3264
+ * the rule still knows the name — 1.29.0 deleted `field` with no autofix left, so a consumer skipping 1.28.x reaches 1.29.x with a hard compile error and
3877
3265
  * nothing but tsc to explain it. The message says that in so many words; it used to describe the
3878
3266
  * alias as still shipping and still bundling `key`, which read as "nothing is broken yet".
3879
3267
  *
@@ -3985,10 +3373,9 @@ const NO_IMPORT_META_ENV_MESSAGE =
3985
3373
  /**
3986
3374
  * `import.meta.env` — a MemberExpression whose object is the `import.meta` MetaProperty and whose
3987
3375
  * property is `env`. Repo-local only: a CONSUMER's own app code legitimately uses
3988
- * `import.meta.env.VITE_*` for its own config, so this must never reach the shipped preset — only
3989
- * `/.oxlintrc.json` wires it, scoped to `packages/basalt-ui/src/**` the same way the Mantine-free
3990
- * boundaries above are scoped, rather than through path logic in the rule itself (unlike
3991
- * `token-layer-boundary`, which every package resolves the plugin from and so has to self-scope).
3376
+ * `import.meta.env.VITE_*`, so it is wired in `/.oxlintrc.json` alone and SELF-SCOPES to
3377
+ * `packages/basalt-ui/src/**` by path (like `token-layer-boundary`): the playground, which is
3378
+ * a consumer, keeps its own `import.meta.env`.
3992
3379
  */
3993
3380
  // Ships: repo-local only
3994
3381
  const noImportMetaEnv = {
@@ -4000,7 +3387,7 @@ const noImportMetaEnv = {
4000
3387
  schema: [],
4001
3388
  },
4002
3389
  create(context) {
4003
- if (isTestFile(context)) return {}
3390
+ if (isTestFile(context) || !isBasaltOwnSource(getFilename(context))) return {}
4004
3391
 
4005
3392
  return {
4006
3393
  MemberExpression(node) {
@@ -4470,11 +3857,283 @@ const rawBreakpoint = {
4470
3857
  },
4471
3858
  }
4472
3859
 
3860
+ // ── Rule 34 — no-dev-helper ─────────────────────────────────────────────────────────────────────
3861
+
3862
+ const NO_DEV_HELPER_MESSAGE =
3863
+ 'Dev-only import() outside a literal `process.env.NODE_ENV` gate — bundlers fold the dev branch ' +
3864
+ "away only for a literal `process.env.NODE_ENV !== 'production'` (or `=== 'development'`) read " +
3865
+ 'at the use site; `isDev()` or any helper/const leaks the whole dev module into the production ' +
3866
+ 'first-paint chunk (the check-budgets provider-only budget pins it). (basalt/no-dev-helper)'
3867
+
3868
+ /** Specifiers of dev-only modules: the dev dock, the theme lab, any `*-devtools` package. */
3869
+ const DEV_MODULE_SPECIFIER = /(?:^|[/.-])(?:dev-dock|theme-lab|devtools)(?:$|[/.-])/
3870
+ /** TS wrappers that still READ the value they wrap (`Dock as X`, `Dock!`, `Dock satisfies X`). */
3871
+ const TS_VALUE_WRAPPERS = new Set([
3872
+ 'TSAsExpression',
3873
+ 'TSNonNullExpression',
3874
+ 'TSSatisfiesExpression',
3875
+ 'TSInstantiationExpression',
3876
+ 'TSTypeAssertion',
3877
+ ])
3878
+
3879
+ /** The dev modules themselves — reachable only through a gated import, so their own imports are exempt. */
3880
+ const DEV_MODULE_FILE = /(?:^|[\\/])(?:dev-dock\.[cm]?[jt]sx?|theme-lab[\\/])/
3881
+
3882
+ /** `process.env.NODE_ENV` / `process.env['NODE_ENV']` as a literal read. */
3883
+ function isNodeEnvRead(node) {
3884
+ if (node?.type !== 'MemberExpression') return false
3885
+ const property = node.property
3886
+ const named = node.computed
3887
+ ? isStringLiteral(property) && property.value === 'NODE_ENV'
3888
+ : property?.name === 'NODE_ENV'
3889
+ const env = node.object
3890
+ return (
3891
+ named &&
3892
+ env?.type === 'MemberExpression' &&
3893
+ !env.computed &&
3894
+ env.property?.name === 'env' &&
3895
+ env.object?.type === 'Identifier' &&
3896
+ env.object.name === 'process'
3897
+ )
3898
+ }
3899
+
3900
+ const flip = (env) => (env === 'dev' ? 'prod' : env === 'prod' ? 'dev' : undefined)
3901
+
3902
+ /** The environment `test` pins when it is TRUTHY: 'dev', 'prod' or undefined. */
3903
+ function truthyEnv(test) {
3904
+ if (test?.type === 'BinaryExpression') return comparisonEnv(test)
3905
+ if (test?.type === 'UnaryExpression' && test.operator === '!') return falsyEnv(test.argument)
3906
+ if (test?.type === 'LogicalExpression' && test.operator === '&&') {
3907
+ return truthyEnv(test.left) ?? truthyEnv(test.right)
3908
+ }
3909
+ return undefined
3910
+ }
3911
+
3912
+ /** The environment `test` pins when it is FALSY — `a && env` pins nothing, `a || env` pins both. */
3913
+ function falsyEnv(test) {
3914
+ if (test?.type === 'BinaryExpression') return flip(comparisonEnv(test))
3915
+ if (test?.type === 'UnaryExpression' && test.operator === '!') return truthyEnv(test.argument)
3916
+ if (test?.type === 'LogicalExpression' && test.operator === '||') {
3917
+ return falsyEnv(test.left) ?? falsyEnv(test.right)
3918
+ }
3919
+ return undefined
3920
+ }
3921
+
3922
+ /** 'dev' when the literal NODE_ENV comparison is true in development, 'prod' when in production. */
3923
+ function comparisonEnv(test) {
3924
+ const [read, literal] = isNodeEnvRead(test.left)
3925
+ ? [test.left, test.right]
3926
+ : [test.right, test.left]
3927
+ if (!isNodeEnvRead(read) || !isStringLiteral(literal)) return undefined
3928
+ const equal = test.operator === '===' || test.operator === '=='
3929
+ const unequal = test.operator === '!==' || test.operator === '!='
3930
+ if (!equal && !unequal) return undefined
3931
+ if (literal.value === 'production') return equal ? 'prod' : 'dev'
3932
+ if (literal.value === 'development') return equal ? 'dev' : 'prod'
3933
+ return undefined
3934
+ }
3935
+
3936
+ /** True when `node` sits in a branch that only runs in development, by a literal NODE_ENV test. */
3937
+ function underDevGate(node) {
3938
+ let child = node
3939
+ for (let parent = node.parent; parent; child = parent, parent = parent.parent) {
3940
+ if (parent.type === 'IfStatement' || parent.type === 'ConditionalExpression') {
3941
+ if (parent.consequent === child && truthyEnv(parent.test) === 'dev') return true
3942
+ if (parent.alternate === child && falsyEnv(parent.test) === 'dev') return true
3943
+ }
3944
+ if (parent.type === 'LogicalExpression' && parent.right === child) {
3945
+ if (parent.operator === '&&' && truthyEnv(parent.left) === 'dev') return true
3946
+ if (parent.operator === '||' && falsyEnv(parent.left) === 'dev') return true
3947
+ }
3948
+ }
3949
+ return false
3950
+ }
3951
+
3952
+ const FUNCTION_TYPES = new Set([
3953
+ 'ArrowFunctionExpression',
3954
+ 'FunctionExpression',
3955
+ 'FunctionDeclaration',
3956
+ ])
3957
+
3958
+ /**
3959
+ * The identifier a LOADER function is bound to: `const Dock = lazy(() => import(dev))`,
3960
+ * `const load = () => import(dev)`, `function load() { return import(dev) }`. An import with no
3961
+ * function boundary (`const m = import(dev)`) or one inside an anonymous callback is not bound to
3962
+ * anything, so only its own site can gate it.
3963
+ */
3964
+ function loaderBinding(node) {
3965
+ let fn = node.parent
3966
+ while (fn && !FUNCTION_TYPES.has(fn.type)) fn = fn.parent
3967
+ if (!fn) return undefined
3968
+ if (fn.type === 'FunctionDeclaration') return fn.id ?? undefined
3969
+ let holder = fn.parent
3970
+ if (holder?.type === 'CallExpression') holder = holder.parent
3971
+ return holder?.type === 'VariableDeclarator' && holder.id?.type === 'Identifier'
3972
+ ? holder.id
3973
+ : undefined
3974
+ }
3975
+
3976
+ /**
3977
+ * Is this identifier a USE of a loader binding? Stricter than {@link isValueReferencePosition}
3978
+ * (which only has to tell an import from a render): a loader's uses are judged for gating, so
3979
+ * property keys, class member names, JSX closers and declaration ids must not count, while TS
3980
+ * value wrappers (`Dock!`, `Dock as X`) still do.
3981
+ */
3982
+ function isLoaderUsePosition(node) {
3983
+ const parent = node.parent
3984
+ if (parent === null || parent === undefined) return false
3985
+ switch (parent.type) {
3986
+ case 'MemberExpression':
3987
+ return parent.object === node || parent.computed
3988
+ case 'Property':
3989
+ case 'MethodDefinition':
3990
+ case 'PropertyDefinition':
3991
+ case 'AccessorProperty':
3992
+ return parent.value === node || (parent.computed && parent.key === node)
3993
+ case 'VariableDeclarator':
3994
+ return parent.init === node
3995
+ case 'FunctionDeclaration':
3996
+ case 'ImportSpecifier':
3997
+ case 'ImportDefaultSpecifier':
3998
+ case 'ImportNamespaceSpecifier':
3999
+ case 'JSXAttribute':
4000
+ case 'JSXClosingElement':
4001
+ case 'JSXNamespacedName':
4002
+ case 'LabeledStatement':
4003
+ return false
4004
+ case 'JSXMemberExpression':
4005
+ return parent.object === node
4006
+ default:
4007
+ return TS_VALUE_WRAPPERS.has(parent.type) || !parent.type.startsWith('TS')
4008
+ }
4009
+ }
4010
+
4011
+ // Ships: repo-local only
4012
+ const noDevHelper = {
4013
+ meta: {
4014
+ type: 'problem',
4015
+ docs: {
4016
+ description:
4017
+ 'Require a literal process.env.NODE_ENV gate on dynamic import() of dev-only modules.',
4018
+ },
4019
+ schema: [],
4020
+ },
4021
+ create(context) {
4022
+ if (isTestFile(context) || DEV_MODULE_FILE.test(getFilename(context))) return {}
4023
+ const sourceCode = context.sourceCode ?? context.getSourceCode?.()
4024
+ /** Loader bindings whose own references must all sit under the gate. */
4025
+ const loaders = []
4026
+ /** Every reference candidate, by name — judged against its binding at `Program:exit`. */
4027
+ const references = new Map()
4028
+ const addReference = (node) => {
4029
+ if (!isLoaderUsePosition(node)) return
4030
+ const same = references.get(node.name)
4031
+ if (same === undefined) references.set(node.name, [node])
4032
+ else same.push(node)
4033
+ }
4034
+ /** True when `reference` provably resolves to the declaration `id`; unresolvable is NOT a use. */
4035
+ const resolvesTo = (reference, id) => soleDefinition(sourceCode, reference)?.name === id
4036
+ return {
4037
+ Identifier: addReference,
4038
+ JSXIdentifier: addReference,
4039
+ ImportExpression(node) {
4040
+ const source = node.source
4041
+ let specifier
4042
+ if (isStringLiteral(source)) specifier = source.value
4043
+ else if (source?.quasis?.length === 1) specifier = source.quasis[0].value.cooked
4044
+ if (typeof specifier !== 'string' || !DEV_MODULE_SPECIFIER.test(specifier)) return
4045
+ if (underDevGate(node)) return
4046
+ const id = loaderBinding(node)
4047
+ if (id !== undefined) {
4048
+ loaders.push({ node, id })
4049
+ return
4050
+ }
4051
+ if (!hasThemeAllow(context, node, 'no-dev-helper')) {
4052
+ context.report({ node, message: NO_DEV_HELPER_MESSAGE })
4053
+ }
4054
+ },
4055
+ 'Program:exit'() {
4056
+ for (const { node, id } of loaders) {
4057
+ const uses = (references.get(id.name) ?? []).filter((use) => resolvesTo(use, id))
4058
+ if (uses.length > 0 && uses.every(underDevGate)) continue
4059
+ if (hasThemeAllow(context, node, 'no-dev-helper')) continue
4060
+ context.report({ node, message: NO_DEV_HELPER_MESSAGE })
4061
+ }
4062
+ },
4063
+ }
4064
+ },
4065
+ }
4066
+
4067
+ // ── Rule 35 — route-view-transition ─────────────────────────────────────────────────────────────
4068
+
4069
+ const ROUTE_VIEW_TRANSITION_MESSAGE =
4070
+ 'A React <ViewTransition> around <Outlet> never fires — the router commits through ' +
4071
+ "useSyncExternalStore, which is not a transition — and it competes with the router's own. Route " +
4072
+ "transitions are the router's: createBasaltRouter sets defaultViewTransition. Remove the wrapper. " +
4073
+ '(basalt/route-view-transition)'
4074
+
4075
+ /**
4076
+ * True when the JSX name `nameNode` is `imported` from `source`: a named import (aliased or not),
4077
+ * or `ns.imported` / `Default.imported` off a namespace or default import. Type-only imports never
4078
+ * count. Unresolved or shadowed names do not match.
4079
+ */
4080
+ function isImportedJsxName(sourceCode, nameNode, source, imported) {
4081
+ const fromSource = (def) =>
4082
+ def?.type === 'ImportBinding' &&
4083
+ def.parent?.importKind !== 'type' &&
4084
+ def.parent?.source?.value === source
4085
+ if (nameNode?.type === 'JSXIdentifier') {
4086
+ const def = soleDefinition(sourceCode, nameNode)
4087
+ if (!fromSource(def) || def.node.type !== 'ImportSpecifier' || def.node.importKind === 'type')
4088
+ return false
4089
+ const name = def.node.imported
4090
+ return (name?.type === 'Identifier' ? name.name : name?.value) === imported
4091
+ }
4092
+ if (nameNode?.type === 'JSXMemberExpression' && nameNode.object?.type === 'JSXIdentifier') {
4093
+ if (nameNode.property?.name !== imported) return false
4094
+ const def = soleDefinition(sourceCode, nameNode.object)
4095
+ return (
4096
+ fromSource(def) &&
4097
+ (def.node.type === 'ImportNamespaceSpecifier' || def.node.type === 'ImportDefaultSpecifier')
4098
+ )
4099
+ }
4100
+ return false
4101
+ }
4102
+
4103
+ // Ships: error
4104
+ const routeViewTransition = {
4105
+ meta: {
4106
+ type: 'problem',
4107
+ docs: {
4108
+ description: "Disallow a React <ViewTransition> around the router's <Outlet>.",
4109
+ },
4110
+ schema: [],
4111
+ },
4112
+ create(context) {
4113
+ if (isTestFile(context)) return {}
4114
+ const sourceCode = context.sourceCode ?? context.getSourceCode?.()
4115
+ const reported = new Set()
4116
+ return {
4117
+ JSXOpeningElement(node) {
4118
+ if (!isImportedJsxName(sourceCode, node.name, '@tanstack/react-router', 'Outlet')) return
4119
+ // Walk up: every enclosing <ViewTransition> has this <Outlet> in its subtree.
4120
+ for (let up = node.parent?.parent; up; up = up.parent) {
4121
+ if (up.type !== 'JSXElement' || reported.has(up)) continue
4122
+ const opening = up.openingElement
4123
+ if (!isImportedJsxName(sourceCode, opening?.name, 'react', 'ViewTransition')) continue
4124
+ reported.add(up)
4125
+ if (hasThemeAllow(context, opening, 'route-view-transition')) continue
4126
+ context.report({ node: opening, message: ROUTE_VIEW_TRANSITION_MESSAGE })
4127
+ }
4128
+ },
4129
+ }
4130
+ },
4131
+ }
4132
+
4473
4133
  // ── Grace ledger ────────────────────────────────────────────────────────────────────────────────
4474
4134
 
4475
4135
  /**
4476
4136
  * @typedef {{ since: string; promote: string; why: string }} GraceEntry
4477
- * @typedef {{ since: string; why: string }} AdvisoryEntry
4478
4137
  */
4479
4138
 
4480
4139
  /**
@@ -4484,9 +4143,8 @@ const rawBreakpoint = {
4484
4143
  * nothing tracking them: `configs/oxlint.json` is generated and its top-level keys are fixed by
4485
4144
  * oxlint's own parser, so the ledger cannot live there.
4486
4145
  *
4487
- * `oxlint-plugin.test.ts` asserts the two agree in both directions — a rule listed here or in
4488
- * {@link PLUGIN_RULE_ADVISORY} must be `warn` in the shipped preset, and one absent from both must
4489
- * be `error`. Deleting a `PLUGIN_RULE_GRACE` entry IS the promotion, and the test makes the config
4146
+ * `oxlint-plugin.test.ts` asserts the two agree in both directions — a rule listed here must be
4147
+ * `warn` in the shipped preset, and one absent must be `error`. Deleting a `PLUGIN_RULE_GRACE` entry IS the promotion, and the test makes the config
4490
4148
  * change mandatory in the same commit.
4491
4149
  *
4492
4150
  * Each entry is `{ since, promote, why }` (semver strings, C16 — `docs/CONTROLS-SPEC.md` §1): a
@@ -4502,39 +4160,6 @@ const rawBreakpoint = {
4502
4160
  */
4503
4161
  export const PLUGIN_RULE_GRACE = {}
4504
4162
 
4505
- /**
4506
- * Plugin rules that stay `warn` in the shipped preset PERMANENTLY — outside the C16 version gate,
4507
- * and not expected to ever promote. `oxlint-plugin.test.ts` still requires `warn` in the shipped
4508
- * preset and a `why` long enough to justify it, but skips these when checking `promote`.
4509
- *
4510
- * `shadow-basalt-export` is the one entry: a name collision is strong evidence a component was
4511
- * forked rather than imported, but not proof, and renaming the fork defeats the check outright — so
4512
- * there is no version at which flipping it to `error` is safe to promise in advance.
4513
- *
4514
- * @type {Record<string, AdvisoryEntry>}
4515
- */
4516
- export const PLUGIN_RULE_ADVISORY = {
4517
- 'shadow-basalt-export': {
4518
- since: '1.20.0',
4519
- why:
4520
- 'new in the round-4 guard minor (1.20.0), widened in round-5 (1.21.0 — reads every ' +
4521
- 'published barrel, not just the root one, so a chart-layer fork is visible), narrowed at ' +
4522
- '1.22.0 (`isBasaltScopedFile` plus a component-shaped declaration). A name collision is ' +
4523
- 'strong evidence and not proof, and renaming defeats it outright, so this stays a permanent ' +
4524
- 'warn rather than a grace entry with no honest promote date.',
4525
- },
4526
- 'deprecated-export': {
4527
- since: '1.28.0',
4528
- why:
4529
- 'B4, the sunset half of the deprecation lifecycle (`../MAINTAINING.md`). A deprecation is a ' +
4530
- 'SCHEDULE, not a defect: the code compiles, runs and is correct until the `removeIn` minor, ' +
4531
- 'so promoting this to `error` would fail a consumer build over something that still works — ' +
4532
- 'the one outcome the no-majors doctrine exists to avoid. There is no version at which that ' +
4533
- 'stops being true, which is what makes it advisory rather than a grace entry: enforcement ' +
4534
- 'arrives by the export ceasing to exist, not by this rule changing level.',
4535
- },
4536
- }
4537
-
4538
4163
  // ── Plugin export ───────────────────────────────────────────────────────────────────────────────
4539
4164
 
4540
4165
  export default {
@@ -4544,8 +4169,6 @@ export default {
4544
4169
  'raw-size-literal': rawSizeLiteral,
4545
4170
  'card-inset': cardInset,
4546
4171
  'chart-in-raw-surface': chartInRawSurface,
4547
- 'hand-rolled-plot': handRolledPlot,
4548
- 'chart-legend-literal': chartLegendLiteral,
4549
4172
  'shadow-basalt-export': shadowBasaltExport,
4550
4173
  'hand-rolled-shell': handRolledShell,
4551
4174
  'raw-scroll-container': rawScrollContainer,
@@ -4553,22 +4176,20 @@ export default {
4553
4176
  'control-outside-home': controlOutsideHome,
4554
4177
  'bound-control-outside-home': boundControlOutsideHome,
4555
4178
  'control-size-literal': controlSizeLiteral,
4556
- 'page-bar-budget': pageBarBudget,
4557
4179
  'in-body-page-title': inBodyPageTitle,
4558
4180
  'responsive-twin': responsiveTwin,
4559
4181
  'search-literal-link': searchLiteralLink,
4560
4182
  'use-search-from-literal': useSearchFromLiteral,
4561
- 'provider-above-router': providerAboveRouter,
4562
4183
  'query-fn-unwrap': queryFnUnwrap,
4563
- 'deprecated-export': deprecatedExport,
4564
4184
  'forms-field-key': formsFieldKey,
4565
- 'visx-boundary': visxBoundary,
4566
- 'visx-tooltip': visxTooltip,
4185
+ 'chart-engine-boundary': chartEngineBoundary,
4186
+ 'chart-host-single': chartHostSingle,
4567
4187
  'token-layer-boundary': tokenLayerBoundary,
4568
- 'agent-resume-guard': agentResumeGuard,
4569
4188
  'agent-no-raw-usechat': agentNoRawUseChat,
4570
4189
  'ai-sdk-major': aiSdkMajor,
4571
4190
  'no-import-meta-env': noImportMetaEnv,
4572
4191
  'raw-breakpoint': rawBreakpoint,
4192
+ 'no-dev-helper': noDevHelper,
4193
+ 'route-view-transition': routeViewTransition,
4573
4194
  },
4574
4195
  }