basalt-ui 1.6.0 → 1.7.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 (43) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/agent/rules/basalt-mantine.md +80 -40
  4. package/agent/rules/basalt-tokens.md +3 -1
  5. package/agent/skills/basalt-design/SKILL.md +21 -7
  6. package/agent/templates/CLAUDE-block.md.tpl +3 -2
  7. package/agent/templates/DESIGN.md.tpl +2 -1
  8. package/configs/oxlint-plugin.js +75 -1
  9. package/configs/oxlint.json +1 -0
  10. package/dist/cli/index.d.ts +9 -0
  11. package/dist/cli/index.d.ts.map +1 -1
  12. package/dist/cli/index.js +2 -0
  13. package/dist/dashboard/index.d.ts +1 -1
  14. package/dist/dashboard/index.d.ts.map +1 -1
  15. package/dist/dashboard/stat-card.d.ts +6 -1
  16. package/dist/dashboard/stat-card.d.ts.map +1 -1
  17. package/dist/dashboard/stat-card.js +37 -4
  18. package/dist/guard/index.d.ts +7 -1
  19. package/dist/guard/index.d.ts.map +1 -1
  20. package/dist/guard/index.js +16 -1
  21. package/dist/guard/types.d.ts +18 -3
  22. package/dist/guard/types.d.ts.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/surfaces.d.ts +2 -2
  26. package/dist/surfaces.d.ts.map +1 -1
  27. package/dist/surfaces.js +8 -2
  28. package/dist/theme/controls.module.css +5 -3
  29. package/dist/vite.d.ts +6 -0
  30. package/dist/vite.d.ts.map +1 -1
  31. package/dist/vite.js +21 -1
  32. package/llms.txt +3 -3
  33. package/package.json +1 -1
  34. package/src/cli/index.ts +11 -0
  35. package/src/dashboard/index.ts +1 -1
  36. package/src/dashboard/stat-card.tsx +61 -2
  37. package/src/guard/check-source.test.ts +63 -1
  38. package/src/guard/index.ts +39 -3
  39. package/src/guard/types.ts +18 -2
  40. package/src/index.ts +1 -0
  41. package/src/surfaces.ts +8 -2
  42. package/src/theme/controls.module.css +5 -3
  43. package/src/vite.ts +62 -1
package/src/index.ts CHANGED
@@ -76,6 +76,7 @@ export {
76
76
  type DeltaBadgeProps,
77
77
  StatCard,
78
78
  type StatCardProps,
79
+ type StatCardTone,
79
80
  EmptyState,
80
81
  type EmptyStateProps,
81
82
  SettingsSection,
package/src/surfaces.ts CHANGED
@@ -186,9 +186,15 @@ export const SURFACES = {
186
186
  layer: 'mantine-coupled',
187
187
  rule: 'mantine',
188
188
  skill: ['basalt-app', 'basalt-design'],
189
- guardKinds: ['raw-motion-value', 'card-with-border', 'raw-form-control', 'sub-16-input-font'],
189
+ guardKinds: [
190
+ 'raw-motion-value',
191
+ 'card-with-border',
192
+ 'raw-form-control',
193
+ 'sub-16-input-font',
194
+ 'mantine-shade-index',
195
+ ],
190
196
  description:
191
- 'BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard, EmptyState, SettingsSection/SettingsRow/DangerZone)',
197
+ 'BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard with threshold tone, EmptyState, SettingsSection/SettingsRow/DangerZone)',
192
198
  optionalPeers: ['react-markdown', 'remark-gfm', 'shiki', 'beautiful-mermaid'],
193
199
  forbiddenImports: [], // the no-charts/tokens-reexport invariant is comment-only today; Phase-4 plugin
194
200
  },
@@ -214,8 +214,10 @@ html[data-mantine-color-scheme] .inputWrapper[data-variant='default']:not([data-
214
214
  }
215
215
 
216
216
  /*
217
- * CheckboxCard / RadioCard (`withBorder`, the default): panel bg + `shadow-card`, no border — the
218
- * same triad as Card/Paper. Mantine's own border-color rule wraps `[data-with-border]` in
217
+ * CheckboxCard / RadioCard (`withBorder`, the default): panel bg + depth, no border — the same
218
+ * triad as Card/Paper, but on `shadow-raised`, since a selection card is something you click (the
219
+ * control-vs-panel line, see the header above). Mantine's own border-color rule wraps
220
+ * `[data-with-border]` in
219
221
  * `:where()` (0 specificity), so a plain class + real attribute selector ((0,2,0)) already wins;
220
222
  * no `html[…]` prefix fight needed, same reasoning as Button/ActionIcon above.
221
223
  */
@@ -240,7 +242,7 @@ html[data-mantine-color-scheme] .inputWrapper[data-variant='default']:not([data-
240
242
 
241
243
  /*
242
244
  * Focus = accent border (via --input-bd-focus above) + a subtle accent ring layered over the
243
- * resting `--field-depth`, so the field keeps its shadow-card depth while focused.
245
+ * resting `--field-depth`, so the field keeps its `shadow-raised` depth while focused.
244
246
  */
245
247
  .input:focus,
246
248
  .input:focus-within {
package/src/vite.ts CHANGED
@@ -13,8 +13,13 @@
13
13
  *
14
14
  * NEVER reference `import.meta.env` here — this file is invoked inside the consumer's vite config
15
15
  * at config-evaluation time (plain Node), where only `process.env` exists.
16
+ *
17
+ * It also carries the enforcement notice (`warnIfUnenforced`): this is the one basalt seam that runs
18
+ * on every dev start and every build of a consumer app, so it is the only place that can tell an app
19
+ * it installed the components and skipped `basalt-ui init`.
16
20
  */
17
- import { resolve } from 'node:path'
21
+ import { existsSync } from 'node:fs'
22
+ import { dirname, resolve } from 'node:path'
18
23
  import { searchForWorkspaceRoot } from 'vite'
19
24
  import type { HtmlTagDescriptor, Plugin, PluginOption, UserConfig } from 'vite'
20
25
  import { SURFACE } from './tokens/palette'
@@ -46,6 +51,55 @@ export type BasaltViteOptions = {
46
51
  basaltSrc?: string
47
52
  /** App version surfaced via the `__APP_VERSION__` define. Defaults to `0.0.0`. */
48
53
  version?: string
54
+ /**
55
+ * Print a one-time notice when `basalt-ui init` has never been run in this project (no
56
+ * `.basalt/manifest.json` found at or above the cwd) — see `warnIfUnenforced`. Default `true`.
57
+ * Set `false` to adopt the components without the toolchain and stop being told about it.
58
+ */
59
+ enforcementNotice?: boolean
60
+ }
61
+
62
+ /** How many directories up from `cwd` to look for `.basalt/manifest.json`. Three covers the common
63
+ * shapes: config at the app root, an app in `web/` or `apps/<name>/` under a repo root. */
64
+ const UNENFORCED_SEARCH_DEPTH = 3
65
+
66
+ /** Set once the notice has been printed, so a watch-mode restart doesn't reprint it every reload. */
67
+ let unenforcedNoticePrinted = false
68
+
69
+ /**
70
+ * Print one notice when basalt-ui is installed as a component library and nothing else.
71
+ *
72
+ * `basalt-ui init` is what places the oxlint preset, the theme-guard wiring, the lefthook stub and
73
+ * the agent rules. Skipping it leaves a consumer with the components and NONE of the enforcement —
74
+ * and, critically, no signal that this is the case. The failure is silent and cumulative: every
75
+ * hand-rolled `<Card withBorder radius="md" padding="lg">` renders perfectly, so the app drifts a
76
+ * card idiom at a time until someone looks at a screenshot and asks why the design system did
77
+ * nothing. The first real consumer reached seven such cards across six files that way.
78
+ *
79
+ * This is the only seam basalt owns that runs on every dev start and every build, which makes it
80
+ * the only place that can say so at the time it is still cheap to fix. It stays a notice, never an
81
+ * error: declining to adopt the toolchain is a legitimate choice, and a build that fails because a
82
+ * lint preset is absent would be a worse bug than the one this prevents.
83
+ */
84
+ function warnIfUnenforced(cwd: string): void {
85
+ if (unenforcedNoticePrinted) return
86
+
87
+ let dir = cwd
88
+ for (let i = 0; i <= UNENFORCED_SEARCH_DEPTH; i++) {
89
+ if (existsSync(resolve(dir, '.basalt/manifest.json'))) return
90
+ const parent = dirname(dir)
91
+ if (parent === dir) break
92
+ dir = parent
93
+ }
94
+
95
+ unenforcedNoticePrinted = true
96
+ // oxlint-disable-next-line no-console -- a build-time notice has no other channel
97
+ console.warn(
98
+ '\n[basalt-ui] Installed, but `basalt-ui init` has never run here — no oxlint preset, no theme\n' +
99
+ ' guard, no agent rules. You have the components and none of the enforcement.\n' +
100
+ ' Fix: `bunx basalt-ui init`, then add `oxlint . && basalt-ui check-theme` as your\n' +
101
+ ' lint script. Silence: pass `enforcementNotice: false` to basaltViteConfig().\n',
102
+ )
49
103
  }
50
104
 
51
105
  export function basaltViteConfig(opts: BasaltViteOptions): UserConfig {
@@ -55,6 +109,13 @@ export function basaltViteConfig(opts: BasaltViteOptions): UserConfig {
55
109
  // on without editing its vite config. process.env (not import.meta.env) — this runs in Node.
56
110
  const basaltSrc = opts.basaltSrc ?? process.env['BASALT_LOCAL']
57
111
 
112
+ // Skipped under basaltSrc/BASALT_LOCAL: that path means basalt-ui itself is being developed from
113
+ // a sibling checkout (the playground, or a consumer debugging against local source), where the
114
+ // enforcement config lives in basalt's own repo and a manifest is not expected.
115
+ if (opts.enforcementNotice !== false && basaltSrc === undefined) {
116
+ warnIfUnenforced(process.cwd())
117
+ }
118
+
58
119
  const config: UserConfig = {
59
120
  define: {
60
121
  __APP_VERSION__: JSON.stringify(process.env['BUILD_VERSION'] ?? version),