@sentry/junior-dashboard 0.264.0 → 0.266.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +2 -5
  3. package/dist/app.js +119 -35
  4. package/dist/client.js +145 -136
  5. package/dist/index.d.ts +0 -1
  6. package/dist/index.js +119 -35
  7. package/dist/tailwind.css +1 -1
  8. package/package.json +5 -4
  9. package/src/api/schema.ts +0 -1
  10. package/src/app.ts +5 -20
  11. package/src/client/App.tsx +29 -31
  12. package/src/client/api.ts +7 -0
  13. package/src/client/components/ActorAvatar.tsx +10 -22
  14. package/src/client/components/BottomSheet.tsx +12 -2
  15. package/src/client/components/ConnectionBanner.tsx +4 -3
  16. package/src/client/components/CopyButton.tsx +82 -0
  17. package/src/client/components/DetailsPanel.tsx +133 -0
  18. package/src/client/components/Drawer.tsx +15 -24
  19. package/src/client/components/EmptyTelemetry.tsx +5 -3
  20. package/src/client/components/FilterBar.tsx +16 -31
  21. package/src/client/components/ImageAttachment.tsx +49 -63
  22. package/src/client/components/InlineError.tsx +8 -4
  23. package/src/client/components/Metric.tsx +33 -23
  24. package/src/client/components/Notice.tsx +19 -26
  25. package/src/client/components/PageContentSkeleton.tsx +1 -1
  26. package/src/client/components/Pagination.tsx +27 -27
  27. package/src/client/components/ParticipantAvatarStack.tsx +43 -39
  28. package/src/client/components/ProfileMenu.tsx +27 -93
  29. package/src/client/components/README.md +103 -78
  30. package/src/client/components/SearchInput.tsx +51 -41
  31. package/src/client/components/SegmentedNav.tsx +1 -1
  32. package/src/client/components/ShimmerText.tsx +2 -6
  33. package/src/client/components/ThemeSwitcher.tsx +19 -15
  34. package/src/client/components/VersionDriftBanner.tsx +9 -7
  35. package/src/client/components/charts/ActivityChart.tsx +173 -288
  36. package/src/client/components/charts/CacheWrites.tsx +59 -37
  37. package/src/client/components/charts/InputCacheChart.tsx +137 -131
  38. package/src/client/components/charts/README.md +4 -4
  39. package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
  40. package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
  41. package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
  42. package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
  43. package/src/client/components/layout/DashboardHeader.tsx +44 -90
  44. package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
  45. package/src/client/components/layout/VisualViewportShell.tsx +2 -2
  46. package/src/client/components/ui/accordion.tsx +77 -0
  47. package/src/client/components/ui/alert-dialog.tsx +89 -0
  48. package/src/client/components/ui/alert.tsx +64 -0
  49. package/src/client/components/ui/avatar.tsx +53 -0
  50. package/src/client/components/ui/button.tsx +86 -0
  51. package/src/client/components/ui/chart.tsx +74 -0
  52. package/src/client/components/ui/checkbox.tsx +26 -0
  53. package/src/client/components/ui/collapsible.tsx +22 -0
  54. package/src/client/components/ui/dialog.tsx +66 -0
  55. package/src/client/components/ui/empty.tsx +32 -0
  56. package/src/client/components/ui/field.tsx +49 -0
  57. package/src/client/components/ui/input-group.tsx +96 -0
  58. package/src/client/components/ui/input.tsx +35 -0
  59. package/src/client/components/ui/native-select.tsx +57 -0
  60. package/src/client/components/ui/pagination.tsx +60 -0
  61. package/src/client/components/ui/popover.tsx +67 -0
  62. package/src/client/components/ui/radio-group.tsx +32 -0
  63. package/src/client/components/ui/sheet.tsx +13 -3
  64. package/src/client/components/ui/skeleton.tsx +19 -0
  65. package/src/client/components/ui/spinner.tsx +21 -0
  66. package/src/client/components/ui/table.tsx +101 -0
  67. package/src/client/components/ui/tabs.tsx +75 -0
  68. package/src/client/components/ui/textarea.tsx +17 -0
  69. package/src/client/components/ui/toast.tsx +215 -0
  70. package/src/client/components/ui/toggle-group.tsx +36 -0
  71. package/src/client/components/ui/toggle.tsx +37 -0
  72. package/src/client/components/ui/tooltip.tsx +168 -0
  73. package/src/client/components/ui/trace.tsx +96 -0
  74. package/src/client/conversations/ChatLayout.tsx +7 -3
  75. package/src/client/conversations/ConversationBrief.tsx +17 -6
  76. package/src/client/conversations/ConversationChildAgents.tsx +5 -5
  77. package/src/client/conversations/ConversationComposer.tsx +26 -12
  78. package/src/client/conversations/ConversationDetails.tsx +147 -62
  79. package/src/client/conversations/ConversationEventDetails.tsx +141 -0
  80. package/src/client/conversations/ConversationEventLog.tsx +150 -129
  81. package/src/client/conversations/ConversationHeader.tsx +13 -7
  82. package/src/client/conversations/ConversationHeaderActions.tsx +97 -93
  83. package/src/client/conversations/ConversationHomeList.tsx +1 -1
  84. package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
  85. package/src/client/conversations/ConversationMeta.tsx +55 -88
  86. package/src/client/conversations/ConversationPage.tsx +220 -101
  87. package/src/client/conversations/ConversationSidebar.tsx +49 -29
  88. package/src/client/conversations/ConversationUsageBreakdown.tsx +108 -65
  89. package/src/client/conversations/ConversationWatches.tsx +173 -0
  90. package/src/client/conversations/ConversationWorkspace.tsx +12 -22
  91. package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
  92. package/src/client/conversations/DetailsList.tsx +139 -0
  93. package/src/client/conversations/EventDetails.tsx +42 -8
  94. package/src/client/conversations/EventUsage.tsx +99 -74
  95. package/src/client/conversations/ForkConversationButton.tsx +27 -16
  96. package/src/client/conversations/ObjectCard.tsx +20 -8
  97. package/src/client/conversations/PendingMailboxStack.tsx +52 -38
  98. package/src/client/conversations/README.md +89 -13
  99. package/src/client/conversations/ShareConversationButton.tsx +28 -17
  100. package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
  101. package/src/client/conversations/ToolFrame.tsx +36 -52
  102. package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
  103. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
  104. package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
  105. package/src/client/conversations/TranscriptEventView.tsx +35 -16
  106. package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
  107. package/src/client/conversations/TranscriptLoading.tsx +1 -1
  108. package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
  109. package/src/client/conversations/TranscriptMessageContextView.tsx +35 -18
  110. package/src/client/conversations/TranscriptMessageView.tsx +6 -1
  111. package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
  112. package/src/client/conversations/TranscriptStructuredEventView.tsx +25 -15
  113. package/src/client/conversations/TranscriptSummary.tsx +34 -2
  114. package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
  115. package/src/client/conversations/TranscriptToolView.tsx +1 -1
  116. package/src/client/conversations/TranscriptTurnContextView.tsx +144 -65
  117. package/src/client/conversations/TranscriptView.tsx +63 -27
  118. package/src/client/conversations/eventLog.ts +58 -0
  119. package/src/client/conversations/eventTime.ts +60 -0
  120. package/src/client/conversations/queries.ts +29 -0
  121. package/src/client/conversations/transcriptBottomPinning.ts +34 -0
  122. package/src/client/conversations/transcriptMentions.tsx +18 -5
  123. package/src/client/format.ts +5 -0
  124. package/src/client/pages/ConnectionsPage.tsx +2 -2
  125. package/src/client/pages/PersonalTokensPage.tsx +4 -4
  126. package/src/client/pages/SettingsPage.tsx +9 -10
  127. package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
  128. package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
  129. package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
  130. package/src/client/pages/automations/AutomationDetails.tsx +193 -0
  131. package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
  132. package/src/client/pages/automations/AutomationEditor.tsx +41 -29
  133. package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
  134. package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
  135. package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
  136. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
  137. package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
  138. package/src/client/pages/automations/AutomationFilters.tsx +189 -74
  139. package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
  140. package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
  141. package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
  142. package/src/client/pages/automations/AutomationRow.tsx +12 -138
  143. package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
  144. package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
  145. package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
  146. package/src/client/pages/automations/AutomationStatus.tsx +201 -0
  147. package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
  148. package/src/client/pages/automations/AutomationsPage.tsx +80 -68
  149. package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
  150. package/src/client/pages/automations/README.md +57 -0
  151. package/src/client/pages/automations/TriggerFilters.tsx +22 -0
  152. package/src/client/pages/automations/automationFormat.ts +37 -23
  153. package/src/client/pages/code/CodeActivityChart.tsx +59 -101
  154. package/src/client/pages/code/CodePage.tsx +52 -55
  155. package/src/client/pages/code/RepositoryPage.tsx +6 -8
  156. package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
  157. package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
  158. package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
  159. package/src/client/pages/memory/MemoryDetails.tsx +218 -0
  160. package/src/client/pages/memory/MemoryPage.tsx +168 -112
  161. package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
  162. package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
  163. package/src/client/pages/memory/memoryRecord.ts +1 -1
  164. package/src/client/pages/people/ContributionGrid.tsx +23 -16
  165. package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
  166. package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
  167. package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
  168. package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
  169. package/src/client/pages/system/GuardianActivity.tsx +1 -2
  170. package/src/client/pages/system/PluginBarChart.tsx +106 -87
  171. package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
  172. package/src/client/pages/system/README.md +23 -0
  173. package/src/client/pages/system/SystemNavigation.tsx +5 -1
  174. package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
  175. package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
  176. package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
  177. package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
  178. package/src/client/pages/system/WorkspaceList.tsx +20 -30
  179. package/src/client/pages/system/WorkspacesPage.tsx +117 -119
  180. package/src/client/pages/system/sandboxSize.ts +8 -0
  181. package/src/client/pages/system/workspaceQueries.ts +53 -0
  182. package/src/client/pages/user/PluginUserPage.tsx +51 -2
  183. package/src/client/pages/user/pluginUserPageData.ts +1 -6
  184. package/src/client/styles.ts +4 -0
  185. package/src/client.tsx +7 -1
  186. package/src/mock-reporting/automation-collection.ts +4 -2
  187. package/src/mock-reporting/fixtures.ts +23 -7
  188. package/src/mock-reporting/routes.ts +12 -0
  189. package/src/mock-reporting/watches.ts +94 -0
  190. package/src/shell.ts +4 -9
  191. package/src/tailwind.css +0 -10
  192. package/src/client/components/Button.tsx +0 -118
  193. package/src/client/components/Field.tsx +0 -53
  194. package/src/client/components/SegmentedTabs.tsx +0 -119
  195. package/src/client/components/Select.tsx +0 -52
  196. package/src/client/components/Skeleton.tsx +0 -17
  197. package/src/client/components/TextInput.tsx +0 -58
  198. package/src/client/components/Tooltip.tsx +0 -216
  199. package/src/client/components/charts/useChartLayout.ts +0 -27
  200. package/src/client/components/ui/sheet.LICENSE +0 -21
  201. package/src/client/conversations/ConversationDetailsFrame.tsx +0 -119
  202. package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
  203. package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
  204. package/src/client/pages/dev/ChartFixtures.tsx +0 -325
  205. package/src/client/pages/dev/ComponentsPage.tsx +0 -899
  206. package/src/client/pages/dev/ConversationFixture.tsx +0 -231
  207. package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
  208. package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
  209. package/src/client/pages/dev/SelectFixtures.tsx +0 -62
  210. package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
  211. package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
  212. package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
  213. package/src/client/pages/system/SnapshotSummary.tsx +0 -78
@@ -1,21 +1,16 @@
1
- import { useMemo, useState } from "react";
1
+ import { useMemo, useState, type KeyboardEvent } from "react";
2
2
  import { ArrowLeft } from "lucide-react";
3
- import {
4
- Link,
5
- Navigate,
6
- useNavigate,
7
- useParams,
8
- useSearchParams,
9
- } from "react-router";
3
+ import { Link, Navigate, useParams, useSearchParams } from "react-router";
10
4
  import type {
11
5
  AutomationExecution,
12
6
  AutomationExecutionList,
7
+ AutomationRun,
13
8
  } from "@sentry/junior/api/schema";
14
9
 
15
10
  import { useAutomationExecutionsData } from "../../api";
16
11
  import { InlineError } from "../../components/InlineError";
17
12
  import { PageContentSkeleton } from "../../components/PageContentSkeleton";
18
- import type { StatusChipTone } from "../../components/StatusChip";
13
+ import { SelectableRow } from "../../components/SelectableRow";
19
14
  import { StatusDot } from "../../components/StatusDot";
20
15
  import {
21
16
  selectTimeSeries,
@@ -24,83 +19,130 @@ import {
24
19
  } from "../../components/controls/TimeRangeSelector";
25
20
  import { Card } from "../../components/layout/Card";
26
21
  import { PageHeader } from "../../components/layout/PageHeader";
27
- import { conversationPath } from "../../conversations/conversationRoutes";
28
22
  import {
29
23
  formatCompactNumber,
30
24
  formatCostSummary,
31
25
  formatRuntime,
32
- formatTime,
33
26
  automationPath,
34
27
  } from "../../format";
35
28
  import { DashboardApiError } from "../../http";
36
29
  import { pathWithSearch } from "../../searchParams";
37
30
  import { cn } from "../../styles";
38
31
  import { AutomationExecutionStatusChart } from "./AutomationExecutionStatusChart";
32
+ import {
33
+ AUTOMATION_RUN_PARAM,
34
+ AutomationRunDetails,
35
+ AutomationRunsFrame,
36
+ RunConversationLink,
37
+ useAutomationRunSelection,
38
+ } from "./AutomationRunDetails";
39
+ import {
40
+ automationRunStatusTone,
41
+ automationRunTitle,
42
+ formatAutomationRunTime,
43
+ } from "./automationFormat";
39
44
 
40
45
  /** Leading conversation column flexes; metric columns stay equal fixed widths. */
41
- const EXECUTION_GRID = "grid-cols-[minmax(0,1fr)_5.5rem_5.5rem_5.5rem_auto]";
46
+ const EXECUTION_GRID =
47
+ "grid-cols-[minmax(0,1fr)_5.5rem_5.5rem_5.5rem_auto_2rem]";
42
48
 
43
- /** Render one automation's terminal executions as a conversation-style list. */
49
+ /** Render one automation's terminal executions beside the selected run. */
44
50
  export function AutomationExecutionsPage(props: { enabled: boolean }) {
45
51
  const { automationId } = useParams();
46
52
  const [searchParams] = useSearchParams();
47
53
  const query = useAutomationExecutionsData(props.enabled, automationId);
54
+ const backSearch = new URLSearchParams(searchParams);
55
+ backSearch.delete(AUTOMATION_RUN_PARAM);
48
56
  const backTo = pathWithSearch(
49
57
  automationId ? automationPath(automationId) : "/automations/list",
50
- searchParams,
58
+ backSearch,
59
+ );
60
+ const runs = useMemo(
61
+ () =>
62
+ query.data
63
+ ? query.data.executions.map(
64
+ (execution): AutomationRun => ({
65
+ ...execution,
66
+ automationId: query.data.automation.id,
67
+ automationTitle: query.data.automation.title,
68
+ kind: query.data.automation.kind,
69
+ }),
70
+ )
71
+ : [],
72
+ [query.data],
73
+ );
74
+ const selection = useAutomationRunSelection(
75
+ runs.map((run) => run.executionId),
51
76
  );
52
77
 
53
78
  if (!automationId) {
54
79
  return <Navigate replace to="/automations" />;
55
80
  }
56
- if (!query.data && !query.error) {
57
- return (
58
- <>
59
- <PageHeader
60
- description="Terminal runs for one scheduled or event automation."
61
- title="Automation executions"
62
- />
63
- <PageContentSkeleton
64
- label="Loading automation executions"
65
- variant="list"
66
- />
67
- </>
68
- );
69
- }
70
- if (query.error || !query.data) {
71
- return (
72
- <>
73
- <PageHeader
74
- description="Terminal runs for one scheduled or event automation."
75
- title="Automation executions"
81
+ const loading = !query.data && !query.error;
82
+ return (
83
+ <AutomationRunsFrame
84
+ details={
85
+ selection.runId ? (
86
+ <AutomationRunDetails
87
+ key={selection.runId}
88
+ loading={loading}
89
+ onClose={selection.close}
90
+ run={runs.find((run) => run.executionId === selection.runId)}
91
+ showAutomation={false}
92
+ />
93
+ ) : null
94
+ }
95
+ label="Executions list"
96
+ listRef={selection.listRef}
97
+ >
98
+ {query.data ? (
99
+ <AutomationExecutionsView
100
+ backTo={backTo}
101
+ data={query.data}
102
+ onRowsKeyDown={selection.onRowsKeyDown}
103
+ onSelect={selection.toggle}
104
+ selectedRunId={selection.runId}
76
105
  />
77
- <Card padding="md">
78
- <InlineError>
79
- {query.error instanceof DashboardApiError &&
80
- query.error.status === 404
81
- ? "This automation was not found or is not visible to you."
82
- : "Automation executions could not be loaded. Try again."}
83
- </InlineError>
84
- <Link
85
- className="mt-3 inline-flex items-center gap-2 font-sans text-xs text-dashboard-text-muted no-underline hover:text-dashboard-text"
86
- to={
87
- automationId ? automationPath(automationId) : "/automations/list"
88
- }
89
- >
90
- <ArrowLeft aria-hidden="true" size={14} />
91
- Back to automations
92
- </Link>
93
- </Card>
94
- </>
95
- );
96
- }
97
-
98
- return <AutomationExecutionsView backTo={backTo} data={query.data} />;
106
+ ) : (
107
+ <>
108
+ <PageHeader
109
+ description="Terminal runs for one scheduled or event automation."
110
+ title="Automation executions"
111
+ />
112
+ {loading ? (
113
+ <PageContentSkeleton
114
+ label="Loading automation executions"
115
+ variant="list"
116
+ />
117
+ ) : (
118
+ <Card padding="md">
119
+ <InlineError>
120
+ {query.error instanceof DashboardApiError &&
121
+ query.error.status === 404
122
+ ? "This automation was not found or is not visible to you."
123
+ : "Automation executions could not be loaded. Try again."}
124
+ </InlineError>
125
+ <Link
126
+ className="mt-3 inline-flex items-center gap-2 font-sans text-xs text-dashboard-text-muted no-underline hover:text-dashboard-text"
127
+ to={automationPath(automationId)}
128
+ >
129
+ <ArrowLeft aria-hidden="true" size={14} />
130
+ Back to automations
131
+ </Link>
132
+ </Card>
133
+ )}
134
+ </>
135
+ )}
136
+ </AutomationRunsFrame>
137
+ );
99
138
  }
100
139
 
101
140
  function AutomationExecutionsView(props: {
102
141
  backTo: string;
103
142
  data: AutomationExecutionList;
143
+ onRowsKeyDown(event: KeyboardEvent<HTMLElement>): void;
144
+ onSelect(executionId: string): void;
145
+ selectedRunId: string | undefined;
104
146
  }) {
105
147
  const { data } = props;
106
148
  const [range, setRange] = useState<TimeRangeDays>(30);
@@ -171,24 +213,29 @@ function AutomationExecutionsView(props: {
171
213
  <div className="min-w-0 overflow-x-auto">
172
214
  <div className="min-w-[36rem]">
173
215
  <div
216
+ aria-hidden="true"
174
217
  className={cn(
175
218
  "sticky top-0 z-[1] hidden items-center gap-4 border-b border-dashboard-border-subtle bg-dashboard-overlay-soft px-4 py-2.5 font-sans font-medium text-xs text-dashboard-text-muted md:grid",
176
219
  EXECUTION_GRID,
177
220
  )}
178
- role="row"
179
221
  >
180
- <div>Conversation</div>
222
+ <div>Run</div>
181
223
  <div>Duration</div>
182
224
  <div>Tokens</div>
183
225
  <div>Cost</div>
184
- <div className="sr-only">Status</div>
185
226
  </div>
186
- <div className="min-w-0" role="table">
227
+ <div
228
+ className="min-w-0"
229
+ onKeyDown={props.onRowsKeyDown}
230
+ role="list"
231
+ >
187
232
  {data.executions.map((execution) => (
188
233
  <ExecutionRow
189
234
  execution={execution}
190
235
  fallbackTitle={data.automation.title}
191
236
  key={execution.executionId}
237
+ onSelect={() => props.onSelect(execution.executionId)}
238
+ selected={props.selectedRunId === execution.executionId}
192
239
  />
193
240
  ))}
194
241
  </div>
@@ -209,12 +256,10 @@ function AutomationExecutionsView(props: {
209
256
  function ExecutionRow(props: {
210
257
  execution: AutomationExecution;
211
258
  fallbackTitle: string;
259
+ onSelect(): void;
260
+ selected: boolean;
212
261
  }) {
213
- const { execution, fallbackTitle } = props;
214
- const navigate = useNavigate();
215
- const title =
216
- execution.title?.trim() ||
217
- (execution.conversationId ? fallbackTitle : "No conversation");
262
+ const { execution } = props;
218
263
  const costLabel =
219
264
  formatCostSummary(
220
265
  execution.costUsd === undefined
@@ -226,56 +271,52 @@ function ExecutionRow(props: {
226
271
  execution.totalTokens === undefined
227
272
  ? "—"
228
273
  : formatCompactNumber(execution.totalTokens);
229
- const openConversation = () => {
230
- if (!execution.conversationId) return;
231
- navigate(conversationPath(execution.conversationId));
232
- };
274
+ const tone = automationRunStatusTone(execution.status);
233
275
 
234
276
  return (
235
277
  <div
236
- aria-disabled={!execution.conversationId}
237
- className={cn(
238
- "group grid min-w-0 items-center gap-4 overflow-hidden border-b border-dashboard-border-subtle px-4 py-3 text-left text-inherit transition-colors last:border-b-0 max-md:grid-cols-1 max-md:gap-y-2 max-md:px-4 max-md:py-3.5 md:grid",
239
- EXECUTION_GRID,
240
- execution.conversationId
241
- ? "cursor-pointer hover:bg-dashboard-fill-soft"
242
- : "cursor-default opacity-80",
243
- )}
244
- onClick={openConversation}
245
- onKeyDown={(event) => {
246
- if (!execution.conversationId) return;
247
- if (event.key === "Enter" || event.key === " ") {
248
- event.preventDefault();
249
- openConversation();
250
- }
251
- }}
252
- role={execution.conversationId ? "link" : "row"}
253
- tabIndex={execution.conversationId ? 0 : undefined}
278
+ className="border-b border-dashboard-border-subtle last:border-b-0"
279
+ data-run-id={execution.executionId}
280
+ role="listitem"
254
281
  >
255
- <div className="min-w-0 overflow-hidden">
256
- <div className="flex min-w-0 items-center gap-2 overflow-hidden">
257
- <StatusDot
258
- className="md:hidden"
259
- label={execution.status}
260
- tone={runStatusTone(execution.status)}
261
- />
262
- <div className="min-w-0 truncate text-sm font-medium leading-snug text-dashboard-text">
263
- {title}
264
- </div>
282
+ <SelectableRow
283
+ className={cn(
284
+ "grid min-w-0 items-center gap-4 overflow-hidden px-4 py-3 max-md:grid-cols-1 max-md:gap-y-2 max-md:py-3.5",
285
+ EXECUTION_GRID,
286
+ )}
287
+ onSelect={props.onSelect}
288
+ selected={props.selected}
289
+ >
290
+ <button
291
+ aria-expanded={props.selected}
292
+ className="block min-w-0 cursor-pointer rounded-md border-0 bg-transparent p-0 text-left focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
293
+ onClick={props.onSelect}
294
+ type="button"
295
+ >
296
+ <span className="flex min-w-0 items-center gap-2 overflow-hidden">
297
+ <StatusDot
298
+ className="md:hidden"
299
+ label={execution.status}
300
+ tone={tone}
301
+ />
302
+ <span className="min-w-0 truncate text-sm font-medium leading-snug text-dashboard-text">
303
+ {automationRunTitle(execution, props.fallbackTitle)}
304
+ </span>
305
+ </span>
306
+ <span className="mt-1 block truncate text-xs leading-relaxed text-dashboard-text-muted">
307
+ {formatAutomationRunTime(execution.executedAt)}
308
+ </span>
309
+ </button>
310
+ <MetricCell label="Duration" value={durationLabel} />
311
+ <MetricCell label="Tokens" value={tokensLabel} />
312
+ <MetricCell label="Cost" value={costLabel} />
313
+ <div className="hidden justify-self-center md:block">
314
+ <StatusDot label={execution.status} tone={tone} />
265
315
  </div>
266
- <div className="mt-1 truncate text-xs leading-relaxed text-dashboard-text-muted">
267
- {formatRunDate(execution.executedAt)}
316
+ <div className="max-md:hidden">
317
+ <RunConversationLink conversationId={execution.conversationId} />
268
318
  </div>
269
- </div>
270
- <MetricCell label="Duration" value={durationLabel} />
271
- <MetricCell label="Tokens" value={tokensLabel} />
272
- <MetricCell label="Cost" value={costLabel} />
273
- <div className="hidden justify-self-center md:block">
274
- <StatusDot
275
- label={execution.status}
276
- tone={runStatusTone(execution.status)}
277
- />
278
- </div>
319
+ </SelectableRow>
279
320
  </div>
280
321
  );
281
322
  }
@@ -297,12 +338,6 @@ function MetricCell(props: { label: string; value: string }) {
297
338
  );
298
339
  }
299
340
 
300
- function runStatusTone(status: AutomationExecution["status"]): StatusChipTone {
301
- if (status === "failed") return "danger";
302
- if (status === "blocked") return "warning";
303
- return "success";
304
- }
305
-
306
341
  function countByStatus(executions: AutomationExecution[]) {
307
342
  return executions.reduce(
308
343
  (counts, execution) => {
@@ -312,14 +347,3 @@ function countByStatus(executions: AutomationExecution[]) {
312
347
  { blocked: 0, completed: 0, failed: 0 },
313
348
  );
314
349
  }
315
-
316
- function formatRunDate(value: string): string {
317
- return formatTime(value, {
318
- day: "numeric",
319
- hour: "numeric",
320
- minute: "2-digit",
321
- month: "short",
322
- timeZoneName: "short",
323
- year: "numeric",
324
- });
325
- }