basalt-ui 1.23.1 → 1.24.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 +14 -0
  3. package/MIGRATING.md +138 -4
  4. package/README.md +123 -32
  5. package/agent/rules/basalt-data.md +45 -13
  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 +61 -4
  23. package/dist/data/data-table.d.ts.map +1 -1
  24. package/dist/data/data-table.js +41 -4
  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 +1 -1
  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 +158 -0
  55. package/src/data/data-table.tsx +123 -5
  56. package/src/data/table.ts +6 -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
@@ -0,0 +1,70 @@
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
+ /**
8
+ * The HTTP status behind a thrown envelope (Eden Treaty's `{ status, value }`, a `Response`-shaped
9
+ * throw), when there is one. Use it to BRANCH — 404 → "not found" copy — rather than to build a
10
+ * message; `toErrorMessage` already folds the status into its text when the body says nothing.
11
+ *
12
+ * @example
13
+ * if (errorStatus(err) === 404) return <EmptyState title="Gone" />
14
+ */
15
+ export function errorStatus(err: unknown): number | undefined {
16
+ if (err === null || typeof err !== 'object') return undefined
17
+ const status = (err as { status?: unknown }).status
18
+ return typeof status === 'number' ? status : undefined
19
+ }
20
+
21
+ /** Strings that technically decoded but tell a reader nothing. `JSON.stringify` produces most. */
22
+ const UNUSABLE = new Set(['', '{}', '[]', 'null', 'undefined', '[object Object]'])
23
+
24
+ function decode(err: unknown): string {
25
+ if (err instanceof Error) return err.message
26
+ if (typeof err === 'string') return err
27
+ if (typeof err === 'object' && err !== null) {
28
+ const obj = err as Record<string, unknown>
29
+ if (typeof obj['message'] === 'string') return obj['message']
30
+ // Eden-style { value: ... } or { status, value } envelopes — unwrap one level.
31
+ if ('value' in obj) return decode(obj['value'])
32
+ }
33
+ try {
34
+ return JSON.stringify(err) ?? ''
35
+ } catch {
36
+ return String(err)
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Extract a human-readable message from an unknown thrown value (e.g. the raw Eden envelope
42
+ * thrown by `unwrap`). Removes the 8-line three-branch boilerplate consumers duplicate.
43
+ *
44
+ * Resolution order:
45
+ * 1. `Error` instance → err.message
46
+ * 2. `{ message: string }` → message (Eden / Elysia error shape)
47
+ * 3. `{ value: ... }` → recursively (Eden nested value envelope)
48
+ * 4. plain string → itself
49
+ * 5. anything else → `JSON.stringify`
50
+ *
51
+ * **If that yields nothing a human can read** (`''`, `'{}'`, `'[]'`, `'null'`, `'undefined'`,
52
+ * `'[object Object]'` — the shapes `JSON.stringify` produces for an opaque envelope) the `fallback`
53
+ * is returned instead, with `(HTTP <status>)` appended when the envelope carries one. Without that
54
+ * guard the caller renders a literal `{}` at the user, or — for `toErrorMessage(undefined)` —
55
+ * the actual value `undefined`, since `JSON.stringify(undefined)` is not a string at all.
56
+ *
57
+ * @example
58
+ * try {
59
+ * await unwrap(api.resource.post({ body }))
60
+ * } catch (e) {
61
+ * toast(toErrorMessage(e, 'Could not create share')) // → "slug already in use"
62
+ * // → "Could not create share (HTTP 502)"
63
+ * }
64
+ */
65
+ export function toErrorMessage(err: unknown, fallback = 'The request failed.'): string {
66
+ const message = decode(err).trim()
67
+ if (!UNUSABLE.has(message)) return message
68
+ const status = errorStatus(err)
69
+ return status === undefined ? fallback : `${fallback} (HTTP ${status})`
70
+ }
@@ -69,41 +69,9 @@ export async function unwrap<TData>(
69
69
  return data as TData
70
70
  }
71
71
 
72
- // ── toErrorMessage ────────────────────────────────────────────────────────────────────────────────
72
+ // ── Error decoding ────────────────────────────────────────────────────────────────────────────────
73
73
 
74
- /**
75
- * Extract a human-readable message from an unknown thrown value (e.g. the raw Eden envelope
76
- * thrown by `unwrap`). Removes the 8-line three-branch boilerplate consumers duplicate.
77
- *
78
- * Resolution order:
79
- * 1. `Error` instance → err.message
80
- * 2. `{ message: string }` → message (Eden / Elysia error shape)
81
- * 3. `{ value: ... }` → toErrorMessage(value) recursively (Eden nested value envelope)
82
- * 4. plain string → itself
83
- * 5. anything else → JSON.stringify with safe fallback to String()
84
- *
85
- * @example
86
- * try {
87
- * await unwrap(api.resource.post({ body }))
88
- * } catch (e) {
89
- * toast(toErrorMessage(e))
90
- * }
91
- */
92
- export function toErrorMessage(err: unknown): string {
93
- if (err instanceof Error) return err.message
94
- if (typeof err === 'string') return err
95
- if (typeof err === 'object' && err !== null) {
96
- const obj = err as Record<string, unknown>
97
- if (typeof obj['message'] === 'string') return obj['message']
98
- // Eden-style { value: ... } or { status, value } envelopes — unwrap one level.
99
- if ('value' in obj) return toErrorMessage(obj['value'])
100
- }
101
- try {
102
- return JSON.stringify(err)
103
- } catch {
104
- return String(err)
105
- }
106
- }
74
+ export { toErrorMessage, errorStatus } from './error-message'
107
75
 
108
76
  // ── Re-exports from @tanstack/react-query ────────────────────────────────────────────────────────
109
77
 
package/src/surfaces.ts CHANGED
@@ -194,7 +194,7 @@ export const SURFACES = {
194
194
  'mantine-shade-index',
195
195
  ],
196
196
  description:
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)',
197
+ '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)',
198
198
  optionalPeers: [
199
199
  'react-markdown',
200
200
  'remark-gfm',