@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
@@ -0,0 +1,207 @@
1
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2
+ import { Plug } from "lucide-react";
3
+ import type { z } from "zod";
4
+ import {
5
+ connectionListSchema,
6
+ disconnectConnectionResponseSchema,
7
+ startConnectionResponseSchema,
8
+ type connectionSchema,
9
+ } from "@sentry/junior/api/schema";
10
+ import { del, fetchDashboardJson, post } from "../http";
11
+ import { dashboardContainerClass } from "../styles";
12
+ import { getDashboardAgentName } from "../agentName";
13
+ import { useDocumentTitle } from "../documentTitle";
14
+ import { Button } from "../components/Button";
15
+ import { InlineError } from "../components/InlineError";
16
+ import { Card } from "../components/layout/Card";
17
+
18
+ type Connection = z.infer<typeof connectionSchema>;
19
+
20
+ const connectionsQueryKey = ["dashboard", "connections"] as const;
21
+
22
+ function connectionPath(provider: string): string {
23
+ return `/api/connections/${encodeURIComponent(provider)}`;
24
+ }
25
+
26
+ /** Connect and disconnect the viewer's plugin accounts. */
27
+ export function ConnectionsPage() {
28
+ useDocumentTitle("Connected accounts");
29
+ const queryClient = useQueryClient();
30
+ const connectionsQuery = useQuery({
31
+ queryKey: connectionsQueryKey,
32
+ queryFn: ({ signal }) =>
33
+ fetchDashboardJson(connectionListSchema, "/api/connections", signal),
34
+ // OAuth finishes in another tab. Refresh when the viewer returns.
35
+ refetchOnWindowFocus: true,
36
+ retry: false,
37
+ });
38
+ const connectMutation = useMutation({
39
+ mutationFn: (input: { provider: string; popup: Window | null }) =>
40
+ post(startConnectionResponseSchema, connectionPath(input.provider), {}),
41
+ onSuccess: ({ authorizationUrl }, { popup }) => {
42
+ // A blocked popup returns null, so use this tab. A tab the viewer
43
+ // closed means they cancelled; keep them on the dashboard.
44
+ if (!popup) {
45
+ window.location.assign(authorizationUrl);
46
+ return;
47
+ }
48
+ if (popup.closed) return;
49
+ popup.opener = null;
50
+ popup.location.href = authorizationUrl;
51
+ },
52
+ onError: (_error, { popup }) => {
53
+ popup?.close();
54
+ },
55
+ });
56
+ const disconnectMutation = useMutation({
57
+ mutationFn: (connection: Connection) =>
58
+ del(
59
+ disconnectConnectionResponseSchema,
60
+ connectionPath(connection.provider),
61
+ ),
62
+ onSuccess: (_result, connection) => {
63
+ queryClient.setQueryData<{ connections: Connection[] }>(
64
+ connectionsQueryKey,
65
+ (current) =>
66
+ current && {
67
+ connections: current.connections.map((item) =>
68
+ item.provider === connection.provider
69
+ ? {
70
+ provider: item.provider,
71
+ displayName: item.displayName,
72
+ description: item.description,
73
+ connected: false,
74
+ }
75
+ : item,
76
+ ),
77
+ },
78
+ );
79
+ },
80
+ });
81
+ const connections = connectionsQuery.data?.connections ?? [];
82
+ const busy = connectMutation.isPending || disconnectMutation.isPending;
83
+ const pendingProvider = connectMutation.isPending
84
+ ? connectMutation.variables?.provider
85
+ : disconnectMutation.isPending
86
+ ? disconnectMutation.variables?.provider
87
+ : undefined;
88
+ const listFailed = !connectionsQuery.data && connectionsQuery.isError;
89
+ const displayedError = connectMutation.isError
90
+ ? "Could not start the connection. Try again."
91
+ : disconnectMutation.isError
92
+ ? "Could not disconnect the account. Try again."
93
+ : listFailed
94
+ ? "Could not load connected accounts. Try again."
95
+ : undefined;
96
+
97
+ return (
98
+ <div className={`${dashboardContainerClass} px-4 py-8 md:px-8`}>
99
+ <section className="mx-auto w-full max-w-3xl">
100
+ <h1 className="m-0 text-2xl font-bold">Connected accounts</h1>
101
+ <p className="mt-2 mb-0 max-w-2xl text-sm text-dashboard-text-muted">
102
+ Connect an account so {getDashboardAgentName()} can act as you in
103
+ conversations you start here.
104
+ </p>
105
+
106
+ <Card className="mb-0 mt-6" padding="md" variant="raised">
107
+ {displayedError ? (
108
+ <InlineError className={listFailed ? undefined : "mb-4"}>
109
+ {displayedError}
110
+ </InlineError>
111
+ ) : null}
112
+ {listFailed ? null : (
113
+ <div className="space-y-2">
114
+ {connectionsQuery.isPending ? (
115
+ <p className="m-0 text-sm text-dashboard-text-muted">
116
+ Loading connected accounts…
117
+ </p>
118
+ ) : connections.length === 0 ? (
119
+ <p className="m-0 text-sm text-dashboard-text-muted">
120
+ No installed plugins support account connections.
121
+ </p>
122
+ ) : (
123
+ connections.map((connection) => (
124
+ <div
125
+ className="flex items-center gap-3 rounded-md border border-dashboard-border-strong p-3"
126
+ key={connection.provider}
127
+ >
128
+ <Plug
129
+ className="shrink-0 text-dashboard-accent"
130
+ size={16}
131
+ />
132
+ <div className="min-w-0 flex-1">
133
+ <p className="m-0 truncate text-sm font-semibold">
134
+ {connection.displayName}
135
+ </p>
136
+ <p className="mt-1 mb-0 line-clamp-2 break-words text-xs text-dashboard-text-muted">
137
+ {connection.connected ? (
138
+ connection.account ? (
139
+ <>
140
+ Connected as{" "}
141
+ {connection.account.url ? (
142
+ <a
143
+ className="text-dashboard-text"
144
+ href={connection.account.url}
145
+ rel="noreferrer"
146
+ target="_blank"
147
+ >
148
+ {connection.account.label}
149
+ </a>
150
+ ) : (
151
+ connection.account.label
152
+ )}
153
+ </>
154
+ ) : (
155
+ "Connected"
156
+ )
157
+ ) : (
158
+ connection.description
159
+ )}
160
+ </p>
161
+ </div>
162
+ {connection.connected ? (
163
+ <Button
164
+ aria-busy={pendingProvider === connection.provider}
165
+ aria-label={`Disconnect ${connection.displayName}`}
166
+ disabled={busy}
167
+ onClick={() => {
168
+ connectMutation.reset();
169
+ disconnectMutation.mutate(connection);
170
+ }}
171
+ tone="danger"
172
+ >
173
+ {pendingProvider === connection.provider
174
+ ? "Disconnecting…"
175
+ : "Disconnect"}
176
+ </Button>
177
+ ) : (
178
+ <Button
179
+ aria-busy={pendingProvider === connection.provider}
180
+ aria-label={`Connect ${connection.displayName}`}
181
+ disabled={busy}
182
+ onClick={() => {
183
+ disconnectMutation.reset();
184
+ // Open the tab during the click so popup blockers
185
+ // allow it, then point it at the provider.
186
+ const popup = window.open("about:blank", "_blank");
187
+ connectMutation.mutate({
188
+ provider: connection.provider,
189
+ popup,
190
+ });
191
+ }}
192
+ >
193
+ {pendingProvider === connection.provider
194
+ ? "Connecting…"
195
+ : "Connect"}
196
+ </Button>
197
+ )}
198
+ </div>
199
+ ))
200
+ )}
201
+ </div>
202
+ )}
203
+ </Card>
204
+ </section>
205
+ </div>
206
+ );
207
+ }
@@ -110,11 +110,11 @@ export function PersonalTokensPage() {
110
110
 
111
111
  <Card className="mb-0 mt-6" padding="md" variant="raised">
112
112
  {createdToken ? (
113
- <div className="mt-5 rounded border border-emerald-400/40 bg-emerald-400/5 p-3">
113
+ <div className="mt-5 rounded-md border border-emerald-400/40 bg-emerald-400/5 p-3">
114
114
  <p className="mt-0 mb-2 text-sm font-semibold">
115
115
  Copy this token now. It won't be shown again.
116
116
  </p>
117
- <code className="block overflow-x-auto rounded bg-black p-2 text-xs text-emerald-300">
117
+ <code className="block overflow-x-auto rounded-md bg-dashboard-ink p-2 text-xs text-emerald-300">
118
118
  {createdToken.token}
119
119
  </code>
120
120
  <Button
@@ -166,10 +166,10 @@ export function PersonalTokensPage() {
166
166
  ) : (
167
167
  tokens.map((token) => (
168
168
  <div
169
- className="flex items-center gap-3 rounded border border-white/10 p-3"
169
+ className="flex items-center gap-3 rounded-md border border-dashboard-border-strong p-3"
170
170
  key={token.id}
171
171
  >
172
- <KeyRound className="text-[#beaaff]" size={16} />
172
+ <KeyRound className="text-dashboard-accent" size={16} />
173
173
  <div className="min-w-0 flex-1">
174
174
  <p className="m-0 truncate text-sm font-semibold">
175
175
  {token.name}
@@ -31,7 +31,7 @@ export function AdminPage(props: { pages: PluginUserPageLink[] }) {
31
31
  {adminPages.length === 0 ? (
32
32
  <Card padding="md">
33
33
  <div className="flex items-center gap-4">
34
- <div className="grid size-10 shrink-0 place-items-center rounded border border-white/[0.07] bg-white/[0.025] text-dashboard-text-muted">
34
+ <div className="grid size-10 shrink-0 place-items-center rounded-md border border-dashboard-border bg-dashboard-fill-soft text-dashboard-text-muted">
35
35
  <Boxes aria-hidden="true" size={17} />
36
36
  </div>
37
37
  <p className="m-0 text-sm text-dashboard-text-muted">
@@ -50,7 +50,7 @@ export function AdminPage(props: { pages: PluginUserPageLink[] }) {
50
50
  <Card padding="md">
51
51
  <div className="flex items-center gap-3">
52
52
  <div className="min-w-0 flex-1">
53
- <div className="font-mono text-xs uppercase tracking-[0.12em] text-dashboard-text-muted">
53
+ <div className="font-sans font-medium text-xs text-dashboard-text-muted">
54
54
  {page.pluginDisplayName}
55
55
  </div>
56
56
  <h2 className="mt-1 mb-0 font-display text-base font-medium text-dashboard-text">
@@ -45,7 +45,7 @@ export function AutomationCostChart(props: {
45
45
  <h2 className="m-0 font-display text-xl font-medium text-dashboard-text">
46
46
  {formatCostSummary({ total }) || "$0.00"}
47
47
  </h2>
48
- <p className="mt-1 mb-0 font-mono text-xs leading-relaxed text-dashboard-text-muted">
48
+ <p className="mt-1 mb-0 font-sans text-xs leading-relaxed text-dashboard-text-muted">
49
49
  Spend from conversations linked to completed automation executions.
50
50
  </p>
51
51
  </div>
@@ -112,7 +112,7 @@ export function AutomationCostChart(props: {
112
112
  />
113
113
  </ChartSvg>
114
114
  {!hasCost ? (
115
- <div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-mono text-xs text-dashboard-text-muted">
115
+ <div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-sans text-xs text-dashboard-text-muted">
116
116
  No linked spend in this period.
117
117
  </div>
118
118
  ) : null}
@@ -17,6 +17,7 @@ import {
17
17
  import { conversationPath } from "../../conversations/conversationRoutes";
18
18
  import { formatTime, peoplePath } from "../../format";
19
19
  import { TranscriptText } from "../../conversations/TranscriptText";
20
+ import { cn, dashboardInlineLinkClass } from "../../styles";
20
21
 
21
22
  /** Show one automation's instruction and metadata in a right-side slide-out. */
22
23
  export function AutomationDetailsDrawer(props: {
@@ -30,7 +31,7 @@ export function AutomationDetailsDrawer(props: {
30
31
  const { automation } = props;
31
32
  const createdBy = automation.createdByEmail ? (
32
33
  <Link
33
- className="font-semibold text-dashboard-text underline decoration-white/20 underline-offset-2 transition-colors hover:decoration-white/60"
34
+ className={cn("font-semibold", dashboardInlineLinkClass)}
34
35
  to={peoplePath(automation.createdByEmail)}
35
36
  >
36
37
  {automation.ownedByViewer ? "you" : automation.createdBy}
@@ -175,10 +176,7 @@ function AutomationExecutionSummary(props: {
175
176
  return (
176
177
  <div className="text-sm text-dashboard-text-muted">
177
178
  {automation.totalRuns > 0 ? (
178
- <Link
179
- className="text-dashboard-text underline decoration-white/20 underline-offset-2 hover:decoration-white/60"
180
- to={executionsPath}
181
- >
179
+ <Link className={dashboardInlineLinkClass} to={executionsPath}>
182
180
  {runCount} runs / {rangeLabel}
183
181
  </Link>
184
182
  ) : (
@@ -189,10 +187,7 @@ function AutomationExecutionSummary(props: {
189
187
  <span className="mx-2 opacity-45">·</span>
190
188
  <span>
191
189
  {automation.totalRuns > 0 ? (
192
- <Link
193
- className="text-dashboard-text underline decoration-white/20 underline-offset-2 hover:decoration-white/60"
194
- to={executionsPath}
195
- >
190
+ <Link className={dashboardInlineLinkClass} to={executionsPath}>
196
191
  {automation.totalRuns} total
197
192
  </Link>
198
193
  ) : (
@@ -201,7 +196,7 @@ function AutomationExecutionSummary(props: {
201
196
  {automation.lastRunAt ? " · Last run " : " · Never run"}
202
197
  {automation.lastRunAt && automation.lastConversationId ? (
203
198
  <Link
204
- className="text-dashboard-text underline decoration-white/20 underline-offset-2 hover:decoration-white/60"
199
+ className={dashboardInlineLinkClass}
205
200
  to={conversationPath(automation.lastConversationId)}
206
201
  >
207
202
  {formatAutomationRunTime(automation.lastRunAt)}
@@ -425,7 +425,7 @@ export function AutomationEditor(props: {
425
425
  <div className="flex shrink-0 gap-2">
426
426
  <Link
427
427
  to={props.returnPath}
428
- className="inline-flex min-h-11 items-center rounded border border-dashboard-border-emphasis px-3 text-sm text-dashboard-text no-underline sm:min-h-9"
428
+ className="inline-flex min-h-11 items-center rounded-md border border-dashboard-border-emphasis px-3 text-sm text-dashboard-text no-underline sm:min-h-9"
429
429
  >
430
430
  Cancel
431
431
  </Link>
@@ -45,7 +45,7 @@ export function AutomationExecutionChart(props: {
45
45
  <h2 className="m-0 font-display text-xl font-medium text-dashboard-text">
46
46
  {totals.reduce((sum, value) => sum + value, 0).toLocaleString()} runs
47
47
  </h2>
48
- <p className="mt-1 mb-0 font-mono text-xs leading-relaxed text-dashboard-text-muted">
48
+ <p className="mt-1 mb-0 font-sans text-xs leading-relaxed text-dashboard-text-muted">
49
49
  Completed automation executions over time.
50
50
  </p>
51
51
  </div>
@@ -104,7 +104,7 @@ export function AutomationExecutionChart(props: {
104
104
  />
105
105
  </ChartSvg>
106
106
  {!hasExecutions ? (
107
- <div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-mono text-xs text-dashboard-text-muted">
107
+ <div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-sans text-xs text-dashboard-text-muted">
108
108
  No automation executions in this period.
109
109
  </div>
110
110
  ) : null}
@@ -44,7 +44,7 @@ export function AutomationExecutionStatusChart(props: {
44
44
  <h2 className="m-0 font-display text-xl font-medium text-dashboard-text">
45
45
  Executions over time
46
46
  </h2>
47
- <p className="mt-1 mb-0 font-mono text-xs leading-relaxed text-dashboard-text-muted">
47
+ <p className="mt-1 mb-0 font-sans text-xs leading-relaxed text-dashboard-text-muted">
48
48
  {bucketUnit === "hour"
49
49
  ? "Terminal runs for this automation each hour."
50
50
  : bucketUnit === "6hour"
@@ -121,7 +121,7 @@ export function AutomationExecutionStatusChart(props: {
121
121
  />
122
122
  </ChartSvg>
123
123
  {!hasExecutions ? (
124
- <div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-mono text-xs text-dashboard-text-muted">
124
+ <div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-sans text-xs text-dashboard-text-muted">
125
125
  No automation executions in this period.
126
126
  </div>
127
127
  ) : null}
@@ -87,7 +87,7 @@ export function AutomationExecutionsPage(props: { enabled: boolean }) {
87
87
  : "Automation executions could not be loaded. Try again."}
88
88
  </InlineError>
89
89
  <Link
90
- className="mt-3 inline-flex items-center gap-2 font-mono text-xs text-dashboard-text-muted no-underline hover:text-dashboard-text"
90
+ className="mt-3 inline-flex items-center gap-2 font-sans text-xs text-dashboard-text-muted no-underline hover:text-dashboard-text"
91
91
  to={
92
92
  automationId ? automationPath(automationId) : "/automations/list"
93
93
  }
@@ -122,7 +122,7 @@ function AutomationExecutionsView(props: {
122
122
  <>
123
123
  <div>
124
124
  <Link
125
- className="mb-3 inline-flex items-center gap-2 font-mono text-xs text-dashboard-text-muted no-underline hover:text-dashboard-text"
125
+ className="mb-3 inline-flex items-center gap-2 font-sans text-xs text-dashboard-text-muted no-underline hover:text-dashboard-text"
126
126
  to={props.backTo}
127
127
  >
128
128
  <ArrowLeft aria-hidden="true" size={14} />
@@ -177,7 +177,7 @@ function AutomationExecutionsView(props: {
177
177
  <div className="min-w-[36rem]">
178
178
  <div
179
179
  className={cn(
180
- "sticky top-0 z-[1] hidden 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 md:grid",
180
+ "sticky top-0 z-[1] hidden 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 md:grid",
181
181
  EXECUTION_GRID,
182
182
  )}
183
183
  role="row"
@@ -290,7 +290,7 @@ function MetricCell(props: { label: string; value: string }) {
290
290
  <div className="min-w-0 max-md:flex max-md:items-center max-md:gap-2">
291
291
  <div
292
292
  aria-hidden="true"
293
- className="mb-1 hidden font-mono text-2xs uppercase tracking-[0.1em] text-dashboard-text-muted max-md:mb-0 max-md:block"
293
+ className="mb-1 hidden font-sans font-medium text-2xs text-dashboard-text-muted max-md:mb-0 max-md:block"
294
294
  >
295
295
  {props.label}
296
296
  </div>
@@ -58,7 +58,7 @@ export function AutomationRow(props: {
58
58
  <button
59
59
  aria-expanded={props.selected}
60
60
  aria-label={`View automation details: ${automation.title}`}
61
- className="block w-full min-w-0 cursor-pointer rounded border-0 bg-transparent p-0 text-left focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
61
+ className="block w-full min-w-0 cursor-pointer rounded-md border-0 bg-transparent p-0 text-left focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
62
62
  onClick={props.onSelect}
63
63
  type="button"
64
64
  >
@@ -88,7 +88,7 @@ export function AutomationRow(props: {
88
88
  <div className="mt-2.5 flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-dashboard-text-muted">
89
89
  {automation.createdByEmail ? (
90
90
  <Link
91
- className="inline-flex min-w-0 max-w-full items-center gap-2 rounded text-dashboard-text no-underline hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
91
+ className="inline-flex min-w-0 max-w-full items-center gap-2 rounded-md text-dashboard-text no-underline hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
92
92
  title={automation.createdBy}
93
93
  to={peoplePath(automation.createdByEmail)}
94
94
  >
@@ -320,12 +320,12 @@ function AutomationActions(props: {
320
320
  <Link
321
321
  role="menuitem"
322
322
  to={props.editPath}
323
- className="flex min-h-11 items-center rounded px-3 text-sm text-dashboard-text no-underline hover:bg-dashboard-fill-hover focus-visible:outline focus-visible:outline-dashboard-focus"
323
+ className="flex min-h-11 items-center rounded-md px-3 text-sm text-dashboard-text no-underline hover:bg-dashboard-fill-hover focus-visible:outline focus-visible:outline-dashboard-focus"
324
324
  >
325
325
  Edit automation
326
326
  </Link>
327
327
  <button
328
- className="flex min-h-11 w-full cursor-pointer items-center gap-2 rounded border-0 bg-transparent px-3 text-sm text-rose-300 hover:bg-dashboard-fill-hover focus-visible:bg-dashboard-fill-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
328
+ className="flex min-h-11 w-full cursor-pointer items-center gap-2 rounded-md border-0 bg-transparent px-3 text-sm text-rose-300 hover:bg-dashboard-fill-hover focus-visible:bg-dashboard-fill-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
329
329
  onClick={() => {
330
330
  close();
331
331
  props.onDelete();
@@ -156,7 +156,7 @@ export function AutomationRunsPage(props: { enabled: boolean }) {
156
156
  <div className="min-w-[44rem]">
157
157
  <div
158
158
  className={cn(
159
- "sticky top-0 z-[1] hidden 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 md:grid",
159
+ "sticky top-0 z-[1] hidden 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 md:grid",
160
160
  RUN_GRID,
161
161
  )}
162
162
  role="row"
@@ -278,7 +278,7 @@ function MetricCell(props: { label: string; value: string }) {
278
278
  <div className="min-w-0 max-md:flex max-md:items-center max-md:gap-2">
279
279
  <div
280
280
  aria-hidden="true"
281
- className="mb-1 hidden font-mono text-2xs uppercase tracking-[0.1em] text-dashboard-text-muted max-md:mb-0 max-md:block"
281
+ className="mb-1 hidden font-sans font-medium text-2xs text-dashboard-text-muted max-md:mb-0 max-md:block"
282
282
  >
283
283
  {props.label}
284
284
  </div>
@@ -297,7 +297,7 @@ function AutomationKindIcon(props: { kind: AutomationRun["kind"] }) {
297
297
  return (
298
298
  <span
299
299
  aria-label={label}
300
- className="inline-flex size-3.5 shrink-0 items-center justify-center text-cyan-300/80"
300
+ className="inline-flex size-3.5 shrink-0 items-center justify-center text-dashboard-accent/80"
301
301
  role="img"
302
302
  title={label}
303
303
  >
@@ -152,7 +152,7 @@ export function AutomationScheduleFields(props: {
152
152
  })
153
153
  }
154
154
  className={cn(
155
- "min-h-11 cursor-pointer rounded border px-1 text-xs font-medium capitalize focus-visible:outline-2 focus-visible:outline-dashboard-focus sm:min-h-10 sm:text-sm",
155
+ "min-h-11 cursor-pointer rounded-md border px-1 text-xs font-medium capitalize focus-visible:outline-2 focus-visible:outline-dashboard-focus sm:min-h-10 sm:text-sm",
156
156
  value.weekdays?.includes(day)
157
157
  ? "border-dashboard-focus/50 bg-dashboard-focus/10 text-dashboard-focus"
158
158
  : "border-dashboard-border-emphasis text-dashboard-text-muted hover:bg-dashboard-fill-hover",
@@ -77,7 +77,7 @@ export function AutomationVersionsPage(props: { enabled: boolean }) {
77
77
  function BackLink(props: { to: string }) {
78
78
  return (
79
79
  <Link
80
- className="mb-3 inline-flex items-center gap-2 font-mono text-xs text-dashboard-text-muted no-underline hover:text-dashboard-text"
80
+ className="mb-3 inline-flex items-center gap-2 font-sans text-xs text-dashboard-text-muted no-underline hover:text-dashboard-text"
81
81
  to={props.to}
82
82
  >
83
83
  <ArrowLeft aria-hidden="true" size={14} />
@@ -40,7 +40,10 @@ export function CodeActivityChart(props: {
40
40
  const gap = Math.max(1, barWidth * 0.2);
41
41
  const groupWidth = barWidth * 3 + gap * 2;
42
42
  const totals = days.map((day) => day.created + day.merged + day.closed);
43
- const maximum = Math.max(1, ...days.flatMap((day) => [day.created, day.merged, day.closed]));
43
+ const maximum = Math.max(
44
+ 1,
45
+ ...days.flatMap((day) => [day.created, day.merged, day.closed]),
46
+ );
44
47
  const average = activityChartAverage(days.map((day) => day.created));
45
48
  const hasActivity = totals.some((total) => total > 0);
46
49
 
@@ -51,7 +54,7 @@ export function CodeActivityChart(props: {
51
54
  <h2 className="m-0 font-display text-xl font-medium text-dashboard-text">
52
55
  Changes over time
53
56
  </h2>
54
- <p className="mt-1 mb-0 font-mono text-xs leading-relaxed text-dashboard-text-muted">
57
+ <p className="mt-1 mb-0 font-sans text-xs leading-relaxed text-dashboard-text-muted">
55
58
  Code changes created, merged, and closed each day.
56
59
  </p>
57
60
  </div>
@@ -106,9 +109,7 @@ export function CodeActivityChart(props: {
106
109
  date={day.date}
107
110
  summary={`${day.created} created, ${day.merged} merged, ${day.closed} closed`}
108
111
  >
109
- <g
110
- tabIndex={0}
111
- >
112
+ <g tabIndex={0}>
112
113
  {series.map((entry, seriesIndex) => {
113
114
  const height = (entry.value / maximum) * layout.plotHeight;
114
115
  const x = groupX + seriesIndex * (barWidth + gap);
@@ -151,7 +152,7 @@ export function CodeActivityChart(props: {
151
152
  />
152
153
  </ChartSvg>
153
154
  {!hasActivity ? (
154
- <div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-mono text-xs text-dashboard-text-muted">
155
+ <div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-sans text-xs text-dashboard-text-muted">
155
156
  No code changes in this period.
156
157
  </div>
157
158
  ) : null}
@@ -18,7 +18,7 @@ export function CodeChangeRow(props: { change: CodeChangeSummaryReport }) {
18
18
  <div className="truncate font-display text-sm text-dashboard-text">
19
19
  {change.url ? (
20
20
  <a
21
- className="text-inherit no-underline hover:text-cyan-100"
21
+ className="text-inherit no-underline hover:text-dashboard-accent-text"
22
22
  href={change.url}
23
23
  rel="noreferrer"
24
24
  target="_blank"
@@ -96,7 +96,7 @@ function CodeOverview(props: {
96
96
  <col className="w-28" />
97
97
  <col className="w-28" />
98
98
  </colgroup>
99
- <thead className="font-mono text-xs uppercase tracking-[0.1em] text-dashboard-text-muted">
99
+ <thead className="font-sans font-medium text-xs text-dashboard-text-muted">
100
100
  <tr className="border-b border-dashboard-border-subtle">
101
101
  <th className="px-4 py-2.5 font-medium">Repository</th>
102
102
  <th className="px-4 py-2.5 text-right font-medium whitespace-nowrap">
@@ -119,7 +119,7 @@ function CodeOverview(props: {
119
119
  <td className="min-w-0 px-4 py-3">
120
120
  <div className="truncate font-display text-sm text-dashboard-text">
121
121
  <Link
122
- className="text-inherit no-underline hover:text-cyan-100"
122
+ className="text-inherit no-underline hover:text-dashboard-accent-text"
123
123
  to={codeRepositoryPath(repository.id)}
124
124
  >
125
125
  {repository.name}
@@ -129,13 +129,13 @@ function CodeOverview(props: {
129
129
  {repository.provider}
130
130
  </div>
131
131
  </td>
132
- <td className="px-4 py-3 text-right font-mono text-sm whitespace-nowrap text-dashboard-text">
132
+ <td className="px-4 py-3 text-right font-sans text-sm tabular-nums whitespace-nowrap text-dashboard-text">
133
133
  {repository.created}
134
134
  </td>
135
- <td className="px-4 py-3 text-right font-mono text-sm whitespace-nowrap text-dashboard-text">
135
+ <td className="px-4 py-3 text-right font-sans text-sm tabular-nums whitespace-nowrap text-dashboard-text">
136
136
  {formatMergeRate(repository.mergeRate)}
137
137
  </td>
138
- <td className="px-4 py-3 text-right font-mono text-sm whitespace-nowrap text-dashboard-text">
138
+ <td className="px-4 py-3 text-right font-sans text-sm tabular-nums whitespace-nowrap text-dashboard-text">
139
139
  {formatCodeCost(repository.medianCostUsd)}
140
140
  </td>
141
141
  </tr>
@@ -35,13 +35,13 @@ import {
35
35
  RepositoryChangesTab,
36
36
  RepositoryConversationsTab,
37
37
  } from "./RepositoryTabs";
38
+ import { cn, dashboardInlineLinkClass } from "../../styles";
38
39
 
39
40
  const RECENT_ROWS = 5;
40
41
  const DESCRIPTION = "Conversations and code changes from Junior.";
41
42
 
42
43
  /** Inline link style used by `ConversationSummary` location links. */
43
- const inlineLinkClass =
44
- "font-semibold text-dashboard-text underline decoration-white/20 underline-offset-2 transition-colors hover:decoration-white/60";
44
+ const inlineLinkClass = cn("font-semibold", dashboardInlineLinkClass);
45
45
 
46
46
  /** Render one repository tab under the shared header and tab bar. */
47
47
  export function RepositoryPage(props: { tab: CodeRepositoryTab }) {