@sentry/junior-dashboard 0.247.0 → 0.249.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 (139) hide show
  1. package/dist/app.js +69 -15
  2. package/dist/client.js +51 -50
  3. package/dist/index.js +69 -15
  4. package/dist/tailwind.css +1 -1
  5. package/package.json +3 -3
  6. package/src/client/App.tsx +1 -5
  7. package/src/client/components/ActorAvatar.tsx +7 -4
  8. package/src/client/components/AutomationCard.tsx +2 -2
  9. package/src/client/components/Button.tsx +12 -11
  10. package/src/client/components/ConversationList.tsx +3 -3
  11. package/src/client/components/ConversationSummary.tsx +2 -1
  12. package/src/client/components/DetailList.tsx +3 -3
  13. package/src/client/components/Drawer.tsx +2 -2
  14. package/src/client/components/EmptyTelemetry.tsx +1 -1
  15. package/src/client/components/Field.tsx +1 -2
  16. package/src/client/components/FilterBar.tsx +6 -6
  17. package/src/client/components/Metric.tsx +11 -16
  18. package/src/client/components/Notice.tsx +7 -7
  19. package/src/client/components/PageContentSkeleton.tsx +2 -2
  20. package/src/client/components/Pagination.tsx +2 -2
  21. package/src/client/components/ParticipantAvatarStack.tsx +4 -4
  22. package/src/client/components/ProfileMenu.tsx +5 -7
  23. package/src/client/components/README.md +122 -58
  24. package/src/client/components/SearchInput.tsx +4 -3
  25. package/src/client/components/SegmentedNav.tsx +10 -17
  26. package/src/client/components/SegmentedTabs.tsx +3 -3
  27. package/src/client/components/Select.tsx +1 -1
  28. package/src/client/components/SelectableRow.tsx +2 -2
  29. package/src/client/components/Skeleton.tsx +1 -1
  30. package/src/client/components/StatusChip.tsx +1 -1
  31. package/src/client/components/StatusDot.tsx +1 -1
  32. package/src/client/components/TextInput.tsx +1 -1
  33. package/src/client/components/Tooltip.tsx +2 -2
  34. package/src/client/components/VersionDriftBanner.tsx +2 -2
  35. package/src/client/components/charts/ActivityChart.tsx +5 -6
  36. package/src/client/components/charts/CacheWrites.tsx +3 -3
  37. package/src/client/components/charts/ChartHeader.tsx +4 -4
  38. package/src/client/components/charts/ChartLegend.tsx +1 -1
  39. package/src/client/components/charts/InputCacheChart.tsx +5 -5
  40. package/src/client/components/charts/WorkspaceUsageChart.tsx +4 -11
  41. package/src/client/components/controls/DirectorySortSelect.tsx +2 -2
  42. package/src/client/components/directory/DirectoryMetric.tsx +2 -2
  43. package/src/client/components/directory/DirectoryRowsSkeleton.tsx +5 -5
  44. package/src/client/components/directory/DirectoryTable.tsx +3 -3
  45. package/src/client/components/layout/CardHeader.tsx +4 -4
  46. package/src/client/components/layout/DashboardChrome.tsx +9 -3
  47. package/src/client/components/layout/DashboardHeader.tsx +7 -7
  48. package/src/client/components/layout/PageHeader.tsx +2 -2
  49. package/src/client/components/layout/SecondaryNavigation.tsx +7 -14
  50. package/src/client/components/layout/SectionIntro.tsx +1 -1
  51. package/src/client/components/layout/SectionTitle.tsx +1 -1
  52. package/src/client/components/metrics/StatCard.tsx +2 -2
  53. package/src/client/conversations/ConversationArchiveNotices.tsx +1 -1
  54. package/src/client/conversations/ConversationBrief.tsx +4 -4
  55. package/src/client/conversations/ConversationChildAgents.tsx +13 -4
  56. package/src/client/conversations/ConversationComposer.tsx +8 -7
  57. package/src/client/conversations/ConversationHeader.tsx +5 -5
  58. package/src/client/conversations/ConversationHeaderActions.tsx +9 -9
  59. package/src/client/conversations/ConversationHomeList.tsx +3 -3
  60. package/src/client/conversations/ConversationListStatusIcon.tsx +1 -1
  61. package/src/client/conversations/ConversationMemories.tsx +4 -4
  62. package/src/client/conversations/ConversationMeta.tsx +30 -11
  63. package/src/client/conversations/ConversationPage.tsx +13 -5
  64. package/src/client/conversations/ConversationSidebar.tsx +6 -6
  65. package/src/client/conversations/ConversationUsageBreakdown.tsx +278 -0
  66. package/src/client/conversations/ConversationWorkspace.tsx +1 -1
  67. package/src/client/conversations/ConversationWorkspaceLoading.tsx +4 -4
  68. package/src/client/conversations/CopyMarkdownButton.tsx +1 -1
  69. package/src/client/conversations/EventUsage.tsx +2 -2
  70. package/src/client/conversations/PendingMailboxStack.tsx +5 -5
  71. package/src/client/conversations/README.md +9 -0
  72. package/src/client/conversations/SubagentTranscriptDrawer.tsx +39 -23
  73. package/src/client/conversations/TelemetryMetrics.tsx +327 -180
  74. package/src/client/conversations/ToolFrame.tsx +36 -14
  75. package/src/client/conversations/TranscriptActivityGroup.tsx +10 -3
  76. package/src/client/conversations/TranscriptAttachmentsDeliveredView.tsx +4 -6
  77. package/src/client/conversations/TranscriptContextEventView.tsx +2 -2
  78. package/src/client/conversations/TranscriptEventView.tsx +1 -1
  79. package/src/client/conversations/TranscriptMarkdown.tsx +3 -3
  80. package/src/client/conversations/TranscriptMessageContextView.tsx +7 -7
  81. package/src/client/conversations/TranscriptMessageView.tsx +2 -6
  82. package/src/client/conversations/TranscriptRailEvent.tsx +20 -8
  83. package/src/client/conversations/TranscriptReasoningView.tsx +12 -12
  84. package/src/client/conversations/TranscriptStructuredEventView.tsx +16 -11
  85. package/src/client/conversations/TranscriptSubagentView.tsx +64 -39
  86. package/src/client/conversations/TranscriptText.tsx +5 -4
  87. package/src/client/conversations/TranscriptToolView.tsx +9 -11
  88. package/src/client/conversations/TranscriptTurnContextView.tsx +3 -3
  89. package/src/client/conversations/TranscriptView.tsx +3 -3
  90. package/src/client/conversations/UsageCard.tsx +172 -0
  91. package/src/client/conversations/eventTranscript.ts +7 -0
  92. package/src/client/conversations/transcriptMarkdownLinks.ts +2 -4
  93. package/src/client/conversations/transcriptStyles.ts +1 -1
  94. package/src/client/format.ts +1 -0
  95. package/src/client/pages/PersonalTokensPage.tsx +4 -4
  96. package/src/client/pages/admin/AdminPage.tsx +2 -2
  97. package/src/client/pages/automations/AutomationCostChart.tsx +2 -2
  98. package/src/client/pages/automations/AutomationDetailsDrawer.tsx +5 -10
  99. package/src/client/pages/automations/AutomationEditor.tsx +1 -1
  100. package/src/client/pages/automations/AutomationExecutionChart.tsx +2 -2
  101. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +2 -2
  102. package/src/client/pages/automations/AutomationExecutionsPage.tsx +4 -4
  103. package/src/client/pages/automations/AutomationRow.tsx +4 -4
  104. package/src/client/pages/automations/AutomationRunsPage.tsx +3 -3
  105. package/src/client/pages/automations/AutomationScheduleFields.tsx +1 -1
  106. package/src/client/pages/automations/AutomationVersionsPage.tsx +1 -1
  107. package/src/client/pages/code/CodeActivityChart.tsx +7 -6
  108. package/src/client/pages/code/CodeChangeRow.tsx +1 -1
  109. package/src/client/pages/code/CodePage.tsx +5 -5
  110. package/src/client/pages/code/RepositoryPage.tsx +2 -2
  111. package/src/client/pages/dev/ComponentsPage.tsx +40 -146
  112. package/src/client/pages/dev/TranscriptActivityFixtures.tsx +206 -0
  113. package/src/client/pages/locations/LocationDetailPage.tsx +4 -4
  114. package/src/client/pages/locations/LocationDirectory.tsx +1 -1
  115. package/src/client/pages/locations/PrivateActivityCard.tsx +3 -3
  116. package/src/client/pages/memory/MemoryCostChart.tsx +3 -5
  117. package/src/client/pages/memory/MemoryDetailsDrawer.tsx +5 -5
  118. package/src/client/pages/memory/MemoryPage.tsx +15 -15
  119. package/src/client/pages/memory/MemoryTimeline.tsx +3 -5
  120. package/src/client/pages/people/ContributionGrid.tsx +8 -5
  121. package/src/client/pages/people/PersonProfilePage.tsx +3 -6
  122. package/src/client/pages/people/ProfilePluginReports.tsx +2 -2
  123. package/src/client/pages/system/GuardianActivity.tsx +9 -9
  124. package/src/client/pages/system/PluginBarChart.tsx +4 -6
  125. package/src/client/pages/system/PluginDetails.tsx +11 -7
  126. package/src/client/pages/system/PluginPanels.tsx +8 -8
  127. package/src/client/pages/system/PluginReportRecordSet.tsx +14 -14
  128. package/src/client/pages/system/PluginReports.tsx +7 -11
  129. package/src/client/pages/system/SkillInventory.tsx +9 -9
  130. package/src/client/pages/system/SystemActivity.tsx +1 -1
  131. package/src/client/pages/system/SystemPage.tsx +3 -6
  132. package/src/client/pages/system/WorkspaceDetails.tsx +2 -5
  133. package/src/client/pages/system/WorkspaceEditor.tsx +2 -2
  134. package/src/client/pages/system/WorkspaceList.tsx +2 -2
  135. package/src/client/pages/user/PluginUserPage.tsx +3 -3
  136. package/src/client/styles.ts +5 -4
  137. package/src/client/types.ts +3 -0
  138. package/src/mock-reporting/fixtures.ts +73 -14
  139. package/src/tailwind.css +36 -38
@@ -1,4 +1,5 @@
1
1
  import { useState, type ReactNode } from "react";
2
+ import type { LucideIcon } from "lucide-react";
2
3
 
3
4
  import { cn } from "../styles";
4
5
  import {
@@ -8,10 +9,17 @@ import {
8
9
  import { useTranscriptSearch } from "./transcriptSearch";
9
10
  import { TranscriptSummary } from "./TranscriptSummary";
10
11
 
12
+ /** Frame expanded transcript activity content below its row title. */
13
+ export const transcriptActivityBodyClass =
14
+ "ml-6 mt-1.5 min-w-0 overflow-hidden rounded-md border border-dashboard-border-subtle bg-dashboard-surface-panel";
15
+
11
16
  /** Render the shared expandable/non-expandable frame for transcript tools. */
12
17
  export function ToolFrame(props: {
13
18
  children?: ReactNode;
14
19
  expandable?: boolean;
20
+ /** Icon in the activity icon column. Rail events pass their own icon. */
21
+ icon?: LucideIcon;
22
+ iconClassName?: string;
15
23
  meta: string[];
16
24
  mobileSummaryMeta?: string;
17
25
  signature: ReactNode;
@@ -21,7 +29,8 @@ export function ToolFrame(props: {
21
29
  const metaText = props.meta.join(" · ");
22
30
  const interactive = props.expandable ?? Boolean(props.children);
23
31
  const staticFrame = searchActive || !interactive;
24
- const header = (
32
+ const Icon = props.icon;
33
+ const row = (
25
34
  <TranscriptHeadingRow
26
35
  left={
27
36
  <>
@@ -62,42 +71,55 @@ export function ToolFrame(props: {
62
71
  rightClassName="ml-3 min-w-0 max-md:hidden"
63
72
  />
64
73
  );
74
+ const header = Icon ? (
75
+ <div className="grid min-w-0 grid-cols-[1rem_minmax(0,1fr)] items-center gap-2">
76
+ <Icon
77
+ aria-hidden="true"
78
+ className={cn("size-3.5 justify-self-center", props.iconClassName)}
79
+ strokeWidth={2}
80
+ />
81
+ {row}
82
+ </div>
83
+ ) : (
84
+ row
85
+ );
65
86
  const mobileMeta =
66
87
  metaText && props.children ? (
67
- <div className="hidden min-w-0 break-words bg-black/15 px-2 py-1 font-mono text-xs leading-snug text-dashboard-text-muted max-md:block">
88
+ <div className="hidden min-w-0 break-words border-b border-dashboard-border-subtle px-2.5 py-1.5 font-sans text-xs tabular-nums leading-snug text-dashboard-text-muted max-md:block">
68
89
  {metaText}
69
90
  </div>
70
91
  ) : null;
71
92
 
93
+ const body = props.children ? (
94
+ <div className={transcriptActivityBodyClass}>
95
+ {mobileMeta}
96
+ {props.children}
97
+ </div>
98
+ ) : null;
99
+
72
100
  // Force-expand tool details during search so highlighted matches are visible.
73
101
  if (staticFrame) {
74
102
  return (
75
- <div className="min-w-0 max-w-full overflow-hidden">
76
- <div className="block px-2 py-1.5 font-mono text-xs leading-tight text-dashboard-text-muted">
103
+ <div className="min-w-0 max-w-full">
104
+ <div className="block py-1.5 font-mono text-xs leading-tight text-dashboard-text-muted">
77
105
  {header}
78
106
  </div>
79
- {mobileMeta}
80
- {props.children}
107
+ {body}
81
108
  </div>
82
109
  );
83
110
  }
84
111
 
85
112
  return (
86
113
  <details
87
- className="group min-w-0 max-w-full overflow-hidden"
114
+ className="group min-w-0 max-w-full"
88
115
  open={open}
89
116
  onToggle={(event) => setOpen(event.currentTarget.open)}
90
117
  >
91
- <TranscriptSummary className="block font-mono text-xs leading-tight text-dashboard-text-muted">
118
+ <TranscriptSummary className="-mx-2 block font-mono text-xs leading-tight text-dashboard-text-muted">
92
119
  {header}
93
120
  </TranscriptSummary>
94
121
  {/* Closed details hide DOM, but still mount React children and run Shiki. */}
95
- {open ? (
96
- <>
97
- {mobileMeta}
98
- {props.children}
99
- </>
100
- ) : null}
122
+ {open ? body : null}
101
123
  </details>
102
124
  );
103
125
  }
@@ -7,6 +7,7 @@ import { formatMs } from "../format";
7
7
  import type { RenderedTranscriptEntry } from "./transcriptRenderModel";
8
8
  import { cn } from "../styles";
9
9
  import { useTranscriptSearch } from "./transcriptSearch";
10
+ import { TranscriptActivityNesting } from "./TranscriptRailEvent";
10
11
  import { TranscriptSummary } from "./TranscriptSummary";
11
12
 
12
13
  function countLabel(count: number, singular: string, plural: string): string {
@@ -156,7 +157,11 @@ export function TranscriptActivityGroup(props: {
156
157
  const live = hasLiveActivity(props.entries);
157
158
 
158
159
  if (searchActive) {
159
- return <div className="grid min-w-0 gap-1">{rows}</div>;
160
+ return (
161
+ <TranscriptActivityNesting.Provider value>
162
+ <div className="grid min-w-0 gap-1">{rows}</div>
163
+ </TranscriptActivityNesting.Provider>
164
+ );
160
165
  }
161
166
 
162
167
  const open = activityGroupOpen(userOpen);
@@ -167,7 +172,7 @@ export function TranscriptActivityGroup(props: {
167
172
  flush
168
173
  className={cn(
169
174
  "flex items-center gap-1 text-left text-xs leading-tight text-dashboard-text-muted",
170
- live && "text-cyan-100/80",
175
+ live && "text-dashboard-accent-text/80",
171
176
  )}
172
177
  onClick={(event) => {
173
178
  event.preventDefault();
@@ -190,7 +195,9 @@ export function TranscriptActivityGroup(props: {
190
195
  </span>
191
196
  </Tooltip>
192
197
  </TranscriptSummary>
193
- <div className="mt-1.5 grid min-w-0 gap-1">{rows}</div>
198
+ <TranscriptActivityNesting.Provider value>
199
+ <div className="mt-1.5 grid min-w-0 gap-1">{rows}</div>
200
+ </TranscriptActivityNesting.Provider>
194
201
  </details>
195
202
  );
196
203
  }
@@ -1,13 +1,13 @@
1
1
  import { TranscriptAttachments } from "./TranscriptAttachment";
2
2
 
3
3
  import { getDashboardAgentName } from "../agentName";
4
- import { formatMessageTimestamp } from "../format";
5
4
  import type {
6
5
  ConversationTranscript,
7
6
  TranscriptViewAttachmentsDeliveredPart,
8
7
  } from "../types";
9
8
  import { TranscriptMessageHeading } from "./TranscriptHeadingRow";
10
9
  import { TranscriptMessageShell } from "./TranscriptMessageShell";
10
+ import { TranscriptTimestamp } from "./TranscriptTimestamp";
11
11
 
12
12
  /** Render host-delivered conversation attachments as first-class transcript media. */
13
13
  export function TranscriptAttachmentsDeliveredView(props: {
@@ -15,17 +15,15 @@ export function TranscriptAttachmentsDeliveredView(props: {
15
15
  part: TranscriptViewAttachmentsDeliveredPart;
16
16
  timestamp?: number;
17
17
  }) {
18
- const timestamp = formatMessageTimestamp(props.timestamp);
19
-
20
18
  return (
21
19
  <TranscriptMessageShell role="assistant" actor={getDashboardAgentName()}>
22
20
  <TranscriptMessageHeading>
23
- <span className="max-w-full truncate font-sans text-sm font-semibold leading-6 text-cyan-100">
21
+ <span className="max-w-full truncate font-sans text-sm font-semibold leading-6 text-dashboard-accent-text">
24
22
  {getDashboardAgentName()}
25
23
  </span>
26
- {timestamp ? (
24
+ {typeof props.timestamp === "number" ? (
27
25
  <span className="text-xs leading-6 text-dashboard-text-muted">
28
- {timestamp}
26
+ <TranscriptTimestamp value={props.timestamp} />
29
27
  </span>
30
28
  ) : null}
31
29
  </TranscriptMessageHeading>
@@ -18,10 +18,10 @@ export function TranscriptContextEventView(props: {
18
18
  ? ` using the ${event.modelProfile} profile (${event.modelId})`
19
19
  : "";
20
20
  return (
21
- <article className="min-w-0 px-0.5 py-1 first:mt-0.5">
21
+ <article className="min-w-0 py-0.5">
22
22
  <div className="flex min-w-0 flex-wrap items-center justify-between gap-x-3 gap-y-1">
23
23
  <strong
24
- className={`font-display text-xs font-semibold ${
24
+ className={`font-sans text-xs font-semibold ${
25
25
  handoff ? "text-sky-100" : "text-amber-100"
26
26
  }`}
27
27
  >
@@ -41,7 +41,7 @@ export function TranscriptEventView(props: { message: TranscriptViewMessage }) {
41
41
  strokeWidth={2.2}
42
42
  />
43
43
  </TranscriptSummary>
44
- <pre className="mt-2 max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-lg border border-dashboard-border bg-dashboard-surface px-3 py-2 font-mono text-xs leading-relaxed text-dashboard-text-muted">
44
+ <pre className="mt-2 max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-lg border border-dashboard-border bg-dashboard-surface-panel px-3 py-2 font-sans text-xs leading-relaxed text-dashboard-text-muted">
45
45
  <HighlightText text={text} />
46
46
  </pre>
47
47
  </details>
@@ -177,7 +177,7 @@ function MarkdownTable(props: {
177
177
 
178
178
  return (
179
179
  <div className="min-w-0 max-w-full overflow-x-auto rounded-md border border-dashboard-border bg-dashboard-fill-faint">
180
- <table className="w-max border-collapse text-left text-sm">
180
+ <table className="w-max min-w-full border-collapse text-left text-sm">
181
181
  <thead className="bg-dashboard-overlay-soft">
182
182
  <tr>
183
183
  {Array.from({ length: columnCount }, (_, index) => {
@@ -185,7 +185,7 @@ function MarkdownTable(props: {
185
185
  return (
186
186
  <th
187
187
  className={cn(
188
- "whitespace-nowrap border-b border-dashboard-border px-2.5 py-1.5 font-mono text-xs font-medium tracking-[0.04em] text-dashboard-text-muted",
188
+ "whitespace-nowrap border-b border-dashboard-border px-2.5 py-1.5 font-sans text-xs font-medium text-dashboard-text-muted",
189
189
  tableCellAlignClass(alignment),
190
190
  )}
191
191
  key={`h-${index}`}
@@ -365,7 +365,7 @@ function renderInlineCode(text: string, keyBase: number): ReactNode[] {
365
365
  }
366
366
  nodes.push(
367
367
  <code
368
- className="rounded-[0.25rem] bg-white/[0.08] px-1 py-0.5 font-mono text-[0.9em] text-cyan-50"
368
+ className="rounded-[0.25rem] bg-dashboard-fill-strong px-1 py-0.5 font-mono text-[0.9em] text-dashboard-text"
369
369
  key={`code-${keyBase}-${part++}`}
370
370
  >
371
371
  <HighlightText text={match[2] ?? ""} />
@@ -31,12 +31,12 @@ export function TranscriptMessageContextView(props: {
31
31
  return (
32
32
  <>
33
33
  <details
34
- className="group/message-context min-w-0 rounded-lg bg-white/[0.025] px-3 py-2.5 md:hidden"
34
+ className="group/message-context min-w-0 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface-panel px-3 py-2.5 md:hidden"
35
35
  data-transcript-message-context
36
36
  >
37
37
  <TranscriptSummary
38
38
  flush
39
- className="flex items-center justify-between gap-2 font-display text-xs font-semibold text-dashboard-text-muted"
39
+ className="flex items-center justify-between gap-2 font-sans text-xs font-medium text-dashboard-text-muted"
40
40
  >
41
41
  <span className="min-w-0 truncate">Context from {actor}</span>
42
42
  <ChevronRight
@@ -45,7 +45,7 @@ export function TranscriptMessageContextView(props: {
45
45
  size={14}
46
46
  />
47
47
  </TranscriptSummary>
48
- <div className="mt-2 whitespace-pre-wrap pt-2 text-sm leading-relaxed text-dashboard-text/75">
48
+ <div className="mt-2 whitespace-pre-wrap pt-2 text-sm leading-relaxed text-dashboard-text-subtle">
49
49
  {content}
50
50
  </div>
51
51
  {!props.redacted && props.message.contexts?.length ? (
@@ -55,25 +55,25 @@ export function TranscriptMessageContextView(props: {
55
55
  ) : null}
56
56
  </details>
57
57
  <article
58
- className="hidden min-w-0 rounded-lg bg-white/[0.025] px-3 py-2.5 md:block"
58
+ className="hidden min-w-0 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface-panel px-3 py-2.5 md:block"
59
59
  data-transcript-message-context
60
60
  >
61
61
  <TranscriptHeadingRow
62
62
  left={
63
- <span className="font-display text-xs font-semibold text-dashboard-text-muted">
63
+ <span className="font-sans text-xs font-medium text-dashboard-text-muted">
64
64
  Context from {actor}
65
65
  </span>
66
66
  }
67
67
  leftClassName="min-w-0"
68
68
  right={
69
69
  timestamp ? (
70
- <TranscriptHeadingMeta className="font-mono text-2xs text-dashboard-text-muted/70">
70
+ <TranscriptHeadingMeta className="text-xs tabular-nums text-dashboard-text-muted">
71
71
  {timestamp}
72
72
  </TranscriptHeadingMeta>
73
73
  ) : undefined
74
74
  }
75
75
  />
76
- <div className="mt-1 whitespace-pre-wrap text-sm leading-relaxed text-dashboard-text/75">
76
+ <div className="mt-1 whitespace-pre-wrap text-sm leading-relaxed text-dashboard-text-subtle">
77
77
  {content}
78
78
  </div>
79
79
  {!props.redacted && props.message.contexts?.length ? (
@@ -160,11 +160,7 @@ function TranscriptMessageHeader(props: {
160
160
  </span>
161
161
  ) : null}
162
162
  {showSlack && timestamp ? <span aria-hidden="true">·</span> : null}
163
- {props.message.role === "assistant" ? (
164
- <TranscriptTimestamp value={props.message.timestamp} />
165
- ) : (
166
- timestamp
167
- )}
163
+ <TranscriptTimestamp value={props.message.timestamp} />
168
164
  </span>
169
165
  ) : null}
170
166
  </TranscriptMessageHeading>
@@ -176,7 +172,7 @@ function transcriptRoleLabelClass(role: string): string {
176
172
 
177
173
  return cn(
178
174
  "inline-block max-w-full truncate font-sans text-sm font-semibold leading-6",
179
- kind === "assistant" && "text-cyan-100",
175
+ kind === "assistant" && "text-dashboard-accent-text",
180
176
  kind === "user" && "text-dashboard-text",
181
177
  kind === "system" && "text-amber-200",
182
178
  kind === "tool" && "text-dashboard-text-muted",
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from "react";
1
+ import { createContext, useContext, type ReactNode } from "react";
2
2
  import type { ObjectType } from "@sentry/junior-plugin-api";
3
3
  import { ObjectIcon } from "../components/ObjectIcon";
4
4
  import {
@@ -33,6 +33,13 @@ type TranscriptRailEventKind =
33
33
  | "structured_event"
34
34
  | "subagent";
35
35
 
36
+ /**
37
+ * True inside an activity group. Grouped rows use the narrow activity icon
38
+ * column. Top-level rows put the icon in the avatar column, so their text
39
+ * lines up with message text.
40
+ */
41
+ export const TranscriptActivityNesting = createContext(false);
42
+
36
43
  /** Mark noteworthy transcript events with an inline status icon. */
37
44
  export function TranscriptRailEvent(props: {
38
45
  children: ReactNode;
@@ -42,30 +49,35 @@ export function TranscriptRailEvent(props: {
42
49
  }) {
43
50
  const marker = transcriptRailMarker(props.kind);
44
51
  const Icon = props.icon ?? marker.icon;
52
+ const nested = useContext(TranscriptActivityNesting);
45
53
 
46
54
  return (
47
55
  <div
48
56
  className={cn(
49
57
  "grid min-w-0 items-start",
50
- props.kind === "event"
51
- ? "grid-cols-[2rem_minmax(0,1fr)] gap-3"
52
- : "grid-cols-[auto_minmax(0,1fr)] gap-2",
58
+ nested
59
+ ? "grid-cols-[1rem_minmax(0,1fr)] gap-2"
60
+ : "grid-cols-[2rem_minmax(0,1fr)] gap-3",
53
61
  )}
54
62
  data-transcript-rail-event={props.kind}
55
63
  >
56
64
  <span
57
65
  aria-hidden="true"
58
66
  className={cn(
59
- props.kind === "event"
60
- ? "mt-1 grid h-5 w-8 place-items-center"
61
- : "mt-1.5 grid size-5 place-items-center",
67
+ nested
68
+ ? "grid h-6 w-4 place-items-center"
69
+ : "grid h-6 w-8 place-items-center",
70
+ // Card rows: center the icon on the card's first line.
71
+ !nested &&
72
+ (props.kind === "subagent" || props.kind === "message_context") &&
73
+ "mt-1.5",
62
74
  marker.className,
63
75
  )}
64
76
  >
65
77
  {props.objectType ? (
66
78
  <ObjectIcon objectType={props.objectType} size={14} decorative />
67
79
  ) : (
68
- <Icon size={11} strokeWidth={2.2} />
80
+ <Icon size={nested ? 14 : 15} strokeWidth={2} />
69
81
  )}
70
82
  </span>
71
83
  <div className="min-w-0">{props.children}</div>
@@ -9,6 +9,7 @@ import {
9
9
  } from "./TranscriptHeadingRow";
10
10
  import { HighlightText, useTranscriptSearch } from "./transcriptSearch";
11
11
  import { TranscriptSummary } from "./TranscriptSummary";
12
+ import { transcriptActivityBodyClass } from "./ToolFrame";
12
13
 
13
14
  /** Render reasoning collapsed with a short preview until expanded or searched. */
14
15
  export function TranscriptReasoningView(props: {
@@ -22,23 +23,23 @@ export function TranscriptReasoningView(props: {
22
23
  <>
23
24
  <span
24
25
  aria-label="Reasoning"
25
- className="mt-0.5 inline-flex size-4 shrink-0 items-center justify-center text-violet-200/80"
26
+ className="inline-flex size-4 shrink-0 items-center justify-center text-violet-200/80"
26
27
  title="Reasoning"
27
28
  >
28
- <Lightbulb aria-hidden="true" size={13} strokeWidth={1.8} />
29
+ <Lightbulb aria-hidden="true" size={14} strokeWidth={2} />
29
30
  </span>
30
31
  <TranscriptHeadingRow
31
32
  left={
32
33
  searchActive ? (
33
- <span className="block min-w-0 italic text-violet-100/75">
34
+ <span className="block min-w-0 text-dashboard-text-muted">
34
35
  Reasoning
35
36
  </span>
36
37
  ) : (
37
38
  <>
38
- <span className="block min-w-0 truncate italic text-violet-100/70 group-open/reasoning:hidden">
39
+ <span className="block min-w-0 truncate italic text-dashboard-text-muted group-open/reasoning:hidden">
39
40
  {rendered}
40
41
  </span>
41
- <span className="hidden min-w-0 italic text-violet-100/75 group-open/reasoning:inline">
42
+ <span className="hidden min-w-0 text-dashboard-text-muted group-open/reasoning:inline">
42
43
  Reasoning
43
44
  </span>
44
45
  </>
@@ -46,7 +47,7 @@ export function TranscriptReasoningView(props: {
46
47
  }
47
48
  right={
48
49
  timestamp ? (
49
- <TranscriptHeadingMeta className="min-w-0 break-words text-2xs not-italic text-dashboard-text-muted max-md:hidden">
50
+ <TranscriptHeadingMeta className="min-w-0 break-words text-xs tabular-nums text-dashboard-text-muted max-md:hidden">
50
51
  {timestamp}
51
52
  </TranscriptHeadingMeta>
52
53
  ) : undefined
@@ -56,9 +57,8 @@ export function TranscriptReasoningView(props: {
56
57
  </>
57
58
  );
58
59
  const content = (
59
- <div className="grid min-w-0 grid-cols-[1rem_minmax(0,1fr)] gap-2 bg-violet-950/15 px-2 pb-2 pt-1.5">
60
- <span aria-hidden="true" />
61
- <div className="min-w-0 whitespace-pre-wrap break-words text-xs leading-relaxed italic text-violet-100/65">
60
+ <div className={cn(transcriptActivityBodyClass, "px-3 py-2.5")}>
61
+ <div className="min-w-0 whitespace-pre-wrap break-words text-sm leading-relaxed text-dashboard-text-subtle">
62
62
  <HighlightText text={rendered} />
63
63
  </div>
64
64
  </div>
@@ -67,7 +67,7 @@ export function TranscriptReasoningView(props: {
67
67
  if (searchActive) {
68
68
  return (
69
69
  <div className={reasoningFrameClass()}>
70
- <div className="grid list-none grid-cols-[1rem_minmax(0,1fr)] items-start gap-2 px-2 py-1.5">
70
+ <div className="grid list-none grid-cols-[1rem_minmax(0,1fr)] items-center gap-2 py-1.5">
71
71
  {summary}
72
72
  </div>
73
73
  {content}
@@ -77,7 +77,7 @@ export function TranscriptReasoningView(props: {
77
77
 
78
78
  return (
79
79
  <details className={cn("group/reasoning", reasoningFrameClass())}>
80
- <TranscriptSummary className="grid grid-cols-[1rem_minmax(0,1fr)] items-start gap-2">
80
+ <TranscriptSummary className="-mx-2 grid grid-cols-[1rem_minmax(0,1fr)] items-center gap-2">
81
81
  {summary}
82
82
  </TranscriptSummary>
83
83
  {content}
@@ -86,5 +86,5 @@ export function TranscriptReasoningView(props: {
86
86
  }
87
87
 
88
88
  function reasoningFrameClass(): string {
89
- return "min-w-0 overflow-hidden rounded-md bg-violet-300/[0.075] text-sm leading-relaxed text-violet-100/70";
89
+ return "min-w-0 text-xs leading-tight";
90
90
  }
@@ -14,24 +14,29 @@ export function TranscriptStructuredEventView(props: {
14
14
  const presentation = props.part.presentation;
15
15
  const timestamp = formatMessageTimestamp(props.timestamp);
16
16
  const details = presentation.details ?? [];
17
- // Match TranscriptRailEvent icon offset (mt-1.5) so single-line titles share a baseline.
18
- const surfaceClass =
19
- "min-w-0 px-0.5 pb-1 pt-1.5 font-mono text-xs leading-tight";
17
+ // The rail icon box is h-6. A leading-6 title shares its center line.
18
+ const surfaceClass = "min-w-0 font-sans text-xs";
19
+ // A lone detail often repeats the event title, such as "Loaded AGENTS.md"
20
+ // over "AGENTS.md". Skip that repeated title.
21
+ const repeatsTitle = (title: string) =>
22
+ details.length === 1 && presentation.title.includes(title);
20
23
  const body =
21
24
  details.length > 0 ? (
22
- <div className="grid gap-4 pb-1 pt-3">
25
+ <div className="grid gap-4 pb-1 pt-2">
23
26
  {details.map((detail, index) => (
24
27
  <section key={`${detail.title}:${index}`}>
25
- <div className="whitespace-pre-wrap break-words font-sans text-sm font-medium text-dashboard-text">
26
- <HighlightText text={detail.title} />
27
- </div>
28
+ {repeatsTitle(detail.title) ? null : (
29
+ <div className="whitespace-pre-wrap break-words font-sans text-sm font-medium text-dashboard-text">
30
+ <HighlightText text={detail.title} />
31
+ </div>
32
+ )}
28
33
  {detail.description ? (
29
34
  <div className="mt-1 whitespace-pre-wrap break-words text-xs leading-relaxed text-dashboard-text-muted">
30
35
  <HighlightText text={detail.description} />
31
36
  </div>
32
37
  ) : null}
33
38
  {detail.content ? (
34
- <pre className="mt-2 max-h-[32rem] overflow-auto whitespace-pre-wrap break-words font-mono text-xs leading-relaxed text-dashboard-text-muted">
39
+ <pre className="mt-2 max-h-[32rem] overflow-auto whitespace-pre-wrap break-words rounded-md border border-dashboard-border-subtle bg-dashboard-surface-panel px-3 py-2.5 font-mono text-xs leading-normal text-dashboard-text-muted first:mt-0">
35
40
  <HighlightText text={detail.content} />
36
41
  </pre>
37
42
  ) : null}
@@ -39,7 +44,7 @@ export function TranscriptStructuredEventView(props: {
39
44
  <div className="mt-2 flex flex-wrap gap-1.5">
40
45
  {detail.metadata.map((value) => (
41
46
  <span
42
- className="font-mono text-xs text-dashboard-text-muted"
47
+ className="text-xs text-dashboard-text-muted"
43
48
  key={value}
44
49
  >
45
50
  <HighlightText text={value} />
@@ -54,7 +59,7 @@ export function TranscriptStructuredEventView(props: {
54
59
  const header = (
55
60
  <div className="grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-start gap-2 max-md:grid-cols-[minmax(0,1fr)]">
56
61
  <div className="min-w-0">
57
- <div className="font-display text-sm font-semibold text-dashboard-text">
62
+ <div className="text-sm font-medium leading-6 text-dashboard-text">
58
63
  <HighlightText text={presentation.title} />
59
64
  </div>
60
65
  {presentation.preview ? (
@@ -64,7 +69,7 @@ export function TranscriptStructuredEventView(props: {
64
69
  ) : null}
65
70
  </div>
66
71
  {timestamp ? (
67
- <span className="font-mono text-xs text-dashboard-text-muted max-md:hidden">
72
+ <span className="text-xs leading-6 tabular-nums text-dashboard-text-muted max-md:hidden">
68
73
  {timestamp}
69
74
  </span>
70
75
  ) : null}
@@ -1,65 +1,90 @@
1
- import { formatMessageTimestamp } from "../format";
1
+ import { ChevronRight } from "lucide-react";
2
+
3
+ import { StatusChip } from "../components/StatusChip";
4
+ import { formatElapsedDuration, formatMessageTimestamp } from "../format";
5
+ import { cn } from "../styles";
2
6
  import type { TranscriptViewSubagentPart } from "../types";
3
- import { ToolFrame } from "./ToolFrame";
7
+ import { subagentStatusTone } from "./ConversationChildAgents";
4
8
  import { HighlightText } from "./transcriptSearch";
5
9
 
6
- /** Render a child-agent lifecycle event inside the transcript stream. */
10
+ /**
11
+ * Render a child agent as a compact card in the transcript stream. The card
12
+ * matches the child agent rows in the details drawer: name, status, and run
13
+ * time first, then the task title. It opens the child transcript.
14
+ */
7
15
  export function TranscriptSubagentView(props: {
8
16
  onOpenTranscript?: (part: TranscriptViewSubagentPart) => void;
9
17
  part: TranscriptViewSubagentPart;
10
18
  timestamp?: number;
11
19
  }) {
12
20
  const label = props.part.agentName ?? props.part.subagentKind;
13
- const summary = props.part.title ?? props.part.task;
21
+ const headline = props.part.title ?? props.part.task;
14
22
  const status = props.part.status;
15
- const meta = [
16
- status,
23
+ const duration = formatElapsedDuration(
24
+ props.timestamp,
25
+ props.part.finishedTimestamp,
26
+ );
27
+ const started =
17
28
  typeof props.timestamp === "number"
18
- ? formatMessageTimestamp(props.timestamp)
19
- : undefined,
20
- ].filter(isString);
29
+ ? `Started ${formatMessageTimestamp(props.timestamp)}`
30
+ : undefined;
31
+ const interactive = Boolean(props.onOpenTranscript);
21
32
 
22
- const frame = (
23
- <ToolFrame
24
- meta={meta}
25
- mobileSummaryMeta={status}
26
- signature={
27
- <>
28
- <strong className="min-w-0 break-words font-bold text-cyan-100">
29
- <HighlightText text={label} />
30
- </strong>
31
- {summary ? (
32
- <span className="min-w-0 truncate text-dashboard-text-muted">
33
- <HighlightText text={summary} />
34
- </span>
35
- ) : null}
36
- {props.part.status === "running" ? (
37
- <span
38
- aria-hidden="true"
39
- className="mt-px size-1.5 shrink-0 animate-pulse rounded-full bg-cyan-300"
40
- />
41
- ) : null}
42
- </>
43
- }
44
- />
33
+ const content = (
34
+ <>
35
+ <span className="flex min-w-0 items-center gap-2 text-xs leading-5">
36
+ <span className="min-w-0 truncate font-sans text-sm font-semibold text-dashboard-text">
37
+ <HighlightText text={label} />
38
+ </span>
39
+ <StatusChip
40
+ className={cn("shrink-0", status === "running" && "animate-pulse")}
41
+ size="compact"
42
+ tone={subagentStatusTone(status)}
43
+ >
44
+ {status}
45
+ </StatusChip>
46
+ {duration ? (
47
+ <span className="shrink-0 tabular-nums text-dashboard-text-muted">
48
+ {duration}
49
+ </span>
50
+ ) : null}
51
+ {interactive ? (
52
+ <ChevronRight
53
+ aria-hidden="true"
54
+ className="ml-auto size-4 shrink-0 text-dashboard-text-faint transition-colors group-hover/subagent:text-dashboard-text"
55
+ />
56
+ ) : null}
57
+ </span>
58
+ {headline ? (
59
+ <span className="block min-w-0 truncate text-sm leading-snug text-dashboard-text-subtle">
60
+ <HighlightText text={headline} />
61
+ </span>
62
+ ) : null}
63
+ </>
45
64
  );
65
+ const frameClass =
66
+ "group/subagent grid w-full min-w-0 max-w-[37.5rem] gap-0.5 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface-panel px-3 py-2 text-left text-inherit";
46
67
 
47
68
  if (!props.onOpenTranscript) {
48
- return frame;
69
+ return (
70
+ <div className={frameClass} title={started}>
71
+ {content}
72
+ </div>
73
+ );
49
74
  }
50
75
 
51
76
  return (
52
77
  <button
53
78
  aria-label={`Open ${label} transcript`}
54
- className="block w-full min-w-0 cursor-pointer text-left transition-colors hover:bg-white/[0.035] focus-visible:outline focus-visible:outline-1 focus-visible:outline-cyan-300/55"
79
+ className={cn(
80
+ frameClass,
81
+ "cursor-pointer transition-colors hover:border-dashboard-border-emphasis hover:bg-dashboard-surface-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dashboard-focus",
82
+ )}
55
83
  onClick={() => props.onOpenTranscript?.(props.part)}
84
+ title={started}
56
85
  type="button"
57
86
  >
58
- {frame}
87
+ {content}
59
88
  </button>
60
89
  );
61
90
  }
62
-
63
- function isString(value: string | undefined): value is string {
64
- return typeof value === "string" && value.length > 0;
65
- }