@sentry/junior-dashboard 0.264.0 → 0.265.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +2 -5
  3. package/dist/app.js +119 -35
  4. package/dist/client.js +145 -136
  5. package/dist/index.d.ts +0 -1
  6. package/dist/index.js +119 -35
  7. package/dist/tailwind.css +1 -1
  8. package/package.json +5 -4
  9. package/src/api/schema.ts +0 -1
  10. package/src/app.ts +5 -20
  11. package/src/client/App.tsx +29 -31
  12. package/src/client/api.ts +7 -0
  13. package/src/client/components/BottomSheet.tsx +12 -2
  14. package/src/client/components/CopyButton.tsx +83 -0
  15. package/src/client/components/DetailsPanel.tsx +133 -0
  16. package/src/client/components/Drawer.tsx +15 -24
  17. package/src/client/components/FilterBar.tsx +16 -31
  18. package/src/client/components/Metric.tsx +33 -23
  19. package/src/client/components/PageContentSkeleton.tsx +1 -1
  20. package/src/client/components/Pagination.tsx +1 -1
  21. package/src/client/components/ParticipantAvatarStack.tsx +40 -36
  22. package/src/client/components/ProfileMenu.tsx +27 -93
  23. package/src/client/components/README.md +86 -77
  24. package/src/client/components/SegmentedNav.tsx +1 -1
  25. package/src/client/components/ShimmerText.tsx +2 -6
  26. package/src/client/components/ThemeSwitcher.tsx +19 -15
  27. package/src/client/components/charts/ActivityChart.tsx +173 -288
  28. package/src/client/components/charts/CacheWrites.tsx +63 -50
  29. package/src/client/components/charts/InputCacheChart.tsx +137 -131
  30. package/src/client/components/charts/README.md +4 -4
  31. package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
  32. package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
  33. package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
  34. package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
  35. package/src/client/components/layout/DashboardHeader.tsx +44 -90
  36. package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
  37. package/src/client/components/layout/VisualViewportShell.tsx +2 -2
  38. package/src/client/components/ui/accordion.tsx +77 -0
  39. package/src/client/components/ui/alert-dialog.tsx +89 -0
  40. package/src/client/components/ui/button.tsx +85 -0
  41. package/src/client/components/ui/chart.tsx +74 -0
  42. package/src/client/components/ui/checkbox.tsx +26 -0
  43. package/src/client/components/ui/collapsible.tsx +22 -0
  44. package/src/client/components/ui/field.tsx +49 -0
  45. package/src/client/components/ui/input.tsx +35 -0
  46. package/src/client/components/ui/native-select.tsx +57 -0
  47. package/src/client/components/ui/popover.tsx +67 -0
  48. package/src/client/components/ui/radio-group.tsx +32 -0
  49. package/src/client/components/ui/sheet.tsx +13 -3
  50. package/src/client/components/ui/skeleton.tsx +19 -0
  51. package/src/client/components/ui/table.tsx +80 -0
  52. package/src/client/components/ui/tabs.tsx +75 -0
  53. package/src/client/components/ui/textarea.tsx +17 -0
  54. package/src/client/components/ui/toast.tsx +215 -0
  55. package/src/client/components/ui/toggle-group.tsx +36 -0
  56. package/src/client/components/ui/toggle.tsx +37 -0
  57. package/src/client/components/ui/tooltip.tsx +168 -0
  58. package/src/client/components/ui/trace.tsx +96 -0
  59. package/src/client/conversations/ChatLayout.tsx +7 -3
  60. package/src/client/conversations/ConversationBrief.tsx +17 -6
  61. package/src/client/conversations/ConversationComposer.tsx +26 -12
  62. package/src/client/conversations/ConversationDetails.tsx +147 -62
  63. package/src/client/conversations/ConversationEventDetails.tsx +141 -0
  64. package/src/client/conversations/ConversationEventLog.tsx +150 -129
  65. package/src/client/conversations/ConversationHeader.tsx +8 -3
  66. package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
  67. package/src/client/conversations/ConversationHomeList.tsx +1 -1
  68. package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
  69. package/src/client/conversations/ConversationMeta.tsx +55 -88
  70. package/src/client/conversations/ConversationPage.tsx +220 -101
  71. package/src/client/conversations/ConversationSidebar.tsx +48 -29
  72. package/src/client/conversations/ConversationWatches.tsx +165 -0
  73. package/src/client/conversations/ConversationWorkspace.tsx +12 -22
  74. package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
  75. package/src/client/conversations/DetailsList.tsx +124 -0
  76. package/src/client/conversations/EventDetails.tsx +42 -8
  77. package/src/client/conversations/ForkConversationButton.tsx +27 -16
  78. package/src/client/conversations/ObjectCard.tsx +20 -8
  79. package/src/client/conversations/PendingMailboxStack.tsx +52 -33
  80. package/src/client/conversations/README.md +86 -13
  81. package/src/client/conversations/ShareConversationButton.tsx +25 -13
  82. package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
  83. package/src/client/conversations/ToolFrame.tsx +36 -52
  84. package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
  85. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
  86. package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
  87. package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
  88. package/src/client/conversations/TranscriptLoading.tsx +1 -1
  89. package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
  90. package/src/client/conversations/TranscriptMessageContextView.tsx +8 -2
  91. package/src/client/conversations/TranscriptMessageView.tsx +6 -1
  92. package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
  93. package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
  94. package/src/client/conversations/TranscriptToolView.tsx +1 -1
  95. package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
  96. package/src/client/conversations/TranscriptView.tsx +63 -27
  97. package/src/client/conversations/eventLog.ts +57 -0
  98. package/src/client/conversations/eventTime.ts +60 -0
  99. package/src/client/conversations/queries.ts +29 -0
  100. package/src/client/conversations/transcriptBottomPinning.ts +34 -0
  101. package/src/client/format.ts +5 -0
  102. package/src/client/pages/ConnectionsPage.tsx +2 -2
  103. package/src/client/pages/PersonalTokensPage.tsx +4 -4
  104. package/src/client/pages/SettingsPage.tsx +9 -10
  105. package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
  106. package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
  107. package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
  108. package/src/client/pages/automations/AutomationDetails.tsx +193 -0
  109. package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
  110. package/src/client/pages/automations/AutomationEditor.tsx +41 -29
  111. package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
  112. package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
  113. package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
  114. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
  115. package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
  116. package/src/client/pages/automations/AutomationFilters.tsx +189 -74
  117. package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
  118. package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
  119. package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
  120. package/src/client/pages/automations/AutomationRow.tsx +12 -138
  121. package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
  122. package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
  123. package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
  124. package/src/client/pages/automations/AutomationStatus.tsx +201 -0
  125. package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
  126. package/src/client/pages/automations/AutomationsPage.tsx +80 -68
  127. package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
  128. package/src/client/pages/automations/README.md +57 -0
  129. package/src/client/pages/automations/TriggerFilters.tsx +22 -0
  130. package/src/client/pages/automations/automationFormat.ts +37 -23
  131. package/src/client/pages/code/CodeActivityChart.tsx +59 -101
  132. package/src/client/pages/code/CodePage.tsx +52 -55
  133. package/src/client/pages/code/RepositoryPage.tsx +6 -8
  134. package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
  135. package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
  136. package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
  137. package/src/client/pages/memory/MemoryDetails.tsx +218 -0
  138. package/src/client/pages/memory/MemoryPage.tsx +168 -112
  139. package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
  140. package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
  141. package/src/client/pages/memory/memoryRecord.ts +1 -1
  142. package/src/client/pages/people/ContributionGrid.tsx +23 -16
  143. package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
  144. package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
  145. package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
  146. package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
  147. package/src/client/pages/system/GuardianActivity.tsx +1 -2
  148. package/src/client/pages/system/PluginBarChart.tsx +106 -87
  149. package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
  150. package/src/client/pages/system/README.md +23 -0
  151. package/src/client/pages/system/SystemNavigation.tsx +5 -1
  152. package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
  153. package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
  154. package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
  155. package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
  156. package/src/client/pages/system/WorkspaceList.tsx +20 -30
  157. package/src/client/pages/system/WorkspacesPage.tsx +117 -119
  158. package/src/client/pages/system/sandboxSize.ts +8 -0
  159. package/src/client/pages/system/workspaceQueries.ts +53 -0
  160. package/src/client/pages/user/PluginUserPage.tsx +51 -2
  161. package/src/client/pages/user/pluginUserPageData.ts +1 -6
  162. package/src/client/styles.ts +4 -0
  163. package/src/client.tsx +7 -1
  164. package/src/mock-reporting/automation-collection.ts +4 -2
  165. package/src/mock-reporting/fixtures.ts +23 -7
  166. package/src/mock-reporting/routes.ts +12 -0
  167. package/src/mock-reporting/watches.ts +94 -0
  168. package/src/shell.ts +4 -9
  169. package/src/tailwind.css +0 -10
  170. package/src/client/components/Button.tsx +0 -118
  171. package/src/client/components/Field.tsx +0 -53
  172. package/src/client/components/SegmentedTabs.tsx +0 -119
  173. package/src/client/components/Select.tsx +0 -52
  174. package/src/client/components/Skeleton.tsx +0 -17
  175. package/src/client/components/TextInput.tsx +0 -58
  176. package/src/client/components/Tooltip.tsx +0 -216
  177. package/src/client/components/charts/useChartLayout.ts +0 -27
  178. package/src/client/components/ui/sheet.LICENSE +0 -21
  179. package/src/client/conversations/ConversationDetailsFrame.tsx +0 -119
  180. package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
  181. package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
  182. package/src/client/pages/dev/ChartFixtures.tsx +0 -325
  183. package/src/client/pages/dev/ComponentsPage.tsx +0 -899
  184. package/src/client/pages/dev/ConversationFixture.tsx +0 -231
  185. package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
  186. package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
  187. package/src/client/pages/dev/SelectFixtures.tsx +0 -62
  188. package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
  189. package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
  190. package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
  191. package/src/client/pages/system/SnapshotSummary.tsx +0 -78
@@ -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
  );
@@ -1,20 +1,21 @@
1
+ import { Bar, BarChart, CartesianGrid } from "recharts";
1
2
  import {
2
3
  ActivityChartAverageLine,
3
- ActivityChartDateLabels,
4
- ActivityChartGrid,
5
4
  activityChartAverage,
6
- ActivityChartTooltip,
7
- ActivityTooltipRows,
8
- ChartSvg,
9
- createActivityChartLayout,
5
+ activityChartMargin,
6
+ ActivityChartTooltipContent,
7
+ ActivityChartXAxis,
8
+ ActivityChartYAxis,
10
9
  } from "./ActivityChart";
11
10
  import { ChartHeader } from "./ChartHeader";
12
11
  import type { TimeRangeDays } from "../controls/TimeRangeSelector";
13
12
  import { Card } from "../layout/Card";
13
+ import { ChartContainer, ChartTooltip } from "../ui/chart";
14
14
  import { formatActivityChartAverage, formatCompactNumber } from "../../format";
15
15
  import type { WorkspaceUsageDay } from "./workspaceUsage";
16
16
 
17
17
  const USAGE_COLOR = "var(--color-violet-400)";
18
+ const chartConfig = { count: { color: USAGE_COLOR, label: "Usage" } };
18
19
 
19
20
  /** Plot successful Workspace switches for one recipe over a trailing window. */
20
21
  export function WorkspaceUsageChart(props: {
@@ -22,12 +23,6 @@ export function WorkspaceUsageChart(props: {
22
23
  range: TimeRangeDays;
23
24
  workspaceName: string;
24
25
  }) {
25
- const layout = createActivityChartLayout(200);
26
- const step =
27
- props.days.length > 0
28
- ? layout.plotWidth / props.days.length
29
- : layout.plotWidth;
30
- const barWidth = Math.max(2, Math.min(13, step * 0.68));
31
26
  const values = props.days.map((day) => day.count);
32
27
  const maximum = Math.max(1, ...values);
33
28
  const average = activityChartAverage(values);
@@ -43,57 +38,42 @@ export function WorkspaceUsageChart(props: {
43
38
  totalLabel={`last ${props.range} days`}
44
39
  />
45
40
  <div className="relative px-2 py-3 sm:px-4 sm:py-4">
46
- <ChartSvg
47
- aria-label={`Usage for ${props.workspaceName} during the last ${props.range} days`}
48
- className="min-h-40 overflow-visible"
49
- layout={layout}
41
+ <ChartContainer
42
+ className="aspect-auto h-50 w-full"
43
+ config={chartConfig}
50
44
  >
51
- <ActivityChartGrid layout={layout} maximum={maximum} />
52
- {props.days.map((day, index) => {
53
- const x = layout.left + index * step + step / 2;
54
- const barHeight = (day.count / maximum) * layout.plotHeight;
55
- const renderedHeight = Math.max(day.count ? 2 : 0, barHeight);
56
- return (
57
- <ActivityChartTooltip
58
- key={day.date}
59
- content={<ActivityTooltipRows rows={[["usage", day.count]]} />}
60
- date={day.date}
61
- summary={`${day.count} switches`}
62
- >
63
- <g tabIndex={0}>
64
- <rect
65
- fill={USAGE_COLOR}
66
- height={renderedHeight}
67
- opacity={day.count ? 0.82 : 0.08}
68
- rx="2"
69
- width={barWidth}
70
- x={x - barWidth / 2}
71
- y={layout.top + layout.plotHeight - renderedHeight}
72
- />
73
- <rect
74
- fill="transparent"
75
- height={layout.plotHeight}
76
- width={step}
77
- x={layout.left + index * step}
78
- y={layout.top}
79
- />
80
- </g>
81
- </ActivityChartTooltip>
82
- );
83
- })}
84
- <ActivityChartAverageLine
85
- average={average}
86
- format={formatActivityChartAverage}
87
- layout={layout}
88
- maximum={maximum}
89
- stroke={USAGE_COLOR}
90
- />
91
- <ActivityChartDateLabels
92
- dates={props.days.map((day) => day.date)}
93
- layout={layout}
94
- xPosition={(index) => layout.left + index * step + step / 2}
95
- />
96
- </ChartSvg>
45
+ <BarChart
46
+ aria-label={`Usage for ${props.workspaceName} during the last ${props.range} days`}
47
+ data={props.days}
48
+ margin={activityChartMargin}
49
+ role="img"
50
+ >
51
+ <CartesianGrid strokeDasharray="3 5" vertical={false} />
52
+ <ActivityChartYAxis maximum={maximum} />
53
+ <ActivityChartXAxis keys={props.days.map((day) => day.date)} />
54
+ <ChartTooltip
55
+ content={
56
+ <ActivityChartTooltipContent
57
+ rows={(day: WorkspaceUsageDay) => [["usage", day.count]]}
58
+ />
59
+ }
60
+ />
61
+ <Bar
62
+ dataKey="count"
63
+ fill="var(--color-count)"
64
+ fillOpacity={0.82}
65
+ isAnimationActive={false}
66
+ maxBarSize={13}
67
+ minPointSize={(value) => (value ? 2 : 0)}
68
+ radius={2}
69
+ />
70
+ <ActivityChartAverageLine
71
+ average={average}
72
+ format={formatActivityChartAverage}
73
+ stroke={USAGE_COLOR}
74
+ />
75
+ </BarChart>
76
+ </ChartContainer>
97
77
  {!hasUsage ? (
98
78
  <div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-sans text-xs text-dashboard-text-muted">
99
79
  No usage in this period.
@@ -1,4 +1,4 @@
1
- import { Select } from "../Select";
1
+ import { NativeSelect } from "../ui/native-select";
2
2
 
3
3
  export type DirectorySortOption = {
4
4
  label: string;
@@ -17,7 +17,7 @@ export function DirectorySortSelect(props: {
17
17
  <span className="flex h-full items-center border-r border-dashboard-border-subtle px-2 font-sans font-medium text-xs text-dashboard-text-muted">
18
18
  Sort
19
19
  </span>
20
- <Select
20
+ <NativeSelect
21
21
  aria-label={props.ariaLabel}
22
22
  variant="sort"
23
23
  value={props.value}
@@ -32,7 +32,7 @@ export function DirectorySortSelect(props: {
32
32
  {option.label}
33
33
  </option>
34
34
  ))}
35
- </Select>
35
+ </NativeSelect>
36
36
  </label>
37
37
  );
38
38
  }
@@ -1,4 +1,4 @@
1
- import { ToggleButton } from "../Button";
1
+ import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group";
2
2
 
3
3
  /**
4
4
  * Fixed dashboard reporting window.
@@ -124,9 +124,7 @@ function sumHoursIntoSixHours<T extends { date: string }>(args: {
124
124
  // Fill the trailing 28 six-hour buckets when enough hour rows exist.
125
125
  if (args.hours.length >= 24) {
126
126
  const lastHour = args.hours[args.hours.length - 1]?.date;
127
- const endMs = lastHour
128
- ? Date.parse(`${lastHour}:00:00.000Z`)
129
- : Date.now();
127
+ const endMs = lastHour ? Date.parse(`${lastHour}:00:00.000Z`) : Date.now();
130
128
  const end = new Date(endMs);
131
129
  end.setUTCMinutes(0, 0, 0);
132
130
  end.setUTCHours(Math.floor(end.getUTCHours() / 6) * 6, 0, 0, 0);
@@ -155,21 +153,18 @@ export function TimeRangeSelector(props: {
155
153
  value: TimeRangeDays;
156
154
  }) {
157
155
  return (
158
- <div
156
+ <ToggleGroup
159
157
  aria-label="Reporting period"
160
- className="flex items-center gap-1"
161
- role="group"
158
+ onValueChange={([next]) => {
159
+ if (next) props.onChange(Number(next) as TimeRangeDays);
160
+ }}
161
+ value={[String(props.value)]}
162
162
  >
163
163
  {(props.options ?? DEFAULT_OPTIONS).map((days) => (
164
- <ToggleButton
165
- key={days}
166
- onClick={() => props.onChange(days)}
167
- pressed={props.value === days}
168
- variant="segment"
169
- >
164
+ <ToggleGroupItem key={days} value={String(days)}>
170
165
  {timeRangeLabel(days)}
171
- </ToggleButton>
166
+ </ToggleGroupItem>
172
167
  ))}
173
- </div>
168
+ </ToggleGroup>
174
169
  );
175
170
  }
@@ -1,13 +1,11 @@
1
1
  import { ArrowLeft, Menu, X } from "lucide-react";
2
- import { useEffect, useRef, type ReactNode } from "react";
2
+ import { useEffect, useRef, type ReactNode, type RefObject } from "react";
3
3
  import { Link, NavLink } from "react-router";
4
4
 
5
5
  import { getDashboardAgentName } from "../../agentName";
6
- import {
7
- acquireBodyScrollLock,
8
- releaseBodyScrollLock,
9
- } from "../../bodyScrollLock";
10
6
  import { JuniorLogo } from "../JuniorLogo";
7
+ import { Sheet, SheetClose, SheetContent } from "../ui/sheet";
8
+ import { useSheet } from "../ui/useSheet";
11
9
  import {
12
10
  MobileHeaderActionsSlot,
13
11
  MobileHeaderLiveSlot,
@@ -22,15 +20,6 @@ export type DashboardHeaderNavItem = {
22
20
  to: string;
23
21
  };
24
22
 
25
- const focusableSelector = [
26
- "a[href]",
27
- "button:not([disabled])",
28
- "input:not([disabled]):not([type='hidden'])",
29
- "select:not([disabled])",
30
- "textarea:not([disabled])",
31
- "[tabindex]:not([tabindex='-1'])",
32
- ].join(",");
33
-
34
23
  /** Render the primary dashboard shell header and optional mobile nav sheet. */
35
24
  export function DashboardHeader(props: {
36
25
  /** Back target used on mobile conversation detail. */
@@ -50,9 +39,7 @@ export function DashboardHeader(props: {
50
39
  version?: string;
51
40
  workspaceActive: boolean;
52
41
  }) {
53
- const sheetRef = useRef<HTMLDivElement>(null);
54
- const openButtonRef = useRef<HTMLButtonElement>(null);
55
- const previousFocusRef = useRef<HTMLElement | undefined>(undefined);
42
+ const closeRef = useRef<HTMLButtonElement>(null);
56
43
  const onOpenChangeRef = useRef(props.onMobileNavigationOpenChange);
57
44
  onOpenChangeRef.current = props.onMobileNavigationOpenChange;
58
45
  // Conversation chrome is path-driven. Undefined title keeps the loading
@@ -90,56 +77,7 @@ export function DashboardHeader(props: {
90
77
  };
91
78
  closeWhenDesktop();
92
79
  mobile.addEventListener("change", closeWhenDesktop);
93
-
94
- previousFocusRef.current =
95
- document.activeElement instanceof HTMLElement
96
- ? document.activeElement
97
- : (openButtonRef.current ?? undefined);
98
- acquireBodyScrollLock();
99
- const focusFrame = requestAnimationFrame(() => {
100
- sheetRef.current
101
- ?.querySelector<HTMLElement>("[data-mobile-nav-close]")
102
- ?.focus();
103
- });
104
-
105
- const onKeyDown = (event: KeyboardEvent) => {
106
- if (event.key !== "Tab") return;
107
- const sheet = sheetRef.current;
108
- const focusable = Array.from(
109
- sheet?.querySelectorAll<HTMLElement>(focusableSelector) ?? [],
110
- ).filter(
111
- (element) =>
112
- element.tabIndex >= 0 && element.getClientRects().length > 0,
113
- );
114
- const first = focusable.at(0);
115
- const last = focusable.at(-1);
116
- if (!sheet || !first || !last) {
117
- event.preventDefault();
118
- return;
119
- }
120
- const active = document.activeElement;
121
- if (event.shiftKey && (active === first || !sheet.contains(active))) {
122
- event.preventDefault();
123
- last.focus();
124
- } else if (
125
- !event.shiftKey &&
126
- (active === last || !sheet.contains(active))
127
- ) {
128
- event.preventDefault();
129
- first.focus();
130
- }
131
- };
132
-
133
- window.addEventListener("keydown", onKeyDown);
134
- return () => {
135
- cancelAnimationFrame(focusFrame);
136
- mobile.removeEventListener("change", closeWhenDesktop);
137
- window.removeEventListener("keydown", onKeyDown);
138
- releaseBodyScrollLock();
139
- const previousFocus = previousFocusRef.current;
140
- previousFocusRef.current = undefined;
141
- if (previousFocus?.isConnected) previousFocus.focus();
142
- };
80
+ return () => mobile.removeEventListener("change", closeWhenDesktop);
143
81
  }, [props.mobileNavigationOpen]);
144
82
 
145
83
  return (
@@ -168,7 +106,6 @@ export function DashboardHeader(props: {
168
106
  aria-label="Open navigation"
169
107
  className="grid size-10 cursor-pointer place-items-center rounded-lg border-0 bg-transparent text-dashboard-text transition-colors hover:bg-dashboard-fill-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus/70 md:hidden"
170
108
  onClick={() => props.onMobileNavigationOpenChange(true)}
171
- ref={openButtonRef}
172
109
  tabIndex={props.mobileNavigationOpen ? -1 : undefined}
173
110
  type="button"
174
111
  >
@@ -225,22 +162,9 @@ export function DashboardHeader(props: {
225
162
  ) : null}
226
163
  </div>
227
164
  {props.mobileNavigationOpen ? (
228
- <div
229
- aria-label="Navigation"
230
- aria-modal="true"
231
- className="fixed inset-0 z-50 flex flex-col bg-dashboard-bg-elevated md:hidden"
232
- id="mobile-navigation"
233
- onClick={(event) => {
234
- // Close on route taps and sheet actions such as Log out.
235
- if (
236
- event.target instanceof Element &&
237
- event.target.closest("a[href], [data-mobile-nav-dismiss]")
238
- ) {
239
- onOpenChangeRef.current(false);
240
- }
241
- }}
242
- ref={sheetRef}
243
- role="dialog"
165
+ <MobileNavigationSheet
166
+ closeRef={closeRef}
167
+ onClose={() => onOpenChangeRef.current(false)}
244
168
  >
245
169
  <div className="flex items-center justify-between gap-3 border-b border-dashboard-border-subtle bg-dashboard-surface-raised px-3 py-2 pt-[max(0.5rem,env(safe-area-inset-top))]">
246
170
  <Link
@@ -250,15 +174,13 @@ export function DashboardHeader(props: {
250
174
  >
251
175
  {getDashboardAgentName()}
252
176
  </Link>
253
- <button
177
+ <SheetClose
254
178
  aria-label="Close navigation"
255
179
  className="grid size-10 cursor-pointer place-items-center rounded-lg border-0 bg-transparent text-dashboard-text transition-colors hover:bg-dashboard-fill-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus/70"
256
- data-mobile-nav-close
257
- onClick={() => props.onMobileNavigationOpenChange(false)}
258
- type="button"
180
+ ref={closeRef}
259
181
  >
260
182
  <X aria-hidden="true" size={20} strokeWidth={2} />
261
- </button>
183
+ </SheetClose>
262
184
  </div>
263
185
  <div className="flex min-h-0 flex-1 flex-col">
264
186
  <div className="min-h-0 flex-1 overflow-y-auto overscroll-contain p-3">
@@ -307,8 +229,40 @@ export function DashboardHeader(props: {
307
229
  </div>
308
230
  ) : null}
309
231
  </div>
310
- </div>
232
+ </MobileNavigationSheet>
311
233
  ) : null}
312
234
  </header>
313
235
  );
314
236
  }
237
+
238
+ /** Show the mobile navigation in a full-screen shared sheet. */
239
+ function MobileNavigationSheet(props: {
240
+ children: ReactNode;
241
+ closeRef: RefObject<HTMLButtonElement | null>;
242
+ onClose(): void;
243
+ }) {
244
+ const sheet = useSheet(props.onClose, props.closeRef);
245
+ return (
246
+ <Sheet {...sheet.rootProps}>
247
+ <SheetContent
248
+ {...sheet.contentProps}
249
+ aria-label="Navigation"
250
+ className="inset-0 gap-0 bg-dashboard-bg-elevated outline-none data-[side=left]:w-full data-[side=left]:border-r-0 data-[side=left]:sm:max-w-none md:hidden"
251
+ id="mobile-navigation"
252
+ onClick={(event) => {
253
+ // Close on route taps and sheet actions such as Log out.
254
+ if (
255
+ event.target instanceof Element &&
256
+ event.target.closest("a[href], [data-mobile-nav-dismiss]")
257
+ ) {
258
+ props.onClose();
259
+ }
260
+ }}
261
+ showCloseButton={false}
262
+ side="left"
263
+ >
264
+ {props.children}
265
+ </SheetContent>
266
+ </Sheet>
267
+ );
268
+ }
@@ -22,6 +22,7 @@ export type SecondaryNavigationItem = SegmentedNavItem;
22
22
  */
23
23
  export function SecondaryNavigation(props: {
24
24
  ariaLabel: string;
25
+ className?: string;
25
26
  children: ReactNode;
26
27
  items: SecondaryNavigationItem[];
27
28
  }) {
@@ -34,7 +35,7 @@ export function SecondaryNavigation(props: {
34
35
  );
35
36
 
36
37
  return (
37
- <div className="min-w-0">
38
+ <div className={cn("min-w-0", props.className)}>
38
39
  <SecondaryNavigationPortal
39
40
  desktop={
40
41
  <div className={cn(dashboardContainerClass, "min-w-0 px-4 md:px-8")}>
@@ -6,13 +6,13 @@ import { cn } from "../../styles";
6
6
  /**
7
7
  * Own the dashboard visual-viewport shell.
8
8
  *
9
- * Only this component may pin the conversation workspace to visualViewport
9
+ * Only this component may pin pages with separate scroll panes to visualViewport
10
10
  * geometry and write the related CSS variables / body scroll lock.
11
11
  */
12
12
  export function VisualViewportShell(props: {
13
13
  children: ReactNode;
14
14
  className?: string;
15
- /** Pin the shell to the mobile visual viewport (conversation routes). */
15
+ /** Pin pages with separate scroll panes to the mobile visual viewport. */
16
16
  enabled: boolean;
17
17
  style?: CSSProperties;
18
18
  }) {