@sentry/junior-dashboard 0.264.0 → 0.266.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +2 -5
  3. package/dist/app.js +119 -35
  4. package/dist/client.js +145 -136
  5. package/dist/index.d.ts +0 -1
  6. package/dist/index.js +119 -35
  7. package/dist/tailwind.css +1 -1
  8. package/package.json +5 -4
  9. package/src/api/schema.ts +0 -1
  10. package/src/app.ts +5 -20
  11. package/src/client/App.tsx +29 -31
  12. package/src/client/api.ts +7 -0
  13. package/src/client/components/ActorAvatar.tsx +10 -22
  14. package/src/client/components/BottomSheet.tsx +12 -2
  15. package/src/client/components/ConnectionBanner.tsx +4 -3
  16. package/src/client/components/CopyButton.tsx +82 -0
  17. package/src/client/components/DetailsPanel.tsx +133 -0
  18. package/src/client/components/Drawer.tsx +15 -24
  19. package/src/client/components/EmptyTelemetry.tsx +5 -3
  20. package/src/client/components/FilterBar.tsx +16 -31
  21. package/src/client/components/ImageAttachment.tsx +49 -63
  22. package/src/client/components/InlineError.tsx +8 -4
  23. package/src/client/components/Metric.tsx +33 -23
  24. package/src/client/components/Notice.tsx +19 -26
  25. package/src/client/components/PageContentSkeleton.tsx +1 -1
  26. package/src/client/components/Pagination.tsx +27 -27
  27. package/src/client/components/ParticipantAvatarStack.tsx +43 -39
  28. package/src/client/components/ProfileMenu.tsx +27 -93
  29. package/src/client/components/README.md +103 -78
  30. package/src/client/components/SearchInput.tsx +51 -41
  31. package/src/client/components/SegmentedNav.tsx +1 -1
  32. package/src/client/components/ShimmerText.tsx +2 -6
  33. package/src/client/components/ThemeSwitcher.tsx +19 -15
  34. package/src/client/components/VersionDriftBanner.tsx +9 -7
  35. package/src/client/components/charts/ActivityChart.tsx +173 -288
  36. package/src/client/components/charts/CacheWrites.tsx +59 -37
  37. package/src/client/components/charts/InputCacheChart.tsx +137 -131
  38. package/src/client/components/charts/README.md +4 -4
  39. package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
  40. package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
  41. package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
  42. package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
  43. package/src/client/components/layout/DashboardHeader.tsx +44 -90
  44. package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
  45. package/src/client/components/layout/VisualViewportShell.tsx +2 -2
  46. package/src/client/components/ui/accordion.tsx +77 -0
  47. package/src/client/components/ui/alert-dialog.tsx +89 -0
  48. package/src/client/components/ui/alert.tsx +64 -0
  49. package/src/client/components/ui/avatar.tsx +53 -0
  50. package/src/client/components/ui/button.tsx +86 -0
  51. package/src/client/components/ui/chart.tsx +74 -0
  52. package/src/client/components/ui/checkbox.tsx +26 -0
  53. package/src/client/components/ui/collapsible.tsx +22 -0
  54. package/src/client/components/ui/dialog.tsx +66 -0
  55. package/src/client/components/ui/empty.tsx +32 -0
  56. package/src/client/components/ui/field.tsx +49 -0
  57. package/src/client/components/ui/input-group.tsx +96 -0
  58. package/src/client/components/ui/input.tsx +35 -0
  59. package/src/client/components/ui/native-select.tsx +57 -0
  60. package/src/client/components/ui/pagination.tsx +60 -0
  61. package/src/client/components/ui/popover.tsx +67 -0
  62. package/src/client/components/ui/radio-group.tsx +32 -0
  63. package/src/client/components/ui/sheet.tsx +13 -3
  64. package/src/client/components/ui/skeleton.tsx +19 -0
  65. package/src/client/components/ui/spinner.tsx +21 -0
  66. package/src/client/components/ui/table.tsx +101 -0
  67. package/src/client/components/ui/tabs.tsx +75 -0
  68. package/src/client/components/ui/textarea.tsx +17 -0
  69. package/src/client/components/ui/toast.tsx +215 -0
  70. package/src/client/components/ui/toggle-group.tsx +36 -0
  71. package/src/client/components/ui/toggle.tsx +37 -0
  72. package/src/client/components/ui/tooltip.tsx +168 -0
  73. package/src/client/components/ui/trace.tsx +96 -0
  74. package/src/client/conversations/ChatLayout.tsx +7 -3
  75. package/src/client/conversations/ConversationBrief.tsx +17 -6
  76. package/src/client/conversations/ConversationChildAgents.tsx +5 -5
  77. package/src/client/conversations/ConversationComposer.tsx +26 -12
  78. package/src/client/conversations/ConversationDetails.tsx +147 -62
  79. package/src/client/conversations/ConversationEventDetails.tsx +141 -0
  80. package/src/client/conversations/ConversationEventLog.tsx +150 -129
  81. package/src/client/conversations/ConversationHeader.tsx +13 -7
  82. package/src/client/conversations/ConversationHeaderActions.tsx +97 -93
  83. package/src/client/conversations/ConversationHomeList.tsx +1 -1
  84. package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
  85. package/src/client/conversations/ConversationMeta.tsx +55 -88
  86. package/src/client/conversations/ConversationPage.tsx +220 -101
  87. package/src/client/conversations/ConversationSidebar.tsx +49 -29
  88. package/src/client/conversations/ConversationUsageBreakdown.tsx +108 -65
  89. package/src/client/conversations/ConversationWatches.tsx +173 -0
  90. package/src/client/conversations/ConversationWorkspace.tsx +12 -22
  91. package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
  92. package/src/client/conversations/DetailsList.tsx +139 -0
  93. package/src/client/conversations/EventDetails.tsx +42 -8
  94. package/src/client/conversations/EventUsage.tsx +99 -74
  95. package/src/client/conversations/ForkConversationButton.tsx +27 -16
  96. package/src/client/conversations/ObjectCard.tsx +20 -8
  97. package/src/client/conversations/PendingMailboxStack.tsx +52 -38
  98. package/src/client/conversations/README.md +89 -13
  99. package/src/client/conversations/ShareConversationButton.tsx +28 -17
  100. package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
  101. package/src/client/conversations/ToolFrame.tsx +36 -52
  102. package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
  103. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
  104. package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
  105. package/src/client/conversations/TranscriptEventView.tsx +35 -16
  106. package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
  107. package/src/client/conversations/TranscriptLoading.tsx +1 -1
  108. package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
  109. package/src/client/conversations/TranscriptMessageContextView.tsx +35 -18
  110. package/src/client/conversations/TranscriptMessageView.tsx +6 -1
  111. package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
  112. package/src/client/conversations/TranscriptStructuredEventView.tsx +25 -15
  113. package/src/client/conversations/TranscriptSummary.tsx +34 -2
  114. package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
  115. package/src/client/conversations/TranscriptToolView.tsx +1 -1
  116. package/src/client/conversations/TranscriptTurnContextView.tsx +144 -65
  117. package/src/client/conversations/TranscriptView.tsx +63 -27
  118. package/src/client/conversations/eventLog.ts +58 -0
  119. package/src/client/conversations/eventTime.ts +60 -0
  120. package/src/client/conversations/queries.ts +29 -0
  121. package/src/client/conversations/transcriptBottomPinning.ts +34 -0
  122. package/src/client/conversations/transcriptMentions.tsx +18 -5
  123. package/src/client/format.ts +5 -0
  124. package/src/client/pages/ConnectionsPage.tsx +2 -2
  125. package/src/client/pages/PersonalTokensPage.tsx +4 -4
  126. package/src/client/pages/SettingsPage.tsx +9 -10
  127. package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
  128. package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
  129. package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
  130. package/src/client/pages/automations/AutomationDetails.tsx +193 -0
  131. package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
  132. package/src/client/pages/automations/AutomationEditor.tsx +41 -29
  133. package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
  134. package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
  135. package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
  136. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
  137. package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
  138. package/src/client/pages/automations/AutomationFilters.tsx +189 -74
  139. package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
  140. package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
  141. package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
  142. package/src/client/pages/automations/AutomationRow.tsx +12 -138
  143. package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
  144. package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
  145. package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
  146. package/src/client/pages/automations/AutomationStatus.tsx +201 -0
  147. package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
  148. package/src/client/pages/automations/AutomationsPage.tsx +80 -68
  149. package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
  150. package/src/client/pages/automations/README.md +57 -0
  151. package/src/client/pages/automations/TriggerFilters.tsx +22 -0
  152. package/src/client/pages/automations/automationFormat.ts +37 -23
  153. package/src/client/pages/code/CodeActivityChart.tsx +59 -101
  154. package/src/client/pages/code/CodePage.tsx +52 -55
  155. package/src/client/pages/code/RepositoryPage.tsx +6 -8
  156. package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
  157. package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
  158. package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
  159. package/src/client/pages/memory/MemoryDetails.tsx +218 -0
  160. package/src/client/pages/memory/MemoryPage.tsx +168 -112
  161. package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
  162. package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
  163. package/src/client/pages/memory/memoryRecord.ts +1 -1
  164. package/src/client/pages/people/ContributionGrid.tsx +23 -16
  165. package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
  166. package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
  167. package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
  168. package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
  169. package/src/client/pages/system/GuardianActivity.tsx +1 -2
  170. package/src/client/pages/system/PluginBarChart.tsx +106 -87
  171. package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
  172. package/src/client/pages/system/README.md +23 -0
  173. package/src/client/pages/system/SystemNavigation.tsx +5 -1
  174. package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
  175. package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
  176. package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
  177. package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
  178. package/src/client/pages/system/WorkspaceList.tsx +20 -30
  179. package/src/client/pages/system/WorkspacesPage.tsx +117 -119
  180. package/src/client/pages/system/sandboxSize.ts +8 -0
  181. package/src/client/pages/system/workspaceQueries.ts +53 -0
  182. package/src/client/pages/user/PluginUserPage.tsx +51 -2
  183. package/src/client/pages/user/pluginUserPageData.ts +1 -6
  184. package/src/client/styles.ts +4 -0
  185. package/src/client.tsx +7 -1
  186. package/src/mock-reporting/automation-collection.ts +4 -2
  187. package/src/mock-reporting/fixtures.ts +23 -7
  188. package/src/mock-reporting/routes.ts +12 -0
  189. package/src/mock-reporting/watches.ts +94 -0
  190. package/src/shell.ts +4 -9
  191. package/src/tailwind.css +0 -10
  192. package/src/client/components/Button.tsx +0 -118
  193. package/src/client/components/Field.tsx +0 -53
  194. package/src/client/components/SegmentedTabs.tsx +0 -119
  195. package/src/client/components/Select.tsx +0 -52
  196. package/src/client/components/Skeleton.tsx +0 -17
  197. package/src/client/components/TextInput.tsx +0 -58
  198. package/src/client/components/Tooltip.tsx +0 -216
  199. package/src/client/components/charts/useChartLayout.ts +0 -27
  200. package/src/client/components/ui/sheet.LICENSE +0 -21
  201. package/src/client/conversations/ConversationDetailsFrame.tsx +0 -119
  202. package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
  203. package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
  204. package/src/client/pages/dev/ChartFixtures.tsx +0 -325
  205. package/src/client/pages/dev/ComponentsPage.tsx +0 -899
  206. package/src/client/pages/dev/ConversationFixture.tsx +0 -231
  207. package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
  208. package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
  209. package/src/client/pages/dev/SelectFixtures.tsx +0 -62
  210. package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
  211. package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
  212. package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
  213. package/src/client/pages/system/SnapshotSummary.tsx +0 -78
@@ -1,23 +1,30 @@
1
+ import { ChevronRight } from "lucide-react";
1
2
  import { useState } from "react";
2
3
  import type { ConversationMetricDay } from "@sentry/junior/api/schema";
3
4
 
4
- import { ToggleButton } from "../Button";
5
5
  import { Card } from "../layout/Card";
6
+ import {
7
+ Collapsible,
8
+ CollapsibleContent,
9
+ CollapsibleTrigger,
10
+ } from "../ui/collapsible";
11
+ import { dashboardDisclosureChevronClass } from "../../styles";
12
+ import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group";
6
13
  import { formatCompactNumber } from "../../format";
7
14
  import {
8
15
  type TimeRangeBucketUnit,
9
16
  timeRangeBucketPerLabel,
10
17
  } from "../controls/TimeRangeSelector";
18
+ import { Bar, BarChart, CartesianGrid, XAxis } from "recharts";
19
+ import { ChartContainer, ChartTooltip } from "../ui/chart";
11
20
  import {
12
- ActivityChartDateLabels,
13
- ActivityChartGrid,
14
- ActivityChartTooltip,
15
- ActivityTooltipRows,
16
- ChartSvg,
21
+ activityChartMargin,
22
+ ActivityChartTooltipContent,
23
+ ActivityChartXAxis,
24
+ ActivityChartYAxis,
17
25
  } from "./ActivityChart";
18
26
  import { ChartHeader } from "./ChartHeader";
19
27
  import { CacheWrites } from "./CacheWrites";
20
- import { useChartLayout } from "./useChartLayout";
21
28
  import {
22
29
  CACHE_INPUT_SERIES,
23
30
  cacheInputTotal,
@@ -26,6 +33,13 @@ import {
26
33
  summarizeCacheInput,
27
34
  } from "./cache-input";
28
35
 
36
+ const chartConfig = {
37
+ ...Object.fromEntries(
38
+ CACHE_INPUT_SERIES.map(({ color, key, label }) => [key, { color, label }]),
39
+ ),
40
+ missing: { color: "var(--color-slate-400)", label: "Missing data" },
41
+ };
42
+
29
43
  /** Show cache reuse and write volume without treating missing counters as zero. */
30
44
  export function InputCacheChart(props: {
31
45
  bucketUnit: TimeRangeBucketUnit;
@@ -33,40 +47,53 @@ export function InputCacheChart(props: {
33
47
  }) {
34
48
  const [mode, setMode] = useState<"share" | "tokens">("share");
35
49
  const summary = summarizeCacheInput(props.days);
36
- const { ref, layout } = useChartLayout(160);
37
- const reportedTotals = props.days.map((day) =>
38
- CACHE_INPUT_SERIES.reduce((sum, { key }) => sum + (day[key] ?? 0), 0),
39
- );
40
- const maximum = mode === "share" ? 100 : Math.max(1, ...reportedTotals);
41
- const step = layout.plotWidth / Math.max(1, props.days.length);
42
- const barWidth = Math.min(32, step * 0.7);
43
- const x = (index: number) => layout.left + step * (index + 0.5);
50
+ const data = props.days.map((day) => {
51
+ const total = cacheInputTotal(day);
52
+ const plotted = Object.fromEntries(
53
+ CACHE_INPUT_SERIES.map(({ key }) => {
54
+ const value = day[key] ?? 0;
55
+ return [
56
+ key,
57
+ mode === "share" ? (total ? (value / total) * 100 : 0) : value,
58
+ ];
59
+ }),
60
+ );
61
+ return {
62
+ ...plotted,
63
+ date: day.date,
64
+ day,
65
+ // A zero-height bar that `minPointSize` draws as a gray mark.
66
+ missing: total === undefined && hasCacheActivity(day) ? 0 : undefined,
67
+ };
68
+ });
69
+ const maximum =
70
+ mode === "share"
71
+ ? 100
72
+ : Math.max(
73
+ 1,
74
+ ...props.days.map((day) =>
75
+ CACHE_INPUT_SERIES.reduce(
76
+ (sum, { key }) => sum + (day[key] ?? 0),
77
+ 0,
78
+ ),
79
+ ),
80
+ );
44
81
 
45
82
  return (
46
83
  <Card>
47
84
  <ChartHeader
48
85
  title="Input cache"
49
86
  trailing={
50
- <div
87
+ <ToggleGroup
51
88
  aria-label="Cache chart display"
52
- className="flex gap-1"
53
- role="group"
89
+ onValueChange={([next]) => {
90
+ if (next) setMode(next);
91
+ }}
92
+ value={[mode]}
54
93
  >
55
- <ToggleButton
56
- pressed={mode === "share"}
57
- variant="segment"
58
- onClick={() => setMode("share")}
59
- >
60
- Percent
61
- </ToggleButton>
62
- <ToggleButton
63
- pressed={mode === "tokens"}
64
- variant="segment"
65
- onClick={() => setMode("tokens")}
66
- >
67
- Tokens
68
- </ToggleButton>
69
- </div>
94
+ <ToggleGroupItem value="share">Percent</ToggleGroupItem>
95
+ <ToggleGroupItem value="tokens">Tokens</ToggleGroupItem>
96
+ </ToggleGroup>
70
97
  }
71
98
  />
72
99
  <dl className="m-0 flex flex-wrap gap-x-5 gap-y-2 px-4 py-3 font-sans tabular-nums text-xs sm:px-5">
@@ -97,112 +124,91 @@ export function InputCacheChart(props: {
97
124
  reported values.
98
125
  </p>
99
126
  ) : null}
100
- <div className="px-2 pb-2" ref={ref}>
101
- <ChartSvg
102
- aria-label={`Input cache ${mode} per ${timeRangeBucketPerLabel(props.bucketUnit)}`}
103
- layout={layout}
127
+ <div className="px-2 pb-2">
128
+ <ChartContainer
129
+ className="aspect-auto h-40 w-full"
130
+ config={chartConfig}
104
131
  >
105
- <ActivityChartGrid
106
- format={
107
- mode === "share" ? (value) => `${value}%` : formatCompactNumber
108
- }
109
- layout={layout}
110
- maximum={maximum}
111
- />
112
- {props.days.map((day, index) => {
113
- const total = cacheInputTotal(day);
114
- const incomplete = total === undefined && hasCacheActivity(day);
115
- let offset = 0;
116
- const bars = CACHE_INPUT_SERIES.map(({ key, color }) => {
117
- const value = day[key] ?? 0;
118
- const plotted =
119
- mode === "share" ? (total ? (value / total) * 100 : 0) : value;
120
- const height = (plotted / maximum) * layout.plotHeight;
121
- offset += height;
122
- return (
123
- <rect
124
- key={key}
125
- fill={color}
126
- opacity={0.85}
127
- height={height}
128
- width={barWidth}
129
- x={x(index) - barWidth / 2}
130
- y={layout.top + layout.plotHeight - offset}
131
- />
132
- );
133
- });
134
- return (
135
- <ActivityChartTooltip
136
- key={day.date}
137
- date={day.date}
138
- summary={
139
- incomplete
140
- ? "Missing cache data"
141
- : total === undefined
142
- ? "No input reported"
143
- : `${formatCacheShare(day.cachedInputTokens, total)} cached · ${formatCompactNumber(total)} input tokens`
144
- }
145
- content={
146
- <ActivityTooltipRows
147
- rows={CACHE_INPUT_SERIES.map(({ key, label }) => [
132
+ <BarChart
133
+ aria-label={`Input cache ${mode} per ${timeRangeBucketPerLabel(props.bucketUnit)}`}
134
+ data={data}
135
+ margin={activityChartMargin}
136
+ role="img"
137
+ >
138
+ <CartesianGrid strokeDasharray="3 5" vertical={false} />
139
+ <ActivityChartYAxis
140
+ format={
141
+ mode === "share" ? (value) => `${value}%` : formatCompactNumber
142
+ }
143
+ maximum={maximum}
144
+ />
145
+ <ActivityChartXAxis keys={data.map((day) => day.date)} />
146
+ {/* A hidden second axis lets the missing mark overlay the stack. */}
147
+ <XAxis dataKey="date" hide xAxisId="missing" />
148
+ <ChartTooltip
149
+ content={
150
+ <ActivityChartTooltipContent
151
+ rows={({ day }: (typeof data)[number]) => {
152
+ const total = cacheInputTotal(day);
153
+ return CACHE_INPUT_SERIES.map(({ key, label }) => [
148
154
  label,
149
155
  day[key] === undefined
150
156
  ? "Not reported"
151
157
  : `${formatCompactNumber(day[key])} · ${formatCacheShare(day[key], total)}`,
152
- ])}
153
- />
154
- }
155
- >
156
- <g tabIndex={0}>
157
- <rect
158
- fill="transparent"
159
- x={x(index) - step / 2}
160
- y={layout.top}
161
- width={step}
162
- height={layout.plotHeight}
163
- />
164
- {bars}
165
- {incomplete ? (
166
- <rect
167
- fill="var(--color-slate-400)"
168
- x={x(index) - barWidth / 2}
169
- y={layout.top + layout.plotHeight - 3}
170
- width={barWidth}
171
- height={3}
172
- />
173
- ) : null}
174
- </g>
175
- </ActivityChartTooltip>
176
- );
177
- })}
178
- <ActivityChartDateLabels
179
- dates={props.days.map((day) => day.date)}
180
- layout={layout}
181
- xPosition={x}
182
- />
183
- </ChartSvg>
158
+ ]);
159
+ }}
160
+ />
161
+ }
162
+ />
163
+ {CACHE_INPUT_SERIES.map(({ key }) => (
164
+ <Bar
165
+ dataKey={key}
166
+ fill={`var(--color-${key})`}
167
+ fillOpacity={0.85}
168
+ isAnimationActive={false}
169
+ key={key}
170
+ maxBarSize={32}
171
+ stackId="input"
172
+ />
173
+ ))}
174
+ <Bar
175
+ dataKey="missing"
176
+ fill="var(--color-missing)"
177
+ isAnimationActive={false}
178
+ maxBarSize={32}
179
+ minPointSize={3}
180
+ xAxisId="missing"
181
+ />
182
+ </BarChart>
183
+ </ChartContainer>
184
184
  </div>
185
185
  <CacheWrites days={props.days} bucketUnit={props.bucketUnit} />
186
- <details className="border-t border-dashboard-border-subtle px-4 py-3 font-sans tabular-nums text-xs text-dashboard-text-muted sm:px-5">
187
- <summary className="cursor-pointer text-dashboard-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dashboard-focus">
186
+ <Collapsible className="border-t border-dashboard-border-subtle px-4 py-3 font-sans tabular-nums text-xs text-dashboard-text-muted sm:px-5">
187
+ <CollapsibleTrigger className="group flex cursor-pointer items-center gap-1 text-dashboard-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dashboard-focus">
188
+ <ChevronRight
189
+ aria-hidden="true"
190
+ className={dashboardDisclosureChevronClass}
191
+ />
188
192
  How to read this
189
- </summary>
190
- <div className="mt-3 grid gap-2 leading-relaxed">
191
- <p className="m-0">
192
- From cache is input reused. Written to cache is input saved for
193
- reuse. Uncached is the remaining input.
194
- </p>
195
- <p className="m-0">
196
- Percentages use total input tokens, not call counts. Gray marks and
197
- — mean missing data; zero means a reported zero. The latest interval
198
- may be incomplete.
199
- </p>
200
- <p className="m-0">
201
- These totals can show when reuse changes, but not why. They do not
202
- show individual calls or prove that every call reported cache use.
203
- </p>
204
- </div>
205
- </details>
193
+ </CollapsibleTrigger>
194
+ <CollapsibleContent hiddenUntilFound>
195
+ <div className="mt-3 grid gap-2 leading-relaxed">
196
+ <p className="m-0">
197
+ From cache is input reused. Written to cache is input saved for
198
+ reuse. Uncached is the remaining input.
199
+ </p>
200
+ <p className="m-0">
201
+ Percentages use total input tokens, not call counts. Gray marks
202
+ and — mean missing data; zero means a reported zero. The latest
203
+ interval may be incomplete.
204
+ </p>
205
+ <p className="m-0">
206
+ These totals can show when reuse changes, but not why. They do not
207
+ show individual calls or prove that every call reported cache use.
208
+ </p>
209
+ </div>
210
+ </CollapsibleContent>
211
+ </Collapsible>
206
212
  </Card>
207
213
  );
208
214
  }
@@ -27,12 +27,12 @@ or a cache failure rate.
27
27
  The list and chart consume the same selected buckets. There is no local time
28
28
  selector or comparison window. The list is collapsed until needed.
29
29
 
30
- `useChartLayout` measures the available width. Charts keep a fixed, compact
31
- height rather than growing taller on wide screens.
30
+ The chart fills the available width. It keeps a fixed, compact height rather
31
+ than growing taller on wide screens.
32
32
 
33
33
  This report has no per-call model, release, environment, trace, or write-cost
34
34
  fields. Add a reporting contract before adding those filters or drilldowns.
35
35
  A large write alone cannot identify cache expiry, a cold start, or prefix loss.
36
36
 
37
- The chart gallery includes a reuse drop and a missing counter. The System
38
- browser scenario covers display modes and verifies that the page time control also filters the list.
37
+ The System browser scenario covers display modes and verifies that the page
38
+ time control also filters the list.
@@ -11,16 +11,17 @@ import {
11
11
  formatCompactNumber,
12
12
  formatCostSummary,
13
13
  } from "../../format";
14
+ import { Area, Bar, CartesianGrid, ComposedChart, Scatter } from "recharts";
15
+ import { ChartContainer, ChartTooltip } from "../ui/chart";
14
16
  import {
15
17
  ActivityChartAverageLine,
16
- ActivityChartDateLabels,
17
- ActivityChartGrid,
18
- ActivityChartTooltip,
19
18
  activityChartAverage,
20
- ChartSvg,
19
+ activityChartMargin,
20
+ ActivityChartTooltipContent,
21
+ ActivityChartXAxis,
22
+ ActivityChartYAxis,
21
23
  } from "./ActivityChart";
22
24
  import { ChartHeader } from "./ChartHeader";
23
- import { useChartLayout } from "./useChartLayout";
24
25
  import { InputCacheChart } from "./InputCacheChart";
25
26
 
26
27
  type Metric = "costUsd" | "durationMs" | "tokens";
@@ -123,108 +124,80 @@ function MetricChart(props: {
123
124
  days: ConversationMetricDay[];
124
125
  }) {
125
126
  const { chart, days } = props;
126
- const { ref, layout } = useChartLayout(
127
- 150,
128
- chart.metric === "costUsd" ? 72 : 64,
129
- );
130
127
  const values = days.map((day) => metricValue(day, chart.metric));
131
- const maximum = Math.max(Number.EPSILON, ...values);
132
- const step = layout.plotWidth / Math.max(1, days.length);
133
- const points = values.map((value, index) => ({
134
- x: layout.left + step * index + step / 2,
135
- y: layout.top + layout.plotHeight - (value / maximum) * layout.plotHeight,
128
+ const data = days.map((day, index) => ({
129
+ date: day.date,
130
+ value: values[index]!,
136
131
  }));
137
- const area = points.length
138
- ? `M ${points[0]!.x} ${layout.top + layout.plotHeight} L ${points
139
- .map((point) => `${point.x} ${point.y}`)
140
- .join(" L ")} L ${points.at(-1)!.x} ${layout.top + layout.plotHeight} Z`
141
- : "";
132
+ const maximum = Math.max(Number.EPSILON, ...values);
142
133
  const total = values.reduce((sum, value) => sum + value, 0);
143
134
  const average = activityChartAverage(values);
144
- const barWidth = Math.max(1.5, Math.min(8, step * 0.65));
135
+ const series = {
136
+ dataKey: "value",
137
+ fill: "var(--color-value)",
138
+ isAnimationActive: false,
139
+ } as const;
145
140
 
146
141
  return (
147
142
  <Card>
148
143
  <ChartHeader title={chart.title} total={chart.format(total)} />
149
- <div className="px-2 py-2" ref={ref}>
150
- <ChartSvg
151
- aria-label={`${chart.title} per ${props.bucketUnit === "6hour" ? "6 hours" : props.bucketUnit}`}
152
- layout={layout}
144
+ <div className="px-2 py-2">
145
+ <ChartContainer
146
+ className="aspect-auto h-38 w-full"
147
+ config={{ value: { color: chart.color, label: chart.title } }}
153
148
  >
154
- <ActivityChartGrid
155
- format={chart.axisFormat}
156
- layout={layout}
157
- maximum={maximum}
158
- />
159
- {chart.type === "area" && area ? (
160
- <>
161
- <path d={area} fill={chart.color} fillOpacity={0.08} />
162
- <polyline
163
- fill="none"
164
- points={points
165
- .map((point) => `${point.x},${point.y}`)
166
- .join(" ")}
167
- stroke={chart.color}
149
+ <ComposedChart
150
+ aria-label={`${chart.title} per ${props.bucketUnit === "6hour" ? "6 hours" : props.bucketUnit}`}
151
+ data={data}
152
+ margin={activityChartMargin}
153
+ role="img"
154
+ >
155
+ <CartesianGrid strokeDasharray="3 5" vertical={false} />
156
+ <ActivityChartYAxis format={chart.axisFormat} maximum={maximum} />
157
+ <ActivityChartXAxis keys={data.map((day) => day.date)} />
158
+ <ChartTooltip
159
+ content={
160
+ <ActivityChartTooltipContent
161
+ rows={(day: (typeof data)[number]) => [
162
+ [chart.title, chart.format(day.value)],
163
+ ]}
164
+ />
165
+ }
166
+ />
167
+ {chart.type === "bar" ? (
168
+ <Bar
169
+ {...series}
170
+ fillOpacity={0.8}
171
+ maxBarSize={8}
172
+ minPointSize={(value) => (value ? 2 : 0)}
173
+ radius={1.5}
174
+ />
175
+ ) : chart.type === "area" ? (
176
+ <Area
177
+ {...series}
178
+ dot={{ fillOpacity: 0.45, r: 2.5, stroke: "none" }}
179
+ fillOpacity={0.08}
180
+ stroke="var(--color-value)"
168
181
  strokeLinecap="round"
169
182
  strokeLinejoin="round"
170
- strokeWidth="2"
183
+ strokeWidth={2}
184
+ type="linear"
171
185
  />
172
- </>
173
- ) : null}
174
- {days.map((day, index) => {
175
- const value = values[index]!;
176
- const point = points[index]!;
177
- const barHeight = (value / maximum) * layout.plotHeight;
178
- const renderedBarHeight = Math.max(value ? 2 : 0, barHeight);
179
- return (
180
- <ActivityChartTooltip
181
- key={day.date}
182
- content={chart.format(value)}
183
- date={day.date}
184
- summary={chart.format(value)}
185
- >
186
- {chart.type === "bar" ? (
187
- <rect
188
- fill={chart.color}
189
- height={renderedBarHeight}
190
- opacity={value ? 0.8 : 0.1}
191
- rx="1.5"
192
- tabIndex={0}
193
- width={barWidth}
194
- x={point.x - barWidth / 2}
195
- y={layout.top + layout.plotHeight - renderedBarHeight}
196
- />
197
- ) : (
198
- <circle
199
- cx={point.x}
200
- cy={point.y}
201
- fill={chart.color}
202
- opacity={chart.type === "scatter" ? 0.75 : 0.45}
203
- r={chart.type === "scatter" ? 3.5 : 2.5}
204
- tabIndex={0}
205
- />
206
- )}
207
- </ActivityChartTooltip>
208
- );
209
- })}
210
- <ActivityChartAverageLine
211
- average={average}
212
- format={
213
- chart.metric === "tokens"
214
- ? formatActivityChartAverage
215
- : chart.format
216
- }
217
- layout={layout}
218
- maximum={maximum}
219
- stroke={chart.color}
220
- unit={timeRangeBucketAverageUnit(props.bucketUnit)}
221
- />
222
- <ActivityChartDateLabels
223
- dates={days.map((day) => day.date)}
224
- layout={layout}
225
- xPosition={(index) => points[index]?.x ?? layout.left}
226
- />
227
- </ChartSvg>
186
+ ) : (
187
+ <Scatter {...series} fillOpacity={0.75} />
188
+ )}
189
+ <ActivityChartAverageLine
190
+ average={average}
191
+ format={
192
+ chart.metric === "tokens"
193
+ ? formatActivityChartAverage
194
+ : chart.format
195
+ }
196
+ stroke={chart.color}
197
+ unit={timeRangeBucketAverageUnit(props.bucketUnit)}
198
+ />
199
+ </ComposedChart>
200
+ </ChartContainer>
228
201
  </div>
229
202
  </Card>
230
203
  );