@sentry/junior-dashboard 0.264.0 → 0.265.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) 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/BottomSheet.tsx +12 -2
  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 +15 -24
  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 +86 -77
  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 +13 -3
  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/conversations/ChatLayout.tsx +7 -3
  60. package/src/client/conversations/ConversationBrief.tsx +17 -6
  61. package/src/client/conversations/ConversationComposer.tsx +26 -12
  62. package/src/client/conversations/ConversationDetails.tsx +147 -62
  63. package/src/client/conversations/ConversationEventDetails.tsx +141 -0
  64. package/src/client/conversations/ConversationEventLog.tsx +150 -129
  65. package/src/client/conversations/ConversationHeader.tsx +8 -3
  66. package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
  67. package/src/client/conversations/ConversationHomeList.tsx +1 -1
  68. package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
  69. package/src/client/conversations/ConversationMeta.tsx +55 -88
  70. package/src/client/conversations/ConversationPage.tsx +220 -101
  71. package/src/client/conversations/ConversationSidebar.tsx +48 -29
  72. package/src/client/conversations/ConversationWatches.tsx +165 -0
  73. package/src/client/conversations/ConversationWorkspace.tsx +12 -22
  74. package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
  75. package/src/client/conversations/DetailsList.tsx +124 -0
  76. package/src/client/conversations/EventDetails.tsx +42 -8
  77. package/src/client/conversations/ForkConversationButton.tsx +27 -16
  78. package/src/client/conversations/ObjectCard.tsx +20 -8
  79. package/src/client/conversations/PendingMailboxStack.tsx +52 -33
  80. package/src/client/conversations/README.md +86 -13
  81. package/src/client/conversations/ShareConversationButton.tsx +25 -13
  82. package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
  83. package/src/client/conversations/ToolFrame.tsx +36 -52
  84. package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
  85. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
  86. package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
  87. package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
  88. package/src/client/conversations/TranscriptLoading.tsx +1 -1
  89. package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
  90. package/src/client/conversations/TranscriptMessageContextView.tsx +8 -2
  91. package/src/client/conversations/TranscriptMessageView.tsx +6 -1
  92. package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
  93. package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
  94. package/src/client/conversations/TranscriptToolView.tsx +1 -1
  95. package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
  96. package/src/client/conversations/TranscriptView.tsx +63 -27
  97. package/src/client/conversations/eventLog.ts +57 -0
  98. package/src/client/conversations/eventTime.ts +60 -0
  99. package/src/client/conversations/queries.ts +29 -0
  100. package/src/client/conversations/transcriptBottomPinning.ts +34 -0
  101. package/src/client/format.ts +5 -0
  102. package/src/client/pages/ConnectionsPage.tsx +2 -2
  103. package/src/client/pages/PersonalTokensPage.tsx +4 -4
  104. package/src/client/pages/SettingsPage.tsx +9 -10
  105. package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
  106. package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
  107. package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
  108. package/src/client/pages/automations/AutomationDetails.tsx +193 -0
  109. package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
  110. package/src/client/pages/automations/AutomationEditor.tsx +41 -29
  111. package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
  112. package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
  113. package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
  114. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
  115. package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
  116. package/src/client/pages/automations/AutomationFilters.tsx +189 -74
  117. package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
  118. package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
  119. package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
  120. package/src/client/pages/automations/AutomationRow.tsx +12 -138
  121. package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
  122. package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
  123. package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
  124. package/src/client/pages/automations/AutomationStatus.tsx +201 -0
  125. package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
  126. package/src/client/pages/automations/AutomationsPage.tsx +80 -68
  127. package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
  128. package/src/client/pages/automations/README.md +57 -0
  129. package/src/client/pages/automations/TriggerFilters.tsx +22 -0
  130. package/src/client/pages/automations/automationFormat.ts +37 -23
  131. package/src/client/pages/code/CodeActivityChart.tsx +59 -101
  132. package/src/client/pages/code/CodePage.tsx +52 -55
  133. package/src/client/pages/code/RepositoryPage.tsx +6 -8
  134. package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
  135. package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
  136. package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
  137. package/src/client/pages/memory/MemoryDetails.tsx +218 -0
  138. package/src/client/pages/memory/MemoryPage.tsx +168 -112
  139. package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
  140. package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
  141. package/src/client/pages/memory/memoryRecord.ts +1 -1
  142. package/src/client/pages/people/ContributionGrid.tsx +23 -16
  143. package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
  144. package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
  145. package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
  146. package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
  147. package/src/client/pages/system/GuardianActivity.tsx +1 -2
  148. package/src/client/pages/system/PluginBarChart.tsx +106 -87
  149. package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
  150. package/src/client/pages/system/README.md +23 -0
  151. package/src/client/pages/system/SystemNavigation.tsx +5 -1
  152. package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
  153. package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
  154. package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
  155. package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
  156. package/src/client/pages/system/WorkspaceList.tsx +20 -30
  157. package/src/client/pages/system/WorkspacesPage.tsx +117 -119
  158. package/src/client/pages/system/sandboxSize.ts +8 -0
  159. package/src/client/pages/system/workspaceQueries.ts +53 -0
  160. package/src/client/pages/user/PluginUserPage.tsx +51 -2
  161. package/src/client/pages/user/pluginUserPageData.ts +1 -6
  162. package/src/client/styles.ts +4 -0
  163. package/src/client.tsx +7 -1
  164. package/src/mock-reporting/automation-collection.ts +4 -2
  165. package/src/mock-reporting/fixtures.ts +23 -7
  166. package/src/mock-reporting/routes.ts +12 -0
  167. package/src/mock-reporting/watches.ts +94 -0
  168. package/src/shell.ts +4 -9
  169. package/src/tailwind.css +0 -10
  170. package/src/client/components/Button.tsx +0 -118
  171. package/src/client/components/Field.tsx +0 -53
  172. package/src/client/components/SegmentedTabs.tsx +0 -119
  173. package/src/client/components/Select.tsx +0 -52
  174. package/src/client/components/Skeleton.tsx +0 -17
  175. package/src/client/components/TextInput.tsx +0 -58
  176. package/src/client/components/Tooltip.tsx +0 -216
  177. package/src/client/components/charts/useChartLayout.ts +0 -27
  178. package/src/client/components/ui/sheet.LICENSE +0 -21
  179. package/src/client/conversations/ConversationDetailsFrame.tsx +0 -119
  180. package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
  181. package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
  182. package/src/client/pages/dev/ChartFixtures.tsx +0 -325
  183. package/src/client/pages/dev/ComponentsPage.tsx +0 -899
  184. package/src/client/pages/dev/ConversationFixture.tsx +0 -231
  185. package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
  186. package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
  187. package/src/client/pages/dev/SelectFixtures.tsx +0 -62
  188. package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
  189. package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
  190. package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
  191. package/src/client/pages/system/SnapshotSummary.tsx +0 -78
@@ -18,7 +18,8 @@ shared controls and overlays.
18
18
  The caller owns the native `details` element and its content.
19
19
  - Activity rows (tools, reasoning, and grouped rail events) share one
20
20
  layout: a 1rem icon column, an 8px gap, then the title and metadata.
21
- Expanded content sits in `transcriptActivityBodyClass` under the title.
21
+ Tool and reasoning rows use `ui/trace`. Transcript search forces them open.
22
+ Expanded content sits in `TraceCollapsibleContent` under the title.
22
23
  `TranscriptActivityNesting` tells a rail event that it is inside an activity
23
24
  group. Outside a group, the rail icon sits in the avatar column so the text
24
25
  lines up with message text.
@@ -40,23 +41,46 @@ shared controls and overlays.
40
41
  Automation. Render this text with `TranscriptText` or `MentionText`. Both
41
42
  require `mentions`, so a caller cannot forget them. Do not resolve names in
42
43
  the dashboard, and do not rewrite tokens into links on the server.
43
- - `TranscriptTurnContextView` owns context content. The shared `Drawer` adds
44
- its header and layout to the Base UI `Sheet` focus and dismissal boundary.
44
+ - `TranscriptTurnContextView` owns context content. A transcript with a
45
+ `turnContextPanel` shows the context in the page panel. See "Page panel".
46
+ Other transcripts, such as a subagent drawer, open a modal `Drawer`. Each
47
+ `Transcript` sets the panel for its own rows, so a nested transcript does not
48
+ use the panel of the page behind it. The panel keys context by message id,
49
+ so every control for one message shows the same open state.
45
50
 
46
- Review these components together in `/dev/transcripts`. Use the conversation
47
- fixture for messages, events, activity, context, and typing. Use the attachment
48
- fixture for file wrapping. Keep visual checks out of automated assertions;
49
- browser tests cover opening, closing, and keyboard interaction.
51
+ Review these components together on the mock Conversations. Run
52
+ `JUNIOR_DASHBOARD_MOCK_CONVERSATIONS=true pnpm dev` for messages, events,
53
+ activity, context, typing, and attachments. Keep visual checks out of automated
54
+ assertions; browser tests cover opening, closing, and keyboard interaction.
50
55
 
51
56
  ## Conversation details
52
57
 
53
- `ConversationPage` owns whether details are open. The page is keyed by the
54
- Conversation, so switching Conversations closes details. `ConversationHeader`
55
- only toggles details and restores focus through explicit control refs after
56
- dismissal.
58
+ `ConversationPage` owns whether details are open. At 1440px and above, details
59
+ open by default. An explicit choice lasts for the current Conversation. The page
60
+ is keyed by the Conversation, so switching Conversations resets that choice.
61
+ `ConversationHeader` toggles details and restores focus through explicit control
62
+ refs after dismissal.
57
63
 
58
- `ConversationDetails` owns one set of tabs and content. `ConversationDetailsFrame`
59
- uses a 400px aside at 1280px and above. The chat and aside scroll separately.
64
+ ## Page panel
65
+
66
+ The Conversation page has one panel on the right. It shows Conversation
67
+ details, event details, or turn context. Only one shows at a time. Each uses
68
+ `DetailsPanel`: a 400px aside at 1280px and above, and a `BottomSheet` below.
69
+
70
+ - Event details and turn context show in place of Conversation details.
71
+ - When you close them, Conversation details show again if they are open. The
72
+ details stay open by choice or by the 1440px default.
73
+ - A link that opens an event sets details to closed. When you close that
74
+ event, no panel shows.
75
+ - The details control closes event details and turn context, then shows or
76
+ hides Conversation details.
77
+ - Turn context belongs to the Conversation view. A view change closes it.
78
+ Select the same context control again to close it.
79
+ - Closing a panel returns focus to the row or control that opened it.
80
+
81
+ `ConversationDetails` owns one set of tabs and content. It supplies its title and
82
+ actions to `DetailsPanel`, which uses a 400px aside at 1280px and above. The chat
83
+ and aside scroll separately.
60
84
  Smaller windows use `BottomSheet` with a visible title and close control. The sheet and
61
85
  side drawers use the shadcn Base UI `Sheet` and share the app's document
62
86
  scroll lock. A resize changes the frame and keeps the selected tab. Tabs stay
@@ -64,3 +88,52 @@ fixed above the scrolling content in both layouts. The sheet title and close con
64
88
  control, tap the backdrop, or press Escape to dismiss it. Base UI owns the
65
89
  transition lifecycle.
66
90
  A pending exit cannot close a new panel or a different Conversation.
91
+
92
+ ## Event log
93
+
94
+ `ChatLayout` keeps the Conversation header outside the transcript scroll region.
95
+ `ConversationEventLog` fills the pane without a card border or page inset. Its
96
+ search toolbar and column labels stay together at the top while rows scroll.
97
+ `Transcript` shares the search value with its owner, or keeps it locally when
98
+ used alone. Event log search stays visible; the transcript search toggle is for
99
+ the Conversation view.
100
+
101
+ Rows keep time, event type, and summary in columns when the pane is at least
102
+ 512px wide. Smaller panes put the summary below time and type. Model and usage
103
+ metadata share one optional line. Missing model metadata is omitted from rows.
104
+ Selected rows have a background and an accent edge. Up and Down select the
105
+ previous or next visible row while a row has focus. Selection stops at the
106
+ ends of the filtered list. The selected row stays clear of the fixed toolbar.
107
+ `eventLogRows` and `filterEventLogRows` own the row text and search match.
108
+ The page uses them too, so its step controls follow the same rows.
109
+
110
+ The timezone toggle uses `useEventTime` to share a UTC/local preference through
111
+ `@uidotdev/usehooks` and browser local storage. It applies to event row clocks
112
+ and detail timestamps. Local mode follows the browser timezone. The button uses
113
+ a generic zone name because a log can span standard and daylight-saving time.
114
+ Detail timestamps use the zone name for each event date. The mode, not
115
+ the timezone identifier, persists across visits and tabs. This hook belongs to
116
+ the client-rendered event viewer. Raw JSON retains the original ISO values.
117
+
118
+ ## Event details
119
+
120
+ The URL owns the selected event in the main Conversation. `ConversationPage`
121
+ places `ConversationEventDetails` beside the log in the page panel. Selecting
122
+ another row changes the event and resets its scroll position and disclosures.
123
+ Closing returns focus to that row. Switching Conversations removes the panel.
124
+
125
+ The previous and next controls select the adjacent visible row. They follow
126
+ the event search, like Up and Down. They are necessary below 1280px, where the
127
+ sheet covers the log. At the ends of the list, the control is disabled but
128
+ keeps focus. The sheet title is the event type.
129
+
130
+ A linked event can be older than the loaded pages. While the event loads, the
131
+ panel stays open. Only its header shows placeholders, so it does not compete
132
+ with the log. While the transcript loads, `ConversationEventLogLoading` shows
133
+ the log toolbar, columns, and row skeletons in the event log view.
134
+
135
+ Standalone transcripts, such as a subagent drawer, keep selection in
136
+ `ConversationEventLog`. Their event details use a nested drawer on desktop and
137
+ `BottomSheet` on smaller screens. This keeps the main log free of modal behavior
138
+ while a nested transcript retains its parent drawer's focus rules. These event
139
+ details have the same previous and next controls.
@@ -2,8 +2,12 @@ import { useState } from "react";
2
2
  import { useHref } from "react-router";
3
3
  import { Check, Share2 } from "lucide-react";
4
4
 
5
- import { Button } from "../components/Button";
6
- import { IconButtonTooltip } from "../components/Tooltip";
5
+ import { Button } from "../components/ui/button";
6
+ import {
7
+ Tooltip,
8
+ TooltipContent,
9
+ TooltipTrigger,
10
+ } from "../components/ui/tooltip";
7
11
  import { conversationPath } from "./conversationRoutes";
8
12
 
9
13
  /** Share a conversation link without changing who can access it. */
@@ -72,17 +76,25 @@ export function ShareConversationButton(props: {
72
76
  );
73
77
  const button =
74
78
  props.layout === "icon" ? (
75
- <IconButtonTooltip label={label} placement="below">
76
- <Button
77
- aria-label={label}
78
- className="text-dashboard-text-muted"
79
- disabled={status === "sharing"}
80
- onClick={() => void shareLink()}
81
- size="icon"
82
- >
83
- <Icon aria-hidden="true" size={15} />
84
- </Button>
85
- </IconButtonTooltip>
79
+ <Tooltip>
80
+ <TooltipTrigger
81
+ render={
82
+ <Button
83
+ focusableWhenDisabled
84
+ aria-label={label}
85
+ className="text-dashboard-text-muted"
86
+ disabled={status === "sharing"}
87
+ onClick={() => void shareLink()}
88
+ size="icon"
89
+ >
90
+ <Icon aria-hidden="true" size={15} />
91
+ </Button>
92
+ }
93
+ />
94
+ <TooltipContent size="compact" side="bottom">
95
+ {label}
96
+ </TooltipContent>
97
+ </Tooltip>
86
98
  ) : props.layout === "menu" ? (
87
99
  <button
88
100
  aria-label={label}
@@ -14,7 +14,7 @@ import { Drawer } from "../components/Drawer";
14
14
  import { SearchInput } from "../components/SearchInput";
15
15
  import { StatusChip } from "../components/StatusChip";
16
16
  import { subagentStatusTone } from "./ConversationChildAgents";
17
- import { CopyMarkdownButton } from "./CopyMarkdownButton";
17
+ import { CopyButton } from "../components/CopyButton";
18
18
  import {
19
19
  TranscriptSearchToggle,
20
20
  TranscriptViewToggle,
@@ -69,7 +69,8 @@ function SubagentTranscriptDrawerContent(props: {
69
69
  ? props.target.agentName
70
70
  : undefined;
71
71
  const startedAt = detail ? Date.parse(detail.startedAt) : undefined;
72
- const searchOpenVisible = searchOpen || search.length > 0;
72
+ const searchOpenVisible =
73
+ view === "rich" && (searchOpen || search.length > 0);
73
74
 
74
75
  const titleId = "subagent-transcript-drawer-title";
75
76
 
@@ -77,21 +78,25 @@ function SubagentTranscriptDrawerContent(props: {
77
78
  <Drawer
78
79
  actions={
79
80
  <>
80
- <TranscriptSearchToggle
81
- onClick={() => {
82
- if (searchOpenVisible) {
83
- setSearchOpen(false);
84
- if (search.length > 0) setSearch("");
85
- return;
86
- }
87
- setSearchOpen(true);
88
- }}
89
- open={searchOpenVisible}
90
- />
81
+ {view === "rich" ? (
82
+ <TranscriptSearchToggle
83
+ onClick={() => {
84
+ if (searchOpenVisible) {
85
+ setSearchOpen(false);
86
+ if (search.length > 0) setSearch("");
87
+ return;
88
+ }
89
+ setSearchOpen(true);
90
+ }}
91
+ open={searchOpenVisible}
92
+ />
93
+ ) : null}
91
94
  <TranscriptViewToggle onChange={setView} value={view} />
92
- <CopyMarkdownButton
95
+ <CopyButton
96
+ label="Copy as Markdown"
97
+ copyingLabel="Preparing Markdown"
93
98
  key={props.target.conversationId}
94
- getMarkdown={
99
+ getText={
95
100
  detail
96
101
  ? async () =>
97
102
  buildConversationMarkdown(
@@ -184,6 +189,7 @@ function SubagentTranscriptDrawerContent(props: {
184
189
  loadingPreviousPage={query.isLoadingPreviousPage}
185
190
  onLoadPreviousPage={query.loadPreviousPage}
186
191
  search={search}
192
+ onSearchChange={setSearch}
187
193
  transcript={detail}
188
194
  view={view}
189
195
  />
@@ -1,17 +1,17 @@
1
1
  import { useState, type ReactNode } from "react";
2
2
  import type { LucideIcon } from "lucide-react";
3
3
 
4
+ import {
5
+ TraceCollapsible,
6
+ TraceCollapsibleContent,
7
+ TraceCollapsibleTrigger,
8
+ } from "../components/ui/trace";
4
9
  import { cn } from "../styles";
5
10
  import {
6
11
  TranscriptHeadingMeta,
7
12
  TranscriptHeadingRow,
8
13
  } from "./TranscriptHeadingRow";
9
14
  import { useTranscriptSearch } from "./transcriptSearch";
10
- import { TranscriptSummary } from "./TranscriptSummary";
11
-
12
- /** Frame expanded transcript activity content below its row title. */
13
- export const transcriptActivityBodyClass =
14
- "ml-6 mt-1.5 min-w-0 overflow-hidden rounded-md border border-dashboard-border-subtle bg-dashboard-surface-panel";
15
15
 
16
16
  /** Render the shared expandable/non-expandable frame for transcript tools. */
17
17
  export function ToolFrame(props: {
@@ -27,30 +27,22 @@ export function ToolFrame(props: {
27
27
  const { active: searchActive } = useTranscriptSearch();
28
28
  const [open, setOpen] = useState(false);
29
29
  const metaText = props.meta.join(" · ");
30
- const interactive = props.expandable ?? Boolean(props.children);
31
- const staticFrame = searchActive || !interactive;
30
+ const expandable = props.expandable ?? Boolean(props.children);
31
+ // Force-expand tool details during search so highlighted matches are visible.
32
+ const expanded = expandable && (searchActive || open);
32
33
  const Icon = props.icon;
33
34
  const row = (
34
35
  <TranscriptHeadingRow
36
+ className="flex-1"
35
37
  left={
36
38
  <>
37
39
  {props.signature}
38
- {props.mobileSummaryMeta ? (
40
+ {props.mobileSummaryMeta && !expanded ? (
39
41
  <>
40
- <span
41
- className={cn(
42
- "hidden text-dashboard-text-muted max-md:inline",
43
- !staticFrame && "max-md:group-open:hidden",
44
- )}
45
- >
42
+ <span className="hidden text-dashboard-text-muted max-md:inline">
46
43
  ·
47
44
  </span>
48
- <span
49
- className={cn(
50
- "hidden shrink-0 whitespace-nowrap text-dashboard-text-muted max-md:inline",
51
- !staticFrame && "max-md:group-open:hidden",
52
- )}
53
- >
45
+ <span className="hidden shrink-0 whitespace-nowrap text-dashboard-text-muted max-md:inline">
54
46
  {props.mobileSummaryMeta}
55
47
  </span>
56
48
  </>
@@ -59,7 +51,7 @@ export function ToolFrame(props: {
59
51
  }
60
52
  leftClassName={cn(
61
53
  "gap-x-1 gap-y-0.5",
62
- staticFrame ? "flex-wrap" : "flex-nowrap group-open:flex-wrap",
54
+ expandable && !expanded ? "flex-nowrap" : "flex-wrap",
63
55
  )}
64
56
  right={
65
57
  metaText ? (
@@ -72,54 +64,46 @@ export function ToolFrame(props: {
72
64
  />
73
65
  );
74
66
  const header = Icon ? (
75
- <div className="grid min-w-0 grid-cols-[1rem_minmax(0,1fr)] items-center gap-2">
67
+ <span className="grid min-w-0 flex-1 grid-cols-[1rem_minmax(0,1fr)] items-center gap-2">
76
68
  <Icon
77
69
  aria-hidden="true"
78
70
  className={cn("size-3.5 justify-self-center", props.iconClassName)}
79
71
  strokeWidth={2}
80
72
  />
81
73
  {row}
82
- </div>
74
+ </span>
83
75
  ) : (
84
76
  row
85
77
  );
86
- const mobileMeta =
87
- metaText && props.children ? (
88
- <div className="hidden min-w-0 break-words border-b border-dashboard-border-subtle px-2.5 py-1.5 font-sans text-xs tabular-nums leading-snug text-dashboard-text-muted max-md:block">
89
- {metaText}
90
- </div>
91
- ) : null;
92
78
 
93
- const body = props.children ? (
94
- <div className={transcriptActivityBodyClass}>
95
- {mobileMeta}
96
- {props.children}
97
- </div>
98
- ) : null;
99
-
100
- // Force-expand tool details during search so highlighted matches are visible.
101
- if (staticFrame) {
79
+ if (!expandable) {
102
80
  return (
103
- <div className="min-w-0 max-w-full">
104
- <div className="block py-1.5 font-mono text-xs leading-tight text-dashboard-text-muted">
105
- {header}
106
- </div>
107
- {body}
81
+ <div className="flex min-w-0 max-w-full py-1.5 font-mono text-xs leading-tight text-dashboard-text-muted">
82
+ {header}
108
83
  </div>
109
84
  );
110
85
  }
111
86
 
112
87
  return (
113
- <details
114
- className="group min-w-0 max-w-full"
115
- open={open}
116
- onToggle={(event) => setOpen(event.currentTarget.open)}
88
+ <TraceCollapsible
89
+ className="max-w-full"
90
+ // Search holds rows open, so the row button is off until search ends.
91
+ disabled={searchActive}
92
+ open={expanded}
93
+ onOpenChange={setOpen}
117
94
  >
118
- <TranscriptSummary className="-mx-2 block font-mono text-xs leading-tight text-dashboard-text-muted">
95
+ <TraceCollapsibleTrigger className="-mx-2 w-auto font-mono text-xs leading-tight">
119
96
  {header}
120
- </TranscriptSummary>
121
- {/* Closed details hide DOM, but still mount React children and run Shiki. */}
122
- {open ? body : null}
123
- </details>
97
+ </TraceCollapsibleTrigger>
98
+ {/* The closed panel unmounts, so closed rows do not run Shiki. */}
99
+ <TraceCollapsibleContent>
100
+ {metaText ? (
101
+ <div className="hidden min-w-0 break-words border-b border-dashboard-border-subtle px-2.5 py-1.5 font-sans text-xs tabular-nums leading-snug text-dashboard-text-muted max-md:block">
102
+ {metaText}
103
+ </div>
104
+ ) : null}
105
+ {props.children}
106
+ </TraceCollapsibleContent>
107
+ </TraceCollapsible>
124
108
  );
125
109
  }
@@ -2,7 +2,11 @@ import { Fragment, useState, type ReactNode } from "react";
2
2
  import { ChevronRight } from "lucide-react";
3
3
 
4
4
  import { ShimmerText } from "../components/ShimmerText";
5
- import { Tooltip } from "../components/Tooltip";
5
+ import {
6
+ Tooltip,
7
+ TooltipContent,
8
+ TooltipTrigger,
9
+ } from "../components/ui/tooltip";
6
10
  import { formatMs } from "../format";
7
11
  import type { RenderedTranscriptEntry } from "./transcriptRenderModel";
8
12
  import { cn } from "../styles";
@@ -180,8 +184,12 @@ export function TranscriptActivityGroup(props: {
180
184
  setUserOpen(!open);
181
185
  }}
182
186
  >
183
- <Tooltip content={summary} placement="above">
184
- <span className="inline-flex min-w-0 max-w-full items-center gap-1">
187
+ <Tooltip>
188
+ <TooltipTrigger
189
+ render={
190
+ <span className="inline-flex min-w-0 max-w-full items-center gap-1" />
191
+ }
192
+ >
185
193
  <span className="min-w-0 truncate group-open/activity-run:hidden">
186
194
  <ShimmerText active={live}>{label}</ShimmerText>
187
195
  </span>
@@ -193,7 +201,8 @@ export function TranscriptActivityGroup(props: {
193
201
  className="size-3 shrink-0 opacity-55 transition-transform group-open/activity-run:rotate-90"
194
202
  strokeWidth={2.2}
195
203
  />
196
- </span>
204
+ </TooltipTrigger>
205
+ <TooltipContent>{summary}</TooltipContent>
197
206
  </Tooltip>
198
207
  </TranscriptSummary>
199
208
  <TranscriptActivityNesting.Provider value>
@@ -2,7 +2,8 @@ import { ArrowRight, ChevronRight } from "lucide-react";
2
2
  import { useId, useState } from "react";
3
3
  import type { ConversationDetailReport } from "@sentry/junior/api/schema";
4
4
 
5
- import { ButtonLink } from "../components/Button";
5
+ import { buttonVariants } from "../components/ui/button";
6
+ import { Link } from "react-router";
6
7
  import { ObjectIcon } from "../components/ObjectIcon";
7
8
  import { automationPath, formatRelativeTime } from "../format";
8
9
  import { messageRawText } from "./transcriptRenderModel";
@@ -70,13 +71,13 @@ export function TranscriptAutomationTriggerView(props: {
70
71
  <HighlightText text={name} />
71
72
  </span>
72
73
  {automationId ? (
73
- <ButtonLink
74
- className="h-8 shrink-0 text-xs"
74
+ <Link
75
+ className={cn(buttonVariants(), "h-8 shrink-0 text-xs")}
75
76
  to={automationPath(automationId)}
76
77
  >
77
78
  Open automation
78
79
  <ArrowRight aria-hidden="true" size={14} />
79
- </ButtonLink>
80
+ </Link>
80
81
  ) : null}
81
82
  </div>
82
83
  {details.length > 0 ? (
@@ -1,3 +1,11 @@
1
+ import { ChevronRight } from "lucide-react";
2
+
3
+ import {
4
+ Collapsible,
5
+ CollapsibleContent,
6
+ CollapsibleTrigger,
7
+ } from "../components/ui/collapsible";
8
+ import { dashboardDisclosureChevronClass } from "../styles";
1
9
  import { formatCompactNumber, formatMessageTimestamp } from "../format";
2
10
  import type { TranscriptViewContextEventPart } from "../types";
3
11
  import { TranscriptText } from "./TranscriptText";
@@ -39,18 +47,24 @@ export function TranscriptContextEventView(props: {
39
47
  : `Earlier context was summarized${compactionDetail}${compactionModel} before execution continued.`}
40
48
  </div>
41
49
  {event.summary ? (
42
- <details className="mt-2">
43
- <summary className="cursor-pointer select-none text-xs font-medium text-dashboard-text-muted">
44
- Continuation summary
45
- </summary>
46
- <div className="mt-1.5 whitespace-pre-wrap text-xs leading-relaxed text-dashboard-text-muted">
47
- <TranscriptText
48
- mentions={props.part.mentions ?? []}
49
- role="system"
50
- text={event.summary}
50
+ <Collapsible className="mt-2">
51
+ <CollapsibleTrigger className="group flex cursor-pointer select-none items-center gap-1 text-xs font-medium text-dashboard-text-muted focus-visible:outline-2 focus-visible:outline-dashboard-focus">
52
+ <ChevronRight
53
+ aria-hidden="true"
54
+ className={dashboardDisclosureChevronClass}
51
55
  />
52
- </div>
53
- </details>
56
+ Continuation summary
57
+ </CollapsibleTrigger>
58
+ <CollapsibleContent hiddenUntilFound>
59
+ <div className="mt-1.5 whitespace-pre-wrap text-xs leading-relaxed text-dashboard-text-muted">
60
+ <TranscriptText
61
+ mentions={props.part.mentions ?? []}
62
+ role="system"
63
+ text={event.summary}
64
+ />
65
+ </div>
66
+ </CollapsibleContent>
67
+ </Collapsible>
54
68
  ) : null}
55
69
  </article>
56
70
  );
@@ -2,7 +2,7 @@ import type { ReactNode } from "react";
2
2
 
3
3
  import { cn } from "../styles";
4
4
 
5
- /** Align transcript labels and metadata as one centered heading row. */
5
+ /** Align transcript labels and metadata as one centered heading row. Spans keep it valid inside a row button. */
6
6
  export function TranscriptHeadingRow(props: {
7
7
  className?: string;
8
8
  left: ReactNode;
@@ -14,31 +14,31 @@ export function TranscriptHeadingRow(props: {
14
14
  props.right !== undefined && props.right !== null && props.right !== false;
15
15
 
16
16
  return (
17
- <div
17
+ <span
18
18
  className={cn(
19
19
  "flex min-w-0 items-start justify-between gap-2 md:items-center md:gap-3",
20
20
  props.className,
21
21
  )}
22
22
  >
23
- <div
23
+ <span
24
24
  className={cn(
25
25
  "flex min-w-0 items-center gap-2 overflow-hidden",
26
26
  props.leftClassName,
27
27
  )}
28
28
  >
29
29
  {props.left}
30
- </div>
30
+ </span>
31
31
  {hasRight ? (
32
- <div
32
+ <span
33
33
  className={cn(
34
34
  "min-w-0 max-w-[60%] shrink text-right md:max-w-none md:shrink-0",
35
35
  props.rightClassName,
36
36
  )}
37
37
  >
38
38
  {props.right}
39
- </div>
39
+ </span>
40
40
  ) : null}
41
- </div>
41
+ </span>
42
42
  );
43
43
  }
44
44
 
@@ -1,5 +1,5 @@
1
1
  import { TranscriptRow, TranscriptRows } from "./TranscriptRows";
2
- import { Skeleton } from "../components/Skeleton";
2
+ import { Skeleton } from "../components/ui/skeleton";
3
3
 
4
4
  /** Match the usual transcript rhythm while the selected conversation loads. */
5
5
  export function TranscriptLoading() {
@@ -1,5 +1,13 @@
1
1
  import type { ReactNode } from "react";
2
2
 
3
+ import {
4
+ Table,
5
+ TableBody,
6
+ TableCell,
7
+ TableHead,
8
+ TableHeader,
9
+ TableRow,
10
+ } from "../components/ui/table";
3
11
  import { cn } from "../styles";
4
12
  import {
5
13
  findTranscriptMarkdownLinks,
@@ -184,51 +192,48 @@ function MarkdownTable(props: {
184
192
  );
185
193
 
186
194
  return (
187
- <div className="min-w-0 max-w-full overflow-x-auto rounded-md border border-dashboard-border bg-dashboard-fill-faint">
188
- <table className="w-max min-w-full border-collapse text-left text-sm">
189
- <thead className="bg-dashboard-overlay-soft">
190
- <tr>
195
+ <div className="min-w-0 max-w-full overflow-hidden rounded-md border border-dashboard-border bg-dashboard-fill-faint">
196
+ <Table className="w-max min-w-full">
197
+ <TableHeader className="bg-dashboard-overlay-soft">
198
+ <TableRow className="border-dashboard-border">
191
199
  {Array.from({ length: columnCount }, (_, index) => {
192
200
  const alignment = props.block.alignments[index] ?? null;
193
201
  return (
194
- <th
202
+ <TableHead
195
203
  className={cn(
196
- "whitespace-nowrap border-b border-dashboard-border px-2.5 py-1.5 font-sans text-xs font-medium text-dashboard-text-muted",
204
+ "px-2.5 py-1.5",
197
205
  tableCellAlignClass(alignment),
198
206
  )}
199
207
  key={`h-${index}`}
200
208
  scope="col"
201
209
  >
202
210
  {renderInlineMarkdown(props.block.headers[index] ?? "")}
203
- </th>
211
+ </TableHead>
204
212
  );
205
213
  })}
206
- </tr>
207
- </thead>
208
- <tbody>
214
+ </TableRow>
215
+ </TableHeader>
216
+ <TableBody>
209
217
  {props.block.rows.map((row, rowIndex) => (
210
- <tr
211
- className="border-b border-dashboard-border-subtle last:border-b-0"
212
- key={`r-${rowIndex}`}
213
- >
218
+ <TableRow key={`r-${rowIndex}`}>
214
219
  {Array.from({ length: columnCount }, (_, index) => {
215
220
  const alignment = props.block.alignments[index] ?? null;
216
221
  return (
217
- <td
222
+ <TableCell
218
223
  className={cn(
219
- "whitespace-nowrap px-2.5 py-1.5 align-top text-dashboard-text",
224
+ "px-2.5 py-1.5 align-top",
220
225
  tableCellAlignClass(alignment),
221
226
  )}
222
227
  key={`c-${rowIndex}-${index}`}
223
228
  >
224
229
  {renderInlineMarkdown(row[index] ?? "")}
225
- </td>
230
+ </TableCell>
226
231
  );
227
232
  })}
228
- </tr>
233
+ </TableRow>
229
234
  ))}
230
- </tbody>
231
- </table>
235
+ </TableBody>
236
+ </Table>
232
237
  </div>
233
238
  );
234
239
  }