@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,6 +1,15 @@
1
1
  import type { ConversationUsage } from "@sentry/junior/api/schema";
2
2
 
3
3
  import { formatCostBreakdown, summarizeCost } from "../format";
4
+ import {
5
+ Table,
6
+ TableBody,
7
+ TableCell,
8
+ TableHead,
9
+ TableFooter,
10
+ TableHeader,
11
+ TableRow,
12
+ } from "../components/ui/table";
4
13
  import { cn } from "../styles";
5
14
 
6
15
  /** Align recorded token counts with their costs without adding overlapping counters. */
@@ -31,83 +40,53 @@ export function EventUsage({ usage }: { usage?: ConversationUsage }) {
31
40
  );
32
41
  }
33
42
 
43
+ const total = rows.find((row) => row.label === "Total");
34
44
  return (
35
45
  <>
36
- <div className="min-w-0 overflow-x-auto">
37
- <table
38
- aria-label="Model call token and cost breakdown"
39
- className="w-full min-w-72 table-fixed border-collapse text-xs leading-relaxed"
40
- >
41
- <colgroup>
42
- <col />
43
- <col className="w-24" />
44
- <col className="w-24" />
45
- </colgroup>
46
- <thead>
47
- <tr className="border-b border-dashboard-border text-dashboard-text-muted">
48
- <th scope="col" className="py-2 text-left font-normal">
49
- Usage
50
- </th>
51
- <th scope="col" className="py-2 text-right font-normal">
52
- Tokens
53
- </th>
54
- <th scope="col" className="py-2 text-right font-normal">
55
- Cost (USD)
56
- </th>
57
- </tr>
58
- </thead>
59
- <tbody>
60
- {rows.map((row) => (
61
- <tr
62
- key={row.label}
63
- className={cn(
64
- "border-b border-dashboard-border-subtle last:border-b-0",
65
- row.label === "Total" &&
66
- "border-t border-dashboard-border font-semibold",
67
- )}
68
- >
69
- <th
70
- scope="row"
71
- className={cn(
72
- "py-2 text-left font-normal",
73
- row.label === "Total"
74
- ? "font-semibold text-dashboard-text"
75
- : "text-dashboard-text-muted",
76
- )}
77
- >
78
- {row.label}
79
- </th>
80
- <td className="py-2 text-right font-sans tabular-nums text-dashboard-text">
81
- {row.tokens === undefined ? (
82
- <span
83
- aria-label="Not recorded"
84
- className="text-dashboard-text-faint"
85
- >
86
- —
87
- </span>
88
- ) : (
89
- row.tokens.toLocaleString("en-US")
90
- )}
91
- </td>
92
- <td className="py-2 text-right font-sans tabular-nums text-dashboard-text">
93
- {row.cost === undefined ? (
94
- <span
95
- aria-label="Not recorded separately"
96
- className="text-dashboard-text-faint"
97
- >
98
- —
99
- </span>
100
- ) : (
101
- <span title={`${row.cost} USD`}>
102
- {formatCostBreakdown({ total: row.cost })}
103
- </span>
104
- )}
105
- </td>
106
- </tr>
46
+ <Table
47
+ aria-label="Model call token and cost breakdown"
48
+ className="min-w-72 table-fixed text-xs leading-relaxed"
49
+ >
50
+ <colgroup>
51
+ <col />
52
+ <col className="w-24" />
53
+ <col className="w-24" />
54
+ </colgroup>
55
+ <TableHeader>
56
+ <TableRow className="border-dashboard-border">
57
+ <TableHead
58
+ scope="col"
59
+ className="px-0 py-2 leading-relaxed font-normal"
60
+ >
61
+ Usage
62
+ </TableHead>
63
+ <TableHead
64
+ scope="col"
65
+ className="px-0 py-2 text-right leading-relaxed font-normal"
66
+ >
67
+ Tokens
68
+ </TableHead>
69
+ <TableHead
70
+ scope="col"
71
+ className="px-0 py-2 text-right leading-relaxed font-normal"
72
+ >
73
+ Cost (USD)
74
+ </TableHead>
75
+ </TableRow>
76
+ </TableHeader>
77
+ <TableBody>
78
+ {rows
79
+ .filter((row) => row !== total)
80
+ .map((row) => (
81
+ <UsageRow key={row.label} {...row} />
107
82
  ))}
108
- </tbody>
109
- </table>
110
- </div>
83
+ </TableBody>
84
+ {total ? (
85
+ <TableFooter className="font-semibold">
86
+ <UsageRow {...total} total />
87
+ </TableFooter>
88
+ ) : null}
89
+ </Table>
111
90
  <p className="m-0 text-xs leading-relaxed text-dashboard-text-muted">
112
91
  This call only · Estimated costs
113
92
  <br />
@@ -116,3 +95,49 @@ export function EventUsage({ usage }: { usage?: ConversationUsage }) {
116
95
  </>
117
96
  );
118
97
  }
98
+
99
+ function UsageRow(props: {
100
+ cost?: number;
101
+ label: string;
102
+ tokens?: number;
103
+ total?: boolean;
104
+ }) {
105
+ return (
106
+ <TableRow>
107
+ <TableHead
108
+ scope="row"
109
+ className={cn(
110
+ "px-0 py-2 leading-relaxed",
111
+ props.total
112
+ ? "font-semibold text-dashboard-text"
113
+ : "font-normal text-dashboard-text-muted",
114
+ )}
115
+ >
116
+ {props.label}
117
+ </TableHead>
118
+ <TableCell className="px-0 py-2 text-right font-sans tabular-nums">
119
+ {props.tokens === undefined ? (
120
+ <span aria-label="Not recorded" className="text-dashboard-text-faint">
121
+ —
122
+ </span>
123
+ ) : (
124
+ props.tokens.toLocaleString("en-US")
125
+ )}
126
+ </TableCell>
127
+ <TableCell className="px-0 py-2 text-right font-sans tabular-nums">
128
+ {props.cost === undefined ? (
129
+ <span
130
+ aria-label="Not recorded separately"
131
+ className="text-dashboard-text-faint"
132
+ >
133
+ —
134
+ </span>
135
+ ) : (
136
+ <span title={`${props.cost} USD`}>
137
+ {formatCostBreakdown({ total: props.cost })}
138
+ </span>
139
+ )}
140
+ </TableCell>
141
+ </TableRow>
142
+ );
143
+ }
@@ -2,10 +2,15 @@ import { GitFork } from "lucide-react";
2
2
  import { useId, useState } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
  import { useNavigate } from "react-router";
5
- import { Button } from "../components/Button";
5
+ import { Button } from "../components/ui/button";
6
+ import { Textarea } from "../components/ui/textarea";
7
+ import { Field, FieldLabel } from "../components/ui/field";
8
+ import {
9
+ Tooltip,
10
+ TooltipContent,
11
+ TooltipTrigger,
12
+ } from "../components/ui/tooltip";
6
13
  import { Drawer } from "../components/Drawer";
7
- import { Field } from "../components/Field";
8
- import { TextArea } from "../components/TextInput";
9
14
  import { DashboardApiError } from "../http";
10
15
  import { conversationPath } from "./conversationRoutes";
11
16
  import { useForkConversation } from "./queries";
@@ -17,14 +22,20 @@ export function ForkConversationButton(props: ForkTarget) {
17
22
  const [open, setOpen] = useState(false);
18
23
  return (
19
24
  <>
20
- <Button
21
- size="icon"
22
- aria-label="Fork after this message"
23
- title="Fork after this message"
24
- onClick={() => setOpen(true)}
25
- >
26
- <GitFork size={14} aria-hidden="true" />
27
- </Button>
25
+ <Tooltip>
26
+ <TooltipTrigger
27
+ render={
28
+ <Button
29
+ size="icon"
30
+ aria-label="Fork after this message"
31
+ onClick={() => setOpen(true)}
32
+ >
33
+ <GitFork size={14} aria-hidden="true" />
34
+ </Button>
35
+ }
36
+ />
37
+ <TooltipContent size="compact">Fork after this message</TooltipContent>
38
+ </Tooltip>
28
39
  {open
29
40
  ? createPortal(
30
41
  <ForkConversationDialog
@@ -82,12 +93,12 @@ function ForkConversationDialog(props: ForkTarget & { onClose(): void }) {
82
93
  <p className="m-0">
83
94
  Creates a fork of the conversation from this point.
84
95
  </p>
85
- <Field htmlFor={inputId} label="Message">
86
- <TextArea
96
+ <Field>
97
+ <FieldLabel htmlFor={inputId}>Message</FieldLabel>
98
+ <Textarea
87
99
  id={inputId}
88
- prose
89
100
  autoFocus
90
- className="min-h-28"
101
+ className="min-h-28 leading-relaxed"
91
102
  placeholder="Message Junior…"
92
103
  value={message}
93
104
  disabled={fork.isPending}
@@ -112,7 +123,7 @@ function ForkConversationDialog(props: ForkTarget & { onClose(): void }) {
112
123
  </p>
113
124
  ) : null}
114
125
  <Button
115
- tone="primary"
126
+ variant="primary"
116
127
  type="submit"
117
128
  disabled={!message.trim() || fork.isPending}
118
129
  >
@@ -3,10 +3,16 @@ import {
3
3
  objectFactFields,
4
4
  objectPresentation,
5
5
  } from "@sentry/junior-plugin-api";
6
- import { ArrowUpRight } from "lucide-react";
6
+ import { ArrowUpRight, ChevronRight } from "lucide-react";
7
7
  import { ObjectIcon } from "../components/ObjectIcon";
8
8
  import { AutomationCard } from "../components/AutomationCard";
9
9
  import { StatusChip } from "../components/StatusChip";
10
+ import {
11
+ Collapsible,
12
+ CollapsibleContent,
13
+ CollapsibleTrigger,
14
+ } from "../components/ui/collapsible";
15
+ import { dashboardDisclosureChevronClass } from "../styles";
10
16
 
11
17
  /** Show the latest saved annotation. Opening a source is always explicit. */
12
18
  export function ObjectCard({ card }: { card: OwnedObjectAnnotation }) {
@@ -78,14 +84,20 @@ export function ObjectCard({ card }: { card: OwnedObjectAnnotation }) {
78
84
  )}
79
85
  {fields.length > 0 && facts(fields)}
80
86
  {card.description && (
81
- <details className="min-w-0 border-t border-dashboard-border pt-3">
82
- <summary className="cursor-pointer text-xs font-medium text-dashboard-text-muted focus-visible:outline-dashboard-focus">
87
+ <Collapsible className="min-w-0 border-t border-dashboard-border pt-3">
88
+ <CollapsibleTrigger className="group flex cursor-pointer items-center gap-1 text-xs font-medium text-dashboard-text-muted focus-visible:outline-dashboard-focus">
89
+ <ChevronRight
90
+ aria-hidden="true"
91
+ className={dashboardDisclosureChevronClass}
92
+ />
83
93
  More details
84
- </summary>
85
- <p className="mb-0 mt-3 whitespace-pre-wrap break-words text-sm text-dashboard-text-muted">
86
- {card.description}
87
- </p>
88
- </details>
94
+ </CollapsibleTrigger>
95
+ <CollapsibleContent hiddenUntilFound>
96
+ <p className="mb-0 mt-3 whitespace-pre-wrap break-words text-sm text-dashboard-text-muted">
97
+ {card.description}
98
+ </p>
99
+ </CollapsibleContent>
100
+ </Collapsible>
89
101
  )}
90
102
  {card.warning && (
91
103
  <p className="m-0 break-words text-xs text-amber-500">
@@ -9,13 +9,17 @@ import {
9
9
  import {
10
10
  AlertCircle,
11
11
  Clock3,
12
- LoaderCircle,
13
12
  SkipForward,
14
13
  X,
15
14
  type LucideIcon,
16
15
  } from "lucide-react";
17
16
 
18
- import { Tooltip } from "../components/Tooltip";
17
+ import {
18
+ Tooltip,
19
+ TooltipContent,
20
+ TooltipTrigger,
21
+ } from "../components/ui/tooltip";
22
+ import { Spinner } from "../components/ui/spinner";
19
23
  import { actorLabel } from "../format";
20
24
  import type { ConversationMailboxMessage } from "./conversationOutbox";
21
25
  import {
@@ -29,12 +33,12 @@ const COLLAPSED_PENDING_ROW_COUNT = 2;
29
33
 
30
34
  function pendingDeliveryMeta(
31
35
  message: Pick<ConversationMailboxMessage, "clientStatus" | "delivery">,
32
- ): { icon: LucideIcon; label: string; spin?: boolean } {
36
+ ): { icon: LucideIcon | typeof Spinner; label: string } {
33
37
  if (message.clientStatus === "failed") {
34
38
  return { icon: AlertCircle, label: "Failed to send" };
35
39
  }
36
40
  if (message.clientStatus === "sending") {
37
- return { icon: LoaderCircle, label: "Sending", spin: true };
41
+ return { icon: Spinner, label: "Sending" };
38
42
  }
39
43
  if (message.delivery === "interrupt") {
40
44
  return { icon: SkipForward, label: "Interrupt" };
@@ -44,13 +48,18 @@ function pendingDeliveryMeta(
44
48
 
45
49
  function PendingMetaIcon(props: { children: ReactElement; label: string }) {
46
50
  return (
47
- <Tooltip content={props.label} placement="above">
48
- <span
49
- aria-label={props.label}
50
- className="inline-flex text-dashboard-text-muted"
51
- >
52
- {props.children}
53
- </span>
51
+ <Tooltip>
52
+ <TooltipTrigger
53
+ render={
54
+ <span
55
+ aria-label={props.label}
56
+ className="inline-flex text-dashboard-text-muted"
57
+ >
58
+ {props.children}
59
+ </span>
60
+ }
61
+ />
62
+ <TooltipContent>{props.label}</TooltipContent>
54
63
  </Tooltip>
55
64
  );
56
65
  }
@@ -69,43 +78,48 @@ function PendingMetaIcons(props: {
69
78
  return (
70
79
  <TranscriptHeadingMeta className="flex min-w-0 items-center justify-end gap-1.5 text-xs leading-none text-dashboard-text-muted">
71
80
  {props.showSlack ? (
72
- <Tooltip content="Slack" placement="above">
73
- <span className="inline-flex text-dashboard-text-muted">
74
- <SlackMark className="size-3.5" />
75
- </span>
81
+ <Tooltip>
82
+ <TooltipTrigger
83
+ render={
84
+ <span className="inline-flex text-dashboard-text-muted">
85
+ <SlackMark className="size-3.5" />
86
+ </span>
87
+ }
88
+ />
89
+ <TooltipContent>Slack</TooltipContent>
76
90
  </Tooltip>
77
91
  ) : null}
78
92
  <PendingMetaIcon label={delivery.label}>
79
93
  <DeliveryIcon
80
94
  aria-hidden="true"
81
- className={delivery.spin ? "animate-spin" : undefined}
82
- size={13}
95
+ className="size-3.25"
83
96
  strokeWidth={2.2}
84
97
  />
85
98
  </PendingMetaIcon>
86
99
  {props.onCancel ? (
87
- <Tooltip content="Remove queued message" placement="above">
88
- <button
89
- aria-label={
90
- props.cancelError
91
- ? "Could not remove. Try again."
92
- : "Remove queued message"
100
+ <Tooltip>
101
+ <TooltipTrigger
102
+ render={
103
+ <button
104
+ aria-label={
105
+ props.cancelError
106
+ ? "Could not remove. Try again."
107
+ : "Remove queued message"
108
+ }
109
+ className="inline-flex size-5 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent p-0 text-dashboard-text-muted transition-colors hover:bg-dashboard-fill-hover hover:text-amber-50 focus-visible:outline focus-visible:outline-1 focus-visible:outline-amber-200/55 disabled:cursor-default disabled:opacity-50"
110
+ disabled={props.cancelDisabled}
111
+ onClick={props.onCancel}
112
+ type="button"
113
+ >
114
+ {props.cancelPending ? (
115
+ <Spinner aria-hidden="true" className="size-3.25" />
116
+ ) : (
117
+ <X aria-hidden="true" size={14} strokeWidth={2.2} />
118
+ )}
119
+ </button>
93
120
  }
94
- className="inline-flex size-5 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent p-0 text-dashboard-text-muted transition-colors hover:bg-dashboard-fill-hover hover:text-amber-50 focus-visible:outline focus-visible:outline-1 focus-visible:outline-amber-200/55 disabled:cursor-default disabled:opacity-50"
95
- disabled={props.cancelDisabled}
96
- onClick={props.onCancel}
97
- type="button"
98
- >
99
- {props.cancelPending ? (
100
- <LoaderCircle
101
- aria-hidden="true"
102
- className="animate-spin"
103
- size={13}
104
- />
105
- ) : (
106
- <X aria-hidden="true" size={14} strokeWidth={2.2} />
107
- )}
108
- </button>
121
+ />
122
+ <TooltipContent>Remove queued message</TooltipContent>
109
123
  </Tooltip>
110
124
  ) : null}
111
125
  </TranscriptHeadingMeta>
@@ -16,9 +16,13 @@ shared controls and overlays.
16
16
  `flush` when the text already sits on the transcript edge. It extends the
17
17
  padded control around that text. Do not repeat negative margins at call sites.
18
18
  The caller owns the native `details` element and its content.
19
+ `TranscriptSummaryTrigger` gives a `ui/collapsible` trigger the same look.
20
+ Its caller owns the `Collapsible` and its content. Transcript search holds
21
+ event and context rows open.
19
22
  - Activity rows (tools, reasoning, and grouped rail events) share one
20
23
  layout: a 1rem icon column, an 8px gap, then the title and metadata.
21
- Expanded content sits in `transcriptActivityBodyClass` under the title.
24
+ Tool and reasoning rows use `ui/trace`. Transcript search forces them open.
25
+ Expanded content sits in `TraceCollapsibleContent` under the title.
22
26
  `TranscriptActivityNesting` tells a rail event that it is inside an activity
23
27
  group. Outside a group, the rail icon sits in the avatar column so the text
24
28
  lines up with message text.
@@ -40,23 +44,46 @@ shared controls and overlays.
40
44
  Automation. Render this text with `TranscriptText` or `MentionText`. Both
41
45
  require `mentions`, so a caller cannot forget them. Do not resolve names in
42
46
  the dashboard, and do not rewrite tokens into links on the server.
43
- - `TranscriptTurnContextView` owns context content. The shared `Drawer` adds
44
- its header and layout to the Base UI `Sheet` focus and dismissal boundary.
47
+ - `TranscriptTurnContextView` owns context content. A transcript with a
48
+ `turnContextPanel` shows the context in the page panel. See "Page panel".
49
+ Other transcripts, such as a subagent drawer, open a modal `Drawer`. Each
50
+ `Transcript` sets the panel for its own rows, so a nested transcript does not
51
+ use the panel of the page behind it. The panel keys context by message id,
52
+ so every control for one message shows the same open state.
45
53
 
46
- Review these components together in `/dev/transcripts`. Use the conversation
47
- fixture for messages, events, activity, context, and typing. Use the attachment
48
- fixture for file wrapping. Keep visual checks out of automated assertions;
49
- browser tests cover opening, closing, and keyboard interaction.
54
+ Review these components together on the mock Conversations. Run
55
+ `JUNIOR_DASHBOARD_MOCK_CONVERSATIONS=true pnpm dev` for messages, events,
56
+ activity, context, typing, and attachments. Keep visual checks out of automated
57
+ assertions; browser tests cover opening, closing, and keyboard interaction.
50
58
 
51
59
  ## Conversation details
52
60
 
53
- `ConversationPage` owns whether details are open. The page is keyed by the
54
- Conversation, so switching Conversations closes details. `ConversationHeader`
55
- only toggles details and restores focus through explicit control refs after
56
- dismissal.
61
+ `ConversationPage` owns whether details are open. At 1440px and above, details
62
+ open by default. An explicit choice lasts for the current Conversation. The page
63
+ is keyed by the Conversation, so switching Conversations resets that choice.
64
+ `ConversationHeader` toggles details and restores focus through explicit control
65
+ refs after dismissal.
57
66
 
58
- `ConversationDetails` owns one set of tabs and content. `ConversationDetailsFrame`
59
- uses a 400px aside at 1280px and above. The chat and aside scroll separately.
67
+ ## Page panel
68
+
69
+ The Conversation page has one panel on the right. It shows Conversation
70
+ details, event details, or turn context. Only one shows at a time. Each uses
71
+ `DetailsPanel`: a 400px aside at 1280px and above, and a `BottomSheet` below.
72
+
73
+ - Event details and turn context show in place of Conversation details.
74
+ - When you close them, Conversation details show again if they are open. The
75
+ details stay open by choice or by the 1440px default.
76
+ - A link that opens an event sets details to closed. When you close that
77
+ event, no panel shows.
78
+ - The details control closes event details and turn context, then shows or
79
+ hides Conversation details.
80
+ - Turn context belongs to the Conversation view. A view change closes it.
81
+ Select the same context control again to close it.
82
+ - Closing a panel returns focus to the row or control that opened it.
83
+
84
+ `ConversationDetails` owns one set of tabs and content. It supplies its title and
85
+ actions to `DetailsPanel`, which uses a 400px aside at 1280px and above. The chat
86
+ and aside scroll separately.
60
87
  Smaller windows use `BottomSheet` with a visible title and close control. The sheet and
61
88
  side drawers use the shadcn Base UI `Sheet` and share the app's document
62
89
  scroll lock. A resize changes the frame and keeps the selected tab. Tabs stay
@@ -64,3 +91,52 @@ fixed above the scrolling content in both layouts. The sheet title and close con
64
91
  control, tap the backdrop, or press Escape to dismiss it. Base UI owns the
65
92
  transition lifecycle.
66
93
  A pending exit cannot close a new panel or a different Conversation.
94
+
95
+ ## Event log
96
+
97
+ `ChatLayout` keeps the Conversation header outside the transcript scroll region.
98
+ `ConversationEventLog` fills the pane without a card border or page inset. Its
99
+ search toolbar and column labels stay together at the top while rows scroll.
100
+ `Transcript` shares the search value with its owner, or keeps it locally when
101
+ used alone. Event log search stays visible; the transcript search toggle is for
102
+ the Conversation view.
103
+
104
+ Rows keep time, event type, and summary in columns when the pane is at least
105
+ 512px wide. Smaller panes put the summary below time and type. Model and usage
106
+ metadata share one optional line. Missing model metadata is omitted from rows.
107
+ Selected rows have a background and an accent edge. Up and Down select the
108
+ previous or next visible row while a row has focus. Selection stops at the
109
+ ends of the filtered list. The selected row stays clear of the fixed toolbar.
110
+ `eventLogRows` and `filterEventLogRows` own the row text and search match.
111
+ The page uses them too, so its step controls follow the same rows.
112
+
113
+ The timezone toggle uses `useEventTime` to share a UTC/local preference through
114
+ `@uidotdev/usehooks` and browser local storage. It applies to event row clocks
115
+ and detail timestamps. Local mode follows the browser timezone. The button uses
116
+ a generic zone name because a log can span standard and daylight-saving time.
117
+ Detail timestamps use the zone name for each event date. The mode, not
118
+ the timezone identifier, persists across visits and tabs. This hook belongs to
119
+ the client-rendered event viewer. Raw JSON retains the original ISO values.
120
+
121
+ ## Event details
122
+
123
+ The URL owns the selected event in the main Conversation. `ConversationPage`
124
+ places `ConversationEventDetails` beside the log in the page panel. Selecting
125
+ another row changes the event and resets its scroll position and disclosures.
126
+ Closing returns focus to that row. Switching Conversations removes the panel.
127
+
128
+ The previous and next controls select the adjacent visible row. They follow
129
+ the event search, like Up and Down. They are necessary below 1280px, where the
130
+ sheet covers the log. At the ends of the list, the control is disabled but
131
+ keeps focus. The sheet title is the event type.
132
+
133
+ A linked event can be older than the loaded pages. While the event loads, the
134
+ panel stays open. Only its header shows placeholders, so it does not compete
135
+ with the log. While the transcript loads, `ConversationEventLogLoading` shows
136
+ the log toolbar, columns, and row skeletons in the event log view.
137
+
138
+ Standalone transcripts, such as a subagent drawer, keep selection in
139
+ `ConversationEventLog`. Their event details use a nested drawer on desktop and
140
+ `BottomSheet` on smaller screens. This keeps the main log free of modal behavior
141
+ while a nested transcript retains its parent drawer's focus rules. These event
142
+ details have the same previous and next controls.
@@ -2,8 +2,12 @@ import { useState } from "react";
2
2
  import { useHref } from "react-router";
3
3
  import { Check, Share2 } from "lucide-react";
4
4
 
5
- import { Button } from "../components/Button";
6
- import { IconButtonTooltip } from "../components/Tooltip";
5
+ import { Button } from "../components/ui/button";
6
+ import {
7
+ Tooltip,
8
+ TooltipContent,
9
+ TooltipTrigger,
10
+ } from "../components/ui/tooltip";
7
11
  import { conversationPath } from "./conversationRoutes";
8
12
 
9
13
  /** Share a conversation link without changing who can access it. */
@@ -72,27 +76,34 @@ export function ShareConversationButton(props: {
72
76
  );
73
77
  const button =
74
78
  props.layout === "icon" ? (
75
- <IconButtonTooltip label={label} placement="below">
76
- <Button
77
- aria-label={label}
78
- className="text-dashboard-text-muted"
79
- disabled={status === "sharing"}
80
- onClick={() => void shareLink()}
81
- size="icon"
82
- >
83
- <Icon aria-hidden="true" size={15} />
84
- </Button>
85
- </IconButtonTooltip>
79
+ <Tooltip>
80
+ <TooltipTrigger
81
+ render={
82
+ <Button
83
+ focusableWhenDisabled
84
+ aria-label={label}
85
+ className="text-dashboard-text-muted"
86
+ disabled={status === "sharing"}
87
+ onClick={() => void shareLink()}
88
+ size="icon"
89
+ >
90
+ <Icon aria-hidden="true" size={15} />
91
+ </Button>
92
+ }
93
+ />
94
+ <TooltipContent size="compact" side="bottom">
95
+ {label}
96
+ </TooltipContent>
97
+ </Tooltip>
86
98
  ) : props.layout === "menu" ? (
87
- <button
99
+ <Button
88
100
  aria-label={label}
89
- className="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 disabled:cursor-not-allowed disabled:opacity-50"
90
101
  disabled={status === "sharing"}
91
102
  onClick={() => void shareLink()}
92
- type="button"
103
+ size="menu"
93
104
  >
94
105
  {content}
95
- </button>
106
+ </Button>
96
107
  ) : (
97
108
  <Button
98
109
  aria-label={label}