@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
@@ -30,7 +30,7 @@ export function SearchInput(props: {
30
30
  <input
31
31
  aria-label={props.label}
32
32
  className={cn(
33
- "w-full rounded-lg border border-dashboard-border font-mono text-xs text-dashboard-text outline-none transition-colors placeholder:text-dashboard-text-muted hover:border-dashboard-border-emphasis focus:border-dashboard-focus",
33
+ "w-full rounded-md border border-dashboard-border font-sans text-sm text-dashboard-text outline-none transition-colors placeholder:text-dashboard-text-muted/70 hover:border-dashboard-border-emphasis focus:border-dashboard-focus",
34
34
  searchInputClass[size],
35
35
  )}
36
36
  onChange={(event) => props.onChange(event.currentTarget.value)}
@@ -49,7 +49,8 @@ const searchIconClass: Record<SearchInputSize, string> = {
49
49
  };
50
50
 
51
51
  const searchInputClass: Record<SearchInputSize, string> = {
52
- compact: "h-9 bg-dashboard-overlay-soft pr-3 pl-8 focus:ring-1 focus:ring-dashboard-focus/25",
52
+ compact:
53
+ "h-9 bg-dashboard-overlay-soft pr-3 pl-8 focus:ring-1 focus:ring-dashboard-focus/25",
53
54
  default:
54
- "h-10 bg-dashboard-overlay-soft pr-3 pl-9 focus:ring-2 focus:ring-dashboard-focus/20",
55
+ "h-10 bg-dashboard-overlay-soft pr-3 pl-9 focus:ring-1 focus:ring-dashboard-focus/25",
55
56
  };
@@ -2,11 +2,6 @@ import { useLayoutEffect, useRef, useState, type CSSProperties } from "react";
2
2
  import { Link, NavLink, useLocation } from "react-router";
3
3
 
4
4
  import { pathWithSearch } from "../searchParams";
5
- import {
6
- segmentedTabClass,
7
- segmentedTabsFrameClass,
8
- segmentedTabsInsetClass,
9
- } from "./SegmentedTabs";
10
5
  import { cn } from "../styles";
11
6
 
12
7
  export type SegmentedNavItem = {
@@ -84,7 +79,10 @@ function edgeFade(edges: HiddenEdges): CSSProperties | undefined {
84
79
  }
85
80
 
86
81
  /**
87
- * Show links between sibling pages in the segmented pill track.
82
+ * Show links between sibling pages as underline tabs. The current page has
83
+ * a solid underline that sits on the border below the row. App navigation
84
+ * uses pills and in-place panel switches use `SegmentedTabs`, so the three
85
+ * levels stay distinct.
88
86
  *
89
87
  * With `fill`, links share the row width equally while their labels fit.
90
88
  * Otherwise each link keeps its label width. When the links do not fit, the
@@ -139,26 +137,21 @@ export function SegmentedNav(props: {
139
137
 
140
138
  const linkClass = ({ isActive }: { isActive: boolean }) =>
141
139
  cn(
142
- segmentedTabClass(isActive),
143
- "flex items-center justify-center whitespace-nowrap no-underline",
140
+ "relative flex h-10 items-center justify-center whitespace-nowrap font-sans text-sm font-medium no-underline transition-colors after:absolute after:inset-x-0 after:bottom-0 after:h-0.5 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus",
141
+ isActive
142
+ ? "text-dashboard-text after:bg-dashboard-text-solid"
143
+ : "text-dashboard-text-muted hover:text-dashboard-text",
144
144
  props.fill ? "min-w-max flex-[1_0_0]" : "shrink-0",
145
145
  );
146
146
 
147
147
  return (
148
148
  <nav
149
149
  aria-label={props.ariaLabel}
150
- className={cn(
151
- segmentedTabsFrameClass,
152
- "flex min-w-0 max-w-full",
153
- !props.fill && "w-fit",
154
- )}
150
+ className={cn("flex min-w-0 max-w-full", !props.fill && "w-fit")}
155
151
  >
156
- {/* Pad the scrolling row, not the frame, so the last link keeps its
157
- padding when the row is scrolled to the end. */}
158
152
  <div
159
153
  className={cn(
160
- segmentedTabsInsetClass,
161
- "relative flex min-w-0 flex-1 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
154
+ "relative -mx-1 flex gap-5 px-1 min-w-0 flex-1 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
162
155
  )}
163
156
  ref={rowRef}
164
157
  style={edgeFade(edges)}
@@ -3,11 +3,11 @@ import { useId, type ReactNode } from "react";
3
3
  import { cn } from "../styles";
4
4
 
5
5
  /** Style the border and fill of the segmented pill track. */
6
- export const segmentedTabsFrameClass =
6
+ const segmentedTabsFrameClass =
7
7
  "rounded-lg border border-dashboard-border bg-dashboard-surface-panel";
8
8
 
9
9
  /** Style the spacing inside the segmented pill track. */
10
- export const segmentedTabsInsetClass = "gap-1 p-1";
10
+ const segmentedTabsInsetClass = "gap-1 p-1";
11
11
 
12
12
  /** Style one tab or link in the segmented pill track. */
13
13
  export function segmentedTabClass(selected: boolean): string {
@@ -88,7 +88,7 @@ export function SegmentedTabs<const Value extends string>(props: {
88
88
  </div>
89
89
  <div
90
90
  aria-labelledby={`${id}-${props.value}`}
91
- className="min-w-0 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
91
+ className="min-w-0 rounded-md focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
92
92
  id={`${id}-panel`}
93
93
  role="tabpanel"
94
94
  tabIndex={0}
@@ -10,7 +10,7 @@ export function Select({
10
10
  <select
11
11
  {...props}
12
12
  className={cn(
13
- "block min-h-11 w-full min-w-0 rounded border border-dashboard-border-emphasis bg-dashboard-ink px-3 py-2 text-base text-dashboard-text focus:border-dashboard-focus focus:outline-none aria-invalid:border-rose-300 disabled:cursor-not-allowed disabled:opacity-60 sm:min-h-10 sm:text-sm",
13
+ "block min-h-11 w-full min-w-0 rounded-md border border-dashboard-border-emphasis bg-dashboard-ink px-3 py-2 text-base text-dashboard-text focus:border-dashboard-focus focus:outline-none aria-invalid:border-rose-300 disabled:cursor-not-allowed disabled:opacity-60 sm:min-h-10 sm:text-sm",
14
14
  className,
15
15
  )}
16
16
  />
@@ -27,8 +27,8 @@ export function SelectableRow(props: {
27
27
  className={cn(
28
28
  "group cursor-pointer transition-colors",
29
29
  props.selected
30
- ? "bg-cyan-300/[0.045]"
31
- : "hover:bg-white/[0.035] focus-within:bg-white/[0.035]",
30
+ ? "bg-dashboard-accent/[0.045]"
31
+ : "hover:bg-dashboard-fill-soft focus-within:bg-dashboard-fill-soft",
32
32
  props.className,
33
33
  )}
34
34
  onClick={handleClick}
@@ -9,7 +9,7 @@ export function Skeleton(props: { as?: ElementType; className?: string }) {
9
9
  <Component
10
10
  aria-hidden="true"
11
11
  className={cn(
12
- "animate-pulse rounded bg-dashboard-fill-soft motion-reduce:animate-none",
12
+ "animate-pulse rounded-md bg-dashboard-fill-soft motion-reduce:animate-none",
13
13
  props.className,
14
14
  )}
15
15
  />
@@ -13,7 +13,7 @@ export type StatusChipTone =
13
13
  export type StatusChipSize = "default" | "compact";
14
14
 
15
15
  const toneClass: Record<StatusChipTone, string> = {
16
- accent: "text-violet-300",
16
+ accent: "text-dashboard-accent",
17
17
  danger: "text-rose-300",
18
18
  info: "text-cyan-300",
19
19
  neutral: "text-dashboard-text-muted",
@@ -5,7 +5,7 @@ const toneClass: Record<StatusChipTone, string> = {
5
5
  accent: "bg-violet-300",
6
6
  danger: "bg-rose-300",
7
7
  info: "bg-cyan-300",
8
- neutral: "bg-white/30",
8
+ neutral: "bg-dashboard-text-faint",
9
9
  success: "bg-emerald-300",
10
10
  warning: "bg-amber-300",
11
11
  };
@@ -3,7 +3,7 @@ import type { InputHTMLAttributes, TextareaHTMLAttributes } from "react";
3
3
  import { cn } from "../styles";
4
4
 
5
5
  const textControlClassName =
6
- "block w-full rounded border border-dashboard-border-emphasis bg-dashboard-ink px-3 py-2 text-sm text-dashboard-text focus:border-dashboard-focus focus:outline-none aria-invalid:border-rose-300 disabled:cursor-not-allowed disabled:opacity-50";
6
+ "block w-full rounded-md border border-dashboard-border-emphasis bg-dashboard-ink px-3 py-2 font-sans text-sm text-dashboard-text placeholder:text-dashboard-text-muted/70 focus:border-dashboard-focus focus:outline-none focus:ring-1 focus:ring-dashboard-focus/25 aria-invalid:border-rose-300 disabled:cursor-not-allowed disabled:opacity-50";
7
7
 
8
8
  export type TextInputProps = Omit<
9
9
  InputHTMLAttributes<HTMLInputElement>,
@@ -166,7 +166,7 @@ export function Tooltip({
166
166
  className={cn(
167
167
  "z-50 select-text outline-none",
168
168
  className ??
169
- "min-w-36 max-w-lg rounded-md border border-dashboard-border-emphasis bg-dashboard-surface-raised px-3 py-2 font-mono text-xs leading-relaxed text-dashboard-text-muted shadow-2xl shadow-black/70",
169
+ "min-w-36 max-w-lg rounded-md border border-dashboard-border-emphasis bg-dashboard-surface-raised px-3 py-2 font-sans text-xs leading-relaxed text-dashboard-text-muted shadow-2xl shadow-black/70",
170
170
  )}
171
171
  collisionPadding={VIEWPORT_GAP}
172
172
  hideWhenDetached
@@ -176,7 +176,7 @@ export function Tooltip({
176
176
  sideOffset={ANCHOR_GAP}
177
177
  >
178
178
  {label ? (
179
- <div className="mb-1 font-semibold uppercase tracking-[0.1em] text-dashboard-text-muted">
179
+ <div className="mb-1 font-semibold text-dashboard-text-muted">
180
180
  {label}
181
181
  </div>
182
182
  ) : null}
@@ -7,12 +7,12 @@ export function VersionDriftBanner(props: { serverVersion?: string }) {
7
7
  return (
8
8
  <div
9
9
  aria-live="polite"
10
- className="flex flex-wrap items-center justify-center gap-x-3 gap-y-1 border-t border-cyan-300/15 bg-cyan-300/[0.07] px-3 py-1.5 text-center font-sans text-xs text-cyan-100/80"
10
+ className="flex flex-wrap items-center justify-center gap-x-3 gap-y-1 border-t border-dashboard-accent/15 bg-dashboard-accent/[0.07] px-3 py-1.5 text-center font-sans text-xs text-dashboard-accent-text/80"
11
11
  role="status"
12
12
  >
13
13
  <span>The dashboard changed. Refresh to load the latest version.</span>
14
14
  <button
15
- className="cursor-pointer border-0 bg-transparent p-0 font-mono font-semibold text-cyan-100 underline decoration-cyan-300/40 underline-offset-2 hover:text-dashboard-text-solid"
15
+ className="cursor-pointer border-0 bg-transparent p-0 font-sans font-semibold text-dashboard-accent-text underline decoration-dashboard-accent/40 underline-offset-2 hover:text-dashboard-text-solid"
16
16
  onClick={() => window.location.reload()}
17
17
  type="button"
18
18
  >
@@ -65,9 +65,7 @@ export function isActivityHourBucket(date: string): boolean {
65
65
  /** UTC start of a day (`YYYY-MM-DD`) or hour (`YYYY-MM-DDTHH`) bucket key. */
66
66
  export function activityBucketStartMs(date: string): number {
67
67
  return Date.parse(
68
- isActivityHourBucket(date)
69
- ? `${date}:00:00.000Z`
70
- : `${date}T00:00:00.000Z`,
68
+ isActivityHourBucket(date) ? `${date}:00:00.000Z` : `${date}T00:00:00.000Z`,
71
69
  );
72
70
  }
73
71
 
@@ -134,7 +132,7 @@ const ChartSvgScaleContext = createContext(1);
134
132
 
135
133
  /** Shared visual style for SVG chart axis labels. */
136
134
  export const chartAxisLabelClassName =
137
- "fill-white/50 font-mono leading-none text-dashboard-text-muted";
135
+ "fill-dashboard-text-muted font-sans tabular-nums leading-none text-dashboard-text-muted";
138
136
 
139
137
  type ChartAxisLabelProps = Omit<
140
138
  SVGProps<SVGTextElement>,
@@ -167,7 +165,7 @@ export function ChartAxisHtmlLabel(props: {
167
165
  return (
168
166
  <span
169
167
  className={cn(
170
- "font-mono text-2xs leading-none text-dashboard-text-muted",
168
+ "font-sans tabular-nums text-2xs leading-none text-dashboard-text-muted",
171
169
  props.className,
172
170
  )}
173
171
  style={props.style}
@@ -331,7 +329,8 @@ export function activityChartAverage(values: readonly number[]): number {
331
329
  }
332
330
 
333
331
  /** Shared visual style for average-line labels. */
334
- export const chartAverageLabelClassName = "fill-white font-mono leading-none";
332
+ export const chartAverageLabelClassName =
333
+ "fill-dashboard-text-solid font-sans tabular-nums leading-none";
335
334
 
336
335
  /** Solid chip behind average labels so series colors never wash out the text. */
337
336
  const chartAverageLabelChip = "rgba(9, 12, 14, 0.92)";
@@ -21,17 +21,17 @@ export function CacheWrites(props: {
21
21
  const unit = props.bucketUnit === "6hour" ? "6 hours" : props.bucketUnit;
22
22
  return (
23
23
  <details className="border-t border-dashboard-border-subtle px-4 py-3 sm:px-5">
24
- <summary className="cursor-pointer font-mono text-xs text-dashboard-text focus-visible:outline focus-visible:outline-cyan-300">
24
+ <summary className="cursor-pointer font-sans tabular-nums text-xs text-dashboard-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dashboard-focus">
25
25
  Largest cache writes
26
26
  </summary>
27
- <p className="mb-2 mt-3 font-mono text-xs leading-relaxed text-dashboard-text-muted">
27
+ <p className="mb-2 mt-3 font-sans tabular-nums text-xs leading-relaxed text-dashboard-text-muted">
28
28
  Top three by tokens written per {unit}, within the selected time window.
29
29
  Large writes alone do not mean a cache problem.
30
30
  </p>
31
31
  <div className="overflow-x-auto">
32
32
  <table
33
33
  aria-label="Largest cache writes"
34
- className="w-full min-w-80 table-fixed border-collapse font-mono text-xs tabular-nums"
34
+ className="w-full min-w-80 table-fixed border-collapse font-sans text-xs tabular-nums"
35
35
  >
36
36
  <thead className="text-dashboard-text-muted">
37
37
  <tr>
@@ -4,11 +4,11 @@ import { cn } from "../../styles";
4
4
 
5
5
  /** Shared title style for chart headers and period-total values. */
6
6
  export const chartHeaderPrimaryClassName =
7
- "m-0 font-mono text-sm font-medium text-dashboard-text";
7
+ "m-0 font-sans text-sm font-medium text-dashboard-text";
8
8
 
9
9
  /** Shared supporting copy style for chart descriptions and total captions. */
10
10
  export const chartHeaderSecondaryClassName =
11
- "mt-1 mb-0 font-mono text-xs leading-relaxed text-dashboard-text-muted";
11
+ "mt-1 mb-0 font-sans text-xs leading-relaxed text-dashboard-text-muted";
12
12
 
13
13
  /**
14
14
  * Card header used by metric charts.
@@ -26,7 +26,7 @@ export function ChartHeader(props: {
26
26
  return (
27
27
  <div
28
28
  className={cn(
29
- "flex flex-wrap items-start justify-between gap-3 border-b border-white/[0.06] px-4 py-4 sm:px-5",
29
+ "flex flex-wrap items-start justify-between gap-3 border-b border-dashboard-border-subtle px-4 py-4 sm:px-5",
30
30
  props.className,
31
31
  )}
32
32
  >
@@ -42,7 +42,7 @@ export function ChartHeader(props: {
42
42
  <div className={chartHeaderSecondaryClassName}>{totalLabel}</div>
43
43
  </div>
44
44
  ) : props.trailing ? (
45
- <div className="font-mono text-xs text-dashboard-text-muted">
45
+ <div className="font-sans text-xs text-dashboard-text-muted">
46
46
  {props.trailing}
47
47
  </div>
48
48
  ) : null}
@@ -19,7 +19,7 @@ export function ChartLegend(props: {
19
19
  <ul
20
20
  aria-label={props.ariaLabel}
21
21
  className={cn(
22
- "m-0 flex list-none flex-wrap gap-x-4 gap-y-1 p-0 font-mono text-2xs text-dashboard-text-muted",
22
+ "m-0 flex list-none flex-wrap gap-x-4 gap-y-1 p-0 font-sans text-2xs text-dashboard-text-muted",
23
23
  !props.inline && "mt-4",
24
24
  )}
25
25
  >
@@ -69,7 +69,7 @@ export function InputCacheChart(props: {
69
69
  </div>
70
70
  }
71
71
  />
72
- <dl className="m-0 flex flex-wrap gap-x-5 gap-y-2 px-4 py-3 font-mono text-xs sm:px-5">
72
+ <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">
73
73
  {CACHE_INPUT_SERIES.map(({ key, label, color }) => (
74
74
  <div key={key} className="flex items-center gap-2">
75
75
  <dt className="flex items-center gap-1.5 text-dashboard-text-muted">
@@ -87,11 +87,11 @@ export function InputCacheChart(props: {
87
87
  ))}
88
88
  </dl>
89
89
  {summary.activePeriods === 0 ? (
90
- <p className="m-0 px-4 pb-2 font-mono text-xs text-dashboard-text-muted sm:px-5">
90
+ <p className="m-0 px-4 pb-2 font-sans tabular-nums text-xs text-dashboard-text-muted sm:px-5">
91
91
  No input data in this time window.
92
92
  </p>
93
93
  ) : summary.incompletePeriods > 0 ? (
94
- <p className="m-0 px-4 pb-2 font-mono text-xs text-dashboard-text-muted sm:px-5">
94
+ <p className="m-0 px-4 pb-2 font-sans tabular-nums text-xs text-dashboard-text-muted sm:px-5">
95
95
  Missing data in {summary.incompletePeriods} of {summary.activePeriods}{" "}
96
96
  active time intervals. Percentages are unavailable; Tokens shows the
97
97
  reported values.
@@ -183,8 +183,8 @@ export function InputCacheChart(props: {
183
183
  </ChartSvg>
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-mono text-xs text-dashboard-text-muted sm:px-5">
187
- <summary className="cursor-pointer text-dashboard-text focus-visible:outline focus-visible:outline-cyan-300">
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">
188
188
  How to read this
189
189
  </summary>
190
190
  <div className="mt-3 grid gap-2 leading-relaxed">
@@ -11,10 +11,7 @@ import {
11
11
  import { ChartHeader } from "./ChartHeader";
12
12
  import type { TimeRangeDays } from "../controls/TimeRangeSelector";
13
13
  import { Card } from "../layout/Card";
14
- import {
15
- formatActivityChartAverage,
16
- formatCompactNumber,
17
- } from "../../format";
14
+ import { formatActivityChartAverage, formatCompactNumber } from "../../format";
18
15
  import type { WorkspaceUsageDay } from "./workspaceUsage";
19
16
 
20
17
  const USAGE_COLOR = "#a78bfa";
@@ -59,15 +56,11 @@ export function WorkspaceUsageChart(props: {
59
56
  return (
60
57
  <ActivityChartTooltip
61
58
  key={day.date}
62
- content={
63
- <ActivityTooltipRows rows={[["usage", day.count]]} />
64
- }
59
+ content={<ActivityTooltipRows rows={[["usage", day.count]]} />}
65
60
  date={day.date}
66
61
  summary={`${day.count} switches`}
67
62
  >
68
- <g
69
- tabIndex={0}
70
- >
63
+ <g tabIndex={0}>
71
64
  <rect
72
65
  fill={USAGE_COLOR}
73
66
  height={renderedHeight}
@@ -102,7 +95,7 @@ export function WorkspaceUsageChart(props: {
102
95
  />
103
96
  </ChartSvg>
104
97
  {!hasUsage ? (
105
- <div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-mono text-xs text-dashboard-text-muted">
98
+ <div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-sans text-xs text-dashboard-text-muted">
106
99
  No usage in this period.
107
100
  </div>
108
101
  ) : null}
@@ -12,12 +12,12 @@ export function DirectorySortSelect(props: {
12
12
  }) {
13
13
  return (
14
14
  <label className="grid h-9 min-w-0 grid-cols-[auto_minmax(0,1fr)] items-center overflow-hidden rounded-lg border border-dashboard-border-strong bg-dashboard-fill-faint transition-colors hover:border-dashboard-border-emphasis focus-within:border-amber-500/35 focus-within:ring-1 focus-within:ring-amber-500/15">
15
- <span className="flex h-full items-center border-r border-dashboard-border-subtle px-2 font-mono text-xs uppercase tracking-[0.12em] text-dashboard-text-muted">
15
+ <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">
16
16
  Sort
17
17
  </span>
18
18
  <select
19
19
  aria-label={props.ariaLabel}
20
- className="h-full min-w-0 bg-dashboard-control px-2 font-mono text-xs text-dashboard-text-muted outline-none [color-scheme:var(--dashboard-color-scheme,dark)]"
20
+ className="h-full min-w-0 bg-dashboard-control px-2 font-sans text-xs text-dashboard-text-muted outline-none [color-scheme:var(--dashboard-color-scheme,dark)]"
21
21
  value={props.value}
22
22
  onChange={(event) => props.onChange(event.currentTarget.value)}
23
23
  >
@@ -6,11 +6,11 @@ export function DirectoryMetric(props: { label: string; value: ReactNode }) {
6
6
  <div className="justify-self-end text-right max-md:justify-self-stretch max-md:text-left">
7
7
  <div
8
8
  aria-hidden="true"
9
- className="mb-1 hidden font-mono text-xs uppercase tracking-[0.1em] text-dashboard-text-muted max-md:block"
9
+ className="mb-1 hidden font-sans font-medium text-xs text-dashboard-text-muted max-md:block"
10
10
  >
11
11
  {props.label}
12
12
  </div>
13
- <div className="font-display text-xl font-light leading-none text-dashboard-text md:font-mono md:text-xs md:text-dashboard-text-muted">
13
+ <div className="font-display text-xl font-light leading-none text-dashboard-text md:font-sans md:tabular-nums md:text-xs md:text-dashboard-text-muted">
14
14
  <span className="sr-only">{props.label}: </span>
15
15
  {props.value}
16
16
  </div>
@@ -13,19 +13,19 @@ export function DirectoryRowsSkeleton(props: { wideRuntime?: boolean }) {
13
13
  {Array.from({ length: 5 }, (_, index) => (
14
14
  <div
15
15
  aria-hidden="true"
16
- className={`grid min-w-0 ${gridColumns} items-center gap-4 border-b border-white/[0.055] px-4 py-3.5 last:border-b-0 max-md:gap-x-3 max-md:gap-y-4`}
16
+ className={`grid min-w-0 ${gridColumns} items-center gap-4 border-b border-dashboard-border-subtle px-4 py-3.5 last:border-b-0 max-md:gap-x-3 max-md:gap-y-4`}
17
17
  key={index}
18
18
  >
19
19
  <div className="flex min-w-0 items-center gap-3 max-md:col-span-3">
20
- <span className="size-9 shrink-0 rounded bg-white/[0.07]" />
20
+ <span className="size-9 shrink-0 rounded-md bg-dashboard-fill-hover" />
21
21
  <div className="grid min-w-0 flex-1 gap-2">
22
- <span className="h-3.5 w-2/5 rounded bg-white/[0.08]" />
23
- <span className="h-2.5 w-3/5 rounded bg-white/[0.045]" />
22
+ <span className="h-3.5 w-2/5 rounded-md bg-dashboard-fill-strong" />
23
+ <span className="h-2.5 w-3/5 rounded-md bg-dashboard-fill-soft" />
24
24
  </div>
25
25
  </div>
26
26
  {[0, 1, 2].map((metric) => (
27
27
  <span
28
- className="h-4 w-10 justify-self-end rounded bg-white/[0.06]"
28
+ className="h-4 w-10 justify-self-end rounded-md bg-dashboard-fill-hover"
29
29
  key={metric}
30
30
  />
31
31
  ))}
@@ -43,7 +43,7 @@ export function DirectoryTable(props: {
43
43
  <div
44
44
  aria-hidden="true"
45
45
  className={cn(
46
- "grid items-center gap-4 border-b border-dashboard-border-subtle bg-dashboard-overlay-soft px-4 py-2.5 font-mono text-xs uppercase tracking-[0.1em] text-dashboard-text-muted max-md:hidden",
46
+ "grid items-center gap-4 border-b border-dashboard-border-subtle bg-dashboard-overlay-soft px-4 py-2.5 font-sans font-medium text-xs text-dashboard-text-muted max-md:hidden",
47
47
  directoryGridClass,
48
48
  )}
49
49
  >
@@ -84,7 +84,7 @@ export function DirectoryIdentity(props: {
84
84
  <div className="flex min-w-0 items-center gap-3 max-md:col-span-3">
85
85
  <span
86
86
  className={cn(
87
- "grid size-9 shrink-0 place-items-center rounded border border-white/10 transition-colors",
87
+ "grid size-9 shrink-0 place-items-center rounded-md border border-dashboard-border-strong transition-colors",
88
88
  props.iconClassName,
89
89
  )}
90
90
  >
@@ -95,7 +95,7 @@ export function DirectoryIdentity(props: {
95
95
  {props.title}
96
96
  </div>
97
97
  {props.description ? (
98
- <div className="mt-1 truncate font-mono text-xs leading-tight text-dashboard-text-muted">
98
+ <div className="mt-1 truncate font-sans text-xs leading-tight text-dashboard-text-muted">
99
99
  {props.description}
100
100
  </div>
101
101
  ) : null}
@@ -12,22 +12,22 @@ export function CardHeader(props: {
12
12
  return (
13
13
  <div
14
14
  className={cn(
15
- "flex flex-wrap items-start justify-between gap-4 border-b border-white/[0.06] px-4 py-4 sm:px-5",
15
+ "flex flex-wrap items-start justify-between gap-4 border-b border-dashboard-border-subtle px-4 py-4 sm:px-5",
16
16
  props.className,
17
17
  )}
18
18
  >
19
19
  <div>
20
- <h3 className="m-0 font-mono text-xs font-medium uppercase tracking-[0.14em] text-dashboard-text-muted">
20
+ <h3 className="m-0 font-sans text-xs font-medium text-dashboard-text-muted">
21
21
  {props.title}
22
22
  </h3>
23
23
  {props.description ? (
24
- <p className="mt-1 mb-0 font-mono text-xs leading-relaxed text-dashboard-text-muted">
24
+ <p className="mt-1 mb-0 font-sans text-xs leading-relaxed text-dashboard-text-muted">
25
25
  {props.description}
26
26
  </p>
27
27
  ) : null}
28
28
  </div>
29
29
  {props.trailing ? (
30
- <div className="font-mono text-xs text-dashboard-text-muted">
30
+ <div className="font-sans text-xs text-dashboard-text-muted">
31
31
  {props.trailing}
32
32
  </div>
33
33
  ) : null}
@@ -101,7 +101,11 @@ export function DashboardChromeProvider(props: { children: ReactNode }) {
101
101
  );
102
102
  }
103
103
 
104
- /** Sticky shell chrome: primary header, secondary navigation slot, and banner. */
104
+ /**
105
+ * Sticky shell chrome: primary header, secondary navigation slot, and banner.
106
+ * One bottom border closes the header and page links, so page link
107
+ * underlines sit on it. Banners render below that border.
108
+ */
105
109
  export function DashboardChrome(props: {
106
110
  banner?: ReactNode;
107
111
  header: ReactNode;
@@ -113,8 +117,10 @@ export function DashboardChrome(props: {
113
117
 
114
118
  return (
115
119
  <div className="sticky top-0 z-30 bg-dashboard-bg/95 pt-[env(safe-area-inset-top)]">
116
- {props.header}
117
- <div className="hidden md:block" ref={chrome.secondarySlotRef} />
120
+ <div className="border-b border-dashboard-border-strong">
121
+ {props.header}
122
+ <div className="hidden md:block" ref={chrome.secondarySlotRef} />
123
+ </div>
118
124
  {props.banner}
119
125
  </div>
120
126
  );
@@ -74,14 +74,14 @@ export function DashboardHeader(props: {
74
74
  cn(
75
75
  "shrink-0 whitespace-nowrap rounded-lg px-3 py-2 font-sans text-sm font-medium tracking-normal no-underline transition-colors",
76
76
  isActive
77
- ? "bg-cyan-300/[0.1] text-cyan-50"
77
+ ? "bg-dashboard-accent/[0.1] text-dashboard-accent-text"
78
78
  : cn("hover:bg-dashboard-fill-soft", dashboardInteractiveTextClass),
79
79
  );
80
80
  const sheetLinkClass = ({ isActive }: { isActive: boolean }) =>
81
81
  cn(
82
- "rounded-lg px-3 py-3 font-mono text-sm font-medium tracking-normal no-underline transition-colors",
82
+ "rounded-lg px-3 py-3 font-sans text-sm font-medium no-underline transition-colors",
83
83
  isActive
84
- ? "bg-cyan-300/[0.1] text-cyan-50"
84
+ ? "bg-dashboard-accent/[0.1] text-dashboard-accent-text"
85
85
  : cn("hover:bg-dashboard-fill-soft", dashboardInteractiveTextClass),
86
86
  );
87
87
 
@@ -148,7 +148,7 @@ export function DashboardHeader(props: {
148
148
  }, [props.mobileNavigationOpen]);
149
149
 
150
150
  return (
151
- <header className="relative border-b border-dashboard-border-emphasis bg-dashboard-bg">
151
+ <header className="relative bg-dashboard-bg">
152
152
  <div
153
153
  className={cn(
154
154
  props.compact ? "w-full min-w-0" : dashboardContainerClass,
@@ -200,7 +200,7 @@ export function DashboardHeader(props: {
200
200
  )}
201
201
  to="/"
202
202
  >
203
- <span className="truncate font-mono text-sm font-semibold md:hidden">
203
+ <span className="truncate font-sans text-sm font-semibold md:hidden">
204
204
  {getDashboardAgentName()}
205
205
  </span>
206
206
  <span className="hidden md:block">
@@ -252,7 +252,7 @@ export function DashboardHeader(props: {
252
252
  <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))]">
253
253
  <Link
254
254
  aria-label={`${getDashboardAgentName()} home`}
255
- className="min-w-0 truncate font-mono text-sm font-semibold text-inherit no-underline"
255
+ className="min-w-0 truncate font-sans text-sm font-semibold text-inherit no-underline"
256
256
  to="/"
257
257
  >
258
258
  {getDashboardAgentName()}
@@ -307,7 +307,7 @@ export function DashboardHeader(props: {
307
307
  </div>
308
308
  ) : null}
309
309
  {props.version ? (
310
- <p className="m-0 font-mono text-xs text-dashboard-text-muted">
310
+ <p className="m-0 font-sans text-xs text-dashboard-text-muted">
311
311
  junior version {props.version}
312
312
  </p>
313
313
  ) : null}
@@ -36,12 +36,12 @@ export function PageHeader(props: {
36
36
  const actions = props.actions ?? rangeActions;
37
37
 
38
38
  return (
39
- <header className="flex min-w-0 items-center justify-between gap-6 border-b border-white/[0.05] pb-4 sm:pb-6 max-md:flex-col max-md:items-stretch">
39
+ <header className="flex min-w-0 items-center justify-between gap-6 border-b border-dashboard-border-subtle pb-4 sm:pb-6 max-md:flex-col max-md:items-stretch">
40
40
  <div className="min-w-0">
41
41
  <h2 className="m-0 font-display text-3xl font-light leading-none tracking-[-0.03em] text-dashboard-text sm:text-4xl">
42
42
  {props.title}
43
43
  </h2>
44
- <div className="mt-2 max-w-2xl font-mono text-xs leading-relaxed text-dashboard-text-muted sm:text-sm">
44
+ <div className="mt-2 max-w-2xl font-sans text-xs leading-relaxed text-dashboard-text-muted sm:text-sm">
45
45
  {props.description}
46
46
  </div>
47
47
  </div>
@@ -27,31 +27,24 @@ export function SecondaryNavigation(props: {
27
27
  }) {
28
28
  const menuLinkClass = ({ isActive }: { isActive: boolean }) =>
29
29
  cn(
30
- "rounded-lg px-3 py-3 pl-6 font-mono text-sm font-medium no-underline transition-colors",
30
+ "rounded-lg px-3 py-3 pl-6 font-sans text-sm font-medium no-underline transition-colors",
31
31
  isActive
32
- ? "bg-cyan-300/[0.1] text-cyan-50"
33
- : cn("hover:bg-white/[0.035]", dashboardInteractiveTextClass),
32
+ ? "bg-dashboard-accent/[0.1] text-dashboard-accent-text"
33
+ : cn("hover:bg-dashboard-fill-faint", dashboardInteractiveTextClass),
34
34
  );
35
35
 
36
36
  return (
37
37
  <div className="min-w-0">
38
38
  <SecondaryNavigationPortal
39
39
  desktop={
40
- <div className="border-b border-white/[0.06] bg-white/[0.018]">
41
- <div
42
- className={cn(
43
- dashboardContainerClass,
44
- "min-w-0 px-4 py-2 md:px-8",
45
- )}
46
- >
47
- <SegmentedNav ariaLabel={props.ariaLabel} items={props.items} />
48
- </div>
40
+ <div className={cn(dashboardContainerClass, "min-w-0 px-4 md:px-8")}>
41
+ <SegmentedNav ariaLabel={props.ariaLabel} items={props.items} />
49
42
  </div>
50
43
  }
51
44
  mobile={
52
45
  <nav
53
46
  aria-label={props.ariaLabel}
54
- className="mt-3 grid gap-1 border-t border-white/[0.07] pt-3"
47
+ className="mt-3 grid gap-1 border-t border-dashboard-border pt-3"
55
48
  >
56
49
  {props.items.map((item) => (
57
50
  <SiblingPageLink
@@ -63,7 +56,7 @@ export function SecondaryNavigation(props: {
63
56
  </nav>
64
57
  }
65
58
  />
66
- <div className="px-4 pt-4 sm:px-8 md:hidden">
59
+ <div className="border-b border-dashboard-border px-4 pt-2 sm:px-8 md:hidden">
67
60
  <SegmentedNav ariaLabel={props.ariaLabel} fill items={props.items} />
68
61
  </div>
69
62
  {props.children}