@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,10 +1,8 @@
1
1
  import { useEffect, useMemo, useState } from "react";
2
2
  import { CalendarClock, Zap } from "lucide-react";
3
- import { useNavigate } from "react-router";
4
3
  import type { AutomationRun } from "@sentry/junior/api/schema";
5
4
 
6
5
  import { useAutomationRunsData } from "../../api";
7
- import { ToggleButton } from "../../components/Button";
8
6
  import { FilterBar, FilterGroup } from "../../components/FilterBar";
9
7
  import { InlineError } from "../../components/InlineError";
10
8
  import { PageContentSkeleton } from "../../components/PageContentSkeleton";
@@ -13,22 +11,32 @@ import {
13
11
  pageItems,
14
12
  PagePagination,
15
13
  } from "../../components/Pagination";
16
- import type { StatusChipTone } from "../../components/StatusChip";
14
+ import { SelectableRow } from "../../components/SelectableRow";
17
15
  import { StatusDot } from "../../components/StatusDot";
16
+ import { ToggleGroup, ToggleGroupItem } from "../../components/ui/toggle-group";
18
17
  import { Card } from "../../components/layout/Card";
19
18
  import { PageHeader } from "../../components/layout/PageHeader";
20
- import { conversationPath } from "../../conversations/conversationRoutes";
21
19
  import {
22
20
  formatCompactNumber,
23
21
  formatCostSummary,
24
22
  formatRuntime,
25
- formatTime,
26
23
  } from "../../format";
27
24
  import {
28
25
  useDebouncedSearchParam,
29
26
  useSearchParamEnum,
30
27
  } from "../../searchParams";
31
28
  import { cn } from "../../styles";
29
+ import {
30
+ AutomationRunDetails,
31
+ AutomationRunsFrame,
32
+ RunConversationLink,
33
+ useAutomationRunSelection,
34
+ } from "./AutomationRunDetails";
35
+ import {
36
+ automationRunStatusTone,
37
+ automationRunTitle,
38
+ formatAutomationRunTime,
39
+ } from "./automationFormat";
32
40
 
33
41
  const RUN_PAGE_SIZE = 25;
34
42
  const RUN_KINDS = ["all", "scheduled", "event"] as const;
@@ -36,7 +44,7 @@ const RUN_STATUSES = ["all", "completed", "failed", "blocked"] as const;
36
44
  const EMPTY_RUNS: AutomationRun[] = [];
37
45
  /** Leading label columns flex; metric columns stay equal fixed widths. */
38
46
  const RUN_GRID =
39
- "grid-cols-[minmax(0,1.4fr)_minmax(0,1.2fr)_5.5rem_5.5rem_5.5rem]";
47
+ "grid-cols-[minmax(0,1.4fr)_minmax(0,1.2fr)_5.5rem_5.5rem_5.5rem_2rem]";
40
48
 
41
49
  type RunKindFilter = (typeof RUN_KINDS)[number];
42
50
  type RunStatusFilter = (typeof RUN_STATUSES)[number];
@@ -85,9 +93,29 @@ export function AutomationRunsPage(props: { enabled: boolean }) {
85
93
  }, [page, totalPages]);
86
94
 
87
95
  const loading = !query.data && !query.error;
96
+ const selection = useAutomationRunSelection(
97
+ pagedRuns.map((run) => run.executionId),
98
+ );
99
+ const selectedRun = selection.runId
100
+ ? runs.find((run) => run.executionId === selection.runId)
101
+ : undefined;
88
102
 
89
103
  return (
90
- <>
104
+ <AutomationRunsFrame
105
+ details={
106
+ selection.runId ? (
107
+ <AutomationRunDetails
108
+ key={selection.runId}
109
+ loading={loading}
110
+ onClose={selection.close}
111
+ run={selectedRun}
112
+ showAutomation
113
+ />
114
+ ) : null
115
+ }
116
+ label="Runs list"
117
+ listRef={selection.listRef}
118
+ >
91
119
  <PageHeader
92
120
  description="Newest runs across your automations and automations in public destinations."
93
121
  title="Runs"
@@ -105,28 +133,34 @@ export function AutomationRunsPage(props: { enabled: boolean }) {
105
133
  }}
106
134
  >
107
135
  <FilterGroup label="Type">
108
- {RUN_KINDS.map((value) => (
109
- <ToggleButton
110
- key={value}
111
- onClick={() => setKind(value)}
112
- pressed={kind === value}
113
- variant="pill"
114
- >
115
- {value}
116
- </ToggleButton>
117
- ))}
136
+ <ToggleGroup
137
+ className="flex-wrap gap-2"
138
+ onValueChange={([next]) => {
139
+ if (next) setKind(next);
140
+ }}
141
+ value={[kind]}
142
+ >
143
+ {RUN_KINDS.map((value) => (
144
+ <ToggleGroupItem key={value} value={value} variant="pill">
145
+ {value}
146
+ </ToggleGroupItem>
147
+ ))}
148
+ </ToggleGroup>
118
149
  </FilterGroup>
119
150
  <FilterGroup label="Status">
120
- {RUN_STATUSES.map((value) => (
121
- <ToggleButton
122
- key={value}
123
- onClick={() => setStatus(value)}
124
- pressed={status === value}
125
- variant="pill"
126
- >
127
- {value}
128
- </ToggleButton>
129
- ))}
151
+ <ToggleGroup
152
+ className="flex-wrap gap-2"
153
+ onValueChange={([next]) => {
154
+ if (next) setStatus(next);
155
+ }}
156
+ value={[status]}
157
+ >
158
+ {RUN_STATUSES.map((value) => (
159
+ <ToggleGroupItem key={value} value={value} variant="pill">
160
+ {value}
161
+ </ToggleGroupItem>
162
+ ))}
163
+ </ToggleGroup>
130
164
  </FilterGroup>
131
165
  </FilterBar>
132
166
  <div className="flex flex-wrap items-baseline justify-between gap-x-5 gap-y-1 border-b border-dashboard-border-subtle pb-3">
@@ -155,11 +189,11 @@ export function AutomationRunsPage(props: { enabled: boolean }) {
155
189
  <div className="min-w-0 overflow-x-auto">
156
190
  <div className="min-w-[44rem]">
157
191
  <div
192
+ aria-hidden="true"
158
193
  className={cn(
159
194
  "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",
160
195
  RUN_GRID,
161
196
  )}
162
- role="row"
163
197
  >
164
198
  <div>Run</div>
165
199
  <div>Automation</div>
@@ -167,11 +201,17 @@ export function AutomationRunsPage(props: { enabled: boolean }) {
167
201
  <div>Tokens</div>
168
202
  <div>Cost</div>
169
203
  </div>
170
- <div className="min-w-0" role="table">
204
+ <div
205
+ className="min-w-0"
206
+ onKeyDown={selection.onRowsKeyDown}
207
+ role="list"
208
+ >
171
209
  {pagedRuns.map((run) => (
172
210
  <AutomationRunRow
173
211
  key={`${run.kind}:${run.executionId}`}
212
+ onSelect={() => selection.toggle(run.executionId)}
174
213
  run={run}
214
+ selected={selection.runId === run.executionId}
175
215
  />
176
216
  ))}
177
217
  </div>
@@ -193,7 +233,7 @@ export function AutomationRunsPage(props: { enabled: boolean }) {
193
233
  ) : null}
194
234
  </>
195
235
  )}
196
- </>
236
+ </AutomationRunsFrame>
197
237
  );
198
238
  }
199
239
 
@@ -208,12 +248,12 @@ function emptyRunsText(input: {
208
248
  return "No visible automations have run yet.";
209
249
  }
210
250
 
211
- function AutomationRunRow(props: { run: AutomationRun }) {
251
+ function AutomationRunRow(props: {
252
+ onSelect(): void;
253
+ run: AutomationRun;
254
+ selected: boolean;
255
+ }) {
212
256
  const { run } = props;
213
- const navigate = useNavigate();
214
- const title =
215
- run.title?.trim() ||
216
- (run.conversationId ? run.automationTitle : "No conversation");
217
257
  const costLabel =
218
258
  formatCostSummary(
219
259
  run.costUsd === undefined ? undefined : { total: run.costUsd },
@@ -221,54 +261,56 @@ function AutomationRunRow(props: { run: AutomationRun }) {
221
261
  const durationLabel = formatRuntime(run.durationMs) || "—";
222
262
  const tokensLabel =
223
263
  run.totalTokens === undefined ? "—" : formatCompactNumber(run.totalTokens);
224
- const openConversation = () => {
225
- if (run.conversationId) navigate(conversationPath(run.conversationId));
226
- };
227
264
 
228
265
  return (
229
266
  <div
230
- aria-disabled={!run.conversationId}
231
- className={cn(
232
- "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",
233
- RUN_GRID,
234
- run.conversationId
235
- ? "cursor-pointer hover:bg-dashboard-fill-soft"
236
- : "cursor-default opacity-80",
237
- )}
238
- onClick={openConversation}
239
- onKeyDown={(event) => {
240
- if (!run.conversationId) return;
241
- if (event.key === "Enter" || event.key === " ") {
242
- event.preventDefault();
243
- openConversation();
244
- }
245
- }}
246
- role={run.conversationId ? "link" : "row"}
247
- tabIndex={run.conversationId ? 0 : undefined}
267
+ className="border-b border-dashboard-border-subtle last:border-b-0"
268
+ data-run-id={run.executionId}
269
+ role="listitem"
248
270
  >
249
- <div className="min-w-0 overflow-hidden">
250
- <div className="truncate text-sm font-medium leading-snug text-dashboard-text">
251
- {title}
252
- </div>
253
- <div className="mt-1 truncate text-xs text-dashboard-text-muted">
254
- {formatRunDate(run.executedAt)}
255
- </div>
256
- </div>
257
- <div className="min-w-0 overflow-hidden">
258
- <div className="truncate text-sm text-dashboard-text">
259
- {run.automationTitle}
260
- </div>
261
- <div className="mt-1 flex min-w-0 items-center gap-1.5 overflow-hidden text-dashboard-text-muted">
262
- <AutomationKindIcon kind={run.kind} />
263
- <StatusDot label={run.status} tone={runStatusTone(run.status)} />
264
- <span className="truncate text-xs text-dashboard-text-muted">
265
- {run.status}
271
+ <SelectableRow
272
+ className={cn(
273
+ "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",
274
+ RUN_GRID,
275
+ )}
276
+ onSelect={props.onSelect}
277
+ selected={props.selected}
278
+ >
279
+ <button
280
+ aria-expanded={props.selected}
281
+ 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"
282
+ onClick={props.onSelect}
283
+ type="button"
284
+ >
285
+ <span className="block truncate text-sm font-medium leading-snug text-dashboard-text">
286
+ {automationRunTitle(run, run.automationTitle)}
287
+ </span>
288
+ <span className="mt-1 block truncate text-xs text-dashboard-text-muted">
289
+ {formatAutomationRunTime(run.executedAt)}
266
290
  </span>
291
+ </button>
292
+ <div className="min-w-0 overflow-hidden">
293
+ <div className="truncate text-sm text-dashboard-text">
294
+ {run.automationTitle}
295
+ </div>
296
+ <div className="mt-1 flex min-w-0 items-center gap-1.5 overflow-hidden text-dashboard-text-muted">
297
+ <AutomationKindIcon kind={run.kind} />
298
+ <StatusDot
299
+ label={run.status}
300
+ tone={automationRunStatusTone(run.status)}
301
+ />
302
+ <span className="truncate text-xs text-dashboard-text-muted">
303
+ {run.status}
304
+ </span>
305
+ </div>
267
306
  </div>
268
- </div>
269
- <MetricCell label="Duration" value={durationLabel} />
270
- <MetricCell label="Tokens" value={tokensLabel} />
271
- <MetricCell label="Cost" value={costLabel} />
307
+ <MetricCell label="Duration" value={durationLabel} />
308
+ <MetricCell label="Tokens" value={tokensLabel} />
309
+ <MetricCell label="Cost" value={costLabel} />
310
+ <div className="max-md:hidden">
311
+ <RunConversationLink conversationId={run.conversationId} />
312
+ </div>
313
+ </SelectableRow>
272
314
  </div>
273
315
  );
274
316
  }
@@ -305,20 +347,3 @@ function AutomationKindIcon(props: { kind: AutomationRun["kind"] }) {
305
347
  </span>
306
348
  );
307
349
  }
308
-
309
- function runStatusTone(status: AutomationRun["status"]): StatusChipTone {
310
- if (status === "failed") return "danger";
311
- if (status === "blocked") return "warning";
312
- return "success";
313
- }
314
-
315
- function formatRunDate(value: string): string {
316
- return formatTime(value, {
317
- day: "numeric",
318
- hour: "numeric",
319
- minute: "2-digit",
320
- month: "short",
321
- timeZoneName: "short",
322
- year: "numeric",
323
- });
324
- }
@@ -6,12 +6,12 @@ import {
6
6
  automationSchedulePreviewSchema,
7
7
  type AutomationEdit,
8
8
  } from "@sentry/junior/api/schema";
9
- import { Field } from "../../components/Field";
10
- import { Select } from "../../components/Select";
11
- import { TextInput } from "../../components/TextInput";
12
- import { Button } from "../../components/Button";
9
+ import { Button } from "../../components/ui/button";
10
+ import { Input } from "../../components/ui/input";
11
+ import { Field, FieldDescription, FieldLabel } from "../../components/ui/field";
12
+ import { NativeSelect } from "../../components/ui/native-select";
13
+ import { ToggleGroup, ToggleGroupItem } from "../../components/ui/toggle-group";
13
14
  import { post, DashboardApiError } from "../../http";
14
- import { cn } from "../../styles";
15
15
  import {
16
16
  scheduleDraft,
17
17
  weekdays,
@@ -64,8 +64,9 @@ export function AutomationScheduleFields(props: {
64
64
  return (
65
65
  <>
66
66
  <div className="grid grid-cols-1 items-start gap-4 sm:grid-cols-2">
67
- <Field label="Repeat" htmlFor="schedule-frequency">
68
- <Select
67
+ <Field>
68
+ <FieldLabel htmlFor="schedule-frequency">Repeat</FieldLabel>
69
+ <NativeSelect
69
70
  id="schedule-frequency"
70
71
  value={frequency}
71
72
  onChange={(e) => {
@@ -106,10 +107,11 @@ export function AutomationScheduleFields(props: {
106
107
  <option value="weekly">Every week</option>
107
108
  <option value="monthly">Every month</option>
108
109
  <option value="yearly">Every year</option>
109
- </Select>
110
+ </NativeSelect>
110
111
  </Field>
111
- <Field label="Time" htmlFor="schedule-time">
112
- <TextInput
112
+ <Field>
113
+ <FieldLabel htmlFor="schedule-time">Time</FieldLabel>
114
+ <Input
113
115
  size="comfortable"
114
116
  id="schedule-time"
115
117
  type="time"
@@ -136,41 +138,31 @@ export function AutomationScheduleFields(props: {
136
138
  {value.frequency === "weekly" ? (
137
139
  <fieldset>
138
140
  <legend className="mb-2 text-sm font-semibold">On</legend>
139
- <div className="grid grid-cols-7 gap-1.5">
141
+ <ToggleGroup
142
+ className="grid grid-cols-7 gap-1.5"
143
+ multiple
144
+ onValueChange={(next) =>
145
+ props.onChange({ ...value, weekdays: next })
146
+ }
147
+ value={value.weekdays ?? []}
148
+ >
140
149
  {[...weekdays.slice(1), weekdays[0]].map((day) => (
141
- <button
142
- type="button"
150
+ <ToggleGroupItem
143
151
  key={day}
144
152
  aria-label={day}
145
- aria-pressed={value.weekdays?.includes(day) ?? false}
146
- onClick={() =>
147
- props.onChange({
148
- ...value,
149
- weekdays: value.weekdays?.includes(day)
150
- ? value.weekdays.filter((d) => d !== day)
151
- : [...(value.weekdays ?? []), day],
152
- })
153
- }
154
- className={cn(
155
- "min-h-11 cursor-pointer rounded-md border px-1 text-xs font-medium capitalize focus-visible:outline-2 focus-visible:outline-dashboard-focus sm:min-h-10 sm:text-sm",
156
- value.weekdays?.includes(day)
157
- ? "border-dashboard-focus/50 bg-dashboard-focus/10 text-dashboard-focus"
158
- : "border-dashboard-border-emphasis text-dashboard-text-muted hover:bg-dashboard-fill-hover",
159
- )}
153
+ className="h-auto min-h-11 min-w-0 border-dashboard-border-emphasis px-1 capitalize data-pressed:border-dashboard-focus/50 data-pressed:bg-dashboard-focus/10 data-pressed:text-dashboard-focus sm:min-h-10 sm:text-sm"
154
+ value={day}
160
155
  >
161
156
  {day.slice(0, 3)}
162
- </button>
157
+ </ToggleGroupItem>
163
158
  ))}
164
- </div>
159
+ </ToggleGroup>
165
160
  </fieldset>
166
161
  ) : null}
167
162
  <div className="grid grid-cols-1 items-start gap-4 sm:grid-cols-2">
168
- <Field
169
- label="Interval"
170
- htmlFor="schedule-interval"
171
- help="Number of calendar periods between runs."
172
- >
173
- <TextInput
163
+ <Field>
164
+ <FieldLabel htmlFor="schedule-interval">Interval</FieldLabel>
165
+ <Input
174
166
  size="comfortable"
175
167
  id="schedule-interval"
176
168
  type="number"
@@ -181,9 +173,13 @@ export function AutomationScheduleFields(props: {
181
173
  props.onChange({ ...value, interval: e.target.valueAsNumber })
182
174
  }
183
175
  />
176
+ <FieldDescription id="schedule-interval-help">
177
+ Number of calendar periods between runs.
178
+ </FieldDescription>
184
179
  </Field>
185
- <Field label="Start date" htmlFor="schedule-start">
186
- <TextInput
180
+ <Field>
181
+ <FieldLabel htmlFor="schedule-start">Start date</FieldLabel>
182
+ <Input
187
183
  size="comfortable"
188
184
  id="schedule-start"
189
185
  type="date"
@@ -199,8 +195,9 @@ export function AutomationScheduleFields(props: {
199
195
  </div>
200
196
  {value.frequency === "monthly" || value.frequency === "yearly" ? (
201
197
  <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
202
- <Field label="Day of month" htmlFor="schedule-day">
203
- <TextInput
198
+ <Field>
199
+ <FieldLabel htmlFor="schedule-day">Day of month</FieldLabel>
200
+ <Input
204
201
  size="comfortable"
205
202
  id="schedule-day"
206
203
  type="number"
@@ -216,8 +213,9 @@ export function AutomationScheduleFields(props: {
216
213
  />
217
214
  </Field>
218
215
  {value.frequency === "yearly" ? (
219
- <Field label="Month" htmlFor="schedule-month">
220
- <Select
216
+ <Field>
217
+ <FieldLabel htmlFor="schedule-month">Month</FieldLabel>
218
+ <NativeSelect
221
219
  id="schedule-month"
222
220
  value={value.month ?? 1}
223
221
  onChange={(e) =>
@@ -235,15 +233,16 @@ export function AutomationScheduleFields(props: {
235
233
  }).format(new Date(Date.UTC(2026, index, 1)))}
236
234
  </option>
237
235
  ))}
238
- </Select>
236
+ </NativeSelect>
239
237
  </Field>
240
238
  ) : null}
241
239
  </div>
242
240
  ) : null}
243
241
  </>
244
242
  ) : (
245
- <Field label="Date" htmlFor="schedule-date">
246
- <TextInput
243
+ <Field>
244
+ <FieldLabel htmlFor="schedule-date">Date</FieldLabel>
245
+ <Input
247
246
  size="comfortable"
248
247
  id="schedule-date"
249
248
  type="date"
@@ -257,12 +256,9 @@ export function AutomationScheduleFields(props: {
257
256
  />
258
257
  </Field>
259
258
  )}
260
- <Field
261
- label="Timezone"
262
- htmlFor="schedule-timezone"
263
- help="Use an IANA timezone. The schedule follows local time, including daylight saving time."
264
- >
265
- <TextInput
259
+ <Field>
260
+ <FieldLabel htmlFor="schedule-timezone">Timezone</FieldLabel>
261
+ <Input
266
262
  size="comfortable"
267
263
  id="schedule-timezone"
268
264
  list="automation-timezones"
@@ -276,6 +272,10 @@ export function AutomationScheduleFields(props: {
276
272
  <option key={zone}>{zone}</option>
277
273
  ))}
278
274
  </datalist>
275
+ <FieldDescription id="schedule-timezone-help">
276
+ Use an IANA timezone. The schedule follows local time, including
277
+ daylight saving time.
278
+ </FieldDescription>
279
279
  </Field>
280
280
  <div
281
281
  role="status"