@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
@@ -13,6 +13,16 @@ import {
13
13
  totalConversationCost,
14
14
  } from "../format";
15
15
  import { ShimmerText } from "../components/ShimmerText";
16
+ import {
17
+ Table,
18
+ TableBody,
19
+ TableCell,
20
+ TableFooter,
21
+ TableHead,
22
+ TableHeader,
23
+ TableRow,
24
+ } from "../components/ui/table";
25
+ import { cn } from "../styles";
16
26
  import type { Conversation } from "../types";
17
27
  import { liveModelId } from "./ConversationMeta";
18
28
  import {
@@ -70,7 +80,7 @@ function tokenTypeParts(
70
80
  return parts.length ? parts : undefined;
71
81
  }
72
82
 
73
- const CELL = "text-right align-top font-sans tabular-nums";
83
+ const CELL = "px-0 text-right align-top font-sans tabular-nums";
74
84
 
75
85
  /**
76
86
  * One usage table. Every table in the breakdown shares column widths, so the
@@ -85,85 +95,118 @@ function BreakdownTable(props: {
85
95
  total?: { cost: string; count: string; label: string };
86
96
  }) {
87
97
  return (
88
- <div className="min-w-0 overflow-x-auto">
89
- <table
90
- aria-label={props.label}
91
- className="w-full min-w-72 table-fixed border-collapse text-xs leading-relaxed"
92
- >
93
- <colgroup>
94
- <col />
95
- <col className="w-20" />
96
- <col className="w-20" />
97
- </colgroup>
98
- <thead>
99
- <tr className="border-b border-dashboard-border text-dashboard-text-muted">
100
- <th scope="col" className="py-2 text-left font-normal">
101
- {props.label}
102
- </th>
103
- <th scope="col" className="py-2 text-right font-normal">
104
- {props.countLabel}
105
- </th>
106
- <th scope="col" className="py-2 text-right font-normal">
107
- Cost
108
- </th>
109
- </tr>
110
- </thead>
111
- {props.rows.map((row) => (
112
- <tbody
98
+ <Table
99
+ aria-label={props.label}
100
+ className="min-w-72 table-fixed text-xs leading-relaxed"
101
+ >
102
+ <colgroup>
103
+ <col />
104
+ <col className="w-20" />
105
+ <col className="w-20" />
106
+ </colgroup>
107
+ <TableHeader>
108
+ <TableRow className="border-dashboard-border">
109
+ <TableHead
110
+ scope="col"
111
+ className="px-0 py-2 leading-relaxed font-normal"
112
+ >
113
+ {props.label}
114
+ </TableHead>
115
+ <TableHead
116
+ scope="col"
117
+ className="px-0 py-2 text-right leading-relaxed font-normal"
118
+ >
119
+ {props.countLabel}
120
+ </TableHead>
121
+ <TableHead
122
+ scope="col"
123
+ className="px-0 py-2 text-right leading-relaxed font-normal"
124
+ >
125
+ Cost
126
+ </TableHead>
127
+ </TableRow>
128
+ </TableHeader>
129
+ {props.rows.map((row) => {
130
+ const padding = row.parts ? "pt-2 pb-0.5" : "py-2";
131
+ return (
132
+ <TableBody
113
133
  className="border-b border-dashboard-border-subtle last-of-type:border-b-0"
114
134
  key={row.key}
115
135
  >
116
- <tr className="text-dashboard-text">
117
- <th
136
+ {/* The tbody draws the group border, so its rows have none. */}
137
+ <TableRow className="border-0">
138
+ <TableHead
118
139
  scope="row"
119
- className={`pr-3 text-left font-normal ${row.parts ? "pt-2 pb-0.5" : "py-2"}`}
140
+ className={cn(
141
+ "pl-0 pr-3 align-top leading-relaxed font-normal text-dashboard-text",
142
+ padding,
143
+ )}
120
144
  >
121
145
  <span className="block truncate">{row.label}</span>
122
- </th>
123
- <td className={`${CELL} ${row.parts ? "pt-2 pb-0.5" : "py-2"}`}>
124
- {row.count}
125
- </td>
126
- <td className={`${CELL} ${row.parts ? "pt-2 pb-0.5" : "py-2"}`}>
127
- {row.cost}
128
- </td>
129
- </tr>
146
+ </TableHead>
147
+ <TableCell className={cn(CELL, padding)}>{row.count}</TableCell>
148
+ <TableCell className={cn(CELL, padding)}>{row.cost}</TableCell>
149
+ </TableRow>
130
150
  {row.parts?.map((part, index, parts) => {
131
- const padding = index === parts.length - 1 ? "pb-2" : "";
151
+ const partPadding =
152
+ index === parts.length - 1 ? "pt-0 pb-2" : "py-0";
132
153
  return (
133
- <tr
134
- className="text-2xs leading-5 text-dashboard-text-faint"
154
+ <TableRow
155
+ className="border-0 text-2xs leading-5"
135
156
  key={part.label}
136
157
  >
137
- <th
158
+ <TableHead
138
159
  scope="row"
139
- className={`truncate pl-3 pr-3 text-left font-normal ${padding}`}
160
+ className={cn(
161
+ "truncate pl-3 pr-3 align-top text-2xs leading-5 font-normal text-dashboard-text-faint",
162
+ partPadding,
163
+ )}
140
164
  >
141
165
  {part.label}
142
- </th>
143
- <td className={`${CELL} ${padding}`}>{part.count}</td>
144
- <td className={`${CELL} ${padding}`}>{part.cost}</td>
145
- </tr>
166
+ </TableHead>
167
+ <TableCell
168
+ className={cn(
169
+ CELL,
170
+ "text-dashboard-text-faint",
171
+ partPadding,
172
+ )}
173
+ >
174
+ {part.count}
175
+ </TableCell>
176
+ <TableCell
177
+ className={cn(
178
+ CELL,
179
+ "text-dashboard-text-faint",
180
+ partPadding,
181
+ )}
182
+ >
183
+ {part.cost}
184
+ </TableCell>
185
+ </TableRow>
146
186
  );
147
187
  })}
148
- </tbody>
149
- ))}
150
- {props.total ? (
151
- <tfoot>
152
- <tr className="border-t border-dashboard-border font-semibold text-dashboard-text">
153
- <th scope="row" className="py-2 text-left font-semibold">
154
- {props.total.label}
155
- </th>
156
- <td className="py-2 text-right font-sans tabular-nums">
157
- {props.total.count}
158
- </td>
159
- <td className="py-2 text-right font-sans tabular-nums">
160
- {props.total.cost}
161
- </td>
162
- </tr>
163
- </tfoot>
164
- ) : null}
165
- </table>
166
- </div>
188
+ </TableBody>
189
+ );
190
+ })}
191
+ {props.total ? (
192
+ <TableFooter className="font-semibold">
193
+ <TableRow>
194
+ <TableHead
195
+ scope="row"
196
+ className="px-0 py-2 leading-relaxed font-semibold text-dashboard-text"
197
+ >
198
+ {props.total.label}
199
+ </TableHead>
200
+ <TableCell className="px-0 py-2 text-right font-sans tabular-nums">
201
+ {props.total.count}
202
+ </TableCell>
203
+ <TableCell className="px-0 py-2 text-right font-sans tabular-nums">
204
+ {props.total.cost}
205
+ </TableCell>
206
+ </TableRow>
207
+ </TableFooter>
208
+ ) : null}
209
+ </Table>
167
210
  );
168
211
  }
169
212
 
@@ -0,0 +1,173 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+ import {
3
+ Bot,
4
+ ChevronRight,
5
+ CircleDot,
6
+ Eye,
7
+ FolderGit2,
8
+ GitPullRequest,
9
+ Hash,
10
+ LockKeyhole,
11
+ type LucideIcon,
12
+ Package,
13
+ Rocket,
14
+ Tag,
15
+ Timer,
16
+ Users,
17
+ } from "lucide-react";
18
+ import { useState, type ReactNode } from "react";
19
+ import {
20
+ conversationWatchesReportSchema,
21
+ type ConversationWatch,
22
+ } from "@sentry/junior/api/schema";
23
+
24
+ import { formatTime } from "../format";
25
+ import { fetchDashboardJson } from "../http";
26
+ import { dashboardDisclosureChevronClass } from "../styles";
27
+ import { DetailsList, DetailsRow } from "./DetailsList";
28
+
29
+ /** Load the active Watches that one Conversation owns. */
30
+ export function useConversationWatches(conversationId: string) {
31
+ return useQuery({
32
+ queryFn: ({ signal }) =>
33
+ fetchDashboardJson(
34
+ conversationWatchesReportSchema,
35
+ `/api/conversations/${encodeURIComponent(conversationId)}/watches`,
36
+ signal,
37
+ ),
38
+ queryKey: ["conversation", conversationId, "watches"],
39
+ retry: false,
40
+ });
41
+ }
42
+
43
+ /**
44
+ * Show what can wake this Conversation: one line per active Watch. A line
45
+ * expands to show the reason and the event types that deliver to it.
46
+ */
47
+ export function ConversationWatches(props: { watches: ConversationWatch[] }) {
48
+ return (
49
+ <DetailsList
50
+ itemKey={(watch) => watch.id}
51
+ items={props.watches}
52
+ renderItem={(watch) => <WatchRow watch={watch} />}
53
+ />
54
+ );
55
+ }
56
+
57
+ const resourceIcons: Partial<Record<string, LucideIcon>> = {
58
+ channel: Hash,
59
+ deployment: Rocket,
60
+ deployment_source: Rocket,
61
+ issue: CircleDot,
62
+ pull_request: GitPullRequest,
63
+ release_source: Tag,
64
+ repository: FolderGit2,
65
+ team: Users,
66
+ timer: Timer,
67
+ workspace_snapshot: Package,
68
+ };
69
+
70
+ /**
71
+ * Show one Watch as a single line with its resource. Clicking the line shows
72
+ * why the Watch exists and which events wake the Conversation. The expiry is
73
+ * an internal limit, so the row does not show it.
74
+ */
75
+ function WatchRow(props: { watch: ConversationWatch }) {
76
+ const { watch } = props;
77
+ const [expanded, setExpanded] = useState(false);
78
+ const Icon = watch.redacted
79
+ ? LockKeyhole
80
+ : (resourceIcons[watch.resourceType] ?? Eye);
81
+ const resource = `${watch.namespace} ${watch.resourceType.replaceAll("_", " ")}`;
82
+ // A timer has one event, and its fire time already says when it wakes.
83
+ const events = watch.firesAt ? [] : watch.events;
84
+ const filters = Object.entries(watch.match ?? {});
85
+ const row = (
86
+ <DetailsRow
87
+ expanded={expanded}
88
+ icon={<Icon aria-hidden="true" className="size-3.5" />}
89
+ meta={
90
+ <span className="grid size-5 place-items-center">
91
+ <ChevronRight
92
+ aria-hidden="true"
93
+ className={dashboardDisclosureChevronClass}
94
+ />
95
+ </span>
96
+ }
97
+ onToggle={() => setExpanded((value) => !value)}
98
+ title={watch.label ?? `Private ${resource}`}
99
+ />
100
+ );
101
+ if (!expanded) return row;
102
+
103
+ return (
104
+ <>
105
+ {row}
106
+ {/* Indent under the title: icon column, column gap, and row padding. */}
107
+ <div className="grid min-w-0 gap-1.5 pb-2 pl-9 pr-1.5 pt-0.5 text-xs leading-4 text-dashboard-text-muted">
108
+ {watch.intent ? (
109
+ <p className="m-0 break-words">{watch.intent}</p>
110
+ ) : null}
111
+ {watch.firesAt ? (
112
+ <p className="m-0">
113
+ Fires{" "}
114
+ <time dateTime={watch.firesAt}>
115
+ {formatTime(watch.firesAt, {
116
+ dateStyle: "medium",
117
+ timeStyle: "short",
118
+ })}
119
+ </time>
120
+ </p>
121
+ ) : null}
122
+ {events.length > 0 || filters.length > 0 ? (
123
+ <div className="flex flex-wrap items-center gap-1">
124
+ {events.map((event) => (
125
+ <WatchChip key={event}>
126
+ {watchEventLabel(event, watch.resourceType)}
127
+ </WatchChip>
128
+ ))}
129
+ {filters.map(([key, value]) => (
130
+ <WatchChip filter key={`match:${key}`}>
131
+ {key.replaceAll("_", " ")}: {[value].flat().join(", ")}
132
+ </WatchChip>
133
+ ))}
134
+ </div>
135
+ ) : null}
136
+ {watch.childAgent ? (
137
+ <p className="m-0 flex min-w-0 items-center gap-1">
138
+ <Bot aria-hidden="true" className="size-3 shrink-0" />
139
+ <span className="truncate">
140
+ via {watch.childAgent.name ?? "subagent"}
141
+ </span>
142
+ </p>
143
+ ) : null}
144
+ </div>
145
+ </>
146
+ );
147
+ }
148
+
149
+ /** Label one event type, or a dashed match filter that narrows the events. */
150
+ function WatchChip(props: { children: ReactNode; filter?: boolean }) {
151
+ return (
152
+ <span
153
+ className={
154
+ props.filter
155
+ ? "min-w-0 truncate rounded-md border border-dashed border-dashboard-border px-1.5 py-px text-xs leading-4 text-dashboard-text-muted"
156
+ : "min-w-0 truncate rounded-md bg-dashboard-fill-soft px-1.5 py-px text-xs leading-4 text-dashboard-text-subtle"
157
+ }
158
+ >
159
+ {props.children}
160
+ </span>
161
+ );
162
+ }
163
+
164
+ /**
165
+ * Turn an event type into a short trigger label. The row already names the
166
+ * resource, so `pull_request.checks.failed` reads as "checks failed".
167
+ */
168
+ function watchEventLabel(event: string, resourceType: string): string {
169
+ const parts = event.split(".");
170
+ const rest =
171
+ parts[0] === resourceType && parts.length > 1 ? parts.slice(1) : parts;
172
+ return rest.join(" ").replaceAll("_", " ");
173
+ }
@@ -5,8 +5,8 @@ import { useNavigate, useParams } from "react-router";
5
5
 
6
6
  import { useConversationsData } from "../api";
7
7
  import { ConversationSidebar } from "./ConversationSidebar";
8
- import { ToggleButton } from "../components/Button";
9
8
  import { SearchInput } from "../components/SearchInput";
9
+ import { ToggleGroup, ToggleGroupItem } from "../components/ui/toggle-group";
10
10
  import { pageCount, pageItems, PagePagination } from "../components/Pagination";
11
11
  import { ConversationHomeList } from "./ConversationHomeList";
12
12
  import { ConversationComposer } from "./ConversationComposer";
@@ -171,7 +171,6 @@ function NewConversationView(props: {
171
171
  ): Promise<void>;
172
172
  }) {
173
173
  const [visibility, setVisibility] = useState<"private" | "public">("public");
174
- const isPublic = visibility === "public";
175
174
 
176
175
  return (
177
176
  <section
@@ -185,32 +184,23 @@ function NewConversationView(props: {
185
184
  draftId="new"
186
185
  error={props.error}
187
186
  footerStart={
188
- <div
187
+ <ToggleGroup
189
188
  aria-label="Conversation visibility"
190
- className="inline-flex shrink-0 items-center gap-0.5 rounded-lg border border-dashboard-border bg-dashboard-surface-panel p-0.5"
191
- role="group"
189
+ className="inline-flex shrink-0 gap-0.5 rounded-lg border border-dashboard-border bg-dashboard-surface-panel p-0.5"
190
+ onValueChange={([next]) => {
191
+ if (next) setVisibility(next);
192
+ }}
193
+ value={[visibility]}
192
194
  >
193
- <ToggleButton
194
- className="min-w-20 gap-1.5"
195
- onClick={() => setVisibility("public")}
196
- pressed={isPublic}
197
- type="button"
198
- variant="segment"
199
- >
195
+ <ToggleGroupItem className="min-w-20 gap-1.5" value="public">
200
196
  <Globe2 aria-hidden="true" className="size-3" />
201
197
  Public
202
- </ToggleButton>
203
- <ToggleButton
204
- className="min-w-20 gap-1.5"
205
- onClick={() => setVisibility("private")}
206
- pressed={!isPublic}
207
- type="button"
208
- variant="segment"
209
- >
198
+ </ToggleGroupItem>
199
+ <ToggleGroupItem className="min-w-20 gap-1.5" value="private">
210
200
  <LockKeyhole aria-hidden="true" className="size-3" />
211
201
  Private
212
- </ToggleButton>
213
- </div>
202
+ </ToggleGroupItem>
203
+ </ToggleGroup>
214
204
  }
215
205
  label="Start a conversation"
216
206
  restoreDraftOnError
@@ -1,4 +1,4 @@
1
- import { Skeleton } from "../components/Skeleton";
1
+ import { Skeleton } from "../components/ui/skeleton";
2
2
  import { dashboardContainerClass, cn } from "../styles";
3
3
  import { ChatLayout } from "./ChatLayout";
4
4
  import { ConversationHomeListLoading } from "./ConversationHomeList";
@@ -0,0 +1,139 @@
1
+ import { ChevronRight } from "lucide-react";
2
+ import { useState, type ReactNode } from "react";
3
+
4
+ import { cn, dashboardDisclosureChevronClass } from "../styles";
5
+
6
+ /** Rows a details list shows before the reader asks for the rest. */
7
+ const COLLAPSED_ROW_COUNT = 3;
8
+
9
+ /**
10
+ * Show rows in a details card. A list with more than three rows shows the
11
+ * first three and a row that shows the rest.
12
+ */
13
+ export function DetailsList<T>(props: {
14
+ items: readonly T[];
15
+ itemKey(item: T): string;
16
+ renderItem(item: T): ReactNode;
17
+ }) {
18
+ const [expanded, setExpanded] = useState(false);
19
+ const hidden = props.items.length - COLLAPSED_ROW_COUNT;
20
+ const visible =
21
+ expanded || hidden <= 0
22
+ ? props.items
23
+ : props.items.slice(0, COLLAPSED_ROW_COUNT);
24
+
25
+ return (
26
+ // Rows own their padding, so a hover fill sits inside the card with an
27
+ // even inset. The negative margins keep row text on the card padding.
28
+ <ul className="-mx-1.5 -mb-1.5 -mt-1 grid list-none gap-0.5 p-0">
29
+ {visible.map((item) => (
30
+ <li key={props.itemKey(item)}>{props.renderItem(item)}</li>
31
+ ))}
32
+ {hidden > 0 ? (
33
+ <li>
34
+ <button
35
+ aria-expanded={expanded}
36
+ className="group flex min-h-8 w-full items-center gap-2.5 rounded-md px-1.5 py-1.5 text-left text-xs font-medium leading-4 text-dashboard-text-muted transition-colors hover:bg-dashboard-surface-hover hover:text-dashboard-text focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus"
37
+ onClick={() => setExpanded((value) => !value)}
38
+ type="button"
39
+ >
40
+ <span className="grid size-5 shrink-0 place-items-center">
41
+ <ChevronRight
42
+ aria-hidden="true"
43
+ className={dashboardDisclosureChevronClass}
44
+ />
45
+ </span>
46
+ {expanded ? "Show fewer" : `Show ${hidden} more`}
47
+ </button>
48
+ </li>
49
+ ) : null}
50
+ </ul>
51
+ );
52
+ }
53
+
54
+ /**
55
+ * Lay out one details list row: an icon, a title, and short trailing text on
56
+ * the first line, then optional lines under the title. A row with `href`
57
+ * opens that link in a new tab. A row with `onToggle` is a disclosure button
58
+ * that shows or hides content the caller renders under it.
59
+ */
60
+ export function DetailsRow(props: {
61
+ children?: ReactNode;
62
+ expanded?: boolean;
63
+ href?: string;
64
+ icon: ReactNode;
65
+ meta?: ReactNode;
66
+ onToggle?(): void;
67
+ title: ReactNode;
68
+ /** Native hover text with detail that the row does not show. */
69
+ tooltip?: string;
70
+ }) {
71
+ const interactive = props.href !== undefined || props.onToggle !== undefined;
72
+ const className = cn(
73
+ "grid w-full min-w-0 grid-cols-[auto_minmax(0,1fr)_auto] items-start gap-x-2.5 gap-y-1 rounded-md px-1.5 py-1.5 text-left text-inherit no-underline",
74
+ interactive &&
75
+ "group transition-colors hover:bg-dashboard-surface-hover focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus",
76
+ );
77
+ const content = (
78
+ <>
79
+ <span className="grid size-5 shrink-0 place-items-center text-dashboard-text-muted">
80
+ {props.icon}
81
+ </span>
82
+ <span className="min-w-0 truncate text-sm font-medium leading-5 text-dashboard-text">
83
+ {props.title}
84
+ </span>
85
+ <span className="shrink-0 text-xs leading-5 tabular-nums text-dashboard-text-muted">
86
+ {props.meta}
87
+ </span>
88
+ {props.children}
89
+ </>
90
+ );
91
+ if (props.href) {
92
+ return (
93
+ <a
94
+ className={className}
95
+ href={props.href}
96
+ rel="noreferrer"
97
+ target="_blank"
98
+ title={props.tooltip}
99
+ >
100
+ {content}
101
+ </a>
102
+ );
103
+ }
104
+ if (props.onToggle) {
105
+ return (
106
+ <button
107
+ aria-expanded={props.expanded ?? false}
108
+ className={className}
109
+ onClick={props.onToggle}
110
+ title={props.tooltip}
111
+ type="button"
112
+ >
113
+ {content}
114
+ </button>
115
+ );
116
+ }
117
+ return (
118
+ <div className={className} title={props.tooltip}>
119
+ {content}
120
+ </div>
121
+ );
122
+ }
123
+
124
+ /** Place one extra line under the title of a `DetailsRow`. */
125
+ export function DetailsRowLine(props: {
126
+ children: ReactNode;
127
+ className?: string;
128
+ }) {
129
+ return (
130
+ <div
131
+ className={cn(
132
+ "col-start-2 col-end-4 min-w-0 text-xs leading-4 text-dashboard-text-muted",
133
+ props.className,
134
+ )}
135
+ >
136
+ {props.children}
137
+ </div>
138
+ );
139
+ }
@@ -1,5 +1,15 @@
1
1
  import type { ConversationReportEvent } from "@sentry/junior/api/schema";
2
- import type { ReactNode } from "react";
2
+ import { ChevronRight } from "lucide-react";
3
+ import { useState, type ReactNode } from "react";
4
+
5
+ import { HighlightedCode } from "../code";
6
+ import { CopyButton } from "../components/CopyButton";
7
+ import {
8
+ Collapsible,
9
+ CollapsibleContent,
10
+ CollapsibleTrigger,
11
+ } from "../components/ui/collapsible";
12
+ import { dashboardDisclosureChevronClass } from "../styles";
3
13
 
4
14
  import { RedactedMarker } from "./TranscriptRedacted";
5
15
  import { TranscriptText } from "./TranscriptText";
@@ -178,14 +188,38 @@ export function EventDetails({ event }: { event: ConversationReportEvent }) {
178
188
  </DetailSection>
179
189
  ) : null}
180
190
  {content}
181
- <details className="border-t border-dashboard-border pt-4">
182
- <summary className="cursor-pointer text-xs text-dashboard-text-muted hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-dashboard-focus">
191
+ <RawEventJson event={event} />
192
+ </div>
193
+ );
194
+ }
195
+
196
+ function RawEventJson({ event }: { event: ConversationReportEvent }) {
197
+ const [open, setOpen] = useState(false);
198
+ const json = open ? JSON.stringify(event, null, 2) : "";
199
+ return (
200
+ <div className="relative min-w-0 border-t border-dashboard-border pt-4">
201
+ <Collapsible onOpenChange={setOpen} open={open}>
202
+ <CollapsibleTrigger className="group flex min-h-9 cursor-pointer items-center gap-1 pr-12 text-xs text-dashboard-text-muted hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-dashboard-focus">
203
+ <ChevronRight
204
+ aria-hidden="true"
205
+ className={dashboardDisclosureChevronClass}
206
+ />
183
207
  Raw JSON
184
- </summary>
185
- <pre className="mb-0 mt-3 whitespace-pre-wrap break-words font-mono text-xs leading-relaxed text-dashboard-text">
186
- {JSON.stringify(event, null, 2)}
187
- </pre>
188
- </details>
208
+ </CollapsibleTrigger>
209
+ <CollapsibleContent>
210
+ <div className="absolute right-0 top-4">
211
+ <CopyButton getText={() => json} label="Copy JSON" />
212
+ </div>
213
+ <div
214
+ aria-label="Raw event JSON"
215
+ className="mt-3 max-h-80 min-w-0 overflow-auto overscroll-contain rounded-lg border border-dashboard-border bg-dashboard-surface-panel p-3 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus"
216
+ role="region"
217
+ tabIndex={0}
218
+ >
219
+ <HighlightedCode code={json} language="json" />
220
+ </div>
221
+ </CollapsibleContent>
222
+ </Collapsible>
189
223
  </div>
190
224
  );
191
225
  }