@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
@@ -16,7 +16,7 @@ export function SectionIntro(props: {
16
16
  {props.title}
17
17
  </h2>
18
18
  {props.description ? (
19
- <p className="mt-1.5 mb-0 max-w-2xl font-mono text-xs leading-relaxed text-dashboard-text-muted">
19
+ <p className="mt-1.5 mb-0 max-w-2xl font-sans text-xs leading-relaxed text-dashboard-text-muted">
20
20
  {props.description}
21
21
  </p>
22
22
  ) : null}
@@ -3,7 +3,7 @@ import type { ReactNode } from "react";
3
3
  /** Render compact section titles that fit inside operational panels. */
4
4
  export function SectionTitle(props: { children: ReactNode }) {
5
5
  return (
6
- <div className="min-w-0 break-words font-mono text-xs font-medium uppercase leading-tight tracking-[0.14em] text-dashboard-text-muted">
6
+ <div className="min-w-0 break-words font-sans text-xs font-medium leading-tight text-dashboard-text-muted">
7
7
  {props.children}
8
8
  </div>
9
9
  );
@@ -14,7 +14,7 @@ export function StatCard(props: {
14
14
  return (
15
15
  <Card className="relative p-4 sm:p-5">
16
16
  <div className="flex items-start justify-between gap-4">
17
- <div className="font-mono text-xs font-medium uppercase tracking-[0.14em] text-dashboard-text-muted">
17
+ <div className="font-sans text-xs font-medium text-dashboard-text-muted">
18
18
  {props.label}
19
19
  </div>
20
20
  <Icon aria-hidden="true" className="text-amber-400/55" size={15} />
@@ -23,7 +23,7 @@ export function StatCard(props: {
23
23
  {props.value}
24
24
  </div>
25
25
  {props.detail ? (
26
- <div className="mt-2 font-mono text-xs leading-relaxed text-dashboard-text-muted">
26
+ <div className="mt-2 font-sans text-xs leading-relaxed text-dashboard-text-muted">
27
27
  {props.detail}
28
28
  </div>
29
29
  ) : null}
@@ -102,7 +102,7 @@ function ArchivedNotice(props: {
102
102
  >
103
103
  {restore.error ? (
104
104
  <div
105
- className="border-t border-rose-300/25 bg-rose-400/[0.12] px-3 py-2 font-mono text-xs text-rose-50/85"
105
+ className="border-t border-rose-300/25 bg-rose-400/[0.12] px-3 py-2 font-sans text-xs text-rose-50/85"
106
106
  role="alert"
107
107
  >
108
108
  Could not restore the conversation.
@@ -49,7 +49,7 @@ export function ConversationBrief(props: {
49
49
  <StatusChip tone={outcomeTones[content.outcome.status]}>
50
50
  {displayLabel(content.outcome.status)}
51
51
  </StatusChip>
52
- <span className="font-mono text-xs text-dashboard-text-muted">
52
+ <span className="text-xs text-dashboard-text-muted">
53
53
  Brief v{props.brief.version} · updated{" "}
54
54
  {formatTime(props.brief.updatedAt, {
55
55
  dateStyle: "medium",
@@ -101,14 +101,14 @@ export function ConversationBrief(props: {
101
101
  key={`${link.kind}:${link.url}`}
102
102
  >
103
103
  <a
104
- className="min-w-0 break-all font-medium text-cyan-200 underline decoration-cyan-300/30 underline-offset-2 hover:text-cyan-100"
104
+ className="min-w-0 break-all font-medium text-dashboard-accent-text underline decoration-dashboard-accent/30 underline-offset-2 hover:text-dashboard-accent-text"
105
105
  href={link.url}
106
106
  rel="noopener noreferrer"
107
107
  target="_blank"
108
108
  >
109
109
  {link.label}
110
110
  </a>
111
- <span className="font-mono text-xs text-dashboard-text-muted">
111
+ <span className="text-xs text-dashboard-text-muted">
112
112
  {displayLabel(link.kind)}
113
113
  </span>
114
114
  {link.status ? (
@@ -172,7 +172,7 @@ function BriefDecisions(props: { decisions: BriefContent["decisions"] }) {
172
172
  <span className="min-w-0">{decision.text}</span>
173
173
  </div>
174
174
  {decision.by ? (
175
- <div className="mt-0.5 font-mono text-xs text-dashboard-text-muted">
175
+ <div className="mt-0.5 text-xs text-dashboard-text-muted">
176
176
  by · {decision.by}
177
177
  </div>
178
178
  ) : null}
@@ -110,21 +110,30 @@ function ChildAgentRow(props: {
110
110
  // A title is often a short form of the task. Skip the task when it adds nothing.
111
111
  const detail = agent.title && task && task !== agent.title ? task : undefined;
112
112
  const runtime = formatRuntime(agent.cumulativeDurationMs);
113
+ // Totals include this subagent's own subagents. Say so, so the row does not
114
+ // read as the work of this subagent alone.
115
+ const nested = agent.childAgentTotals?.count;
113
116
  const stats = [
114
117
  formatUsageTotal(agent.cumulativeUsage),
115
118
  formatConversationCostTotal(agent.cumulativeUsage, agent.auxiliaryCosts),
116
119
  runtime ? `${runtime} runtime` : "",
120
+ nested
121
+ ? `incl. ${nested} nested ${nested === 1 ? "subagent" : "subagents"}`
122
+ : "",
117
123
  ].filter(Boolean);
118
124
 
119
125
  return (
120
126
  <button
121
- className="grid w-full min-w-0 cursor-pointer gap-1.5 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface px-3 py-2.5 text-left text-inherit transition-colors duration-200 hover:border-dashboard-border-emphasis hover:bg-dashboard-surface-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dashboard-focus"
127
+ className="grid w-full min-w-0 cursor-pointer gap-1.5 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface-panel px-3 py-2.5 text-left text-inherit transition-colors duration-200 hover:border-dashboard-border-emphasis hover:bg-dashboard-surface-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dashboard-focus"
122
128
  onClick={() =>
123
129
  props.onOpen({
124
130
  conversationId: agent.conversationId,
125
131
  ...(agent.redacted
126
132
  ? undefined
127
- : { label: agent.title ?? agent.name }),
133
+ : {
134
+ ...(agent.name ? { agentName: agent.name } : undefined),
135
+ label: agent.title ?? agent.name,
136
+ }),
128
137
  status: agent.status,
129
138
  })
130
139
  }
@@ -137,7 +146,7 @@ function ChildAgentRow(props: {
137
146
  Private subagent
138
147
  </span>
139
148
  ) : (
140
- <span className="min-w-0 truncate font-mono text-dashboard-text-muted">
149
+ <span className="min-w-0 truncate font-medium text-dashboard-text-muted">
141
150
  {agent.name ?? "subagent"}
142
151
  </span>
143
152
  )}
@@ -179,7 +188,7 @@ function ChildAgentRow(props: {
179
188
  </span>
180
189
  ) : null}
181
190
  {stats.length > 0 ? (
182
- <span className="block font-mono text-xs tabular-nums text-dashboard-text-muted">
191
+ <span className="block text-xs tabular-nums text-dashboard-text-muted">
183
192
  {stats.join(" · ")}
184
193
  </span>
185
194
  ) : null}
@@ -351,7 +351,7 @@ export const ConversationComposer = memo(function ConversationComposer(
351
351
  {props.error || imageError ? (
352
352
  <div
353
353
  aria-live="polite"
354
- className="min-w-0 font-mono text-xs leading-relaxed text-red-300/80"
354
+ className="min-w-0 font-sans text-xs leading-relaxed text-rose-300"
355
355
  >
356
356
  {imageError ?? props.error}
357
357
  </div>
@@ -361,10 +361,10 @@ export const ConversationComposer = memo(function ConversationComposer(
361
361
  // Accessory chrome needs a full footer row. Side-by-side mobile is
362
362
  // only for the bare reply dock.
363
363
  props.footerStart
364
- ? "block overflow-hidden focus-within:border-cyan-300/35 focus-within:ring-1 focus-within:ring-cyan-300/35"
365
- : "grid grid-cols-[minmax(0,1fr)_auto] items-end overflow-hidden focus-within:border-cyan-300/35 focus-within:ring-1 focus-within:ring-cyan-300/35 md:block",
364
+ ? "block overflow-hidden focus-within:border-dashboard-focus/45 focus-within:ring-1 focus-within:ring-dashboard-focus/25"
365
+ : "grid grid-cols-[minmax(0,1fr)_auto] items-end overflow-hidden focus-within:border-dashboard-accent/35 focus-within:ring-1 focus-within:ring-dashboard-focus/35 md:block",
366
366
  dashboardComposerSurfaceClass,
367
- dragging && "ring-2 ring-cyan-300",
367
+ dragging && "ring-2 ring-dashboard-focus",
368
368
  )}
369
369
  onDragOver={(event) => {
370
370
  if (!event.dataTransfer.types.includes("Files")) return;
@@ -431,7 +431,7 @@ export const ConversationComposer = memo(function ConversationComposer(
431
431
  autoCapitalize="off"
432
432
  autoComplete="off"
433
433
  autoCorrect="off"
434
- className="min-h-12 max-h-28 w-full resize-none overflow-y-auto border-0 bg-transparent px-3 py-3 font-sans text-base leading-relaxed text-dashboard-text outline-none placeholder:text-dashboard-text-muted/65 md:min-h-14 md:max-h-none md:resize-y md:overflow-visible md:px-4 md:py-3"
434
+ className="min-h-12 max-h-28 w-full resize-none overflow-y-auto border-0 bg-transparent px-3 py-3 font-sans text-base leading-relaxed text-dashboard-text outline-none placeholder:text-dashboard-text-muted/65 md:min-h-14 md:max-h-none md:overflow-visible md:px-4 md:py-3"
435
435
  disabled={Boolean(props.restoreDraftOnError && createPending)}
436
436
  enterKeyHint="send"
437
437
  id={id}
@@ -462,6 +462,7 @@ export const ConversationComposer = memo(function ConversationComposer(
462
462
  aria-label="Attach images"
463
463
  disabled={sendLocked || readingImages}
464
464
  onClick={() => fileInputRef.current?.click()}
465
+ size="icon"
465
466
  title="Attach images (up to 3 MB total)"
466
467
  type="button"
467
468
  >
@@ -478,7 +479,6 @@ export const ConversationComposer = memo(function ConversationComposer(
478
479
  {props.onStop ? (
479
480
  <Button
480
481
  aria-label={props.stopPending ? "Stopping" : "Stop"}
481
- className="rounded-lg font-sans"
482
482
  disabled={props.stopPending || !online}
483
483
  onClick={() => void stopTurn()}
484
484
  title="Stop Junior"
@@ -491,7 +491,7 @@ export const ConversationComposer = memo(function ConversationComposer(
491
491
  ) : null}
492
492
  <Button
493
493
  aria-label={sendLocked ? "Sending message" : props.submitLabel}
494
- className="rounded-lg !border-0 !bg-cyan-100 font-sans !text-dashboard-text-inverse hover:!bg-cyan-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-cyan-300/55 disabled:!bg-dashboard-fill-hover disabled:!text-dashboard-text-muted"
494
+ className="disabled:!border-transparent disabled:!bg-dashboard-fill-hover disabled:!text-dashboard-text-muted disabled:!opacity-100"
495
495
  disabled={
496
496
  (!canSend && !images.length) ||
497
497
  !online ||
@@ -499,6 +499,7 @@ export const ConversationComposer = memo(function ConversationComposer(
499
499
  readingImages
500
500
  }
501
501
  title={sendLocked ? "Sending message" : props.submitLabel}
502
+ tone="primary"
502
503
  type="submit"
503
504
  >
504
505
  <Send aria-hidden="true" size={14} />
@@ -114,7 +114,7 @@ export function ConversationHeader(props: {
114
114
  <Ellipsis aria-hidden="true" size={18} strokeWidth={2} />
115
115
  </HeaderIconButton>
116
116
  {mobileMenuOpen ? (
117
- <div className="absolute right-0 top-[calc(100%+0.35rem)] z-40 w-56 rounded-xl border border-white/[0.08] bg-dashboard-surface-raised/95 p-1.5 shadow-2xl shadow-black/75 backdrop-blur-xl">
117
+ <div className="absolute right-0 top-[calc(100%+0.35rem)] z-40 w-56 rounded-xl border border-dashboard-border-strong bg-dashboard-surface-raised/95 p-1.5 shadow-2xl shadow-black/75 backdrop-blur-xl">
118
118
  <ConversationHeaderActions
119
119
  archive={props.archive}
120
120
  conversationId={props.conversationId}
@@ -138,7 +138,7 @@ export function ConversationHeader(props: {
138
138
  />
139
139
  {openMobileNavigation ? (
140
140
  <button
141
- className="mt-0.5 flex w-full cursor-pointer items-center gap-2.5 rounded-md border-0 bg-transparent px-2.5 py-2 text-left text-sm font-semibold text-dashboard-text transition-colors hover:bg-white/10 focus-visible:bg-white/10 focus-visible:outline-none"
141
+ className="mt-0.5 flex w-full cursor-pointer items-center gap-2.5 rounded-md border-0 bg-transparent px-2.5 py-2 text-left text-sm font-semibold text-dashboard-text transition-colors hover:bg-dashboard-fill-hover focus-visible:bg-dashboard-fill-hover focus-visible:outline-none"
142
142
  onClick={() => {
143
143
  setMobileMenuOpen(false);
144
144
  openMobileNavigation();
@@ -175,7 +175,7 @@ export function ConversationHeader(props: {
175
175
  <MobileHeaderLivePortal>{liveIndicator}</MobileHeaderLivePortal>
176
176
  <header
177
177
  className={cn(
178
- "sticky top-0 z-10 -mx-4 border-b border-dashboard-border-emphasis bg-dashboard-bg md:-mx-7",
178
+ "sticky top-0 z-10 -mx-4 border-b border-dashboard-border bg-dashboard-bg md:-mx-7",
179
179
  !showMobileHeader && "hidden md:block",
180
180
  )}
181
181
  >
@@ -221,12 +221,12 @@ export function ConversationHeader(props: {
221
221
  {props.linkedWork ? (
222
222
  <div
223
223
  aria-label="Linked work"
224
- className="border-t border-dashboard-border bg-dashboard-surface-panel px-4 py-3 md:px-7"
224
+ className="border-t border-dashboard-border-subtle px-4 py-2.5 md:px-7"
225
225
  >
226
226
  {props.linkedWork}
227
227
  </div>
228
228
  ) : null}
229
- <div className="hidden border-t border-dashboard-border px-7 md:flex">
229
+ <div className="hidden border-t border-dashboard-border-subtle px-7 md:flex">
230
230
  <TranscriptViewToggle
231
231
  labelled
232
232
  onChange={props.onViewChange}
@@ -96,7 +96,7 @@ export function ConversationHeaderActions(props: {
96
96
  <Button
97
97
  aria-label="Conversation details"
98
98
  aria-pressed={props.detailsOpen}
99
- className="ml-2 rounded-lg font-sans text-xs"
99
+ className="ml-2"
100
100
  onClick={props.onDetailsClick}
101
101
  >
102
102
  <PanelRight aria-hidden="true" size={15} strokeWidth={2} />
@@ -153,13 +153,13 @@ function ViewModeButton(props: {
153
153
  <ToggleButton
154
154
  aria-label={props.label}
155
155
  className={cn(
156
- "!normal-case !no-underline font-sans",
156
+ "!no-underline",
157
157
  props.labelled
158
- ? "relative flex h-11 items-center gap-2 px-0 py-0 text-xs after:absolute after:inset-x-0 after:bottom-0 after:h-0.5"
159
- : "grid size-7 place-items-center rounded px-0 py-0",
158
+ ? "relative flex h-11 items-center gap-2 rounded-none px-0 py-0 text-sm after:absolute after:inset-x-0 after:bottom-0 after:h-0.5"
159
+ : "grid size-7 place-items-center rounded-md px-0 py-0",
160
160
  props.active &&
161
161
  (props.labelled
162
- ? "text-cyan-100 after:bg-cyan-100"
162
+ ? "text-dashboard-text after:bg-dashboard-text-solid"
163
163
  : "bg-dashboard-fill-hover text-dashboard-text"),
164
164
  )}
165
165
  onClick={props.onClick}
@@ -191,7 +191,7 @@ export function HeaderIconButton(props: {
191
191
  aria-pressed={props.pressed}
192
192
  className={cn(
193
193
  "text-dashboard-text-muted",
194
- props.pressed && "bg-white/10 text-dashboard-text",
194
+ props.pressed && "bg-dashboard-fill-strong text-dashboard-text",
195
195
  )}
196
196
  onClick={props.onClick}
197
197
  size="icon"
@@ -233,8 +233,8 @@ function MenuActionButton(props: {
233
233
  aria-label={props.label}
234
234
  aria-pressed={props.pressed}
235
235
  className={cn(
236
- "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-white/10 focus-visible:bg-white/10 focus-visible:outline-none",
237
- props.pressed && "bg-white/[0.08]",
236
+ "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-strong focus-visible:bg-dashboard-fill-strong focus-visible:outline-none",
237
+ props.pressed && "bg-dashboard-fill-strong",
238
238
  )}
239
239
  onClick={props.onClick}
240
240
  type="button"
@@ -258,7 +258,7 @@ function ArchiveConversationButton(
258
258
  return (
259
259
  <button
260
260
  aria-label={label}
261
- 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-white/10 focus-visible:bg-white/10 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
261
+ 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-strong focus-visible:bg-dashboard-fill-strong focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
262
262
  disabled={props.disabled}
263
263
  onClick={props.onClick}
264
264
  type="button"
@@ -172,7 +172,7 @@ function ConversationCardSection(props: {
172
172
  return (
173
173
  <section aria-labelledby={`conversation-section-${props.section.key}`}>
174
174
  <h3
175
- className="mb-2 mt-0 px-1 font-display text-xs font-semibold uppercase tracking-[0.08em] text-dashboard-text-muted"
175
+ className="mb-2 mt-0 px-1 font-sans text-xs font-medium text-dashboard-text-muted"
176
176
  id={`conversation-section-${props.section.key}`}
177
177
  >
178
178
  {props.section.label}
@@ -238,7 +238,7 @@ function ConversationCard(props: {
238
238
  </div>
239
239
  <div className="flex items-start gap-3">
240
240
  <Link
241
- className="pointer-events-auto whitespace-nowrap font-mono text-xs text-dashboard-text-muted hover:text-dashboard-text focus:outline-none focus:ring-2 focus:ring-dashboard-focus"
241
+ className="pointer-events-auto whitespace-nowrap font-sans text-xs tabular-nums text-dashboard-text-muted hover:text-dashboard-text focus:outline-none focus:ring-2 focus:ring-dashboard-focus"
242
242
  to={conversationPath(conversation.id)}
243
243
  >
244
244
  {formatRelativeTime(conversation.lastSeenAt)}
@@ -272,7 +272,7 @@ function ConversationCard(props: {
272
272
  {status === "active" ? "Working…" : "No recent message"}
273
273
  </p>
274
274
  )}
275
- <div className="relative z-[1] flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-1 font-mono text-xs text-dashboard-text-muted pointer-events-none">
275
+ <div className="relative z-[1] flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-1 font-sans text-xs text-dashboard-text-muted pointer-events-none">
276
276
  {location ? <span className="truncate">{location}</span> : null}
277
277
  {location && participants.length > 0 ? (
278
278
  <span aria-hidden="true">·</span>
@@ -45,7 +45,7 @@ export function ConversationListStatusIcon(props: {
45
45
  className={cn(
46
46
  "size-1.5 shrink-0 rounded-full",
47
47
  props.status === "failed" && "bg-rose-300",
48
- props.status === "idle" && "bg-white/25",
48
+ props.status === "idle" && "bg-dashboard-text-faint",
49
49
  )}
50
50
  />
51
51
  );
@@ -66,17 +66,17 @@ export function ConversationMemories(props: { conversationId: string }) {
66
66
  <ol className="m-0 grid list-none gap-3 p-0">
67
67
  {query.data.memories.map((memory) => (
68
68
  <li
69
- className="grid gap-2 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface px-3 py-3"
69
+ className="grid gap-2 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface-panel px-3 py-3"
70
70
  key={memory.id}
71
71
  >
72
72
  <p className="m-0 whitespace-pre-wrap break-words text-sm leading-relaxed text-dashboard-text">
73
73
  {memory.content}
74
74
  </p>
75
- <div className="flex flex-wrap items-center gap-1.5 font-mono text-xs text-dashboard-text-muted">
76
- <span className="rounded border border-dashboard-border-subtle bg-dashboard-surface-raised px-1.5 py-0.5">
75
+ <div className="flex flex-wrap items-center gap-1.5 text-xs tabular-nums text-dashboard-text-muted">
76
+ <span className="rounded-md border border-dashboard-border-subtle bg-dashboard-surface-raised px-1.5 py-0.5">
77
77
  {memory.kind}
78
78
  </span>
79
- <span className="rounded border border-dashboard-border-subtle bg-dashboard-surface-raised px-1.5 py-0.5">
79
+ <span className="rounded-md border border-dashboard-border-subtle bg-dashboard-surface-raised px-1.5 py-0.5">
80
80
  {memory.visibility}
81
81
  </span>
82
82
  <time className="ml-auto" dateTime={memory.capturedAt}>
@@ -24,8 +24,13 @@ import {
24
24
  } from "../format";
25
25
  import { Tooltip } from "../components/Tooltip";
26
26
  import { MetricList, type MetricListItem } from "../components/Metric";
27
- import { cn } from "../styles";
28
- import { CostMetric, DurationMetric, TokenMetric } from "./TelemetryMetrics";
27
+ import { cn, dashboardInlineLinkClass } from "../styles";
28
+ import {
29
+ CostMetric,
30
+ DurationMetric,
31
+ SubagentsMetric,
32
+ TokenMetric,
33
+ } from "./TelemetryMetrics";
29
34
  import type { Conversation } from "../types";
30
35
  import {
31
36
  projectSidebarAnnotationBadges,
@@ -44,9 +49,9 @@ export function PendingAuthorization(props: {
44
49
  };
45
50
  }) {
46
51
  return (
47
- <div className="mb-2 rounded-lg border border-cyan-300/20 bg-cyan-300/[0.07] px-3 py-2.5 font-sans text-sm text-dashboard-text">
52
+ <div className="mb-2 rounded-lg border border-dashboard-accent/20 bg-dashboard-accent/[0.07] px-3 py-2.5 font-sans text-sm text-dashboard-text">
48
53
  <a
49
- className="font-medium text-cyan-200 underline decoration-cyan-300/40 underline-offset-2 hover:text-cyan-100"
54
+ className="font-medium text-dashboard-accent-text underline decoration-dashboard-accent/40 underline-offset-2 hover:text-dashboard-accent-text"
50
55
  href={props.authorization.authorizationUrl}
51
56
  rel="noreferrer"
52
57
  target="_blank"
@@ -77,7 +82,7 @@ export function ConversationPrivacyChip(props: {
77
82
  className={
78
83
  isPublic
79
84
  ? "inline-flex max-w-full items-center gap-1 rounded-full border border-dashboard-border-emphasis bg-dashboard-fill-soft px-2 py-0.5 font-sans text-2xs font-medium text-dashboard-text-muted"
80
- : "inline-flex max-w-full items-center gap-1 rounded-full border border-white/[0.1] bg-white/[0.04] px-2 py-0.5 font-sans text-2xs font-medium text-dashboard-text-muted"
85
+ : "inline-flex max-w-full items-center gap-1 rounded-full border border-dashboard-border-strong bg-dashboard-fill-soft px-2 py-0.5 font-sans text-2xs font-medium text-dashboard-text-muted"
81
86
  }
82
87
  role="note"
83
88
  title={`${fullLabel}. ${detail}`}
@@ -171,7 +176,7 @@ export function ConversationSidebarAnnotations(props: {
171
176
  <SidebarAnnotationGroupChip group={group} key={group.label} />
172
177
  ))}
173
178
  {badges.overflowGroupCount > 0 ? (
174
- <span className="inline-flex h-5 min-w-7 shrink-0 items-center justify-center rounded-full border border-white/12 bg-dashboard-control px-1.5 font-mono text-2xs leading-none text-dashboard-text-muted">
179
+ <span className="inline-flex h-5 min-w-7 shrink-0 items-center justify-center rounded-full border border-dashboard-border-strong bg-dashboard-control px-1.5 font-sans text-2xs tabular-nums leading-none text-dashboard-text-muted">
175
180
  +{badges.overflowGroupCount}
176
181
  </span>
177
182
  ) : null}
@@ -220,7 +225,7 @@ function SidebarAnnotationGroupChip(props: {
220
225
  group: SidebarAnnotationBadgeGroup;
221
226
  }) {
222
227
  return (
223
- <span className="inline-flex h-5 min-w-0 max-w-60 shrink-0 items-center gap-1 truncate rounded-full border border-white/10 bg-dashboard-control px-1.5 font-sans text-2xs leading-none text-dashboard-text-muted">
228
+ <span className="inline-flex h-5 min-w-0 max-w-60 shrink-0 items-center gap-1 truncate rounded-full border border-dashboard-border-strong bg-dashboard-control px-1.5 font-sans text-2xs leading-none text-dashboard-text-muted">
224
229
  {props.group.annotations.map((annotation) =>
225
230
  annotation.objectType ? (
226
231
  <ObjectIcon
@@ -264,7 +269,7 @@ function SidebarAnnotationStatusChip(props: {
264
269
  // real chips, not floating glyphs. Box-shadow cutout (not border) keeps
265
270
  // layout at 18px while punching a surface-colored ring through the chip
266
271
  // underneath — classic avatar-facepile silhouette.
267
- "relative box-border inline-flex size-[18px] shrink-0 items-center justify-center rounded-full border border-white/12 bg-dashboard-control",
272
+ "relative box-border inline-flex size-[18px] shrink-0 items-center justify-center rounded-full border border-dashboard-border-strong bg-dashboard-control",
268
273
  props.stacked && "-ml-1",
269
274
  )}
270
275
  style={{
@@ -415,7 +420,7 @@ export function ConversationAnnotations(props: {
415
420
  className={cn(
416
421
  "inline-flex min-w-0 max-w-full shrink-0 items-center gap-2 rounded-md font-sans leading-snug text-dashboard-text no-underline transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus",
417
422
  props.layout === "strip"
418
- ? "px-1 py-0.5 text-xs hover:text-cyan-100"
423
+ ? "px-1 py-0.5 text-xs hover:text-dashboard-accent-text"
419
424
  : "min-h-10 border border-dashboard-border px-3 py-2 text-sm hover:border-dashboard-border-interactive hover:bg-dashboard-surface-hover",
420
425
  )}
421
426
  href={link.url ?? undefined}
@@ -466,7 +471,7 @@ function resourceLinkTitle(link: {
466
471
  function LocationLink(props: { label: string; locationUrl?: string }) {
467
472
  return props.locationUrl ? (
468
473
  <a
469
- className="text-dashboard-text underline decoration-white/20 underline-offset-2 transition-colors hover:decoration-white/60"
474
+ className={dashboardInlineLinkClass}
470
475
  href={props.locationUrl}
471
476
  rel="noreferrer"
472
477
  target="_blank"
@@ -489,7 +494,7 @@ function SourceTask(props: {
489
494
  const title = props.sourceTask.title?.trim();
490
495
  const link = automationId ? (
491
496
  <Link
492
- className="text-dashboard-text underline decoration-white/20 underline-offset-2 transition-colors hover:decoration-white/60"
497
+ className={dashboardInlineLinkClass}
493
498
  to={automationPath(automationId)}
494
499
  >
495
500
  Triggered by {kindLabel}
@@ -598,6 +603,8 @@ function conversationStatItems(props: {
598
603
  const live = (props.detail?.status ?? props.conversation.status) === "active";
599
604
  const activeModelId = liveModelId(props.detail);
600
605
  const sourceTask = props.detail?.sourceTask;
606
+ const childAgentTotals =
607
+ props.detail?.childAgentTotals ?? props.conversation.childAgentTotals;
601
608
  const rawStats: Array<MetricListItem | undefined> = [
602
609
  location
603
610
  ? {
@@ -615,6 +622,8 @@ function conversationStatItems(props: {
615
622
  ? {
616
623
  content: (
617
624
  <DurationMetric
625
+ childAgentTotals={childAgentTotals}
626
+ durationMs={props.conversation.cumulativeDurationMs}
618
627
  endedAt={props.conversation.lastSeenAt}
619
628
  label={durationLabel}
620
629
  startedAt={props.conversation.startedAt}
@@ -627,6 +636,8 @@ function conversationStatItems(props: {
627
636
  ? {
628
637
  content: (
629
638
  <TokenMetric
639
+ detailsHint={variant === "compact"}
640
+ childAgentTotals={childAgentTotals}
630
641
  compactionCount={
631
642
  completeDetail
632
643
  ? completeDetail.events.filter(
@@ -650,10 +661,12 @@ function conversationStatItems(props: {
650
661
  ? {
651
662
  content: (
652
663
  <CostMetric
664
+ detailsHint={variant === "compact"}
653
665
  auxiliaryCosts={
654
666
  props.detail?.auxiliaryCosts ??
655
667
  props.conversation.auxiliaryCosts
656
668
  }
669
+ childAgentTotals={childAgentTotals}
657
670
  live={live}
658
671
  liveModelId={activeModelId}
659
672
  modelUsage={props.detail?.modelUsage}
@@ -663,6 +676,12 @@ function conversationStatItems(props: {
663
676
  key: "cost",
664
677
  }
665
678
  : undefined,
679
+ childAgentTotals
680
+ ? {
681
+ content: <SubagentsMetric childAgentTotals={childAgentTotals} />,
682
+ key: "subagents",
683
+ }
684
+ : undefined,
666
685
  ];
667
686
  return rawStats.filter((item): item is MetricListItem => item !== undefined);
668
687
  }
@@ -37,6 +37,7 @@ import {
37
37
  hasConversationStats,
38
38
  PendingAuthorization,
39
39
  } from "./ConversationMeta";
40
+ import { ConversationUsageBreakdown } from "./ConversationUsageBreakdown";
40
41
  import { PendingMailboxStack } from "./PendingMailboxStack";
41
42
  import {
42
43
  buildConversations,
@@ -185,6 +186,7 @@ export function ConversationPage(props: {
185
186
  conversation: ConversationTranscript;
186
187
  }) => {
187
188
  setSubagentTarget({
189
+ ...(part.agentName ? { agentName: part.agentName } : undefined),
188
190
  conversationId: part.childConversationId,
189
191
  label: part.title ?? part.agentName,
190
192
  status: part.status,
@@ -300,10 +302,16 @@ export function ConversationPage(props: {
300
302
  conversation,
301
303
  detail: detail.data,
302
304
  }) ? (
303
- <ConversationStats
304
- conversation={conversation}
305
- detail={detail.data}
306
- />
305
+ <div className="grid min-w-0 gap-5">
306
+ <ConversationStats
307
+ conversation={conversation}
308
+ detail={detail.data}
309
+ />
310
+ <ConversationUsageBreakdown
311
+ conversation={conversation}
312
+ detail={detail.data}
313
+ />
314
+ </div>
307
315
  ) : null
308
316
  }
309
317
  title={title}
@@ -320,7 +328,7 @@ export function ConversationPage(props: {
320
328
  {detail.isPending ? (
321
329
  <TranscriptLoading />
322
330
  ) : detail.error && !detail.data ? (
323
- <Card className="border-white/[0.07] bg-white/[0.025] p-4 font-sans text-xs leading-relaxed text-dashboard-text-muted">
331
+ <Card className="border-dashboard-border bg-dashboard-fill-soft p-4 font-sans text-xs leading-relaxed text-dashboard-text-muted">
324
332
  {detail.error.message}
325
333
  </Card>
326
334
  ) : (
@@ -88,8 +88,8 @@ export function ConversationSidebar(props: {
88
88
  className={cn(
89
89
  "relative min-w-0",
90
90
  isLanding
91
- ? "border-t border-white/[0.07] bg-transparent"
92
- : "grid h-full min-h-0 grid-rows-[auto_auto_minmax(0,1fr)] overflow-hidden border-r border-dashboard-border-emphasis bg-dashboard-surface-panel",
91
+ ? "border-t border-dashboard-border bg-transparent"
92
+ : "grid h-full min-h-0 grid-rows-[auto_auto_minmax(0,1fr)] overflow-hidden border-r border-dashboard-border bg-dashboard-surface-panel",
93
93
  )}
94
94
  >
95
95
  <div className="px-5 py-3">
@@ -106,7 +106,7 @@ export function ConversationSidebar(props: {
106
106
  {isLanding ? null : (
107
107
  <button
108
108
  aria-label="New conversation"
109
- className="grid size-7 cursor-pointer place-items-center rounded-md text-dashboard-text-muted transition hover:bg-white/[0.05] hover:text-dashboard-text focus:outline-none focus:ring-2 focus:ring-cyan-300/35"
109
+ className="grid size-7 cursor-pointer place-items-center rounded-md text-dashboard-text-muted transition hover:bg-dashboard-fill-hover hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus"
110
110
  onClick={props.onNewConversation}
111
111
  title="New conversation"
112
112
  type="button"
@@ -293,7 +293,7 @@ const ConversationSidebarRow = memo(function ConversationSidebarRow(props: {
293
293
  <Link
294
294
  aria-current={props.selected ? "page" : undefined}
295
295
  className={cn(
296
- "block min-w-0 rounded-lg px-3 py-2 text-inherit no-underline transition-colors hover:bg-dashboard-fill-hover max-sm:pr-10",
296
+ "block min-w-0 rounded-lg px-3 py-2 text-inherit no-underline transition-colors hover:bg-dashboard-fill-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus max-sm:pr-10",
297
297
  props.selected && "bg-dashboard-surface-active",
298
298
  )}
299
299
  to={conversationPath(props.conversation.id)}
@@ -323,7 +323,7 @@ const ConversationSidebarRow = memo(function ConversationSidebarRow(props: {
323
323
  </Link>
324
324
  <button
325
325
  aria-label={`${props.conversation.archivedAt ? "Restore" : "Archive"} ${title}`}
326
- className="absolute right-1.5 top-1/2 z-10 grid size-7 -translate-y-1/2 cursor-pointer place-items-center rounded-md bg-dashboard-surface-active text-dashboard-text-muted transition hover:text-dashboard-text focus:outline-none focus:ring-2 focus:ring-cyan-300/35 sm:pointer-events-none sm:opacity-0 sm:focus:pointer-events-auto sm:focus:opacity-100 sm:group-focus-within:pointer-events-auto sm:group-focus-within:opacity-100 sm:group-hover:pointer-events-auto sm:group-hover:opacity-100 disabled:cursor-not-allowed"
326
+ className="absolute right-1.5 top-1/2 z-10 grid size-7 -translate-y-1/2 cursor-pointer place-items-center rounded-md bg-dashboard-surface-active text-dashboard-text-muted transition hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus sm:pointer-events-none sm:opacity-0 sm:focus:pointer-events-auto sm:focus:opacity-100 sm:group-focus-within:pointer-events-auto sm:group-focus-within:opacity-100 sm:group-hover:pointer-events-auto sm:group-hover:opacity-100 disabled:cursor-not-allowed"
327
327
  disabled={archive.isPending}
328
328
  onClick={() =>
329
329
  archive.mutate({
@@ -413,7 +413,7 @@ function ArchivedConversationNotice(props: {
413
413
  >
414
414
  {restore.error ? (
415
415
  <div
416
- className="border-t border-rose-300/25 bg-rose-400/[0.12] px-3 py-2 font-mono text-xs text-rose-50/85"
416
+ className="border-t border-rose-300/25 bg-rose-400/[0.12] px-3 py-2 font-sans text-xs text-rose-50/85"
417
417
  role="alert"
418
418
  >
419
419
  Could not restore the conversation.