@sentry/junior-dashboard 0.259.0 → 0.261.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.
- package/README.md +5 -3
- package/dist/app.js +88 -37
- package/dist/chunks/github-light-QHW3AEMR.js +1 -0
- package/dist/client.js +86 -50
- package/dist/index.js +88 -37
- package/dist/tailwind.css +1 -1
- package/package.json +6 -6
- package/src/client/App.tsx +0 -1
- package/src/client/code.tsx +10 -4
- package/src/client/components/ProfileMenu.tsx +3 -0
- package/src/client/components/README.md +14 -3
- package/src/client/components/Select.tsx +41 -7
- package/src/client/components/ThemeSwitcher.tsx +48 -0
- package/src/client/components/charts/ActivityChart.tsx +6 -7
- package/src/client/components/charts/InputCacheChart.tsx +1 -1
- package/src/client/components/charts/SystemMetricCharts.tsx +3 -3
- package/src/client/components/charts/WorkspaceUsageChart.tsx +1 -1
- package/src/client/components/charts/cache-input.ts +11 -3
- package/src/client/components/controls/DirectorySortSelect.tsx +6 -4
- package/src/client/components/layout/DashboardHeader.tsx +2 -9
- package/src/client/conversations/ConversationBrief.tsx +35 -9
- package/src/client/conversations/ConversationHomeList.tsx +7 -1
- package/src/client/conversations/ConversationMeta.tsx +19 -7
- package/src/client/conversations/ConversationTranscript.tsx +8 -11
- package/src/client/conversations/ConversationWorkspace.tsx +9 -12
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -4
- package/src/client/conversations/EventDetails.tsx +9 -5
- package/src/client/conversations/README.md +6 -0
- package/src/client/conversations/TranscriptContextEventView.tsx +5 -1
- package/src/client/conversations/TranscriptEventView.tsx +48 -9
- package/src/client/conversations/TranscriptMessageView.tsx +6 -1
- package/src/client/conversations/TranscriptStructuredEventView.tsx +4 -2
- package/src/client/conversations/TranscriptText.tsx +28 -20
- package/src/client/conversations/conversationActivityPreview.ts +6 -0
- package/src/client/conversations/eventTranscript.ts +4 -1
- package/src/client/conversations/transcriptMentions.tsx +27 -10
- package/src/client/debouncedValue.ts +14 -0
- package/src/client/pages/automations/AutomationCostChart.tsx +2 -2
- package/src/client/pages/automations/AutomationDetailsDrawer.tsx +4 -1
- package/src/client/pages/automations/AutomationExecutionChart.tsx +2 -2
- package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +3 -3
- package/src/client/pages/automations/AutomationFilters.tsx +4 -3
- package/src/client/pages/code/CodeActivityChart.tsx +4 -4
- package/src/client/pages/code/RepositoryTabs.tsx +3 -2
- package/src/client/pages/dev/ComponentsPage.tsx +11 -3
- package/src/client/pages/dev/ConversationFixture.tsx +29 -0
- package/src/client/pages/dev/SelectFixtures.tsx +62 -0
- package/src/client/pages/locations/LocationActivityChart.tsx +14 -4
- package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +14 -9
- package/src/client/pages/memory/MemoryCostChart.tsx +5 -5
- package/src/client/pages/memory/MemoryDetailsDrawer.tsx +9 -2
- package/src/client/pages/memory/MemoryTimeline.tsx +2 -2
- package/src/client/pages/people/ContributionGrid.tsx +8 -8
- package/src/client/pages/people/PeopleActivityChart.tsx +23 -10
- package/src/client/pages/system/BaseSandboxCard.tsx +76 -0
- package/src/client/pages/system/ConversationActivityChart.tsx +2 -2
- package/src/client/pages/system/PluginBarChart.tsx +12 -6
- package/src/client/pages/system/SandboxSizeSummary.tsx +47 -0
- package/src/client/pages/system/WorkspaceDetails.tsx +11 -1
- package/src/client/pages/system/WorkspaceEditor.tsx +19 -0
- package/src/client/pages/system/WorkspaceFormPage.tsx +28 -19
- package/src/client/pages/system/WorkspaceList.tsx +6 -1
- package/src/client/pages/system/WorkspacesPage.tsx +18 -12
- package/src/client/pages/system/workspaceDraft.ts +8 -0
- package/src/client/theme.ts +52 -0
- package/src/client/types.ts +8 -1
- package/src/mock-reporting/fixtures.ts +48 -35
- package/src/shell.ts +4 -1
- package/src/tailwind.css +109 -0
- package/src/theme.ts +49 -0
- package/src/client/pages/system/BaselineSnapshotCard.tsx +0 -18
|
@@ -22,6 +22,7 @@ import { formatCostSummary, peoplePath } from "../format";
|
|
|
22
22
|
import { pluginUserPagePath } from "../pages/user/PluginUserPage";
|
|
23
23
|
import { cn, dashboardInteractiveTextClass } from "../styles";
|
|
24
24
|
import type { Identity } from "../types";
|
|
25
|
+
import { ThemeSwitcher } from "./ThemeSwitcher";
|
|
25
26
|
|
|
26
27
|
type ProfileMenuProps = {
|
|
27
28
|
identity: Identity;
|
|
@@ -302,6 +303,7 @@ export function ProfileMenu({
|
|
|
302
303
|
>
|
|
303
304
|
Log out
|
|
304
305
|
</button>
|
|
306
|
+
<ThemeSwitcher className="px-3 py-2" />
|
|
305
307
|
</nav>
|
|
306
308
|
);
|
|
307
309
|
}
|
|
@@ -405,6 +407,7 @@ export function ProfileMenu({
|
|
|
405
407
|
id="profile-popover"
|
|
406
408
|
>
|
|
407
409
|
{identityBlock}
|
|
410
|
+
<ThemeSwitcher className="border-b border-dashboard-border-strong px-2.5 py-2" />
|
|
408
411
|
{menuLinks(() => setOpen(false))}
|
|
409
412
|
</div>
|
|
410
413
|
) : null}
|
|
@@ -6,8 +6,18 @@ the policy. `pnpm dashboard-style:check` enforces the token rules below.
|
|
|
6
6
|
|
|
7
7
|
## Foundations
|
|
8
8
|
|
|
9
|
-
Tokens live in the `@theme` block of `src/tailwind.css`.
|
|
10
|
-
|
|
9
|
+
Tokens live in the `@theme` block of `src/tailwind.css`. Keep every color in a
|
|
10
|
+
token so each theme can redefine it.
|
|
11
|
+
|
|
12
|
+
- **Themes.** Dark is the default. The light theme redefines every token under
|
|
13
|
+
`:root[data-theme="light"]`. It also mirrors the light status and chart
|
|
14
|
+
shades (50-400) to their dark pair, so `text-rose-300` stays readable on the
|
|
15
|
+
light canvas. The head script in `src/theme.ts` sets `data-theme` before first
|
|
16
|
+
paint from the saved choice or the system color scheme. `ThemeSwitcher` saves
|
|
17
|
+
the choice for this browser. Use the `light:` variant only for one-off values
|
|
18
|
+
that no token covers, such as provider brand colors.
|
|
19
|
+
- **Charts.** Use `var(--color-*)` for series colors in SVG attributes, not hex
|
|
20
|
+
values, so they follow the theme.
|
|
11
21
|
|
|
12
22
|
- **Surfaces.** Use the surface ladder, darkest to lightest: `bg`,
|
|
13
23
|
`bg-elevated` (drawers), `surface-panel` (sidebar, cards, bubbles),
|
|
@@ -63,7 +73,8 @@ exist yet. Keep every color in a token so a second theme can redefine them.
|
|
|
63
73
|
and `SearchInput`. Use `TextInput size="comfortable"` for larger mobile
|
|
64
74
|
controls. Use `TextArea prose` for instructions, not code. `Field error`
|
|
65
75
|
renders an error at `<htmlFor>-error`. The owning control must set
|
|
66
|
-
`aria-invalid` and `aria-describedby`. `Select`
|
|
76
|
+
`aria-invalid` and `aria-describedby`. `Select` owns the native control and its
|
|
77
|
+
inset arrow. Use its `filter` and `sort` variants for compact controls.
|
|
67
78
|
- **Status.** Use `StatusDot` for dense table or list markers. Use `StatusChip`
|
|
68
79
|
when the label must stay visible. `StatusChip` is a small dot and
|
|
69
80
|
sentence-case text, with no border or fill. It is not a control.
|
|
@@ -1,18 +1,52 @@
|
|
|
1
1
|
import type { SelectHTMLAttributes } from "react";
|
|
2
|
+
import { ChevronDown } from "lucide-react";
|
|
2
3
|
import { cn } from "../styles";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
const selectClassName = {
|
|
6
|
+
form: "min-h-11 rounded-md border border-dashboard-border-emphasis bg-dashboard-ink pl-3 py-2 text-base text-dashboard-text focus:border-dashboard-focus focus:outline-none aria-invalid:border-rose-300 sm:min-h-10 sm:text-sm",
|
|
7
|
+
filter:
|
|
8
|
+
"h-9 rounded-lg border border-dashboard-border-emphasis bg-dashboard-control pl-2 text-sm text-dashboard-text transition-colors hover:border-dashboard-border-interactive focus:border-dashboard-focus focus:outline-none focus:ring-1 focus:ring-dashboard-focus/25",
|
|
9
|
+
sort: "h-full bg-dashboard-control pl-2 font-sans text-xs text-dashboard-text-muted outline-none",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** Keep native choices and position their arrow consistently across the dashboard. */
|
|
5
13
|
export function Select({
|
|
6
14
|
className,
|
|
15
|
+
variant = "form",
|
|
7
16
|
...props
|
|
8
|
-
}: SelectHTMLAttributes<HTMLSelectElement>
|
|
17
|
+
}: SelectHTMLAttributes<HTMLSelectElement> & {
|
|
18
|
+
variant?: keyof typeof selectClassName;
|
|
19
|
+
}) {
|
|
20
|
+
const showArrow = !props.multiple && (props.size ?? 1) <= 1;
|
|
21
|
+
|
|
9
22
|
return (
|
|
10
|
-
<
|
|
11
|
-
{...props}
|
|
23
|
+
<span
|
|
12
24
|
className={cn(
|
|
13
|
-
"block
|
|
14
|
-
|
|
25
|
+
"relative block w-full min-w-0",
|
|
26
|
+
variant === "sort" && "h-full",
|
|
15
27
|
)}
|
|
16
|
-
|
|
28
|
+
>
|
|
29
|
+
<select
|
|
30
|
+
{...props}
|
|
31
|
+
className={cn(
|
|
32
|
+
"block w-full min-w-0 disabled:cursor-not-allowed disabled:opacity-60 [color-scheme:var(--dashboard-color-scheme,dark)]",
|
|
33
|
+
selectClassName[variant],
|
|
34
|
+
showArrow
|
|
35
|
+
? "appearance-none pr-9 forced-colors:appearance-auto"
|
|
36
|
+
: "pr-3",
|
|
37
|
+
className,
|
|
38
|
+
)}
|
|
39
|
+
/>
|
|
40
|
+
{showArrow ? (
|
|
41
|
+
<ChevronDown
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
className={cn(
|
|
44
|
+
"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-dashboard-text-muted forced-colors:hidden",
|
|
45
|
+
props.disabled && "opacity-60",
|
|
46
|
+
)}
|
|
47
|
+
size={16}
|
|
48
|
+
/>
|
|
49
|
+
) : null}
|
|
50
|
+
</span>
|
|
17
51
|
);
|
|
18
52
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Monitor, Moon, Sun, type LucideIcon } from "lucide-react";
|
|
2
|
+
|
|
3
|
+
import type { DashboardThemeChoice } from "../../theme";
|
|
4
|
+
import { cn } from "../styles";
|
|
5
|
+
import { useDashboardThemeChoice } from "../theme";
|
|
6
|
+
import { ToggleButton } from "./Button";
|
|
7
|
+
|
|
8
|
+
const themeChoices = [
|
|
9
|
+
{ Icon: Monitor, label: "System", value: "system" },
|
|
10
|
+
{ Icon: Sun, label: "Light", value: "light" },
|
|
11
|
+
{ Icon: Moon, label: "Dark", value: "dark" },
|
|
12
|
+
] as const satisfies ReadonlyArray<{
|
|
13
|
+
Icon: LucideIcon;
|
|
14
|
+
label: string;
|
|
15
|
+
value: DashboardThemeChoice;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
/** Choose the dashboard theme for this browser: system, light, or dark. */
|
|
19
|
+
export function ThemeSwitcher(props: { className?: string }) {
|
|
20
|
+
const [choice, setChoice] = useDashboardThemeChoice();
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
className={cn("flex items-center justify-between gap-3", props.className)}
|
|
24
|
+
>
|
|
25
|
+
<span className="font-sans text-sm font-semibold text-dashboard-text">
|
|
26
|
+
Theme
|
|
27
|
+
</span>
|
|
28
|
+
<div
|
|
29
|
+
aria-label="Theme"
|
|
30
|
+
className="flex gap-0.5 rounded-lg border border-dashboard-border p-0.5"
|
|
31
|
+
role="group"
|
|
32
|
+
>
|
|
33
|
+
{themeChoices.map(({ Icon, label, value }) => (
|
|
34
|
+
<ToggleButton
|
|
35
|
+
aria-label={`${label} theme`}
|
|
36
|
+
key={value}
|
|
37
|
+
onClick={() => setChoice(value)}
|
|
38
|
+
pressed={choice === value}
|
|
39
|
+
title={label}
|
|
40
|
+
variant="segment"
|
|
41
|
+
>
|
|
42
|
+
<Icon aria-hidden="true" size={14} strokeWidth={2} />
|
|
43
|
+
</ToggleButton>
|
|
44
|
+
))}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -248,7 +248,7 @@ export function ActivityChartGrid(props: {
|
|
|
248
248
|
return (
|
|
249
249
|
<g key={ratio}>
|
|
250
250
|
<line
|
|
251
|
-
stroke="
|
|
251
|
+
stroke="var(--color-dashboard-border-subtle)"
|
|
252
252
|
strokeDasharray="3 5"
|
|
253
253
|
x1={props.layout.left}
|
|
254
254
|
x2={props.layout.width - props.layout.right}
|
|
@@ -332,9 +332,6 @@ export function activityChartAverage(values: readonly number[]): number {
|
|
|
332
332
|
export const chartAverageLabelClassName =
|
|
333
333
|
"fill-dashboard-text-solid font-sans tabular-nums leading-none";
|
|
334
334
|
|
|
335
|
-
/** Solid chip behind average labels so series colors never wash out the text. */
|
|
336
|
-
const chartAverageLabelChip = "rgba(9, 12, 14, 0.92)";
|
|
337
|
-
|
|
338
335
|
/**
|
|
339
336
|
* Opt-in horizontal average across the plotted buckets.
|
|
340
337
|
* Renders a dashed guide plus a compact "value / day" label on the right.
|
|
@@ -380,7 +377,7 @@ export function ActivityChartAverageLine(props: {
|
|
|
380
377
|
return (
|
|
381
378
|
<g aria-label={`average ${label}`} pointerEvents="none">
|
|
382
379
|
<line
|
|
383
|
-
stroke={props.stroke ?? "
|
|
380
|
+
stroke={props.stroke ?? "var(--color-dashboard-text-faint)"}
|
|
384
381
|
strokeDasharray="4 4"
|
|
385
382
|
strokeOpacity={0.85}
|
|
386
383
|
strokeWidth={1.25}
|
|
@@ -389,8 +386,10 @@ export function ActivityChartAverageLine(props: {
|
|
|
389
386
|
y1={y}
|
|
390
387
|
y2={y}
|
|
391
388
|
/>
|
|
389
|
+
{/* Solid chip so series colors never wash out the label text. */}
|
|
392
390
|
<rect
|
|
393
|
-
fill=
|
|
391
|
+
fill="var(--color-dashboard-bg-elevated)"
|
|
392
|
+
fillOpacity={0.92}
|
|
394
393
|
height={chipHeight}
|
|
395
394
|
rx={3}
|
|
396
395
|
width={chipWidth}
|
|
@@ -399,7 +398,7 @@ export function ActivityChartAverageLine(props: {
|
|
|
399
398
|
/>
|
|
400
399
|
<text
|
|
401
400
|
className={chartAverageLabelClassName}
|
|
402
|
-
fill="
|
|
401
|
+
fill="var(--color-dashboard-text-solid)"
|
|
403
402
|
fontSize={fontSize}
|
|
404
403
|
textAnchor="middle"
|
|
405
404
|
x={chipX + chipWidth / 2}
|
|
@@ -49,7 +49,7 @@ function compactDuration(value: number): string {
|
|
|
49
49
|
function tokenChart(): ChartConfig {
|
|
50
50
|
return {
|
|
51
51
|
axisFormat: formatCompactNumber,
|
|
52
|
-
color: "
|
|
52
|
+
color: "var(--color-cyan-400)",
|
|
53
53
|
format: formatCompactNumber,
|
|
54
54
|
metric: "tokens",
|
|
55
55
|
title: "Token usage",
|
|
@@ -61,7 +61,7 @@ function supportingCharts(): ChartConfig[] {
|
|
|
61
61
|
return [
|
|
62
62
|
{
|
|
63
63
|
axisFormat: compactCurrency,
|
|
64
|
-
color: "
|
|
64
|
+
color: "var(--color-amber-400)",
|
|
65
65
|
format: (value) => formatCostSummary({ total: value }),
|
|
66
66
|
metric: "costUsd",
|
|
67
67
|
title: "Model spend",
|
|
@@ -69,7 +69,7 @@ function supportingCharts(): ChartConfig[] {
|
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
axisFormat: compactDuration,
|
|
72
|
-
color: "
|
|
72
|
+
color: "var(--color-violet-400)",
|
|
73
73
|
format: formatDuration,
|
|
74
74
|
metric: "durationMs",
|
|
75
75
|
title: "Runtime",
|
|
@@ -14,7 +14,7 @@ import { Card } from "../layout/Card";
|
|
|
14
14
|
import { formatActivityChartAverage, formatCompactNumber } from "../../format";
|
|
15
15
|
import type { WorkspaceUsageDay } from "./workspaceUsage";
|
|
16
16
|
|
|
17
|
-
const USAGE_COLOR = "
|
|
17
|
+
const USAGE_COLOR = "var(--color-violet-400)";
|
|
18
18
|
|
|
19
19
|
/** Plot successful Workspace switches for one recipe over a trailing window. */
|
|
20
20
|
export function WorkspaceUsageChart(props: {
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import type { ConversationMetricDay } from "@sentry/junior/api/schema";
|
|
2
2
|
|
|
3
3
|
export const CACHE_INPUT_SERIES = [
|
|
4
|
-
{
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
{
|
|
5
|
+
key: "cachedInputTokens",
|
|
6
|
+
label: "From cache",
|
|
7
|
+
color: "var(--color-cyan-400)",
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
key: "cacheCreationTokens",
|
|
11
|
+
label: "Written to cache",
|
|
12
|
+
color: "var(--color-amber-400)",
|
|
13
|
+
},
|
|
14
|
+
{ key: "inputTokens", label: "Uncached", color: "var(--color-violet-400)" },
|
|
7
15
|
] as const;
|
|
8
16
|
|
|
9
17
|
export type CacheInput = Pick<
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { Select } from "../Select";
|
|
2
|
+
|
|
1
3
|
export type DirectorySortOption = {
|
|
2
4
|
label: string;
|
|
3
5
|
value: string;
|
|
4
6
|
};
|
|
5
7
|
|
|
6
|
-
/**
|
|
8
|
+
/** Keep directory sorting consistent with the dashboard's shared select. */
|
|
7
9
|
export function DirectorySortSelect(props: {
|
|
8
10
|
ariaLabel: string;
|
|
9
11
|
onChange(value: string): void;
|
|
@@ -15,9 +17,9 @@ export function DirectorySortSelect(props: {
|
|
|
15
17
|
<span className="flex h-full items-center border-r border-dashboard-border-subtle px-2 font-sans font-medium text-xs text-dashboard-text-muted">
|
|
16
18
|
Sort
|
|
17
19
|
</span>
|
|
18
|
-
<
|
|
20
|
+
<Select
|
|
19
21
|
aria-label={props.ariaLabel}
|
|
20
|
-
|
|
22
|
+
variant="sort"
|
|
21
23
|
value={props.value}
|
|
22
24
|
onChange={(event) => props.onChange(event.currentTarget.value)}
|
|
23
25
|
>
|
|
@@ -30,7 +32,7 @@ export function DirectorySortSelect(props: {
|
|
|
30
32
|
{option.label}
|
|
31
33
|
</option>
|
|
32
34
|
))}
|
|
33
|
-
</
|
|
35
|
+
</Select>
|
|
34
36
|
</label>
|
|
35
37
|
);
|
|
36
38
|
}
|
|
@@ -14,11 +14,7 @@ import {
|
|
|
14
14
|
MobileSecondaryNavigationSlot,
|
|
15
15
|
useRegisterOpenMobileNavigation,
|
|
16
16
|
} from "./DashboardChrome";
|
|
17
|
-
import {
|
|
18
|
-
cn,
|
|
19
|
-
dashboardContainerClass,
|
|
20
|
-
dashboardInteractiveTextClass,
|
|
21
|
-
} from "../../styles";
|
|
17
|
+
import { cn, dashboardInteractiveTextClass } from "../../styles";
|
|
22
18
|
|
|
23
19
|
export type DashboardHeaderNavItem = {
|
|
24
20
|
key: string;
|
|
@@ -37,7 +33,6 @@ const focusableSelector = [
|
|
|
37
33
|
|
|
38
34
|
/** Render the primary dashboard shell header and optional mobile nav sheet. */
|
|
39
35
|
export function DashboardHeader(props: {
|
|
40
|
-
compact?: boolean;
|
|
41
36
|
/** Back target used on mobile conversation detail. */
|
|
42
37
|
mobileBackTo?: string;
|
|
43
38
|
mobileNavigationOpen: boolean;
|
|
@@ -151,12 +146,10 @@ export function DashboardHeader(props: {
|
|
|
151
146
|
<header className="relative bg-dashboard-bg">
|
|
152
147
|
<div
|
|
153
148
|
className={cn(
|
|
154
|
-
|
|
155
|
-
"grid grid-cols-[2.5rem_minmax(0,1fr)_2.5rem] items-center gap-x-2 px-3 py-2 md:gap-x-5 md:gap-y-3 md:px-4 md:py-4",
|
|
149
|
+
"grid w-full min-w-0 grid-cols-[2.5rem_minmax(0,1fr)_2.5rem] items-center gap-x-2 px-3 py-2 md:gap-x-5 md:gap-y-3 md:px-7 md:py-3",
|
|
156
150
|
props.profile
|
|
157
151
|
? "md:grid-cols-[auto_minmax(0,1fr)_auto]"
|
|
158
152
|
: "md:grid-cols-[auto_minmax(0,1fr)]",
|
|
159
|
-
props.compact ? "md:px-7 md:py-3" : "md:px-8",
|
|
160
153
|
)}
|
|
161
154
|
>
|
|
162
155
|
{conversationMode && props.mobileBackTo ? (
|
|
@@ -5,6 +5,7 @@ import type { ConversationDetailReport } from "@sentry/junior/api/schema";
|
|
|
5
5
|
import { Detail, DetailList } from "../components/DetailList";
|
|
6
6
|
import { StatusChip, type StatusChipTone } from "../components/StatusChip";
|
|
7
7
|
import { formatTime } from "../format";
|
|
8
|
+
import { MentionPeopleProvider, renderMentionText } from "./transcriptMentions";
|
|
8
9
|
|
|
9
10
|
type ConversationBriefReport = NonNullable<ConversationDetailReport["brief"]>;
|
|
10
11
|
|
|
@@ -24,13 +25,24 @@ const outcomeTones: Record<BriefContent["outcome"]["status"], StatusChipTone> =
|
|
|
24
25
|
export function ConversationBrief(props: {
|
|
25
26
|
brief: ConversationBriefReport;
|
|
26
27
|
variant?: "summary" | "full";
|
|
28
|
+
}) {
|
|
29
|
+
return (
|
|
30
|
+
<MentionPeopleProvider mentions={props.brief.mentions ?? []}>
|
|
31
|
+
<BriefContentView brief={props.brief} variant={props.variant} />
|
|
32
|
+
</MentionPeopleProvider>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function BriefContentView(props: {
|
|
37
|
+
brief: ConversationBriefReport;
|
|
38
|
+
variant?: "summary" | "full";
|
|
27
39
|
}) {
|
|
28
40
|
const { content } = props.brief;
|
|
29
41
|
if (props.variant === "summary") {
|
|
30
42
|
return (
|
|
31
43
|
<div className="grid min-w-0 gap-4">
|
|
32
44
|
<p className="m-0 text-sm leading-relaxed text-dashboard-text">
|
|
33
|
-
{content.summary}
|
|
45
|
+
<BriefText text={content.summary} />
|
|
34
46
|
</p>
|
|
35
47
|
{content.openDecisions.length > 0 ? (
|
|
36
48
|
<div className="grid gap-2">
|
|
@@ -58,12 +70,16 @@ export function ConversationBrief(props: {
|
|
|
58
70
|
</span>
|
|
59
71
|
</div>
|
|
60
72
|
<p className="m-0 text-sm leading-relaxed text-dashboard-text">
|
|
61
|
-
{content.summary}
|
|
73
|
+
<BriefText text={content.summary} />
|
|
62
74
|
</p>
|
|
63
75
|
<BriefSection title="Intent & outcome">
|
|
64
76
|
<DetailList>
|
|
65
|
-
<Detail label="Intent">
|
|
66
|
-
|
|
77
|
+
<Detail label="Intent">
|
|
78
|
+
<BriefText text={content.intent} />
|
|
79
|
+
</Detail>
|
|
80
|
+
<Detail label="Outcome">
|
|
81
|
+
<BriefText text={content.outcome.text} />
|
|
82
|
+
</Detail>
|
|
67
83
|
</DetailList>
|
|
68
84
|
</BriefSection>
|
|
69
85
|
{content.decisions.length || content.openDecisions.length ? (
|
|
@@ -169,11 +185,13 @@ function BriefDecisions(props: { decisions: BriefContent["decisions"] }) {
|
|
|
169
185
|
>
|
|
170
186
|
{decision.kind}
|
|
171
187
|
</StatusChip>
|
|
172
|
-
<span className="min-w-0">
|
|
188
|
+
<span className="min-w-0">
|
|
189
|
+
<BriefText text={decision.text} />
|
|
190
|
+
</span>
|
|
173
191
|
</div>
|
|
174
192
|
{decision.by ? (
|
|
175
193
|
<div className="mt-0.5 text-xs text-dashboard-text-muted">
|
|
176
|
-
by · {decision.by}
|
|
194
|
+
by · <BriefText text={decision.by} />
|
|
177
195
|
</div>
|
|
178
196
|
) : null}
|
|
179
197
|
</li>
|
|
@@ -189,10 +207,12 @@ function BriefOpenDecisions(props: {
|
|
|
189
207
|
<ul className="m-0 grid list-none gap-2 p-0">
|
|
190
208
|
{props.decisions.map((decision, index) => (
|
|
191
209
|
<li key={`${decision.text}:${index}`}>
|
|
192
|
-
<div>
|
|
210
|
+
<div>
|
|
211
|
+
<BriefText text={decision.text} />
|
|
212
|
+
</div>
|
|
193
213
|
{decision.owner ? (
|
|
194
214
|
<div className="mt-1 text-xs text-dashboard-text-muted">
|
|
195
|
-
Owner: {decision.owner}
|
|
215
|
+
Owner: <BriefText text={decision.owner} />
|
|
196
216
|
</div>
|
|
197
217
|
) : null}
|
|
198
218
|
</li>
|
|
@@ -205,12 +225,18 @@ function BriefList(props: { items: string[] }) {
|
|
|
205
225
|
return (
|
|
206
226
|
<ul className="my-0 grid gap-1.5 pl-4">
|
|
207
227
|
{props.items.map((item, index) => (
|
|
208
|
-
<li key={`${item}:${index}`}>
|
|
228
|
+
<li key={`${item}:${index}`}>
|
|
229
|
+
<BriefText text={item} />
|
|
230
|
+
</li>
|
|
209
231
|
))}
|
|
210
232
|
</ul>
|
|
211
233
|
);
|
|
212
234
|
}
|
|
213
235
|
|
|
236
|
+
function BriefText(props: { text: string }) {
|
|
237
|
+
return <>{renderMentionText(props.text, "brief")}</>;
|
|
238
|
+
}
|
|
239
|
+
|
|
214
240
|
function displayLabel(value: string): string {
|
|
215
241
|
return value.replaceAll("_", " ");
|
|
216
242
|
}
|
|
@@ -22,6 +22,7 @@ import { ConversationArchiveNotices } from "./ConversationArchiveNotices";
|
|
|
22
22
|
import { ConversationListStatusIcon } from "./ConversationListStatusIcon";
|
|
23
23
|
import { conversationPath } from "./conversationRoutes";
|
|
24
24
|
import { useArchiveConversation } from "./queries";
|
|
25
|
+
import { MentionText } from "./transcriptMentions";
|
|
25
26
|
import {
|
|
26
27
|
buildConversationSections,
|
|
27
28
|
type ConversationSection,
|
|
@@ -265,7 +266,12 @@ function ConversationCard(props: {
|
|
|
265
266
|
</div>
|
|
266
267
|
{conversation.activityPreview ? (
|
|
267
268
|
<p className="relative z-[1] m-0 line-clamp-2 font-sans text-sm leading-relaxed text-dashboard-text-subtle pointer-events-none">
|
|
268
|
-
|
|
269
|
+
<MentionText
|
|
270
|
+
mentions={conversation.activityPreview.mentions ?? []}
|
|
271
|
+
text={formatConversationActivityPreview(
|
|
272
|
+
conversation.activityPreview.text,
|
|
273
|
+
)}
|
|
274
|
+
/>
|
|
269
275
|
</p>
|
|
270
276
|
) : (
|
|
271
277
|
<p className="relative z-[1] m-0 font-sans text-sm leading-relaxed text-dashboard-text-muted pointer-events-none">
|
|
@@ -167,7 +167,7 @@ export function ConversationSidebarAnnotations(props: {
|
|
|
167
167
|
{useMobileFacepile ? (
|
|
168
168
|
<SidebarAnnotationIconFacepile
|
|
169
169
|
annotations={mobileFacepileAnnotations}
|
|
170
|
-
// Discs sit on the sidebar panel / row surface
|
|
170
|
+
// Discs sit on the sidebar panel / row surface.
|
|
171
171
|
cutoutColor="var(--color-dashboard-surface-panel)"
|
|
172
172
|
/>
|
|
173
173
|
) : (
|
|
@@ -343,21 +343,33 @@ type SidebarAnnotationIconName = NonNullable<
|
|
|
343
343
|
>;
|
|
344
344
|
|
|
345
345
|
const SIDEBAR_ICON_PRESENTATION = {
|
|
346
|
-
"circle-dot": {
|
|
346
|
+
"circle-dot": {
|
|
347
|
+
className: "text-[#3fb950] light:text-[#1a7f37]",
|
|
348
|
+
Icon: CircleDot,
|
|
349
|
+
label: "Open",
|
|
350
|
+
},
|
|
347
351
|
"circle-dashed": {
|
|
348
|
-
className: "text-[#8c959f]",
|
|
352
|
+
className: "text-[#8c959f] light:text-[#59636e]",
|
|
349
353
|
Icon: CircleDashed,
|
|
350
354
|
label: "Draft",
|
|
351
355
|
},
|
|
352
|
-
"circle-x": {
|
|
353
|
-
|
|
356
|
+
"circle-x": {
|
|
357
|
+
className: "text-[#f85149] light:text-[#cf222e]",
|
|
358
|
+
Icon: CircleX,
|
|
359
|
+
label: "Closed",
|
|
360
|
+
},
|
|
361
|
+
"git-merge": {
|
|
362
|
+
className: "text-[#a371f7] light:text-[#8250df]",
|
|
363
|
+
Icon: GitMerge,
|
|
364
|
+
label: "Merged",
|
|
365
|
+
},
|
|
354
366
|
"git-pull-request": {
|
|
355
|
-
className: "text-[#3fb950]",
|
|
367
|
+
className: "text-[#3fb950] light:text-[#1a7f37]",
|
|
356
368
|
Icon: GitPullRequest,
|
|
357
369
|
label: "Open pull request",
|
|
358
370
|
},
|
|
359
371
|
"triangle-alert": {
|
|
360
|
-
className: "text-[#d29922]",
|
|
372
|
+
className: "text-[#d29922] light:text-[#9a6700]",
|
|
361
373
|
Icon: TriangleAlert,
|
|
362
374
|
label: "Needs attention",
|
|
363
375
|
},
|
|
@@ -43,7 +43,6 @@ import { transcriptEmptyClass } from "./transcriptStyles";
|
|
|
43
43
|
import { entryMatchesSearch, useTranscriptSearch } from "./transcriptSearch";
|
|
44
44
|
import { TranscriptRow, TranscriptRows } from "./TranscriptRows";
|
|
45
45
|
import { TranscriptTimestampProvider } from "./TranscriptTimestamp";
|
|
46
|
-
import { TranscriptMentionPeopleProvider } from "./transcriptMentions";
|
|
47
46
|
|
|
48
47
|
type TranscriptEntry = ReturnType<typeof groupTranscriptMessages>[number];
|
|
49
48
|
type TranscriptContextEntry = Extract<TranscriptEntry, { kind: "context" }>;
|
|
@@ -86,16 +85,14 @@ export const ConversationTranscriptView = memo(
|
|
|
86
85
|
|
|
87
86
|
return (
|
|
88
87
|
<TranscriptTimestampProvider>
|
|
89
|
-
<
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
</section>
|
|
98
|
-
</TranscriptMentionPeopleProvider>
|
|
88
|
+
<section className="min-w-0 pt-1">
|
|
89
|
+
<SegmentEvents
|
|
90
|
+
onOpenSubagentTranscript={props.onOpenSubagentTranscript}
|
|
91
|
+
conversation={props.conversation}
|
|
92
|
+
messages={messages}
|
|
93
|
+
responding={props.responding}
|
|
94
|
+
/>
|
|
95
|
+
</section>
|
|
99
96
|
</TranscriptTimestampProvider>
|
|
100
97
|
);
|
|
101
98
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InputImage } from "@sentry/junior/api/schema";
|
|
2
|
-
import {
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
3
|
import { Globe2, LockKeyhole } from "lucide-react";
|
|
4
4
|
import { useNavigate, useParams } from "react-router";
|
|
5
5
|
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
} from "./queries";
|
|
18
18
|
import { conversationPath, NEW_CONVERSATION_PATH } from "./conversationRoutes";
|
|
19
19
|
import { buildConversations, getDashboardTimeZone } from "../format";
|
|
20
|
+
import { useDebouncedValue } from "../debouncedValue";
|
|
20
21
|
import type { Conversation } from "../types";
|
|
21
|
-
import { cn, dashboardContainerClass } from "../styles";
|
|
22
22
|
import { ConversationPage } from "./ConversationPage";
|
|
23
23
|
import { useConversationFinishedIndicators } from "./useConversationFinishedIndicators";
|
|
24
24
|
|
|
@@ -28,7 +28,7 @@ const CONVERSATION_PAGE_SIZE = 20;
|
|
|
28
28
|
export function ConversationWorkspace() {
|
|
29
29
|
const [page, setPage] = useState(1);
|
|
30
30
|
const [query, setQuery] = useState("");
|
|
31
|
-
const search =
|
|
31
|
+
const search = useDebouncedValue(query.trim());
|
|
32
32
|
const params = useParams();
|
|
33
33
|
const navigate = useNavigate();
|
|
34
34
|
const selectedId = params.conversationId;
|
|
@@ -90,12 +90,7 @@ export function ConversationWorkspace() {
|
|
|
90
90
|
|
|
91
91
|
if (home) {
|
|
92
92
|
return (
|
|
93
|
-
<main
|
|
94
|
-
className={cn(
|
|
95
|
-
dashboardContainerClass,
|
|
96
|
-
"h-full min-h-0 overflow-y-auto overscroll-contain px-4 py-4 sm:px-6 sm:py-6",
|
|
97
|
-
)}
|
|
98
|
-
>
|
|
93
|
+
<main className="h-full min-h-0 w-full overflow-y-auto overscroll-contain px-4 py-4 sm:px-6 sm:py-6">
|
|
99
94
|
<div className="mx-auto grid w-full max-w-6xl gap-8">
|
|
100
95
|
{createView}
|
|
101
96
|
<section aria-label="Conversations" className="grid gap-3">
|
|
@@ -192,25 +187,27 @@ function NewConversationView(props: {
|
|
|
192
187
|
footerStart={
|
|
193
188
|
<div
|
|
194
189
|
aria-label="Conversation visibility"
|
|
195
|
-
className="inline-flex items-center gap-
|
|
190
|
+
className="inline-flex shrink-0 items-center gap-0.5 rounded-lg border border-dashboard-border bg-dashboard-surface-panel p-0.5"
|
|
196
191
|
role="group"
|
|
197
192
|
>
|
|
198
193
|
<ToggleButton
|
|
194
|
+
className="min-w-20 gap-1.5"
|
|
199
195
|
onClick={() => setVisibility("public")}
|
|
200
196
|
pressed={isPublic}
|
|
201
197
|
type="button"
|
|
202
198
|
variant="segment"
|
|
203
199
|
>
|
|
204
|
-
<Globe2 aria-hidden="true" className="
|
|
200
|
+
<Globe2 aria-hidden="true" className="size-3" />
|
|
205
201
|
Public
|
|
206
202
|
</ToggleButton>
|
|
207
203
|
<ToggleButton
|
|
204
|
+
className="min-w-20 gap-1.5"
|
|
208
205
|
onClick={() => setVisibility("private")}
|
|
209
206
|
pressed={!isPublic}
|
|
210
207
|
type="button"
|
|
211
208
|
variant="segment"
|
|
212
209
|
>
|
|
213
|
-
<LockKeyhole aria-hidden="true" className="
|
|
210
|
+
<LockKeyhole aria-hidden="true" className="size-3" />
|
|
214
211
|
Private
|
|
215
212
|
</ToggleButton>
|
|
216
213
|
</div>
|
|
@@ -58,10 +58,7 @@ function ConversationHomeLoading() {
|
|
|
58
58
|
<main
|
|
59
59
|
aria-busy="true"
|
|
60
60
|
aria-live="polite"
|
|
61
|
-
className=
|
|
62
|
-
dashboardContainerClass,
|
|
63
|
-
"h-full min-h-0 overflow-y-auto overscroll-contain px-4 py-4 sm:px-6 sm:py-6",
|
|
64
|
-
)}
|
|
61
|
+
className="h-full min-h-0 w-full overflow-y-auto overscroll-contain px-4 py-4 sm:px-6 sm:py-6"
|
|
65
62
|
role="status"
|
|
66
63
|
>
|
|
67
64
|
<span className="sr-only">Loading conversations</span>
|