basalt-ui 1.6.0 → 1.8.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 (45) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +19 -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 +24 -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 +8 -1
  16. package/dist/dashboard/stat-card.d.ts.map +1 -1
  17. package/dist/dashboard/stat-card.js +42 -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.test.tsx +81 -0
  37. package/src/dashboard/stat-card.tsx +75 -2
  38. package/src/guard/check-source.test.ts +64 -1
  39. package/src/guard/index.ts +42 -3
  40. package/src/guard/types.ts +18 -2
  41. package/src/index.ts +1 -0
  42. package/src/surfaces.ts +8 -2
  43. package/src/theme/contrast.test.ts +18 -1
  44. package/src/theme/controls.module.css +5 -3
  45. package/src/vite.ts +62 -1
@@ -13,6 +13,7 @@ const SURFACE_SHADOW = /\bboxShadow\s*:\s*(?!['"`]?[^'"`]*(?:var\(|\$\{))['"`][^
13
13
  const CARD_SURFACE_TAG = /<(?:Card|Paper)(?![\w.])(?:=>|[^>])*?>/g;
14
14
  const WITH_BORDER_PROP = /\bwithBorder\b(?!\s*=\s*\{\s*false\s*\})/;
15
15
  const OFF_SYSTEM_SURFACE_VAR = /var\(--mantine-color-(gray|dark)-\d/g;
16
+ const MANTINE_SHADE_INDEX = /\b(?:color|c|bg|backgroundColor|borderColor)\s*=\s*\{?\s*['"][a-z]+\.\d\b|var\(--mantine-color-(?!gray-|dark-)[a-z]+-\d/g;
16
17
  const RAW_HTML_TAG = /<(?:div|span|section|header|nav|footer|aside|main|article|ul|ol)\b/;
17
18
  const INLINE_STYLE = /style=\{\{/;
18
19
  const LAYOUT_SURFACE_PROP = /\b(?:display|padding|margin|gap|flex|grid|border|background|width|height)\b/;
@@ -59,6 +60,7 @@ const DEFAULT_GUARD_CONFIG = {
59
60
  spacingSteps: DEFAULT_SPACING_STEPS,
60
61
  rawRadius: true,
61
62
  forbiddenAccents: DEFAULT_FORBIDDEN_ACCENTS,
63
+ mantineShadeIndex: true,
62
64
  rawSurface: true,
63
65
  cardWithBorder: true,
64
66
  offSystemSurfaceVar: true,
@@ -75,7 +77,9 @@ const DEFAULT_GUARD_CONFIG = {
75
77
  exemptRules: {},
76
78
  severity: {}
77
79
  };
78
- const GRACE_PERIOD_KINDS = {};
80
+ const GRACE_PERIOD_KINDS = {
81
+ "mantine-shade-index": "introduced 1.7.0 \u2014 promote to error in 1.9.0"
82
+ };
79
83
  function severityOf(kind, cfg) {
80
84
  return cfg.severity?.[kind] ?? (kind in GRACE_PERIOD_KINDS ? "warn" : "error");
81
85
  }
@@ -283,6 +287,12 @@ const GUARD_RULES = {
283
287
  appliesTo: (relPath) => !relPath.endsWith(".css"),
284
288
  message: "For an off-identity accent use blue/gray or a status hue (red/green/orange/yellow)."
285
289
  },
290
+ "mantine-shade-index": {
291
+ kind: "mantine-shade-index",
292
+ pattern: MANTINE_SHADE_INDEX,
293
+ enabled: (cfg) => cfg.mantineShadeIndex,
294
+ message: 'Shade-pinned Mantine color \u2014 one fixed swatch in BOTH color schemes, so it cannot stay legible in either. Use VX.status.* / --vx-status-* for a verdict color, or the bare hue name (c="red") to let the theme resolve the shade per scheme.'
295
+ },
286
296
  "raw-spacing": {
287
297
  kind: "raw-spacing",
288
298
  pattern: (cfg) => new RegExp(
@@ -469,6 +479,11 @@ function checkSource(text, relPath, cfg) {
469
479
  findings.push({ relPath, line: i + 1, token: m[0], kind: "off-system-surface-var" });
470
480
  }
471
481
  }
482
+ if (GUARD_RULES["mantine-shade-index"].enabled(cfg)) {
483
+ for (const m of line.matchAll(GUARD_RULES["mantine-shade-index"].pattern)) {
484
+ findings.push({ relPath, line: i + 1, token: m[0], kind: "mantine-shade-index" });
485
+ }
486
+ }
472
487
  if (GUARD_RULES["raw-html-layout"].enabled(cfg) && ruleApplies("raw-html-layout", relPath) && RAW_HTML_TAG.test(line) && INLINE_STYLE.test(line) && LAYOUT_SURFACE_PROP.test(line)) {
473
488
  findings.push({ relPath, line: i + 1, token: "<raw-html style>", kind: "raw-html-layout" });
474
489
  }
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Guard types — Mantine-free, dependency-free (zero imports beyond TS types).
3
3
  *
4
- * GuardKind is the closed set of 19 violation kinds the theme guard can emit.
4
+ * GuardKind is the closed set of 20 violation kinds the theme guard can emit.
5
5
  * Finding is the structured result per violation, replacing the old `Violation` shape.
6
6
  * GuardConfig is the per-run configuration that drives checkSource.
7
7
  */
8
- /** The 19 theme-guard violation kinds. */
9
- export type GuardKind = 'raw-hex' | 'raw-color-fn' | 'localstorage-theme' | 'off-identity-accent' | 'raw-spacing' | 'raw-radius' | 'raw-surface' | 'card-with-border' | 'off-system-surface-var' | 'raw-html-layout' | 'inline-spacing' | 'inline-display' | 'raw-visx-axis' | 'raw-motion-value' | 'unframed-chart' | 'chart-missing-aria-label' | 'raw-form-control' | 'sub-16-input-font' | 'raw-font-family';
8
+ /** The 20 theme-guard violation kinds. */
9
+ export type GuardKind = 'raw-hex' | 'raw-color-fn' | 'localstorage-theme' | 'off-identity-accent' | 'mantine-shade-index' | 'raw-spacing' | 'raw-radius' | 'raw-surface' | 'card-with-border' | 'off-system-surface-var' | 'raw-html-layout' | 'inline-spacing' | 'inline-display' | 'raw-visx-axis' | 'raw-motion-value' | 'unframed-chart' | 'chart-missing-aria-label' | 'raw-form-control' | 'sub-16-input-font' | 'raw-font-family';
10
10
  /**
11
11
  * How hard a finding lands. `error` fails the build; `warn` reports and passes.
12
12
  *
@@ -59,6 +59,21 @@ export type GuardConfig = {
59
59
  readonly rawRadius: boolean;
60
60
  /** Off-identity Mantine accent families forbidden as chrome accents. */
61
61
  readonly forbiddenAccents: readonly string[];
62
+ /**
63
+ * Flag a SHADE-PINNED Mantine color — `c="yellow.7"`, `bg="blue.4"`, `var(--mantine-color-red-6)`.
64
+ *
65
+ * This is the sibling of `off-identity-accent`, and the two are deliberately disjoint. That kind
66
+ * polices WHICH hue you reach for and permits the bare status names (`c="red"`, `color="green"`)
67
+ * on purpose: they resolve through the theme, so they flip correctly across color schemes. This
68
+ * kind polices the SHADE INDEX, which does not — a pinned step is one fixed swatch in both
69
+ * schemes, so a `yellow.7` legible on the dark page is the one that fails contrast on the light
70
+ * one. Status color belongs on `VX.status.*` (`--vx-status-warn|bad|good|…`), which is emitted per
71
+ * scheme.
72
+ *
73
+ * `gray-*`/`dark-*` in `var()` form are excluded here because `off-system-surface-var` already
74
+ * owns them (surface color, its own message). Default true.
75
+ */
76
+ readonly mantineShadeIndex: boolean;
62
77
  /** Flag ad-hoc inline surface styling (border/borderRadius/boxShadow literals). Default true. */
63
78
  readonly rawSurface: boolean;
64
79
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/guard/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,0CAA0C;AAC1C,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,cAAc,GACd,oBAAoB,GACpB,qBAAqB,GACrB,aAAa,GACb,YAAY,GACZ,aAAa,GACb,kBAAkB,GAClB,wBAAwB,GACxB,iBAAiB,GACjB,gBAAgB,GAChB,gBAAgB,GAChB,eAAe,GACf,kBAAkB,GAClB,gBAAgB,GAChB,0BAA0B,GAC1B,kBAAkB,GAClB,mBAAmB,GACnB,iBAAiB,CAAA;AAErB;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,CAAA;AAE5C;;;;;;;GAOG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;CACjC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,wGAAwG;IACxG,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5C,iGAAiG;IACjG,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC,2GAA2G;IAC3G,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAA;IACrC,sGAAsG;IACtG,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,4FAA4F;IAC5F,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,yFAAyF;IACzF,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,kGAAkG;IAClG,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAA;IACvC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;IAC1D;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAA;CAC7D,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/guard/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,0CAA0C;AAC1C,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,cAAc,GACd,oBAAoB,GACpB,qBAAqB,GACrB,qBAAqB,GACrB,aAAa,GACb,YAAY,GACZ,aAAa,GACb,kBAAkB,GAClB,wBAAwB,GACxB,iBAAiB,GACjB,gBAAgB,GAChB,gBAAgB,GAChB,eAAe,GACf,kBAAkB,GAClB,gBAAgB,GAChB,0BAA0B,GAC1B,kBAAkB,GAClB,mBAAmB,GACnB,iBAAiB,CAAA;AAErB;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,CAAA;AAE5C;;;;;;;GAOG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;CACjC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,wGAAwG;IACxG,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5C;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAA;IACnC,iGAAiG;IACjG,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC,2GAA2G;IAC3G,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAA;IACrC,sGAAsG;IACtG,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,4FAA4F;IAC5F,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,yFAAyF;IACzF,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,kGAAkG;IAClG,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAA;IACvC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;IAC1D;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAA;CAC7D,CAAA"}
package/dist/index.d.ts CHANGED
@@ -13,7 +13,7 @@ export { MOTION_DURATION, MOTION_SPRING, MOTION_EASE_STANDARD } from './motion/i
13
13
  export { BasaltShell, NavCountBadge, AppSidebar, SidebarSearch, type SidebarSearchConfig, SidebarAccount, MobileNav, AppBreadcrumbs, type BreadcrumbLinkRenderer, PageHeaderProvider, PageActions, PageActionsOutlet, type SidebarSection, type SidebarItem, type BasaltShellProps, type BrandConfig, type SettingsMenuItem, type AppSidebarProps, type MobileNavItem, type MobileNavSection, type NavLinkRenderer, type MobileNavLinkRenderer, type AccountBadgeTone, type BasaltIdentity, type BasaltRole, type BasaltPlan, type AccountMenuItem, type BasaltAccountState, type BasaltAccountActions, type BasaltAccountProps, } from './shell/index.js';
14
14
  export { type BasaltRegister, type Slot, type Series, type SeriesKey, type AsyncState, assertNever, type StandardSchemaV1, } from './register.js';
15
15
  export { createPersistedState, type PersistedStateOptions, useOnlineStatus, readPersistedValue, } from './state.js';
16
- export { DeltaBadge, type DeltaBadgeProps, StatCard, type StatCardProps, EmptyState, type EmptyStateProps, SettingsSection, type SettingsSectionProps, SettingsRow, type SettingsRowProps, DangerZone, type DangerZoneProps, } from './dashboard/index.js';
16
+ export { DeltaBadge, type DeltaBadgeProps, StatCard, type StatCardProps, type StatCardTone, EmptyState, type EmptyStateProps, SettingsSection, type SettingsSectionProps, SettingsRow, type SettingsRowProps, DangerZone, type DangerZoneProps, } from './dashboard/index.js';
17
17
  export { ConnectivityProvider, ConnectivityIndicator, useConnectivity } from './connectivity/index.js';
18
18
  export type { ConnectivityStatus, ConnectivitySnapshot, ConnectivityProviderProps, ConnectivityOverride, } from './connectivity/index.js';
19
19
  export type { SurfaceSpec, RuleName, SkillName } from './surfaces.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,cAAc,EACd,KAAK,mBAAmB,EACxB,mBAAmB,EACnB,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAC5E,YAAY,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAC1E,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACnE,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAC/E,OAAO,EACL,WAAW,EACX,aAAa,EACb,UAAU,EACV,aAAa,EACb,KAAK,mBAAmB,EACxB,cAAc,EACd,SAAS,EACT,cAAc,EACd,KAAK,sBAAsB,EAC3B,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,GACxB,MAAM,SAAS,CAAA;AAKhB,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,IAAI,EACT,KAAK,MAAM,EACX,KAAK,SAAS,EACd,KAAK,UAAU,EACf,WAAW,EACX,KAAK,gBAAgB,GACtB,MAAM,YAAY,CAAA;AAGnB,OAAO,EACL,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,eAAe,EACf,kBAAkB,GACnB,MAAM,SAAS,CAAA;AAGhB,OAAO,EACL,UAAU,EACV,KAAK,eAAe,EACpB,QAAQ,EACR,KAAK,aAAa,EAClB,UAAU,EACV,KAAK,eAAe,EACpB,eAAe,EACf,KAAK,oBAAoB,EACzB,WAAW,EACX,KAAK,gBAAgB,EACrB,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAC7F,YAAY,EACV,kBAAkB,EAClB,oBAAoB,EACpB,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,gBAAgB,CAAA;AAKvB,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAClE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAU9C,OAAO,EACL,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,EACR,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,oBAAoB,EACpB,eAAe,EACf,sBAAsB,EACtB,sBAAsB,EACtB,aAAa,EACb,qBAAqB,GACtB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAClF,YAAY,EACV,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,WAAW,EACX,SAAS,EACT,cAAc,GACf,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,cAAc,EACd,KAAK,mBAAmB,EACxB,mBAAmB,EACnB,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAC5E,YAAY,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAC1E,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACnE,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAC/E,OAAO,EACL,WAAW,EACX,aAAa,EACb,UAAU,EACV,aAAa,EACb,KAAK,mBAAmB,EACxB,cAAc,EACd,SAAS,EACT,cAAc,EACd,KAAK,sBAAsB,EAC3B,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,GACxB,MAAM,SAAS,CAAA;AAKhB,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,IAAI,EACT,KAAK,MAAM,EACX,KAAK,SAAS,EACd,KAAK,UAAU,EACf,WAAW,EACX,KAAK,gBAAgB,GACtB,MAAM,YAAY,CAAA;AAGnB,OAAO,EACL,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,eAAe,EACf,kBAAkB,GACnB,MAAM,SAAS,CAAA;AAGhB,OAAO,EACL,UAAU,EACV,KAAK,eAAe,EACpB,QAAQ,EACR,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,UAAU,EACV,KAAK,eAAe,EACpB,eAAe,EACf,KAAK,oBAAoB,EACzB,WAAW,EACX,KAAK,gBAAgB,EACrB,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAC7F,YAAY,EACV,kBAAkB,EAClB,oBAAoB,EACpB,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,gBAAgB,CAAA;AAKvB,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAClE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAU9C,OAAO,EACL,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,EACR,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,oBAAoB,EACpB,eAAe,EACf,sBAAsB,EACtB,sBAAsB,EACtB,aAAa,EACb,qBAAqB,GACtB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAClF,YAAY,EACV,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,WAAW,EACX,SAAS,EACT,cAAc,GACf,MAAM,SAAS,CAAA"}
@@ -130,8 +130,8 @@ export declare const SURFACES: {
130
130
  readonly layer: "mantine-coupled";
131
131
  readonly rule: "mantine";
132
132
  readonly skill: readonly ["basalt-app", "basalt-design"];
133
- readonly guardKinds: readonly ["raw-motion-value", "card-with-border", "raw-form-control", "sub-16-input-font"];
134
- readonly description: "BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard, EmptyState, SettingsSection/SettingsRow/DangerZone)";
133
+ readonly guardKinds: readonly ["raw-motion-value", "card-with-border", "raw-form-control", "sub-16-input-font", "mantine-shade-index"];
134
+ readonly description: "BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard with threshold tone, EmptyState, SettingsSection/SettingsRow/DangerZone)";
135
135
  readonly optionalPeers: readonly ["react-markdown", "remark-gfm", "shiki", "beautiful-mermaid"];
136
136
  readonly forbiddenImports: readonly [];
137
137
  };
@@ -1 +1 @@
1
- {"version":3,"file":"surfaces.d.ts","sourceRoot":"","sources":["../src/surfaces.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"surfaces.d.ts","sourceRoot":"","sources":["../src/surfaces.ts"],"names":[],"mappings":"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"}
package/dist/surfaces.js CHANGED
@@ -22,8 +22,14 @@ const SURFACES = {
22
22
  layer: "mantine-coupled",
23
23
  rule: "mantine",
24
24
  skill: ["basalt-app", "basalt-design"],
25
- guardKinds: ["raw-motion-value", "card-with-border", "raw-form-control", "sub-16-input-font"],
26
- description: "BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard, EmptyState, SettingsSection/SettingsRow/DangerZone)",
25
+ guardKinds: [
26
+ "raw-motion-value",
27
+ "card-with-border",
28
+ "raw-form-control",
29
+ "sub-16-input-font",
30
+ "mantine-shade-index"
31
+ ],
32
+ description: "BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard with threshold tone, EmptyState, SettingsSection/SettingsRow/DangerZone)",
27
33
  optionalPeers: ["react-markdown", "remark-gfm", "shiki", "beautiful-mermaid"],
28
34
  forbiddenImports: []
29
35
  // the no-charts/tokens-reexport invariant is comment-only today; Phase-4 plugin
@@ -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/dist/vite.d.ts CHANGED
@@ -25,6 +25,12 @@ export type BasaltViteOptions = {
25
25
  basaltSrc?: string;
26
26
  /** App version surfaced via the `__APP_VERSION__` define. Defaults to `0.0.0`. */
27
27
  version?: string;
28
+ /**
29
+ * Print a one-time notice when `basalt-ui init` has never been run in this project (no
30
+ * `.basalt/manifest.json` found at or above the cwd) — see `warnIfUnenforced`. Default `true`.
31
+ * Set `false` to adopt the components without the toolchain and stop being told about it.
32
+ */
33
+ enforcementNotice?: boolean;
28
34
  };
29
35
  export declare function basaltViteConfig(opts: BasaltViteOptions): UserConfig;
30
36
  export type BasaltAppOptions = {
@@ -1 +1 @@
1
- {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAA6B,YAAY,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AAI/E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,UAAU,CAyEpE;AAgBD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gGAAgG;IAChG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACrD,mGAAmG;IACnG,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,wDAAwD;IACxD,OAAO,CAAC,EAAE,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAA;IAChE;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mGAAmG;IACnG,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,wFAAwF;IACxF,KAAK,CAAC,EAAE,KAAK,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChC,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAC3B,oFAAoF;IACpF,GAAG,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,SAAS,GAAG,qBAAqB,CAAA;KAAE,CAAA;IACvF,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,KAAK,CAAA;IAChB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClD,CAAA;AAuZD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,EAAE,CAMzE"}
1
+ {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAA6B,YAAY,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AAI/E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA;AA6CD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,UAAU,CAgFpE;AAgBD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gGAAgG;IAChG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACrD,mGAAmG;IACnG,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,wDAAwD;IACxD,OAAO,CAAC,EAAE,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAA;IAChE;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mGAAmG;IACnG,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,wFAAwF;IACxF,KAAK,CAAC,EAAE,KAAK,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChC,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAC3B,oFAAoF;IACpF,GAAG,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,SAAS,GAAG,qBAAqB,CAAA;KAAE,CAAA;IACvF,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,KAAK,CAAA;IAChB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClD,CAAA;AAuZD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,EAAE,CAMzE"}
package/dist/vite.js CHANGED
@@ -1,10 +1,30 @@
1
- import { resolve } from "node:path";
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
2
3
  import { searchForWorkspaceRoot } from "vite";
3
4
  import { SURFACE } from "./tokens/palette.js";
4
5
  import { resolveColorMix } from "./vite-color-mix.js";
6
+ const UNENFORCED_SEARCH_DEPTH = 3;
7
+ let unenforcedNoticePrinted = false;
8
+ function warnIfUnenforced(cwd) {
9
+ if (unenforcedNoticePrinted) return;
10
+ let dir = cwd;
11
+ for (let i = 0; i <= UNENFORCED_SEARCH_DEPTH; i++) {
12
+ if (existsSync(resolve(dir, ".basalt/manifest.json"))) return;
13
+ const parent = dirname(dir);
14
+ if (parent === dir) break;
15
+ dir = parent;
16
+ }
17
+ unenforcedNoticePrinted = true;
18
+ console.warn(
19
+ "\n[basalt-ui] Installed, but `basalt-ui init` has never run here \u2014 no oxlint preset, no theme\n guard, no agent rules. You have the components and none of the enforcement.\n Fix: `bunx basalt-ui init`, then add `oxlint . && basalt-ui check-theme` as your\n lint script. Silence: pass `enforcementNotice: false` to basaltViteConfig().\n"
20
+ );
21
+ }
5
22
  function basaltViteConfig(opts) {
6
23
  const { port, host, allowedHosts, apiTarget, version = "0.0.0" } = opts;
7
24
  const basaltSrc = opts.basaltSrc ?? process.env["BASALT_LOCAL"];
25
+ if (opts.enforcementNotice !== false && basaltSrc === void 0) {
26
+ warnIfUnenforced(process.cwd());
27
+ }
8
28
  const config = {
9
29
  define: {
10
30
  __APP_VERSION__: JSON.stringify(process.env["BUILD_VERSION"] ?? version),
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # basalt-ui v1.6.0
1
+ # basalt-ui v1.8.0
2
2
  # Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets
3
3
  #
4
4
  # basalt-ui is an opinionated framework for Mantine-based React apps.
@@ -19,11 +19,11 @@
19
19
  # ./tokens.css subpath. Guide: docs/FRAMEWORK-FREE.md in the repo.
20
20
 
21
21
  Package: basalt-ui
22
- Version: 1.6.0
22
+ Version: 1.8.0
23
23
 
24
24
  ## basalt-ui
25
25
  Import: import { ... } from 'basalt-ui'
26
- Description: BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard, EmptyState, SettingsSection/SettingsRow/DangerZone)
26
+ Description: BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard with threshold tone, EmptyState, SettingsSection/SettingsRow/DangerZone)
27
27
  Layer: mantine-coupled
28
28
  Rule: basalt-mantine
29
29
  OptionalPeers: react-markdown@^10.1.0, remark-gfm@^4.0.1, shiki@^4.3.0, beautiful-mermaid@^1.1.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basalt-ui",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets",
5
5
  "keywords": [
6
6
  "charts",
package/src/cli/index.ts CHANGED
@@ -80,6 +80,15 @@ export type BasaltConfig = {
80
80
  * surface tokens. Default: `true` (ON). Set `false` to disable the `off-system-surface-var` check.
81
81
  */
82
82
  offSystemSurfaceVar?: boolean
83
+ /**
84
+ * Flag a SHADE-PINNED Mantine color — `c="yellow.7"`, `bg="blue.4"`,
85
+ * `var(--mantine-color-red-6)`. A pinned step is one fixed swatch in BOTH color schemes, so it
86
+ * cannot stay legible in either; route a verdict color through `VX.status.*` / `--vx-status-*`,
87
+ * or drop the index (`c="red"`) and let the theme resolve the shade per scheme. `gray-*`/`dark-*`
88
+ * in `var()` form belong to `off-system-surface-var` instead. Default: `true` (ON). Set `false`
89
+ * to disable the `mantine-shade-index` check.
90
+ */
91
+ mantineShadeIndex?: boolean
83
92
  /**
84
93
  * Flag raw lowercase JSX layout/surface elements (`div`/`span`/`section`/…) carrying an inline
85
94
  * `style={{}}` with a layout/surface property — steer to a Mantine layout primitive
@@ -348,6 +357,7 @@ export function checkTheme(cwd: string = process.cwd()): number {
348
357
  spacingSteps: cfg.spacingSteps ?? DEFAULT_GUARD_CONFIG.spacingSteps,
349
358
  rawRadius: cfg.rawRadius ?? DEFAULT_GUARD_CONFIG.rawRadius,
350
359
  forbiddenAccents: cfg.forbiddenAccents ?? DEFAULT_GUARD_CONFIG.forbiddenAccents,
360
+ mantineShadeIndex: cfg.mantineShadeIndex ?? DEFAULT_GUARD_CONFIG.mantineShadeIndex,
351
361
  rawSurface: cfg.rawSurface ?? DEFAULT_GUARD_CONFIG.rawSurface,
352
362
  cardWithBorder: cfg.cardWithBorder ?? DEFAULT_GUARD_CONFIG.cardWithBorder,
353
363
  offSystemSurfaceVar: cfg.offSystemSurfaceVar ?? DEFAULT_GUARD_CONFIG.offSystemSurfaceVar,
@@ -1827,6 +1837,7 @@ export async function guardHook(cwd: string = process.cwd()): Promise<number> {
1827
1837
  spacingSteps: cfg.spacingSteps ?? DEFAULT_GUARD_CONFIG.spacingSteps,
1828
1838
  rawRadius: cfg.rawRadius ?? DEFAULT_GUARD_CONFIG.rawRadius,
1829
1839
  forbiddenAccents: cfg.forbiddenAccents ?? DEFAULT_GUARD_CONFIG.forbiddenAccents,
1840
+ mantineShadeIndex: cfg.mantineShadeIndex ?? DEFAULT_GUARD_CONFIG.mantineShadeIndex,
1830
1841
  rawSurface: cfg.rawSurface ?? DEFAULT_GUARD_CONFIG.rawSurface,
1831
1842
  cardWithBorder: cfg.cardWithBorder ?? DEFAULT_GUARD_CONFIG.cardWithBorder,
1832
1843
  offSystemSurfaceVar: cfg.offSystemSurfaceVar ?? DEFAULT_GUARD_CONFIG.offSystemSurfaceVar,
@@ -1,5 +1,5 @@
1
1
  export { DeltaBadge, type DeltaBadgeProps } from './delta-badge'
2
- export { StatCard, type StatCardProps } from './stat-card'
2
+ export { StatCard, type StatCardProps, type StatCardTone } from './stat-card'
3
3
  export { EmptyState, type EmptyStateProps } from './empty-state'
4
4
  export {
5
5
  SettingsSection,
@@ -0,0 +1,81 @@
1
+ /**
2
+ * `StatCard`'s threshold rail — the one behavior in this component that carries information rather
3
+ * than layout, and the one where a regression is silent: a wrong rail colour still renders a
4
+ * perfectly good-looking card.
5
+ *
6
+ * Two invariants, both asserted against real rendered markup:
7
+ *
8
+ * 1. EVERY tone states its verdict in TEXT. The rail is `aria-hidden` decoration; colour alone must
9
+ * never be the only carrier of a threshold (WCAG 1.4.1), so a `VisuallyHidden` string ships with
10
+ * it — for `good` no less than for `bad`.
11
+ * 2. OMISSION IS NOT `good`. `tone` undefined means "fine, OR nothing measured", and those two are
12
+ * the same untinted card on purpose. A card with no reading must not be able to render green by
13
+ * leaving the prop off, so the absence case asserts no rail and no verdict text at all — which a
14
+ * future `tone = tone ?? 'good'` style default would immediately fail.
15
+ *
16
+ * No jsdom is configured in this repo (see `theme/use-basalt-spacing.test.tsx`'s doc) —
17
+ * `renderToStaticMarkup` inside a real `MantineProvider` is enough here, since the rail is an inline
18
+ * style on server-rendered markup.
19
+ */
20
+ import { MantineProvider } from '@mantine/core'
21
+ import { describe, expect, test } from 'bun:test'
22
+ import { renderToStaticMarkup } from 'react-dom/server'
23
+ import { StatCard } from './stat-card'
24
+ import type { StatCardTone } from './stat-card'
25
+
26
+ function render(tone?: StatCardTone) {
27
+ return renderToStaticMarkup(
28
+ <MantineProvider>
29
+ <StatCard label="Downtime · last 24h" value="0 min" tone={tone} />
30
+ </MantineProvider>,
31
+ )
32
+ }
33
+
34
+ const TONES: { tone: StatCardTone; token: string; verdict: string }[] = [
35
+ { tone: 'good', token: 'var(--vx-status-good)', verdict: 'Within the good threshold' },
36
+ { tone: 'warn', token: 'var(--vx-status-warn)', verdict: 'Past the warning threshold' },
37
+ { tone: 'bad', token: 'var(--vx-status-bad)', verdict: 'Past the severe threshold' },
38
+ ]
39
+
40
+ describe('every tone draws its rail from the per-scheme status token', () => {
41
+ for (const { tone, token } of TONES) {
42
+ test(tone, () => {
43
+ const markup = render(tone)
44
+ // A hex here would be one scheme's colour frozen into both — the rail has to re-resolve with
45
+ // the palette, including under a consumer's `derive` retune.
46
+ expect(markup).toContain(`background:${token}`)
47
+ expect(markup).toContain(`data-tone="${tone}"`)
48
+ })
49
+ }
50
+ })
51
+
52
+ describe('every tone also states its verdict in text', () => {
53
+ for (const { tone, verdict } of TONES) {
54
+ test(tone, () => {
55
+ expect(render(tone)).toContain(verdict)
56
+ })
57
+ }
58
+ })
59
+
60
+ describe('omitting tone is not "good" — it is untinted, and says nothing', () => {
61
+ const markup = render()
62
+
63
+ test('no rail is drawn', () => {
64
+ expect(markup).not.toContain('var(--vx-status-')
65
+ })
66
+
67
+ test('the card carries no data-tone at all', () => {
68
+ // Part of the same contract, and the half a consumer can style against: `[data-tone]` must not
69
+ // match an unmarked card. A default of any kind — including a placeholder like `tone ?? 'none'`
70
+ // that draws no rail — would put the attribute back and break that selector silently.
71
+ expect(markup).not.toContain('data-tone')
72
+ })
73
+
74
+ test('no verdict is announced', () => {
75
+ for (const { verdict } of TONES) expect(markup).not.toContain(verdict)
76
+ })
77
+
78
+ test('the value still renders — the card is normal, just unmarked', () => {
79
+ expect(markup).toContain('0 min')
80
+ })
81
+ })
@@ -8,6 +8,18 @@
8
8
  * `src/dashboard` stays @visx-free — `sparkline` is a plain `ReactNode` slot, never a chart import
9
9
  * here. Pass a `LineSparkline`/`BarSparkline` from `basalt-ui/charts` at the call site.
10
10
  *
11
+ * `tone` marks the card as past a threshold with an accent rail down its leading edge. It exists
12
+ * because `value` is typed `string`: without it, a card whose number has crossed a threshold reads
13
+ * exactly as calm as one that hasn't, and the only way out from the consumer side is to wrap the
14
+ * card in a hand-rolled positioned `<Box>` — which is a second card idiom in the app, drawn by code
15
+ * the guard has no way to recognize as one. The rail is the largest signal available without
16
+ * touching the value's own type: full card height, overlaying the card's edge rather than adding to
17
+ * it, so layout is unchanged. `undefined` renders nothing at all — absence of a reading is neither a
18
+ * good one nor a bad one, and must never be tinted. `'good'` is a deliberate positive verdict, never
19
+ * the meaning of omission: it exists for the threshold where the calm number IS the finding (zero
20
+ * downtime over the window), and a card that has measured nothing must not be able to reach green by
21
+ * leaving the prop off.
22
+ *
11
23
  * @example
12
24
  * import { StatCard } from 'basalt-ui'
13
25
  * import { LineSparkline } from 'basalt-ui/charts'
@@ -18,12 +30,33 @@
18
30
  * delta={4.2}
19
31
  * sparkline={<LineSparkline data={history} width={160} height={32} />}
20
32
  * />
33
+ *
34
+ * @example
35
+ * // Past a threshold — an accent rail down the leading edge, value untouched.
36
+ * <StatCard label="Downtime" value="29 min" tone="bad" />
37
+ *
38
+ * @example
39
+ * // The earned zero — a measured value worth asserting, not a card that merely has no reading.
40
+ * <StatCard label="Downtime · last 24h" value="0 min" tone="good" />
21
41
  */
22
- import { Card, Group } from '@mantine/core'
42
+ import { Box, Card, Group, VisuallyHidden } from '@mantine/core'
23
43
  import type { ReactNode } from 'react'
24
44
  import { DeltaBadge } from './delta-badge'
25
45
  import { VX } from '../tokens'
26
46
 
47
+ /** A threshold verdict on the card's value. `undefined` is never tinted — see the module docblock. */
48
+ export type StatCardTone = 'good' | 'warn' | 'bad'
49
+
50
+ /** The rail's width, in px. Wide enough to read as a deliberate mark at a glance, narrow enough
51
+ * that it cannot be mistaken for the card's own edge. */
52
+ const TONE_RAIL_WIDTH = 3
53
+
54
+ const TONE_LABEL: Record<StatCardTone, string> = {
55
+ good: 'Within the good threshold',
56
+ warn: 'Past the warning threshold',
57
+ bad: 'Past the severe threshold',
58
+ }
59
+
27
60
  export type StatCardProps = {
28
61
  /** Mono uppercase micro-label (docs/DESIGN-SPEC.md §3: 12.5px, tracking 0.06em, faint). */
29
62
  label: string
@@ -38,9 +71,22 @@ export type StatCardProps = {
38
71
  sparkline?: ReactNode
39
72
  /** Optional header-right slot (e.g. a ghost "..." menu trigger) — consumer-owned. */
40
73
  menu?: ReactNode
74
+ /** Threshold verdict — draws an accent rail down the card's leading edge and announces itself to
75
+ * assistive tech. Omitting it is NOT `'good'`: omitted covers a reading that is fine AND one that
76
+ * is absent, and stays untinted so a card with nothing measured can never render green. Pass
77
+ * `'good'` only to assert a measured value that has earned the verdict. */
78
+ tone?: StatCardTone
41
79
  }
42
80
 
43
- export function StatCard({ label, value, delta, deltaPeriod, sparkline, menu }: StatCardProps) {
81
+ export function StatCard({
82
+ label,
83
+ value,
84
+ delta,
85
+ deltaPeriod,
86
+ sparkline,
87
+ menu,
88
+ tone,
89
+ }: StatCardProps) {
44
90
  return (
45
91
  <Card
46
92
  style={{
@@ -56,8 +102,35 @@ export function StatCard({ label, value, delta, deltaPeriod, sparkline, menu }:
56
102
  flexDirection: 'column',
57
103
  gap: 'var(--vx-space-stat-card-gap)',
58
104
  overflow: 'hidden',
105
+ // Anchors the tone rail. Set unconditionally so a card's stacking context does not change
106
+ // depending on whether it happens to have crossed a threshold this render.
107
+ position: 'relative',
59
108
  }}
109
+ data-tone={tone}
60
110
  >
111
+ {tone !== undefined && (
112
+ <>
113
+ {/* The rail is decoration; the verdict it encodes is real information, so it is also
114
+ stated in text for anyone who cannot see a 3px colour bar. Colour alone is never the
115
+ only carrier of a threshold. */}
116
+ <VisuallyHidden>{TONE_LABEL[tone]}</VisuallyHidden>
117
+ <Box
118
+ aria-hidden="true"
119
+ style={{
120
+ position: 'absolute',
121
+ insetBlock: 0,
122
+ insetInlineStart: 0,
123
+ width: TONE_RAIL_WIDTH,
124
+ // `StatCardTone`'s members are `VX.status` keys by construction, so the rail reads the
125
+ // per-scheme `--vx-status-*` solid directly — never a hex, so it re-resolves in both
126
+ // schemes and under a consumer's `derive` retune. Contrast against
127
+ // `--vx-surface-panel` is measured in `theme/contrast.test.ts`.
128
+ background: VX.status[tone],
129
+ }}
130
+ />
131
+ </>
132
+ )}
133
+
61
134
  <Group justify="space-between" align="flex-start" wrap="nowrap" gap="xs">
62
135
  <span
63
136
  style={{
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Unit tests for checkSource — the pure (text, relPath, cfg) → Finding[] core.
3
3
  *
4
- * Covers all 19 guard kinds. Co-located with the guard, excluded from tsc
4
+ * Covers all 20 guard kinds. Co-located with the guard, excluded from tsc
5
5
  * (tsconfig exclude: src/**\/*.test.ts), run via `bun test`.
6
6
  *
7
7
  * The walker/reporter half is covered by the integration test in
@@ -154,6 +154,69 @@ describe('off-identity-accent', () => {
154
154
  })
155
155
  })
156
156
 
157
+ // ── 4b. mantine-shade-index ──────────────────────────────────────────────────
158
+
159
+ describe('mantine-shade-index', () => {
160
+ it('flags a shade-pinned text color (c="yellow.7")', () => {
161
+ const f = find(`<Text c="yellow.7">stale</Text>`)
162
+ expect(kinds(f)).toContain('mantine-shade-index')
163
+ })
164
+
165
+ it('flags bg="blue.4" and color="red.6"', () => {
166
+ expect(kinds(find(`<Box bg="blue.4" />`))).toContain('mantine-shade-index')
167
+ expect(kinds(find(`<ThemeIcon color="red.6" />`))).toContain('mantine-shade-index')
168
+ })
169
+
170
+ it('flags the var() form for a non-surface hue', () => {
171
+ const f = find(`const a = { color: 'var(--mantine-color-yellow-7)' }`)
172
+ expect(kinds(f)).toContain('mantine-shade-index')
173
+ })
174
+
175
+ it('flags the var() form in a .css file too — unlike the prop-only kinds', () => {
176
+ const f = find(`.a { color: var(--mantine-color-red-6); }`, 'src/app.css')
177
+ expect(kinds(f)).toContain('mantine-shade-index')
178
+ })
179
+
180
+ // The two kinds partition the space: gray/dark var() steps are surface color and belong to
181
+ // off-system-surface-var, so a single violation must never report under both.
182
+ it('leaves gray/dark var() steps to off-system-surface-var', () => {
183
+ const f = find(`const a = { background: 'var(--mantine-color-gray-3)' }`)
184
+ expect(kinds(f)).toContain('off-system-surface-var')
185
+ expect(kinds(f)).not.toContain('mantine-shade-index')
186
+ })
187
+
188
+ it('does NOT flag a bare status hue — it resolves per scheme, which is the point', () => {
189
+ expect(kinds(find(`<Text c="red">down</Text>`))).not.toContain('mantine-shade-index')
190
+ expect(kinds(find(`<ThemeIcon color="green" />`))).not.toContain('mantine-shade-index')
191
+ })
192
+
193
+ it('does NOT flag c="dimmed" or a token ref', () => {
194
+ expect(kinds(find(`<Text c="dimmed">age</Text>`))).not.toContain('mantine-shade-index')
195
+ expect(kinds(find(`<Text c={VX.status.warn}>age</Text>`))).not.toContain('mantine-shade-index')
196
+ })
197
+
198
+ it('does NOT flag a theme-allow line', () => {
199
+ const f = find(`<Text c="yellow.7" /> // theme-allow: design exception`)
200
+ expect(kinds(f)).not.toContain('mantine-shade-index')
201
+ })
202
+
203
+ it('is disabled by the mantineShadeIndex knob', () => {
204
+ const f = checkSource(`<Text c="yellow.7" />`, PATH, {
205
+ ...DEFAULT_GUARD_CONFIG,
206
+ mantineShadeIndex: false,
207
+ })
208
+ expect(kinds(f)).not.toContain('mantine-shade-index')
209
+ })
210
+
211
+ // The grace-minor doctrine (package CLAUDE.md): a kind that rejects previously-passing code
212
+ // lands as `warn` for one minor. Deleting its GRACE_PERIOD_KINDS entry flips this — scheduled
213
+ // for 1.9.0, deferred one minor because 1.8.0 shipped the same day as 1.7.0 (see the entry).
214
+ it('lands as a warning, not an error, for its grace minor', () => {
215
+ const f = find(`<Text c="yellow.7" />`)
216
+ expect(f.find((x) => x.kind === 'mantine-shade-index')?.severity).toBe('warn')
217
+ })
218
+ })
219
+
157
220
  // ── 5. raw-spacing ───────────────────────────────────────────────────────────
158
221
 
159
222
  describe('raw-spacing', () => {