@sentry/junior-dashboard 0.263.0 → 0.265.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +2 -5
  3. package/dist/app.js +119 -35
  4. package/dist/client.js +148 -135
  5. package/dist/index.d.ts +0 -1
  6. package/dist/index.js +119 -35
  7. package/dist/tailwind.css +1 -1
  8. package/package.json +9 -4
  9. package/src/api/schema.ts +0 -1
  10. package/src/app.ts +5 -20
  11. package/src/client/App.tsx +29 -31
  12. package/src/client/api.ts +7 -0
  13. package/src/client/components/BottomSheet.tsx +26 -18
  14. package/src/client/components/CopyButton.tsx +83 -0
  15. package/src/client/components/DetailsPanel.tsx +133 -0
  16. package/src/client/components/Drawer.tsx +34 -33
  17. package/src/client/components/FilterBar.tsx +16 -31
  18. package/src/client/components/Metric.tsx +33 -23
  19. package/src/client/components/PageContentSkeleton.tsx +1 -1
  20. package/src/client/components/Pagination.tsx +1 -1
  21. package/src/client/components/ParticipantAvatarStack.tsx +40 -36
  22. package/src/client/components/ProfileMenu.tsx +27 -93
  23. package/src/client/components/README.md +101 -64
  24. package/src/client/components/SegmentedNav.tsx +1 -1
  25. package/src/client/components/ShimmerText.tsx +2 -6
  26. package/src/client/components/ThemeSwitcher.tsx +19 -15
  27. package/src/client/components/charts/ActivityChart.tsx +173 -288
  28. package/src/client/components/charts/CacheWrites.tsx +63 -50
  29. package/src/client/components/charts/InputCacheChart.tsx +137 -131
  30. package/src/client/components/charts/README.md +4 -4
  31. package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
  32. package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
  33. package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
  34. package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
  35. package/src/client/components/layout/DashboardHeader.tsx +44 -90
  36. package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
  37. package/src/client/components/layout/VisualViewportShell.tsx +2 -2
  38. package/src/client/components/ui/accordion.tsx +77 -0
  39. package/src/client/components/ui/alert-dialog.tsx +89 -0
  40. package/src/client/components/ui/button.tsx +85 -0
  41. package/src/client/components/ui/chart.tsx +74 -0
  42. package/src/client/components/ui/checkbox.tsx +26 -0
  43. package/src/client/components/ui/collapsible.tsx +22 -0
  44. package/src/client/components/ui/field.tsx +49 -0
  45. package/src/client/components/ui/input.tsx +35 -0
  46. package/src/client/components/ui/native-select.tsx +57 -0
  47. package/src/client/components/ui/popover.tsx +67 -0
  48. package/src/client/components/ui/radio-group.tsx +32 -0
  49. package/src/client/components/ui/sheet.tsx +151 -0
  50. package/src/client/components/ui/skeleton.tsx +19 -0
  51. package/src/client/components/ui/table.tsx +80 -0
  52. package/src/client/components/ui/tabs.tsx +75 -0
  53. package/src/client/components/ui/textarea.tsx +17 -0
  54. package/src/client/components/ui/toast.tsx +215 -0
  55. package/src/client/components/ui/toggle-group.tsx +36 -0
  56. package/src/client/components/ui/toggle.tsx +37 -0
  57. package/src/client/components/ui/tooltip.tsx +168 -0
  58. package/src/client/components/ui/trace.tsx +96 -0
  59. package/src/client/components/ui/useSheet.ts +74 -0
  60. package/src/client/conversations/ChatLayout.tsx +7 -3
  61. package/src/client/conversations/ConversationBrief.tsx +17 -6
  62. package/src/client/conversations/ConversationChildAgents.tsx +87 -58
  63. package/src/client/conversations/ConversationComposer.tsx +26 -12
  64. package/src/client/conversations/ConversationDetails.tsx +198 -95
  65. package/src/client/conversations/ConversationEventDetails.tsx +141 -0
  66. package/src/client/conversations/ConversationEventLog.tsx +150 -129
  67. package/src/client/conversations/ConversationHeader.tsx +8 -3
  68. package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
  69. package/src/client/conversations/ConversationHomeList.tsx +1 -1
  70. package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
  71. package/src/client/conversations/ConversationMeta.tsx +111 -92
  72. package/src/client/conversations/ConversationPage.tsx +241 -110
  73. package/src/client/conversations/ConversationSidebar.tsx +48 -29
  74. package/src/client/conversations/ConversationUsageBreakdown.tsx +16 -3
  75. package/src/client/conversations/ConversationWatches.tsx +165 -0
  76. package/src/client/conversations/ConversationWorkspace.tsx +12 -22
  77. package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
  78. package/src/client/conversations/DetailsList.tsx +124 -0
  79. package/src/client/conversations/EventDetails.tsx +42 -8
  80. package/src/client/conversations/ForkConversationButton.tsx +27 -16
  81. package/src/client/conversations/ObjectCard.tsx +20 -8
  82. package/src/client/conversations/PendingMailboxStack.tsx +52 -33
  83. package/src/client/conversations/README.md +93 -19
  84. package/src/client/conversations/ShareConversationButton.tsx +28 -3
  85. package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
  86. package/src/client/conversations/ToolFrame.tsx +36 -52
  87. package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
  88. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
  89. package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
  90. package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
  91. package/src/client/conversations/TranscriptLoading.tsx +1 -1
  92. package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
  93. package/src/client/conversations/TranscriptMessageContextView.tsx +8 -2
  94. package/src/client/conversations/TranscriptMessageView.tsx +6 -1
  95. package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
  96. package/src/client/conversations/TranscriptSubagentView.tsx +3 -3
  97. package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
  98. package/src/client/conversations/TranscriptToolView.tsx +1 -1
  99. package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
  100. package/src/client/conversations/TranscriptView.tsx +63 -27
  101. package/src/client/conversations/eventLog.ts +57 -0
  102. package/src/client/conversations/eventTime.ts +60 -0
  103. package/src/client/conversations/queries.ts +29 -0
  104. package/src/client/conversations/transcriptBottomPinning.ts +34 -0
  105. package/src/client/format.ts +5 -0
  106. package/src/client/lib/utils.ts +1 -0
  107. package/src/client/pages/ConnectionsPage.tsx +2 -2
  108. package/src/client/pages/PersonalTokensPage.tsx +4 -4
  109. package/src/client/pages/SettingsPage.tsx +9 -10
  110. package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
  111. package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
  112. package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
  113. package/src/client/pages/automations/AutomationDetails.tsx +193 -0
  114. package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
  115. package/src/client/pages/automations/AutomationEditor.tsx +41 -29
  116. package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
  117. package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
  118. package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
  119. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
  120. package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
  121. package/src/client/pages/automations/AutomationFilters.tsx +189 -74
  122. package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
  123. package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
  124. package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
  125. package/src/client/pages/automations/AutomationRow.tsx +12 -138
  126. package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
  127. package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
  128. package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
  129. package/src/client/pages/automations/AutomationStatus.tsx +201 -0
  130. package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
  131. package/src/client/pages/automations/AutomationsPage.tsx +80 -68
  132. package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
  133. package/src/client/pages/automations/README.md +57 -0
  134. package/src/client/pages/automations/TriggerFilters.tsx +22 -0
  135. package/src/client/pages/automations/automationFormat.ts +37 -23
  136. package/src/client/pages/code/CodeActivityChart.tsx +59 -101
  137. package/src/client/pages/code/CodePage.tsx +52 -55
  138. package/src/client/pages/code/RepositoryPage.tsx +6 -8
  139. package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
  140. package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
  141. package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
  142. package/src/client/pages/memory/MemoryDetails.tsx +218 -0
  143. package/src/client/pages/memory/MemoryPage.tsx +168 -112
  144. package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
  145. package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
  146. package/src/client/pages/memory/memoryRecord.ts +1 -1
  147. package/src/client/pages/people/ContributionGrid.tsx +23 -16
  148. package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
  149. package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
  150. package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
  151. package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
  152. package/src/client/pages/system/GuardianActivity.tsx +1 -2
  153. package/src/client/pages/system/PluginBarChart.tsx +106 -87
  154. package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
  155. package/src/client/pages/system/README.md +23 -0
  156. package/src/client/pages/system/SystemNavigation.tsx +5 -1
  157. package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
  158. package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
  159. package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
  160. package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
  161. package/src/client/pages/system/WorkspaceList.tsx +20 -30
  162. package/src/client/pages/system/WorkspacesPage.tsx +117 -119
  163. package/src/client/pages/system/sandboxSize.ts +8 -0
  164. package/src/client/pages/system/workspaceQueries.ts +53 -0
  165. package/src/client/pages/user/PluginUserPage.tsx +51 -2
  166. package/src/client/pages/user/pluginUserPageData.ts +1 -6
  167. package/src/client/styles.ts +4 -0
  168. package/src/client.tsx +7 -1
  169. package/src/mock-reporting/automation-collection.ts +4 -2
  170. package/src/mock-reporting/fixtures.ts +23 -7
  171. package/src/mock-reporting/routes.ts +12 -0
  172. package/src/mock-reporting/watches.ts +94 -0
  173. package/src/shell.ts +4 -9
  174. package/src/tailwind.css +77 -10
  175. package/src/client/components/Button.tsx +0 -118
  176. package/src/client/components/Field.tsx +0 -53
  177. package/src/client/components/SegmentedTabs.tsx +0 -119
  178. package/src/client/components/Select.tsx +0 -52
  179. package/src/client/components/Sheet.LICENSE +0 -21
  180. package/src/client/components/Sheet.tsx +0 -141
  181. package/src/client/components/Skeleton.tsx +0 -17
  182. package/src/client/components/TextInput.tsx +0 -58
  183. package/src/client/components/Tooltip.tsx +0 -216
  184. package/src/client/components/charts/useChartLayout.ts +0 -27
  185. package/src/client/conversations/ConversationDetailsFrame.tsx +0 -91
  186. package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
  187. package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
  188. package/src/client/pages/dev/ChartFixtures.tsx +0 -325
  189. package/src/client/pages/dev/ComponentsPage.tsx +0 -899
  190. package/src/client/pages/dev/ConversationFixture.tsx +0 -231
  191. package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
  192. package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
  193. package/src/client/pages/dev/SelectFixtures.tsx +0 -62
  194. package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
  195. package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
  196. package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
  197. package/src/client/pages/system/SnapshotSummary.tsx +0 -78
@@ -1,11 +1,10 @@
1
1
  import type { AutomationExecutionStatusDay } from "@sentry/junior/api/schema";
2
+ import { Bar, BarChart, CartesianGrid } from "recharts";
2
3
  import {
3
- ActivityChartDateLabels,
4
- ActivityChartGrid,
5
- ActivityChartTooltip,
6
- ActivityTooltipRows,
7
- ChartSvg,
8
- createActivityChartLayout,
4
+ activityChartMargin,
5
+ ActivityChartTooltipContent,
6
+ ActivityChartXAxis,
7
+ ActivityChartYAxis,
9
8
  } from "../../components/charts/ActivityChart";
10
9
  import { ChartLegend } from "../../components/charts/ChartLegend";
11
10
  import {
@@ -13,6 +12,7 @@ import {
13
12
  type TimeRangeDays,
14
13
  } from "../../components/controls/TimeRangeSelector";
15
14
  import { Card } from "../../components/layout/Card";
15
+ import { ChartContainer, ChartTooltip } from "../../components/ui/chart";
16
16
 
17
17
  const series = [
18
18
  { color: "var(--color-emerald-300)", key: "completed", label: "Completed" },
@@ -20,6 +20,10 @@ const series = [
20
20
  { color: "var(--color-amber-300)", key: "blocked", label: "Blocked" },
21
21
  ] as const;
22
22
 
23
+ const chartConfig = Object.fromEntries(
24
+ series.map((item) => [item.key, { color: item.color, label: item.label }]),
25
+ );
26
+
23
27
  /** Render one automation's terminal executions by status over a trailing window. */
24
28
  export function AutomationExecutionStatusChart(props: {
25
29
  bucketUnit?: TimeRangeBucketUnit;
@@ -30,12 +34,8 @@ export function AutomationExecutionStatusChart(props: {
30
34
  const bucketUnit = props.bucketUnit ?? "day";
31
35
 
32
36
  const days = props.days;
33
- const layout = createActivityChartLayout(220);
34
37
  const totals = days.map((day) => day.completed + day.failed + day.blocked);
35
38
  const maximum = Math.max(1, ...totals);
36
- const step =
37
- days.length > 0 ? layout.plotWidth / days.length : layout.plotWidth;
38
- const barWidth = Math.max(2, Math.min(13, step * 0.68));
39
39
  const hasExecutions = totals.some((total) => total > 0);
40
40
 
41
41
  return (
@@ -59,67 +59,44 @@ export function AutomationExecutionStatusChart(props: {
59
59
  />
60
60
 
61
61
  <div className="relative mt-3 overflow-hidden">
62
- <ChartSvg
63
- aria-label={`Automation executions during the last ${props.range === 1 ? "24 hours" : `${props.range} days`}`}
64
- className="min-h-40"
65
- layout={layout}
62
+ <ChartContainer
63
+ className="aspect-auto h-55 w-full"
64
+ config={chartConfig}
66
65
  >
67
- <ActivityChartGrid layout={layout} maximum={maximum} />
68
- {days.map((day, dayIndex) => {
69
- let stackedHeight = 0;
70
- const x = layout.left + dayIndex * step + (step - barWidth) / 2;
71
- const total = totals[dayIndex] ?? 0;
72
- return (
73
- <ActivityChartTooltip
74
- key={day.date}
75
- content={
76
- <ActivityTooltipRows
77
- rows={[
78
- ["completed", day.completed],
79
- ["failed", day.failed],
80
- ["blocked", day.blocked],
81
- ["total", total],
82
- ]}
83
- />
84
- }
85
- date={day.date}
86
- summary={`${day.completed} completed, ${day.failed} failed, ${day.blocked} blocked, ${total} total`}
87
- >
88
- <g tabIndex={0}>
89
- {series.map((item) => {
90
- const value = day[item.key];
91
- const segmentHeight = (value / maximum) * layout.plotHeight;
92
- stackedHeight += segmentHeight;
93
- return (
94
- <rect
95
- fill={item.color}
96
- height={segmentHeight}
97
- key={item.key}
98
- opacity={0.82}
99
- rx="1"
100
- width={barWidth}
101
- x={x}
102
- y={layout.top + layout.plotHeight - stackedHeight}
103
- />
104
- );
105
- })}
106
- <rect
107
- fill="transparent"
108
- height={layout.plotHeight}
109
- width={Math.max(barWidth, 8)}
110
- x={x - (Math.max(barWidth, 8) - barWidth) / 2}
111
- y={layout.top}
112
- />
113
- </g>
114
- </ActivityChartTooltip>
115
- );
116
- })}
117
- <ActivityChartDateLabels
118
- dates={days.map((day) => day.date)}
119
- layout={layout}
120
- xPosition={(index) => layout.left + index * step + step / 2}
121
- />
122
- </ChartSvg>
66
+ <BarChart
67
+ aria-label={`Automation executions during the last ${props.range === 1 ? "24 hours" : `${props.range} days`}`}
68
+ data={days}
69
+ margin={activityChartMargin}
70
+ role="img"
71
+ >
72
+ <CartesianGrid strokeDasharray="3 5" vertical={false} />
73
+ <ActivityChartYAxis maximum={maximum} />
74
+ <ActivityChartXAxis keys={days.map((day) => day.date)} />
75
+ <ChartTooltip
76
+ content={
77
+ <ActivityChartTooltipContent
78
+ rows={(day: AutomationExecutionStatusDay) => [
79
+ ["completed", day.completed],
80
+ ["failed", day.failed],
81
+ ["blocked", day.blocked],
82
+ ["total", day.completed + day.failed + day.blocked],
83
+ ]}
84
+ />
85
+ }
86
+ />
87
+ {series.map((item) => (
88
+ <Bar
89
+ dataKey={item.key}
90
+ fill={`var(--color-${item.key})`}
91
+ fillOpacity={0.82}
92
+ isAnimationActive={false}
93
+ key={item.key}
94
+ maxBarSize={13}
95
+ stackId="status"
96
+ />
97
+ ))}
98
+ </BarChart>
99
+ </ChartContainer>
123
100
  {!hasExecutions ? (
124
101
  <div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-sans text-xs text-dashboard-text-muted">
125
102
  No automation executions in this period.
@@ -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
- }