@sentry/junior-dashboard 0.248.0 → 0.250.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/README.md +3 -0
  2. package/dist/app.js +2 -2
  3. package/dist/client.js +40 -38
  4. package/dist/index.js +2 -2
  5. package/dist/tailwind.css +1 -1
  6. package/package.json +3 -3
  7. package/src/client/App.tsx +19 -5
  8. package/src/client/components/ActorAvatar.tsx +7 -4
  9. package/src/client/components/AutomationCard.tsx +2 -2
  10. package/src/client/components/Button.tsx +12 -11
  11. package/src/client/components/ConversationList.tsx +3 -3
  12. package/src/client/components/ConversationSummary.tsx +2 -1
  13. package/src/client/components/DetailList.tsx +3 -3
  14. package/src/client/components/Drawer.tsx +2 -2
  15. package/src/client/components/EmptyTelemetry.tsx +1 -1
  16. package/src/client/components/Field.tsx +1 -2
  17. package/src/client/components/FilterBar.tsx +6 -6
  18. package/src/client/components/Metric.tsx +4 -4
  19. package/src/client/components/Notice.tsx +7 -7
  20. package/src/client/components/PageContentSkeleton.tsx +2 -2
  21. package/src/client/components/Pagination.tsx +2 -2
  22. package/src/client/components/ParticipantAvatarStack.tsx +4 -4
  23. package/src/client/components/ProfileMenu.tsx +17 -7
  24. package/src/client/components/README.md +122 -58
  25. package/src/client/components/SearchInput.tsx +4 -3
  26. package/src/client/components/SegmentedNav.tsx +10 -17
  27. package/src/client/components/SegmentedTabs.tsx +3 -3
  28. package/src/client/components/Select.tsx +1 -1
  29. package/src/client/components/SelectableRow.tsx +2 -2
  30. package/src/client/components/Skeleton.tsx +1 -1
  31. package/src/client/components/StatusChip.tsx +1 -1
  32. package/src/client/components/StatusDot.tsx +1 -1
  33. package/src/client/components/TextInput.tsx +1 -1
  34. package/src/client/components/Tooltip.tsx +2 -2
  35. package/src/client/components/VersionDriftBanner.tsx +2 -2
  36. package/src/client/components/charts/ActivityChart.tsx +5 -6
  37. package/src/client/components/charts/CacheWrites.tsx +3 -3
  38. package/src/client/components/charts/ChartHeader.tsx +4 -4
  39. package/src/client/components/charts/ChartLegend.tsx +1 -1
  40. package/src/client/components/charts/InputCacheChart.tsx +5 -5
  41. package/src/client/components/charts/WorkspaceUsageChart.tsx +4 -11
  42. package/src/client/components/controls/DirectorySortSelect.tsx +2 -2
  43. package/src/client/components/directory/DirectoryMetric.tsx +2 -2
  44. package/src/client/components/directory/DirectoryRowsSkeleton.tsx +5 -5
  45. package/src/client/components/directory/DirectoryTable.tsx +3 -3
  46. package/src/client/components/layout/CardHeader.tsx +4 -4
  47. package/src/client/components/layout/DashboardChrome.tsx +9 -3
  48. package/src/client/components/layout/DashboardHeader.tsx +7 -7
  49. package/src/client/components/layout/PageHeader.tsx +2 -2
  50. package/src/client/components/layout/SecondaryNavigation.tsx +7 -14
  51. package/src/client/components/layout/SectionIntro.tsx +1 -1
  52. package/src/client/components/layout/SectionTitle.tsx +1 -1
  53. package/src/client/components/metrics/StatCard.tsx +2 -2
  54. package/src/client/conversations/ConversationArchiveNotices.tsx +1 -1
  55. package/src/client/conversations/ConversationBrief.tsx +4 -4
  56. package/src/client/conversations/ConversationChildAgents.tsx +7 -4
  57. package/src/client/conversations/ConversationComposer.tsx +8 -7
  58. package/src/client/conversations/ConversationHeader.tsx +5 -5
  59. package/src/client/conversations/ConversationHeaderActions.tsx +9 -9
  60. package/src/client/conversations/ConversationHomeList.tsx +3 -3
  61. package/src/client/conversations/ConversationListStatusIcon.tsx +1 -1
  62. package/src/client/conversations/ConversationMemories.tsx +4 -4
  63. package/src/client/conversations/ConversationMeta.tsx +10 -10
  64. package/src/client/conversations/ConversationPage.tsx +2 -1
  65. package/src/client/conversations/ConversationSidebar.tsx +6 -6
  66. package/src/client/conversations/ConversationUsageBreakdown.tsx +4 -4
  67. package/src/client/conversations/ConversationWorkspace.tsx +1 -1
  68. package/src/client/conversations/ConversationWorkspaceLoading.tsx +4 -4
  69. package/src/client/conversations/CopyMarkdownButton.tsx +1 -1
  70. package/src/client/conversations/EventUsage.tsx +2 -2
  71. package/src/client/conversations/PendingMailboxStack.tsx +5 -5
  72. package/src/client/conversations/README.md +9 -0
  73. package/src/client/conversations/SubagentTranscriptDrawer.tsx +39 -23
  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 +1 -1
  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/pages/ConnectionsPage.tsx +207 -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/ChartFixtures.tsx +265 -21
  112. package/src/client/pages/dev/ComponentsPage.tsx +40 -146
  113. package/src/client/pages/dev/TranscriptActivityFixtures.tsx +206 -0
  114. package/src/client/pages/locations/LocationDetailPage.tsx +4 -4
  115. package/src/client/pages/locations/LocationDirectory.tsx +1 -1
  116. package/src/client/pages/locations/PrivateActivityCard.tsx +3 -3
  117. package/src/client/pages/memory/MemoryCostChart.tsx +3 -5
  118. package/src/client/pages/memory/MemoryDetailsDrawer.tsx +5 -5
  119. package/src/client/pages/memory/MemoryPage.tsx +15 -15
  120. package/src/client/pages/memory/MemoryTimeline.tsx +3 -5
  121. package/src/client/pages/people/ContributionGrid.tsx +8 -5
  122. package/src/client/pages/people/PersonProfilePage.tsx +3 -6
  123. package/src/client/pages/people/ProfilePluginReports.tsx +2 -2
  124. package/src/client/pages/system/GuardianActivity.tsx +9 -9
  125. package/src/client/pages/system/PluginBarChart.tsx +4 -6
  126. package/src/client/pages/system/PluginDetails.tsx +11 -7
  127. package/src/client/pages/system/PluginPanels.tsx +8 -8
  128. package/src/client/pages/system/PluginReportRecordSet.tsx +14 -14
  129. package/src/client/pages/system/PluginReports.tsx +7 -11
  130. package/src/client/pages/system/SkillInventory.tsx +9 -9
  131. package/src/client/pages/system/SystemActivity.tsx +1 -1
  132. package/src/client/pages/system/SystemPage.tsx +3 -6
  133. package/src/client/pages/system/WorkspaceDetails.tsx +2 -5
  134. package/src/client/pages/system/WorkspaceEditor.tsx +2 -2
  135. package/src/client/pages/system/WorkspaceList.tsx +2 -2
  136. package/src/client/pages/user/PluginUserPage.tsx +3 -3
  137. package/src/client/styles.ts +5 -4
  138. package/src/client/types.ts +2 -0
  139. package/src/tailwind.css +36 -38
@@ -1,55 +1,128 @@
1
1
  # Dashboard UI kit
2
2
 
3
3
  Shared dashboard components live here. Feature pages compose these surfaces
4
- instead of copying Tailwind blocks.
5
-
6
- ## Layers
7
-
8
- 1. **Primitives** — `Button` / `ToggleButton`, `Field`, `TextInput`,
9
- `StatusChip`, `StatusDot`, `Notice`, `FormNotice`, `Select`, `Drawer`, `Tooltip`, `Skeleton`,
10
- `PageContentSkeleton`, `SegmentedTabs`, `SegmentedNav`, and other small reusable controls.
11
- Use the segmented pill track to pick one of a few siblings. Use
12
- `SegmentedTabs` to switch panels in place. Pass the selected value, items,
13
- change handler, and selected panel content. It owns tab/panel labels and
14
- keyboard focus. Use `SegmentedNav` for links between sibling pages. With
15
- `fill`, links share the row width equally while their labels fit. Otherwise
16
- each link keeps its label width. Rows that do not fit scroll sideways. Keep
17
- collection filters in `FilterTabList`. Prefer
18
- `ToggleButton` variants (`pill`, `segment`, `text`) over one-off pressed
19
- styles. Prefer `Field` sizes (`default`, `compact`) over handwritten labels.
20
- Prefer `StatusDot` for dense table or list status markers and `StatusChip`
21
- when the label itself must stay visible. `StatusChip` uses a small dot and
22
- sentence-case text, without a border, fill, or all-caps styling. It is not
23
- an interactive control. Prefer `PageContentSkeleton`
24
- variants over ad-hoc pulse blocks in route modules. Use `PageRouteLoading`
25
- when a standard page must keep its real header above a body skeleton. Use a
26
- feature-owned loading component for distinct geometry, such as conversations.
27
- `ObjectIcon` owns native Ticket, Code change, Automation, Deployment, and
28
- Item icons. It uses the shared type/status mapping and original Octicons
29
- paths. Do not replace it with a provider logo or a status-only icon.
30
- Structural colors live
31
- as slim `dashboard-*` tokens in `src/tailwind.css` (canvas, surface, text,
32
- border, fill, overlay, focus). Prefer those roles over `white/*`, `black/*`,
33
- or hex literals. Status accents stay as Tailwind palette utilities.
34
- 2. **Patterns** — layout, filters, directory tables, charts, metrics.
35
- Activity time-series charts own bucket labels in
36
- `charts/ActivityChart` (`formatActivityDate` for axes,
37
- `ActivityChartTooltip` / `formatActivityTooltipDate` for hover labels).
38
- New day/hour charts should compose those helpers instead of formatting
39
- UTC bucket keys inline.
40
- Conversation mobile geometry is a hard ownership edge (see
41
- `policies/frontend-components.md`):
42
- - `layout/VisualViewportShell` — fixed shell, visualViewport CSS vars, body lock
43
- - `conversations/ChatLayout` — scroll-above-dock frame for reply threads
44
- - `conversations/ComposerDock` — reply bottom pad / dock chrome only
45
- Pages pass children only. Home and create are one landing surface, not a dock,
46
- and must
47
- not reuse `ComposerDock`. Do not invent viewport height, offset, or bottom
48
- safe-area math in page modules.
49
- 3. **Features** — stay under `conversations/` or `pages/` until a surface is
50
- shared by two real callers. Transcript layout stays in `conversations/`;
51
- see [Transcript components](../conversations/README.md) for ownership.
52
- Context panels use the shared `Drawer`, not a separate modal implementation.
4
+ instead of copying Tailwind blocks. See `policies/frontend-components.md` for
5
+ the policy. `pnpm dashboard-style:check` enforces the token rules below.
6
+
7
+ ## Foundations
8
+
9
+ Tokens live in the `@theme` block of `src/tailwind.css`. Light mode does not
10
+ exist yet. Keep every color in a token so a second theme can redefine them.
11
+
12
+ - **Surfaces.** Use the surface ladder, darkest to lightest: `bg`,
13
+ `bg-elevated` (drawers), `surface-panel` (sidebar, cards, bubbles),
14
+ `surface-raised` (buttons, tooltips), `control`, `surface-hover`,
15
+ `surface-active` (selected rows). Use `fill-*` for translucent fills and
16
+ `overlay-*` for scrims. Do not use `white/*` or `black/*` fills, borders, or
17
+ rings, or hex surface colors. Shadows may use black.
18
+ - **Borders.** Use `border-subtle` for hairlines inside a surface,
19
+ `border` for card edges, `border-strong` for shell edges, and
20
+ `border-emphasis` or `border-interactive` for controls and hover.
21
+ - **Text.** Brightest to dimmest: `text-solid`, `text`, `text-subtle`,
22
+ `text-muted`, `text-faint`.
23
+ - **Accent.** Lavender is the one brand accent. Use `accent` for icons,
24
+ active markers, and tinted fills, such as `bg-dashboard-accent/10`. Use
25
+ `accent-text` for links and accent text. Keep Tailwind palette colors for
26
+ status (info, success, warning, error) and for categorical chart, event, and
27
+ subagent colors.
28
+ - **Focus.** Use `outline-dashboard-focus` for keyboard focus. Do not use other
29
+ colors for focus rings.
30
+ - **Type faces.** `font-sans` is the UI face for prose, controls, navigation,
31
+ labels, and names. `font-display` is for headings, record titles, and large
32
+ metric values. `font-mono` is for machine values: code, ids, paths, file
33
+ names, tool names, and raw payloads. Show counts, times, and prices in
34
+ `font-sans tabular-nums`.
35
+ - **Type scale.** Use the named steps `text-2xs` through `text-4xl`. Keep
36
+ `text-2xs` for compact badges and chart annotations.
37
+ - **Labels.** Section, card, field, and column labels use
38
+ `font-sans text-xs font-medium text-dashboard-text-muted` in sentence case.
39
+ Do not use uppercase or letter spacing for labels.
40
+ - **Radius.** Controls and chips use `rounded-md`. Cards, list rows, and panels
41
+ use `rounded-lg`. The composer, menus, and transcript object cards use
42
+ `rounded-xl`. Avatars and count badges use `rounded-full`.
43
+
44
+ ## Shared class helpers
45
+
46
+ `src/client/styles.ts` holds the few class strings that many components share.
47
+
48
+ - `cn` joins class names.
49
+ - `dashboardContainerClass` sets the page width.
50
+ - `dashboardInlineLinkClass` styles a neutral underlined link inside prose.
51
+ - `dashboardInteractiveTextClass` styles muted text that brightens on hover.
52
+ - `dashboardComposerSurfaceClass` styles the composer surface.
53
+
54
+ ## Components
55
+
56
+ ### Primitives
57
+
58
+ - **Actions.** `Button` (`default`, `primary`, `danger`, and `size="icon"`) and
59
+ `ButtonLink`. Form pages use `Button tone="primary"` for their one main save
60
+ action. Use `ToggleButton` variants (`pill`, `segment`, `text`) for pressed
61
+ states, not one-off styles.
62
+ - **Forms.** `Field` (`default`, `compact`), `TextInput`, `TextArea`, `Select`,
63
+ and `SearchInput`. Use `TextInput size="comfortable"` for larger mobile
64
+ controls. Use `TextArea prose` for instructions, not code. `Field error`
65
+ renders an error at `<htmlFor>-error`. The owning control must set
66
+ `aria-invalid` and `aria-describedby`. `Select` matches native text controls.
67
+ - **Status.** Use `StatusDot` for dense table or list markers. Use `StatusChip`
68
+ when the label must stay visible. `StatusChip` is a small dot and
69
+ sentence-case text, with no border or fill. It is not a control.
70
+ - **Messages.** Use `Notice` for compact transient messages and `FormNotice`
71
+ for wrapping form recovery text.
72
+ - **Overlays.** `Drawer` owns dismissal, focus trapping, focus return, scroll
73
+ locking, and panel layout. Context panels use it, not a separate modal.
74
+ `Tooltip` and `IconButtonTooltip` own hover text.
75
+ - **Identity.** `ActorAvatar`, `ParticipantAvatarStack`, and `ObjectIcon`.
76
+ `ObjectIcon` owns native Ticket, Code change, Automation, Deployment, and
77
+ Item icons. It uses the shared type and status mapping and the original
78
+ Octicons paths. Do not replace it with a provider logo or a status-only icon.
79
+ - **Loading.** Use `Skeleton` for placeholder leaves and `PageContentSkeleton`
80
+ variants for page bodies. Use `PageRouteLoading` when a page keeps its real
81
+ header above a body skeleton. Use a feature-owned loading component for
82
+ distinct geometry, such as conversations.
83
+
84
+ ### Navigation
85
+
86
+ The dashboard has three navigation levels. Each has its own look, so the
87
+ levels stay distinct.
88
+
89
+ - **App navigation** in `layout/DashboardHeader` uses pills.
90
+ - **Page links** use `SegmentedNav`, through `layout/SecondaryNavigation`. They
91
+ are underline tabs that sit on the header border. With `fill`, links share
92
+ the row width while their labels fit. Rows that do not fit scroll sideways.
93
+ - **Panel switches** use `SegmentedTabs`, which draws a pill track. Pass the
94
+ selected value, items, change handler, and panel content. It owns tab and
95
+ panel labels and keyboard focus.
96
+
97
+ Keep collection filters in `FilterTabList`.
98
+
99
+ ### Patterns
100
+
101
+ - **Layout.** `layout/` holds the shell chrome, page headers, cards, and
102
+ section titles. `DashboardChrome` owns the sticky header block and its one
103
+ bottom border.
104
+ - **Directory tables and metrics.** `directory/` and `metrics/`.
105
+ - **Charts.** Activity time-series charts own bucket labels in
106
+ `charts/ActivityChart` (`formatActivityDate` for axes,
107
+ `ActivityChartTooltip` and `formatActivityTooltipDate` for hover labels). New
108
+ day or hour charts compose those helpers. They do not format UTC bucket keys
109
+ inline.
110
+ - **Conversation mobile geometry** is a hard ownership edge:
111
+ - `layout/VisualViewportShell` owns the fixed shell, visualViewport CSS
112
+ variables, and body lock.
113
+ - `conversations/ChatLayout` owns the scroll-above-dock frame for reply
114
+ threads.
115
+ - `conversations/ComposerDock` owns the reply bottom pad and dock chrome.
116
+
117
+ Pages pass children only. Home and create are one landing surface, not a
118
+ dock, and must not use `ComposerDock`. Do not add viewport height, offset, or
119
+ bottom safe-area math in page modules.
120
+
121
+ ### Features
122
+
123
+ Feature surfaces stay under `conversations/` or `pages/` until two real callers
124
+ share them. Transcript layout stays in `conversations/`. See
125
+ [Transcript components](../conversations/README.md) for ownership.
53
126
 
54
127
  ## Rules
55
128
 
@@ -58,12 +131,3 @@ instead of copying Tailwind blocks.
58
131
  - Add gallery fixtures under `/dev/<section>` for new or changed shared
59
132
  components. Keep the `/dev` index as the catalog only.
60
133
  - One-off page glue may stay inline when extraction only adds indirection.
61
-
62
- See `policies/frontend-components.md`.
63
-
64
- Use `TextInput size="comfortable"` for larger mobile form controls.
65
- Form pages use `Button tone="primary"` for their one main save action. Use
66
- `TextArea prose` for instructions rather than code. `Field error` renders an
67
- error at `<htmlFor>-error`; the owning control must set `aria-invalid` and
68
- `aria-describedby`. `Select` matches native text controls. Use `FormNotice` for
69
- wrapping form recovery text; use `Notice` for compact transient messages.
@@ -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>