@sentry/junior-dashboard 0.277.0 → 0.278.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/dist/app.js +30 -41
- package/dist/client.js +90 -90
- package/dist/index.js +30 -41
- package/dist/tailwind.css +1 -1
- package/package.json +5 -4
- package/src/app.ts +1 -1
- package/src/client/App.tsx +9 -4
- package/src/client/components/DetailsPanel.tsx +1 -1
- package/src/client/components/ui/resizable.tsx +50 -0
- package/src/client/conversations/AutoArchiveToggle.tsx +50 -14
- package/src/client/conversations/ConversationHeader.tsx +6 -2
- package/src/client/conversations/ConversationHeaderActions.tsx +4 -0
- package/src/client/conversations/ConversationMeta.tsx +2 -7
- package/src/client/conversations/ConversationPage.tsx +155 -72
- package/src/client/conversations/ConversationSidebar.tsx +2 -1
- package/src/client/conversations/ConversationTranscript.tsx +11 -8
- package/src/client/conversations/ConversationWorkspace.tsx +129 -41
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +17 -3
- package/src/client/conversations/EventDetails.tsx +2 -62
- package/src/client/conversations/TranscriptActivityGroup.tsx +36 -14
- package/src/client/conversations/TranscriptContextEventView.tsx +7 -18
- package/src/client/conversations/TranscriptRailEvent.tsx +0 -8
- package/src/client/conversations/TranscriptView.tsx +89 -20
- package/src/client/conversations/conversationSidebarLayout.ts +61 -0
- package/src/client/conversations/eventLog.ts +2 -31
- package/src/client/conversations/eventTranscript.ts +9 -42
- package/src/client/conversations/queries.ts +76 -15
- package/src/client/conversations/transcript.ts +46 -16
- package/src/client/conversations/transcriptBottomPinning.ts +40 -24
- package/src/client/conversations/transcriptReadPosition.ts +31 -7
- package/src/client/conversations/transcriptRenderModel.ts +1 -14
- package/src/client/conversations/transcriptSearch.tsx +3 -11
- package/src/client/conversations/transcriptUnreadHistory.ts +248 -0
- package/src/client/http.ts +5 -5
- package/src/client/markdownExport.ts +32 -42
- package/src/client/pages/memory/MemoryDetails.tsx +6 -33
- package/src/client/pages/memory/MemoryPage.tsx +7 -92
- package/src/client/pages/memory/MemoryTimeline.tsx +16 -32
- package/src/client/pages/memory/memoryDashboard.ts +2 -38
- package/src/client/pages/memory/memoryRecord.ts +1 -6
- package/src/client/types.ts +18 -33
- package/src/conversation-cache.ts +6 -8
- package/src/mock-reporting/fixtures.ts +23 -33
- package/src/mock-reporting/routes.ts +3 -1
- package/src/tailwind.css +30 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-dashboard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.278.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -34,16 +34,17 @@
|
|
|
34
34
|
"react-dom": "^19.2.6",
|
|
35
35
|
"react-is": "^19.2.6",
|
|
36
36
|
"react-markdown": "^10.1.0",
|
|
37
|
+
"react-resizable-panels": "^4.17.0",
|
|
37
38
|
"react-router": "^7.18.2",
|
|
38
39
|
"recharts": "3.8.0",
|
|
39
40
|
"remark-breaks": "^4.0.0",
|
|
40
41
|
"remark-gfm": "^4.0.1",
|
|
41
42
|
"shiki": "4.1.0",
|
|
42
43
|
"zod": "^4.5.4",
|
|
43
|
-
"@sentry/junior-plugin-api": "0.
|
|
44
|
+
"@sentry/junior-plugin-api": "0.278.0"
|
|
44
45
|
},
|
|
45
46
|
"peerDependencies": {
|
|
46
|
-
"@sentry/junior": "0.
|
|
47
|
+
"@sentry/junior": "0.278.0"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
50
|
"@oxlint/plugins": "1.79.0",
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
"tw-animate-css": "^1.4.0",
|
|
65
66
|
"typescript": "^7.0.2",
|
|
66
67
|
"vitest": "^4.1.12",
|
|
67
|
-
"@sentry/junior": "0.
|
|
68
|
+
"@sentry/junior": "0.278.0"
|
|
68
69
|
},
|
|
69
70
|
"scripts": {
|
|
70
71
|
"build": "pnpm --filter @sentry/junior-plugin-api build && pnpm --filter @sentry/junior build && pnpm run build:client && pnpm run build:css && pnpm run build:server",
|
package/src/app.ts
CHANGED
|
@@ -537,7 +537,7 @@ export function createDashboardApp(
|
|
|
537
537
|
};
|
|
538
538
|
|
|
539
539
|
app.use("*", requireAuth);
|
|
540
|
-
// Core detail reads set a version
|
|
540
|
+
// Core detail reads set a version token before they build the report. Mock
|
|
541
541
|
// reporting has no stored version, so it revalidates on content instead.
|
|
542
542
|
if (options.mockConversations) {
|
|
543
543
|
app.use("/api/conversations/:conversationId", revalidateConversation);
|
package/src/client/App.tsx
CHANGED
|
@@ -34,6 +34,7 @@ import { useDashboardServerVersion } from "./dashboard-version";
|
|
|
34
34
|
import { ConversationWorkspace } from "./conversations/ConversationWorkspace";
|
|
35
35
|
import { ConversationWorkspaceLoading } from "./conversations/ConversationWorkspaceLoading";
|
|
36
36
|
import { useConversationData } from "./conversations/queries";
|
|
37
|
+
import { readConversationStartPosition } from "./conversations/transcriptReadPosition";
|
|
37
38
|
import { codeRepositoryTabs } from "./pages/code/codeRepositoryRoutes";
|
|
38
39
|
import { RepositoryPage } from "./pages/code/RepositoryPage";
|
|
39
40
|
import { CodePage } from "./pages/code/CodePage";
|
|
@@ -154,8 +155,12 @@ export function DashboardShell() {
|
|
|
154
155
|
location.pathname.startsWith("/conversations/");
|
|
155
156
|
const conversationId = conversationIdFromPath(location.pathname);
|
|
156
157
|
const conversationsQuery = useConversationsData();
|
|
157
|
-
//
|
|
158
|
-
|
|
158
|
+
// The shell owns the open Conversation's data, and the page reads it. On a
|
|
159
|
+
// direct load, this fetch runs beside the dashboard core reads, before the
|
|
160
|
+
// page mounts. One fetch for each visit also brings the unread page.
|
|
161
|
+
const conversationDetail = useConversationData(conversationId, {
|
|
162
|
+
unreadPageAtOpen: readConversationStartPosition() === "unread",
|
|
163
|
+
});
|
|
159
164
|
const mobileConversation = useMemo(() => {
|
|
160
165
|
if (!conversationId) return undefined;
|
|
161
166
|
return buildConversations(
|
|
@@ -439,7 +444,7 @@ export function DashboardShell() {
|
|
|
439
444
|
loading ? (
|
|
440
445
|
<ConversationWorkspaceLoading detail={false} />
|
|
441
446
|
) : data ? (
|
|
442
|
-
<ConversationWorkspace />
|
|
447
|
+
<ConversationWorkspace detail={conversationDetail} />
|
|
443
448
|
) : (
|
|
444
449
|
<LoadingView
|
|
445
450
|
label={query.error?.message ?? "Dashboard unavailable"}
|
|
@@ -481,7 +486,7 @@ export function DashboardShell() {
|
|
|
481
486
|
loading ? (
|
|
482
487
|
<ConversationWorkspaceLoading detail />
|
|
483
488
|
) : data ? (
|
|
484
|
-
<ConversationWorkspace />
|
|
489
|
+
<ConversationWorkspace detail={conversationDetail} />
|
|
485
490
|
) : (
|
|
486
491
|
<LoadingView
|
|
487
492
|
label={query.error?.message ?? "Dashboard unavailable"}
|
|
@@ -124,7 +124,7 @@ export function DetailsPanel(props: {
|
|
|
124
124
|
</Button>
|
|
125
125
|
</div>
|
|
126
126
|
</header>
|
|
127
|
-
<div className="min-h-0 overflow-y-auto overscroll-contain">
|
|
127
|
+
<div className="thin-scrollbar min-h-0 overflow-y-auto overscroll-contain">
|
|
128
128
|
{props.children}
|
|
129
129
|
</div>
|
|
130
130
|
</aside>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as ResizablePrimitive from "react-resizable-panels";
|
|
2
|
+
import { cn } from "@dashboard/lib/utils";
|
|
3
|
+
|
|
4
|
+
/** Lay out resizable panels in one row or column. */
|
|
5
|
+
function ResizablePanelGroup({
|
|
6
|
+
className,
|
|
7
|
+
...props
|
|
8
|
+
}: ResizablePrimitive.GroupProps) {
|
|
9
|
+
return (
|
|
10
|
+
<ResizablePrimitive.Group
|
|
11
|
+
data-slot="resizable-panel-group"
|
|
12
|
+
className={cn(
|
|
13
|
+
"flex h-full w-full aria-[orientation=vertical]:flex-col",
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Hold one resizable region inside a panel group. */
|
|
22
|
+
function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) {
|
|
23
|
+
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Drag or use arrow keys to resize the panels on each side. */
|
|
27
|
+
function ResizableHandle({
|
|
28
|
+
withHandle,
|
|
29
|
+
className,
|
|
30
|
+
...props
|
|
31
|
+
}: ResizablePrimitive.SeparatorProps & {
|
|
32
|
+
withHandle?: boolean;
|
|
33
|
+
}) {
|
|
34
|
+
return (
|
|
35
|
+
<ResizablePrimitive.Separator
|
|
36
|
+
data-slot="resizable-handle"
|
|
37
|
+
className={cn(
|
|
38
|
+
"relative flex w-px items-center justify-center bg-border ring-offset-background after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90",
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{withHandle && (
|
|
44
|
+
<div className="z-10 flex h-6 w-1 shrink-0 rounded-lg bg-border" />
|
|
45
|
+
)}
|
|
46
|
+
</ResizablePrimitive.Separator>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { ResizableHandle, ResizablePanel, ResizablePanelGroup };
|
|
@@ -1,32 +1,68 @@
|
|
|
1
|
+
import { useId } from "react";
|
|
2
|
+
import { CircleHelp } from "lucide-react";
|
|
3
|
+
|
|
1
4
|
import { Checkbox } from "../components/ui/checkbox";
|
|
2
5
|
import { InlineError } from "../components/InlineError";
|
|
6
|
+
import {
|
|
7
|
+
Popover,
|
|
8
|
+
PopoverContent,
|
|
9
|
+
PopoverDescription,
|
|
10
|
+
PopoverTrigger,
|
|
11
|
+
} from "../components/ui/popover";
|
|
3
12
|
import { useSetConversationAutoArchive } from "./autoArchiveQueries";
|
|
4
13
|
|
|
14
|
+
const AUTO_ARCHIVE_HELP =
|
|
15
|
+
"Archive this conversation for you when its linked work is merged or closed and nobody replies during your wait time.";
|
|
16
|
+
|
|
5
17
|
/** Let a participant turn auto-archive on or off for this conversation. */
|
|
6
18
|
export function AutoArchiveToggle(props: {
|
|
7
19
|
autoArchive: boolean;
|
|
8
20
|
conversationId: string;
|
|
9
21
|
}) {
|
|
10
22
|
const update = useSetConversationAutoArchive(props.conversationId);
|
|
23
|
+
const helpId = useId();
|
|
11
24
|
return (
|
|
12
25
|
<div className="grid gap-1">
|
|
13
|
-
<
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
<div className="flex items-center gap-1.5">
|
|
27
|
+
<label className="flex cursor-pointer items-center gap-2.5">
|
|
28
|
+
<Checkbox
|
|
29
|
+
aria-describedby={helpId}
|
|
30
|
+
checked={props.autoArchive}
|
|
31
|
+
disabled={update.isPending}
|
|
32
|
+
onCheckedChange={(checked) => update.mutate(checked)}
|
|
33
|
+
/>
|
|
21
34
|
<span className="font-semibold text-dashboard-text">
|
|
22
|
-
Auto
|
|
23
|
-
</span>
|
|
24
|
-
<span>
|
|
25
|
-
Archive this conversation for you when its linked work is merged or
|
|
26
|
-
closed and nobody replies during your wait time.
|
|
35
|
+
Auto archive
|
|
27
36
|
</span>
|
|
37
|
+
</label>
|
|
38
|
+
{/* A popover, not a tooltip: touch screens open it with a tap. */}
|
|
39
|
+
<Popover>
|
|
40
|
+
<PopoverTrigger
|
|
41
|
+
delay={0}
|
|
42
|
+
openOnHover
|
|
43
|
+
render={
|
|
44
|
+
<button
|
|
45
|
+
aria-label="About auto archive"
|
|
46
|
+
className="grid place-items-center rounded-sm text-dashboard-text-muted hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dashboard-focus"
|
|
47
|
+
type="button"
|
|
48
|
+
>
|
|
49
|
+
<CircleHelp aria-hidden="true" size={14} strokeWidth={2} />
|
|
50
|
+
</button>
|
|
51
|
+
}
|
|
52
|
+
/>
|
|
53
|
+
<PopoverContent
|
|
54
|
+
aria-label="About auto archive"
|
|
55
|
+
className="w-64 px-3 py-2 text-xs"
|
|
56
|
+
side="top"
|
|
57
|
+
>
|
|
58
|
+
<PopoverDescription>{AUTO_ARCHIVE_HELP}</PopoverDescription>
|
|
59
|
+
</PopoverContent>
|
|
60
|
+
</Popover>
|
|
61
|
+
{/* Keep the help text for screen readers when the popover is closed. */}
|
|
62
|
+
<span className="sr-only" id={helpId}>
|
|
63
|
+
{AUTO_ARCHIVE_HELP}
|
|
28
64
|
</span>
|
|
29
|
-
</
|
|
65
|
+
</div>
|
|
30
66
|
{update.isError ? (
|
|
31
67
|
<InlineError>Could not save auto-archive. Try again.</InlineError>
|
|
32
68
|
) : null}
|
|
@@ -49,6 +49,7 @@ export function ConversationHeader(props: {
|
|
|
49
49
|
privacy: ReactNode;
|
|
50
50
|
search: string;
|
|
51
51
|
sentryConversationUrl?: string;
|
|
52
|
+
sidebarToggle?: ReactNode;
|
|
52
53
|
title: string;
|
|
53
54
|
view: TranscriptViewMode;
|
|
54
55
|
}) {
|
|
@@ -165,8 +166,11 @@ export function ConversationHeader(props: {
|
|
|
165
166
|
!showMobileHeader && "hidden md:block",
|
|
166
167
|
)}
|
|
167
168
|
>
|
|
168
|
-
<div className="hidden h-
|
|
169
|
+
<div className="hidden h-13 min-w-0 items-center justify-between gap-4 px-4 md:flex">
|
|
169
170
|
<div className="flex min-w-0 items-center gap-2">
|
|
171
|
+
{props.sidebarToggle ? (
|
|
172
|
+
<div className="shrink-0">{props.sidebarToggle}</div>
|
|
173
|
+
) : null}
|
|
170
174
|
{props.agentTree ? (
|
|
171
175
|
<div className="min-w-0 shrink-[2]">{props.agentTree}</div>
|
|
172
176
|
) : props.location ? (
|
|
@@ -220,9 +224,9 @@ export function ConversationHeader(props: {
|
|
|
220
224
|
</ConversationOverflowMenu>
|
|
221
225
|
<HeaderIconButton
|
|
222
226
|
ref={detailsButtonRef}
|
|
227
|
+
expanded={detailsOpen}
|
|
223
228
|
label="Conversation details"
|
|
224
229
|
onClick={props.onDetailsClick}
|
|
225
|
-
pressed={detailsOpen}
|
|
226
230
|
>
|
|
227
231
|
<PanelRight aria-hidden="true" size={15} strokeWidth={2} />
|
|
228
232
|
</HeaderIconButton>
|
|
@@ -179,6 +179,8 @@ function ViewModeItem(props: {
|
|
|
179
179
|
/** Shared icon control used by conversation and subagent transcript headers. */
|
|
180
180
|
export function HeaderIconButton(props: {
|
|
181
181
|
children: ReactNode;
|
|
182
|
+
controls?: string;
|
|
183
|
+
expanded?: boolean;
|
|
182
184
|
ref?: Ref<HTMLButtonElement>;
|
|
183
185
|
label: string;
|
|
184
186
|
onClick(): void;
|
|
@@ -192,6 +194,8 @@ export function HeaderIconButton(props: {
|
|
|
192
194
|
render={
|
|
193
195
|
<Button
|
|
194
196
|
ref={props.ref}
|
|
197
|
+
aria-controls={props.controls}
|
|
198
|
+
aria-expanded={props.expanded}
|
|
195
199
|
aria-label={props.label}
|
|
196
200
|
aria-pressed={props.pressed}
|
|
197
201
|
className={cn(
|
|
@@ -469,7 +469,7 @@ function SourceTask(props: {
|
|
|
469
469
|
);
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
-
/** Resolve the model
|
|
472
|
+
/** Resolve the model that an open turn last called. */
|
|
473
473
|
export function liveModelId(
|
|
474
474
|
detail: ConversationDetailReport | undefined,
|
|
475
475
|
): string | undefined {
|
|
@@ -484,12 +484,7 @@ export function liveModelId(
|
|
|
484
484
|
if (openTurns.size === 0) modelId = undefined;
|
|
485
485
|
continue;
|
|
486
486
|
}
|
|
487
|
-
if (openTurns.size
|
|
488
|
-
if (data.type === "turn_routed" && openTurns.has(data.turnId)) {
|
|
489
|
-
modelId = data.modelId;
|
|
490
|
-
continue;
|
|
491
|
-
}
|
|
492
|
-
if (data.type === "handoff") modelId = data.modelId;
|
|
487
|
+
if (openTurns.size > 0 && event.model) modelId = event.model.modelId;
|
|
493
488
|
}
|
|
494
489
|
return openTurns.size > 0 ? modelId : undefined;
|
|
495
490
|
}
|