@sentry/junior-dashboard 0.265.0 → 0.267.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.
- package/dist/app.js +49 -12
- package/dist/client.js +94 -63
- package/dist/index.js +49 -12
- package/dist/tailwind.css +1 -1
- package/package.json +11 -5
- package/src/app.ts +0 -2
- package/src/auth.ts +4 -7
- package/src/client/App.tsx +35 -53
- package/src/client/components/ActorAvatar.tsx +10 -22
- package/src/client/components/ConnectionBanner.tsx +4 -3
- package/src/client/components/CopyButton.tsx +3 -4
- package/src/client/components/DetailsPanel.tsx +12 -13
- package/src/client/components/Drawer.tsx +46 -23
- package/src/client/components/EmptyTelemetry.tsx +5 -3
- package/src/client/components/ImageAttachment.tsx +49 -63
- package/src/client/components/InlineError.tsx +8 -4
- package/src/client/components/Notice.tsx +19 -26
- package/src/client/components/Pagination.tsx +26 -26
- package/src/client/components/ParticipantAvatarStack.tsx +13 -10
- package/src/client/components/README.md +28 -3
- package/src/client/components/SearchInput.tsx +51 -41
- package/src/client/components/VersionDriftBanner.tsx +9 -7
- package/src/client/components/charts/CacheWrites.tsx +52 -43
- package/src/client/components/ui/alert.tsx +64 -0
- package/src/client/components/ui/avatar.tsx +53 -0
- package/src/client/components/ui/button-group.tsx +59 -0
- package/src/client/components/ui/button.tsx +2 -1
- package/src/client/components/ui/dialog.tsx +60 -0
- package/src/client/components/ui/drawer.tsx +140 -0
- package/src/client/components/ui/empty.tsx +61 -0
- package/src/client/components/ui/input-group.tsx +96 -0
- package/src/client/components/ui/pagination.tsx +60 -0
- package/src/client/components/ui/separator.tsx +22 -0
- package/src/client/components/ui/spinner.tsx +21 -0
- package/src/client/components/ui/table.tsx +22 -1
- package/src/client/components/ui/tooltip.tsx +1 -1
- package/src/client/components/ui/useSheet.ts +5 -1
- package/src/client/conversations/ComposerImages.tsx +66 -14
- package/src/client/conversations/ConversationChildAgents.tsx +5 -5
- package/src/client/conversations/ConversationComposer.tsx +84 -12
- package/src/client/conversations/ConversationDetails.tsx +3 -27
- package/src/client/conversations/ConversationEventDetails.tsx +26 -10
- package/src/client/conversations/ConversationHeader.tsx +5 -4
- package/src/client/conversations/ConversationHeaderActions.tsx +7 -11
- package/src/client/conversations/ConversationPage.tsx +156 -2
- package/src/client/conversations/ConversationSidebar.tsx +5 -4
- package/src/client/conversations/ConversationTranscript.tsx +48 -16
- package/src/client/conversations/ConversationUsageBreakdown.tsx +108 -65
- package/src/client/conversations/ConversationWatches.tsx +67 -59
- package/src/client/conversations/DetailsList.tsx +77 -29
- package/src/client/conversations/EventDetails.tsx +46 -19
- package/src/client/conversations/EventUsage.tsx +99 -74
- package/src/client/conversations/ForkConversationButton.tsx +79 -73
- package/src/client/conversations/PendingMailboxStack.tsx +312 -119
- package/src/client/conversations/README.md +30 -10
- package/src/client/conversations/ShareConversationButton.tsx +3 -4
- package/src/client/conversations/SubagentTranscriptDrawer.tsx +19 -22
- package/src/client/conversations/TranscriptActivityGroup.tsx +22 -19
- package/src/client/conversations/TranscriptAttachment.tsx +39 -4
- package/src/client/conversations/TranscriptAutomationTriggerView.tsx +14 -19
- package/src/client/conversations/TranscriptEventView.tsx +35 -16
- package/src/client/conversations/TranscriptFailureView.tsx +10 -13
- package/src/client/conversations/TranscriptMarkdown.tsx +232 -436
- package/src/client/conversations/TranscriptMessageContextView.tsx +31 -20
- package/src/client/conversations/TranscriptMessageView.tsx +7 -1
- package/src/client/conversations/TranscriptStructuredEventView.tsx +25 -15
- package/src/client/conversations/TranscriptSummary.tsx +13 -7
- package/src/client/conversations/TranscriptTurnContextView.tsx +78 -56
- package/src/client/conversations/TranscriptView.tsx +7 -3
- package/src/client/conversations/UserInputRequestCard.tsx +244 -0
- package/src/client/conversations/conversationOutbox.ts +46 -0
- package/src/client/conversations/eventLog.ts +1 -0
- package/src/client/conversations/eventTime.ts +13 -1
- package/src/client/conversations/eventTranscript.ts +12 -5
- package/src/client/conversations/queries.ts +127 -26
- package/src/client/conversations/transcriptMarkdownLinks.ts +5 -367
- package/src/client/conversations/transcriptMentions.tsx +18 -5
- package/src/client/conversations/userInputRequest.ts +56 -0
- package/src/client/format.ts +10 -3
- package/src/client/pages/automations/{AutomationLifecycle.tsx → AutomationActions.tsx} +60 -69
- package/src/client/pages/automations/AutomationDraftChanges.tsx +171 -0
- package/src/client/pages/automations/AutomationEditor.tsx +182 -156
- package/src/client/pages/automations/AutomationEventFields.tsx +119 -45
- package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +1 -1
- package/src/client/pages/automations/AutomationFormSection.tsx +20 -2
- package/src/client/pages/automations/AutomationOutcomeFields.tsx +1 -1
- package/src/client/pages/automations/AutomationOverview.tsx +238 -0
- package/src/client/pages/automations/AutomationPage.tsx +343 -0
- package/src/client/pages/automations/AutomationRecentRuns.tsx +3 -2
- package/src/client/pages/automations/AutomationRow.tsx +11 -13
- package/src/client/pages/automations/AutomationRunDetails.tsx +4 -2
- package/src/client/pages/automations/{AutomationExecutionsPage.tsx → AutomationRunsTab.tsx} +94 -103
- package/src/client/pages/automations/AutomationStatus.tsx +164 -108
- package/src/client/pages/automations/AutomationVersionsTab.tsx +518 -0
- package/src/client/pages/automations/AutomationsPage.tsx +10 -98
- package/src/client/pages/automations/AutomationsPageLayout.tsx +5 -23
- package/src/client/pages/automations/README.md +56 -46
- package/src/client/types.ts +3 -0
- package/src/mock-reporting/fixtures.ts +44 -2
- package/src/mock-reporting/watches.ts +1 -1
- package/src/client/conversations/transcriptStyles.ts +0 -10
- package/src/client/pages/automations/AutomationDetails.tsx +0 -193
- package/src/client/pages/automations/AutomationEditPage.tsx +0 -176
- package/src/client/pages/automations/AutomationVersionsPage.tsx +0 -390
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
|
|
3
3
|
import { cn } from "../styles";
|
|
4
|
+
import { Alert } from "./ui/alert";
|
|
4
5
|
|
|
5
6
|
/** Render a compact error message with consistent alert semantics. */
|
|
6
7
|
export function InlineError(props: {
|
|
@@ -8,11 +9,14 @@ export function InlineError(props: {
|
|
|
8
9
|
className?: string;
|
|
9
10
|
}) {
|
|
10
11
|
return (
|
|
11
|
-
<
|
|
12
|
-
className={cn(
|
|
13
|
-
|
|
12
|
+
<Alert
|
|
13
|
+
className={cn(
|
|
14
|
+
"block rounded-none border-0 bg-transparent p-0 text-rose-300",
|
|
15
|
+
props.className,
|
|
16
|
+
)}
|
|
17
|
+
variant="destructive"
|
|
14
18
|
>
|
|
15
19
|
{props.children}
|
|
16
|
-
</
|
|
20
|
+
</Alert>
|
|
17
21
|
);
|
|
18
22
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
2
2
|
import type { LucideIcon } from "lucide-react";
|
|
3
3
|
|
|
4
4
|
import { cn } from "../styles";
|
|
5
|
+
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
|
|
6
|
+
import { Button } from "./ui/button";
|
|
5
7
|
|
|
6
8
|
type NoticeTone = "default" | "error";
|
|
7
9
|
|
|
@@ -14,7 +16,7 @@ type NoticeProps = {
|
|
|
14
16
|
tone?: NoticeTone;
|
|
15
17
|
};
|
|
16
18
|
|
|
17
|
-
type NoticeActionProps =
|
|
19
|
+
type NoticeActionProps = ComponentProps<typeof Button> & {
|
|
18
20
|
tone?: NoticeTone;
|
|
19
21
|
};
|
|
20
22
|
|
|
@@ -29,11 +31,10 @@ export function Notice({
|
|
|
29
31
|
}: NoticeProps) {
|
|
30
32
|
const isError = tone === "error";
|
|
31
33
|
return (
|
|
32
|
-
<
|
|
33
|
-
className=
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
)}
|
|
34
|
+
<Alert
|
|
35
|
+
className="block gap-0 overflow-hidden rounded-xl p-0 shadow-[0_16px_40px_rgba(0,0,0,0.5)]"
|
|
36
|
+
role={isError ? "alert" : "status"}
|
|
37
|
+
variant={isError ? "destructive" : "default"}
|
|
37
38
|
>
|
|
38
39
|
<div className="flex min-w-0 items-center gap-3 px-3 py-3">
|
|
39
40
|
{Icon ? (
|
|
@@ -48,31 +49,26 @@ export function Notice({
|
|
|
48
49
|
<Icon aria-hidden="true" size={16} />
|
|
49
50
|
</div>
|
|
50
51
|
) : null}
|
|
51
|
-
<div className="min-w-0 flex-1"
|
|
52
|
-
<
|
|
53
|
-
className={cn(
|
|
54
|
-
"font-display text-sm font-medium leading-snug",
|
|
55
|
-
isError ? "text-rose-50" : "text-dashboard-text",
|
|
56
|
-
)}
|
|
57
|
-
>
|
|
52
|
+
<div className="min-w-0 flex-1">
|
|
53
|
+
<AlertTitle className="font-display text-sm leading-snug">
|
|
58
54
|
{title}
|
|
59
55
|
{detail ? " " : null}
|
|
60
|
-
</
|
|
56
|
+
</AlertTitle>
|
|
61
57
|
{detail ? (
|
|
62
|
-
<
|
|
58
|
+
<AlertDescription
|
|
63
59
|
className={cn(
|
|
64
60
|
"mt-0.5 truncate font-sans text-xs",
|
|
65
|
-
isError
|
|
61
|
+
isError && "text-rose-100/70",
|
|
66
62
|
)}
|
|
67
63
|
>
|
|
68
64
|
{detail}
|
|
69
|
-
</
|
|
65
|
+
</AlertDescription>
|
|
70
66
|
) : null}
|
|
71
67
|
</div>
|
|
72
68
|
{action}
|
|
73
69
|
</div>
|
|
74
70
|
{children}
|
|
75
|
-
</
|
|
71
|
+
</Alert>
|
|
76
72
|
);
|
|
77
73
|
}
|
|
78
74
|
|
|
@@ -80,22 +76,19 @@ export function Notice({
|
|
|
80
76
|
export function NoticeAction({
|
|
81
77
|
className,
|
|
82
78
|
tone = "default",
|
|
83
|
-
type = "button",
|
|
84
79
|
...props
|
|
85
80
|
}: NoticeActionProps) {
|
|
86
81
|
const isError = tone === "error";
|
|
87
82
|
return (
|
|
88
|
-
<
|
|
83
|
+
<Button
|
|
89
84
|
{...props}
|
|
90
85
|
className={cn(
|
|
91
|
-
"shrink-0
|
|
92
|
-
isError ? "focus:ring-rose-300/40" : "focus:ring-dashboard-focus/35",
|
|
86
|
+
"h-auto shrink-0 rounded-lg border-0 px-3 py-1.5 text-xs leading-normal",
|
|
93
87
|
isError
|
|
94
|
-
? "
|
|
95
|
-
: "
|
|
88
|
+
? "bg-rose-200/15 text-rose-50 hover:bg-rose-200/25 hover:text-rose-50 focus-visible:outline-rose-300/55 data-disabled:hover:bg-rose-200/15 data-disabled:hover:text-rose-50"
|
|
89
|
+
: "bg-dashboard-accent/10 text-dashboard-accent-text hover:bg-dashboard-accent/20 hover:text-dashboard-accent-text data-disabled:hover:bg-dashboard-accent/10 data-disabled:hover:text-dashboard-accent-text",
|
|
96
90
|
className,
|
|
97
91
|
)}
|
|
98
|
-
type={type}
|
|
99
92
|
/>
|
|
100
93
|
);
|
|
101
94
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
2
|
-
|
|
3
1
|
import { cn } from "../styles";
|
|
4
2
|
import { Button } from "./ui/button";
|
|
3
|
+
import {
|
|
4
|
+
Pagination,
|
|
5
|
+
PaginationContent,
|
|
6
|
+
PaginationItem,
|
|
7
|
+
PaginationNext,
|
|
8
|
+
PaginationPrevious,
|
|
9
|
+
} from "./ui/pagination";
|
|
5
10
|
|
|
6
11
|
/** Render a centered load-more control for cursor or progressive lists. */
|
|
7
12
|
export function LoadMorePagination(props: {
|
|
@@ -54,10 +59,9 @@ export function PagePagination(props: {
|
|
|
54
59
|
const end = Math.min(props.total, current * props.pageSize);
|
|
55
60
|
|
|
56
61
|
return (
|
|
57
|
-
<
|
|
58
|
-
aria-label="Pagination"
|
|
62
|
+
<Pagination
|
|
59
63
|
className={cn(
|
|
60
|
-
"flex
|
|
64
|
+
"flex-wrap items-center justify-between gap-3",
|
|
61
65
|
props.className,
|
|
62
66
|
)}
|
|
63
67
|
>
|
|
@@ -65,27 +69,23 @@ export function PagePagination(props: {
|
|
|
65
69
|
Showing {start.toLocaleString("en-US")}-{end.toLocaleString("en-US")} of{" "}
|
|
66
70
|
{props.total.toLocaleString("en-US")}
|
|
67
71
|
</p>
|
|
68
|
-
<
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
>
|
|
75
|
-
|
|
76
|
-
</Button>
|
|
77
|
-
<span className="min-w-16 text-center font-sans tabular-nums text-xs text-dashboard-text-muted">
|
|
72
|
+
<PaginationContent>
|
|
73
|
+
<PaginationItem>
|
|
74
|
+
<PaginationPrevious
|
|
75
|
+
disabled={current <= 1}
|
|
76
|
+
onClick={() => props.onPageChange(current - 1)}
|
|
77
|
+
/>
|
|
78
|
+
</PaginationItem>
|
|
79
|
+
<PaginationItem className="min-w-16 text-center font-sans tabular-nums text-xs text-dashboard-text-muted">
|
|
78
80
|
{current} / {props.pageCount}
|
|
79
|
-
</
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
81
|
+
</PaginationItem>
|
|
82
|
+
<PaginationItem>
|
|
83
|
+
<PaginationNext
|
|
84
|
+
disabled={current >= props.pageCount}
|
|
85
|
+
onClick={() => props.onPageChange(current + 1)}
|
|
86
|
+
/>
|
|
87
|
+
</PaginationItem>
|
|
88
|
+
</PaginationContent>
|
|
89
|
+
</Pagination>
|
|
90
90
|
);
|
|
91
91
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ActorIdentity } from "@sentry/junior/api/schema";
|
|
2
2
|
|
|
3
3
|
import { actorLabel } from "../format";
|
|
4
|
-
import { cn } from "../styles";
|
|
5
4
|
import type { Conversation } from "../types";
|
|
6
5
|
import { ActorAvatar } from "./ActorAvatar";
|
|
6
|
+
import { Avatar, AvatarFallback } from "./ui/avatar";
|
|
7
7
|
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
|
8
8
|
|
|
9
9
|
const MAX_VISIBLE_PARTICIPANTS = 3;
|
|
@@ -51,7 +51,8 @@ function ParticipantTooltipContent(props: { participant: ActorIdentity }) {
|
|
|
51
51
|
);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
/** Show one actor avatar with their name and email on hover or focus. */
|
|
55
|
+
export function ParticipantAvatar(props: {
|
|
55
56
|
participant: ActorIdentity;
|
|
56
57
|
size: ParticipantAvatarStackProps["size"];
|
|
57
58
|
}) {
|
|
@@ -62,6 +63,8 @@ function Avatar(props: {
|
|
|
62
63
|
render={
|
|
63
64
|
<span
|
|
64
65
|
aria-label={label}
|
|
66
|
+
// A name on a generic span is often skipped by screen readers.
|
|
67
|
+
role="img"
|
|
65
68
|
className="relative -ml-1 inline-flex rounded-full first:ml-0 pointer-events-none focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus [@media(hover:hover)_and_(pointer:fine)]:pointer-events-auto"
|
|
66
69
|
onKeyDown={(event) => event.stopPropagation()}
|
|
67
70
|
tabIndex={0}
|
|
@@ -101,7 +104,7 @@ export function ParticipantAvatarStack(props: ParticipantAvatarStackProps) {
|
|
|
101
104
|
role="group"
|
|
102
105
|
>
|
|
103
106
|
{visible.map((participant, index) => (
|
|
104
|
-
<
|
|
107
|
+
<ParticipantAvatar
|
|
105
108
|
key={participantKey(participant, index)}
|
|
106
109
|
participant={participant}
|
|
107
110
|
size={props.size}
|
|
@@ -111,19 +114,19 @@ export function ParticipantAvatarStack(props: ParticipantAvatarStackProps) {
|
|
|
111
114
|
<Tooltip>
|
|
112
115
|
<TooltipTrigger
|
|
113
116
|
render={
|
|
114
|
-
<
|
|
117
|
+
<Avatar
|
|
115
118
|
aria-label={`${hidden.length} more participants: ${hidden
|
|
116
119
|
.map(participantDescription)
|
|
117
120
|
.join("; ")}`}
|
|
118
|
-
className=
|
|
119
|
-
"relative -ml-1 inline-grid shrink-0 pointer-events-none [@media(hover:hover)_and_(pointer:fine)]:pointer-events-auto place-items-center rounded-full border-2 border-dashboard-bg bg-dashboard-surface-active font-sans font-semibold tabular-nums leading-none text-dashboard-text-muted focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus",
|
|
120
|
-
props.size === "list" ? "size-6 text-2xs" : "size-7 text-xs",
|
|
121
|
-
)}
|
|
121
|
+
className="-ml-1 pointer-events-none bg-dashboard-surface-active shadow-none focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus [@media(hover:hover)_and_(pointer:fine)]:pointer-events-auto"
|
|
122
122
|
onKeyDown={(event) => event.stopPropagation()}
|
|
123
|
+
size={props.size === "list" ? "sm" : "default"}
|
|
123
124
|
tabIndex={0}
|
|
124
125
|
>
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
<AvatarFallback className="tabular-nums tracking-normal text-dashboard-text-muted">
|
|
127
|
+
+{hidden.length}
|
|
128
|
+
</AvatarFallback>
|
|
129
|
+
</Avatar>
|
|
127
130
|
}
|
|
128
131
|
/>
|
|
129
132
|
<TooltipContent>
|
|
@@ -32,9 +32,15 @@ file when every caller needs the same style or variant. App components compose
|
|
|
32
32
|
|
|
33
33
|
| App component | shadcn source |
|
|
34
34
|
| ------------------------------ | ----------------- |
|
|
35
|
-
| `Drawer
|
|
35
|
+
| `Drawer` | `ui/drawer` |
|
|
36
|
+
| `BottomSheet` | `ui/sheet` |
|
|
36
37
|
| `layout/DashboardHeader` (nav) | `ui/sheet` |
|
|
37
38
|
| `ProfileMenu` (desktop) | `ui/popover` |
|
|
39
|
+
| `ActorAvatar` | `ui/avatar` |
|
|
40
|
+
| `ParticipantAvatarStack` | `ui/avatar` |
|
|
41
|
+
| `SearchInput` | `ui/input-group` |
|
|
42
|
+
| `PagePagination` | `ui/pagination` |
|
|
43
|
+
| `EmptyTelemetry` | `ui/empty` |
|
|
38
44
|
| `TimeRangeSelector` | `ui/toggle-group` |
|
|
39
45
|
| `ThemeSwitcher` | `ui/toggle-group` |
|
|
40
46
|
| `TranscriptViewToggle` | `ui/toggle-group` |
|
|
@@ -42,6 +48,9 @@ file when every caller needs the same style or variant. App components compose
|
|
|
42
48
|
| `charts/ActivityChartTooltip` | `ui/tooltip` |
|
|
43
49
|
| `conversations/ToolFrame` | `ui/trace` |
|
|
44
50
|
| `TranscriptReasoningView` | `ui/trace` |
|
|
51
|
+
| `ImageAttachment` (preview) | `ui/dialog` |
|
|
52
|
+
| `Notice`, `InlineError` | `ui/alert` |
|
|
53
|
+
| App banners | `ui/alert` |
|
|
45
54
|
| Recharts activity charts | `ui/chart` |
|
|
46
55
|
|
|
47
56
|
Rules for adopted components:
|
|
@@ -54,6 +63,14 @@ Rules for adopted components:
|
|
|
54
63
|
- Use `ui/alert-dialog` for every confirmation. Do not use `window.confirm`.
|
|
55
64
|
Keep the confirmed item in state after close, so the closing dialog keeps its
|
|
56
65
|
text.
|
|
66
|
+
- Use `ui/dialog` for a short task form, such as the fork message. Do not put
|
|
67
|
+
a form in a `Drawer` or a `DetailsPanel`. On small screens the dialog sits
|
|
68
|
+
near the top, so the on-screen keyboard does not cover its fields.
|
|
69
|
+
- Use `useSheet` for each app `ui/dialog`, as for sheets. It shares the
|
|
70
|
+
document scroll lock. Only the front modal closes on Escape or a backdrop
|
|
71
|
+
tap. Mount the dialog only while it is open.
|
|
72
|
+
- `ui/alert` sets `role="alert"`. Pass `role="status"` for a message that is
|
|
73
|
+
not an error.
|
|
57
74
|
- `ui/toast` has no global notification manager. Features call
|
|
58
75
|
`useToastManager` inside the mounted `Toaster` provider.
|
|
59
76
|
- Put a checkbox or radio inside its `<label>`. Give each `RadioGroup` an
|
|
@@ -68,6 +85,8 @@ Rules for adopted components:
|
|
|
68
85
|
- Build a form field from `ui/field`. Give `FieldError` the id
|
|
69
86
|
`<control id>-error` and `FieldDescription` the id `<control id>-help`. The
|
|
70
87
|
control sets `aria-invalid` and `aria-describedby`.
|
|
88
|
+
- Use `Button size="menu"` for a full-width icon and label row in a mobile
|
|
89
|
+
overflow menu. A pressed row adds `bg-dashboard-fill-strong`.
|
|
71
90
|
- For a link that looks like a button, put `buttonVariants()` on the router
|
|
72
91
|
`Link`. A form page has one `Button variant="primary"` for its main save
|
|
73
92
|
action.
|
|
@@ -134,14 +153,20 @@ Shared class strings live in `src/client/styles.ts`.
|
|
|
134
153
|
child. Use `ui/accordion` for a set of sections with headings.
|
|
135
154
|
- **Details.** `DetailsPanel` docks an aside at 1280px and above, and uses a
|
|
136
155
|
`BottomSheet` below that width. It owns Escape and the scroll frame for
|
|
137
|
-
docked panels.
|
|
156
|
+
docked panels. Docked actions sit beside the close control, so their
|
|
157
|
+
position does not change with the title width.
|
|
158
|
+
- **Drawer.** `Drawer` is a right-side `ui/drawer`. A `Drawer` opened from
|
|
159
|
+
inside another `Drawer` stacks in front of it. The parent scales back,
|
|
160
|
+
shows a thin edge behind the child, and hides its content. Set `flush` when
|
|
161
|
+
the content owns its insets and sticky headers, such as the event log.
|
|
138
162
|
- **Identity.** `ObjectIcon` owns the native Ticket, Code change, Automation,
|
|
139
163
|
Deployment, and Item icons. Do not replace it with a provider logo or a
|
|
140
164
|
status-only icon.
|
|
141
165
|
- **Loading.** Use `ui/skeleton` for placeholder leaves and `PageContentSkeleton`
|
|
142
166
|
variants for page bodies. Use `PageRouteLoading` when a page keeps its real
|
|
143
167
|
header above a body skeleton. Use a feature-owned loading component for
|
|
144
|
-
distinct geometry, such as conversations.
|
|
168
|
+
distinct geometry, such as conversations. Use `ui/spinner` for an inline
|
|
169
|
+
pending icon.
|
|
145
170
|
|
|
146
171
|
## Navigation
|
|
147
172
|
|
|
@@ -2,6 +2,12 @@ import type { Ref } from "react";
|
|
|
2
2
|
import { Search, X } from "lucide-react";
|
|
3
3
|
|
|
4
4
|
import { cn } from "../styles";
|
|
5
|
+
import {
|
|
6
|
+
InputGroup,
|
|
7
|
+
InputGroupAddon,
|
|
8
|
+
InputGroupButton,
|
|
9
|
+
InputGroupInput,
|
|
10
|
+
} from "./ui/input-group";
|
|
5
11
|
|
|
6
12
|
type SearchInputSize = "compact" | "default";
|
|
7
13
|
|
|
@@ -15,23 +21,14 @@ export function SearchInput(props: {
|
|
|
15
21
|
size?: SearchInputSize;
|
|
16
22
|
value: string;
|
|
17
23
|
}) {
|
|
18
|
-
const size = props.size ?? "compact";
|
|
24
|
+
const size = sizeClass[props.size ?? "compact"];
|
|
19
25
|
return (
|
|
20
|
-
<
|
|
21
|
-
<
|
|
22
|
-
aria-hidden="true"
|
|
23
|
-
className={cn(
|
|
24
|
-
"pointer-events-none absolute top-1/2 -translate-y-1/2 text-dashboard-text-muted",
|
|
25
|
-
searchIconClass[size],
|
|
26
|
-
)}
|
|
27
|
-
size={size === "compact" ? 13 : 14}
|
|
28
|
-
strokeWidth={2.5}
|
|
29
|
-
/>
|
|
30
|
-
<input
|
|
26
|
+
<InputGroup className={cn(size.group, props.className)}>
|
|
27
|
+
<InputGroupInput
|
|
31
28
|
aria-label={props.label}
|
|
32
29
|
className={cn(
|
|
33
|
-
"
|
|
34
|
-
|
|
30
|
+
"[&::-webkit-search-cancel-button]:appearance-none",
|
|
31
|
+
size.input,
|
|
35
32
|
)}
|
|
36
33
|
onChange={(event) => props.onChange(event.currentTarget.value)}
|
|
37
34
|
placeholder={props.placeholder}
|
|
@@ -39,40 +36,53 @@ export function SearchInput(props: {
|
|
|
39
36
|
type="search"
|
|
40
37
|
value={props.value}
|
|
41
38
|
/>
|
|
42
|
-
{
|
|
43
|
-
<
|
|
39
|
+
<InputGroupAddon className={size.icon}>
|
|
40
|
+
<Search aria-hidden="true" size={size.iconSize} strokeWidth={2.5} />
|
|
41
|
+
</InputGroupAddon>
|
|
42
|
+
{/* The clear button keeps its space while hidden, so the field width does not change. */}
|
|
43
|
+
<InputGroupAddon align="inline-end" className={size.clear}>
|
|
44
|
+
<InputGroupButton
|
|
44
45
|
aria-label="Clear search"
|
|
45
|
-
className={
|
|
46
|
-
"absolute top-1/2 grid size-6 -translate-y-1/2 cursor-pointer place-items-center rounded-md text-dashboard-text-muted transition-colors hover:bg-dashboard-fill-strong hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus",
|
|
47
|
-
clearButtonClass[size],
|
|
48
|
-
)}
|
|
46
|
+
className={props.value ? undefined : "invisible"}
|
|
49
47
|
onClick={(event) => {
|
|
50
48
|
props.onChange("");
|
|
51
|
-
// The button
|
|
52
|
-
event.currentTarget
|
|
49
|
+
// The button hides once the value is empty, so keep focus in the search box.
|
|
50
|
+
event.currentTarget
|
|
51
|
+
.closest("[data-slot=input-group]")
|
|
52
|
+
?.querySelector("input")
|
|
53
|
+
?.focus();
|
|
53
54
|
}}
|
|
54
|
-
type="button"
|
|
55
55
|
>
|
|
56
56
|
<X aria-hidden="true" size={14} strokeWidth={2.5} />
|
|
57
|
-
</
|
|
58
|
-
|
|
59
|
-
</
|
|
57
|
+
</InputGroupButton>
|
|
58
|
+
</InputGroupAddon>
|
|
59
|
+
</InputGroup>
|
|
60
60
|
);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
compact:
|
|
75
|
-
"
|
|
76
|
-
|
|
77
|
-
"
|
|
63
|
+
// Match the icon, text, and clear button offsets of the bordered input.
|
|
64
|
+
const sizeClass: Record<
|
|
65
|
+
SearchInputSize,
|
|
66
|
+
{
|
|
67
|
+
clear: string;
|
|
68
|
+
group: string;
|
|
69
|
+
icon: string;
|
|
70
|
+
iconSize: number;
|
|
71
|
+
input: string;
|
|
72
|
+
}
|
|
73
|
+
> = {
|
|
74
|
+
compact: {
|
|
75
|
+
clear: "pr-1.25",
|
|
76
|
+
group: "h-9",
|
|
77
|
+
icon: "pl-2.25",
|
|
78
|
+
iconSize: 13,
|
|
79
|
+
input: "pr-1 pl-2.5",
|
|
80
|
+
},
|
|
81
|
+
default: {
|
|
82
|
+
clear: "pr-1.75",
|
|
83
|
+
group: "h-10",
|
|
84
|
+
icon: "pl-2.75",
|
|
85
|
+
iconSize: 14,
|
|
86
|
+
input: "pr-1 pl-2.75",
|
|
87
|
+
},
|
|
78
88
|
};
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import { JUNIOR_VERSION } from "@sentry/junior/version";
|
|
2
2
|
|
|
3
|
+
import { Alert } from "./ui/alert";
|
|
4
|
+
import { Button } from "./ui/button";
|
|
5
|
+
|
|
3
6
|
/** Prompt the viewer to load the dashboard bundle that matches the server. */
|
|
4
7
|
export function VersionDriftBanner(props: { serverVersion?: string }) {
|
|
5
8
|
if (!dashboardVersionDrift(props.serverVersion)) return null;
|
|
6
9
|
|
|
7
10
|
return (
|
|
8
|
-
<
|
|
11
|
+
<Alert
|
|
9
12
|
aria-live="polite"
|
|
10
|
-
className="flex flex-wrap items-center justify-center gap-x-3 gap-y-1 border-t border-dashboard-accent/15 bg-dashboard-accent/[0.07] px-3 py-1.5 text-center font-sans text-xs text-dashboard-accent-text/80"
|
|
13
|
+
className="flex flex-wrap items-center justify-center gap-x-3 gap-y-1 rounded-none border-0 border-t border-dashboard-accent/15 bg-dashboard-accent/[0.07] px-3 py-1.5 text-center font-sans text-xs text-dashboard-accent-text/80"
|
|
11
14
|
role="status"
|
|
12
15
|
>
|
|
13
16
|
<span>The dashboard changed. Refresh to load the latest version.</span>
|
|
14
|
-
<
|
|
15
|
-
className="
|
|
17
|
+
<Button
|
|
18
|
+
className="h-auto border-0 bg-transparent p-0 text-xs font-semibold leading-normal text-dashboard-accent-text underline decoration-dashboard-accent/40 underline-offset-2 hover:bg-transparent hover:text-dashboard-text-solid"
|
|
16
19
|
onClick={() => window.location.reload()}
|
|
17
|
-
type="button"
|
|
18
20
|
>
|
|
19
21
|
Refresh
|
|
20
|
-
</
|
|
21
|
-
</
|
|
22
|
+
</Button>
|
|
23
|
+
</Alert>
|
|
22
24
|
);
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -9,6 +9,14 @@ import {
|
|
|
9
9
|
CollapsibleContent,
|
|
10
10
|
CollapsibleTrigger,
|
|
11
11
|
} from "../ui/collapsible";
|
|
12
|
+
import {
|
|
13
|
+
Table,
|
|
14
|
+
TableBody,
|
|
15
|
+
TableCell,
|
|
16
|
+
TableHead,
|
|
17
|
+
TableHeader,
|
|
18
|
+
TableRow,
|
|
19
|
+
} from "../ui/table";
|
|
12
20
|
import { dashboardDisclosureChevronClass } from "../../styles";
|
|
13
21
|
|
|
14
22
|
/** Identify the largest cache writes within the page's selected time window. */
|
|
@@ -40,50 +48,51 @@ export function CacheWrites(props: {
|
|
|
40
48
|
Top three by tokens written per {unit}, within the selected time
|
|
41
49
|
window. Large writes alone do not mean a cache problem.
|
|
42
50
|
</p>
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
>
|
|
48
|
-
<
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
<Table
|
|
52
|
+
aria-label="Largest cache writes"
|
|
53
|
+
className="min-w-80 table-fixed font-sans text-xs tabular-nums"
|
|
54
|
+
>
|
|
55
|
+
<TableHeader>
|
|
56
|
+
<TableRow>
|
|
57
|
+
<TableHead scope="col" className="px-0 py-2 font-normal">
|
|
58
|
+
{props.bucketUnit === "day" ? "Day" : "Starting at"}
|
|
59
|
+
</TableHead>
|
|
60
|
+
<TableHead
|
|
61
|
+
scope="col"
|
|
62
|
+
className="w-24 px-0 py-2 text-right font-normal"
|
|
63
|
+
>
|
|
64
|
+
Written
|
|
65
|
+
</TableHead>
|
|
66
|
+
<TableHead
|
|
67
|
+
scope="col"
|
|
68
|
+
className="w-24 px-0 py-2 text-right font-normal"
|
|
69
|
+
>
|
|
70
|
+
From cache
|
|
71
|
+
</TableHead>
|
|
72
|
+
</TableRow>
|
|
73
|
+
</TableHeader>
|
|
74
|
+
<TableBody>
|
|
75
|
+
{ranked.map((day) => (
|
|
76
|
+
<TableRow key={day.date}>
|
|
77
|
+
<TableHead
|
|
78
|
+
scope="row"
|
|
79
|
+
className="px-0 py-2 font-normal whitespace-normal"
|
|
66
80
|
>
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
</tr>
|
|
83
|
-
))}
|
|
84
|
-
</tbody>
|
|
85
|
-
</table>
|
|
86
|
-
</div>
|
|
81
|
+
{formatActivityTooltipDate(day.date)}
|
|
82
|
+
</TableHead>
|
|
83
|
+
<TableCell className="px-0 py-2 text-right whitespace-normal">
|
|
84
|
+
{formatCompactNumber(day.cacheCreationTokens)}
|
|
85
|
+
</TableCell>
|
|
86
|
+
<TableCell className="px-0 py-2 text-right whitespace-normal">
|
|
87
|
+
{formatCacheShare(
|
|
88
|
+
day.cachedInputTokens,
|
|
89
|
+
cacheInputTotal(day),
|
|
90
|
+
)}
|
|
91
|
+
</TableCell>
|
|
92
|
+
</TableRow>
|
|
93
|
+
))}
|
|
94
|
+
</TableBody>
|
|
95
|
+
</Table>
|
|
87
96
|
</CollapsibleContent>
|
|
88
97
|
</Collapsible>
|
|
89
98
|
);
|