@sentry/junior-dashboard 0.263.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.
Files changed (197) 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 +148 -135
  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 +9 -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/BottomSheet.tsx +26 -18
  14. package/src/client/components/CopyButton.tsx +83 -0
  15. package/src/client/components/DetailsPanel.tsx +133 -0
  16. package/src/client/components/Drawer.tsx +34 -33
  17. package/src/client/components/FilterBar.tsx +16 -31
  18. package/src/client/components/Metric.tsx +33 -23
  19. package/src/client/components/PageContentSkeleton.tsx +1 -1
  20. package/src/client/components/Pagination.tsx +1 -1
  21. package/src/client/components/ParticipantAvatarStack.tsx +40 -36
  22. package/src/client/components/ProfileMenu.tsx +27 -93
  23. package/src/client/components/README.md +101 -64
  24. package/src/client/components/SegmentedNav.tsx +1 -1
  25. package/src/client/components/ShimmerText.tsx +2 -6
  26. package/src/client/components/ThemeSwitcher.tsx +19 -15
  27. package/src/client/components/charts/ActivityChart.tsx +173 -288
  28. package/src/client/components/charts/CacheWrites.tsx +63 -50
  29. package/src/client/components/charts/InputCacheChart.tsx +137 -131
  30. package/src/client/components/charts/README.md +4 -4
  31. package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
  32. package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
  33. package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
  34. package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
  35. package/src/client/components/layout/DashboardHeader.tsx +44 -90
  36. package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
  37. package/src/client/components/layout/VisualViewportShell.tsx +2 -2
  38. package/src/client/components/ui/accordion.tsx +77 -0
  39. package/src/client/components/ui/alert-dialog.tsx +89 -0
  40. package/src/client/components/ui/button.tsx +85 -0
  41. package/src/client/components/ui/chart.tsx +74 -0
  42. package/src/client/components/ui/checkbox.tsx +26 -0
  43. package/src/client/components/ui/collapsible.tsx +22 -0
  44. package/src/client/components/ui/field.tsx +49 -0
  45. package/src/client/components/ui/input.tsx +35 -0
  46. package/src/client/components/ui/native-select.tsx +57 -0
  47. package/src/client/components/ui/popover.tsx +67 -0
  48. package/src/client/components/ui/radio-group.tsx +32 -0
  49. package/src/client/components/ui/sheet.tsx +151 -0
  50. package/src/client/components/ui/skeleton.tsx +19 -0
  51. package/src/client/components/ui/table.tsx +80 -0
  52. package/src/client/components/ui/tabs.tsx +75 -0
  53. package/src/client/components/ui/textarea.tsx +17 -0
  54. package/src/client/components/ui/toast.tsx +215 -0
  55. package/src/client/components/ui/toggle-group.tsx +36 -0
  56. package/src/client/components/ui/toggle.tsx +37 -0
  57. package/src/client/components/ui/tooltip.tsx +168 -0
  58. package/src/client/components/ui/trace.tsx +96 -0
  59. package/src/client/components/ui/useSheet.ts +74 -0
  60. package/src/client/conversations/ChatLayout.tsx +7 -3
  61. package/src/client/conversations/ConversationBrief.tsx +17 -6
  62. package/src/client/conversations/ConversationChildAgents.tsx +87 -58
  63. package/src/client/conversations/ConversationComposer.tsx +26 -12
  64. package/src/client/conversations/ConversationDetails.tsx +198 -95
  65. package/src/client/conversations/ConversationEventDetails.tsx +141 -0
  66. package/src/client/conversations/ConversationEventLog.tsx +150 -129
  67. package/src/client/conversations/ConversationHeader.tsx +8 -3
  68. package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
  69. package/src/client/conversations/ConversationHomeList.tsx +1 -1
  70. package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
  71. package/src/client/conversations/ConversationMeta.tsx +111 -92
  72. package/src/client/conversations/ConversationPage.tsx +241 -110
  73. package/src/client/conversations/ConversationSidebar.tsx +48 -29
  74. package/src/client/conversations/ConversationUsageBreakdown.tsx +16 -3
  75. package/src/client/conversations/ConversationWatches.tsx +165 -0
  76. package/src/client/conversations/ConversationWorkspace.tsx +12 -22
  77. package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
  78. package/src/client/conversations/DetailsList.tsx +124 -0
  79. package/src/client/conversations/EventDetails.tsx +42 -8
  80. package/src/client/conversations/ForkConversationButton.tsx +27 -16
  81. package/src/client/conversations/ObjectCard.tsx +20 -8
  82. package/src/client/conversations/PendingMailboxStack.tsx +52 -33
  83. package/src/client/conversations/README.md +93 -19
  84. package/src/client/conversations/ShareConversationButton.tsx +28 -3
  85. package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
  86. package/src/client/conversations/ToolFrame.tsx +36 -52
  87. package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
  88. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
  89. package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
  90. package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
  91. package/src/client/conversations/TranscriptLoading.tsx +1 -1
  92. package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
  93. package/src/client/conversations/TranscriptMessageContextView.tsx +8 -2
  94. package/src/client/conversations/TranscriptMessageView.tsx +6 -1
  95. package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
  96. package/src/client/conversations/TranscriptSubagentView.tsx +3 -3
  97. package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
  98. package/src/client/conversations/TranscriptToolView.tsx +1 -1
  99. package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
  100. package/src/client/conversations/TranscriptView.tsx +63 -27
  101. package/src/client/conversations/eventLog.ts +57 -0
  102. package/src/client/conversations/eventTime.ts +60 -0
  103. package/src/client/conversations/queries.ts +29 -0
  104. package/src/client/conversations/transcriptBottomPinning.ts +34 -0
  105. package/src/client/format.ts +5 -0
  106. package/src/client/lib/utils.ts +1 -0
  107. package/src/client/pages/ConnectionsPage.tsx +2 -2
  108. package/src/client/pages/PersonalTokensPage.tsx +4 -4
  109. package/src/client/pages/SettingsPage.tsx +9 -10
  110. package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
  111. package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
  112. package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
  113. package/src/client/pages/automations/AutomationDetails.tsx +193 -0
  114. package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
  115. package/src/client/pages/automations/AutomationEditor.tsx +41 -29
  116. package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
  117. package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
  118. package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
  119. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
  120. package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
  121. package/src/client/pages/automations/AutomationFilters.tsx +189 -74
  122. package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
  123. package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
  124. package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
  125. package/src/client/pages/automations/AutomationRow.tsx +12 -138
  126. package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
  127. package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
  128. package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
  129. package/src/client/pages/automations/AutomationStatus.tsx +201 -0
  130. package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
  131. package/src/client/pages/automations/AutomationsPage.tsx +80 -68
  132. package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
  133. package/src/client/pages/automations/README.md +57 -0
  134. package/src/client/pages/automations/TriggerFilters.tsx +22 -0
  135. package/src/client/pages/automations/automationFormat.ts +37 -23
  136. package/src/client/pages/code/CodeActivityChart.tsx +59 -101
  137. package/src/client/pages/code/CodePage.tsx +52 -55
  138. package/src/client/pages/code/RepositoryPage.tsx +6 -8
  139. package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
  140. package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
  141. package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
  142. package/src/client/pages/memory/MemoryDetails.tsx +218 -0
  143. package/src/client/pages/memory/MemoryPage.tsx +168 -112
  144. package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
  145. package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
  146. package/src/client/pages/memory/memoryRecord.ts +1 -1
  147. package/src/client/pages/people/ContributionGrid.tsx +23 -16
  148. package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
  149. package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
  150. package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
  151. package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
  152. package/src/client/pages/system/GuardianActivity.tsx +1 -2
  153. package/src/client/pages/system/PluginBarChart.tsx +106 -87
  154. package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
  155. package/src/client/pages/system/README.md +23 -0
  156. package/src/client/pages/system/SystemNavigation.tsx +5 -1
  157. package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
  158. package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
  159. package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
  160. package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
  161. package/src/client/pages/system/WorkspaceList.tsx +20 -30
  162. package/src/client/pages/system/WorkspacesPage.tsx +117 -119
  163. package/src/client/pages/system/sandboxSize.ts +8 -0
  164. package/src/client/pages/system/workspaceQueries.ts +53 -0
  165. package/src/client/pages/user/PluginUserPage.tsx +51 -2
  166. package/src/client/pages/user/pluginUserPageData.ts +1 -6
  167. package/src/client/styles.ts +4 -0
  168. package/src/client.tsx +7 -1
  169. package/src/mock-reporting/automation-collection.ts +4 -2
  170. package/src/mock-reporting/fixtures.ts +23 -7
  171. package/src/mock-reporting/routes.ts +12 -0
  172. package/src/mock-reporting/watches.ts +94 -0
  173. package/src/shell.ts +4 -9
  174. package/src/tailwind.css +77 -10
  175. package/src/client/components/Button.tsx +0 -118
  176. package/src/client/components/Field.tsx +0 -53
  177. package/src/client/components/SegmentedTabs.tsx +0 -119
  178. package/src/client/components/Select.tsx +0 -52
  179. package/src/client/components/Sheet.LICENSE +0 -21
  180. package/src/client/components/Sheet.tsx +0 -141
  181. package/src/client/components/Skeleton.tsx +0 -17
  182. package/src/client/components/TextInput.tsx +0 -58
  183. package/src/client/components/Tooltip.tsx +0 -216
  184. package/src/client/components/charts/useChartLayout.ts +0 -27
  185. package/src/client/conversations/ConversationDetailsFrame.tsx +0 -91
  186. package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
  187. package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
  188. package/src/client/pages/dev/ChartFixtures.tsx +0 -325
  189. package/src/client/pages/dev/ComponentsPage.tsx +0 -899
  190. package/src/client/pages/dev/ConversationFixture.tsx +0 -231
  191. package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
  192. package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
  193. package/src/client/pages/dev/SelectFixtures.tsx +0 -62
  194. package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
  195. package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
  196. package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
  197. package/src/client/pages/system/SnapshotSummary.tsx +0 -78
@@ -1,138 +1,241 @@
1
- import { ExternalLink } from "lucide-react";
2
1
  import { useState, type ReactNode } from "react";
3
2
 
4
- import { formatRelativeTime, formatTime } from "../format";
5
- import { ButtonLink } from "../components/Button";
6
- import { ConversationDetailsFrame } from "./ConversationDetailsFrame";
7
- import { SegmentedTabs } from "../components/SegmentedTabs";
3
+ import { DetailsPanel, useDetailsPanel } from "../components/DetailsPanel";
4
+ import {
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";
8
15
  import {
9
16
  ConversationChildAgents,
10
17
  useConversationChildAgents,
11
18
  } from "./ConversationChildAgents";
19
+ import {
20
+ ConversationLinkedWork,
21
+ type ConversationLink,
22
+ } from "./ConversationLinkedWork";
12
23
  import {
13
24
  ConversationMemories,
14
25
  useConversationMemories,
15
26
  } from "./ConversationMemories";
27
+ import {
28
+ ConversationWatches,
29
+ useConversationWatches,
30
+ } from "./ConversationWatches";
16
31
  import { ShareConversationButton } from "./ShareConversationButton";
17
32
  import type { SubagentTranscriptTarget } from "./SubagentTranscriptDrawer";
18
33
 
19
34
  type DetailsTab = "agents" | "summary" | "memories";
20
35
 
21
- /** Put conversation context before usage and diagnostic links. */
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
+
40
+ /**
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.
44
+ */
22
45
  export function ConversationDetails(props: {
23
- annotations: ReactNode;
24
46
  brief: ReactNode;
25
47
  conversationId: string;
26
48
  identity: ReactNode;
27
- lastActivityAt?: string;
49
+ links: ConversationLink[];
28
50
  onClose(): void;
29
- /** Open one child agent transcript. Details closes first. */
51
+ /** Open one child agent transcript. A details sheet closes first. */
30
52
  onOpenSubagent(target: SubagentTranscriptTarget): void;
53
+ overview: ReactNode;
31
54
  sentryConversationUrl?: string;
32
- stats: ReactNode;
55
+ usage: ReactNode;
33
56
  }) {
34
57
  const [tab, setTab] = useState<DetailsTab>("summary");
58
+ const panel = useDetailsPanel();
35
59
  // Load both lists with details so each tab can show its count.
36
60
  const memories = useConversationMemories(props.conversationId);
37
61
  const agents = useConversationChildAgents(props.conversationId);
38
- const tabs = [
39
- { label: "Summary", value: "summary" },
40
- {
41
- count: memories.data?.memories.length,
42
- label: "Memories",
43
- value: "memories",
44
- },
45
- { count: agents.data?.agents.length, label: "Subagents", value: "agents" },
46
- ] as const;
62
+ const watches = useConversationWatches(props.conversationId).data?.watches;
47
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.
48
66
  const sections = [
49
- { content: props.brief, title: undefined },
50
- { content: props.annotations, title: "Linked work" },
51
- { content: props.identity, title: "Participants" },
52
- { content: props.stats, title: "Usage" },
67
+ { content: props.identity, key: "participants", title: "Participants" },
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
+ },
85
+ { content: props.usage, key: "usage", title: "Usage" },
53
86
  ].filter((section) => section.content != null);
54
87
 
55
88
  return (
56
- <ConversationDetailsFrame onClose={props.onClose}>
57
- <SegmentedTabs
58
- scroll
59
- items={tabs}
60
- label="Conversation details"
61
- onChange={setTab}
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
+ }
99
+ actions={
100
+ <>
101
+ <ShareConversationButton
102
+ key={props.conversationId}
103
+ conversationId={props.conversationId}
104
+ layout="icon"
105
+ />
106
+ {props.sentryConversationUrl ? (
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>
125
+ ) : null}
126
+ </>
127
+ }
128
+ onClose={props.onClose}
129
+ >
130
+ <Tabs
131
+ className="h-full min-h-0 grid-rows-[auto_minmax(0,1fr)] gap-0"
132
+ onValueChange={(value: DetailsTab) => setTab(value)}
62
133
  value={tab}
63
134
  >
64
- {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">
65
177
  <ConversationMemories conversationId={props.conversationId} />
66
- ) : tab === "agents" ? (
178
+ </TabsContent>
179
+ <TabsContent className={detailsPanelClass} value="agents">
67
180
  <ConversationChildAgents
68
181
  conversationId={props.conversationId}
69
182
  onOpenAgent={(target) => {
70
- props.onClose();
183
+ // The panel stays beside the drawer. A modal sheet cannot.
184
+ if (!panel) props.onClose();
71
185
  props.onOpenSubagent(target);
72
186
  }}
73
187
  />
74
- ) : sections.length > 0 ? (
75
- <div className="grid min-w-0 gap-5">
76
- {sections.map((section) => (
77
- <section
78
- className="grid min-w-0 gap-3 border-b border-dashboard-border pb-5 last:border-0 last:pb-0"
79
- key={section.title ?? "summary"}
80
- >
81
- {section.title ? (
82
- <h3 className="m-0 text-sm font-semibold text-dashboard-text">
83
- {section.title}
84
- </h3>
85
- ) : null}
86
- <div className="min-w-0 break-words font-sans text-sm leading-relaxed text-dashboard-text-muted">
87
- {section.content}
88
- </div>
89
- </section>
90
- ))}
91
- </div>
92
- ) : (
93
- <p className="m-0 font-sans text-sm leading-relaxed text-dashboard-text-muted">
94
- No additional conversation details.
95
- </p>
96
- )}
97
- {tab === "summary" ? (
98
- <footer className="mt-5 grid gap-6 border-t border-dashboard-border pt-4 text-xs text-dashboard-text-muted">
99
- {props.lastActivityAt ? (
100
- <div className="flex flex-wrap items-baseline justify-between gap-2">
101
- <span>Last activity</span>
102
- <time
103
- dateTime={props.lastActivityAt}
104
- title={formatTime(props.lastActivityAt, {
105
- dateStyle: "medium",
106
- timeStyle: "short",
107
- })}
108
- >
109
- {formatRelativeTime(props.lastActivityAt)}
110
- </time>
111
- </div>
112
- ) : null}
113
- <div className="flex flex-wrap items-center gap-2">
114
- <ShareConversationButton
115
- key={props.conversationId}
116
- conversationId={props.conversationId}
117
- />
118
- {props.sentryConversationUrl ? (
119
- <ButtonLink
120
- className="w-fit focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
121
- to={props.sentryConversationUrl}
122
- rel="noopener noreferrer"
123
- target="_blank"
124
- >
125
- Open in Sentry
126
- <ExternalLink
127
- aria-hidden="true"
128
- className="size-3.5 shrink-0"
129
- />
130
- </ButtonLink>
131
- ) : null}
132
- </div>
133
- </footer>
134
- ) : null}
135
- </SegmentedTabs>
136
- </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>
201
+ );
202
+ }
203
+
204
+ /** Group one kind of conversation detail in a compact card. */
205
+ function DetailsSection(props: {
206
+ children: ReactNode;
207
+ count?: number;
208
+ title?: string;
209
+ }) {
210
+ return (
211
+ <section className="grid min-w-0 gap-2.5 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface-panel p-3">
212
+ {props.title ? (
213
+ <h3 className="m-0 flex items-center gap-1.5 font-sans text-xs font-medium text-dashboard-text-muted">
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}
220
+ </h3>
221
+ ) : null}
222
+ <div className="min-w-0 break-words font-sans text-xs leading-relaxed text-dashboard-text-muted">
223
+ {props.children}
224
+ </div>
225
+ </section>
226
+ );
227
+ }
228
+
229
+ /** Draw the Sentry glyph in the current text color, sized like a header icon. */
230
+ function SentryGlyph() {
231
+ return (
232
+ <svg
233
+ aria-hidden="true"
234
+ className="size-4"
235
+ fill="currentColor"
236
+ viewBox="0 0 24 24"
237
+ >
238
+ <path d="M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z" />
239
+ </svg>
137
240
  );
138
241
  }
@@ -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
+ }