@vaam-apps/ui 0.1.0 → 0.1.1

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 (283) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/components/data/code.d.ts +0 -1
  3. package/dist/components/data/code.js +0 -1
  4. package/dist/components/data/copy-button.d.ts +0 -1
  5. package/dist/components/data/copy-button.js +0 -1
  6. package/dist/components/data/detail-row.d.ts +0 -1
  7. package/dist/components/data/detail-row.js +0 -1
  8. package/dist/components/data/id-display.d.ts +0 -1
  9. package/dist/components/data/id-display.js +0 -1
  10. package/dist/components/data/masked-value.d.ts +0 -1
  11. package/dist/components/data/masked-value.js +0 -1
  12. package/dist/components/data/money.d.ts +0 -1
  13. package/dist/components/data/money.js +0 -1
  14. package/dist/components/data/phone-display.d.ts +0 -1
  15. package/dist/components/data/phone-display.js +0 -1
  16. package/dist/components/data/stat-tile.d.ts +0 -1
  17. package/dist/components/data/stat-tile.js +0 -1
  18. package/dist/components/data/timestamp-display.d.ts +0 -1
  19. package/dist/components/data/timestamp-display.js +0 -1
  20. package/dist/components/patterns/inline-banner.d.ts +0 -1
  21. package/dist/components/patterns/inline-banner.js +0 -1
  22. package/dist/components/patterns/inline-empty-state.d.ts +0 -1
  23. package/dist/components/patterns/inline-empty-state.js +0 -1
  24. package/dist/components/patterns/live-row.d.ts +0 -1
  25. package/dist/components/patterns/live-row.js +0 -1
  26. package/dist/components/patterns/payload-inspector.d.ts +0 -1
  27. package/dist/components/patterns/payload-inspector.js +0 -1
  28. package/dist/components/patterns/route-skeleton.d.ts +0 -1
  29. package/dist/components/patterns/route-skeleton.js +0 -1
  30. package/dist/components/patterns/screen-layout.d.ts +0 -1
  31. package/dist/components/patterns/screen-layout.js +0 -1
  32. package/dist/components/patterns/stale-write-banner.d.ts +0 -1
  33. package/dist/components/patterns/stale-write-banner.js +0 -1
  34. package/dist/components/patterns/state-timeline.d.ts +0 -1
  35. package/dist/components/patterns/state-timeline.js +0 -1
  36. package/dist/components/primitives/badge.d.ts +0 -1
  37. package/dist/components/primitives/badge.js +0 -1
  38. package/dist/components/primitives/button.d.ts +0 -1
  39. package/dist/components/primitives/button.js +0 -1
  40. package/dist/components/primitives/calendar.d.ts +0 -1
  41. package/dist/components/primitives/calendar.js +0 -1
  42. package/dist/components/primitives/card.d.ts +0 -1
  43. package/dist/components/primitives/card.js +0 -1
  44. package/dist/components/primitives/checkbox.d.ts +0 -1
  45. package/dist/components/primitives/checkbox.js +0 -1
  46. package/dist/components/primitives/chip-select.d.ts +0 -1
  47. package/dist/components/primitives/chip-select.js +0 -1
  48. package/dist/components/primitives/command-menu.d.ts +0 -1
  49. package/dist/components/primitives/command-menu.js +0 -1
  50. package/dist/components/primitives/confirm-dialog.d.ts +0 -1
  51. package/dist/components/primitives/confirm-dialog.js +0 -1
  52. package/dist/components/primitives/date-picker.d.ts +0 -1
  53. package/dist/components/primitives/date-picker.js +0 -1
  54. package/dist/components/primitives/dialog.d.ts +0 -1
  55. package/dist/components/primitives/dialog.js +0 -1
  56. package/dist/components/primitives/drawer.d.ts +0 -1
  57. package/dist/components/primitives/drawer.js +0 -1
  58. package/dist/components/primitives/dropdown-menu.d.ts +0 -1
  59. package/dist/components/primitives/dropdown-menu.js +0 -1
  60. package/dist/components/primitives/form-field.d.ts +0 -1
  61. package/dist/components/primitives/form-field.js +0 -1
  62. package/dist/components/primitives/inline-confirm.d.ts +0 -1
  63. package/dist/components/primitives/inline-confirm.js +0 -1
  64. package/dist/components/primitives/input.d.ts +0 -1
  65. package/dist/components/primitives/input.js +0 -1
  66. package/dist/components/primitives/label.d.ts +0 -1
  67. package/dist/components/primitives/label.js +0 -1
  68. package/dist/components/primitives/pagination.d.ts +0 -1
  69. package/dist/components/primitives/pagination.js +0 -1
  70. package/dist/components/primitives/popover.d.ts +0 -1
  71. package/dist/components/primitives/popover.js +0 -1
  72. package/dist/components/primitives/progress.d.ts +0 -1
  73. package/dist/components/primitives/progress.js +0 -1
  74. package/dist/components/primitives/radio-group.d.ts +0 -1
  75. package/dist/components/primitives/radio-group.js +0 -1
  76. package/dist/components/primitives/select.d.ts +0 -1
  77. package/dist/components/primitives/select.js +0 -1
  78. package/dist/components/primitives/separator.d.ts +0 -1
  79. package/dist/components/primitives/separator.js +0 -1
  80. package/dist/components/primitives/side-nav.d.ts +0 -1
  81. package/dist/components/primitives/side-nav.js +0 -1
  82. package/dist/components/primitives/skeleton.d.ts +0 -1
  83. package/dist/components/primitives/skeleton.js +0 -1
  84. package/dist/components/primitives/spinner.d.ts +0 -1
  85. package/dist/components/primitives/spinner.js +0 -1
  86. package/dist/components/primitives/switch.d.ts +0 -1
  87. package/dist/components/primitives/switch.js +0 -1
  88. package/dist/components/primitives/table.d.ts +0 -1
  89. package/dist/components/primitives/table.js +0 -1
  90. package/dist/components/primitives/tabs.d.ts +0 -1
  91. package/dist/components/primitives/tabs.js +0 -1
  92. package/dist/components/primitives/textarea.d.ts +0 -1
  93. package/dist/components/primitives/textarea.js +0 -1
  94. package/dist/components/primitives/toast.d.ts +0 -1
  95. package/dist/components/primitives/toast.js +0 -1
  96. package/dist/components/primitives/tooltip.d.ts +0 -1
  97. package/dist/components/primitives/tooltip.js +0 -1
  98. package/dist/components/status/state-chip.d.ts +0 -1
  99. package/dist/components/status/state-chip.js +0 -1
  100. package/dist/components/status/state-mark.d.ts +0 -1
  101. package/dist/components/status/state-mark.js +0 -1
  102. package/dist/components/status/status-pill.d.ts +0 -1
  103. package/dist/components/status/status-pill.js +0 -1
  104. package/dist/components/status/status-tokens.d.ts +0 -1
  105. package/dist/components/status/status-tokens.js +0 -1
  106. package/dist/index.d.ts +0 -1
  107. package/dist/index.js +0 -1
  108. package/dist/lib/cn.d.ts +0 -1
  109. package/dist/lib/cn.js +0 -1
  110. package/dist/lib/money.d.ts +0 -1
  111. package/dist/lib/money.js +0 -1
  112. package/dist/lib/omit-undefined.d.ts +0 -1
  113. package/dist/lib/omit-undefined.js +0 -1
  114. package/package.json +3 -7
  115. package/dist/components/data/code.d.ts.map +0 -1
  116. package/dist/components/data/code.js.map +0 -1
  117. package/dist/components/data/copy-button.d.ts.map +0 -1
  118. package/dist/components/data/copy-button.js.map +0 -1
  119. package/dist/components/data/detail-row.d.ts.map +0 -1
  120. package/dist/components/data/detail-row.js.map +0 -1
  121. package/dist/components/data/id-display.d.ts.map +0 -1
  122. package/dist/components/data/id-display.js.map +0 -1
  123. package/dist/components/data/masked-value.d.ts.map +0 -1
  124. package/dist/components/data/masked-value.js.map +0 -1
  125. package/dist/components/data/money.d.ts.map +0 -1
  126. package/dist/components/data/money.js.map +0 -1
  127. package/dist/components/data/phone-display.d.ts.map +0 -1
  128. package/dist/components/data/phone-display.js.map +0 -1
  129. package/dist/components/data/stat-tile.d.ts.map +0 -1
  130. package/dist/components/data/stat-tile.js.map +0 -1
  131. package/dist/components/data/timestamp-display.d.ts.map +0 -1
  132. package/dist/components/data/timestamp-display.js.map +0 -1
  133. package/dist/components/patterns/inline-banner.d.ts.map +0 -1
  134. package/dist/components/patterns/inline-banner.js.map +0 -1
  135. package/dist/components/patterns/inline-empty-state.d.ts.map +0 -1
  136. package/dist/components/patterns/inline-empty-state.js.map +0 -1
  137. package/dist/components/patterns/live-row.d.ts.map +0 -1
  138. package/dist/components/patterns/live-row.js.map +0 -1
  139. package/dist/components/patterns/payload-inspector.d.ts.map +0 -1
  140. package/dist/components/patterns/payload-inspector.js.map +0 -1
  141. package/dist/components/patterns/route-skeleton.d.ts.map +0 -1
  142. package/dist/components/patterns/route-skeleton.js.map +0 -1
  143. package/dist/components/patterns/screen-layout.d.ts.map +0 -1
  144. package/dist/components/patterns/screen-layout.js.map +0 -1
  145. package/dist/components/patterns/stale-write-banner.d.ts.map +0 -1
  146. package/dist/components/patterns/stale-write-banner.js.map +0 -1
  147. package/dist/components/patterns/state-timeline.d.ts.map +0 -1
  148. package/dist/components/patterns/state-timeline.js.map +0 -1
  149. package/dist/components/primitives/badge.d.ts.map +0 -1
  150. package/dist/components/primitives/badge.js.map +0 -1
  151. package/dist/components/primitives/button.d.ts.map +0 -1
  152. package/dist/components/primitives/button.js.map +0 -1
  153. package/dist/components/primitives/calendar.d.ts.map +0 -1
  154. package/dist/components/primitives/calendar.js.map +0 -1
  155. package/dist/components/primitives/card.d.ts.map +0 -1
  156. package/dist/components/primitives/card.js.map +0 -1
  157. package/dist/components/primitives/checkbox.d.ts.map +0 -1
  158. package/dist/components/primitives/checkbox.js.map +0 -1
  159. package/dist/components/primitives/chip-select.d.ts.map +0 -1
  160. package/dist/components/primitives/chip-select.js.map +0 -1
  161. package/dist/components/primitives/command-menu.d.ts.map +0 -1
  162. package/dist/components/primitives/command-menu.js.map +0 -1
  163. package/dist/components/primitives/confirm-dialog.d.ts.map +0 -1
  164. package/dist/components/primitives/confirm-dialog.js.map +0 -1
  165. package/dist/components/primitives/date-picker.d.ts.map +0 -1
  166. package/dist/components/primitives/date-picker.js.map +0 -1
  167. package/dist/components/primitives/dialog.d.ts.map +0 -1
  168. package/dist/components/primitives/dialog.js.map +0 -1
  169. package/dist/components/primitives/drawer.d.ts.map +0 -1
  170. package/dist/components/primitives/drawer.js.map +0 -1
  171. package/dist/components/primitives/dropdown-menu.d.ts.map +0 -1
  172. package/dist/components/primitives/dropdown-menu.js.map +0 -1
  173. package/dist/components/primitives/form-field.d.ts.map +0 -1
  174. package/dist/components/primitives/form-field.js.map +0 -1
  175. package/dist/components/primitives/inline-confirm.d.ts.map +0 -1
  176. package/dist/components/primitives/inline-confirm.js.map +0 -1
  177. package/dist/components/primitives/input.d.ts.map +0 -1
  178. package/dist/components/primitives/input.js.map +0 -1
  179. package/dist/components/primitives/label.d.ts.map +0 -1
  180. package/dist/components/primitives/label.js.map +0 -1
  181. package/dist/components/primitives/pagination.d.ts.map +0 -1
  182. package/dist/components/primitives/pagination.js.map +0 -1
  183. package/dist/components/primitives/popover.d.ts.map +0 -1
  184. package/dist/components/primitives/popover.js.map +0 -1
  185. package/dist/components/primitives/progress.d.ts.map +0 -1
  186. package/dist/components/primitives/progress.js.map +0 -1
  187. package/dist/components/primitives/radio-group.d.ts.map +0 -1
  188. package/dist/components/primitives/radio-group.js.map +0 -1
  189. package/dist/components/primitives/select.d.ts.map +0 -1
  190. package/dist/components/primitives/select.js.map +0 -1
  191. package/dist/components/primitives/separator.d.ts.map +0 -1
  192. package/dist/components/primitives/separator.js.map +0 -1
  193. package/dist/components/primitives/side-nav.d.ts.map +0 -1
  194. package/dist/components/primitives/side-nav.js.map +0 -1
  195. package/dist/components/primitives/skeleton.d.ts.map +0 -1
  196. package/dist/components/primitives/skeleton.js.map +0 -1
  197. package/dist/components/primitives/spinner.d.ts.map +0 -1
  198. package/dist/components/primitives/spinner.js.map +0 -1
  199. package/dist/components/primitives/switch.d.ts.map +0 -1
  200. package/dist/components/primitives/switch.js.map +0 -1
  201. package/dist/components/primitives/table.d.ts.map +0 -1
  202. package/dist/components/primitives/table.js.map +0 -1
  203. package/dist/components/primitives/tabs.d.ts.map +0 -1
  204. package/dist/components/primitives/tabs.js.map +0 -1
  205. package/dist/components/primitives/textarea.d.ts.map +0 -1
  206. package/dist/components/primitives/textarea.js.map +0 -1
  207. package/dist/components/primitives/toast.d.ts.map +0 -1
  208. package/dist/components/primitives/toast.js.map +0 -1
  209. package/dist/components/primitives/tooltip.d.ts.map +0 -1
  210. package/dist/components/primitives/tooltip.js.map +0 -1
  211. package/dist/components/status/state-chip.d.ts.map +0 -1
  212. package/dist/components/status/state-chip.js.map +0 -1
  213. package/dist/components/status/state-mark.d.ts.map +0 -1
  214. package/dist/components/status/state-mark.js.map +0 -1
  215. package/dist/components/status/status-pill.d.ts.map +0 -1
  216. package/dist/components/status/status-pill.js.map +0 -1
  217. package/dist/components/status/status-tokens.d.ts.map +0 -1
  218. package/dist/components/status/status-tokens.js.map +0 -1
  219. package/dist/index.d.ts.map +0 -1
  220. package/dist/index.js.map +0 -1
  221. package/dist/lib/cn.d.ts.map +0 -1
  222. package/dist/lib/cn.js.map +0 -1
  223. package/dist/lib/money.d.ts.map +0 -1
  224. package/dist/lib/money.js.map +0 -1
  225. package/dist/lib/omit-undefined.d.ts.map +0 -1
  226. package/dist/lib/omit-undefined.js.map +0 -1
  227. package/src/components/data/code.tsx +0 -43
  228. package/src/components/data/copy-button.tsx +0 -97
  229. package/src/components/data/detail-row.tsx +0 -113
  230. package/src/components/data/id-display.tsx +0 -49
  231. package/src/components/data/masked-value.tsx +0 -96
  232. package/src/components/data/money.tsx +0 -46
  233. package/src/components/data/phone-display.tsx +0 -60
  234. package/src/components/data/stat-tile.tsx +0 -68
  235. package/src/components/data/timestamp-display.tsx +0 -106
  236. package/src/components/patterns/inline-banner.tsx +0 -60
  237. package/src/components/patterns/inline-empty-state.tsx +0 -45
  238. package/src/components/patterns/live-row.tsx +0 -75
  239. package/src/components/patterns/payload-inspector.tsx +0 -157
  240. package/src/components/patterns/route-skeleton.tsx +0 -77
  241. package/src/components/patterns/screen-layout.tsx +0 -44
  242. package/src/components/patterns/stale-write-banner.tsx +0 -44
  243. package/src/components/patterns/state-timeline.tsx +0 -229
  244. package/src/components/primitives/badge.tsx +0 -40
  245. package/src/components/primitives/button.tsx +0 -54
  246. package/src/components/primitives/calendar.tsx +0 -136
  247. package/src/components/primitives/card.tsx +0 -70
  248. package/src/components/primitives/checkbox.tsx +0 -101
  249. package/src/components/primitives/chip-select.tsx +0 -105
  250. package/src/components/primitives/command-menu.tsx +0 -92
  251. package/src/components/primitives/confirm-dialog.tsx +0 -109
  252. package/src/components/primitives/date-picker.tsx +0 -271
  253. package/src/components/primitives/dialog.tsx +0 -195
  254. package/src/components/primitives/drawer.tsx +0 -326
  255. package/src/components/primitives/dropdown-menu.tsx +0 -128
  256. package/src/components/primitives/form-field.tsx +0 -136
  257. package/src/components/primitives/inline-confirm.tsx +0 -118
  258. package/src/components/primitives/input.tsx +0 -21
  259. package/src/components/primitives/label.tsx +0 -43
  260. package/src/components/primitives/pagination.tsx +0 -110
  261. package/src/components/primitives/popover.tsx +0 -41
  262. package/src/components/primitives/progress.tsx +0 -66
  263. package/src/components/primitives/radio-group.tsx +0 -90
  264. package/src/components/primitives/select.tsx +0 -254
  265. package/src/components/primitives/separator.tsx +0 -49
  266. package/src/components/primitives/side-nav.tsx +0 -253
  267. package/src/components/primitives/skeleton.tsx +0 -25
  268. package/src/components/primitives/spinner.tsx +0 -39
  269. package/src/components/primitives/switch.tsx +0 -74
  270. package/src/components/primitives/table.tsx +0 -157
  271. package/src/components/primitives/tabs.tsx +0 -163
  272. package/src/components/primitives/textarea.tsx +0 -21
  273. package/src/components/primitives/toast.tsx +0 -117
  274. package/src/components/primitives/tooltip.tsx +0 -38
  275. package/src/components/status/state-chip.tsx +0 -70
  276. package/src/components/status/state-mark.tsx +0 -154
  277. package/src/components/status/status-pill.tsx +0 -153
  278. package/src/components/status/status-tokens.ts +0 -173
  279. package/src/index.ts +0 -96
  280. package/src/lib/cn.ts +0 -168
  281. package/src/lib/money.ts +0 -164
  282. package/src/lib/omit-undefined.ts +0 -25
  283. /package/{src → dist}/styles/theme.css +0 -0
@@ -1,157 +0,0 @@
1
- "use client";
2
-
3
- import { useState } from "react";
4
- import { cn } from "../../lib/cn";
5
- // D18: `Tabs` was rebuilt as `ValueTabs` (Headless UI `TabGroup` behind a
6
- // value-based adapter) — aliased on import so nothing below this line
7
- // changes; see tabs.tsx's own module doc for the full mechanism.
8
- import {
9
- ValueTabs as Tabs,
10
- ValueTabsContent as TabsContent,
11
- ValueTabsList as TabsList,
12
- ValueTabsTrigger as TabsTrigger,
13
- } from "../primitives/tabs";
14
-
15
- export interface PayloadExchange {
16
- direction: "request" | "response" | "callback";
17
- method?: string;
18
- url?: string;
19
- status?: number;
20
- durationMs?: number;
21
- headers?: Record<string, string>;
22
- body?: string;
23
- error?: string;
24
- }
25
-
26
- export interface PayloadInspectorProps {
27
- exchanges: PayloadExchange[];
28
- defaultOpen?: number;
29
- /** Bodies over this size collapse behind an explicit "load full payload" action. */
30
- maxInlineBytes?: number;
31
- }
32
-
33
- function StatusChip({ status }: { status?: number | undefined }) {
34
- if (status == null) return null;
35
- const ok = status < 400;
36
- return (
37
- <span
38
- className={cn(
39
- "rounded-sm border px-1.5 py-0.5 font-mono text-caption",
40
- ok
41
- ? "border-edge text-muted-foreground"
42
- : "border-state-danger-border text-state-danger-fg",
43
- )}
44
- >
45
- {status}
46
- </span>
47
- );
48
- }
49
-
50
- function ExchangeBody({
51
- exchange,
52
- maxInlineBytes,
53
- }: {
54
- exchange: PayloadExchange;
55
- maxInlineBytes: number;
56
- }) {
57
- const [forceLoad, setForceLoad] = useState(false);
58
- const body = exchange.body ?? "";
59
- const oversized = !forceLoad && body.length > maxInlineBytes;
60
-
61
- return (
62
- <Tabs defaultValue="body">
63
- <TabsList>
64
- <TabsTrigger value="body">Body</TabsTrigger>
65
- <TabsTrigger value="headers">Headers</TabsTrigger>
66
- {exchange.error != null && <TabsTrigger value="error">Error</TabsTrigger>}
67
- </TabsList>
68
- <TabsContent value="body">
69
- {body.length === 0 ? (
70
- <p className="py-3 text-caption text-subtle-foreground">No response body was recorded.</p>
71
- ) : oversized ? (
72
- <button
73
- type="button"
74
- onClick={() => setForceLoad(true)}
75
- className="py-3 text-caption text-foreground underline"
76
- >
77
- Load full payload ({(body.length / 1024).toFixed(1)} KB)
78
- </button>
79
- ) : (
80
- <pre className="max-h-96 overflow-auto rounded-sm bg-base-100 p-3 font-mono text-[12px] text-foreground">
81
- {body}
82
- </pre>
83
- )}
84
- </TabsContent>
85
- <TabsContent value="headers">
86
- {exchange.headers == null || Object.keys(exchange.headers).length === 0 ? (
87
- <p className="py-3 text-caption text-subtle-foreground">No headers recorded.</p>
88
- ) : (
89
- <dl className="grid grid-cols-[max-content_1fr] gap-x-3 gap-y-1 py-2 font-mono text-[12px]">
90
- {Object.entries(exchange.headers).map(([key, value]) => (
91
- <div key={key} className="contents">
92
- <dt className="text-subtle-foreground">{key}</dt>
93
- <dd className="break-all text-foreground">{value}</dd>
94
- </div>
95
- ))}
96
- </dl>
97
- )}
98
- </TabsContent>
99
- {exchange.error != null && (
100
- <TabsContent value="error">
101
- <pre className="max-h-96 overflow-auto rounded-sm border border-state-danger-border bg-state-danger-bg p-3 font-mono text-[12px] text-state-danger-fg">
102
- {exchange.error}
103
- </pre>
104
- </TabsContent>
105
- )}
106
- </Tabs>
107
- );
108
- }
109
-
110
- /**
111
- * Raw provider request/response and DLR bodies, verbatim (design doc §5.3).
112
- * No syntax-highlighting palette on purpose (§1.3): a rainbow JSON block
113
- * would be the loudest thing on a diagnostic screen. Uses native
114
- * `<details>` for the accordion behaviour — free keyboard/ARIA semantics,
115
- * no extra Radix dependency needed for a plain expand/collapse.
116
- */
117
- export function PayloadInspector({
118
- exchanges,
119
- defaultOpen = 0,
120
- maxInlineBytes = 262144,
121
- }: PayloadInspectorProps) {
122
- return (
123
- <div className="flex flex-col gap-2">
124
- {exchanges.map((exchange, i) => (
125
- <details
126
- // biome-ignore lint/suspicious/noArrayIndexKey: exchanges are a fixed, ordered, append-only log
127
- key={i}
128
- open={i === defaultOpen}
129
- className="rounded-sm border border-edge bg-surface-2"
130
- >
131
- <summary className="flex cursor-pointer list-none items-center gap-2 px-3 py-2 text-body">
132
- <span className="font-mono text-micro text-muted-foreground">{exchange.direction}</span>
133
- {exchange.method != null && (
134
- <span className="font-mono text-caption text-foreground">{exchange.method}</span>
135
- )}
136
- {exchange.url != null && (
137
- <span className="truncate font-mono text-caption text-subtle-foreground">
138
- {exchange.url}
139
- </span>
140
- )}
141
- <span className="ml-auto flex items-center gap-2">
142
- <StatusChip status={exchange.status} />
143
- {exchange.durationMs != null && (
144
- <span className="font-mono text-caption text-subtle-foreground">
145
- {exchange.durationMs}ms
146
- </span>
147
- )}
148
- </span>
149
- </summary>
150
- <div className="border-edge border-t px-3 pb-3">
151
- <ExchangeBody exchange={exchange} maxInlineBytes={maxInlineBytes} />
152
- </div>
153
- </details>
154
- ))}
155
- </div>
156
- );
157
- }
@@ -1,77 +0,0 @@
1
- import { Skeleton } from "../primitives/skeleton";
2
- import { ScreenStack } from "./screen-layout";
3
-
4
- // #308's own mitigation half — a real, visible fallback for the
5
- // `<Suspense>` boundary every screen that reads `useSearchParams()`/
6
- // `useQueryStates()` (`nuqs`) must wrap itself in (Next.js requires this;
7
- // see AGENTS.md's own R6-adjacent note on `page.tsx` shells). The eight
8
- // screens that still need that boundary — `messages`, `webhooks`, `jobs`,
9
- // `workers`, `opt-outs`, `apps`, `users`, `audit-log` — used to pass
10
- // `fallback={null}`, which is exactly the shape #308 diagnosed: a
11
- // `<Suspense>` boundary is a real, load-bearing part of Next's streaming
12
- // SSR pipeline, and its reveal (the swap from the fallback to the real
13
- // tree) is `requestAnimationFrame`-gated by both React's own inline
14
- // completion script (`$RC` schedules `$RV` via rAF) and the hydration
15
- // retry that follows it. A backgrounded/non-composited tab — proven live
16
- // against this exact build, see the PR/issue thread — never runs that rAF
17
- // callback, so `fallback={null}` rendered as a permanently empty `<main>`
18
- // with zero signal that anything was ever wrong. This is NOT a fix for
19
- // that mechanism (nothing in application code can make a browser paint a
20
- // hidden tab), it only makes the wait visible instead of indistinguishable
21
- // from broken. Ordinary, foregrounded browser tabs reveal the real
22
- // boundary within one frame regardless, mitigation or not.
23
- //
24
- // One reusable component rather than nine bespoke skeletons — every
25
- // affected screen needs the same shape (a title bar, an optional filter
26
- // row, several table-row-shaped blocks), and `Skeleton` itself already
27
- // forbids shimmer (design doc §3.8/§5.2, see that primitive's own doc)
28
- // so there is no per-screen animation timing to keep in sync anyway. Per
29
- // R6, this lives in `@vaam-apps/ui`, not duplicated per route.
30
-
31
- export interface RouteSkeletonProps {
32
- /** How many table-row-shaped blocks to render. Doesn't need to match the
33
- * real screen's row height exactly (`Skeleton`'s own doc asks for that
34
- * only where a skeleton stands in for an already-mounted, known-shape
35
- * table) — this fallback exists for a `<main>` that hasn't rendered
36
- * *anything* app-specific yet, not a loading row inside a mounted
37
- * table. */
38
- rows?: number;
39
- /** Whether to render a filter-bar-shaped skeleton row above the table
40
- * rows — most of the eight screens this serves have one (a search box,
41
- * a couple of selects); a couple don't. */
42
- withFilterBar?: boolean;
43
- }
44
-
45
- export function RouteSkeleton({ rows = 6, withFilterBar = true }: RouteSkeletonProps) {
46
- return (
47
- <ScreenStack>
48
- {/* Not `ScreenHeader` here: its `title`/`description` slots render
49
- * inside `<h1>`/`<p>`, and `<p>` cannot contain a block-level
50
- * element like `Skeleton`'s own `<div>` — the browser's HTML parser
51
- * gives `<p>` an implied end tag the instant it meets one (no such
52
- * rule applies to `<h1>`), silently splitting `<p><div/></p>` into
53
- * two siblings and reparenting the div outside the paragraph. That
54
- * is a real, reproduced-live hydration-mismatch shape, not a
55
- * hypothetical — found by rendering this exact component before
56
- * this comment was written. A plain header block, skeleton bars as
57
- * direct children, sidesteps it entirely. */}
58
- <div className="flex flex-col gap-1">
59
- <Skeleton className="h-7 w-48" />
60
- <Skeleton className="h-4 w-80" />
61
- </div>
62
- {withFilterBar && (
63
- <div className="flex flex-wrap items-center gap-2">
64
- <Skeleton className="h-9 w-64" />
65
- <Skeleton className="h-9 w-32" />
66
- <Skeleton className="h-9 w-32" />
67
- </div>
68
- )}
69
- <div className="flex flex-col gap-2">
70
- {Array.from({ length: rows }, (_, i) => (
71
- // biome-ignore lint/suspicious/noArrayIndexKey: static placeholder rows, a fixed count, never reordered/removed individually.
72
- <Skeleton key={i} className="h-11 w-full" />
73
- ))}
74
- </div>
75
- </ScreenStack>
76
- );
77
- }
@@ -1,44 +0,0 @@
1
- import type { ReactNode } from "react";
2
- import { cn } from "../../lib/cn";
3
-
4
- // R6 (AGENTS.md): every route screen was hand-rolling the identical
5
- // `<div className="flex flex-col gap-6">` wrapper plus a
6
- // `<h1 className="font-medium text-foreground text-title">`/description
7
- // `<header>` pair — confirmed by grep, ten of eleven screens under
8
- // `frontends/apps/admin/app/*/*.tsx` had this exact pair before this file
9
- // existed. R6's own test ("if a second route would plausibly use it, it
10
- // belongs in `frontends/packages/ui`") is not a close call here. Extracted
11
- // verbatim from `jobs-screen.tsx`/`workers-screen.tsx`/`opt-outs-screen.tsx`
12
- // — same classes, not a redesign.
13
- //
14
- // New file, added while several route groups were being moved onto R6 in
15
- // parallel — expect another agent's PR to add an equivalent. If both land,
16
- // reconcile onto one of the two rather than keeping both: same shape,
17
- // different name is worse than either alone.
18
-
19
- export interface ScreenStackProps {
20
- children: ReactNode;
21
- className?: string;
22
- }
23
-
24
- /** The page-level vertical rhythm every screen composes its sections into. */
25
- export function ScreenStack({ children, className }: ScreenStackProps) {
26
- return <div className={cn("flex flex-col gap-6", className)}>{children}</div>;
27
- }
28
-
29
- export interface ScreenHeaderProps {
30
- title: ReactNode;
31
- description?: ReactNode;
32
- }
33
-
34
- /** A screen's own `<h1>` + one-line description, styled once. */
35
- export function ScreenHeader({ title, description }: ScreenHeaderProps) {
36
- return (
37
- <header className="flex flex-col gap-1">
38
- <h1 className="font-medium text-foreground text-title">{title}</h1>
39
- {description != null && (
40
- <p className="max-w-xl text-body text-muted-foreground">{description}</p>
41
- )}
42
- </header>
43
- );
44
- }
@@ -1,44 +0,0 @@
1
- import { Button } from "../primitives/button";
2
- import { InlineBanner } from "./inline-banner";
3
-
4
- /**
5
- * "Someone else changed this row since it loaded" — the banner for a
6
- * `412 Precondition Failed` save.
7
- *
8
- * Shared rather than route-local because the thing it reports is not
9
- * route-local: #59 gave `@version` to ten operator-editable models and
10
- * threaded ETag/`If-Match` through the generated REST layer, so *every*
11
- * edit screen in the console can lose this race. `apps/` and `users/` each
12
- * hand-rolled it; the rest will need it as their edit screens land.
13
- *
14
- * The two hand-rolled copies differed in exactly one way — one hardcoded
15
- * its sentence, the other took a `message` prop. The prop version is
16
- * strictly more general, so it wins, with the hardcoded sentence as the
17
- * default so the common case stays a one-line call.
18
- *
19
- * `warning`, not `danger`: nothing was lost and nothing is broken. The
20
- * write was *refused* precisely so the other operator's edit survives —
21
- * which is the mechanism working, not failing. Reloading resolves it.
22
- * `packages/gateway/src/errors.ts`'s `isStaleWriteError` exists to
23
- * distinguish this from a genuine conflict for the same reason.
24
- */
25
- export function StaleWriteBanner({
26
- message = "Someone else changed this row since it loaded. Reload to see their edit.",
27
- onReload,
28
- }: {
29
- message?: string | undefined;
30
- onReload: () => void;
31
- }) {
32
- return (
33
- <InlineBanner variant="warning" className="flex items-center justify-between gap-3">
34
- <span>{message}</span>
35
- {/* `type="button"` is load-bearing, not boilerplate: both call sites
36
- render this *inside* a `<form>`, where a button's default type is
37
- `submit`. Dropping it would make "Reload" submit the very edit the
38
- banner is warning has gone stale. */}
39
- <Button type="button" variant="secondary" size="sm" onClick={onReload}>
40
- Reload
41
- </Button>
42
- </InlineBanner>
43
- );
44
- }
@@ -1,229 +0,0 @@
1
- import { Info } from "lucide-react";
2
- import type { ReactNode } from "react";
3
- import { cn } from "../../lib/cn";
4
- import { Skeleton } from "../primitives/skeleton";
5
- import { StateMark } from "../status/state-mark";
6
- import type { StatusSystem } from "../status/status-tokens";
7
- import { type PayloadExchange, PayloadInspector } from "./payload-inspector";
8
-
9
- export interface StateTransition<S extends string = string> {
10
- toState: S;
11
- /** ISO 8601 timestamp. */
12
- at: string;
13
- actor?: string;
14
- providerKey?: string;
15
- workerNode?: string;
16
- attempt?: number;
17
- maxAttempts?: number;
18
- payload?: PayloadExchange[];
19
- }
20
-
21
- export interface StateTimelineProps<S extends string = string> {
22
- transitions: StateTransition<S>[];
23
- /** The state machine's presentation table — the same one its
24
- * `createStatusPill` is bound to, so a timeline and a pill can never
25
- * disagree about what a state looks like. */
26
- system: StatusSystem<S>;
27
- currentState: S;
28
- /** Whether the timeline has stopped. Passed in rather than derived from
29
- * `system[currentState].family`: terminality is the server's fact, and
30
- * a presentational table is the wrong place to learn it from — see
31
- * `status-tokens.ts`'s own warning. */
32
- isTerminal: boolean;
33
- /**
34
- * Any IANA zone name, e.g. `"UTC"`, `"Africa/Douala"`, `"America/New_York"`.
35
- * The UTC-offset suffix is derived from the zone and the instant, so it
36
- * stays correct across DST and for zones this library has never heard
37
- * of. (It used to be a two-value union with the suffix hard-coded as
38
- * `"Z"` or `"+01"` — correct only for the two zones that union named.)
39
- */
40
- timezone?: string;
41
- /**
42
- * Per-state explanatory notes, rendered beneath the node that entered
43
- * that state.
44
- *
45
- * The point is states that look like bugs to anyone who does not
46
- * already know the product decision behind them — "we never learned the
47
- * outcome, and deliberately will not retry". Without the note the
48
- * operator's next move is to open a SQL client, which is the outcome a
49
- * timeline exists to prevent. Supplied by the caller because the
50
- * explanation is domain knowledge, not presentation.
51
- */
52
- annotations?: Partial<Record<S, string>> | undefined;
53
- }
54
-
55
- /**
56
- * `2026-09-11 14:03:07 +01` — sortable, unambiguous, and carrying its own
57
- * offset so a screenshot pasted into a ticket is still interpretable.
58
- *
59
- * The offset is read out of `Intl`'s own `shortOffset` part rather than
60
- * assumed from the zone name: it is the only way to be right across DST
61
- * and across zones this component has never been told about. `UTC` keeps
62
- * its conventional `Z` rather than the `GMT` that `shortOffset` yields.
63
- */
64
- function formatAbsolute(iso: string, timezone: string): string {
65
- const date = new Date(iso);
66
- const formatter = new Intl.DateTimeFormat("en-CA", {
67
- timeZone: timezone,
68
- year: "numeric",
69
- month: "2-digit",
70
- day: "2-digit",
71
- hour: "2-digit",
72
- minute: "2-digit",
73
- second: "2-digit",
74
- hourCycle: "h23",
75
- timeZoneName: "shortOffset",
76
- });
77
- const parts = formatter.formatToParts(date);
78
- const stamp = parts
79
- .filter((part) => part.type !== "timeZoneName" && part.type !== "literal")
80
- .reduce<string[]>((acc, part) => {
81
- acc.push(part.value);
82
- return acc;
83
- }, []);
84
- const [year, month, day, hour, minute, second] = stamp;
85
- // "GMT+1" / "GMT-05:00" / "GMT" → "+01" / "-05:00" / "Z".
86
- const raw = parts.find((part) => part.type === "timeZoneName")?.value ?? "";
87
- const offset = raw === "GMT" ? "Z" : raw.replace(/^GMT/, "").replace(/^([+-])(\d)$/, "$10$2");
88
- return `${year}-${month}-${day} ${hour}:${minute}:${second} ${offset}`;
89
- }
90
-
91
- function formatElapsed(ms: number): string {
92
- if (ms < 1000) return `+${ms}ms`;
93
- if (ms < 60_000) return `+${(ms / 1000).toFixed(3)}s`;
94
- const totalSeconds = Math.round(ms / 1000);
95
- const minutes = Math.floor(totalSeconds / 60);
96
- const seconds = totalSeconds % 60;
97
- if (minutes < 60) return `+${minutes}m ${String(seconds).padStart(2, "0")}s`;
98
- const hours = Math.floor(minutes / 60);
99
- return `+${hours}h ${String(minutes % 60).padStart(2, "0")}m`;
100
- }
101
-
102
- function AnnotationNode({ text }: { text: string }) {
103
- return (
104
- <li className="relative flex gap-3 pb-4 pl-0">
105
- <div className="flex w-4 shrink-0 justify-center">
106
- <Info size={14} strokeWidth={1.5} className="mt-0.5 text-muted-foreground" />
107
- </div>
108
- <div className="min-w-0 flex-1 rounded-sm border border-edge bg-surface-2 px-3 py-2 text-caption text-muted-foreground">
109
- {text}
110
- </div>
111
- </li>
112
- );
113
- }
114
-
115
- /**
116
- * A record's transition history: one node per state entered, with elapsed
117
- * time between them, optional per-transition metadata, and an optional
118
- * payload inspector.
119
- *
120
- * Generic over the state machine — pass the same [`StatusSystem`] its
121
- * pill is bound to. It used to be hard-wired to one application's message
122
- * states, including that application's own explanatory notes and a
123
- * two-value timezone union; all three are now the caller's.
124
- */
125
- export function StateTimeline<S extends string>({
126
- transitions,
127
- system,
128
- currentState,
129
- isTerminal,
130
- timezone = "UTC",
131
- annotations,
132
- }: StateTimelineProps<S>) {
133
- if (transitions.length === 0) {
134
- return (
135
- <ol className="flex flex-col gap-0">
136
- {[0, 1, 2].map((i) => (
137
- <li key={i} className="flex items-center gap-3 py-2">
138
- <Skeleton className="h-4 w-4 rounded-full" />
139
- <Skeleton className="h-4 w-40" />
140
- </li>
141
- ))}
142
- </ol>
143
- );
144
- }
145
-
146
- const rows: ReactNode[] = [];
147
-
148
- transitions.forEach((transition, i) => {
149
- const previous = transitions[i - 1];
150
- const elapsedMs = previous
151
- ? new Date(transition.at).getTime() - new Date(previous.at).getTime()
152
- : null;
153
- const meta = system[transition.toState];
154
- const isLast = i === transitions.length - 1;
155
-
156
- rows.push(
157
- <li
158
- key={`${transition.toState}-${transition.at}`}
159
- className="relative flex gap-3 pb-6 last:pb-0"
160
- >
161
- <div className="flex w-4 shrink-0 flex-col items-center">
162
- <StateMark meta={meta} size={16} className="text-foreground" />
163
- {!isLast && <span className="mt-1 w-px flex-1 bg-[var(--state-mark-rail,var(--edge))]" />}
164
- </div>
165
- <div className="min-w-0 flex-1">
166
- <div className="flex items-baseline justify-between gap-2">
167
- <p className="font-medium text-body text-foreground">
168
- {meta.label}{" "}
169
- <span className="font-mono text-subtle-foreground">{transition.toState}</span>
170
- </p>
171
- <div className="shrink-0 text-right">
172
- <p className="font-mono text-caption text-foreground">
173
- {formatAbsolute(transition.at, timezone)}
174
- </p>
175
- {elapsedMs != null && (
176
- <p className="font-mono text-caption text-subtle-foreground">
177
- {formatElapsed(elapsedMs)}
178
- </p>
179
- )}
180
- </div>
181
- </div>
182
- {(transition.providerKey || transition.workerNode || transition.attempt) != null && (
183
- <p className="mt-1 font-mono text-caption text-subtle-foreground">
184
- {[
185
- transition.providerKey,
186
- transition.workerNode,
187
- transition.attempt != null
188
- ? `attempt ${transition.attempt}${transition.maxAttempts ? `/${transition.maxAttempts}` : ""}`
189
- : null,
190
- ]
191
- .filter(Boolean)
192
- .join(" · ")}
193
- </p>
194
- )}
195
- {transition.payload != null && transition.payload.length > 0 && (
196
- <div className="mt-2">
197
- <PayloadInspector exchanges={transition.payload} defaultOpen={-1} />
198
- </div>
199
- )}
200
- </div>
201
- </li>,
202
- );
203
-
204
- const annotation = annotations?.[transition.toState];
205
- if (annotation != null) {
206
- rows.push(<AnnotationNode key={`${transition.toState}-annotation`} text={annotation} />);
207
- }
208
- });
209
-
210
- // In-flight cap (design doc §5.3): the rail continues past the last node
211
- // as a dashed segment ending in the current-state glyph, so "still
212
- // moving" is readable without parsing. A terminal timeline instead ends
213
- // the last node's own rail segment (no trailing cap at all).
214
- if (!isTerminal) {
215
- rows.push(
216
- <li key="in-flight-cap" className="relative flex gap-3">
217
- <div className="flex w-4 shrink-0 justify-center">
218
- <span className="h-6 w-px border-edge-strong border-l border-dashed" aria-hidden="true" />
219
- </div>
220
- <div className="flex items-center gap-2 text-caption text-subtle-foreground">
221
- <StateMark meta={system[currentState]} size={12} className="text-muted-foreground" />
222
- <span>still moving</span>
223
- </div>
224
- </li>,
225
- );
226
- }
227
-
228
- return <ol className={cn("flex flex-col")}>{rows}</ol>;
229
- }
@@ -1,40 +0,0 @@
1
- import { cva, type VariantProps } from "class-variance-authority";
2
- import type { HTMLAttributes } from "react";
3
- import { cn } from "../../lib/cn";
4
-
5
- /**
6
- * Non-status tags only: app name, provider key, role, environment, sender
7
- * ID. Never use `Badge` for a message or job state — that's `StatusPill`'s
8
- * job (design doc §5.2); mixing them is how the status language erodes.
9
- *
10
- * D11: `cva()` replaces the previous `variant === "x" && "..."` conditional
11
- * chain, same classes. One deliberate D8 diff, not a byte-identical carry-
12
- * over: the previous base string hard-coded `rounded-sm`, which fought
13
- * daisyUI's own `.badge` rule (`border-radius: var(--radius-selector)`,
14
- * confirmed by reading `daisyui/components/badge.css` directly) at equal
15
- * specificity — a redundant override, not a deliberate choice, and for the
16
- * wrong tier besides: `--radius-selector` (8px) is this register's
17
- * small-control tier, `--radius-sm` now aliases `--radius-field` (12px).
18
- * Dropping the override lets daisyUI's own class govern radius, which is
19
- * exactly constraint 7 ("DaisyUI does the work") and the correct tier for
20
- * a tag-sized control.
21
- */
22
- export const badgeVariants = cva("badge font-mono text-caption", {
23
- variants: {
24
- variant: {
25
- neutral: "badge-neutral",
26
- outline: "badge-outline",
27
- },
28
- },
29
- defaultVariants: { variant: "neutral" },
30
- });
31
-
32
- export type BadgeVariant = NonNullable<VariantProps<typeof badgeVariants>["variant"]>;
33
-
34
- export interface BadgeProps
35
- extends HTMLAttributes<HTMLSpanElement>,
36
- VariantProps<typeof badgeVariants> {}
37
-
38
- export function Badge({ className, variant, ...props }: BadgeProps) {
39
- return <span className={cn(badgeVariants({ variant }), className)} {...props} />;
40
- }
@@ -1,54 +0,0 @@
1
- import { cva, type VariantProps } from "class-variance-authority";
2
- import type { ButtonHTMLAttributes } from "react";
3
- import { forwardRef } from "react";
4
- import { cn } from "../../lib/cn";
5
-
6
- // daisyUI does the heavy lifting (`btn-primary`, `btn-outline`, `btn-ghost`,
7
- // `btn-error`); the design doc's rule (§5.1) is that there is no
8
- // "success"/"warning" button variant — those hues are reserved for status,
9
- // so only these four exist. `btn-primary` is genuinely the achromatic
10
- // inverse fill (§1.3: near-black on light, near-white on dark) — `neutral`
11
- // is a *different*, deliberately quieter token (this theme's surface-3-ish
12
- // fill, used for badges) and using it here read as a washed-out primary
13
- // button in light theme; caught visually, not by inspection.
14
- //
15
- // D4/D11: rebuilt on `cva()` per docs/design/console-redesign.md §6.3 —
16
- // `Slot`/`asChild` are gone (nothing in this repo used `<Button asChild>`,
17
- // confirmed by grep before removing it), and `buttonVariants` is exported
18
- // standalone so a link that must look like a button reaches for the class
19
- // string directly: `<a className={buttonVariants({ variant: "secondary" })}>`.
20
- // `rounded-field` in the base string is the one deliberate D8 addition —
21
- // daisyUI's own `.btn` class already applies `--radius-field` per corner
22
- // internally, so this doesn't change the rendered radius, only makes the
23
- // D8 register explicit at the call site, matching §6.3's own sketch
24
- // verbatim.
25
- export const buttonVariants = cva("btn font-sans font-semibold rounded-field", {
26
- variants: {
27
- variant: {
28
- primary: "btn-primary",
29
- secondary: "btn-outline",
30
- ghost: "btn-ghost",
31
- destructive: "btn-error",
32
- },
33
- size: {
34
- sm: "btn-sm",
35
- md: "",
36
- icon: "btn-square btn-sm",
37
- },
38
- },
39
- defaultVariants: { variant: "primary", size: "md" },
40
- });
41
-
42
- export type ButtonVariant = NonNullable<VariantProps<typeof buttonVariants>["variant"]>;
43
- export type ButtonSize = NonNullable<VariantProps<typeof buttonVariants>["size"]>;
44
-
45
- export interface ButtonProps
46
- extends ButtonHTMLAttributes<HTMLButtonElement>,
47
- VariantProps<typeof buttonVariants> {}
48
-
49
- export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
50
- ({ className, variant, size, ...props }, ref) => (
51
- <button ref={ref} className={cn(buttonVariants({ variant, size }), className)} {...props} />
52
- ),
53
- );
54
- Button.displayName = "Button";