@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.
- package/CHANGELOG.md +25 -0
- package/dist/components/data/code.d.ts +0 -1
- package/dist/components/data/code.js +0 -1
- package/dist/components/data/copy-button.d.ts +0 -1
- package/dist/components/data/copy-button.js +0 -1
- package/dist/components/data/detail-row.d.ts +0 -1
- package/dist/components/data/detail-row.js +0 -1
- package/dist/components/data/id-display.d.ts +0 -1
- package/dist/components/data/id-display.js +0 -1
- package/dist/components/data/masked-value.d.ts +0 -1
- package/dist/components/data/masked-value.js +0 -1
- package/dist/components/data/money.d.ts +0 -1
- package/dist/components/data/money.js +0 -1
- package/dist/components/data/phone-display.d.ts +0 -1
- package/dist/components/data/phone-display.js +0 -1
- package/dist/components/data/stat-tile.d.ts +0 -1
- package/dist/components/data/stat-tile.js +0 -1
- package/dist/components/data/timestamp-display.d.ts +0 -1
- package/dist/components/data/timestamp-display.js +0 -1
- package/dist/components/patterns/inline-banner.d.ts +0 -1
- package/dist/components/patterns/inline-banner.js +0 -1
- package/dist/components/patterns/inline-empty-state.d.ts +0 -1
- package/dist/components/patterns/inline-empty-state.js +0 -1
- package/dist/components/patterns/live-row.d.ts +0 -1
- package/dist/components/patterns/live-row.js +0 -1
- package/dist/components/patterns/payload-inspector.d.ts +0 -1
- package/dist/components/patterns/payload-inspector.js +0 -1
- package/dist/components/patterns/route-skeleton.d.ts +0 -1
- package/dist/components/patterns/route-skeleton.js +0 -1
- package/dist/components/patterns/screen-layout.d.ts +0 -1
- package/dist/components/patterns/screen-layout.js +0 -1
- package/dist/components/patterns/stale-write-banner.d.ts +0 -1
- package/dist/components/patterns/stale-write-banner.js +0 -1
- package/dist/components/patterns/state-timeline.d.ts +0 -1
- package/dist/components/patterns/state-timeline.js +0 -1
- package/dist/components/primitives/badge.d.ts +0 -1
- package/dist/components/primitives/badge.js +0 -1
- package/dist/components/primitives/button.d.ts +0 -1
- package/dist/components/primitives/button.js +0 -1
- package/dist/components/primitives/calendar.d.ts +0 -1
- package/dist/components/primitives/calendar.js +0 -1
- package/dist/components/primitives/card.d.ts +0 -1
- package/dist/components/primitives/card.js +0 -1
- package/dist/components/primitives/checkbox.d.ts +0 -1
- package/dist/components/primitives/checkbox.js +0 -1
- package/dist/components/primitives/chip-select.d.ts +0 -1
- package/dist/components/primitives/chip-select.js +0 -1
- package/dist/components/primitives/command-menu.d.ts +0 -1
- package/dist/components/primitives/command-menu.js +0 -1
- package/dist/components/primitives/confirm-dialog.d.ts +0 -1
- package/dist/components/primitives/confirm-dialog.js +0 -1
- package/dist/components/primitives/date-picker.d.ts +0 -1
- package/dist/components/primitives/date-picker.js +0 -1
- package/dist/components/primitives/dialog.d.ts +0 -1
- package/dist/components/primitives/dialog.js +0 -1
- package/dist/components/primitives/drawer.d.ts +0 -1
- package/dist/components/primitives/drawer.js +0 -1
- package/dist/components/primitives/dropdown-menu.d.ts +0 -1
- package/dist/components/primitives/dropdown-menu.js +0 -1
- package/dist/components/primitives/form-field.d.ts +0 -1
- package/dist/components/primitives/form-field.js +0 -1
- package/dist/components/primitives/inline-confirm.d.ts +0 -1
- package/dist/components/primitives/inline-confirm.js +0 -1
- package/dist/components/primitives/input.d.ts +0 -1
- package/dist/components/primitives/input.js +0 -1
- package/dist/components/primitives/label.d.ts +0 -1
- package/dist/components/primitives/label.js +0 -1
- package/dist/components/primitives/pagination.d.ts +0 -1
- package/dist/components/primitives/pagination.js +0 -1
- package/dist/components/primitives/popover.d.ts +0 -1
- package/dist/components/primitives/popover.js +0 -1
- package/dist/components/primitives/progress.d.ts +0 -1
- package/dist/components/primitives/progress.js +0 -1
- package/dist/components/primitives/radio-group.d.ts +0 -1
- package/dist/components/primitives/radio-group.js +0 -1
- package/dist/components/primitives/select.d.ts +0 -1
- package/dist/components/primitives/select.js +0 -1
- package/dist/components/primitives/separator.d.ts +0 -1
- package/dist/components/primitives/separator.js +0 -1
- package/dist/components/primitives/side-nav.d.ts +0 -1
- package/dist/components/primitives/side-nav.js +0 -1
- package/dist/components/primitives/skeleton.d.ts +0 -1
- package/dist/components/primitives/skeleton.js +0 -1
- package/dist/components/primitives/spinner.d.ts +0 -1
- package/dist/components/primitives/spinner.js +0 -1
- package/dist/components/primitives/switch.d.ts +0 -1
- package/dist/components/primitives/switch.js +0 -1
- package/dist/components/primitives/table.d.ts +0 -1
- package/dist/components/primitives/table.js +0 -1
- package/dist/components/primitives/tabs.d.ts +0 -1
- package/dist/components/primitives/tabs.js +0 -1
- package/dist/components/primitives/textarea.d.ts +0 -1
- package/dist/components/primitives/textarea.js +0 -1
- package/dist/components/primitives/toast.d.ts +0 -1
- package/dist/components/primitives/toast.js +0 -1
- package/dist/components/primitives/tooltip.d.ts +0 -1
- package/dist/components/primitives/tooltip.js +0 -1
- package/dist/components/status/state-chip.d.ts +0 -1
- package/dist/components/status/state-chip.js +0 -1
- package/dist/components/status/state-mark.d.ts +0 -1
- package/dist/components/status/state-mark.js +0 -1
- package/dist/components/status/status-pill.d.ts +0 -1
- package/dist/components/status/status-pill.js +0 -1
- package/dist/components/status/status-tokens.d.ts +0 -1
- package/dist/components/status/status-tokens.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/lib/cn.d.ts +0 -1
- package/dist/lib/cn.js +0 -1
- package/dist/lib/money.d.ts +0 -1
- package/dist/lib/money.js +0 -1
- package/dist/lib/omit-undefined.d.ts +0 -1
- package/dist/lib/omit-undefined.js +0 -1
- package/package.json +3 -7
- package/dist/components/data/code.d.ts.map +0 -1
- package/dist/components/data/code.js.map +0 -1
- package/dist/components/data/copy-button.d.ts.map +0 -1
- package/dist/components/data/copy-button.js.map +0 -1
- package/dist/components/data/detail-row.d.ts.map +0 -1
- package/dist/components/data/detail-row.js.map +0 -1
- package/dist/components/data/id-display.d.ts.map +0 -1
- package/dist/components/data/id-display.js.map +0 -1
- package/dist/components/data/masked-value.d.ts.map +0 -1
- package/dist/components/data/masked-value.js.map +0 -1
- package/dist/components/data/money.d.ts.map +0 -1
- package/dist/components/data/money.js.map +0 -1
- package/dist/components/data/phone-display.d.ts.map +0 -1
- package/dist/components/data/phone-display.js.map +0 -1
- package/dist/components/data/stat-tile.d.ts.map +0 -1
- package/dist/components/data/stat-tile.js.map +0 -1
- package/dist/components/data/timestamp-display.d.ts.map +0 -1
- package/dist/components/data/timestamp-display.js.map +0 -1
- package/dist/components/patterns/inline-banner.d.ts.map +0 -1
- package/dist/components/patterns/inline-banner.js.map +0 -1
- package/dist/components/patterns/inline-empty-state.d.ts.map +0 -1
- package/dist/components/patterns/inline-empty-state.js.map +0 -1
- package/dist/components/patterns/live-row.d.ts.map +0 -1
- package/dist/components/patterns/live-row.js.map +0 -1
- package/dist/components/patterns/payload-inspector.d.ts.map +0 -1
- package/dist/components/patterns/payload-inspector.js.map +0 -1
- package/dist/components/patterns/route-skeleton.d.ts.map +0 -1
- package/dist/components/patterns/route-skeleton.js.map +0 -1
- package/dist/components/patterns/screen-layout.d.ts.map +0 -1
- package/dist/components/patterns/screen-layout.js.map +0 -1
- package/dist/components/patterns/stale-write-banner.d.ts.map +0 -1
- package/dist/components/patterns/stale-write-banner.js.map +0 -1
- package/dist/components/patterns/state-timeline.d.ts.map +0 -1
- package/dist/components/patterns/state-timeline.js.map +0 -1
- package/dist/components/primitives/badge.d.ts.map +0 -1
- package/dist/components/primitives/badge.js.map +0 -1
- package/dist/components/primitives/button.d.ts.map +0 -1
- package/dist/components/primitives/button.js.map +0 -1
- package/dist/components/primitives/calendar.d.ts.map +0 -1
- package/dist/components/primitives/calendar.js.map +0 -1
- package/dist/components/primitives/card.d.ts.map +0 -1
- package/dist/components/primitives/card.js.map +0 -1
- package/dist/components/primitives/checkbox.d.ts.map +0 -1
- package/dist/components/primitives/checkbox.js.map +0 -1
- package/dist/components/primitives/chip-select.d.ts.map +0 -1
- package/dist/components/primitives/chip-select.js.map +0 -1
- package/dist/components/primitives/command-menu.d.ts.map +0 -1
- package/dist/components/primitives/command-menu.js.map +0 -1
- package/dist/components/primitives/confirm-dialog.d.ts.map +0 -1
- package/dist/components/primitives/confirm-dialog.js.map +0 -1
- package/dist/components/primitives/date-picker.d.ts.map +0 -1
- package/dist/components/primitives/date-picker.js.map +0 -1
- package/dist/components/primitives/dialog.d.ts.map +0 -1
- package/dist/components/primitives/dialog.js.map +0 -1
- package/dist/components/primitives/drawer.d.ts.map +0 -1
- package/dist/components/primitives/drawer.js.map +0 -1
- package/dist/components/primitives/dropdown-menu.d.ts.map +0 -1
- package/dist/components/primitives/dropdown-menu.js.map +0 -1
- package/dist/components/primitives/form-field.d.ts.map +0 -1
- package/dist/components/primitives/form-field.js.map +0 -1
- package/dist/components/primitives/inline-confirm.d.ts.map +0 -1
- package/dist/components/primitives/inline-confirm.js.map +0 -1
- package/dist/components/primitives/input.d.ts.map +0 -1
- package/dist/components/primitives/input.js.map +0 -1
- package/dist/components/primitives/label.d.ts.map +0 -1
- package/dist/components/primitives/label.js.map +0 -1
- package/dist/components/primitives/pagination.d.ts.map +0 -1
- package/dist/components/primitives/pagination.js.map +0 -1
- package/dist/components/primitives/popover.d.ts.map +0 -1
- package/dist/components/primitives/popover.js.map +0 -1
- package/dist/components/primitives/progress.d.ts.map +0 -1
- package/dist/components/primitives/progress.js.map +0 -1
- package/dist/components/primitives/radio-group.d.ts.map +0 -1
- package/dist/components/primitives/radio-group.js.map +0 -1
- package/dist/components/primitives/select.d.ts.map +0 -1
- package/dist/components/primitives/select.js.map +0 -1
- package/dist/components/primitives/separator.d.ts.map +0 -1
- package/dist/components/primitives/separator.js.map +0 -1
- package/dist/components/primitives/side-nav.d.ts.map +0 -1
- package/dist/components/primitives/side-nav.js.map +0 -1
- package/dist/components/primitives/skeleton.d.ts.map +0 -1
- package/dist/components/primitives/skeleton.js.map +0 -1
- package/dist/components/primitives/spinner.d.ts.map +0 -1
- package/dist/components/primitives/spinner.js.map +0 -1
- package/dist/components/primitives/switch.d.ts.map +0 -1
- package/dist/components/primitives/switch.js.map +0 -1
- package/dist/components/primitives/table.d.ts.map +0 -1
- package/dist/components/primitives/table.js.map +0 -1
- package/dist/components/primitives/tabs.d.ts.map +0 -1
- package/dist/components/primitives/tabs.js.map +0 -1
- package/dist/components/primitives/textarea.d.ts.map +0 -1
- package/dist/components/primitives/textarea.js.map +0 -1
- package/dist/components/primitives/toast.d.ts.map +0 -1
- package/dist/components/primitives/toast.js.map +0 -1
- package/dist/components/primitives/tooltip.d.ts.map +0 -1
- package/dist/components/primitives/tooltip.js.map +0 -1
- package/dist/components/status/state-chip.d.ts.map +0 -1
- package/dist/components/status/state-chip.js.map +0 -1
- package/dist/components/status/state-mark.d.ts.map +0 -1
- package/dist/components/status/state-mark.js.map +0 -1
- package/dist/components/status/status-pill.d.ts.map +0 -1
- package/dist/components/status/status-pill.js.map +0 -1
- package/dist/components/status/status-tokens.d.ts.map +0 -1
- package/dist/components/status/status-tokens.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/cn.d.ts.map +0 -1
- package/dist/lib/cn.js.map +0 -1
- package/dist/lib/money.d.ts.map +0 -1
- package/dist/lib/money.js.map +0 -1
- package/dist/lib/omit-undefined.d.ts.map +0 -1
- package/dist/lib/omit-undefined.js.map +0 -1
- package/src/components/data/code.tsx +0 -43
- package/src/components/data/copy-button.tsx +0 -97
- package/src/components/data/detail-row.tsx +0 -113
- package/src/components/data/id-display.tsx +0 -49
- package/src/components/data/masked-value.tsx +0 -96
- package/src/components/data/money.tsx +0 -46
- package/src/components/data/phone-display.tsx +0 -60
- package/src/components/data/stat-tile.tsx +0 -68
- package/src/components/data/timestamp-display.tsx +0 -106
- package/src/components/patterns/inline-banner.tsx +0 -60
- package/src/components/patterns/inline-empty-state.tsx +0 -45
- package/src/components/patterns/live-row.tsx +0 -75
- package/src/components/patterns/payload-inspector.tsx +0 -157
- package/src/components/patterns/route-skeleton.tsx +0 -77
- package/src/components/patterns/screen-layout.tsx +0 -44
- package/src/components/patterns/stale-write-banner.tsx +0 -44
- package/src/components/patterns/state-timeline.tsx +0 -229
- package/src/components/primitives/badge.tsx +0 -40
- package/src/components/primitives/button.tsx +0 -54
- package/src/components/primitives/calendar.tsx +0 -136
- package/src/components/primitives/card.tsx +0 -70
- package/src/components/primitives/checkbox.tsx +0 -101
- package/src/components/primitives/chip-select.tsx +0 -105
- package/src/components/primitives/command-menu.tsx +0 -92
- package/src/components/primitives/confirm-dialog.tsx +0 -109
- package/src/components/primitives/date-picker.tsx +0 -271
- package/src/components/primitives/dialog.tsx +0 -195
- package/src/components/primitives/drawer.tsx +0 -326
- package/src/components/primitives/dropdown-menu.tsx +0 -128
- package/src/components/primitives/form-field.tsx +0 -136
- package/src/components/primitives/inline-confirm.tsx +0 -118
- package/src/components/primitives/input.tsx +0 -21
- package/src/components/primitives/label.tsx +0 -43
- package/src/components/primitives/pagination.tsx +0 -110
- package/src/components/primitives/popover.tsx +0 -41
- package/src/components/primitives/progress.tsx +0 -66
- package/src/components/primitives/radio-group.tsx +0 -90
- package/src/components/primitives/select.tsx +0 -254
- package/src/components/primitives/separator.tsx +0 -49
- package/src/components/primitives/side-nav.tsx +0 -253
- package/src/components/primitives/skeleton.tsx +0 -25
- package/src/components/primitives/spinner.tsx +0 -39
- package/src/components/primitives/switch.tsx +0 -74
- package/src/components/primitives/table.tsx +0 -157
- package/src/components/primitives/tabs.tsx +0 -163
- package/src/components/primitives/textarea.tsx +0 -21
- package/src/components/primitives/toast.tsx +0 -117
- package/src/components/primitives/tooltip.tsx +0 -38
- package/src/components/status/state-chip.tsx +0 -70
- package/src/components/status/state-mark.tsx +0 -154
- package/src/components/status/status-pill.tsx +0 -153
- package/src/components/status/status-tokens.ts +0 -173
- package/src/index.ts +0 -96
- package/src/lib/cn.ts +0 -168
- package/src/lib/money.ts +0 -164
- package/src/lib/omit-undefined.ts +0 -25
- /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";
|