@sentry/junior-dashboard 0.264.0 → 0.265.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/LICENSE +201 -0
- package/README.md +2 -5
- package/dist/app.js +119 -35
- package/dist/client.js +145 -136
- package/dist/index.d.ts +0 -1
- package/dist/index.js +119 -35
- package/dist/tailwind.css +1 -1
- package/package.json +5 -4
- package/src/api/schema.ts +0 -1
- package/src/app.ts +5 -20
- package/src/client/App.tsx +29 -31
- package/src/client/api.ts +7 -0
- package/src/client/components/BottomSheet.tsx +12 -2
- package/src/client/components/CopyButton.tsx +83 -0
- package/src/client/components/DetailsPanel.tsx +133 -0
- package/src/client/components/Drawer.tsx +15 -24
- package/src/client/components/FilterBar.tsx +16 -31
- package/src/client/components/Metric.tsx +33 -23
- package/src/client/components/PageContentSkeleton.tsx +1 -1
- package/src/client/components/Pagination.tsx +1 -1
- package/src/client/components/ParticipantAvatarStack.tsx +40 -36
- package/src/client/components/ProfileMenu.tsx +27 -93
- package/src/client/components/README.md +86 -77
- package/src/client/components/SegmentedNav.tsx +1 -1
- package/src/client/components/ShimmerText.tsx +2 -6
- package/src/client/components/ThemeSwitcher.tsx +19 -15
- package/src/client/components/charts/ActivityChart.tsx +173 -288
- package/src/client/components/charts/CacheWrites.tsx +63 -50
- package/src/client/components/charts/InputCacheChart.tsx +137 -131
- package/src/client/components/charts/README.md +4 -4
- package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
- package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
- package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
- package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
- package/src/client/components/layout/DashboardHeader.tsx +44 -90
- package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
- package/src/client/components/layout/VisualViewportShell.tsx +2 -2
- package/src/client/components/ui/accordion.tsx +77 -0
- package/src/client/components/ui/alert-dialog.tsx +89 -0
- package/src/client/components/ui/button.tsx +85 -0
- package/src/client/components/ui/chart.tsx +74 -0
- package/src/client/components/ui/checkbox.tsx +26 -0
- package/src/client/components/ui/collapsible.tsx +22 -0
- package/src/client/components/ui/field.tsx +49 -0
- package/src/client/components/ui/input.tsx +35 -0
- package/src/client/components/ui/native-select.tsx +57 -0
- package/src/client/components/ui/popover.tsx +67 -0
- package/src/client/components/ui/radio-group.tsx +32 -0
- package/src/client/components/ui/sheet.tsx +13 -3
- package/src/client/components/ui/skeleton.tsx +19 -0
- package/src/client/components/ui/table.tsx +80 -0
- package/src/client/components/ui/tabs.tsx +75 -0
- package/src/client/components/ui/textarea.tsx +17 -0
- package/src/client/components/ui/toast.tsx +215 -0
- package/src/client/components/ui/toggle-group.tsx +36 -0
- package/src/client/components/ui/toggle.tsx +37 -0
- package/src/client/components/ui/tooltip.tsx +168 -0
- package/src/client/components/ui/trace.tsx +96 -0
- package/src/client/conversations/ChatLayout.tsx +7 -3
- package/src/client/conversations/ConversationBrief.tsx +17 -6
- package/src/client/conversations/ConversationComposer.tsx +26 -12
- package/src/client/conversations/ConversationDetails.tsx +147 -62
- package/src/client/conversations/ConversationEventDetails.tsx +141 -0
- package/src/client/conversations/ConversationEventLog.tsx +150 -129
- package/src/client/conversations/ConversationHeader.tsx +8 -3
- package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
- package/src/client/conversations/ConversationHomeList.tsx +1 -1
- package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
- package/src/client/conversations/ConversationMeta.tsx +55 -88
- package/src/client/conversations/ConversationPage.tsx +220 -101
- package/src/client/conversations/ConversationSidebar.tsx +48 -29
- package/src/client/conversations/ConversationWatches.tsx +165 -0
- package/src/client/conversations/ConversationWorkspace.tsx +12 -22
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
- package/src/client/conversations/DetailsList.tsx +124 -0
- package/src/client/conversations/EventDetails.tsx +42 -8
- package/src/client/conversations/ForkConversationButton.tsx +27 -16
- package/src/client/conversations/ObjectCard.tsx +20 -8
- package/src/client/conversations/PendingMailboxStack.tsx +52 -33
- package/src/client/conversations/README.md +86 -13
- package/src/client/conversations/ShareConversationButton.tsx +25 -13
- package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
- package/src/client/conversations/ToolFrame.tsx +36 -52
- package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
- package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
- package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
- package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
- package/src/client/conversations/TranscriptLoading.tsx +1 -1
- package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
- package/src/client/conversations/TranscriptMessageContextView.tsx +8 -2
- package/src/client/conversations/TranscriptMessageView.tsx +6 -1
- package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
- package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
- package/src/client/conversations/TranscriptToolView.tsx +1 -1
- package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
- package/src/client/conversations/TranscriptView.tsx +63 -27
- package/src/client/conversations/eventLog.ts +57 -0
- package/src/client/conversations/eventTime.ts +60 -0
- package/src/client/conversations/queries.ts +29 -0
- package/src/client/conversations/transcriptBottomPinning.ts +34 -0
- package/src/client/format.ts +5 -0
- package/src/client/pages/ConnectionsPage.tsx +2 -2
- package/src/client/pages/PersonalTokensPage.tsx +4 -4
- package/src/client/pages/SettingsPage.tsx +9 -10
- package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
- package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
- package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
- package/src/client/pages/automations/AutomationDetails.tsx +193 -0
- package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
- package/src/client/pages/automations/AutomationEditor.tsx +41 -29
- package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
- package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
- package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
- package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
- package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
- package/src/client/pages/automations/AutomationFilters.tsx +189 -74
- package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
- package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
- package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
- package/src/client/pages/automations/AutomationRow.tsx +12 -138
- package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
- package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
- package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
- package/src/client/pages/automations/AutomationStatus.tsx +201 -0
- package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
- package/src/client/pages/automations/AutomationsPage.tsx +80 -68
- package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
- package/src/client/pages/automations/README.md +57 -0
- package/src/client/pages/automations/TriggerFilters.tsx +22 -0
- package/src/client/pages/automations/automationFormat.ts +37 -23
- package/src/client/pages/code/CodeActivityChart.tsx +59 -101
- package/src/client/pages/code/CodePage.tsx +52 -55
- package/src/client/pages/code/RepositoryPage.tsx +6 -8
- package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
- package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
- package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
- package/src/client/pages/memory/MemoryDetails.tsx +218 -0
- package/src/client/pages/memory/MemoryPage.tsx +168 -112
- package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
- package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
- package/src/client/pages/memory/memoryRecord.ts +1 -1
- package/src/client/pages/people/ContributionGrid.tsx +23 -16
- package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
- package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
- package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
- package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
- package/src/client/pages/system/GuardianActivity.tsx +1 -2
- package/src/client/pages/system/PluginBarChart.tsx +106 -87
- package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
- package/src/client/pages/system/README.md +23 -0
- package/src/client/pages/system/SystemNavigation.tsx +5 -1
- package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
- package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
- package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
- package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
- package/src/client/pages/system/WorkspaceList.tsx +20 -30
- package/src/client/pages/system/WorkspacesPage.tsx +117 -119
- package/src/client/pages/system/sandboxSize.ts +8 -0
- package/src/client/pages/system/workspaceQueries.ts +53 -0
- package/src/client/pages/user/PluginUserPage.tsx +51 -2
- package/src/client/pages/user/pluginUserPageData.ts +1 -6
- package/src/client/styles.ts +4 -0
- package/src/client.tsx +7 -1
- package/src/mock-reporting/automation-collection.ts +4 -2
- package/src/mock-reporting/fixtures.ts +23 -7
- package/src/mock-reporting/routes.ts +12 -0
- package/src/mock-reporting/watches.ts +94 -0
- package/src/shell.ts +4 -9
- package/src/tailwind.css +0 -10
- package/src/client/components/Button.tsx +0 -118
- package/src/client/components/Field.tsx +0 -53
- package/src/client/components/SegmentedTabs.tsx +0 -119
- package/src/client/components/Select.tsx +0 -52
- package/src/client/components/Skeleton.tsx +0 -17
- package/src/client/components/TextInput.tsx +0 -58
- package/src/client/components/Tooltip.tsx +0 -216
- package/src/client/components/charts/useChartLayout.ts +0 -27
- package/src/client/components/ui/sheet.LICENSE +0 -21
- package/src/client/conversations/ConversationDetailsFrame.tsx +0 -119
- package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
- package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
- package/src/client/pages/dev/ChartFixtures.tsx +0 -325
- package/src/client/pages/dev/ComponentsPage.tsx +0 -899
- package/src/client/pages/dev/ConversationFixture.tsx +0 -231
- package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
- package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
- package/src/client/pages/dev/SelectFixtures.tsx +0 -62
- package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
- package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
- package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
- package/src/client/pages/system/SnapshotSummary.tsx +0 -78
|
@@ -2,7 +2,7 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { cn } from "../styles";
|
|
4
4
|
import { ShimmerText } from "./ShimmerText";
|
|
5
|
-
import { Tooltip } from "./
|
|
5
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
|
6
6
|
|
|
7
7
|
export type MetricTooltipLine = {
|
|
8
8
|
label?: string;
|
|
@@ -85,34 +85,44 @@ export function MetricValue(props: {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
return (
|
|
88
|
-
<Tooltip
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
<Tooltip>
|
|
89
|
+
<TooltipTrigger
|
|
90
|
+
render={
|
|
91
|
+
<span
|
|
92
|
+
className={cn(
|
|
93
|
+
"inline-flex border-b border-dotted border-dashboard-border-interactive outline-none transition-colors hover:border-dashboard-border-heavy focus-visible:border-dashboard-border-heavy",
|
|
94
|
+
props.className,
|
|
95
|
+
)}
|
|
96
|
+
tabIndex={0}
|
|
97
|
+
>
|
|
98
|
+
{props.children}
|
|
99
|
+
</span>
|
|
100
|
+
}
|
|
101
|
+
/>
|
|
102
|
+
<TooltipContent
|
|
103
|
+
align={
|
|
104
|
+
props.align === "left"
|
|
105
|
+
? "start"
|
|
106
|
+
: props.align === "right"
|
|
107
|
+
? "end"
|
|
108
|
+
: "center"
|
|
109
|
+
}
|
|
110
|
+
className={cn(
|
|
111
|
+
"w-[calc(100vw-2rem)] max-w-80 rounded-lg bg-dashboard-bg text-left font-normal shadow-xl shadow-black/35",
|
|
112
|
+
props.tooltipCard ? "p-3 leading-snug" : "px-3 py-2 leading-relaxed",
|
|
113
|
+
)}
|
|
114
|
+
side={props.tooltipPlacement === "above" ? "top" : "bottom"}
|
|
115
|
+
>
|
|
116
|
+
{props.tooltipCard ? (
|
|
117
|
+
<span className="block max-h-[min(30rem,var(--available-height,30rem))] overflow-y-auto">
|
|
97
118
|
{props.tooltipCard}
|
|
98
119
|
</span>
|
|
99
120
|
) : tooltip ? (
|
|
100
121
|
<span className="block max-h-72 overflow-y-auto">
|
|
101
122
|
<TooltipLines lines={tooltip} />
|
|
102
123
|
</span>
|
|
103
|
-
) : null
|
|
104
|
-
|
|
105
|
-
placement={props.tooltipPlacement ?? "below"}
|
|
106
|
-
>
|
|
107
|
-
<span
|
|
108
|
-
className={cn(
|
|
109
|
-
"inline-flex border-b border-dotted border-dashboard-border-interactive outline-none transition-colors hover:border-dashboard-border-heavy focus-visible:border-dashboard-border-heavy",
|
|
110
|
-
props.className,
|
|
111
|
-
)}
|
|
112
|
-
tabIndex={0}
|
|
113
|
-
>
|
|
114
|
-
{props.children}
|
|
115
|
-
</span>
|
|
124
|
+
) : null}
|
|
125
|
+
</TooltipContent>
|
|
116
126
|
</Tooltip>
|
|
117
127
|
);
|
|
118
128
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
2
2
|
|
|
3
3
|
import { cn } from "../styles";
|
|
4
|
-
import { Button } from "./
|
|
4
|
+
import { Button } from "./ui/button";
|
|
5
5
|
|
|
6
6
|
/** Render a centered load-more control for cursor or progressive lists. */
|
|
7
7
|
export function LoadMorePagination(props: {
|
|
@@ -4,7 +4,7 @@ import { actorLabel } from "../format";
|
|
|
4
4
|
import { cn } from "../styles";
|
|
5
5
|
import type { Conversation } from "../types";
|
|
6
6
|
import { ActorAvatar } from "./ActorAvatar";
|
|
7
|
-
import { Tooltip } from "./
|
|
7
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
|
8
8
|
|
|
9
9
|
const MAX_VISIBLE_PARTICIPANTS = 3;
|
|
10
10
|
|
|
@@ -57,22 +57,25 @@ function Avatar(props: {
|
|
|
57
57
|
}) {
|
|
58
58
|
const label = participantDescription(props.participant);
|
|
59
59
|
return (
|
|
60
|
-
<Tooltip
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
60
|
+
<Tooltip>
|
|
61
|
+
<TooltipTrigger
|
|
62
|
+
render={
|
|
63
|
+
<span
|
|
64
|
+
aria-label={label}
|
|
65
|
+
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
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
67
|
+
tabIndex={0}
|
|
68
|
+
>
|
|
69
|
+
<ActorAvatar
|
|
70
|
+
name={participantName(props.participant)}
|
|
71
|
+
size={props.size}
|
|
72
|
+
/>
|
|
73
|
+
</span>
|
|
74
|
+
}
|
|
75
|
+
/>
|
|
76
|
+
<TooltipContent>
|
|
77
|
+
<ParticipantTooltipContent participant={props.participant} />
|
|
78
|
+
</TooltipContent>
|
|
76
79
|
</Tooltip>
|
|
77
80
|
);
|
|
78
81
|
}
|
|
@@ -105,8 +108,25 @@ export function ParticipantAvatarStack(props: ParticipantAvatarStackProps) {
|
|
|
105
108
|
/>
|
|
106
109
|
))}
|
|
107
110
|
{hidden.length > 0 ? (
|
|
108
|
-
<Tooltip
|
|
109
|
-
|
|
111
|
+
<Tooltip>
|
|
112
|
+
<TooltipTrigger
|
|
113
|
+
render={
|
|
114
|
+
<span
|
|
115
|
+
aria-label={`${hidden.length} more participants: ${hidden
|
|
116
|
+
.map(participantDescription)
|
|
117
|
+
.join("; ")}`}
|
|
118
|
+
className={cn(
|
|
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
|
+
)}
|
|
122
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
123
|
+
tabIndex={0}
|
|
124
|
+
>
|
|
125
|
+
+{hidden.length}
|
|
126
|
+
</span>
|
|
127
|
+
}
|
|
128
|
+
/>
|
|
129
|
+
<TooltipContent>
|
|
110
130
|
<span className="grid gap-1 font-sans">
|
|
111
131
|
{hidden.map((participant, index) => (
|
|
112
132
|
<span key={participantKey(participant, index)}>
|
|
@@ -114,23 +134,7 @@ export function ParticipantAvatarStack(props: ParticipantAvatarStackProps) {
|
|
|
114
134
|
</span>
|
|
115
135
|
))}
|
|
116
136
|
</span>
|
|
117
|
-
|
|
118
|
-
focusable
|
|
119
|
-
triggerClassName="-ml-1 pointer-events-none [@media(hover:hover)_and_(pointer:fine)]:pointer-events-auto"
|
|
120
|
-
>
|
|
121
|
-
<span
|
|
122
|
-
aria-label={`${hidden.length} more participants: ${hidden
|
|
123
|
-
.map(participantDescription)
|
|
124
|
-
.join("; ")}`}
|
|
125
|
-
className={cn(
|
|
126
|
-
"relative inline-grid shrink-0 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",
|
|
127
|
-
props.size === "list" ? "size-6 text-2xs" : "size-7 text-xs",
|
|
128
|
-
)}
|
|
129
|
-
onKeyDown={(event) => event.stopPropagation()}
|
|
130
|
-
tabIndex={0}
|
|
131
|
-
>
|
|
132
|
-
+{hidden.length}
|
|
133
|
-
</span>
|
|
137
|
+
</TooltipContent>
|
|
134
138
|
</Tooltip>
|
|
135
139
|
) : null}
|
|
136
140
|
</span>
|
|
@@ -8,12 +8,7 @@ import {
|
|
|
8
8
|
ShieldCheck,
|
|
9
9
|
UserRound,
|
|
10
10
|
} from "lucide-react";
|
|
11
|
-
import {
|
|
12
|
-
useEffect,
|
|
13
|
-
useRef,
|
|
14
|
-
useState,
|
|
15
|
-
type PointerEvent as ReactPointerEvent,
|
|
16
|
-
} from "react";
|
|
11
|
+
import { useState } from "react";
|
|
17
12
|
import { Link } from "react-router";
|
|
18
13
|
import type { PersonalSpendReport } from "@sentry/junior/api/schema";
|
|
19
14
|
import type { PluginUserPageLink } from "@sentry/junior-plugin-api";
|
|
@@ -23,6 +18,7 @@ import { pluginUserPagePath } from "../pages/user/PluginUserPage";
|
|
|
23
18
|
import { cn, dashboardInteractiveTextClass } from "../styles";
|
|
24
19
|
import type { Identity } from "../types";
|
|
25
20
|
import { ThemeSwitcher } from "./ThemeSwitcher";
|
|
21
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
|
26
22
|
|
|
27
23
|
type ProfileMenuProps = {
|
|
28
24
|
identity: Identity;
|
|
@@ -65,14 +61,6 @@ export function ProfileMenu({
|
|
|
65
61
|
variant = "popover",
|
|
66
62
|
}: ProfileMenuProps) {
|
|
67
63
|
const [open, setOpen] = useState(false);
|
|
68
|
-
const rootRef = useRef<HTMLDivElement>(null);
|
|
69
|
-
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
70
|
-
const openTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
71
|
-
undefined,
|
|
72
|
-
);
|
|
73
|
-
const closeTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
74
|
-
undefined,
|
|
75
|
-
);
|
|
76
64
|
const email = identity.user.email!;
|
|
77
65
|
const name = identity.user.name?.trim() || email;
|
|
78
66
|
const sevenDaySpend = spend
|
|
@@ -85,54 +73,6 @@ export function ProfileMenu({
|
|
|
85
73
|
(page) => page.navigation === "profile",
|
|
86
74
|
);
|
|
87
75
|
|
|
88
|
-
function clearHoverTimers() {
|
|
89
|
-
if (openTimerRef.current) clearTimeout(openTimerRef.current);
|
|
90
|
-
if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
|
|
91
|
-
openTimerRef.current = undefined;
|
|
92
|
-
closeTimerRef.current = undefined;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function openOnHover(event: ReactPointerEvent<HTMLDivElement>) {
|
|
96
|
-
if (event.pointerType !== "mouse") return;
|
|
97
|
-
clearHoverTimers();
|
|
98
|
-
openTimerRef.current = setTimeout(() => setOpen(true), HOVER_OPEN_DELAY_MS);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function closeOnHover(event: ReactPointerEvent<HTMLDivElement>) {
|
|
102
|
-
if (event.pointerType !== "mouse") return;
|
|
103
|
-
clearHoverTimers();
|
|
104
|
-
closeTimerRef.current = setTimeout(
|
|
105
|
-
() => setOpen(false),
|
|
106
|
-
HOVER_CLOSE_DELAY_MS,
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
useEffect(() => clearHoverTimers, []);
|
|
111
|
-
|
|
112
|
-
useEffect(() => {
|
|
113
|
-
if (variant !== "popover" || !open) return;
|
|
114
|
-
|
|
115
|
-
function closeOnOutsideClick(event: PointerEvent) {
|
|
116
|
-
if (!rootRef.current?.contains(event.target as Node)) {
|
|
117
|
-
setOpen(false);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function closeOnEscape(event: KeyboardEvent) {
|
|
122
|
-
if (event.key === "Escape") {
|
|
123
|
-
setOpen(false);
|
|
124
|
-
triggerRef.current?.focus();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
document.addEventListener("pointerdown", closeOnOutsideClick);
|
|
129
|
-
document.addEventListener("keydown", closeOnEscape);
|
|
130
|
-
return () => {
|
|
131
|
-
document.removeEventListener("pointerdown", closeOnOutsideClick);
|
|
132
|
-
document.removeEventListener("keydown", closeOnEscape);
|
|
133
|
-
};
|
|
134
|
-
}, [open, variant]);
|
|
135
|
-
|
|
136
76
|
const avatar = (
|
|
137
77
|
<span
|
|
138
78
|
aria-hidden="true"
|
|
@@ -335,32 +275,28 @@ export function ProfileMenu({
|
|
|
335
275
|
}
|
|
336
276
|
|
|
337
277
|
return (
|
|
338
|
-
<
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
278
|
+
<Popover
|
|
279
|
+
onOpenChange={(nextOpen, details) => {
|
|
280
|
+
// A mouse click always keeps the menu open, also after hover opened it.
|
|
281
|
+
// A keyboard press (detail 0) still toggles it.
|
|
282
|
+
const mouseClick =
|
|
283
|
+
details.event instanceof MouseEvent && details.event.detail > 0;
|
|
284
|
+
if (!nextOpen && details.reason === "trigger-press" && mouseClick) {
|
|
285
|
+
return;
|
|
343
286
|
}
|
|
287
|
+
setOpen(nextOpen);
|
|
344
288
|
}}
|
|
345
|
-
|
|
346
|
-
onPointerLeave={closeOnHover}
|
|
347
|
-
ref={rootRef}
|
|
289
|
+
open={open}
|
|
348
290
|
>
|
|
349
|
-
<
|
|
350
|
-
aria-controls="profile-popover"
|
|
351
|
-
aria-expanded={open}
|
|
352
|
-
aria-haspopup="true"
|
|
291
|
+
<PopoverTrigger
|
|
353
292
|
aria-label={`${open ? "Close" : "Open"} profile menu for ${name}. Personal model spend: 7 days ${sevenDaySpend}, 30 days ${thirtyDaySpend}.`}
|
|
354
293
|
className={cn(
|
|
355
294
|
"group flex h-10 cursor-pointer items-center gap-2 rounded-lg border-0 bg-transparent px-1.5 text-dashboard-text transition-colors hover:bg-dashboard-fill-hover hover:text-dashboard-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus/70",
|
|
356
295
|
open && "bg-dashboard-fill-hover text-dashboard-text",
|
|
357
296
|
)}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}}
|
|
362
|
-
ref={triggerRef}
|
|
363
|
-
type="button"
|
|
297
|
+
closeDelay={HOVER_CLOSE_DELAY_MS}
|
|
298
|
+
delay={HOVER_OPEN_DELAY_MS}
|
|
299
|
+
openOnHover
|
|
364
300
|
>
|
|
365
301
|
<span className="hidden items-center gap-2 md:flex">
|
|
366
302
|
<span className="flex items-baseline gap-1 whitespace-nowrap tabular-nums">
|
|
@@ -399,18 +335,16 @@ export function ProfileMenu({
|
|
|
399
335
|
size={14}
|
|
400
336
|
strokeWidth={2}
|
|
401
337
|
/>
|
|
402
|
-
</
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
) : null}
|
|
414
|
-
</div>
|
|
338
|
+
</PopoverTrigger>
|
|
339
|
+
<PopoverContent
|
|
340
|
+
align="end"
|
|
341
|
+
className="w-64 rounded-xl border-0 bg-dashboard-surface-raised/95 p-1.5 shadow-2xl shadow-black/75 backdrop-blur-xl"
|
|
342
|
+
id="profile-popover"
|
|
343
|
+
>
|
|
344
|
+
{identityBlock}
|
|
345
|
+
<ThemeSwitcher className="border-b border-dashboard-border-strong px-2.5 py-2" />
|
|
346
|
+
{menuLinks(() => setOpen(false))}
|
|
347
|
+
</PopoverContent>
|
|
348
|
+
</Popover>
|
|
415
349
|
);
|
|
416
350
|
}
|
|
@@ -16,23 +16,61 @@ pnpm --filter @sentry/junior-dashboard exec shadcn add <component>
|
|
|
16
16
|
|
|
17
17
|
The CLI writes components to `src/client/components/ui/`. Its `@dashboard/*`
|
|
18
18
|
alias points to `src/client/*`. The existing `@/*` alias still points to core.
|
|
19
|
-
`
|
|
20
|
-
|
|
19
|
+
The `ui/` files join classes with the merging `cn` from
|
|
20
|
+
`src/client/lib/utils.ts`, so a caller's `className` wins over a base class.
|
|
21
21
|
|
|
22
22
|
The shadcn color tokens in `src/tailwind.css` use the dashboard tokens in both
|
|
23
|
-
themes.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
themes. The `dark:` variant follows `data-theme`. Keep the existing Tailwind
|
|
24
|
+
radius scale. Do not apply the CLI's global border or body resets. Check each
|
|
25
|
+
added component against the rules below before use.
|
|
26
|
+
|
|
27
|
+
Each `ui/` file is shadcn source that uses dashboard tokens. Remove exports
|
|
28
|
+
that no app code uses. Use `ui/` components directly. Do not add an app
|
|
29
|
+
component that only renames a `ui/` component or its props. Change the `ui/`
|
|
30
|
+
file when every caller needs the same style or variant. App components compose
|
|
31
|
+
`ui/` source only when they add structure or behavior:
|
|
32
|
+
|
|
33
|
+
| App component | shadcn source |
|
|
34
|
+
| ------------------------------ | ----------------- |
|
|
35
|
+
| `Drawer`, `BottomSheet` | `ui/sheet` |
|
|
36
|
+
| `layout/DashboardHeader` (nav) | `ui/sheet` |
|
|
37
|
+
| `ProfileMenu` (desktop) | `ui/popover` |
|
|
38
|
+
| `TimeRangeSelector` | `ui/toggle-group` |
|
|
39
|
+
| `ThemeSwitcher` | `ui/toggle-group` |
|
|
40
|
+
| `TranscriptViewToggle` | `ui/toggle-group` |
|
|
41
|
+
| `FilterTabList` | `ui/tabs` |
|
|
42
|
+
| `charts/ActivityChartTooltip` | `ui/tooltip` |
|
|
43
|
+
| `conversations/ToolFrame` | `ui/trace` |
|
|
44
|
+
| `TranscriptReasoningView` | `ui/trace` |
|
|
45
|
+
| Recharts activity charts | `ui/chart` |
|
|
46
|
+
|
|
47
|
+
Rules for adopted components:
|
|
48
|
+
|
|
49
|
+
- Use `ui/trace` for transcript activity rows. It composes `ui/collapsible`.
|
|
50
|
+
A closed tool row unmounts its panel, so it does not run Shiki. Reasoning
|
|
51
|
+
sets `hiddenUntilFound`, so browser find reaches closed text. Transcript
|
|
52
|
+
search holds rows open and turns off the row button. Put only inline
|
|
53
|
+
elements, such as `span`, inside the row button.
|
|
54
|
+
- Use `ui/alert-dialog` for every confirmation. Do not use `window.confirm`.
|
|
55
|
+
Keep the confirmed item in state after close, so the closing dialog keeps its
|
|
56
|
+
text.
|
|
57
|
+
- `ui/toast` has no global notification manager. Features call
|
|
58
|
+
`useToastManager` inside the mounted `Toaster` provider.
|
|
59
|
+
- Put a checkbox or radio inside its `<label>`. Give each `RadioGroup` an
|
|
60
|
+
`aria-label`.
|
|
61
|
+
- Give an icon-only control its label with `TooltipContent size="compact"`.
|
|
62
|
+
Do not use the native `title` attribute. Tooltips open on mouse hover and
|
|
63
|
+
keyboard focus only, so a tap still runs the control.
|
|
64
|
+
- When a `ui/button` tooltip trigger can be disabled, set
|
|
65
|
+
`focusableWhenDisabled`. A native disabled button can drop hover and focus,
|
|
66
|
+
so Firefox closes the tooltip and hides pending or result labels.
|
|
67
|
+
`ui/button` styles `data-disabled`, so both disabled modes look the same.
|
|
68
|
+
- Build a form field from `ui/field`. Give `FieldError` the id
|
|
69
|
+
`<control id>-error` and `FieldDescription` the id `<control id>-help`. The
|
|
70
|
+
control sets `aria-invalid` and `aria-describedby`.
|
|
71
|
+
- For a link that looks like a button, put `buttonVariants()` on the router
|
|
72
|
+
`Link`. A form page has one `Button variant="primary"` for its main save
|
|
73
|
+
action.
|
|
36
74
|
|
|
37
75
|
## Foundations
|
|
38
76
|
|
|
@@ -46,9 +84,9 @@ token so each theme can redefine it.
|
|
|
46
84
|
paint from the saved choice or the system color scheme. `ThemeSwitcher` saves
|
|
47
85
|
the choice for this browser. Use the `light:` variant only for one-off values
|
|
48
86
|
that no token covers, such as provider brand colors.
|
|
49
|
-
- **Charts.** Use `var(--color-*)` for series colors
|
|
50
|
-
|
|
51
|
-
|
|
87
|
+
- **Charts.** Use `var(--color-*)` for series colors, not hex values, so they
|
|
88
|
+
follow the theme. Name series colors in the `ChartContainer` config and fill
|
|
89
|
+
each series with `var(--color-<key>)`.
|
|
52
90
|
- **Surfaces.** Use the surface ladder, darkest to lightest: `bg`,
|
|
53
91
|
`bg-elevated` (drawers), `surface-panel` (sidebar, cards, bubbles),
|
|
54
92
|
`surface-raised` (buttons, tooltips), `control`, `surface-hover`,
|
|
@@ -81,80 +119,53 @@ token so each theme can redefine it.
|
|
|
81
119
|
use `rounded-lg`. The composer, menus, and transcript object cards use
|
|
82
120
|
`rounded-xl`. Avatars and count badges use `rounded-full`.
|
|
83
121
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
`src/client/styles.ts` holds the few class strings that many components share.
|
|
87
|
-
|
|
88
|
-
- `cn` joins class names.
|
|
89
|
-
- `dashboardContainerClass` sets the page width.
|
|
90
|
-
- `dashboardInlineLinkClass` styles a neutral underlined link inside prose.
|
|
91
|
-
- `dashboardInteractiveTextClass` styles muted text that brightens on hover.
|
|
92
|
-
- `dashboardComposerSurfaceClass` styles the composer surface.
|
|
122
|
+
Shared class strings live in `src/client/styles.ts`.
|
|
93
123
|
|
|
94
|
-
##
|
|
124
|
+
## App components
|
|
95
125
|
|
|
96
|
-
### Primitives
|
|
97
|
-
|
|
98
|
-
- **Actions.** `Button` (`default`, `primary`, `danger`, and `size="icon"`) and
|
|
99
|
-
`ButtonLink`. Form pages use `Button tone="primary"` for their one main save
|
|
100
|
-
action. Use `ToggleButton` variants (`pill`, `segment`, `text`) for pressed
|
|
101
|
-
states, not one-off styles.
|
|
102
|
-
- **Forms.** `Field` (`default`, `compact`), `TextInput`, `TextArea`, `Select`,
|
|
103
|
-
and `SearchInput`. Use `TextInput size="comfortable"` for larger mobile
|
|
104
|
-
controls. Use `TextArea prose` for instructions, not code. `Field error`
|
|
105
|
-
renders an error at `<htmlFor>-error`. The owning control must set
|
|
106
|
-
`aria-invalid` and `aria-describedby`. `Select` owns the native control and its
|
|
107
|
-
inset arrow. Use its `filter` and `sort` variants for compact controls.
|
|
108
126
|
- **Status.** Use `StatusDot` for dense table or list markers. Use `StatusChip`
|
|
109
|
-
when the label must stay visible. `StatusChip` is a
|
|
110
|
-
sentence-case text, with no border or fill. It is not a control.
|
|
127
|
+
when the label must stay visible. `StatusChip` is not a control.
|
|
111
128
|
- **Messages.** Use `Notice` for compact transient messages and `FormNotice`
|
|
112
129
|
for wrapping form recovery text.
|
|
113
|
-
- **
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
`
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
Item icons.
|
|
123
|
-
|
|
124
|
-
- **Loading.** Use `
|
|
130
|
+
- **Disclosure.** Use `ui/collapsible` for one show-or-hide section. Put a
|
|
131
|
+
`ChevronRight` with `dashboardDisclosureChevronClass` in a `group` trigger.
|
|
132
|
+
Set `hiddenUntilFound` on the content when browser find must reach closed
|
|
133
|
+
text. Closed content then keeps its box, so put margins and padding on a
|
|
134
|
+
child. Use `ui/accordion` for a set of sections with headings.
|
|
135
|
+
- **Details.** `DetailsPanel` docks an aside at 1280px and above, and uses a
|
|
136
|
+
`BottomSheet` below that width. It owns Escape and the scroll frame for
|
|
137
|
+
docked panels.
|
|
138
|
+
- **Identity.** `ObjectIcon` owns the native Ticket, Code change, Automation,
|
|
139
|
+
Deployment, and Item icons. Do not replace it with a provider logo or a
|
|
140
|
+
status-only icon.
|
|
141
|
+
- **Loading.** Use `ui/skeleton` for placeholder leaves and `PageContentSkeleton`
|
|
125
142
|
variants for page bodies. Use `PageRouteLoading` when a page keeps its real
|
|
126
143
|
header above a body skeleton. Use a feature-owned loading component for
|
|
127
144
|
distinct geometry, such as conversations.
|
|
128
145
|
|
|
129
|
-
|
|
146
|
+
## Navigation
|
|
130
147
|
|
|
131
148
|
The dashboard has three navigation levels. Each has its own look, so the
|
|
132
149
|
levels stay distinct.
|
|
133
150
|
|
|
134
151
|
- **App navigation** in `layout/DashboardHeader` uses pills.
|
|
135
152
|
- **Page links** use `SegmentedNav`, through `layout/SecondaryNavigation`. They
|
|
136
|
-
are underline tabs that sit on the header border.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
selected value, items, change handler, and panel content. It owns tab and
|
|
140
|
-
panel labels and keyboard focus. Pass `scroll` to keep the tabs fixed while
|
|
141
|
-
the panel content scrolls. That mode fills its frame and owns the content
|
|
142
|
-
padding, so its scrollbar stays at the frame edge. `BottomSheet` content owns
|
|
143
|
-
its own padding.
|
|
153
|
+
are underline tabs that sit on the header border.
|
|
154
|
+
- **Panel switches** use `ui/tabs`. Give `TabsList` an `aria-label`. Set
|
|
155
|
+
`activateOnFocus` so arrow keys select the tab.
|
|
144
156
|
|
|
145
|
-
Keep collection filters in `FilterTabList`.
|
|
157
|
+
Keep collection filters in `FilterTabList`. It uses `TabsList variant="line"`
|
|
158
|
+
and adds the count badges.
|
|
146
159
|
|
|
147
|
-
|
|
160
|
+
## Patterns
|
|
148
161
|
|
|
149
162
|
- **Layout.** `layout/` holds the shell chrome, page headers, cards, and
|
|
150
163
|
section titles. `DashboardChrome` owns the sticky header block and its one
|
|
151
164
|
bottom border.
|
|
152
|
-
- **
|
|
153
|
-
|
|
154
|
-
`charts/ActivityChart`
|
|
155
|
-
|
|
156
|
-
day or hour charts compose those helpers. They do not format UTC bucket keys
|
|
157
|
-
inline.
|
|
165
|
+
- **Charts.** Put `role="img"` and the chart `aria-label` on the Recharts
|
|
166
|
+
chart, so the tooltip live region stays outside the image. Day and hour
|
|
167
|
+
charts compose the `charts/ActivityChart` axis, average line, and tooltip
|
|
168
|
+
helpers. They do not format UTC bucket keys inline.
|
|
158
169
|
- **Conversation mobile geometry** is a hard ownership edge:
|
|
159
170
|
- `layout/VisualViewportShell` owns the fixed shell, visualViewport CSS
|
|
160
171
|
variables, and body lock.
|
|
@@ -166,16 +177,14 @@ Keep collection filters in `FilterTabList`.
|
|
|
166
177
|
dock, and must not use `ComposerDock`. Do not add viewport height, offset, or
|
|
167
178
|
bottom safe-area math in page modules.
|
|
168
179
|
|
|
169
|
-
### Features
|
|
170
|
-
|
|
171
180
|
Feature surfaces stay under `conversations/` or `pages/` until two real callers
|
|
172
|
-
share them. Transcript
|
|
173
|
-
|
|
181
|
+
share them. See [Transcript components](../conversations/README.md) for
|
|
182
|
+
transcript ownership.
|
|
174
183
|
|
|
175
184
|
## Rules
|
|
176
185
|
|
|
177
186
|
- Prefer a named component on the second real use.
|
|
178
187
|
- Keep Tailwind classes on the owning component.
|
|
179
|
-
-
|
|
180
|
-
|
|
188
|
+
- Check new or changed shared components on a real page with mock data and in
|
|
189
|
+
the dashboard E2E screenshots.
|
|
181
190
|
- One-off page glue may stay inline when extraction only adds indirection.
|
|
@@ -81,7 +81,7 @@ function edgeFade(edges: HiddenEdges): CSSProperties | undefined {
|
|
|
81
81
|
/**
|
|
82
82
|
* Show links between sibling pages as underline tabs. The current page has
|
|
83
83
|
* a solid underline that sits on the border below the row. App navigation
|
|
84
|
-
* uses pills and in-place panel switches use `
|
|
84
|
+
* uses pills and in-place panel switches use `ui/tabs`, so the three
|
|
85
85
|
* levels stay distinct.
|
|
86
86
|
*
|
|
87
87
|
* With `fill`, links share the row width equally while their labels fit.
|
|
@@ -2,11 +2,7 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { cn } from "../styles";
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
export const shimmerTextClassName =
|
|
7
|
-
"animate-[junior-text-shimmer_1.6s_linear_infinite] bg-[linear-gradient(90deg,#777_0%,#d6d6d6_40%,#fff_50%,#d6d6d6_60%,#777_100%)] bg-[length:200%_100%] bg-clip-text text-transparent motion-reduce:animate-none";
|
|
8
|
-
|
|
9
|
-
/** Render text with the dashboard running shimmer when `active` is true. */
|
|
5
|
+
/** Render text with the shadcn `shimmer` utility when `active` is true. */
|
|
10
6
|
export function ShimmerText(props: {
|
|
11
7
|
active?: boolean;
|
|
12
8
|
as?: "span" | "strong";
|
|
@@ -19,7 +15,7 @@ export function ShimmerText(props: {
|
|
|
19
15
|
if (!props.active && !props.className && !props["aria-label"]) {
|
|
20
16
|
return props.children;
|
|
21
17
|
}
|
|
22
|
-
const className = cn(props.active &&
|
|
18
|
+
const className = cn(props.active && "shimmer", props.className);
|
|
23
19
|
if (props.as === "strong") {
|
|
24
20
|
return (
|
|
25
21
|
<strong aria-label={props["aria-label"]} className={className}>
|