basalt-ui 1.23.1 → 1.25.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 (63) hide show
  1. package/AGENTS.md +11 -6
  2. package/CHANGELOG.md +21 -0
  3. package/MIGRATING.md +138 -4
  4. package/README.md +123 -32
  5. package/agent/rules/basalt-data.md +104 -12
  6. package/agent/rules/basalt-query.md +57 -0
  7. package/agent/skills/basalt-app/SKILL.md +7 -0
  8. package/dist/cli/index.d.ts +4 -2
  9. package/dist/cli/index.d.ts.map +1 -1
  10. package/dist/cli/index.js +101 -17
  11. package/dist/dashboard/empty-state.d.ts +6 -2
  12. package/dist/dashboard/empty-state.d.ts.map +1 -1
  13. package/dist/dashboard/empty-state.js +1 -1
  14. package/dist/dashboard/index.d.ts +1 -0
  15. package/dist/dashboard/index.d.ts.map +1 -1
  16. package/dist/dashboard/index.js +8 -0
  17. package/dist/dashboard/query-state.d.ts +96 -0
  18. package/dist/dashboard/query-state.d.ts.map +1 -0
  19. package/dist/dashboard/query-state.js +123 -0
  20. package/dist/dashboard/query-state.test.d.ts +2 -0
  21. package/dist/dashboard/query-state.test.d.ts.map +1 -0
  22. package/dist/data/data-table.d.ts +125 -8
  23. package/dist/data/data-table.d.ts.map +1 -1
  24. package/dist/data/data-table.js +121 -13
  25. package/dist/data/data-table.test.d.ts +2 -0
  26. package/dist/data/data-table.test.d.ts.map +1 -0
  27. package/dist/data/table.d.ts +2 -2
  28. package/dist/data/table.d.ts.map +1 -1
  29. package/dist/guard/index.d.ts.map +1 -1
  30. package/dist/guard/index.js +21 -6
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +7 -1
  34. package/dist/query/error-message.d.ts +41 -0
  35. package/dist/query/error-message.d.ts.map +1 -0
  36. package/dist/query/error-message.js +30 -0
  37. package/dist/query/error-message.test.d.ts +2 -0
  38. package/dist/query/error-message.test.d.ts.map +1 -0
  39. package/dist/query/index.d.ts +1 -19
  40. package/dist/query/index.d.ts.map +1 -1
  41. package/dist/query/index.js +2 -14
  42. package/dist/surfaces.d.ts +1 -1
  43. package/dist/surfaces.js +1 -1
  44. package/llms.txt +3 -3
  45. package/package.json +1 -1
  46. package/src/cli/check-theme.test.ts +34 -0
  47. package/src/cli/index.ts +222 -44
  48. package/src/cli/tokens-css.test.ts +73 -6
  49. package/src/cli/toolchain-wiring.test.ts +146 -0
  50. package/src/dashboard/empty-state.tsx +9 -3
  51. package/src/dashboard/index.ts +11 -0
  52. package/src/dashboard/query-state.test.tsx +152 -0
  53. package/src/dashboard/query-state.tsx +272 -0
  54. package/src/data/data-table.test.tsx +349 -0
  55. package/src/data/data-table.tsx +359 -42
  56. package/src/data/table.ts +8 -1
  57. package/src/guard/check-source.test.ts +141 -0
  58. package/src/guard/index.ts +102 -8
  59. package/src/index.ts +9 -0
  60. package/src/query/error-message.test.ts +67 -0
  61. package/src/query/error-message.ts +70 -0
  62. package/src/query/index.ts +2 -34
  63. package/src/surfaces.ts +1 -1
@@ -1,4 +1,4 @@
1
- const HEX = /#[0-9a-fA-F]{3,8}\b/g;
1
+ const HEX = /#(?!(?<=&#)\d+;)[0-9a-fA-F]{3,8}\b/g;
2
2
  const FUNC = /\b(?:rgba?|hsla?)\((?!\s*\$\{)/g;
3
3
  const LOCALSTORAGE_THEME = /localStorage\s*\.\s*getItem\s*\(\s*['"]theme['"]\s*\)/g;
4
4
  const RAW_FONT_FAMILY_VAR_ESCAPE = /var\(\s*--/.source;
@@ -272,14 +272,19 @@ function parseAllowAnnotation(rest, scope) {
272
272
  const MAX_CSS_CONTINUATION_LINES = 8;
273
273
  function commentBlockEnd(lines, index, tokenAt, syntax) {
274
274
  const line = lines[index] ?? "";
275
- if (syntax === "ts" && /\/\/\s*$/.test(line.slice(0, tokenAt))) return index;
276
- const closer = syntax === "markup" ? "-->" : "*/";
275
+ const markup = syntax === "markup" || syntax === "sfc" && opensMarkupComment(line, tokenAt);
276
+ if (!markup && syntax !== "css" && /\/\/\s*$/.test(line.slice(0, tokenAt))) return index;
277
+ const closer = markup ? "-->" : "*/";
277
278
  if (line.slice(tokenAt).includes(closer)) return index;
278
279
  for (let ahead = index + 1; ahead < lines.length; ahead++) {
279
280
  if ((lines[ahead] ?? "").includes(closer)) return ahead;
280
281
  }
281
282
  return index;
282
283
  }
284
+ function opensMarkupComment(line, tokenAt) {
285
+ const before = line.slice(0, tokenAt);
286
+ return before.lastIndexOf("<!--") > before.lastIndexOf("/*");
287
+ }
283
288
  const COMMENT_OPENER_BEFORE = /(?:\/\/|\/\*|<!--)\s*$/;
284
289
  const COMMENT_ONLY_LINE = /^\s*(?:\{\s*\/\*|\/\/|\/\*|\*|<!--)/;
285
290
  const JSX_COMMENT_CLOSE = /\*\/\s*\}\s*$/;
@@ -375,8 +380,14 @@ function annotationsCover(annotations, kind) {
375
380
  function guardSyntaxFor(relPath) {
376
381
  if (relPath.endsWith(".css")) return "css";
377
382
  if (/\.(?:html?|webmanifest|json)$/.test(relPath)) return "markup";
383
+ if (/\.(?:astro|vue)$/.test(relPath)) return "sfc";
378
384
  return "ts";
379
385
  }
386
+ function stripGuardComments(text, syntax) {
387
+ if (syntax === "markup") return stripMarkupComments(text);
388
+ if (syntax === "sfc") return stripComments(stripMarkupComments(text), "ts");
389
+ return stripComments(text, syntax);
390
+ }
380
391
  const MARKUP_KINDS = /* @__PURE__ */ new Set(["raw-hex", "raw-color-fn", "raw-font-family"]);
381
392
  function stripMarkupComments(text) {
382
393
  return text.replace(/<!--[\s\S]*?-->/g, (m) => m.replace(/[^\n]/g, " "));
@@ -816,7 +827,7 @@ function checkSource(text, relPath, cfg) {
816
827
  GUARD_RULES["raw-radius"].pattern.flags
817
828
  );
818
829
  const syntax = guardSyntaxFor(relPath);
819
- const codeText = syntax === "markup" ? stripMarkupComments(text) : stripComments(text, syntax);
830
+ const codeText = stripGuardComments(text, syntax);
820
831
  const codeLines = codeText.split("\n");
821
832
  const { waivers, fileScoped, declared } = collectAllowAnnotations(
822
833
  lines,
@@ -934,7 +945,11 @@ function checkSource(text, relPath, cfg) {
934
945
  for (const m of line.matchAll(GUARD_RULES["inline-spacing"].pattern)) {
935
946
  if (syntax === "css" && isSubScaleCssSpacing(line, m.index)) continue;
936
947
  const hasUnit = /^[a-z%]/.test(line.slice(m.index + m[0].length));
937
- if (syntax === "ts" && !hasUnit && !isStyleObjectContext(codeText, lineStarts[i] + m.index, styleIdentifiers))
948
+ if (
949
+ // An SFC's script fence IS TS, so it takes the same gate — without it a unitless
950
+ // `padding: 8` in `.astro`/`.vue` frontmatter reports with no style-object behind it.
951
+ (syntax === "ts" || syntax === "sfc") && !hasUnit && !isStyleObjectContext(codeText, lineStarts[i] + m.index, styleIdentifiers)
952
+ )
938
953
  continue;
939
954
  push("inline-spacing", i + 1, m[0]);
940
955
  }
@@ -1049,7 +1064,7 @@ function checkSource(text, relPath, cfg) {
1049
1064
  function findAllowAnnotations(text, relPath, cfg) {
1050
1065
  const lines = text.split("\n");
1051
1066
  const syntax = guardSyntaxFor(relPath);
1052
- const codeText = syntax === "markup" ? stripMarkupComments(text) : stripComments(text, syntax);
1067
+ const codeText = stripGuardComments(text, syntax);
1053
1068
  const { declared } = collectAllowAnnotations(
1054
1069
  lines,
1055
1070
  codeText.split("\n"),
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, type MobileNavProps, projectMobileNav, type ProjectMobileNavOptions, MOBILE_MAX_TABS_DEFAULT, MOBILE_MENU_MAX_DEFAULT, MOBILE_MORE_KEY, AppBreadcrumbs, PageHeaderProvider, PageActions, PageActionsOutlet, type SidebarSection, type SidebarItem, type NavAnchor, type NavAnchorProps, type NavMobilePlacement, type NavSectionMobile, type MobileNavConfig, type MobileNavGroup, type MobileNavModel, type MobileNavSlot, type MobileNavSurface, type BasaltShellProps, type BrandConfig, type SettingsMenuItem, type AppSidebarProps, 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, type StatCardTone, 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, QueryState, type QueryStateProps, type QueryStateLike, type QueryStateVariant, type QueryEmptyCopy, LoadingState, type LoadingStateProps, ErrorState, type ErrorStateProps, } 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":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -35,7 +35,10 @@ import {
35
35
  EmptyState,
36
36
  SettingsSection,
37
37
  SettingsRow,
38
- DangerZone
38
+ DangerZone,
39
+ QueryState,
40
+ LoadingState,
41
+ ErrorState
39
42
  } from "./dashboard/index.js";
40
43
  import { ConnectivityProvider, ConnectivityIndicator, useConnectivity } from "./connectivity/index.js";
41
44
  import {
@@ -68,6 +71,8 @@ export {
68
71
  DangerZone,
69
72
  DeltaBadge,
70
73
  EmptyState,
74
+ ErrorState,
75
+ LoadingState,
71
76
  MOBILE_MAX_TABS_DEFAULT,
72
77
  MOBILE_MENU_MAX_DEFAULT,
73
78
  MOBILE_MORE_KEY,
@@ -79,6 +84,7 @@ export {
79
84
  PageActions,
80
85
  PageActionsOutlet,
81
86
  PageHeaderProvider,
87
+ QueryState,
82
88
  SettingsRow,
83
89
  SettingsSection,
84
90
  SidebarAccount,
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Transport-agnostic error decoding. Deliberately a standalone module with NO
3
+ * `@tanstack/react-query` import — `dashboard/query-state.tsx` renders server messages and must
4
+ * not drag the query peer into the root barrel's graph to do it.
5
+ */
6
+ /**
7
+ * The HTTP status behind a thrown envelope (Eden Treaty's `{ status, value }`, a `Response`-shaped
8
+ * throw), when there is one. Use it to BRANCH — 404 → "not found" copy — rather than to build a
9
+ * message; `toErrorMessage` already folds the status into its text when the body says nothing.
10
+ *
11
+ * @example
12
+ * if (errorStatus(err) === 404) return <EmptyState title="Gone" />
13
+ */
14
+ export declare function errorStatus(err: unknown): number | undefined;
15
+ /**
16
+ * Extract a human-readable message from an unknown thrown value (e.g. the raw Eden envelope
17
+ * thrown by `unwrap`). Removes the 8-line three-branch boilerplate consumers duplicate.
18
+ *
19
+ * Resolution order:
20
+ * 1. `Error` instance → err.message
21
+ * 2. `{ message: string }` → message (Eden / Elysia error shape)
22
+ * 3. `{ value: ... }` → recursively (Eden nested value envelope)
23
+ * 4. plain string → itself
24
+ * 5. anything else → `JSON.stringify`
25
+ *
26
+ * **If that yields nothing a human can read** (`''`, `'{}'`, `'[]'`, `'null'`, `'undefined'`,
27
+ * `'[object Object]'` — the shapes `JSON.stringify` produces for an opaque envelope) the `fallback`
28
+ * is returned instead, with `(HTTP <status>)` appended when the envelope carries one. Without that
29
+ * guard the caller renders a literal `{}` at the user, or — for `toErrorMessage(undefined)` —
30
+ * the actual value `undefined`, since `JSON.stringify(undefined)` is not a string at all.
31
+ *
32
+ * @example
33
+ * try {
34
+ * await unwrap(api.resource.post({ body }))
35
+ * } catch (e) {
36
+ * toast(toErrorMessage(e, 'Could not create share')) // → "slug already in use"
37
+ * // → "Could not create share (HTTP 502)"
38
+ * }
39
+ */
40
+ export declare function toErrorMessage(err: unknown, fallback?: string): string;
41
+ //# sourceMappingURL=error-message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-message.d.ts","sourceRoot":"","sources":["../../src/query/error-message.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI5D;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,SAAwB,GAAG,MAAM,CAKrF"}
@@ -0,0 +1,30 @@
1
+ function errorStatus(err) {
2
+ if (err === null || typeof err !== "object") return void 0;
3
+ const status = err.status;
4
+ return typeof status === "number" ? status : void 0;
5
+ }
6
+ const UNUSABLE = /* @__PURE__ */ new Set(["", "{}", "[]", "null", "undefined", "[object Object]"]);
7
+ function decode(err) {
8
+ if (err instanceof Error) return err.message;
9
+ if (typeof err === "string") return err;
10
+ if (typeof err === "object" && err !== null) {
11
+ const obj = err;
12
+ if (typeof obj["message"] === "string") return obj["message"];
13
+ if ("value" in obj) return decode(obj["value"]);
14
+ }
15
+ try {
16
+ return JSON.stringify(err) ?? "";
17
+ } catch {
18
+ return String(err);
19
+ }
20
+ }
21
+ function toErrorMessage(err, fallback = "The request failed.") {
22
+ const message = decode(err).trim();
23
+ if (!UNUSABLE.has(message)) return message;
24
+ const status = errorStatus(err);
25
+ return status === void 0 ? fallback : `${fallback} (HTTP ${status})`;
26
+ }
27
+ export {
28
+ errorStatus,
29
+ toErrorMessage
30
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=error-message.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-message.test.d.ts","sourceRoot":"","sources":["../../src/query/error-message.test.ts"],"names":[],"mappings":""}
@@ -40,25 +40,7 @@ export declare function unwrap<TData>(response: Promise<{
40
40
  data: TData | null;
41
41
  error: unknown;
42
42
  }>): Promise<TData>;
43
- /**
44
- * Extract a human-readable message from an unknown thrown value (e.g. the raw Eden envelope
45
- * thrown by `unwrap`). Removes the 8-line three-branch boilerplate consumers duplicate.
46
- *
47
- * Resolution order:
48
- * 1. `Error` instance → err.message
49
- * 2. `{ message: string }` → message (Eden / Elysia error shape)
50
- * 3. `{ value: ... }` → toErrorMessage(value) recursively (Eden nested value envelope)
51
- * 4. plain string → itself
52
- * 5. anything else → JSON.stringify with safe fallback to String()
53
- *
54
- * @example
55
- * try {
56
- * await unwrap(api.resource.post({ body }))
57
- * } catch (e) {
58
- * toast(toErrorMessage(e))
59
- * }
60
- */
61
- export declare function toErrorMessage(err: unknown): string;
43
+ export { toErrorMessage, errorStatus } from './error-message.js';
62
44
  export { QueryClientProvider, QueryErrorResetBoundary, useQueryErrorResetBoundary, } from '@tanstack/react-query';
63
45
  export { useQuery, useSuspenseQuery, useMutation, useQueryClient, useInfiniteQuery, queryOptions, } from '@tanstack/react-query';
64
46
  export { BasaltQueryDevtools } from './devtools.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/query/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAW9D;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,WAAW,CAQ/E;AAID;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,MAAM,CAAC,KAAK,EAChC,QAAQ,EAAE,OAAO,CAAC;IAAE,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,GACxD,OAAO,CAAC,KAAK,CAAC,CAQhB;AAID;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAcnD;AAKD,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EACL,QAAQ,EACR,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,YAAY,GACb,MAAM,uBAAuB,CAAA;AAI9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/query/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAW9D;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,WAAW,CAQ/E;AAID;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,MAAM,CAAC,KAAK,EAChC,QAAQ,EAAE,OAAO,CAAC;IAAE,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,GACxD,OAAO,CAAC,KAAK,CAAC,CAQhB;AAID,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAK7D,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EACL,QAAQ,EACR,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,YAAY,GACb,MAAM,uBAAuB,CAAA;AAI9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA"}
@@ -23,20 +23,7 @@ async function unwrap(response) {
23
23
  );
24
24
  return data;
25
25
  }
26
- function toErrorMessage(err) {
27
- if (err instanceof Error) return err.message;
28
- if (typeof err === "string") return err;
29
- if (typeof err === "object" && err !== null) {
30
- const obj = err;
31
- if (typeof obj["message"] === "string") return obj["message"];
32
- if ("value" in obj) return toErrorMessage(obj["value"]);
33
- }
34
- try {
35
- return JSON.stringify(err);
36
- } catch {
37
- return String(err);
38
- }
39
- }
26
+ import { toErrorMessage, errorStatus } from "./error-message.js";
40
27
  import {
41
28
  QueryClientProvider,
42
29
  QueryErrorResetBoundary,
@@ -56,6 +43,7 @@ export {
56
43
  QueryClientProvider,
57
44
  QueryErrorResetBoundary,
58
45
  createBasaltQueryClient,
46
+ errorStatus,
59
47
  queryOptions,
60
48
  toErrorMessage,
61
49
  unwrap,
@@ -131,7 +131,7 @@ export declare const SURFACES: {
131
131
  readonly rule: "mantine";
132
132
  readonly skill: readonly ["basalt-app", "basalt-design"];
133
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)";
134
+ readonly description: "BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard with threshold tone, EmptyState, QueryState/LoadingState/ErrorState, SettingsSection/SettingsRow/DangerZone)";
135
135
  readonly optionalPeers: readonly ["react-markdown", "remark-gfm", "rehype-sanitize", "remend", "shiki", "beautiful-mermaid"];
136
136
  readonly forbiddenImports: readonly [];
137
137
  };
package/dist/surfaces.js CHANGED
@@ -29,7 +29,7 @@ const SURFACES = {
29
29
  "sub-16-input-font",
30
30
  "mantine-shade-index"
31
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)",
32
+ description: "BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard with threshold tone, EmptyState, QueryState/LoadingState/ErrorState, SettingsSection/SettingsRow/DangerZone)",
33
33
  optionalPeers: [
34
34
  "react-markdown",
35
35
  "remark-gfm",
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # basalt-ui v1.23.1
1
+ # basalt-ui v1.25.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.23.1
22
+ Version: 1.25.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 with threshold tone, 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, QueryState/LoadingState/ErrorState, 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, rehype-sanitize@^6.0.0, remend@1.3.0, 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.23.1",
3
+ "version": "1.25.0",
4
4
  "description": "Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets",
5
5
  "keywords": [
6
6
  "charts",
@@ -654,6 +654,40 @@ describe('check-theme scans .css', () => {
654
654
  })
655
655
  })
656
656
 
657
+ // Round 9, rollhook: `SCANNABLE_EXT` stopped at `tsx?|css|html?|webmanifest`, so the marketing
658
+ // site's two `.astro` templates — its ENTIRE markup layer — were never opened, and check-theme
659
+ // reported a clean 4-file scan. `.jsx` and `.vue` were missing from the same regex, so a plain-JS
660
+ // or Vue consumer was unguarded end to end.
661
+ describe('check-theme scans the component extensions it was skipping', () => {
662
+ it('flags a raw hex in an .astro template', () => {
663
+ fixtureAt('pages/index.astro', '<style>\n .hero { color: #ff0000; }\n</style>\n')
664
+ const { code, err } = run()
665
+ expect(code).toBe(1)
666
+ expect(err).toContain('raw-hex')
667
+ expect(err).toContain('index.astro')
668
+ })
669
+
670
+ it('flags a raw hex in a .vue single-file component', () => {
671
+ fixtureAt('components/Hero.vue', '<style>\n .hero { color: #ff0000; }\n</style>\n')
672
+ const { code, err } = run()
673
+ expect(code).toBe(1)
674
+ expect(err).toContain('Hero.vue')
675
+ })
676
+
677
+ it('flags a raw inline border in a .jsx component', () => {
678
+ fixtureAt('Card.jsx', `export const C = () => <div style={{ border: '1px solid #ccc' }} />\n`)
679
+ const { code, err } = run()
680
+ expect(code).toBe(1)
681
+ expect(err).toContain('raw-surface')
682
+ expect(err).toContain('Card.jsx')
683
+ })
684
+
685
+ it('stays quiet on a clean .astro file — the extension is scanned, not condemned', () => {
686
+ fixtureAt('pages/index.astro', '<style>\n .hero { color: var(--vx-ink); }\n</style>\n')
687
+ expect(run().code).toBe(0)
688
+ })
689
+ })
690
+
657
691
  describe('check-theme comment skipping', () => {
658
692
  it('does NOT flag a banned element mentioned in a pure line comment', () => {
659
693
  fixtureAt(