basalt-ui 1.25.0 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (462) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +18 -0
  3. package/MIGRATING.md +546 -20
  4. package/README.md +45 -32
  5. package/agent/rules/basalt-batteries.md +205 -0
  6. package/agent/rules/basalt-charts.md +125 -555
  7. package/agent/rules/basalt-controls.md +155 -0
  8. package/agent/rules/basalt-mantine.md +146 -439
  9. package/agent/rules/basalt-state.md +113 -96
  10. package/agent/rules/basalt-tokens.md +126 -288
  11. package/agent/skills/basalt-app/SKILL.md +70 -195
  12. package/agent/skills/basalt-charts/SKILL.md +54 -234
  13. package/agent/skills/basalt-design/SKILL.md +56 -248
  14. package/agent/templates/CLAUDE-block.md.tpl +31 -40
  15. package/agent/templates/DESIGN.md.tpl +22 -51
  16. package/configs/oxlint-plugin.js +1275 -31
  17. package/configs/oxlint-plugin.test.ts +947 -11
  18. package/configs/oxlint.json +11 -3
  19. package/dist/charts/cursor/resolve.d.ts +18 -0
  20. package/dist/charts/cursor/resolve.d.ts.map +1 -1
  21. package/dist/charts/cursor/resolve.js +15 -6
  22. package/dist/charts/cursor/scope.d.ts +6 -3
  23. package/dist/charts/cursor/scope.d.ts.map +1 -1
  24. package/dist/charts/cursor/store.d.ts +5 -1
  25. package/dist/charts/cursor/store.d.ts.map +1 -1
  26. package/dist/charts/cursor/store.js +4 -4
  27. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
  28. package/dist/charts/hooks/useChartCursor.js +9 -8
  29. package/dist/charts/index.d.ts +2 -2
  30. package/dist/charts/index.d.ts.map +1 -1
  31. package/dist/charts/primitives/ChartCard.d.ts +34 -9
  32. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  33. package/dist/charts/primitives/ChartCard.js +29 -114
  34. package/dist/charts/primitives/ChartCard.test.d.ts +2 -0
  35. package/dist/charts/primitives/ChartCard.test.d.ts.map +1 -0
  36. package/dist/charts/sparklines/BarSparkline.d.ts +26 -2
  37. package/dist/charts/sparklines/BarSparkline.d.ts.map +1 -1
  38. package/dist/charts/sparklines/BarSparkline.js +18 -4
  39. package/dist/cli/check-coverage.test.d.ts +2 -0
  40. package/dist/cli/check-coverage.test.d.ts.map +1 -0
  41. package/dist/cli/index.d.ts +67 -4
  42. package/dist/cli/index.d.ts.map +1 -1
  43. package/dist/cli/index.js +225 -7
  44. package/dist/connectivity/connectivity-indicator.d.ts +4 -1
  45. package/dist/connectivity/connectivity-indicator.d.ts.map +1 -1
  46. package/dist/connectivity/connectivity-indicator.js +1 -1
  47. package/dist/content/index.d.ts +5 -3
  48. package/dist/content/index.d.ts.map +1 -1
  49. package/dist/content/index.js +4 -2
  50. package/dist/content/prose.module.css +7 -28
  51. package/dist/controls/actions.d.ts +169 -0
  52. package/dist/controls/actions.d.ts.map +1 -0
  53. package/dist/controls/actions.js +287 -0
  54. package/dist/controls/actions.module.css +62 -0
  55. package/dist/controls/actions.module.css.d.ts +13 -0
  56. package/dist/controls/actions.test.d.ts +2 -0
  57. package/dist/controls/actions.test.d.ts.map +1 -0
  58. package/dist/controls/actions.type-guard.test.d.ts +2 -0
  59. package/dist/controls/actions.type-guard.test.d.ts.map +1 -0
  60. package/dist/controls/compare-filter.d.ts +41 -0
  61. package/dist/controls/compare-filter.d.ts.map +1 -0
  62. package/dist/controls/compare-filter.js +23 -0
  63. package/dist/controls/control-group.d.ts +19 -0
  64. package/dist/controls/control-group.d.ts.map +1 -0
  65. package/dist/controls/control-group.js +20 -0
  66. package/dist/controls/control-group.test.d.ts +2 -0
  67. package/dist/controls/control-group.test.d.ts.map +1 -0
  68. package/dist/controls/controls.module.css +542 -0
  69. package/dist/controls/controls.module.css.d.ts +35 -0
  70. package/dist/controls/controls.router.test.d.ts +2 -0
  71. package/dist/controls/controls.router.test.d.ts.map +1 -0
  72. package/dist/controls/enum-filter.d.ts +30 -0
  73. package/dist/controls/enum-filter.d.ts.map +1 -0
  74. package/dist/controls/enum-filter.js +91 -0
  75. package/dist/controls/filter-context.d.ts +55 -0
  76. package/dist/controls/filter-context.d.ts.map +1 -0
  77. package/dist/controls/filter-context.js +69 -0
  78. package/dist/controls/filter-pill.d.ts +28 -0
  79. package/dist/controls/filter-pill.d.ts.map +1 -0
  80. package/dist/controls/filter-pill.js +70 -0
  81. package/dist/controls/filter-set.d.ts +13 -0
  82. package/dist/controls/filter-set.d.ts.map +1 -0
  83. package/dist/controls/filter-set.js +125 -0
  84. package/dist/controls/filter-set.test.d.ts +2 -0
  85. package/dist/controls/filter-set.test.d.ts.map +1 -0
  86. package/dist/controls/filter-sheet.d.ts +117 -0
  87. package/dist/controls/filter-sheet.d.ts.map +1 -0
  88. package/dist/controls/filter-sheet.js +146 -0
  89. package/dist/controls/glyphs.d.ts +27 -0
  90. package/dist/controls/glyphs.d.ts.map +1 -0
  91. package/dist/controls/glyphs.js +49 -0
  92. package/dist/controls/index.d.ts +19 -0
  93. package/dist/controls/index.d.ts.map +1 -0
  94. package/dist/controls/index.js +44 -0
  95. package/dist/controls/multi-select-filter.d.ts +23 -0
  96. package/dist/controls/multi-select-filter.d.ts.map +1 -0
  97. package/dist/controls/multi-select-filter.js +89 -0
  98. package/dist/controls/peer-boundary.test.d.ts +2 -0
  99. package/dist/controls/peer-boundary.test.d.ts.map +1 -0
  100. package/dist/controls/props-contract.test.d.ts +2 -0
  101. package/dist/controls/props-contract.test.d.ts.map +1 -0
  102. package/dist/controls/range-filter.d.ts +29 -0
  103. package/dist/controls/range-filter.d.ts.map +1 -0
  104. package/dist/controls/range-filter.js +120 -0
  105. package/dist/controls/search-filter.d.ts +10 -0
  106. package/dist/controls/search-filter.d.ts.map +1 -0
  107. package/dist/controls/search-filter.js +81 -0
  108. package/dist/controls/select-filter.d.ts +74 -0
  109. package/dist/controls/select-filter.d.ts.map +1 -0
  110. package/dist/controls/select-filter.js +10 -0
  111. package/dist/controls/sync-button.d.ts +38 -0
  112. package/dist/controls/sync-button.d.ts.map +1 -0
  113. package/dist/controls/sync-button.js +119 -0
  114. package/dist/controls/sync-button.module.css +63 -0
  115. package/dist/controls/sync-button.module.css.d.ts +11 -0
  116. package/dist/controls/sync-button.test.d.ts +2 -0
  117. package/dist/controls/sync-button.test.d.ts.map +1 -0
  118. package/dist/controls/toggle-filter.d.ts +9 -0
  119. package/dist/controls/toggle-filter.d.ts.map +1 -0
  120. package/dist/controls/toggle-filter.js +42 -0
  121. package/dist/controls/view-tabs.d.ts +23 -0
  122. package/dist/controls/view-tabs.d.ts.map +1 -0
  123. package/dist/controls/view-tabs.js +59 -0
  124. package/dist/controls-dates/date-range-picker.d.ts +4 -0
  125. package/dist/controls-dates/date-range-picker.d.ts.map +1 -0
  126. package/dist/controls-dates/date-range-picker.js +18 -0
  127. package/dist/controls-dates/index.d.ts +9 -0
  128. package/dist/controls-dates/index.d.ts.map +1 -0
  129. package/dist/controls-dates/index.js +4 -0
  130. package/dist/dashboard/delta-badge.d.ts +6 -12
  131. package/dist/dashboard/delta-badge.d.ts.map +1 -1
  132. package/dist/dashboard/delta-badge.js +1 -50
  133. package/dist/dashboard/settings-section.d.ts +6 -4
  134. package/dist/dashboard/settings-section.d.ts.map +1 -1
  135. package/dist/dashboard/settings-section.js +23 -16
  136. package/dist/dashboard/settings-section.module.css +10 -0
  137. package/dist/dashboard/settings-section.module.css.d.ts +1 -0
  138. package/dist/dashboard/settings-section.test.d.ts +2 -0
  139. package/dist/dashboard/settings-section.test.d.ts.map +1 -0
  140. package/dist/dashboard/stat-card.d.ts +44 -9
  141. package/dist/dashboard/stat-card.d.ts.map +1 -1
  142. package/dist/dashboard/stat-card.js +80 -48
  143. package/dist/dashboard/stat-card.module.css +82 -0
  144. package/dist/dashboard/stat-card.module.css.d.ts +11 -0
  145. package/dist/data/data-table.d.ts +37 -15
  146. package/dist/data/data-table.d.ts.map +1 -1
  147. package/dist/data/data-table.js +88 -58
  148. package/dist/guard/grace.test.d.ts +20 -0
  149. package/dist/guard/grace.test.d.ts.map +1 -0
  150. package/dist/guard/index.d.ts +66 -3
  151. package/dist/guard/index.d.ts.map +1 -1
  152. package/dist/guard/index.js +82 -7
  153. package/dist/guard/types.d.ts +3 -3
  154. package/dist/guard/types.d.ts.map +1 -1
  155. package/dist/index.d.ts +12 -2
  156. package/dist/index.d.ts.map +1 -1
  157. package/dist/index.js +13 -10
  158. package/dist/nav/types.d.ts +78 -2
  159. package/dist/nav/types.d.ts.map +1 -1
  160. package/dist/notifications/bell.d.ts +4 -1
  161. package/dist/notifications/bell.d.ts.map +1 -1
  162. package/dist/notifications/index.d.ts +1 -1
  163. package/dist/router-tanstack/field.d.ts +14 -0
  164. package/dist/router-tanstack/field.d.ts.map +1 -0
  165. package/dist/router-tanstack/field.js +4 -0
  166. package/dist/router-tanstack/index.d.ts +6 -2
  167. package/dist/router-tanstack/index.d.ts.map +1 -1
  168. package/dist/router-tanstack/index.js +4 -0
  169. package/dist/router-tanstack/multi-search-param-store.d.ts +4 -52
  170. package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
  171. package/dist/router-tanstack/multi-search-param-store.js +19 -27
  172. package/dist/router-tanstack/search-param-store.d.ts +3 -87
  173. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  174. package/dist/router-tanstack/search-param-store.js +16 -45
  175. package/dist/router-tanstack/search-store.d.ts +81 -0
  176. package/dist/router-tanstack/search-store.d.ts.map +1 -0
  177. package/dist/router-tanstack/search-store.js +187 -0
  178. package/dist/router-tanstack/search-store.router.test.d.ts +2 -0
  179. package/dist/router-tanstack/search-store.router.test.d.ts.map +1 -0
  180. package/dist/router-tanstack/search-store.test.d.ts +2 -0
  181. package/dist/router-tanstack/search-store.test.d.ts.map +1 -0
  182. package/dist/router-tanstack/use-nav.d.ts +0 -2
  183. package/dist/router-tanstack/use-nav.d.ts.map +1 -1
  184. package/dist/router-tanstack/use-nav.js +34 -12
  185. package/dist/router-tanstack/use-nav.test.d.ts +2 -0
  186. package/dist/router-tanstack/use-nav.test.d.ts.map +1 -0
  187. package/dist/section/index.d.ts +2 -0
  188. package/dist/section/index.d.ts.map +1 -0
  189. package/dist/section/index.js +4 -0
  190. package/dist/section/section.d.ts +24 -0
  191. package/dist/section/section.d.ts.map +1 -0
  192. package/dist/section/section.js +126 -0
  193. package/dist/section/section.module.css +104 -0
  194. package/dist/section/section.module.css.d.ts +11 -0
  195. package/dist/section/section.test.d.ts +2 -0
  196. package/dist/section/section.test.d.ts.map +1 -0
  197. package/dist/shell/app-breadcrumbs.d.ts.map +1 -1
  198. package/dist/shell/app-breadcrumbs.js +18 -15
  199. package/dist/shell/app-header.module.css +77 -37
  200. package/dist/shell/app-header.module.css.d.ts +2 -1
  201. package/dist/shell/app-mobile-nav.d.ts +9 -2
  202. package/dist/shell/app-mobile-nav.d.ts.map +1 -1
  203. package/dist/shell/app-mobile-nav.js +98 -11
  204. package/dist/shell/app-mobile-nav.module.css +8 -0
  205. package/dist/shell/app-mobile-nav.module.css.d.ts +1 -0
  206. package/dist/shell/app-sidebar.d.ts +55 -28
  207. package/dist/shell/app-sidebar.d.ts.map +1 -1
  208. package/dist/shell/app-sidebar.js +116 -67
  209. package/dist/shell/app-sidebar.module.css +303 -20
  210. package/dist/shell/app-sidebar.module.css.d.ts +19 -1
  211. package/dist/shell/index.d.ts +55 -21
  212. package/dist/shell/index.d.ts.map +1 -1
  213. package/dist/shell/index.js +40 -18
  214. package/dist/shell/mobile-nav-model.d.ts +13 -2
  215. package/dist/shell/mobile-nav-model.d.ts.map +1 -1
  216. package/dist/shell/mobile-nav-model.js +15 -1
  217. package/dist/shell/nav-count-badge.d.ts +1 -1
  218. package/dist/shell/page-bar.d.ts +51 -0
  219. package/dist/shell/page-bar.d.ts.map +1 -0
  220. package/dist/shell/page-bar.js +154 -0
  221. package/dist/shell/page-bar.module.css +178 -0
  222. package/dist/shell/page-bar.module.css.d.ts +17 -0
  223. package/dist/shell/page-bar.test.d.ts +2 -0
  224. package/dist/shell/page-bar.test.d.ts.map +1 -0
  225. package/dist/shell/sidebar-block-model.d.ts +48 -0
  226. package/dist/shell/sidebar-block-model.d.ts.map +1 -0
  227. package/dist/shell/sidebar-block-model.js +39 -0
  228. package/dist/shell/sidebar-blocks.d.ts +32 -0
  229. package/dist/shell/sidebar-blocks.d.ts.map +1 -0
  230. package/dist/shell/sidebar-blocks.js +169 -0
  231. package/dist/shell/sidebar-search.d.ts +13 -1
  232. package/dist/shell/sidebar-search.d.ts.map +1 -1
  233. package/dist/shell/sidebar-search.js +34 -1
  234. package/dist/shell/sidebar-search.module.css +26 -0
  235. package/dist/shell/sidebar-search.module.css.d.ts +2 -0
  236. package/dist/state/fields.d.ts +392 -0
  237. package/dist/state/fields.d.ts.map +1 -0
  238. package/dist/state/fields.js +369 -0
  239. package/dist/state/fields.test.d.ts +2 -0
  240. package/dist/state/fields.test.d.ts.map +1 -0
  241. package/dist/state/persisted.d.ts +48 -0
  242. package/dist/state/persisted.d.ts.map +1 -0
  243. package/dist/state/persisted.js +105 -0
  244. package/dist/state.d.ts +11 -56
  245. package/dist/state.d.ts.map +1 -1
  246. package/dist/state.js +14 -120
  247. package/dist/styles.css +13 -0
  248. package/dist/surfaces.d.ts +106 -24
  249. package/dist/surfaces.d.ts.map +1 -1
  250. package/dist/surfaces.js +155 -19
  251. package/dist/theme/controls.module.css +16 -1
  252. package/dist/theme/ctl-theme.d.ts +32 -0
  253. package/dist/theme/ctl-theme.d.ts.map +1 -0
  254. package/dist/theme/ctl-theme.js +54 -0
  255. package/dist/theme/ctl-theme.test.d.ts +2 -0
  256. package/dist/theme/ctl-theme.test.d.ts.map +1 -0
  257. package/dist/theme/ctl-tier-coverage.test.d.ts +2 -0
  258. package/dist/theme/ctl-tier-coverage.test.d.ts.map +1 -0
  259. package/dist/theme/divider-law.test.d.ts +2 -0
  260. package/dist/theme/divider-law.test.d.ts.map +1 -0
  261. package/dist/theme/icon-slot.d.ts +44 -0
  262. package/dist/theme/icon-slot.d.ts.map +1 -0
  263. package/dist/theme/icon-slot.js +21 -0
  264. package/dist/theme/icon-slot.module.css +47 -0
  265. package/dist/theme/icon-slot.module.css.d.ts +8 -0
  266. package/dist/theme/icon-slot.test.d.ts +2 -0
  267. package/dist/theme/icon-slot.test.d.ts.map +1 -0
  268. package/dist/theme/index.d.ts +1 -0
  269. package/dist/theme/index.d.ts.map +1 -1
  270. package/dist/theme/index.js +82 -5
  271. package/dist/theme/layout-rhythm-css.test.d.ts +2 -0
  272. package/dist/theme/layout-rhythm-css.test.d.ts.map +1 -0
  273. package/dist/theme/segmented-control.module.css +20 -0
  274. package/dist/theme/shadow-surfaces.test.d.ts.map +1 -1
  275. package/dist/tokens/index.d.ts +12 -0
  276. package/dist/tokens/index.d.ts.map +1 -1
  277. package/dist/tokens/index.js +38 -8
  278. package/dist/tokens/palette.d.ts +97 -61
  279. package/dist/tokens/palette.d.ts.map +1 -1
  280. package/dist/tokens/palette.js +76 -38
  281. package/dist/tokens.css +10 -2
  282. package/dist/widget-header/delta-badge.d.ts +13 -0
  283. package/dist/widget-header/delta-badge.d.ts.map +1 -0
  284. package/dist/widget-header/delta-badge.js +29 -0
  285. package/dist/widget-header/delta-badge.test.d.ts +2 -0
  286. package/dist/widget-header/delta-badge.test.d.ts.map +1 -0
  287. package/dist/widget-header/index.d.ts +3 -0
  288. package/dist/widget-header/index.d.ts.map +1 -0
  289. package/dist/widget-header/index.js +6 -0
  290. package/dist/widget-header/no-mantine.test.d.ts +2 -0
  291. package/dist/widget-header/no-mantine.test.d.ts.map +1 -0
  292. package/dist/widget-header/widget-header.d.ts +37 -0
  293. package/dist/widget-header/widget-header.d.ts.map +1 -0
  294. package/dist/widget-header/widget-header.js +77 -0
  295. package/dist/widget-header/widget-header.module.css +335 -0
  296. package/dist/widget-header/widget-header.module.css.d.ts +24 -0
  297. package/dist/widget-header/widget-header.test.d.ts +2 -0
  298. package/dist/widget-header/widget-header.test.d.ts.map +1 -0
  299. package/llms.txt +30 -17
  300. package/package.json +14 -1
  301. package/src/charts/cursor/resolve.test.ts +29 -1
  302. package/src/charts/cursor/resolve.ts +38 -6
  303. package/src/charts/cursor/scope.tsx +6 -3
  304. package/src/charts/cursor/store.test.ts +19 -1
  305. package/src/charts/cursor/store.ts +10 -5
  306. package/src/charts/hooks/useChartCursor.ts +16 -8
  307. package/src/charts/index.ts +2 -2
  308. package/src/charts/primitives/CartesianChart.test.tsx +124 -0
  309. package/src/charts/primitives/ChartCard.test.tsx +66 -0
  310. package/src/charts/primitives/ChartCard.tsx +86 -146
  311. package/src/charts/sparklines/BarSparkline.tsx +43 -5
  312. package/src/cli/check-coverage.test.ts +297 -0
  313. package/src/cli/check-theme.test.ts +38 -14
  314. package/src/cli/index.ts +392 -16
  315. package/src/cli/init-sync.test.ts +97 -0
  316. package/src/connectivity/connectivity-indicator.tsx +16 -9
  317. package/src/content/index.ts +16 -3
  318. package/src/content/prose.module.css +7 -28
  319. package/src/content/prose.module.css.test.ts +8 -24
  320. package/src/controls/actions.module.css +62 -0
  321. package/src/controls/actions.module.css.d.ts +13 -0
  322. package/src/controls/actions.test.tsx +252 -0
  323. package/src/controls/actions.tsx +655 -0
  324. package/src/controls/actions.type-guard.test.ts +63 -0
  325. package/src/controls/compare-filter.tsx +64 -0
  326. package/src/controls/control-group.test.tsx +229 -0
  327. package/src/controls/control-group.tsx +63 -0
  328. package/src/controls/controls.module.css +542 -0
  329. package/src/controls/controls.module.css.d.ts +35 -0
  330. package/src/controls/controls.router.test.tsx +637 -0
  331. package/src/controls/enum-filter.tsx +159 -0
  332. package/src/controls/filter-context.tsx +153 -0
  333. package/src/controls/filter-pill.tsx +120 -0
  334. package/src/controls/filter-set.test.tsx +472 -0
  335. package/src/controls/filter-set.tsx +225 -0
  336. package/src/controls/filter-sheet.tsx +285 -0
  337. package/src/controls/glyphs.tsx +81 -0
  338. package/src/controls/index.ts +48 -0
  339. package/src/controls/multi-select-filter.tsx +140 -0
  340. package/src/controls/peer-boundary.test.ts +85 -0
  341. package/src/controls/props-contract.test.tsx +106 -0
  342. package/src/controls/range-filter.tsx +184 -0
  343. package/src/controls/search-filter.tsx +123 -0
  344. package/src/controls/select-filter.tsx +89 -0
  345. package/src/controls/sync-button.module.css +63 -0
  346. package/src/controls/sync-button.module.css.d.ts +11 -0
  347. package/src/controls/sync-button.test.tsx +183 -0
  348. package/src/controls/sync-button.tsx +197 -0
  349. package/src/controls/toggle-filter.tsx +60 -0
  350. package/src/controls/view-tabs.tsx +110 -0
  351. package/src/controls-dates/date-range-picker.tsx +42 -0
  352. package/src/controls-dates/index.ts +8 -0
  353. package/src/dashboard/delta-badge.tsx +4 -74
  354. package/src/dashboard/settings-section.module.css +10 -0
  355. package/src/dashboard/settings-section.module.css.d.ts +1 -0
  356. package/src/dashboard/settings-section.test.tsx +56 -0
  357. package/src/dashboard/settings-section.tsx +33 -30
  358. package/src/dashboard/stat-card.module.css +82 -0
  359. package/src/dashboard/stat-card.module.css.d.ts +11 -0
  360. package/src/dashboard/stat-card.test.tsx +67 -1
  361. package/src/dashboard/stat-card.tsx +209 -68
  362. package/src/data/data-table.test.tsx +96 -11
  363. package/src/data/data-table.tsx +220 -84
  364. package/src/guard/check-source.test.ts +126 -13
  365. package/src/guard/grace.test.ts +149 -0
  366. package/src/guard/index.ts +172 -29
  367. package/src/guard/types.ts +4 -2
  368. package/src/index.ts +46 -5
  369. package/src/nav/types.ts +84 -2
  370. package/src/notifications/bell.tsx +6 -3
  371. package/src/notifications/index.ts +1 -1
  372. package/src/router-tanstack/field.ts +31 -0
  373. package/src/router-tanstack/index.ts +24 -2
  374. package/src/router-tanstack/multi-search-param-store.test.ts +71 -3
  375. package/src/router-tanstack/multi-search-param-store.ts +46 -77
  376. package/src/router-tanstack/search-param-store.ts +42 -159
  377. package/src/router-tanstack/search-store.router.test.tsx +661 -0
  378. package/src/router-tanstack/search-store.test.ts +418 -0
  379. package/src/router-tanstack/search-store.ts +380 -0
  380. package/src/router-tanstack/use-nav.test.tsx +187 -0
  381. package/src/router-tanstack/use-nav.ts +72 -14
  382. package/src/section/index.ts +1 -0
  383. package/src/section/section.module.css +104 -0
  384. package/src/section/section.module.css.d.ts +11 -0
  385. package/src/section/section.test.tsx +204 -0
  386. package/src/section/section.tsx +220 -0
  387. package/src/shell/app-breadcrumbs.tsx +44 -31
  388. package/src/shell/app-header.module.css +77 -37
  389. package/src/shell/app-header.module.css.d.ts +2 -1
  390. package/src/shell/app-mobile-nav.module.css +8 -0
  391. package/src/shell/app-mobile-nav.module.css.d.ts +1 -0
  392. package/src/shell/app-mobile-nav.tsx +147 -8
  393. package/src/shell/app-sidebar-account.tsx +2 -2
  394. package/src/shell/app-sidebar.module.css +303 -20
  395. package/src/shell/app-sidebar.module.css.d.ts +19 -1
  396. package/src/shell/app-sidebar.test.tsx +456 -41
  397. package/src/shell/app-sidebar.tsx +276 -140
  398. package/src/shell/index.test.tsx +291 -5
  399. package/src/shell/index.tsx +137 -38
  400. package/src/shell/mobile-nav-model.test.ts +228 -7
  401. package/src/shell/mobile-nav-model.ts +53 -3
  402. package/src/shell/nav-count-badge.tsx +1 -1
  403. package/src/shell/page-bar.module.css +178 -0
  404. package/src/shell/page-bar.module.css.d.ts +17 -0
  405. package/src/shell/page-bar.test.tsx +316 -0
  406. package/src/shell/page-bar.tsx +315 -0
  407. package/src/shell/sidebar-block-model.ts +83 -0
  408. package/src/shell/sidebar-blocks.tsx +301 -0
  409. package/src/shell/sidebar-search.module.css +26 -0
  410. package/src/shell/sidebar-search.module.css.d.ts +2 -0
  411. package/src/shell/sidebar-search.tsx +71 -2
  412. package/src/state/fields.test.tsx +258 -0
  413. package/src/state/fields.ts +892 -0
  414. package/src/state/persisted.ts +211 -0
  415. package/src/state.ts +46 -240
  416. package/src/styles.css +13 -0
  417. package/src/surfaces.ts +203 -36
  418. package/src/theme/controls.module.css +16 -1
  419. package/src/theme/ctl-theme.test.tsx +275 -0
  420. package/src/theme/ctl-theme.tsx +138 -0
  421. package/src/theme/ctl-tier-coverage.test.ts +170 -0
  422. package/src/theme/density-relations.test.ts +106 -51
  423. package/src/theme/divider-law.test.ts +259 -0
  424. package/src/theme/icon-slot.module.css +47 -0
  425. package/src/theme/icon-slot.module.css.d.ts +8 -0
  426. package/src/theme/icon-slot.test.tsx +275 -0
  427. package/src/theme/icon-slot.tsx +59 -0
  428. package/src/theme/index.ts +165 -3
  429. package/src/theme/layout-rhythm-css.test.ts +72 -0
  430. package/src/theme/radius.test.ts +11 -1
  431. package/src/theme/segmented-control.module.css +20 -0
  432. package/src/theme/shadow-surfaces.test.ts +20 -7
  433. package/src/theme/spacing.test.ts +75 -27
  434. package/src/theme/use-basalt-spacing.ts +2 -2
  435. package/src/tokens/density.test.ts +7 -25
  436. package/src/tokens/index.ts +48 -8
  437. package/src/tokens/palette.ts +107 -73
  438. package/src/widget-header/delta-badge.test.tsx +60 -0
  439. package/src/widget-header/delta-badge.tsx +57 -0
  440. package/src/widget-header/index.ts +2 -0
  441. package/src/widget-header/no-mantine.test.ts +33 -0
  442. package/src/widget-header/widget-header.module.css +335 -0
  443. package/src/widget-header/widget-header.module.css.d.ts +24 -0
  444. package/src/widget-header/widget-header.test.tsx +187 -0
  445. package/src/widget-header/widget-header.tsx +168 -0
  446. package/agent/rules/basalt-agent.md +0 -452
  447. package/agent/rules/basalt-app.md +0 -246
  448. package/agent/rules/basalt-commands.md +0 -320
  449. package/agent/rules/basalt-content.md +0 -409
  450. package/agent/rules/basalt-data.md +0 -329
  451. package/agent/rules/basalt-forms.md +0 -186
  452. package/agent/rules/basalt-notifications.md +0 -264
  453. package/agent/rules/basalt-query.md +0 -187
  454. package/agent/rules/basalt-router.md +0 -336
  455. package/dist/content/article-filter-bar.d.ts +0 -40
  456. package/dist/content/article-filter-bar.d.ts.map +0 -1
  457. package/dist/content/article-filter-bar.js +0 -56
  458. package/dist/shell/page-header.d.ts +0 -13
  459. package/dist/shell/page-header.d.ts.map +0 -1
  460. package/dist/shell/page-header.js +0 -23
  461. package/src/content/article-filter-bar.tsx +0 -88
  462. package/src/shell/page-header.tsx +0 -35
@@ -1,264 +0,0 @@
1
- ---
2
- source: basalt-ui
3
- description: Notification conventions for basalt-ui apps — notify helpers, typed registry, persisted history store, bell/center, and BasaltNotifications overlay from the shipped ./notifications battery. @mantine/notifications is an optional peer.
4
- paths:
5
- - 'src/**'
6
- - 'apps/**/src/**'
7
- ---
8
-
9
- # Basalt Notifications
10
-
11
- basalt-ui ships `./notifications` — a Mantine notification adapter battery providing `notify`,
12
- `notifySuccess/Error/Warning/Info`, `notifyPromise`, `defineNotifications`, `emit`,
13
- `useNotificationHistory`, `NotificationBell`, `NotificationCenter`, and `BasaltNotifications`
14
- on top of `@mantine/notifications`. `@mantine/notifications` is an **optional peer** — install it
15
- before using this battery:
16
-
17
- ```bash
18
- bun add @mantine/notifications
19
- ```
20
-
21
- ## Mount BasaltNotifications
22
-
23
- Render **one** `<BasaltNotifications />` inside `BasaltProvider`, before the router. The overlay is
24
- opt-in — it is NOT auto-mounted from `BasaltProvider` to keep the peer out of the root barrel.
25
-
26
- ```tsx
27
- import { BasaltNotifications } from 'basalt-ui/notifications'
28
- import { BasaltProvider } from 'basalt-ui'
29
-
30
- createRoot(root).render(
31
- <BasaltProvider>
32
- <BasaltNotifications />
33
- <App />
34
- </BasaltProvider>,
35
- )
36
- ```
37
-
38
- `BasaltNotifications` wraps Mantine's `<Notifications />` with basalt defaults:
39
- `position="bottom-right"`, `autoClose=4000`, `limit=5`. Pass props to override:
40
-
41
- ```tsx
42
- <BasaltNotifications position="top-right" autoClose={3000} limit={3} />
43
- ```
44
-
45
- NOTE: `BasaltOverlays` (from `basalt-ui/commands`) is the shipped composable mount that wires
46
- notifications together with spotlight/modals in one place. A standalone `<BasaltNotifications />`
47
- remains available for apps that don't use the overlays layer.
48
-
49
- ## notify
50
-
51
- `notify(opts)` is the core helper. It maps intent → Mantine color name, sets the correct aria
52
- `role` for screen reader announcement, applies per-intent `autoClose` defaults, and records the
53
- toast to the persisted history store. Returns the notification id string.
54
-
55
- ```ts
56
- import { notify } from 'basalt-ui/notifications'
57
-
58
- notify({ intent: 'success', message: 'Profile saved' })
59
- notify({ intent: 'error', message: 'Upload failed', title: 'Error', autoClose: false })
60
- notify({ intent: 'warning', message: 'Session expires soon', dedupKey: 'session-warn' })
61
- ```
62
-
63
- ### Intent → Mantine color + aria role + autoClose
64
-
65
- | Intent | `color=` | `role=` | Default autoClose |
66
- | ------- | ---------- | --------------------- | ------------------------- |
67
- | success | `'green'` | `'status'` (polite) | 2000ms |
68
- | info | `'blue'` | `'status'` (polite) | 3000ms |
69
- | warning | `'yellow'` | `'alert'` (assertive) | 5000ms |
70
- | error | `'red'` | `'alert'` (assertive) | `false` (until dismissed) |
71
-
72
- **Never pass raw Mantine colors directly.** Always use `notify`'s `intent` field — the mapping is
73
- the canonical source so screen-reader announcement and autoClose stay consistent.
74
-
75
- ### Two-layer dedup
76
-
77
- 1. **Mantine id-dedup**: passing the same `id` to `notifications.show` is a no-op on screen
78
- (Mantine's built-in dedup). Pass `id` to prevent a toast from appearing twice.
79
- 2. **dedupKey window**: a module-level guard skips any `notify` call using the same `dedupKey`
80
- within 1500ms — useful for event-driven triggers that may fire rapidly.
81
-
82
- ```ts
83
- // Fire rapidly — only the first call within 1500ms shows:
84
- notify({ intent: 'warning', message: 'Rate limit', dedupKey: 'rate-limit-warn' })
85
- ```
86
-
87
- ## Convenience wrappers
88
-
89
- ```ts
90
- import { notifySuccess, notifyError, notifyWarning, notifyInfo } from 'basalt-ui/notifications'
91
-
92
- notifySuccess('Changes saved')
93
- notifyError('Upload failed', { title: 'Storage error' })
94
- notifyWarning('Session expires in 5 minutes')
95
- notifyInfo('New version available', { autoClose: 10000 })
96
- ```
97
-
98
- Each wrapper calls `notify` with the matching intent. The second arg takes all `NotifyOptions`
99
- fields except `intent` and `message`.
100
-
101
- ## notifyPromise
102
-
103
- For async operations — shows a loading toast, then updates to success or error on settle.
104
- Uses `notifications.show` for loading, then `notifications.update` for the final state (required
105
- by Mantine to replace an existing id — `show` with the same id is a no-op).
106
-
107
- ```ts
108
- import { notifyPromise } from 'basalt-ui/notifications'
109
-
110
- await notifyPromise(uploadFile(file), {
111
- loading: 'Uploading…',
112
- success: 'Upload complete',
113
- error: 'Upload failed',
114
- })
115
- ```
116
-
117
- The promise is passed through — `await notifyPromise(...)` resolves/rejects as normal. The final
118
- state is recorded to the notification history store (loading state is not recorded).
119
-
120
- ## defineNotifications + emit
121
-
122
- `defineNotifications` is a const-generic factory (mirrors `defineSeries` from `./tokens`) for
123
- typed notification kind registries. Augment `BasaltRegister.notifications` once with the spec map
124
- type, then use `emit` — unknown kinds are tsc errors.
125
-
126
- ```ts
127
- // notifications.ts — define and augment (app-side, once):
128
- import { defineNotifications } from 'basalt-ui/notifications'
129
-
130
- export const NOTIFICATIONS = defineNotifications({
131
- 'upload:success': { intent: 'success', toMessage: (p: { name: string }) => `Uploaded ${p.name}` },
132
- 'upload:error': { intent: 'error', toMessage: (_: unknown) => 'Upload failed' },
133
- 'session:warn': { intent: 'warning', toMessage: () => 'Session expires soon' },
134
- })
135
-
136
- declare module 'basalt-ui' {
137
- interface BasaltRegister {
138
- notifications: typeof NOTIFICATIONS
139
- }
140
- }
141
- ```
142
-
143
- ```ts
144
- // usage.ts — typed emit, unknown kinds fail tsc:
145
- import { emit } from 'basalt-ui/notifications'
146
-
147
- emit('upload:success', { name: 'photo.jpg' }) // ✓ typed payload
148
- emit('upload:error', null) // ✓
149
- emit('nonexistent', null) // ✗ tsc error
150
- ```
151
-
152
- `emit` resolves `intent` + calls `toMessage(payload)` from the registered spec, then calls
153
- `notify()`. Pass extra `NotifyOptions` as a third argument to override spec defaults.
154
-
155
- ## defineNotification (single-spec helper)
156
-
157
- Mirror of `defineOverlay` from `./commands` — types a single notification spec without registering
158
- it. Use when splitting notification definitions across files before merging into `defineNotifications`.
159
-
160
- ```ts
161
- import { defineNotification, defineNotifications } from 'basalt-ui/notifications'
162
-
163
- // Type an isolated spec constant — useful when splitting across files:
164
- const uploadSuccess = defineNotification({
165
- intent: 'success',
166
- toMessage: (p: { name: string }) => `Uploaded ${p.name}`,
167
- })
168
-
169
- // Merge into the full registry (defineNotifications does the actual registration):
170
- export const NOTIFICATIONS = defineNotifications({ 'upload:success': uploadSuccess })
171
- ```
172
-
173
- WARNING: `defineNotification` only TYPES — it does NOT register. Only `defineNotifications(map)`
174
- registers. Calling `emit` without a prior `defineNotifications` is a no-op at runtime.
175
-
176
- ### `NotificationSpec` has no `title`/`icon` — by design
177
-
178
- `NotificationSpec` is `{ intent, toMessage? }` — deliberately no `title`/`icon` field. Those stay
179
- per-call `NotifyOptions` (`emit(kind, payload, { title: '…' })` or `notify({ ..., title: '…' })`),
180
- so a title that varies per call site isn't forced into the registry. If every call to a given kind
181
- repeats the same title, pass it at each call site — there's no registry-level default to fall back
182
- on.
183
-
184
- ### Colors beyond the 4 intents live outside the registry
185
-
186
- `notify`/`emit` only resolve `intent` to one of the four Mantine colors (§ Intent → Mantine color
187
- above) — there's no `intent` override and no raw-color escape in `NotifyOptions`. A feature needing
188
- per-category colored toasts beyond `success`/`info`/`warning`/`error` (e.g. achievement toasts keyed
189
- by achievement type) cannot ride the typed registry. The documented stance: call Mantine's raw
190
- `notifications.show({ color, ... })` directly, outside `notify`/`emit`. The cost is explicit — a
191
- raw `notifications.show` toast is invisible to the persisted history store, the bell, and the
192
- notification center, since only `notify`/`notifyPromise` record to history.
193
-
194
- ## Notification history store
195
-
196
- Every `notify()` / `notifyPromise()` call records to a module-level persisted external store
197
- (own `useSyncExternalStore` + versioned `{ v, value }` envelope under `basalt:notifications` in
198
- localStorage). The store is **not** hook-scoped — `add`, `markRead`, `markAllRead`, `clear` are
199
- module-level and callable outside React.
200
-
201
- Ring-buffer cap: 50 items. Items store `{ id, intent, title?, message, createdAt, readAt? }` —
202
- **message and title are strings** (ReactNode is not serializable). Cross-tab sync via the
203
- `storage` event.
204
-
205
- ### useNotificationHistory
206
-
207
- ```ts
208
- import { useNotificationHistory } from 'basalt-ui/notifications'
209
-
210
- const { items, unreadCount, markRead, markAllRead, clear } = useNotificationHistory()
211
- ```
212
-
213
- | Field | Type | Description |
214
- | --------------- | --------------------------- | ------------------------------------------ |
215
- | `items` | `NotificationHistoryItem[]` | All history (newest first, ring-buffer 50) |
216
- | `unreadCount` | `number` | Items with no `readAt` |
217
- | `markRead(id)` | `(id: string) => void` | Mark one item read |
218
- | `markAllRead()` | `() => void` | Mark all items read |
219
- | `clear()` | `() => void` | Clear all history |
220
-
221
- ## NotificationBell
222
-
223
- A Mantine `ActionIcon` + `Indicator` showing unread count. Opens a `NotificationCenter` in a
224
- `Popover`. Marks all items read when the center opens. Designed for the shell `globalActions` slot.
225
-
226
- ```tsx
227
- import { NotificationBell } from 'basalt-ui/notifications'
228
- import { BasaltShell } from 'basalt-ui'
229
- ;<BasaltShell globalActions={<NotificationBell />} {...rest} />
230
- ```
231
-
232
- The bell renders a bell SVG icon (no `@tabler/icons` dependency — icons are ReactNode per house
233
- rules). The unread indicator is capped at `9+` for counts above 9.
234
-
235
- ## NotificationCenter
236
-
237
- Lists history items (title, message, relative time, unread dot) with "Mark all read" and "Clear"
238
- actions. Consumes `useNotificationHistory`. Composable — mount it inside any Popover, Drawer, or
239
- panel:
240
-
241
- ```tsx
242
- import { NotificationCenter } from 'basalt-ui/notifications'
243
- import { Drawer } from '@mantine/core'
244
- ;<Drawer opened={opened} onClose={close} title="Notifications">
245
- <NotificationCenter maxHeight={500} />
246
- </Drawer>
247
- ```
248
-
249
- `NotificationBell` uses `NotificationCenter` internally. Mount `NotificationCenter` directly only
250
- when you need a custom container (e.g. a full-page notifications view or a custom Drawer).
251
-
252
- ## Deferred (not yet shipped)
253
-
254
- The following features are advisory notes for future work — they are NOT part of the current battery:
255
-
256
- - **Web Push** (`usePushSubscription` + `sw.js` + Elysia push route): server-side push delivery
257
- for background notifications. Requires a service worker, a push endpoint, and a vapid key.
258
- - **Visibility-aware delivery buffering**: buffer toasts while the tab is hidden (`document.hidden`)
259
- and flush on `visibilitychange` — prevents lost toasts when the tab is backgrounded.
260
- - **IntersectionObserver per-item mark-seen**: replace the mark-read-on-open approach with
261
- per-item IntersectionObserver so only viewed items are marked read (higher fidelity).
262
- - **Cross-device read-sync**: propagate `readAt` via an Elysia endpoint so read state syncs
263
- across browser sessions (requires auth and a backend store).
264
- - **Quiet hours**: suppress non-error toasts during a configurable time window.
@@ -1,187 +0,0 @@
1
- ---
2
- source: basalt-ui
3
- description: TanStack Query conventions for basalt-ui apps — query factories, Suspense reads, and mutation/invalidation. basalt-ui ships ./query (createBasaltQueryClient + unwrap + devtools); this rule covers the advisory consumer patterns on top.
4
- paths:
5
- - 'src/lib/queries/**'
6
- - 'apps/**/src/lib/queries/**'
7
- - 'src/routes/**'
8
- - 'apps/**/src/routes/**'
9
- ---
10
-
11
- # Basalt Query — TanStack Query Conventions
12
-
13
- basalt-ui ships `./query` — a thin TanStack Query adapter providing `createBasaltQueryClient`,
14
- `unwrap`, `BasaltQueryDevtools`, and convenience re-exports (`QueryClientProvider`,
15
- `QueryErrorResetBoundary`, `useQueryErrorResetBoundary`). @tanstack/react-query is a **required**
16
- peer — `BasaltProvider` hard-requires it at build time.
17
-
18
- `./query` also re-exports the primary fetch hooks so consumers never need to dual-import from both
19
- `basalt-ui/query` and `@tanstack/react-query`:
20
-
21
- ```ts
22
- import {
23
- useQuery,
24
- useSuspenseQuery,
25
- useMutation,
26
- useQueryClient,
27
- useInfiniteQuery,
28
- queryOptions,
29
- } from 'basalt-ui/query'
30
- ```
31
-
32
- This rule covers the advisory consumer patterns on top of that adapter. Nothing in the framework
33
- enforces these conventions beyond the `unwrap` seam.
34
-
35
- ## Query factories
36
-
37
- Each resource has a factory in `src/lib/queries/<resource>.ts`:
38
-
39
- ```ts
40
- import { queryOptions, useSuspenseQuery } from 'basalt-ui/query'
41
- import { unwrap } from 'basalt-ui/query'
42
- import { api } from '../api-client'
43
-
44
- export const resourceQueries = {
45
- list: (params: ListParams) =>
46
- queryOptions({
47
- queryKey: ['resource', 'list', params],
48
- queryFn: () => unwrap(api.resource.get({ query: params })),
49
- }),
50
- summary: () =>
51
- queryOptions({
52
- queryKey: ['resource', 'summary'],
53
- queryFn: () => unwrap(api.resource.summary.get()),
54
- }),
55
- }
56
- ```
57
-
58
- - Key hierarchy: `[resource, action, ...params]`.
59
- - `queryOptions` and all hooks import from `basalt-ui/query` — do not dual-import from
60
- `@tanstack/react-query` directly.
61
- - `queryFn` unwraps typed responses via `unwrap()` — throws on the error branch so failures surface
62
- to the nearest error boundary or TanStack Query's error state. Also throws when `data` is `null`
63
- with no error (e.g. a 204 No Content or a silent transport failure that returns
64
- `{ data: null, error: null }`). Both cases are bugs the caller must address.
65
- - Never use raw strings as query keys in components — always import from the factory.
66
-
67
- ## Reading data in components
68
-
69
- ```ts
70
- import { useSuspenseQuery } from 'basalt-ui/query'
71
-
72
- const { data } = useSuspenseQuery(resourceQueries.summary())
73
- ```
74
-
75
- - Always `useSuspenseQuery` inside loader-prefetched routes (never `useQuery`) — pairs with the
76
- router's `ensureQueryData` (see basalt-router.md).
77
- - Wrap pages or panels in `<Suspense fallback={…}>` at the route level.
78
-
79
- ## Rendering a query — use `QueryState`, don't write the switch
80
-
81
- ```tsx
82
- import { QueryState } from 'basalt-ui' // NOT basalt-ui/query — it renders Mantine
83
-
84
- function Library() {
85
- const q = useQuery(imageQueries.list())
86
- return (
87
- <QueryState
88
- query={q}
89
- variant="page" // or "section" for a panel inside a page
90
- errorTitle="Could not load images"
91
- empty={{ title: 'No images yet', description: 'Upload one to get started.' }}
92
- isEmpty={(d) => d.items.length === 0}
93
- >
94
- {(data) => <ImageGrid items={data.items} />}
95
- </QueryState>
96
- )
97
- }
98
- ```
99
-
100
- **Rendering only the empty branch is the bug this retires.** A consumer's library showed
101
- `No images` on a **500**, and a share detail showed `Share not found` on a dropped connection —
102
- "nothing here" is what an app renders when nobody wrote the other three branches. Swapping the
103
- import replaced 204 hand-rolled lines across 10 call sites there, with no prop or JSX change.
104
-
105
- Precedence, in evaluation order:
106
-
107
- | Condition | Renders |
108
- | ----------------------------------- | ------------------------------------------------------------------------- |
109
- | `isError`, no `data` | `ErrorState` — `errorTitle` / `errorFallback` / `errorAction`, with Retry |
110
- | no `data`, `fetchStatus === 'idle'` | the `empty` copy (nothing, if `empty` is omitted) |
111
- | no `data` | `loading`, else `LoadingState` |
112
- | `data`, `isEmpty(data)` | the `empty` copy |
113
- | `data` | `children` — a node, or `(data) => node` |
114
- | `data` **and** `isError` | a fixed _Showing cached data_ banner above `children` |
115
-
116
- `errorTitle` / `errorFallback` / `errorAction` reach the no-data error branch only; the stale-data
117
- banner carries its own copy. `empty` is `{ title, description?, icon?, action? }` — `description`
118
- is optional, so a compact panel needs no invented second sentence.
119
-
120
- **`useQuery`, not `useSuspenseQuery`.** A suspense read never hands you an undefined `data` or an
121
- error branch — it throws to the nearest boundary — so `QueryState` has nothing to decide there.
122
- Suspense + an error boundary and `QueryState` are the two ways to render a query; pick one per read.
123
-
124
- `LoadingState` and `ErrorState` ship beside it as the escape hatch for a page that must place its
125
- branches in different DOM positions.
126
-
127
- **`query` is a structural subset, checked at runtime.** It needs `{ data, isError, error,
128
- fetchStatus, refetch }` — a TanStack `UseQueryResult` satisfies it, and a hand-composed object is
129
- legal. Because the subset gives up the compiler's help, a missing `isError` **throws** naming the
130
- field rather than rendering a false claim about the data.
131
-
132
- Decoding an unknown error into copy is `toErrorMessage(err, fallback?)` from `basalt-ui/query`,
133
- with `errorStatus(err)` for the status; `toErrorMessage` folds the status into the fallback when the
134
- body decodes to nothing readable, so an opaque envelope never renders as `{}`.
135
-
136
- ## Mutations
137
-
138
- ```ts
139
- const mutation = useMutation({
140
- mutationFn: (body) => unwrap(api.resource.post({ body })),
141
- onSuccess: () => {
142
- queryClient.invalidateQueries({ queryKey: ['resource'] })
143
- notifications.show({ message: 'Saved', color: 'green' })
144
- },
145
- })
146
- ```
147
-
148
- - Invalidate by resource prefix (first key segment) — catches list + summary at once.
149
- - Show a notification on success; let errors bubble to the global error boundary.
150
- - For the in-button success flip (loading spinner → green `IconCheck`), drive it off `mutation.isPending`
151
- / `mutation.isSuccess` — see the interaction-feedback section in basalt-mantine.md.
152
-
153
- ## DevTools
154
-
155
- Use `BasaltQueryDevtools` from `basalt-ui/query` — it is lazy, production-excluded, and safe to
156
- import unconditionally:
157
-
158
- ```tsx
159
- import { BasaltQueryDevtools, QueryClientProvider } from 'basalt-ui/query'
160
-
161
- function Root() {
162
- return (
163
- <QueryClientProvider client={client}>
164
- {children}
165
- <BasaltQueryDevtools initialIsOpen={false} />
166
- </QueryClientProvider>
167
- )
168
- }
169
- ```
170
-
171
- Never import `@tanstack/react-query-devtools` directly in app code — it is an optional peer and
172
- the lazy wrapper handles the production guard for you.
173
-
174
- ## Eden footguns (hard authoring rules)
175
-
176
- Two patterns silently degrade Eden Treaty inference to `any` — both must be avoided:
177
-
178
- 1. **Non-chained route definitions**: Elysia routes MUST be method-chained on the `app` instance.
179
- A standalone `app.get(...)` (reassigned or called without chaining back to the root) drops the
180
- `App` type to `any`, which propagates into the Treaty client and loses all type safety.
181
-
182
- 2. **Mismatched tsconfig path aliases**: The client package and the server package MUST share or
183
- extend one root `tsconfig` so path aliases (`~/*`, `@/*`, etc.) resolve identically in both.
184
- A mismatch causes Eden's type extraction to fail silently and degrade to `any`.
185
-
186
- Both footguns produce no TypeScript error at the point of breakage — they only manifest as `any`
187
- types on the Treaty client, making them hard to spot. Check these first when Eden types regress.