@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,33 +1,37 @@
1
- import { ChevronRight } from "lucide-react";
2
- import { memo, useEffect, useId, useMemo, useRef, useState } from "react";
1
+ import { memo, useEffect, useMemo, useRef, useState } from "react";
3
2
  import { createPortal } from "react-dom";
4
3
 
5
- import { Drawer } from "../components/Drawer";
4
+ import { Skeleton } from "../components/ui/skeleton";
5
+ import { Toggle } from "../components/ui/toggle";
6
+ import { useEventTime } from "./eventTime";
7
+ import { SearchInput } from "../components/SearchInput";
6
8
  import { cn } from "../styles";
7
9
  import type { ConversationTranscript } from "../types";
8
10
  import {
9
- eventLogModel,
10
- eventLogSummary,
11
+ adjacentEventLogSeqs,
12
+ eventLogRows,
11
13
  eventLogTone,
12
- eventLogUsage,
14
+ filterEventLogRows,
13
15
  } from "./eventLog";
14
- import { EventDetails } from "./EventDetails";
15
- import { ForkConversationButton } from "./ForkConversationButton";
16
+ import { ConversationEventDetails } from "./ConversationEventDetails";
16
17
  import { HighlightText, useTranscriptSearch } from "./transcriptSearch";
17
18
 
18
19
  const rowClass =
19
- "grid grid-cols-[12ch_minmax(0,1fr)_1rem] items-center gap-x-3 px-3 @min-[48rem]:grid-cols-[12ch_19ch_minmax(0,1fr)_1rem]";
20
+ "grid grid-cols-[12ch_minmax(0,1fr)] items-baseline gap-x-3 px-4 @min-[32rem]:grid-cols-[12ch_17ch_minmax(0,1fr)]";
20
21
 
21
22
  /**
22
23
  * Show every reporting event in sequence order, outside the transcript reducer.
23
- * Pass `onSelectedSeqChange` to own the open entry, for example in the URL.
24
+ * Pass `onSelectedSeqChange` to own selection and render details beside the log.
25
+ * Standalone transcripts keep a local selection and show an overlay.
24
26
  */
25
27
  export const ConversationEventLog = memo(function ConversationEventLog(props: {
26
28
  conversation: ConversationTranscript;
29
+ onSearchChange(value: string): void;
27
30
  onSelectedSeqChange?(seq: number | undefined): void;
28
31
  selectedSeq?: number;
29
32
  }) {
30
33
  const { conversation } = props;
34
+ const time = useEventTime();
31
35
  const search = useTranscriptSearch();
32
36
  const [localSelectedSeq, setLocalSelectedSeq] = useState<number>();
33
37
  const selectedSeq = props.onSelectedSeqChange
@@ -35,31 +39,14 @@ export const ConversationEventLog = memo(function ConversationEventLog(props: {
35
39
  : localSelectedSeq;
36
40
  const setSelectedSeq = props.onSelectedSeqChange ?? setLocalSelectedSeq;
37
41
  const listRef = useRef<HTMLOListElement>(null);
38
- // Rows opened by a click are already visible. Scroll only for a linked entry.
42
+ // Clicked rows are visible. Reveal links and keyboard selections after layout.
39
43
  const revealedSeqRef = useRef<number>(undefined);
40
- const titleId = useId();
41
44
  const events = conversation.events;
42
- const rows = useMemo(
43
- () =>
44
- events.map((event) => ({
45
- event,
46
- model: eventLogModel(event),
47
- usage: eventLogUsage(event),
48
- summary: eventLogSummary(event.data).replace(/\s+/g, " ").slice(0, 300),
49
- })),
50
- [events],
51
- );
45
+ const rows = useMemo(() => eventLogRows(events), [events]);
52
46
  // Large payloads are serialized only for an active search or the open entry.
53
47
  const visibleRows = useMemo(
54
- () =>
55
- search.active
56
- ? rows.filter(({ event, summary, usage }) =>
57
- `${summary} ${usage} ${JSON.stringify(event)}`
58
- .toLowerCase()
59
- .includes(search.normalizedQuery),
60
- )
61
- : rows,
62
- [rows, search.active, search.normalizedQuery],
48
+ () => filterEventLogRows(rows, search.normalizedQuery),
49
+ [rows, search.normalizedQuery],
63
50
  );
64
51
  // Resolve the selection from current data so polling cannot leave stale details.
65
52
  const selected = events.find((event) => event.seq === selectedSeq);
@@ -72,7 +59,7 @@ export const ConversationEventLog = memo(function ConversationEventLog(props: {
72
59
  );
73
60
  if (!row) return;
74
61
  revealedSeqRef.current = selectedSeq;
75
- row.scrollIntoView({ block: "center" });
62
+ row.scrollIntoView({ block: "nearest" });
76
63
  }, [selectedSeq, visibleRows]);
77
64
 
78
65
  if (conversation.eventHistory.status === "expired") {
@@ -85,54 +72,89 @@ export const ConversationEventLog = memo(function ConversationEventLog(props: {
85
72
 
86
73
  return (
87
74
  <section aria-label="Conversation event log" className="@container min-w-0">
88
- <div className="mb-3 flex flex-wrap items-center justify-between gap-2 text-xs text-dashboard-text-muted">
89
- <span role="status">
90
- {search.active
91
- ? `${visibleRows.length} of ${events.length} loaded events`
92
- : `${events.length} loaded events`}
93
- </span>
94
- <span>Times in UTC · Select a row for details</span>
95
- </div>
96
- <div className="overflow-hidden rounded-md border border-dashboard-border bg-dashboard-surface-panel font-mono text-xs">
97
- <div
98
- aria-hidden="true"
99
- className={cn(
100
- rowClass,
101
- "border-b border-dashboard-border bg-dashboard-surface-raised py-2 text-dashboard-text-muted",
102
- )}
103
- >
104
- <span>Time (UTC)</span>
105
- <span>Event</span>
106
- <span className="hidden @min-[48rem]:block">Summary</span>
107
- <span />
75
+ <div className="sticky top-0 z-10 bg-dashboard-bg">
76
+ <div className="flex flex-wrap items-center gap-2 border-b border-dashboard-border px-4 py-2">
77
+ <SearchInput
78
+ className="min-w-32 flex-1 @max-[28rem]:basis-full"
79
+ label="Search events"
80
+ onChange={props.onSearchChange}
81
+ placeholder="Search events…"
82
+ size="compact"
83
+ value={search.query}
84
+ />
85
+ <div className="ml-auto flex items-center gap-3 whitespace-nowrap text-xs text-dashboard-text-muted @max-[28rem]:w-full @max-[28rem]:justify-between">
86
+ <span role="status">
87
+ {search.active
88
+ ? `${visibleRows.length} of ${events.length} loaded events`
89
+ : `${events.length} loaded events`}
90
+ </span>
91
+ <Toggle
92
+ aria-label="Use local time"
93
+ onPressedChange={time.toggle}
94
+ pressed={time.local}
95
+ title={time.title}
96
+ >
97
+ <span className="max-w-32 truncate">{time.label}</span>
98
+ </Toggle>
99
+ </div>
108
100
  </div>
101
+ <EventLogColumns />
102
+ </div>
103
+ <div className="font-mono text-xs">
109
104
  <ol
110
105
  aria-label="Events"
111
106
  className="m-0 list-none divide-y divide-dashboard-border-subtle p-0"
112
107
  ref={listRef}
113
108
  >
114
- {visibleRows.map(({ event, summary, model, usage }) => (
115
- <li data-event-seq={event.seq} key={event.seq}>
109
+ {visibleRows.map(({ event, summary, metadata }, index) => (
110
+ <li
111
+ className="scroll-mt-32 scroll-mb-2"
112
+ data-event-seq={event.seq}
113
+ key={event.seq}
114
+ >
116
115
  <button
117
- aria-haspopup="dialog"
116
+ aria-expanded={selectedSeq === event.seq}
118
117
  aria-label={`Event ${event.seq}: ${event.data.type}`}
119
118
  className={cn(
120
119
  rowClass,
121
- "min-h-11 w-full cursor-pointer border-0 bg-transparent py-2 text-left leading-5 hover:bg-dashboard-fill-hover active:bg-dashboard-fill-strong focus-visible:bg-dashboard-fill-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus @min-[48rem]:min-h-8 @min-[48rem]:py-1",
122
- selectedSeq === event.seq && "bg-dashboard-fill-strong",
120
+ "relative min-h-11 w-full cursor-pointer border-0 bg-transparent py-2 text-left leading-5 hover:bg-dashboard-fill-hover active:bg-dashboard-fill-strong focus-visible:bg-dashboard-fill-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus @min-[32rem]:min-h-9 @min-[32rem]:py-1.5",
121
+ selectedSeq === event.seq &&
122
+ "bg-dashboard-fill-strong before:absolute before:inset-y-0 before:left-0 before:w-0.5 before:bg-dashboard-accent before:content-['']",
123
123
  )}
124
124
  onClick={() => {
125
125
  revealedSeqRef.current = event.seq;
126
126
  setSelectedSeq(event.seq);
127
127
  }}
128
+ onKeyDown={(keyEvent) => {
129
+ if (
130
+ keyEvent.altKey ||
131
+ keyEvent.ctrlKey ||
132
+ keyEvent.metaKey ||
133
+ keyEvent.shiftKey ||
134
+ (keyEvent.key !== "ArrowUp" && keyEvent.key !== "ArrowDown")
135
+ )
136
+ return;
137
+ keyEvent.preventDefault();
138
+ const next =
139
+ visibleRows[
140
+ index + (keyEvent.key === "ArrowDown" ? 1 : -1)
141
+ ];
142
+ if (!next) return;
143
+ setSelectedSeq(next.event.seq);
144
+ listRef.current
145
+ ?.querySelector<HTMLButtonElement>(
146
+ `[data-event-seq="${next.event.seq}"] button`,
147
+ )
148
+ ?.focus({ preventScroll: true });
149
+ }}
128
150
  type="button"
129
151
  >
130
152
  <time
131
153
  className="tabular-nums text-dashboard-text-muted"
132
154
  dateTime={event.createdAt}
133
- title={event.createdAt}
155
+ title={time.formatTimestamp(event.createdAt)}
134
156
  >
135
- {event.createdAt.slice(11, 23)}
157
+ {time.formatClock(event.createdAt)}
136
158
  </time>
137
159
  <span
138
160
  className={cn("truncate", eventLogTone(event.data))}
@@ -140,50 +162,19 @@ export const ConversationEventLog = memo(function ConversationEventLog(props: {
140
162
  >
141
163
  <HighlightText text={event.data.type} />
142
164
  </span>
143
- <span className="col-span-2 row-start-2 grid min-w-0 gap-1 text-dashboard-text @min-[48rem]:col-span-1 @min-[48rem]:row-auto">
144
- <span className="flex min-w-0 flex-wrap items-baseline gap-x-2">
145
- <span
146
- className={cn(
147
- "break-all",
148
- model
149
- ? "font-semibold text-violet-300"
150
- : "text-dashboard-text-muted",
151
- )}
152
- >
153
- <HighlightText
154
- text={model?.modelId ?? "Model not recorded"}
155
- />
156
- </span>
157
- {model?.modelProfile ? (
158
- <span className="text-dashboard-text-muted">
159
- <HighlightText text={model.modelProfile} />
160
- </span>
161
- ) : null}
162
- {model?.reasoningLevel ? (
163
- <span className="text-dashboard-text-muted">
164
- <HighlightText
165
- text={`${model.reasoningLevel} reasoning`}
166
- />
167
- </span>
168
- ) : null}
169
- </span>
165
+ <span className="col-span-2 row-start-2 grid min-w-0 gap-0.5 text-dashboard-text @min-[32rem]:col-span-1 @min-[32rem]:row-auto">
170
166
  <span className="truncate" title={summary}>
171
167
  <HighlightText text={summary} />
172
168
  </span>
173
- {usage ? (
169
+ {metadata ? (
174
170
  <span
175
- className="text-dashboard-text-muted"
176
- title="Usage for this event only; costs are estimated USD"
171
+ className="truncate text-dashboard-text-muted"
172
+ title={metadata}
177
173
  >
178
- <HighlightText text={usage} />
174
+ <HighlightText text={metadata} />
179
175
  </span>
180
176
  ) : null}
181
177
  </span>
182
- <ChevronRight
183
- aria-hidden="true"
184
- className="col-start-3 row-start-1 text-dashboard-text-muted @min-[48rem]:col-start-4"
185
- size={14}
186
- />
187
178
  </button>
188
179
  </li>
189
180
  ))}
@@ -196,45 +187,75 @@ export const ConversationEventLog = memo(function ConversationEventLog(props: {
196
187
  </p>
197
188
  ) : null}
198
189
  </div>
199
- {selected
190
+ {selected && !props.onSelectedSeqChange
200
191
  ? createPortal(
201
- <Drawer
202
- closeLabel="Close event details"
203
- header={
204
- <>
205
- <h2
206
- className="m-0 break-words font-mono text-sm font-semibold text-dashboard-text"
207
- id={titleId}
208
- >
209
- {selected.data.type}
210
- </h2>
211
- <p className="mb-0 mt-1 font-mono text-xs text-dashboard-text-muted">
212
- {selected.createdAt}
213
- </p>
214
- </>
215
- }
192
+ <ConversationEventDetails
193
+ overlay
194
+ conversation={conversation}
195
+ event={selected}
216
196
  onClose={() => setSelectedSeq(undefined)}
217
- openKey={`${conversation.conversationId}:${selected.seq}`}
218
- titleId={titleId}
219
- width="wide"
220
- >
221
- {conversation.canFork &&
222
- selected.data.type === "message" &&
223
- selected.data.role === "assistant" &&
224
- !selected.data.redacted ? (
225
- <div className="mb-3 flex items-center gap-2 text-sm">
226
- <ForkConversationButton
227
- conversationId={conversation.conversationId}
228
- messageId={selected.data.messageId}
229
- />
230
- <span>Fork after this reply</span>
231
- </div>
232
- ) : null}
233
- <EventDetails key={selected.seq} event={selected} />
234
- </Drawer>,
197
+ onSelect={setSelectedSeq}
198
+ {...adjacentEventLogSeqs(visibleRows, selected.seq)}
199
+ />,
235
200
  document.body,
236
201
  )
237
202
  : null}
238
203
  </section>
239
204
  );
240
205
  });
206
+
207
+ const loadingRowWidths = ["w-3/5", "w-2/5", "w-4/5", "w-1/2", "w-2/3", "w-1/3"];
208
+
209
+ /** Match the event log toolbar, columns, and rows while the transcript loads. */
210
+ export function ConversationEventLogLoading() {
211
+ return (
212
+ <section aria-busy="true" className="@container min-w-0">
213
+ <span className="sr-only" role="status">
214
+ Loading events
215
+ </span>
216
+ <div className="sticky top-0 z-10 bg-dashboard-bg">
217
+ <div className="flex flex-wrap items-center gap-2 border-b border-dashboard-border px-4 py-2">
218
+ <Skeleton className="h-9 min-w-32 flex-1 @max-[28rem]:basis-full" />
219
+ <Skeleton className="ml-auto h-3 w-24" />
220
+ </div>
221
+ <EventLogColumns />
222
+ </div>
223
+ <ol className="m-0 list-none divide-y divide-dashboard-border-subtle p-0">
224
+ {loadingRowWidths.map((width, index) => (
225
+ <li
226
+ className={cn(
227
+ rowClass,
228
+ "min-h-11 items-center gap-y-2 py-2 @min-[32rem]:min-h-9 @min-[32rem]:py-1.5",
229
+ )}
230
+ key={index}
231
+ >
232
+ <Skeleton className="h-3 w-[10ch]" />
233
+ <Skeleton className="h-3 w-[12ch]" />
234
+ <Skeleton
235
+ className={cn(
236
+ "col-span-2 row-start-2 h-3 @min-[32rem]:col-span-1 @min-[32rem]:row-auto",
237
+ width,
238
+ )}
239
+ />
240
+ </li>
241
+ ))}
242
+ </ol>
243
+ </section>
244
+ );
245
+ }
246
+
247
+ function EventLogColumns() {
248
+ return (
249
+ <div
250
+ aria-hidden="true"
251
+ className={cn(
252
+ rowClass,
253
+ "border-b border-dashboard-border py-2 font-mono text-xs text-dashboard-text-muted",
254
+ )}
255
+ >
256
+ <span>Time</span>
257
+ <span>Event</span>
258
+ <span className="hidden @min-[32rem]:block">Summary</span>
259
+ </div>
260
+ );
261
+ }
@@ -10,6 +10,7 @@ import {
10
10
  import { Ellipsis, Menu } from "lucide-react";
11
11
 
12
12
  import { SearchInput } from "../components/SearchInput";
13
+ import { Button } from "../components/ui/button";
13
14
  import { cn } from "../styles";
14
15
  import {
15
16
  MobileHeaderActionsPortal,
@@ -53,11 +54,16 @@ export function ConversationHeader(props: {
53
54
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
54
55
  const searchInputRef = useRef<HTMLInputElement>(null);
55
56
  const mobileMenuRef = useRef<HTMLDivElement>(null);
56
- const searchOpenVisible = searchOpen || props.search.length > 0;
57
+ const searchOpenVisible =
58
+ props.view === "rich" && (searchOpen || props.search.length > 0);
57
59
  const openMobileNavigation = useOpenMobileNavigation();
58
60
 
59
61
  useEffect(() => {
60
- if (wasDetailsOpen.current && !detailsOpen) {
62
+ if (
63
+ wasDetailsOpen.current &&
64
+ !detailsOpen &&
65
+ document.activeElement === document.body
66
+ ) {
61
67
  const trigger = window.matchMedia("(max-width: 767px)").matches
62
68
  ? menuButtonRef.current
63
69
  : detailsButtonRef.current;
@@ -144,17 +150,17 @@ export function ConversationHeader(props: {
144
150
  view={props.view}
145
151
  />
146
152
  {openMobileNavigation ? (
147
- <button
148
- className="mt-0.5 flex w-full cursor-pointer items-center gap-2.5 rounded-md border-0 bg-transparent px-2.5 py-2 text-left text-sm font-semibold text-dashboard-text transition-colors hover:bg-dashboard-fill-hover focus-visible:bg-dashboard-fill-hover focus-visible:outline-none"
153
+ <Button
154
+ className="mt-0.5"
149
155
  onClick={() => {
150
156
  setMobileMenuOpen(false);
151
157
  openMobileNavigation();
152
158
  }}
153
- type="button"
159
+ size="menu"
154
160
  >
155
161
  <Menu aria-hidden="true" size={16} strokeWidth={2} />
156
162
  App menu
157
- </button>
163
+ </Button>
158
164
  ) : null}
159
165
  </div>
160
166
  ) : null}
@@ -182,7 +188,7 @@ export function ConversationHeader(props: {
182
188
  <MobileHeaderLivePortal>{liveIndicator}</MobileHeaderLivePortal>
183
189
  <header
184
190
  className={cn(
185
- "sticky top-0 z-10 -mx-4 border-b border-dashboard-border bg-dashboard-bg md:-mx-7",
191
+ "relative z-10 border-b border-dashboard-border bg-dashboard-bg",
186
192
  !showMobileHeader && "hidden md:block",
187
193
  )}
188
194
  >