basalt-ui 1.19.1 → 1.21.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 (129) hide show
  1. package/AGENTS.md +19 -1
  2. package/CHANGELOG.md +39 -0
  3. package/MIGRATING.md +326 -0
  4. package/README.md +87 -9
  5. package/agent/rules/basalt-app.md +84 -0
  6. package/agent/rules/basalt-charts.md +32 -17
  7. package/agent/rules/basalt-content.md +4 -0
  8. package/agent/rules/basalt-mantine.md +25 -10
  9. package/agent/rules/basalt-router.md +79 -15
  10. package/agent/rules/basalt-state.md +21 -3
  11. package/agent/rules/basalt-tokens.md +132 -6
  12. package/agent/skills/basalt-app/SKILL.md +45 -6
  13. package/agent/skills/basalt-charts/SKILL.md +5 -3
  14. package/agent/templates/CLAUDE-block.md.tpl +11 -5
  15. package/configs/check.yml +11 -4
  16. package/configs/lefthook.yml +59 -7
  17. package/configs/oxlint-plugin.js +633 -30
  18. package/configs/oxlint-plugin.test.ts +594 -15
  19. package/configs/oxlint.json +10 -1
  20. package/dist/agent/stick-to-bottom.js +1 -1
  21. package/dist/agent-chat/thread-detail-panel.js +1 -1
  22. package/dist/agent-chat/thread-message.js +2 -2
  23. package/dist/agent-chat/tool-chip.js +1 -1
  24. package/dist/charts/kinds/Bars.d.ts +1 -1
  25. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  26. package/dist/charts/kinds/Donut.d.ts +1 -1
  27. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  28. package/dist/charts/kinds/Donut.js +1 -1
  29. package/dist/charts/kinds/DualPanel.d.ts +1 -1
  30. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  31. package/dist/charts/kinds/Heatmap.d.ts +1 -1
  32. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  33. package/dist/charts/kinds/MultiLine.d.ts +1 -1
  34. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  35. package/dist/charts/kinds/StackedArea.d.ts +1 -1
  36. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  37. package/dist/charts/kinds/ZonedLine.d.ts +1 -1
  38. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  39. package/dist/charts/primitives/ChartLegend.js +2 -2
  40. package/dist/charts/primitives/ChartTooltip.js +1 -1
  41. package/dist/cli/index.d.ts +146 -20
  42. package/dist/cli/index.d.ts.map +1 -1
  43. package/dist/cli/index.js +901 -101
  44. package/dist/cli/toolchain-wiring.test.d.ts +2 -0
  45. package/dist/cli/toolchain-wiring.test.d.ts.map +1 -0
  46. package/dist/cli/waiver-audit.test.d.ts +2 -0
  47. package/dist/cli/waiver-audit.test.d.ts.map +1 -0
  48. package/dist/dashboard/delta-badge.js +2 -2
  49. package/dist/dashboard/stat-card.js +1 -1
  50. package/dist/data/virtual-list.js +5 -5
  51. package/dist/guard/index.d.ts +101 -2
  52. package/dist/guard/index.d.ts.map +1 -1
  53. package/dist/guard/index.js +474 -62
  54. package/dist/guard/types.d.ts +25 -5
  55. package/dist/guard/types.d.ts.map +1 -1
  56. package/dist/register.d.ts +25 -0
  57. package/dist/register.d.ts.map +1 -1
  58. package/dist/router-tanstack/multi-search-param-store.d.ts +13 -0
  59. package/dist/router-tanstack/multi-search-param-store.d.ts.map +1 -1
  60. package/dist/router-tanstack/multi-search-param-store.js +4 -1
  61. package/dist/router-tanstack/search-param-store.d.ts +29 -5
  62. package/dist/router-tanstack/search-param-store.d.ts.map +1 -1
  63. package/dist/router-tanstack/search-param-store.js +31 -3
  64. package/dist/router-tanstack/search-param-store.test.d.ts +2 -0
  65. package/dist/router-tanstack/search-param-store.test.d.ts.map +1 -0
  66. package/dist/shell/app-mobile-nav.module.css +11 -3
  67. package/dist/shell/index.d.ts +12 -5
  68. package/dist/shell/index.d.ts.map +1 -1
  69. package/dist/shell/index.js +24 -6
  70. package/dist/surfaces.d.ts +1 -1
  71. package/dist/surfaces.js +1 -1
  72. package/dist/tokens/index.d.ts +5 -3
  73. package/dist/tokens/index.d.ts.map +1 -1
  74. package/dist/tokens/index.js +7 -1
  75. package/dist/tokens/palette.d.ts +35 -27
  76. package/dist/tokens/palette.d.ts.map +1 -1
  77. package/dist/tokens/palette.js +24 -18
  78. package/dist/tokens/text-scale.test.d.ts +2 -0
  79. package/dist/tokens/text-scale.test.d.ts.map +1 -0
  80. package/dist/tokens.css +21 -17
  81. package/dist/vite-color-mix.js +2 -2
  82. package/dist/vite.d.ts +24 -2
  83. package/dist/vite.d.ts.map +1 -1
  84. package/dist/vite.js +54 -13
  85. package/llms.txt +3 -3
  86. package/package.json +4 -3
  87. package/src/agent/stick-to-bottom.tsx +1 -1
  88. package/src/agent-chat/thread-detail-panel.tsx +1 -1
  89. package/src/agent-chat/thread-message.tsx +3 -3
  90. package/src/agent-chat/tool-chip.tsx +1 -1
  91. package/src/charts/kinds/Bars.tsx +1 -1
  92. package/src/charts/kinds/Donut.tsx +3 -2
  93. package/src/charts/kinds/DualPanel.tsx +2 -2
  94. package/src/charts/kinds/Heatmap.tsx +1 -1
  95. package/src/charts/kinds/MultiLine.tsx +1 -1
  96. package/src/charts/kinds/StackedArea.tsx +1 -1
  97. package/src/charts/kinds/ZonedLine.tsx +1 -1
  98. package/src/charts/primitives/ChartLegend.tsx +2 -2
  99. package/src/charts/primitives/ChartTooltip.tsx +1 -1
  100. package/src/cli/index.ts +1614 -164
  101. package/src/cli/init-sync.test.ts +21 -3
  102. package/src/cli/template-ledger.test.ts +1 -1
  103. package/src/cli/tokens-css.test.ts +27 -14
  104. package/src/cli/toolchain-wiring.test.ts +856 -0
  105. package/src/cli/waiver-audit.test.ts +131 -0
  106. package/src/dashboard/delta-badge.tsx +3 -3
  107. package/src/dashboard/stat-card.tsx +1 -1
  108. package/src/data/virtual-list.tsx +8 -8
  109. package/src/guard/check-source.test.ts +1104 -12
  110. package/src/guard/index.ts +1106 -103
  111. package/src/guard/types.ts +29 -4
  112. package/src/register.ts +32 -0
  113. package/src/router-tanstack/multi-search-param-store.ts +16 -1
  114. package/src/router-tanstack/search-param-store.test.ts +135 -0
  115. package/src/router-tanstack/search-param-store.ts +90 -8
  116. package/src/shell/app-mobile-nav.module.css +11 -3
  117. package/src/shell/app-mobile-nav.tsx +1 -1
  118. package/src/shell/index.test.tsx +52 -0
  119. package/src/shell/index.tsx +64 -13
  120. package/src/surfaces.ts +1 -1
  121. package/src/theme/density-relations.test.ts +8 -5
  122. package/src/theme/spacing.test.ts +2 -0
  123. package/src/theme-toggle/index.tsx +2 -2
  124. package/src/tokens/index.ts +28 -3
  125. package/src/tokens/palette.ts +24 -18
  126. package/src/tokens/text-scale.test.ts +51 -0
  127. package/src/vite-color-mix.ts +2 -2
  128. package/src/vite.test.ts +125 -6
  129. package/src/vite.ts +148 -20
@@ -119,6 +119,12 @@ describe('doctor — CLI vs installed basalt-ui version', () => {
119
119
  ).version
120
120
 
121
121
  it('warns when node_modules/basalt-ui version differs from the running CLI', () => {
122
+ // A Mantine dependency puts doctor in the framework profile — without one it reads as a
123
+ // tokens-only consumer, where the missing scaffold is not a failure at all.
124
+ writeFixture(
125
+ 'package.json',
126
+ JSON.stringify({ name: 'fixture', dependencies: { '@mantine/core': '^9' } }),
127
+ )
122
128
  writeFixture(
123
129
  'node_modules/basalt-ui/package.json',
124
130
  JSON.stringify({ name: 'basalt-ui', version: '0.4.2' }),
@@ -132,21 +138,33 @@ describe('doctor — CLI vs installed basalt-ui version', () => {
132
138
  })
133
139
 
134
140
  it('passes when node_modules/basalt-ui version matches the running CLI', () => {
141
+ // A full fixture, not just the manifest: doctor also asserts the guard scans something and the
142
+ // oxlint preset is wired, and a green exit has to mean all of it.
143
+ writeFixture('package.json', JSON.stringify({ name: 'fixture', basalt: { roots: ['src'] } }))
144
+ writeFixture('src/app.tsx', 'export const App = () => null\n')
145
+ writeFixture(
146
+ '.oxlintrc.json',
147
+ '{ "extends": ["./node_modules/basalt-ui/configs/oxlint.json"] }',
148
+ )
135
149
  writeFixture(MANIFEST_PATH, JSON.stringify({ version: 1, files: {} }, null, 2))
136
150
  writeFixture(
137
151
  'node_modules/basalt-ui/package.json',
138
152
  JSON.stringify({ name: 'basalt-ui', version: cliVersion }),
139
153
  )
154
+ // The `extends` above has to RESOLVE, not merely read correctly — see the oxlint-preset check.
155
+ writeFixture('node_modules/basalt-ui/configs/oxlint.json', '{}')
140
156
  const { code, log } = capture(() => doctor(dir))
141
157
  expect(code).toBe(0)
142
158
  expect(log).toContain('matches the installed basalt-ui')
143
159
  })
144
160
 
145
- it('is silent (no node_modules/basalt-ui present) when the package cannot be resolved', () => {
161
+ it('reports the version checks as SKIPPED when the package cannot be resolved', () => {
146
162
  writeFixture(MANIFEST_PATH, JSON.stringify({ version: 1, files: {} }, null, 2))
147
- const { log } = capture(() => doctor(dir))
148
- expect(log).not.toContain('differs from the installed basalt-ui')
163
+ const { code, log } = capture(() => doctor(dir))
149
164
  expect(log).not.toContain('matches the installed basalt-ui')
165
+ // Silence was the bug: two of five checks used to vanish while the footer still read green.
166
+ expect(log).toContain('SKIPPED')
167
+ expect(code).toBe(1)
150
168
  })
151
169
  })
152
170
 
@@ -116,7 +116,7 @@ describe('classify ledger: cross-version format-only drift vs a real edit', () =
116
116
  // older version token — this is what makes the scenario cross-version, not same-version.
117
117
  const olderBlock = currentBlock.replace(/basalt-ui/g, 'basalt')
118
118
  expect(olderBlock).not.toBe(currentBlock)
119
- expect(olderBlock).toContain('bunx basalt sync')
119
+ expect(olderBlock).toContain('basalt sync')
120
120
 
121
121
  // What a PRE-1.0.2 CLI's writeUnit actually recorded at that sync: a RAW hash of the exact
122
122
  // bytes it wrote — the fix only normalizes hashes written by a CLI carrying it, so a genuine
@@ -1,10 +1,13 @@
1
1
  /**
2
2
  * `basalt-ui tokens:css` — the install-free path to the token system.
3
3
  *
4
- * The contract these tests hold is that the command is a FLAG PARSER, nothing more: whatever it
5
- * prints must be exactly what `buildPaletteCss` returns for the same options. If the CLI ever grows
6
- * emission logic of its own, a consumer who ran `bunx basalt-ui tokens:css` and a consumer who
7
- * imported `basalt-ui/tokens` would be looking at two different design systems.
4
+ * The contract these tests hold is that the command holds no emission logic of its own: the token
5
+ * VALUES it prints must be exactly what `buildPaletteCss` returns for the same options, or a
6
+ * consumer who ran `bunx basalt-ui tokens:css` and one who imported `basalt-ui/tokens` would be
7
+ * looking at two different design systems. What the command DOES add is file framing for an
8
+ * artifact a consumer commits — the `@generated` header, a trailing newline, `rgba()` spacing —
9
+ * which `body()` below strips before every comparison. See `./toolchain-wiring.test.ts` for the
10
+ * framing's own tests.
8
11
  */
9
12
  import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
10
13
  import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
@@ -12,7 +15,7 @@ import { tmpdir } from 'node:os'
12
15
  import { join, resolve } from 'node:path'
13
16
 
14
17
  import { buildPaletteCss } from '../tokens'
15
- import { run, tokensCss } from './index'
18
+ import { normalizeColorFunctions, run, tokensCss } from './index'
16
19
 
17
20
  const FIXTURE = join(import.meta.dir, '..', '..', 'tests', 'fixtures', 'palette-default.css')
18
21
 
@@ -34,27 +37,37 @@ function emit(flags: string[], out = 'out.css'): string {
34
37
  return readFileSync(resolve(dir, out), 'utf8')
35
38
  }
36
39
 
40
+ /** The emitted file minus the two-line generated header and the trailing newline. */
41
+ function body(flags: string[], out = 'out.css'): string {
42
+ return emit(flags, out).split('\n').slice(2).join('\n').replace(/\n$/, '')
43
+ }
44
+
45
+ /** What `buildPaletteCss` returns, in the CLI's commit-safe formatting. */
46
+ function expected(opts: Parameters<typeof buildPaletteCss>[0]): string {
47
+ return normalizeColorFunctions(buildPaletteCss(opts))
48
+ }
49
+
37
50
  describe('tokens:css', () => {
38
- it('with no flags emits the shipped default, byte for byte', () => {
39
- expect(emit([])).toBe(readFileSync(FIXTURE, 'utf8'))
51
+ it('with no flags emits the shipped default, byte for byte under the header', () => {
52
+ expect(body([])).toBe(normalizeColorFunctions(readFileSync(FIXTURE, 'utf8')))
40
53
  })
41
54
 
42
55
  it('forwards the selector flags to buildPaletteCss and adds nothing of its own', () => {
43
56
  const flags = ['--selector-attribute', 'data-theme', '--default-scheme', 'light']
44
- expect(emit(flags)).toBe(
45
- buildPaletteCss({ scheme: { attribute: 'data-theme' }, defaultScheme: 'light' }),
57
+ expect(body(flags)).toBe(
58
+ expected({ scheme: { attribute: 'data-theme' }, defaultScheme: 'light' }),
46
59
  )
47
60
  })
48
61
 
49
62
  it('forwards the scheme VALUES and the media fallback', () => {
50
63
  const flags = ['--dark-value', 'night', '--light-value', 'day', '--media-fallback']
51
- expect(emit(flags)).toBe(
52
- buildPaletteCss({ scheme: { darkValue: 'night', lightValue: 'day' }, mediaFallback: true }),
64
+ expect(body(flags)).toBe(
65
+ expected({ scheme: { darkValue: 'night', lightValue: 'day' }, mediaFallback: true }),
53
66
  )
54
67
  })
55
68
 
56
69
  it('forwards --only core', () => {
57
- expect(emit(['--only', 'core'])).toBe(buildPaletteCss({ only: 'core' }))
70
+ expect(body(['--only', 'core'])).toBe(expected({ only: 'core' }))
58
71
  })
59
72
 
60
73
  it('rejects an unknown --only instead of silently emitting everything', () => {
@@ -64,7 +77,7 @@ describe('tokens:css', () => {
64
77
 
65
78
  it('creates the parent directory of --out', () => {
66
79
  const css = emit([], 'nested/deep/tokens.css')
67
- expect(css.startsWith(':root {')).toBe(true)
80
+ expect(css).toContain(':root {')
68
81
  })
69
82
 
70
83
  it('rejects an unknown --default-scheme instead of silently defaulting', () => {
@@ -87,7 +100,7 @@ describe('tokens:css', () => {
87
100
  })
88
101
 
89
102
  it('--no-legacy-aliases output is byte-identical to buildPaletteCss({ legacyAliases: false }) — the CLI holds no emission logic of its own', () => {
90
- expect(emit(['--no-legacy-aliases'])).toBe(buildPaletteCss({ legacyAliases: false }))
103
+ expect(body(['--no-legacy-aliases'])).toBe(expected({ legacyAliases: false }))
91
104
  })
92
105
 
93
106
  it('`--help` short-circuits before the command runs — no file written', () => {