@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
@@ -1,34 +1,52 @@
1
1
  import { useState, type ReactNode } from "react";
2
2
 
3
- import { IconButtonTooltip } from "../components/Tooltip";
3
+ import { DetailsPanel, useDetailsPanel } from "../components/DetailsPanel";
4
4
  import {
5
- ConversationDetailsFrame,
6
- useConversationDetailsPanel,
7
- } from "./ConversationDetailsFrame";
8
- import { SegmentedTabs } from "../components/SegmentedTabs";
5
+ Tooltip,
6
+ TooltipContent,
7
+ TooltipTrigger,
8
+ } from "../components/ui/tooltip";
9
+ import {
10
+ Tabs,
11
+ TabsContent,
12
+ TabsList,
13
+ TabsTrigger,
14
+ } from "../components/ui/tabs";
9
15
  import {
10
16
  ConversationChildAgents,
11
17
  useConversationChildAgents,
12
18
  } from "./ConversationChildAgents";
19
+ import {
20
+ ConversationLinkedWork,
21
+ type ConversationLink,
22
+ } from "./ConversationLinkedWork";
13
23
  import {
14
24
  ConversationMemories,
15
25
  useConversationMemories,
16
26
  } from "./ConversationMemories";
27
+ import {
28
+ ConversationWatches,
29
+ useConversationWatches,
30
+ } from "./ConversationWatches";
17
31
  import { ShareConversationButton } from "./ShareConversationButton";
18
32
  import type { SubagentTranscriptTarget } from "./SubagentTranscriptDrawer";
19
33
 
20
34
  type DetailsTab = "agents" | "summary" | "memories";
21
35
 
36
+ /** Keep the tabs fixed while each panel scrolls to the frame edge. */
37
+ const detailsPanelClass =
38
+ "min-h-0 overflow-y-auto overscroll-contain rounded-none px-4 pb-[max(1rem,env(safe-area-inset-bottom))] focus-visible:-outline-offset-2";
39
+
22
40
  /**
23
- * Show conversation details: the brief and facts first, then linked work,
24
- * participants, and the usage breakdown. Share and Sentry links stay in the
25
- * header so every tab can reach them.
41
+ * Show conversation details: the brief, participants, and run facts first,
42
+ * then watches, linked work, and the usage breakdown. Share and Sentry links
43
+ * stay in the header so every tab can reach them.
26
44
  */
27
45
  export function ConversationDetails(props: {
28
- annotations: ReactNode;
29
46
  brief: ReactNode;
30
47
  conversationId: string;
31
48
  identity: ReactNode;
49
+ links: ConversationLink[];
32
50
  onClose(): void;
33
51
  /** Open one child agent transcript. A details sheet closes first. */
34
52
  onOpenSubagent(target: SubagentTranscriptTarget): void;
@@ -37,29 +55,47 @@ export function ConversationDetails(props: {
37
55
  usage: ReactNode;
38
56
  }) {
39
57
  const [tab, setTab] = useState<DetailsTab>("summary");
40
- const panel = useConversationDetailsPanel();
58
+ const panel = useDetailsPanel();
41
59
  // Load both lists with details so each tab can show its count.
42
60
  const memories = useConversationMemories(props.conversationId);
43
61
  const agents = useConversationChildAgents(props.conversationId);
44
- const tabs = [
45
- { label: "Summary", value: "summary" },
46
- {
47
- count: memories.data?.memories.length,
48
- label: "Memories",
49
- value: "memories",
50
- },
51
- { count: agents.data?.agents.length, label: "Subagents", value: "agents" },
52
- ] as const;
62
+ const watches = useConversationWatches(props.conversationId).data?.watches;
53
63
 
64
+ // People first, then run facts. Watches come before linked work: they say
65
+ // what can wake this agent later, so readers see them without another tab.
54
66
  const sections = [
55
- { content: props.overview, key: "overview", title: undefined },
56
67
  { content: props.identity, key: "participants", title: "Participants" },
57
- { content: props.annotations, key: "linked-work", title: "Linked work" },
68
+ { content: props.overview, key: "overview", title: undefined },
69
+ {
70
+ content: watches?.length ? (
71
+ <ConversationWatches watches={watches} />
72
+ ) : null,
73
+ count: watches?.length,
74
+ key: "watching",
75
+ title: "Watching",
76
+ },
77
+ {
78
+ content: props.links.length ? (
79
+ <ConversationLinkedWork links={props.links} />
80
+ ) : null,
81
+ count: props.links.length,
82
+ key: "linked-work",
83
+ title: "Linked work",
84
+ },
58
85
  { content: props.usage, key: "usage", title: "Usage" },
59
86
  ].filter((section) => section.content != null);
60
87
 
61
88
  return (
62
- <ConversationDetailsFrame
89
+ <DetailsPanel
90
+ label="Conversation details"
91
+ title={
92
+ <h2
93
+ aria-label="Conversation details"
94
+ className="m-0 text-base font-semibold text-dashboard-text"
95
+ >
96
+ Details
97
+ </h2>
98
+ }
63
99
  actions={
64
100
  <>
65
101
  <ShareConversationButton
@@ -68,32 +104,79 @@ export function ConversationDetails(props: {
68
104
  layout="icon"
69
105
  />
70
106
  {props.sentryConversationUrl ? (
71
- <IconButtonTooltip label="Open in Sentry" placement="below">
72
- <a
73
- aria-label="Open in Sentry"
74
- className="grid size-9 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"
75
- href={props.sentryConversationUrl}
76
- rel="noopener noreferrer"
77
- target="_blank"
78
- >
79
- <SentryGlyph />
80
- </a>
81
- </IconButtonTooltip>
107
+ <Tooltip>
108
+ <TooltipTrigger
109
+ render={
110
+ <a
111
+ aria-label="Open in Sentry"
112
+ className="grid size-9 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"
113
+ href={props.sentryConversationUrl}
114
+ rel="noopener noreferrer"
115
+ target="_blank"
116
+ >
117
+ <SentryGlyph />
118
+ </a>
119
+ }
120
+ />
121
+ <TooltipContent size="compact" side="bottom">
122
+ Open in Sentry
123
+ </TooltipContent>
124
+ </Tooltip>
82
125
  ) : null}
83
126
  </>
84
127
  }
85
128
  onClose={props.onClose}
86
129
  >
87
- <SegmentedTabs
88
- scroll
89
- items={tabs}
90
- label="Conversation details"
91
- onChange={setTab}
130
+ <Tabs
131
+ className="h-full min-h-0 grid-rows-[auto_minmax(0,1fr)] gap-0"
132
+ onValueChange={(value: DetailsTab) => setTab(value)}
92
133
  value={tab}
93
134
  >
94
- {tab === "memories" ? (
135
+ <TabsList
136
+ activateOnFocus
137
+ aria-label="Conversation details"
138
+ className="m-4"
139
+ >
140
+ <TabsTrigger value="summary">
141
+ <span className="truncate">Summary</span>
142
+ </TabsTrigger>
143
+ <TabsTrigger value="memories">
144
+ <span className="truncate">Memories</span>
145
+ <TabCount count={memories.data?.memories.length} />
146
+ </TabsTrigger>
147
+ <TabsTrigger value="agents">
148
+ <span className="truncate">Subagents</span>
149
+ <TabCount count={agents.data?.agents.length} />
150
+ </TabsTrigger>
151
+ </TabsList>
152
+ <TabsContent className={detailsPanelClass} value="summary">
153
+ {props.brief != null || sections.length > 0 ? (
154
+ <div className="grid min-w-0 gap-3">
155
+ {props.brief != null ? (
156
+ <div className="min-w-0 break-words px-1 pb-1 font-sans text-sm leading-relaxed text-dashboard-text-muted">
157
+ {props.brief}
158
+ </div>
159
+ ) : null}
160
+ {sections.map((section) => (
161
+ <DetailsSection
162
+ count={section.count}
163
+ key={section.key}
164
+ title={section.title}
165
+ >
166
+ {section.content}
167
+ </DetailsSection>
168
+ ))}
169
+ </div>
170
+ ) : (
171
+ <p className="m-0 font-sans text-sm leading-relaxed text-dashboard-text-muted">
172
+ No additional conversation details.
173
+ </p>
174
+ )}
175
+ </TabsContent>
176
+ <TabsContent className={detailsPanelClass} value="memories">
95
177
  <ConversationMemories conversationId={props.conversationId} />
96
- ) : tab === "agents" ? (
178
+ </TabsContent>
179
+ <TabsContent className={detailsPanelClass} value="agents">
97
180
  <ConversationChildAgents
98
181
  conversationId={props.conversationId}
99
182
  onOpenAgent={(target) => {
@@ -102,36 +185,38 @@ export function ConversationDetails(props: {
102
185
  props.onOpenSubagent(target);
103
186
  }}
104
187
  />
105
- ) : props.brief != null || sections.length > 0 ? (
106
- <div className="grid min-w-0 gap-3">
107
- {props.brief != null ? (
108
- <div className="min-w-0 break-words px-1 pb-1 font-sans text-sm leading-relaxed text-dashboard-text-muted">
109
- {props.brief}
110
- </div>
111
- ) : null}
112
- {sections.map((section) => (
113
- <DetailsSection key={section.key} title={section.title}>
114
- {section.content}
115
- </DetailsSection>
116
- ))}
117
- </div>
118
- ) : (
119
- <p className="m-0 font-sans text-sm leading-relaxed text-dashboard-text-muted">
120
- No additional conversation details.
121
- </p>
122
- )}
123
- </SegmentedTabs>
124
- </ConversationDetailsFrame>
188
+ </TabsContent>
189
+ </Tabs>
190
+ </DetailsPanel>
191
+ );
192
+ }
193
+
194
+ /** Show a panel's entry count. Zero and unknown counts are hidden. */
195
+ function TabCount(props: { count: number | undefined }) {
196
+ if (!props.count) return null;
197
+ return (
198
+ <span className="min-w-5 shrink-0 rounded-full bg-dashboard-fill-strong px-1.5 text-center text-xs font-semibold leading-5 tabular-nums">
199
+ {props.count.toLocaleString("en-US")}
200
+ </span>
125
201
  );
126
202
  }
127
203
 
128
204
  /** Group one kind of conversation detail in a compact card. */
129
- function DetailsSection(props: { children: ReactNode; title?: string }) {
205
+ function DetailsSection(props: {
206
+ children: ReactNode;
207
+ count?: number;
208
+ title?: string;
209
+ }) {
130
210
  return (
131
211
  <section className="grid min-w-0 gap-2.5 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface-panel p-3">
132
212
  {props.title ? (
133
- <h3 className="m-0 font-sans text-xs font-medium text-dashboard-text-muted">
213
+ <h3 className="m-0 flex items-center gap-1.5 font-sans text-xs font-medium text-dashboard-text-muted">
134
214
  {props.title}
215
+ {props.count ? (
216
+ <span className="tabular-nums text-dashboard-text-faint">
217
+ {props.count.toLocaleString("en-US")}
218
+ </span>
219
+ ) : null}
135
220
  </h3>
136
221
  ) : null}
137
222
  <div className="min-w-0 break-words font-sans text-xs leading-relaxed text-dashboard-text-muted">
@@ -0,0 +1,141 @@
1
+ import type { ConversationReportEvent } from "@sentry/junior/api/schema";
2
+ import { ChevronDown, ChevronUp } from "lucide-react";
3
+
4
+ import { cn } from "../styles";
5
+ import { DetailsPanel } from "../components/DetailsPanel";
6
+ import { Button } from "../components/ui/button";
7
+ import { Skeleton } from "../components/ui/skeleton";
8
+ import type { ConversationTranscript } from "../types";
9
+ import { useEventTime } from "./eventTime";
10
+ import { EventDetails } from "./EventDetails";
11
+ import { ForkConversationButton } from "./ForkConversationButton";
12
+
13
+ // Step buttons keep focus at the ends of the log, so style the ARIA state.
14
+ const stepButtonClass =
15
+ "data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:!bg-transparent data-disabled:hover:!text-dashboard-text-muted";
16
+
17
+ /**
18
+ * Show the selected event without blocking the main conversation's event log.
19
+ * Without an event, show a loading state while a linked event loads.
20
+ * `previous` and `next` are the adjacent visible rows of the event log.
21
+ */
22
+ export function ConversationEventDetails(props: {
23
+ conversation?: ConversationTranscript;
24
+ event?: ConversationReportEvent;
25
+ next?: number;
26
+ onClose(): void;
27
+ onSelect?(seq: number): void;
28
+ overlay?: boolean;
29
+ previous?: number;
30
+ }) {
31
+ const { conversation, event, onSelect } = props;
32
+ const time = useEventTime();
33
+ const timestamp = event ? (
34
+ <time
35
+ className="block break-words font-mono text-xs text-dashboard-text-muted"
36
+ dateTime={event.createdAt}
37
+ title={event.createdAt}
38
+ >
39
+ {time.formatTimestamp(event.createdAt)}
40
+ </time>
41
+ ) : null;
42
+ const title = event ? (
43
+ <>
44
+ <h2 className="m-0 mb-1 break-words font-mono text-sm font-semibold text-dashboard-text">
45
+ {event.data.type}
46
+ </h2>
47
+ {timestamp}
48
+ </>
49
+ ) : (
50
+ <div className="grid gap-2 py-0.5">
51
+ <Skeleton className="h-3.5 w-28" />
52
+ <Skeleton className="h-3 w-40" />
53
+ </div>
54
+ );
55
+ const actions =
56
+ onSelect && event ? (
57
+ <>
58
+ <Button
59
+ aria-label="Previous event"
60
+ className={stepButtonClass}
61
+ disabled={props.previous === undefined}
62
+ focusableWhenDisabled
63
+ onClick={() => {
64
+ if (props.previous !== undefined) onSelect(props.previous);
65
+ }}
66
+ size="icon"
67
+ title="Previous event"
68
+ >
69
+ <ChevronUp aria-hidden="true" size={15} />
70
+ </Button>
71
+ <Button
72
+ aria-label="Next event"
73
+ className={stepButtonClass}
74
+ disabled={props.next === undefined}
75
+ focusableWhenDisabled
76
+ onClick={() => {
77
+ if (props.next !== undefined) onSelect(props.next);
78
+ }}
79
+ size="icon"
80
+ title="Next event"
81
+ >
82
+ <ChevronDown aria-hidden="true" size={15} />
83
+ </Button>
84
+ </>
85
+ ) : undefined;
86
+
87
+ return (
88
+ <DetailsPanel
89
+ actions={actions}
90
+ label="Event details"
91
+ onClose={props.onClose}
92
+ overlay={props.overlay}
93
+ sheetTitle={event?.data.type}
94
+ title={title}
95
+ >
96
+ {/* Own the scroll here, so a new event starts at the top with closed
97
+ disclosures, and the sheet actions stay below the content. */}
98
+ <div
99
+ className={cn(
100
+ "h-full min-w-0 overflow-y-auto overscroll-contain p-4 pb-[max(1rem,env(safe-area-inset-bottom))]",
101
+ props.overlay && "xl:p-0",
102
+ )}
103
+ key={event?.seq ?? "loading"}
104
+ >
105
+ {event && conversation ? (
106
+ <>
107
+ <div className="mb-4 xl:hidden">{timestamp}</div>
108
+ {conversation.canFork &&
109
+ event.data.type === "message" &&
110
+ event.data.role === "assistant" &&
111
+ !event.data.redacted ? (
112
+ <div className="mb-3 flex items-center gap-2 text-sm">
113
+ <ForkConversationButton
114
+ conversationId={conversation.conversationId}
115
+ messageId={event.data.messageId}
116
+ />
117
+ <span>Fork after this reply</span>
118
+ </div>
119
+ ) : null}
120
+ <EventDetails event={event} />
121
+ </>
122
+ ) : (
123
+ <EventDetailsLoading />
124
+ )}
125
+ </div>
126
+ </DetailsPanel>
127
+ );
128
+ }
129
+
130
+ /**
131
+ * Keep the body quiet while the event loads. The header placeholders carry the
132
+ * state, so the panel does not compete with the log skeleton.
133
+ */
134
+ function EventDetailsLoading() {
135
+ return (
136
+ <div aria-busy="true" role="status">
137
+ <span className="sr-only">Loading event</span>
138
+ <Skeleton className="h-3 w-40 xl:hidden" />
139
+ </div>
140
+ );
141
+ }