@sentry/junior-dashboard 0.264.0 → 0.266.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +2 -5
  3. package/dist/app.js +119 -35
  4. package/dist/client.js +145 -136
  5. package/dist/index.d.ts +0 -1
  6. package/dist/index.js +119 -35
  7. package/dist/tailwind.css +1 -1
  8. package/package.json +5 -4
  9. package/src/api/schema.ts +0 -1
  10. package/src/app.ts +5 -20
  11. package/src/client/App.tsx +29 -31
  12. package/src/client/api.ts +7 -0
  13. package/src/client/components/ActorAvatar.tsx +10 -22
  14. package/src/client/components/BottomSheet.tsx +12 -2
  15. package/src/client/components/ConnectionBanner.tsx +4 -3
  16. package/src/client/components/CopyButton.tsx +82 -0
  17. package/src/client/components/DetailsPanel.tsx +133 -0
  18. package/src/client/components/Drawer.tsx +15 -24
  19. package/src/client/components/EmptyTelemetry.tsx +5 -3
  20. package/src/client/components/FilterBar.tsx +16 -31
  21. package/src/client/components/ImageAttachment.tsx +49 -63
  22. package/src/client/components/InlineError.tsx +8 -4
  23. package/src/client/components/Metric.tsx +33 -23
  24. package/src/client/components/Notice.tsx +19 -26
  25. package/src/client/components/PageContentSkeleton.tsx +1 -1
  26. package/src/client/components/Pagination.tsx +27 -27
  27. package/src/client/components/ParticipantAvatarStack.tsx +43 -39
  28. package/src/client/components/ProfileMenu.tsx +27 -93
  29. package/src/client/components/README.md +103 -78
  30. package/src/client/components/SearchInput.tsx +51 -41
  31. package/src/client/components/SegmentedNav.tsx +1 -1
  32. package/src/client/components/ShimmerText.tsx +2 -6
  33. package/src/client/components/ThemeSwitcher.tsx +19 -15
  34. package/src/client/components/VersionDriftBanner.tsx +9 -7
  35. package/src/client/components/charts/ActivityChart.tsx +173 -288
  36. package/src/client/components/charts/CacheWrites.tsx +59 -37
  37. package/src/client/components/charts/InputCacheChart.tsx +137 -131
  38. package/src/client/components/charts/README.md +4 -4
  39. package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
  40. package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
  41. package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
  42. package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
  43. package/src/client/components/layout/DashboardHeader.tsx +44 -90
  44. package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
  45. package/src/client/components/layout/VisualViewportShell.tsx +2 -2
  46. package/src/client/components/ui/accordion.tsx +77 -0
  47. package/src/client/components/ui/alert-dialog.tsx +89 -0
  48. package/src/client/components/ui/alert.tsx +64 -0
  49. package/src/client/components/ui/avatar.tsx +53 -0
  50. package/src/client/components/ui/button.tsx +86 -0
  51. package/src/client/components/ui/chart.tsx +74 -0
  52. package/src/client/components/ui/checkbox.tsx +26 -0
  53. package/src/client/components/ui/collapsible.tsx +22 -0
  54. package/src/client/components/ui/dialog.tsx +66 -0
  55. package/src/client/components/ui/empty.tsx +32 -0
  56. package/src/client/components/ui/field.tsx +49 -0
  57. package/src/client/components/ui/input-group.tsx +96 -0
  58. package/src/client/components/ui/input.tsx +35 -0
  59. package/src/client/components/ui/native-select.tsx +57 -0
  60. package/src/client/components/ui/pagination.tsx +60 -0
  61. package/src/client/components/ui/popover.tsx +67 -0
  62. package/src/client/components/ui/radio-group.tsx +32 -0
  63. package/src/client/components/ui/sheet.tsx +13 -3
  64. package/src/client/components/ui/skeleton.tsx +19 -0
  65. package/src/client/components/ui/spinner.tsx +21 -0
  66. package/src/client/components/ui/table.tsx +101 -0
  67. package/src/client/components/ui/tabs.tsx +75 -0
  68. package/src/client/components/ui/textarea.tsx +17 -0
  69. package/src/client/components/ui/toast.tsx +215 -0
  70. package/src/client/components/ui/toggle-group.tsx +36 -0
  71. package/src/client/components/ui/toggle.tsx +37 -0
  72. package/src/client/components/ui/tooltip.tsx +168 -0
  73. package/src/client/components/ui/trace.tsx +96 -0
  74. package/src/client/conversations/ChatLayout.tsx +7 -3
  75. package/src/client/conversations/ConversationBrief.tsx +17 -6
  76. package/src/client/conversations/ConversationChildAgents.tsx +5 -5
  77. package/src/client/conversations/ConversationComposer.tsx +26 -12
  78. package/src/client/conversations/ConversationDetails.tsx +147 -62
  79. package/src/client/conversations/ConversationEventDetails.tsx +141 -0
  80. package/src/client/conversations/ConversationEventLog.tsx +150 -129
  81. package/src/client/conversations/ConversationHeader.tsx +13 -7
  82. package/src/client/conversations/ConversationHeaderActions.tsx +97 -93
  83. package/src/client/conversations/ConversationHomeList.tsx +1 -1
  84. package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
  85. package/src/client/conversations/ConversationMeta.tsx +55 -88
  86. package/src/client/conversations/ConversationPage.tsx +220 -101
  87. package/src/client/conversations/ConversationSidebar.tsx +49 -29
  88. package/src/client/conversations/ConversationUsageBreakdown.tsx +108 -65
  89. package/src/client/conversations/ConversationWatches.tsx +173 -0
  90. package/src/client/conversations/ConversationWorkspace.tsx +12 -22
  91. package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
  92. package/src/client/conversations/DetailsList.tsx +139 -0
  93. package/src/client/conversations/EventDetails.tsx +42 -8
  94. package/src/client/conversations/EventUsage.tsx +99 -74
  95. package/src/client/conversations/ForkConversationButton.tsx +27 -16
  96. package/src/client/conversations/ObjectCard.tsx +20 -8
  97. package/src/client/conversations/PendingMailboxStack.tsx +52 -38
  98. package/src/client/conversations/README.md +89 -13
  99. package/src/client/conversations/ShareConversationButton.tsx +28 -17
  100. package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
  101. package/src/client/conversations/ToolFrame.tsx +36 -52
  102. package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
  103. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
  104. package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
  105. package/src/client/conversations/TranscriptEventView.tsx +35 -16
  106. package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
  107. package/src/client/conversations/TranscriptLoading.tsx +1 -1
  108. package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
  109. package/src/client/conversations/TranscriptMessageContextView.tsx +35 -18
  110. package/src/client/conversations/TranscriptMessageView.tsx +6 -1
  111. package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
  112. package/src/client/conversations/TranscriptStructuredEventView.tsx +25 -15
  113. package/src/client/conversations/TranscriptSummary.tsx +34 -2
  114. package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
  115. package/src/client/conversations/TranscriptToolView.tsx +1 -1
  116. package/src/client/conversations/TranscriptTurnContextView.tsx +144 -65
  117. package/src/client/conversations/TranscriptView.tsx +63 -27
  118. package/src/client/conversations/eventLog.ts +58 -0
  119. package/src/client/conversations/eventTime.ts +60 -0
  120. package/src/client/conversations/queries.ts +29 -0
  121. package/src/client/conversations/transcriptBottomPinning.ts +34 -0
  122. package/src/client/conversations/transcriptMentions.tsx +18 -5
  123. package/src/client/format.ts +5 -0
  124. package/src/client/pages/ConnectionsPage.tsx +2 -2
  125. package/src/client/pages/PersonalTokensPage.tsx +4 -4
  126. package/src/client/pages/SettingsPage.tsx +9 -10
  127. package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
  128. package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
  129. package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
  130. package/src/client/pages/automations/AutomationDetails.tsx +193 -0
  131. package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
  132. package/src/client/pages/automations/AutomationEditor.tsx +41 -29
  133. package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
  134. package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
  135. package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
  136. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
  137. package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
  138. package/src/client/pages/automations/AutomationFilters.tsx +189 -74
  139. package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
  140. package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
  141. package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
  142. package/src/client/pages/automations/AutomationRow.tsx +12 -138
  143. package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
  144. package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
  145. package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
  146. package/src/client/pages/automations/AutomationStatus.tsx +201 -0
  147. package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
  148. package/src/client/pages/automations/AutomationsPage.tsx +80 -68
  149. package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
  150. package/src/client/pages/automations/README.md +57 -0
  151. package/src/client/pages/automations/TriggerFilters.tsx +22 -0
  152. package/src/client/pages/automations/automationFormat.ts +37 -23
  153. package/src/client/pages/code/CodeActivityChart.tsx +59 -101
  154. package/src/client/pages/code/CodePage.tsx +52 -55
  155. package/src/client/pages/code/RepositoryPage.tsx +6 -8
  156. package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
  157. package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
  158. package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
  159. package/src/client/pages/memory/MemoryDetails.tsx +218 -0
  160. package/src/client/pages/memory/MemoryPage.tsx +168 -112
  161. package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
  162. package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
  163. package/src/client/pages/memory/memoryRecord.ts +1 -1
  164. package/src/client/pages/people/ContributionGrid.tsx +23 -16
  165. package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
  166. package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
  167. package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
  168. package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
  169. package/src/client/pages/system/GuardianActivity.tsx +1 -2
  170. package/src/client/pages/system/PluginBarChart.tsx +106 -87
  171. package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
  172. package/src/client/pages/system/README.md +23 -0
  173. package/src/client/pages/system/SystemNavigation.tsx +5 -1
  174. package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
  175. package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
  176. package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
  177. package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
  178. package/src/client/pages/system/WorkspaceList.tsx +20 -30
  179. package/src/client/pages/system/WorkspacesPage.tsx +117 -119
  180. package/src/client/pages/system/sandboxSize.ts +8 -0
  181. package/src/client/pages/system/workspaceQueries.ts +53 -0
  182. package/src/client/pages/user/PluginUserPage.tsx +51 -2
  183. package/src/client/pages/user/pluginUserPageData.ts +1 -6
  184. package/src/client/styles.ts +4 -0
  185. package/src/client.tsx +7 -1
  186. package/src/mock-reporting/automation-collection.ts +4 -2
  187. package/src/mock-reporting/fixtures.ts +23 -7
  188. package/src/mock-reporting/routes.ts +12 -0
  189. package/src/mock-reporting/watches.ts +94 -0
  190. package/src/shell.ts +4 -9
  191. package/src/tailwind.css +0 -10
  192. package/src/client/components/Button.tsx +0 -118
  193. package/src/client/components/Field.tsx +0 -53
  194. package/src/client/components/SegmentedTabs.tsx +0 -119
  195. package/src/client/components/Select.tsx +0 -52
  196. package/src/client/components/Skeleton.tsx +0 -17
  197. package/src/client/components/TextInput.tsx +0 -58
  198. package/src/client/components/Tooltip.tsx +0 -216
  199. package/src/client/components/charts/useChartLayout.ts +0 -27
  200. package/src/client/components/ui/sheet.LICENSE +0 -21
  201. package/src/client/conversations/ConversationDetailsFrame.tsx +0 -119
  202. package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
  203. package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
  204. package/src/client/pages/dev/ChartFixtures.tsx +0 -325
  205. package/src/client/pages/dev/ComponentsPage.tsx +0 -899
  206. package/src/client/pages/dev/ConversationFixture.tsx +0 -231
  207. package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
  208. package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
  209. package/src/client/pages/dev/SelectFixtures.tsx +0 -62
  210. package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
  211. package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
  212. package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
  213. package/src/client/pages/system/SnapshotSummary.tsx +0 -78
@@ -3,6 +3,7 @@ import type { ReactNode } from "react";
3
3
  import { cn } from "../styles";
4
4
  import { Card } from "./layout/Card";
5
5
  import { SearchInput } from "./SearchInput";
6
+ import { Tabs, TabsList, TabsTrigger } from "./ui/tabs";
6
7
 
7
8
  /** Lay out labeled filter groups and an optional search field. */
8
9
  export function FilterBar(props: {
@@ -69,43 +70,27 @@ export function FilterTabList(props: {
69
70
  value: string;
70
71
  }) {
71
72
  return (
72
- <div
73
- aria-label={props.ariaLabel}
74
- className="grid min-w-0 grid-cols-3 gap-1 border-b border-dashboard-border-subtle sm:flex sm:overflow-x-auto"
75
- role="tablist"
76
- >
77
- {props.items.map((item) => {
78
- const selected = props.value === item.value;
79
- return (
80
- <button
81
- aria-selected={selected}
82
- className={cn(
83
- "relative flex min-w-0 cursor-pointer items-center justify-between gap-2 border-0 bg-transparent px-3 py-2.5 font-sans font-medium text-xs transition-colors after:absolute after:inset-x-2 after:bottom-0 after:h-px sm:shrink-0 sm:justify-start",
84
- selected
85
- ? "text-dashboard-accent-text after:bg-dashboard-accent"
86
- : "text-dashboard-text-muted after:bg-transparent hover:text-dashboard-text",
87
- )}
73
+ <Tabs onValueChange={props.onChange} value={props.value}>
74
+ <TabsList
75
+ aria-label={props.ariaLabel}
76
+ className="grid grid-cols-3 sm:flex sm:overflow-x-auto"
77
+ variant="line"
78
+ >
79
+ {props.items.map((item) => (
80
+ <TabsTrigger
81
+ className="group/tab justify-between sm:shrink-0 sm:justify-start"
88
82
  key={item.value || "all"}
89
- onClick={() => props.onChange(item.value)}
90
- role="tab"
91
- type="button"
83
+ value={item.value}
92
84
  >
93
85
  {item.label}
94
86
  {item.count !== undefined ? (
95
- <span
96
- className={cn(
97
- "rounded-sm border px-1.5 py-0.5 text-xs",
98
- selected
99
- ? "border-dashboard-accent/20 bg-dashboard-accent/10 text-dashboard-accent-text"
100
- : "border-dashboard-border bg-dashboard-fill-soft text-dashboard-text-muted",
101
- )}
102
- >
87
+ <span className="rounded-sm border border-dashboard-border bg-dashboard-fill-soft px-1.5 py-0.5 text-xs text-dashboard-text-muted group-data-active/tab:border-dashboard-accent/20 group-data-active/tab:bg-dashboard-accent/10 group-data-active/tab:text-dashboard-accent-text">
103
88
  {item.count.toLocaleString("en-US")}
104
89
  </span>
105
90
  ) : null}
106
- </button>
107
- );
108
- })}
109
- </div>
91
+ </TabsTrigger>
92
+ ))}
93
+ </TabsList>
94
+ </Tabs>
110
95
  );
111
96
  }
@@ -1,17 +1,13 @@
1
1
  import { ExternalLink, X } from "lucide-react";
2
2
  import {
3
3
  type MouseEvent,
4
- useEffect,
5
- useId,
6
- useRef,
7
4
  useState,
8
5
  type ComponentPropsWithoutRef,
9
6
  } from "react";
10
7
 
11
- import {
12
- acquireBodyScrollLock,
13
- releaseBodyScrollLock,
14
- } from "../bodyScrollLock";
8
+ import { Button } from "./ui/button";
9
+ import { Dialog, DialogClose, DialogContent, DialogTitle } from "./ui/dialog";
10
+ import { useSheet } from "./ui/useSheet";
15
11
 
16
12
  /** Return true when an image link click should open the inline preview. */
17
13
  export function shouldPreviewImageAttachment(
@@ -45,9 +41,6 @@ export function ImageAttachment(
45
41
  triggerClassName?: string;
46
42
  } & Omit<ComponentPropsWithoutRef<"img">, "alt" | "className" | "src">,
47
43
  ) {
48
- const dialogRef = useRef<HTMLDialogElement>(null);
49
- const bodyScrollLockedRef = useRef(false);
50
- const titleId = useId();
51
44
  const [open, setOpen] = useState(false);
52
45
  const {
53
46
  context,
@@ -58,20 +51,6 @@ export function ImageAttachment(
58
51
  ...imageProps
59
52
  } = props;
60
53
 
61
- const unlockBodyScroll = () => {
62
- if (!bodyScrollLockedRef.current) return;
63
- releaseBodyScrollLock();
64
- bodyScrollLockedRef.current = false;
65
- };
66
-
67
- const close = () => {
68
- dialogRef.current?.close();
69
- setOpen(false);
70
- unlockBodyScroll();
71
- };
72
-
73
- useEffect(() => () => unlockBodyScroll(), []);
74
-
75
54
  return (
76
55
  <>
77
56
  <a
@@ -80,12 +59,7 @@ export function ImageAttachment(
80
59
  onClick={(event) => {
81
60
  if (!shouldPreviewImageAttachment(event)) return;
82
61
  event.preventDefault();
83
- const dialog = dialogRef.current;
84
- if (!dialog || dialog.open) return;
85
- acquireBodyScrollLock();
86
- bodyScrollLockedRef.current = true;
87
62
  setOpen(true);
88
- dialog.showModal();
89
63
  }}
90
64
  rel="noreferrer"
91
65
  target="_blank"
@@ -97,62 +71,74 @@ export function ImageAttachment(
97
71
  src={src}
98
72
  />
99
73
  </a>
100
- <dialog
101
- aria-labelledby={titleId}
102
- className="m-0 h-dvh max-h-none w-screen max-w-none overflow-hidden border-0 bg-dashboard-ink p-0 text-dashboard-text backdrop:bg-dashboard-overlay md:m-auto md:h-[min(90dvh,960px)] md:w-[min(92vw,1440px)] md:rounded-lg md:border md:border-dashboard-border-emphasis"
103
- onClick={(event) => {
104
- if (event.target === event.currentTarget) close();
105
- }}
106
- onClose={() => {
107
- setOpen(false);
108
- unlockBodyScroll();
109
- }}
110
- ref={dialogRef}
74
+ {open ? (
75
+ <ImagePreview
76
+ context={context}
77
+ filename={filename}
78
+ onClose={() => setOpen(false)}
79
+ src={src}
80
+ />
81
+ ) : null}
82
+ </>
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Show one image above the page. `useSheet` shares the document scroll lock
88
+ * with sheets and keeps Escape and backdrop dismissal on the front modal.
89
+ */
90
+ function ImagePreview(props: {
91
+ context?: string;
92
+ filename: string;
93
+ onClose(): void;
94
+ src: string;
95
+ }) {
96
+ const sheet = useSheet(props.onClose);
97
+ return (
98
+ <Dialog {...sheet.rootProps}>
99
+ <DialogContent
100
+ {...sheet.contentProps}
101
+ className="top-0 left-0 h-dvh w-screen max-w-none translate-x-0 translate-y-0 gap-0 overflow-hidden rounded-none border-0 bg-dashboard-ink p-0 shadow-none sm:max-w-none md:top-1/2 md:left-1/2 md:h-[min(90dvh,960px)] md:w-[min(92vw,1440px)] md:-translate-x-1/2 md:-translate-y-1/2 md:rounded-lg md:border"
111
102
  >
112
103
  <div className="grid h-full min-h-0 grid-rows-[auto_minmax(0,1fr)] bg-dashboard-bg-elevated">
113
104
  <header className="grid grid-cols-[minmax(0,1fr)_auto_auto] items-center gap-1.5 border-b border-dashboard-border-strong bg-dashboard-surface-raised px-3 py-2 pt-[max(0.5rem,env(safe-area-inset-top))] md:px-4">
114
105
  <div className="min-w-0">
115
- <div
116
- className="truncate font-mono text-xs text-dashboard-text"
117
- id={titleId}
118
- >
119
- {filename}
120
- </div>
121
- {context ? (
106
+ <DialogTitle className="truncate font-mono text-xs leading-normal font-normal">
107
+ {props.filename}
108
+ </DialogTitle>
109
+ {props.context ? (
122
110
  <div className="truncate font-mono text-2xs text-dashboard-text-muted">
123
- {context}
111
+ {props.context}
124
112
  </div>
125
113
  ) : null}
126
114
  </div>
127
115
  <a
128
116
  className="inline-flex min-h-10 items-center gap-1.5 rounded-md px-2.5 text-xs font-medium text-dashboard-text-muted no-underline transition-colors hover:bg-dashboard-fill-hover hover:text-dashboard-text"
129
- href={src}
117
+ href={props.src}
130
118
  rel="noreferrer"
131
119
  target="_blank"
132
120
  >
133
121
  <ExternalLink aria-hidden="true" size={14} />
134
122
  <span className="max-sm:sr-only">Open raw file</span>
135
123
  </a>
136
- <button
137
- className="inline-flex min-h-10 items-center gap-1.5 rounded-md px-2.5 text-xs font-medium text-dashboard-text-muted transition-colors hover:bg-dashboard-fill-hover hover:text-dashboard-text"
138
- onClick={close}
139
- type="button"
124
+ <DialogClose
125
+ render={
126
+ <Button className="h-10 gap-1.5 border-0 bg-transparent px-2.5 text-xs text-dashboard-text-muted hover:bg-dashboard-fill-hover hover:text-dashboard-text" />
127
+ }
140
128
  >
141
129
  <X aria-hidden="true" size={16} />
142
130
  <span>Close</span>
143
- </button>
131
+ </DialogClose>
144
132
  </header>
145
133
  <div className="grid min-h-0 place-items-center overflow-auto p-3 pb-[max(0.75rem,env(safe-area-inset-bottom))] md:p-6">
146
- {open ? (
147
- <img
148
- alt={filename}
149
- className="max-h-full max-w-full object-contain"
150
- src={src}
151
- />
152
- ) : null}
134
+ <img
135
+ alt={props.filename}
136
+ className="max-h-full max-w-full object-contain"
137
+ src={props.src}
138
+ />
153
139
  </div>
154
140
  </div>
155
- </dialog>
156
- </>
141
+ </DialogContent>
142
+ </Dialog>
157
143
  );
158
144
  }
@@ -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
- <p
12
- className={cn("m-0 text-sm text-rose-300", props.className)}
13
- role="alert"
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
- </p>
20
+ </Alert>
17
21
  );
18
22
  }
@@ -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 "./Tooltip";
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
- align={props.align}
90
- className={cn(
91
- "w-[calc(100vw-2rem)] max-w-80 rounded-lg border border-dashboard-border-emphasis bg-dashboard-bg text-left text-xs font-normal text-dashboard-text-muted shadow-xl shadow-black/35",
92
- props.tooltipCard ? "p-3 leading-snug" : "px-3 py-2 leading-relaxed",
93
- )}
94
- content={
95
- props.tooltipCard ? (
96
- <span className="block max-h-[min(30rem,var(--radix-hover-card-content-available-height,30rem))] overflow-y-auto">
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,9 @@
1
- import type { ButtonHTMLAttributes, ReactNode } from "react";
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 = ButtonHTMLAttributes<HTMLButtonElement> & {
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
- <div
33
- className={cn(
34
- "overflow-hidden rounded-xl border bg-dashboard-surface-raised shadow-[0_16px_40px_rgba(0,0,0,0.5)]",
35
- isError ? "border-rose-300/25" : "border-dashboard-border-strong",
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" role={isError ? "alert" : "status"}>
52
- <div
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
- </div>
56
+ </AlertTitle>
61
57
  {detail ? (
62
- <div
58
+ <AlertDescription
63
59
  className={cn(
64
60
  "mt-0.5 truncate font-sans text-xs",
65
- isError ? "text-rose-100/70" : "text-dashboard-text-muted",
61
+ isError && "text-rose-100/70",
66
62
  )}
67
63
  >
68
64
  {detail}
69
- </div>
65
+ </AlertDescription>
70
66
  ) : null}
71
67
  </div>
72
68
  {action}
73
69
  </div>
74
70
  {children}
75
- </div>
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
- <button
83
+ <Button
89
84
  {...props}
90
85
  className={cn(
91
- "shrink-0 cursor-pointer px-3 py-1.5 text-xs transition focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-40",
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
- ? "rounded-lg bg-rose-200/15 font-sans font-medium text-rose-50 hover:bg-rose-200/25"
95
- : "rounded-lg bg-dashboard-accent/10 font-sans font-medium text-dashboard-accent-text hover:bg-dashboard-accent/20",
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,7 @@
1
1
  import type { ReactNode } from "react";
2
2
 
3
3
  import { Card } from "./layout/Card";
4
- import { Skeleton } from "./Skeleton";
4
+ import { Skeleton } from "./ui/skeleton";
5
5
  import { cn } from "../styles";
6
6
 
7
7
  export type PageContentSkeletonVariant =
@@ -1,7 +1,12 @@
1
- import { ChevronLeft, ChevronRight } from "lucide-react";
2
-
3
1
  import { cn } from "../styles";
4
- import { Button } from "./Button";
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
- <div
58
- aria-label="Pagination"
62
+ <Pagination
59
63
  className={cn(
60
- "flex flex-wrap items-center justify-between gap-3",
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
- <div className="flex items-center gap-2">
69
- <Button
70
- aria-label="Previous page"
71
- disabled={current <= 1}
72
- onClick={() => props.onPageChange(current - 1)}
73
- size="icon"
74
- >
75
- <ChevronLeft aria-hidden="true" size={16} />
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
- </span>
80
- <Button
81
- aria-label="Next page"
82
- disabled={current >= props.pageCount}
83
- onClick={() => props.onPageChange(current + 1)}
84
- size="icon"
85
- >
86
- <ChevronRight aria-hidden="true" size={16} />
87
- </Button>
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,10 +1,10 @@
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";
7
- import { Tooltip } from "./Tooltip";
6
+ import { Avatar, AvatarFallback } from "./ui/avatar";
7
+ import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
8
8
 
9
9
  const MAX_VISIBLE_PARTICIPANTS = 3;
10
10
 
@@ -51,28 +51,31 @@ function ParticipantTooltipContent(props: { participant: ActorIdentity }) {
51
51
  );
52
52
  }
53
53
 
54
- function Avatar(props: {
54
+ function ParticipantAvatar(props: {
55
55
  participant: ActorIdentity;
56
56
  size: ParticipantAvatarStackProps["size"];
57
57
  }) {
58
58
  const label = participantDescription(props.participant);
59
59
  return (
60
- <Tooltip
61
- content={<ParticipantTooltipContent participant={props.participant} />}
62
- focusable
63
- triggerClassName="-ml-1 first:ml-0 pointer-events-none [@media(hover:hover)_and_(pointer:fine)]:pointer-events-auto"
64
- >
65
- <span
66
- aria-label={label}
67
- className="relative inline-flex rounded-full focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus"
68
- onKeyDown={(event) => event.stopPropagation()}
69
- tabIndex={0}
70
- >
71
- <ActorAvatar
72
- name={participantName(props.participant)}
73
- size={props.size}
74
- />
75
- </span>
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
  }
@@ -98,15 +101,32 @@ export function ParticipantAvatarStack(props: ParticipantAvatarStackProps) {
98
101
  role="group"
99
102
  >
100
103
  {visible.map((participant, index) => (
101
- <Avatar
104
+ <ParticipantAvatar
102
105
  key={participantKey(participant, index)}
103
106
  participant={participant}
104
107
  size={props.size}
105
108
  />
106
109
  ))}
107
110
  {hidden.length > 0 ? (
108
- <Tooltip
109
- content={
111
+ <Tooltip>
112
+ <TooltipTrigger
113
+ render={
114
+ <Avatar
115
+ aria-label={`${hidden.length} more participants: ${hidden
116
+ .map(participantDescription)
117
+ .join("; ")}`}
118
+ 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"
119
+ onKeyDown={(event) => event.stopPropagation()}
120
+ size={props.size === "list" ? "sm" : "default"}
121
+ tabIndex={0}
122
+ >
123
+ <AvatarFallback className="tabular-nums tracking-normal text-dashboard-text-muted">
124
+ +{hidden.length}
125
+ </AvatarFallback>
126
+ </Avatar>
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>