@sentry/junior-dashboard 0.264.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 (191) 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/BottomSheet.tsx +12 -2
  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 +15 -24
  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 +86 -77
  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 +13 -3
  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/conversations/ChatLayout.tsx +7 -3
  60. package/src/client/conversations/ConversationBrief.tsx +17 -6
  61. package/src/client/conversations/ConversationComposer.tsx +26 -12
  62. package/src/client/conversations/ConversationDetails.tsx +147 -62
  63. package/src/client/conversations/ConversationEventDetails.tsx +141 -0
  64. package/src/client/conversations/ConversationEventLog.tsx +150 -129
  65. package/src/client/conversations/ConversationHeader.tsx +8 -3
  66. package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
  67. package/src/client/conversations/ConversationHomeList.tsx +1 -1
  68. package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
  69. package/src/client/conversations/ConversationMeta.tsx +55 -88
  70. package/src/client/conversations/ConversationPage.tsx +220 -101
  71. package/src/client/conversations/ConversationSidebar.tsx +48 -29
  72. package/src/client/conversations/ConversationWatches.tsx +165 -0
  73. package/src/client/conversations/ConversationWorkspace.tsx +12 -22
  74. package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
  75. package/src/client/conversations/DetailsList.tsx +124 -0
  76. package/src/client/conversations/EventDetails.tsx +42 -8
  77. package/src/client/conversations/ForkConversationButton.tsx +27 -16
  78. package/src/client/conversations/ObjectCard.tsx +20 -8
  79. package/src/client/conversations/PendingMailboxStack.tsx +52 -33
  80. package/src/client/conversations/README.md +86 -13
  81. package/src/client/conversations/ShareConversationButton.tsx +25 -13
  82. package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
  83. package/src/client/conversations/ToolFrame.tsx +36 -52
  84. package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
  85. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
  86. package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
  87. package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
  88. package/src/client/conversations/TranscriptLoading.tsx +1 -1
  89. package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
  90. package/src/client/conversations/TranscriptMessageContextView.tsx +8 -2
  91. package/src/client/conversations/TranscriptMessageView.tsx +6 -1
  92. package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
  93. package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
  94. package/src/client/conversations/TranscriptToolView.tsx +1 -1
  95. package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
  96. package/src/client/conversations/TranscriptView.tsx +63 -27
  97. package/src/client/conversations/eventLog.ts +57 -0
  98. package/src/client/conversations/eventTime.ts +60 -0
  99. package/src/client/conversations/queries.ts +29 -0
  100. package/src/client/conversations/transcriptBottomPinning.ts +34 -0
  101. package/src/client/format.ts +5 -0
  102. package/src/client/pages/ConnectionsPage.tsx +2 -2
  103. package/src/client/pages/PersonalTokensPage.tsx +4 -4
  104. package/src/client/pages/SettingsPage.tsx +9 -10
  105. package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
  106. package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
  107. package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
  108. package/src/client/pages/automations/AutomationDetails.tsx +193 -0
  109. package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
  110. package/src/client/pages/automations/AutomationEditor.tsx +41 -29
  111. package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
  112. package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
  113. package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
  114. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
  115. package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
  116. package/src/client/pages/automations/AutomationFilters.tsx +189 -74
  117. package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
  118. package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
  119. package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
  120. package/src/client/pages/automations/AutomationRow.tsx +12 -138
  121. package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
  122. package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
  123. package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
  124. package/src/client/pages/automations/AutomationStatus.tsx +201 -0
  125. package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
  126. package/src/client/pages/automations/AutomationsPage.tsx +80 -68
  127. package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
  128. package/src/client/pages/automations/README.md +57 -0
  129. package/src/client/pages/automations/TriggerFilters.tsx +22 -0
  130. package/src/client/pages/automations/automationFormat.ts +37 -23
  131. package/src/client/pages/code/CodeActivityChart.tsx +59 -101
  132. package/src/client/pages/code/CodePage.tsx +52 -55
  133. package/src/client/pages/code/RepositoryPage.tsx +6 -8
  134. package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
  135. package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
  136. package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
  137. package/src/client/pages/memory/MemoryDetails.tsx +218 -0
  138. package/src/client/pages/memory/MemoryPage.tsx +168 -112
  139. package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
  140. package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
  141. package/src/client/pages/memory/memoryRecord.ts +1 -1
  142. package/src/client/pages/people/ContributionGrid.tsx +23 -16
  143. package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
  144. package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
  145. package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
  146. package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
  147. package/src/client/pages/system/GuardianActivity.tsx +1 -2
  148. package/src/client/pages/system/PluginBarChart.tsx +106 -87
  149. package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
  150. package/src/client/pages/system/README.md +23 -0
  151. package/src/client/pages/system/SystemNavigation.tsx +5 -1
  152. package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
  153. package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
  154. package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
  155. package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
  156. package/src/client/pages/system/WorkspaceList.tsx +20 -30
  157. package/src/client/pages/system/WorkspacesPage.tsx +117 -119
  158. package/src/client/pages/system/sandboxSize.ts +8 -0
  159. package/src/client/pages/system/workspaceQueries.ts +53 -0
  160. package/src/client/pages/user/PluginUserPage.tsx +51 -2
  161. package/src/client/pages/user/pluginUserPageData.ts +1 -6
  162. package/src/client/styles.ts +4 -0
  163. package/src/client.tsx +7 -1
  164. package/src/mock-reporting/automation-collection.ts +4 -2
  165. package/src/mock-reporting/fixtures.ts +23 -7
  166. package/src/mock-reporting/routes.ts +12 -0
  167. package/src/mock-reporting/watches.ts +94 -0
  168. package/src/shell.ts +4 -9
  169. package/src/tailwind.css +0 -10
  170. package/src/client/components/Button.tsx +0 -118
  171. package/src/client/components/Field.tsx +0 -53
  172. package/src/client/components/SegmentedTabs.tsx +0 -119
  173. package/src/client/components/Select.tsx +0 -52
  174. package/src/client/components/Skeleton.tsx +0 -17
  175. package/src/client/components/TextInput.tsx +0 -58
  176. package/src/client/components/Tooltip.tsx +0 -216
  177. package/src/client/components/charts/useChartLayout.ts +0 -27
  178. package/src/client/components/ui/sheet.LICENSE +0 -21
  179. package/src/client/conversations/ConversationDetailsFrame.tsx +0 -119
  180. package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
  181. package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
  182. package/src/client/pages/dev/ChartFixtures.tsx +0 -325
  183. package/src/client/pages/dev/ComponentsPage.tsx +0 -899
  184. package/src/client/pages/dev/ConversationFixture.tsx +0 -231
  185. package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
  186. package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
  187. package/src/client/pages/dev/SelectFixtures.tsx +0 -62
  188. package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
  189. package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
  190. package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
  191. package/src/client/pages/system/SnapshotSummary.tsx +0 -78
@@ -0,0 +1,96 @@
1
+ import * as React from "react";
2
+ import { cn } from "@dashboard/lib/utils";
3
+
4
+ import {
5
+ Collapsible,
6
+ CollapsibleContent,
7
+ CollapsibleTrigger,
8
+ } from "@dashboard/components/ui/collapsible";
9
+ import { ChevronRightIcon } from "lucide-react";
10
+
11
+ function TraceIcon({ className, ...props }: React.ComponentProps<"span">) {
12
+ return (
13
+ <span
14
+ data-slot="trace-icon"
15
+ aria-hidden="true"
16
+ className={cn(
17
+ "flex size-4 shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
18
+ className,
19
+ )}
20
+ {...props}
21
+ />
22
+ );
23
+ }
24
+
25
+ /** Keep one trace row and its details in one open state. */
26
+ function TraceCollapsible({
27
+ className,
28
+ ...props
29
+ }: Omit<React.ComponentProps<typeof Collapsible>, "open"> &
30
+ (
31
+ | { open?: undefined }
32
+ | {
33
+ open: boolean;
34
+ onOpenChange: NonNullable<
35
+ React.ComponentProps<typeof Collapsible>["onOpenChange"]
36
+ >;
37
+ }
38
+ )) {
39
+ return (
40
+ <Collapsible
41
+ data-slot="trace-collapsible"
42
+ className={cn("flex w-full min-w-0 flex-col", className)}
43
+ {...props}
44
+ />
45
+ );
46
+ }
47
+
48
+ /** Render the row button with a chevron that turns while open. */
49
+ function TraceCollapsibleTrigger({
50
+ className,
51
+ children,
52
+ ...props
53
+ }: React.ComponentProps<typeof CollapsibleTrigger>) {
54
+ return (
55
+ <CollapsibleTrigger
56
+ data-slot="trace-collapsible-trigger"
57
+ className={cn(
58
+ "group/trace-collapsible-trigger flex w-fit max-w-full min-w-0 cursor-pointer items-center gap-1 rounded-md px-2 py-1.5 text-left text-dashboard-text-muted transition-colors outline-none hover:bg-dashboard-fill-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus disabled:cursor-default disabled:hover:bg-transparent",
59
+ className,
60
+ )}
61
+ {...props}
62
+ >
63
+ {children}
64
+ <TraceIcon className="transition-transform group-aria-expanded/trace-collapsible-trigger:rotate-90 motion-reduce:transition-none">
65
+ <ChevronRightIcon className="size-3 text-dashboard-text-faint" />
66
+ </TraceIcon>
67
+ </CollapsibleTrigger>
68
+ );
69
+ }
70
+
71
+ /** Reveal row details in a card under the row title. */
72
+ function TraceCollapsibleContent({
73
+ className,
74
+ children,
75
+ ...props
76
+ }: React.ComponentProps<typeof CollapsibleContent>) {
77
+ return (
78
+ <CollapsibleContent
79
+ data-slot="trace-collapsible-content"
80
+ className="h-(--collapsible-panel-height) min-w-0 overflow-hidden transition-[height] duration-200 data-ending-style:h-0 data-starting-style:h-0 motion-reduce:transition-none"
81
+ {...props}
82
+ >
83
+ <div
84
+ data-slot="trace-collapsible-content-inner"
85
+ className={cn(
86
+ "mt-1.5 ml-6 min-w-0 overflow-hidden rounded-md border border-dashboard-border-subtle bg-dashboard-surface-panel",
87
+ className,
88
+ )}
89
+ >
90
+ {children}
91
+ </div>
92
+ </CollapsibleContent>
93
+ );
94
+ }
95
+
96
+ export { TraceCollapsible, TraceCollapsibleTrigger, TraceCollapsibleContent };
@@ -5,12 +5,14 @@ import { cn } from "../styles";
5
5
  /**
6
6
  * Own the conversation chat frame: one scroll region above one dock.
7
7
  *
8
- * Pages pass `scroll` and `dock` only. Do not invent footer padding, safe-area,
8
+ * Pages pass `header`, `scroll`, and `dock`. Do not invent footer padding, safe-area,
9
9
  * or shell height math outside this component and `ComposerDock`.
10
10
  */
11
11
  export function ChatLayout(props: {
12
12
  className?: string;
13
13
  dock?: ReactNode;
14
+ /** Keep conversation chrome outside the scrolling content. */
15
+ header?: ReactNode;
14
16
  /** When true, keep a taller minimum scroll region for long transcripts. */
15
17
  scrollMinTall?: boolean;
16
18
  scroll: ReactNode;
@@ -20,11 +22,13 @@ export function ChatLayout(props: {
20
22
  return (
21
23
  <div
22
24
  className={cn(
23
- "grid h-full min-h-0 min-w-0 grid-rows-[minmax(0,1fr)_minmax(0,auto)]",
24
- props.scrollMinTall && "grid-rows-[minmax(7rem,1fr)_minmax(0,auto)]",
25
+ "grid h-full min-h-0 min-w-0 grid-rows-[auto_minmax(0,1fr)_minmax(0,auto)]",
26
+ props.scrollMinTall &&
27
+ "grid-rows-[auto_minmax(7rem,1fr)_minmax(0,auto)]",
25
28
  props.className,
26
29
  )}
27
30
  >
31
+ <div className="min-w-0">{props.header}</div>
28
32
  <div
29
33
  aria-label={props.scrollAriaLabel}
30
34
  className={cn(
@@ -4,7 +4,13 @@ import type { ConversationDetailReport } from "@sentry/junior/api/schema";
4
4
 
5
5
  import { Detail, DetailList } from "../components/DetailList";
6
6
  import { StatusChip, type StatusChipTone } from "../components/StatusChip";
7
+ import {
8
+ Collapsible,
9
+ CollapsibleContent,
10
+ CollapsibleTrigger,
11
+ } from "../components/ui/collapsible";
7
12
  import { formatTime } from "../format";
13
+ import { cn, dashboardDisclosureChevronClass } from "../styles";
8
14
  import { MentionPeopleProvider, renderMentionText } from "./transcriptMentions";
9
15
 
10
16
  type ConversationBriefReport = NonNullable<ConversationDetailReport["brief"]>;
@@ -160,16 +166,21 @@ function BriefContentView(props: {
160
166
 
161
167
  function BriefSection(props: { children: ReactNode; title: string }) {
162
168
  return (
163
- <details className="group min-w-0 border-t border-dashboard-border pt-3">
164
- <summary className="flex cursor-pointer list-none items-center gap-2 text-sm font-medium text-dashboard-text [&::-webkit-details-marker]:hidden focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus">
169
+ <Collapsible className="min-w-0 border-t border-dashboard-border pt-3">
170
+ <CollapsibleTrigger className="group flex w-full cursor-pointer items-center gap-2 text-left text-sm font-medium text-dashboard-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus">
165
171
  <ChevronRight
166
172
  aria-hidden="true"
167
- className="size-3.5 shrink-0 text-dashboard-text-muted transition-transform group-open:rotate-90"
173
+ className={cn(
174
+ dashboardDisclosureChevronClass,
175
+ "text-dashboard-text-muted",
176
+ )}
168
177
  />
169
178
  {props.title}
170
- </summary>
171
- <div className="mt-3">{props.children}</div>
172
- </details>
179
+ </CollapsibleTrigger>
180
+ <CollapsibleContent hiddenUntilFound>
181
+ <div className="mt-3">{props.children}</div>
182
+ </CollapsibleContent>
183
+ </Collapsible>
173
184
  );
174
185
  }
175
186
 
@@ -12,7 +12,12 @@ import {
12
12
  } from "react";
13
13
  import { ImagePlus, Send, Square } from "lucide-react";
14
14
 
15
- import { Button } from "../components/Button";
15
+ import { Button } from "../components/ui/button";
16
+ import {
17
+ Tooltip,
18
+ TooltipContent,
19
+ TooltipTrigger,
20
+ } from "../components/ui/tooltip";
16
21
  import { useDashboardOnline } from "../connection";
17
22
  import { cn, dashboardComposerSurfaceClass } from "../styles";
18
23
 
@@ -447,16 +452,25 @@ export const ConversationComposer = memo(function ConversationComposer(
447
452
  )}
448
453
  >
449
454
  <div className="flex min-w-0 items-center gap-3">
450
- <Button
451
- aria-label="Attach images"
452
- disabled={sendLocked || readingImages}
453
- onClick={() => fileInputRef.current?.click()}
454
- size="icon"
455
- title="Attach images (up to 3 MB total)"
456
- type="button"
457
- >
458
- <ImagePlus aria-hidden="true" size={16} />
459
- </Button>
455
+ <Tooltip>
456
+ <TooltipTrigger
457
+ render={
458
+ <Button
459
+ focusableWhenDisabled
460
+ aria-label="Attach images"
461
+ disabled={sendLocked || readingImages}
462
+ onClick={() => fileInputRef.current?.click()}
463
+ size="icon"
464
+ type="button"
465
+ >
466
+ <ImagePlus aria-hidden="true" size={16} />
467
+ </Button>
468
+ }
469
+ />
470
+ <TooltipContent size="compact">
471
+ Attach images (up to 3 MB total)
472
+ </TooltipContent>
473
+ </Tooltip>
460
474
  {props.footerStart}
461
475
  {props.footerStart ? null : (
462
476
  <div className="hidden min-w-0 font-sans text-xs leading-relaxed text-dashboard-text-muted md:block">
@@ -488,7 +502,7 @@ export const ConversationComposer = memo(function ConversationComposer(
488
502
  readingImages
489
503
  }
490
504
  title={sendLocked ? "Sending message" : props.submitLabel}
491
- tone="primary"
505
+ variant="primary"
492
506
  type="submit"
493
507
  >
494
508
  <Send aria-hidden="true" size={14} />
@@ -1,34 +1,52 @@
1
1
  import { useState, type ReactNode } from "react";
2
2
 
3
- import { IconButtonTooltip } from "../components/Tooltip";
3
+ import { DetailsPanel, useDetailsPanel } from "../components/DetailsPanel";
4
4
  import {
5
- ConversationDetailsFrame,
6
- useConversationDetailsPanel,
7
- } from "./ConversationDetailsFrame";
8
- import { SegmentedTabs } from "../components/SegmentedTabs";
5
+ Tooltip,
6
+ TooltipContent,
7
+ TooltipTrigger,
8
+ } from "../components/ui/tooltip";
9
+ import {
10
+ Tabs,
11
+ TabsContent,
12
+ TabsList,
13
+ TabsTrigger,
14
+ } from "../components/ui/tabs";
9
15
  import {
10
16
  ConversationChildAgents,
11
17
  useConversationChildAgents,
12
18
  } from "./ConversationChildAgents";
19
+ import {
20
+ ConversationLinkedWork,
21
+ type ConversationLink,
22
+ } from "./ConversationLinkedWork";
13
23
  import {
14
24
  ConversationMemories,
15
25
  useConversationMemories,
16
26
  } from "./ConversationMemories";
27
+ import {
28
+ ConversationWatches,
29
+ useConversationWatches,
30
+ } from "./ConversationWatches";
17
31
  import { ShareConversationButton } from "./ShareConversationButton";
18
32
  import type { SubagentTranscriptTarget } from "./SubagentTranscriptDrawer";
19
33
 
20
34
  type DetailsTab = "agents" | "summary" | "memories";
21
35
 
36
+ /** Keep the tabs fixed while each panel scrolls to the frame edge. */
37
+ const detailsPanelClass =
38
+ "min-h-0 overflow-y-auto overscroll-contain rounded-none px-4 pb-[max(1rem,env(safe-area-inset-bottom))] focus-visible:-outline-offset-2";
39
+
22
40
  /**
23
- * Show conversation details: the brief and facts first, then linked work,
24
- * participants, and the usage breakdown. Share and Sentry links stay in the
25
- * header so every tab can reach them.
41
+ * Show conversation details: the brief, participants, and run facts first,
42
+ * then watches, linked work, and the usage breakdown. Share and Sentry links
43
+ * stay in the header so every tab can reach them.
26
44
  */
27
45
  export function ConversationDetails(props: {
28
- annotations: ReactNode;
29
46
  brief: ReactNode;
30
47
  conversationId: string;
31
48
  identity: ReactNode;
49
+ links: ConversationLink[];
32
50
  onClose(): void;
33
51
  /** Open one child agent transcript. A details sheet closes first. */
34
52
  onOpenSubagent(target: SubagentTranscriptTarget): void;
@@ -37,29 +55,47 @@ export function ConversationDetails(props: {
37
55
  usage: ReactNode;
38
56
  }) {
39
57
  const [tab, setTab] = useState<DetailsTab>("summary");
40
- const panel = useConversationDetailsPanel();
58
+ const panel = useDetailsPanel();
41
59
  // Load both lists with details so each tab can show its count.
42
60
  const memories = useConversationMemories(props.conversationId);
43
61
  const agents = useConversationChildAgents(props.conversationId);
44
- const tabs = [
45
- { label: "Summary", value: "summary" },
46
- {
47
- count: memories.data?.memories.length,
48
- label: "Memories",
49
- value: "memories",
50
- },
51
- { count: agents.data?.agents.length, label: "Subagents", value: "agents" },
52
- ] as const;
62
+ const watches = useConversationWatches(props.conversationId).data?.watches;
53
63
 
64
+ // People first, then run facts. Watches come before linked work: they say
65
+ // what can wake this agent later, so readers see them without another tab.
54
66
  const sections = [
55
- { content: props.overview, key: "overview", title: undefined },
56
67
  { content: props.identity, key: "participants", title: "Participants" },
57
- { content: props.annotations, key: "linked-work", title: "Linked work" },
68
+ { content: props.overview, key: "overview", title: undefined },
69
+ {
70
+ content: watches?.length ? (
71
+ <ConversationWatches watches={watches} />
72
+ ) : null,
73
+ count: watches?.length,
74
+ key: "watching",
75
+ title: "Watching",
76
+ },
77
+ {
78
+ content: props.links.length ? (
79
+ <ConversationLinkedWork links={props.links} />
80
+ ) : null,
81
+ count: props.links.length,
82
+ key: "linked-work",
83
+ title: "Linked work",
84
+ },
58
85
  { content: props.usage, key: "usage", title: "Usage" },
59
86
  ].filter((section) => section.content != null);
60
87
 
61
88
  return (
62
- <ConversationDetailsFrame
89
+ <DetailsPanel
90
+ label="Conversation details"
91
+ title={
92
+ <h2
93
+ aria-label="Conversation details"
94
+ className="m-0 text-base font-semibold text-dashboard-text"
95
+ >
96
+ Details
97
+ </h2>
98
+ }
63
99
  actions={
64
100
  <>
65
101
  <ShareConversationButton
@@ -68,32 +104,79 @@ export function ConversationDetails(props: {
68
104
  layout="icon"
69
105
  />
70
106
  {props.sentryConversationUrl ? (
71
- <IconButtonTooltip label="Open in Sentry" placement="below">
72
- <a
73
- aria-label="Open in Sentry"
74
- className="grid size-9 place-items-center rounded-md text-dashboard-text-muted transition-colors hover:bg-dashboard-fill-strong hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus"
75
- href={props.sentryConversationUrl}
76
- rel="noopener noreferrer"
77
- target="_blank"
78
- >
79
- <SentryGlyph />
80
- </a>
81
- </IconButtonTooltip>
107
+ <Tooltip>
108
+ <TooltipTrigger
109
+ render={
110
+ <a
111
+ aria-label="Open in Sentry"
112
+ className="grid size-9 place-items-center rounded-md text-dashboard-text-muted transition-colors hover:bg-dashboard-fill-strong hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus"
113
+ href={props.sentryConversationUrl}
114
+ rel="noopener noreferrer"
115
+ target="_blank"
116
+ >
117
+ <SentryGlyph />
118
+ </a>
119
+ }
120
+ />
121
+ <TooltipContent size="compact" side="bottom">
122
+ Open in Sentry
123
+ </TooltipContent>
124
+ </Tooltip>
82
125
  ) : null}
83
126
  </>
84
127
  }
85
128
  onClose={props.onClose}
86
129
  >
87
- <SegmentedTabs
88
- scroll
89
- items={tabs}
90
- label="Conversation details"
91
- onChange={setTab}
130
+ <Tabs
131
+ className="h-full min-h-0 grid-rows-[auto_minmax(0,1fr)] gap-0"
132
+ onValueChange={(value: DetailsTab) => setTab(value)}
92
133
  value={tab}
93
134
  >
94
- {tab === "memories" ? (
135
+ <TabsList
136
+ activateOnFocus
137
+ aria-label="Conversation details"
138
+ className="m-4"
139
+ >
140
+ <TabsTrigger value="summary">
141
+ <span className="truncate">Summary</span>
142
+ </TabsTrigger>
143
+ <TabsTrigger value="memories">
144
+ <span className="truncate">Memories</span>
145
+ <TabCount count={memories.data?.memories.length} />
146
+ </TabsTrigger>
147
+ <TabsTrigger value="agents">
148
+ <span className="truncate">Subagents</span>
149
+ <TabCount count={agents.data?.agents.length} />
150
+ </TabsTrigger>
151
+ </TabsList>
152
+ <TabsContent className={detailsPanelClass} value="summary">
153
+ {props.brief != null || sections.length > 0 ? (
154
+ <div className="grid min-w-0 gap-3">
155
+ {props.brief != null ? (
156
+ <div className="min-w-0 break-words px-1 pb-1 font-sans text-sm leading-relaxed text-dashboard-text-muted">
157
+ {props.brief}
158
+ </div>
159
+ ) : null}
160
+ {sections.map((section) => (
161
+ <DetailsSection
162
+ count={section.count}
163
+ key={section.key}
164
+ title={section.title}
165
+ >
166
+ {section.content}
167
+ </DetailsSection>
168
+ ))}
169
+ </div>
170
+ ) : (
171
+ <p className="m-0 font-sans text-sm leading-relaxed text-dashboard-text-muted">
172
+ No additional conversation details.
173
+ </p>
174
+ )}
175
+ </TabsContent>
176
+ <TabsContent className={detailsPanelClass} value="memories">
95
177
  <ConversationMemories conversationId={props.conversationId} />
96
- ) : tab === "agents" ? (
178
+ </TabsContent>
179
+ <TabsContent className={detailsPanelClass} value="agents">
97
180
  <ConversationChildAgents
98
181
  conversationId={props.conversationId}
99
182
  onOpenAgent={(target) => {
@@ -102,36 +185,38 @@ export function ConversationDetails(props: {
102
185
  props.onOpenSubagent(target);
103
186
  }}
104
187
  />
105
- ) : props.brief != null || sections.length > 0 ? (
106
- <div className="grid min-w-0 gap-3">
107
- {props.brief != null ? (
108
- <div className="min-w-0 break-words px-1 pb-1 font-sans text-sm leading-relaxed text-dashboard-text-muted">
109
- {props.brief}
110
- </div>
111
- ) : null}
112
- {sections.map((section) => (
113
- <DetailsSection key={section.key} title={section.title}>
114
- {section.content}
115
- </DetailsSection>
116
- ))}
117
- </div>
118
- ) : (
119
- <p className="m-0 font-sans text-sm leading-relaxed text-dashboard-text-muted">
120
- No additional conversation details.
121
- </p>
122
- )}
123
- </SegmentedTabs>
124
- </ConversationDetailsFrame>
188
+ </TabsContent>
189
+ </Tabs>
190
+ </DetailsPanel>
191
+ );
192
+ }
193
+
194
+ /** Show a panel's entry count. Zero and unknown counts are hidden. */
195
+ function TabCount(props: { count: number | undefined }) {
196
+ if (!props.count) return null;
197
+ return (
198
+ <span className="min-w-5 shrink-0 rounded-full bg-dashboard-fill-strong px-1.5 text-center text-xs font-semibold leading-5 tabular-nums">
199
+ {props.count.toLocaleString("en-US")}
200
+ </span>
125
201
  );
126
202
  }
127
203
 
128
204
  /** Group one kind of conversation detail in a compact card. */
129
- function DetailsSection(props: { children: ReactNode; title?: string }) {
205
+ function DetailsSection(props: {
206
+ children: ReactNode;
207
+ count?: number;
208
+ title?: string;
209
+ }) {
130
210
  return (
131
211
  <section className="grid min-w-0 gap-2.5 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface-panel p-3">
132
212
  {props.title ? (
133
- <h3 className="m-0 font-sans text-xs font-medium text-dashboard-text-muted">
213
+ <h3 className="m-0 flex items-center gap-1.5 font-sans text-xs font-medium text-dashboard-text-muted">
134
214
  {props.title}
215
+ {props.count ? (
216
+ <span className="tabular-nums text-dashboard-text-faint">
217
+ {props.count.toLocaleString("en-US")}
218
+ </span>
219
+ ) : null}
135
220
  </h3>
136
221
  ) : null}
137
222
  <div className="min-w-0 break-words font-sans text-xs leading-relaxed text-dashboard-text-muted">
@@ -0,0 +1,141 @@
1
+ import type { ConversationReportEvent } from "@sentry/junior/api/schema";
2
+ import { ChevronDown, ChevronUp } from "lucide-react";
3
+
4
+ import { cn } from "../styles";
5
+ import { DetailsPanel } from "../components/DetailsPanel";
6
+ import { Button } from "../components/ui/button";
7
+ import { Skeleton } from "../components/ui/skeleton";
8
+ import type { ConversationTranscript } from "../types";
9
+ import { useEventTime } from "./eventTime";
10
+ import { EventDetails } from "./EventDetails";
11
+ import { ForkConversationButton } from "./ForkConversationButton";
12
+
13
+ // Step buttons keep focus at the ends of the log, so style the ARIA state.
14
+ const stepButtonClass =
15
+ "data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:!bg-transparent data-disabled:hover:!text-dashboard-text-muted";
16
+
17
+ /**
18
+ * Show the selected event without blocking the main conversation's event log.
19
+ * Without an event, show a loading state while a linked event loads.
20
+ * `previous` and `next` are the adjacent visible rows of the event log.
21
+ */
22
+ export function ConversationEventDetails(props: {
23
+ conversation?: ConversationTranscript;
24
+ event?: ConversationReportEvent;
25
+ next?: number;
26
+ onClose(): void;
27
+ onSelect?(seq: number): void;
28
+ overlay?: boolean;
29
+ previous?: number;
30
+ }) {
31
+ const { conversation, event, onSelect } = props;
32
+ const time = useEventTime();
33
+ const timestamp = event ? (
34
+ <time
35
+ className="block break-words font-mono text-xs text-dashboard-text-muted"
36
+ dateTime={event.createdAt}
37
+ title={event.createdAt}
38
+ >
39
+ {time.formatTimestamp(event.createdAt)}
40
+ </time>
41
+ ) : null;
42
+ const title = event ? (
43
+ <>
44
+ <h2 className="m-0 mb-1 break-words font-mono text-sm font-semibold text-dashboard-text">
45
+ {event.data.type}
46
+ </h2>
47
+ {timestamp}
48
+ </>
49
+ ) : (
50
+ <div className="grid gap-2 py-0.5">
51
+ <Skeleton className="h-3.5 w-28" />
52
+ <Skeleton className="h-3 w-40" />
53
+ </div>
54
+ );
55
+ const actions =
56
+ onSelect && event ? (
57
+ <>
58
+ <Button
59
+ aria-label="Previous event"
60
+ className={stepButtonClass}
61
+ disabled={props.previous === undefined}
62
+ focusableWhenDisabled
63
+ onClick={() => {
64
+ if (props.previous !== undefined) onSelect(props.previous);
65
+ }}
66
+ size="icon"
67
+ title="Previous event"
68
+ >
69
+ <ChevronUp aria-hidden="true" size={15} />
70
+ </Button>
71
+ <Button
72
+ aria-label="Next event"
73
+ className={stepButtonClass}
74
+ disabled={props.next === undefined}
75
+ focusableWhenDisabled
76
+ onClick={() => {
77
+ if (props.next !== undefined) onSelect(props.next);
78
+ }}
79
+ size="icon"
80
+ title="Next event"
81
+ >
82
+ <ChevronDown aria-hidden="true" size={15} />
83
+ </Button>
84
+ </>
85
+ ) : undefined;
86
+
87
+ return (
88
+ <DetailsPanel
89
+ actions={actions}
90
+ label="Event details"
91
+ onClose={props.onClose}
92
+ overlay={props.overlay}
93
+ sheetTitle={event?.data.type}
94
+ title={title}
95
+ >
96
+ {/* Own the scroll here, so a new event starts at the top with closed
97
+ disclosures, and the sheet actions stay below the content. */}
98
+ <div
99
+ className={cn(
100
+ "h-full min-w-0 overflow-y-auto overscroll-contain p-4 pb-[max(1rem,env(safe-area-inset-bottom))]",
101
+ props.overlay && "xl:p-0",
102
+ )}
103
+ key={event?.seq ?? "loading"}
104
+ >
105
+ {event && conversation ? (
106
+ <>
107
+ <div className="mb-4 xl:hidden">{timestamp}</div>
108
+ {conversation.canFork &&
109
+ event.data.type === "message" &&
110
+ event.data.role === "assistant" &&
111
+ !event.data.redacted ? (
112
+ <div className="mb-3 flex items-center gap-2 text-sm">
113
+ <ForkConversationButton
114
+ conversationId={conversation.conversationId}
115
+ messageId={event.data.messageId}
116
+ />
117
+ <span>Fork after this reply</span>
118
+ </div>
119
+ ) : null}
120
+ <EventDetails event={event} />
121
+ </>
122
+ ) : (
123
+ <EventDetailsLoading />
124
+ )}
125
+ </div>
126
+ </DetailsPanel>
127
+ );
128
+ }
129
+
130
+ /**
131
+ * Keep the body quiet while the event loads. The header placeholders carry the
132
+ * state, so the panel does not compete with the log skeleton.
133
+ */
134
+ function EventDetailsLoading() {
135
+ return (
136
+ <div aria-busy="true" role="status">
137
+ <span className="sr-only">Loading event</span>
138
+ <Skeleton className="h-3 w-40 xl:hidden" />
139
+ </div>
140
+ );
141
+ }