@sentry/junior-dashboard 0.265.0 → 0.267.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 (104) hide show
  1. package/dist/app.js +49 -12
  2. package/dist/client.js +94 -63
  3. package/dist/index.js +49 -12
  4. package/dist/tailwind.css +1 -1
  5. package/package.json +11 -5
  6. package/src/app.ts +0 -2
  7. package/src/auth.ts +4 -7
  8. package/src/client/App.tsx +35 -53
  9. package/src/client/components/ActorAvatar.tsx +10 -22
  10. package/src/client/components/ConnectionBanner.tsx +4 -3
  11. package/src/client/components/CopyButton.tsx +3 -4
  12. package/src/client/components/DetailsPanel.tsx +12 -13
  13. package/src/client/components/Drawer.tsx +46 -23
  14. package/src/client/components/EmptyTelemetry.tsx +5 -3
  15. package/src/client/components/ImageAttachment.tsx +49 -63
  16. package/src/client/components/InlineError.tsx +8 -4
  17. package/src/client/components/Notice.tsx +19 -26
  18. package/src/client/components/Pagination.tsx +26 -26
  19. package/src/client/components/ParticipantAvatarStack.tsx +13 -10
  20. package/src/client/components/README.md +28 -3
  21. package/src/client/components/SearchInput.tsx +51 -41
  22. package/src/client/components/VersionDriftBanner.tsx +9 -7
  23. package/src/client/components/charts/CacheWrites.tsx +52 -43
  24. package/src/client/components/ui/alert.tsx +64 -0
  25. package/src/client/components/ui/avatar.tsx +53 -0
  26. package/src/client/components/ui/button-group.tsx +59 -0
  27. package/src/client/components/ui/button.tsx +2 -1
  28. package/src/client/components/ui/dialog.tsx +60 -0
  29. package/src/client/components/ui/drawer.tsx +140 -0
  30. package/src/client/components/ui/empty.tsx +61 -0
  31. package/src/client/components/ui/input-group.tsx +96 -0
  32. package/src/client/components/ui/pagination.tsx +60 -0
  33. package/src/client/components/ui/separator.tsx +22 -0
  34. package/src/client/components/ui/spinner.tsx +21 -0
  35. package/src/client/components/ui/table.tsx +22 -1
  36. package/src/client/components/ui/tooltip.tsx +1 -1
  37. package/src/client/components/ui/useSheet.ts +5 -1
  38. package/src/client/conversations/ComposerImages.tsx +66 -14
  39. package/src/client/conversations/ConversationChildAgents.tsx +5 -5
  40. package/src/client/conversations/ConversationComposer.tsx +84 -12
  41. package/src/client/conversations/ConversationDetails.tsx +3 -27
  42. package/src/client/conversations/ConversationEventDetails.tsx +26 -10
  43. package/src/client/conversations/ConversationHeader.tsx +5 -4
  44. package/src/client/conversations/ConversationHeaderActions.tsx +7 -11
  45. package/src/client/conversations/ConversationPage.tsx +156 -2
  46. package/src/client/conversations/ConversationSidebar.tsx +5 -4
  47. package/src/client/conversations/ConversationTranscript.tsx +48 -16
  48. package/src/client/conversations/ConversationUsageBreakdown.tsx +108 -65
  49. package/src/client/conversations/ConversationWatches.tsx +67 -59
  50. package/src/client/conversations/DetailsList.tsx +77 -29
  51. package/src/client/conversations/EventDetails.tsx +46 -19
  52. package/src/client/conversations/EventUsage.tsx +99 -74
  53. package/src/client/conversations/ForkConversationButton.tsx +79 -73
  54. package/src/client/conversations/PendingMailboxStack.tsx +312 -119
  55. package/src/client/conversations/README.md +30 -10
  56. package/src/client/conversations/ShareConversationButton.tsx +3 -4
  57. package/src/client/conversations/SubagentTranscriptDrawer.tsx +19 -22
  58. package/src/client/conversations/TranscriptActivityGroup.tsx +22 -19
  59. package/src/client/conversations/TranscriptAttachment.tsx +39 -4
  60. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +14 -19
  61. package/src/client/conversations/TranscriptEventView.tsx +35 -16
  62. package/src/client/conversations/TranscriptFailureView.tsx +10 -13
  63. package/src/client/conversations/TranscriptMarkdown.tsx +232 -436
  64. package/src/client/conversations/TranscriptMessageContextView.tsx +31 -20
  65. package/src/client/conversations/TranscriptMessageView.tsx +7 -1
  66. package/src/client/conversations/TranscriptStructuredEventView.tsx +25 -15
  67. package/src/client/conversations/TranscriptSummary.tsx +13 -7
  68. package/src/client/conversations/TranscriptTurnContextView.tsx +78 -56
  69. package/src/client/conversations/TranscriptView.tsx +7 -3
  70. package/src/client/conversations/UserInputRequestCard.tsx +244 -0
  71. package/src/client/conversations/conversationOutbox.ts +46 -0
  72. package/src/client/conversations/eventLog.ts +1 -0
  73. package/src/client/conversations/eventTime.ts +13 -1
  74. package/src/client/conversations/eventTranscript.ts +12 -5
  75. package/src/client/conversations/queries.ts +127 -26
  76. package/src/client/conversations/transcriptMarkdownLinks.ts +5 -367
  77. package/src/client/conversations/transcriptMentions.tsx +18 -5
  78. package/src/client/conversations/userInputRequest.ts +56 -0
  79. package/src/client/format.ts +10 -3
  80. package/src/client/pages/automations/{AutomationLifecycle.tsx → AutomationActions.tsx} +60 -69
  81. package/src/client/pages/automations/AutomationDraftChanges.tsx +171 -0
  82. package/src/client/pages/automations/AutomationEditor.tsx +182 -156
  83. package/src/client/pages/automations/AutomationEventFields.tsx +119 -45
  84. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +1 -1
  85. package/src/client/pages/automations/AutomationFormSection.tsx +20 -2
  86. package/src/client/pages/automations/AutomationOutcomeFields.tsx +1 -1
  87. package/src/client/pages/automations/AutomationOverview.tsx +238 -0
  88. package/src/client/pages/automations/AutomationPage.tsx +343 -0
  89. package/src/client/pages/automations/AutomationRecentRuns.tsx +3 -2
  90. package/src/client/pages/automations/AutomationRow.tsx +11 -13
  91. package/src/client/pages/automations/AutomationRunDetails.tsx +4 -2
  92. package/src/client/pages/automations/{AutomationExecutionsPage.tsx → AutomationRunsTab.tsx} +94 -103
  93. package/src/client/pages/automations/AutomationStatus.tsx +164 -108
  94. package/src/client/pages/automations/AutomationVersionsTab.tsx +518 -0
  95. package/src/client/pages/automations/AutomationsPage.tsx +10 -98
  96. package/src/client/pages/automations/AutomationsPageLayout.tsx +5 -23
  97. package/src/client/pages/automations/README.md +56 -46
  98. package/src/client/types.ts +3 -0
  99. package/src/mock-reporting/fixtures.ts +44 -2
  100. package/src/mock-reporting/watches.ts +1 -1
  101. package/src/client/conversations/transcriptStyles.ts +0 -10
  102. package/src/client/pages/automations/AutomationDetails.tsx +0 -193
  103. package/src/client/pages/automations/AutomationEditPage.tsx +0 -176
  104. package/src/client/pages/automations/AutomationVersionsPage.tsx +0 -390
@@ -1,5 +1,5 @@
1
1
  import { useState } from "react";
2
- import { Bot, ExternalLink } from "lucide-react";
2
+ import { Bot, CircleAlert, ExternalLink } from "lucide-react";
3
3
  import { Link } from "react-router";
4
4
 
5
5
  import { useConversationData } from "./queries";
@@ -11,6 +11,8 @@ import {
11
11
  } from "../format";
12
12
  import { buildConversationMarkdown } from "../markdownExport";
13
13
  import { Drawer } from "../components/Drawer";
14
+ import { EmptyTelemetry } from "../components/EmptyTelemetry";
15
+ import { Alert, AlertDescription, AlertTitle } from "../components/ui/alert";
14
16
  import { SearchInput } from "../components/SearchInput";
15
17
  import { StatusChip } from "../components/StatusChip";
16
18
  import { subagentStatusTone } from "./ConversationChildAgents";
@@ -22,7 +24,6 @@ import {
22
24
  import { Transcript } from "./TranscriptView";
23
25
  import { TranscriptLoading } from "./TranscriptLoading";
24
26
  import type { TranscriptViewMode } from "./transcriptRenderModel";
25
- import { transcriptEmptyClass } from "./transcriptStyles";
26
27
 
27
28
  /** One child Conversation to show, with what its parent already knows. */
28
29
  export interface SubagentTranscriptTarget {
@@ -108,6 +109,8 @@ function SubagentTranscriptDrawerContent(props: {
108
109
  </>
109
110
  }
110
111
  closeLabel="Close subagent transcript"
112
+ // The event log owns its row insets and sticky toolbar, like the page.
113
+ flush={view === "raw"}
111
114
  header={
112
115
  <>
113
116
  <div className="flex min-w-0 items-center gap-2">
@@ -178,9 +181,19 @@ function SubagentTranscriptDrawerContent(props: {
178
181
  {query.isPending ? (
179
182
  <TranscriptLoading />
180
183
  ) : query.error && !detail ? (
181
- <DrawerEmptyState tone="error">
182
- Conversation failed to load.
183
- </DrawerEmptyState>
184
+ <Alert
185
+ className="gap-1 px-4 py-3 has-[>svg]:gap-x-3"
186
+ variant="destructive"
187
+ >
188
+ <CircleAlert aria-hidden="true" />
189
+ <AlertTitle className="font-display text-base font-semibold leading-tight">
190
+ Conversation failed to load.
191
+ </AlertTitle>
192
+ <AlertDescription className="leading-relaxed">
193
+ Junior could not get this child conversation. Use Open conversation
194
+ to view it on its own page.
195
+ </AlertDescription>
196
+ </Alert>
184
197
  ) : detail ? (
185
198
  <Transcript
186
199
  hasPreviousPage={query.hasPreviousPage}
@@ -194,7 +207,7 @@ function SubagentTranscriptDrawerContent(props: {
194
207
  view={view}
195
208
  />
196
209
  ) : (
197
- <DrawerEmptyState>Conversation unavailable.</DrawerEmptyState>
210
+ <EmptyTelemetry>Conversation unavailable.</EmptyTelemetry>
198
211
  )}
199
212
  </Drawer>
200
213
  );
@@ -209,19 +222,3 @@ function statusLabel(
209
222
  }
210
223
  return detailStatus ?? status;
211
224
  }
212
-
213
- function DrawerEmptyState(props: {
214
- children: string;
215
- tone?: "default" | "error";
216
- }) {
217
- const isError = props.tone === "error";
218
- return (
219
- <div
220
- className={transcriptEmptyClass(isError ? "error" : "default")}
221
- data-tone={props.tone ?? "default"}
222
- role={isError ? "alert" : undefined}
223
- >
224
- {props.children}
225
- </div>
226
- );
227
- }
@@ -2,6 +2,7 @@ import { Fragment, useState, type ReactNode } from "react";
2
2
  import { ChevronRight } from "lucide-react";
3
3
 
4
4
  import { ShimmerText } from "../components/ShimmerText";
5
+ import { Collapsible, CollapsibleContent } from "../components/ui/collapsible";
5
6
  import {
6
7
  Tooltip,
7
8
  TooltipContent,
@@ -12,7 +13,7 @@ import type { RenderedTranscriptEntry } from "./transcriptRenderModel";
12
13
  import { cn } from "../styles";
13
14
  import { useTranscriptSearch } from "./transcriptSearch";
14
15
  import { TranscriptActivityNesting } from "./TranscriptRailEvent";
15
- import { TranscriptSummary } from "./TranscriptSummary";
16
+ import { TranscriptSummaryTrigger } from "./TranscriptSummary";
16
17
 
17
18
  function countLabel(count: number, singular: string, plural: string): string {
18
19
  return count === 1 ? singular : `${count} ${plural}`;
@@ -172,17 +173,17 @@ export function TranscriptActivityGroup(props: {
172
173
  const open = activityGroupOpen(userOpen);
173
174
 
174
175
  return (
175
- <details className="group/activity-run min-w-0 py-0.5" open={open}>
176
- <TranscriptSummary
176
+ <Collapsible
177
+ className="min-w-0 py-0.5"
178
+ onOpenChange={setUserOpen}
179
+ open={open}
180
+ >
181
+ <TranscriptSummaryTrigger
177
182
  flush
178
183
  className={cn(
179
- "flex items-center gap-1 text-left text-xs leading-tight text-dashboard-text-muted",
184
+ "group/activity-run flex items-center gap-1 text-xs leading-tight text-dashboard-text-muted",
180
185
  live && "text-dashboard-accent-text/80",
181
186
  )}
182
- onClick={(event) => {
183
- event.preventDefault();
184
- setUserOpen(!open);
185
- }}
186
187
  >
187
188
  <Tooltip>
188
189
  <TooltipTrigger
@@ -190,24 +191,26 @@ export function TranscriptActivityGroup(props: {
190
191
  <span className="inline-flex min-w-0 max-w-full items-center gap-1" />
191
192
  }
192
193
  >
193
- <span className="min-w-0 truncate group-open/activity-run:hidden">
194
- <ShimmerText active={live}>{label}</ShimmerText>
195
- </span>
196
- <span className="hidden min-w-0 truncate group-open/activity-run:inline">
197
- <ShimmerText active={live}>Hide {label}</ShimmerText>
194
+ <span className="min-w-0 truncate">
195
+ <ShimmerText active={live}>
196
+ {open ? `Hide ${label}` : label}
197
+ </ShimmerText>
198
198
  </span>
199
199
  <ChevronRight
200
200
  aria-hidden="true"
201
- className="size-3 shrink-0 opacity-55 transition-transform group-open/activity-run:rotate-90"
201
+ className="size-3 shrink-0 opacity-55 transition-transform group-aria-expanded/activity-run:rotate-90"
202
202
  strokeWidth={2.2}
203
203
  />
204
204
  </TooltipTrigger>
205
205
  <TooltipContent>{summary}</TooltipContent>
206
206
  </Tooltip>
207
- </TranscriptSummary>
208
- <TranscriptActivityNesting.Provider value>
209
- <div className="mt-1.5 grid min-w-0 gap-1">{rows}</div>
210
- </TranscriptActivityNesting.Provider>
211
- </details>
207
+ </TranscriptSummaryTrigger>
208
+ {/* Closed rows stay in the page, so browser find can open the group. */}
209
+ <CollapsibleContent hiddenUntilFound>
210
+ <TranscriptActivityNesting.Provider value>
211
+ <div className="mt-1.5 grid min-w-0 gap-1">{rows}</div>
212
+ </TranscriptActivityNesting.Provider>
213
+ </CollapsibleContent>
214
+ </Collapsible>
212
215
  );
213
216
  }
@@ -1,12 +1,22 @@
1
1
  import {
2
2
  INPUT_IMAGE_TYPES,
3
+ type InputImage,
3
4
  type MessageAttachment,
4
5
  } from "@sentry/junior/api/schema";
5
6
  import { FileText } from "lucide-react";
6
7
  import { ImageAttachment } from "../components/ImageAttachment";
7
8
  import { HighlightText, useTranscriptSearch } from "./transcriptSearch";
8
9
 
9
- function attachmentUrl(conversationId: string, attachmentId: string): string {
10
+ /** Build a data URL that previews an image before the server stores it. */
11
+ export function inputImageSrc(image: InputImage): string {
12
+ return `data:${image.contentType};base64,${image.data}`;
13
+ }
14
+
15
+ /** Build the private read route for one stored conversation attachment. */
16
+ export function conversationAttachmentUrl(
17
+ conversationId: string,
18
+ attachmentId: string,
19
+ ): string {
10
20
  return `/api/conversations/${encodeURIComponent(conversationId)}/attachments/${encodeURIComponent(attachmentId)}`;
11
21
  }
12
22
 
@@ -35,13 +45,38 @@ export function TranscriptAttachments(props: {
35
45
  );
36
46
  }
37
47
 
48
+ /** Show the images of a send before the server stores them. */
49
+ export function TranscriptLocalImages(props: { images: InputImage[] }) {
50
+ return (
51
+ <div className="flex min-w-0 flex-wrap items-start gap-2">
52
+ {props.images.map((image, index) => (
53
+ <ImageAttachment
54
+ filename={image.filename}
55
+ imageClassName={TRANSCRIPT_IMAGE_CLASS}
56
+ key={`${image.filename}:${index}`}
57
+ src={inputImageSrc(image)}
58
+ triggerClassName={TRANSCRIPT_IMAGE_TRIGGER_CLASS}
59
+ />
60
+ ))}
61
+ </div>
62
+ );
63
+ }
64
+
65
+ const TRANSCRIPT_IMAGE_CLASS =
66
+ "max-h-48 w-auto max-w-full h-auto rounded-lg object-contain";
67
+ const TRANSCRIPT_IMAGE_TRIGGER_CLASS =
68
+ "block min-w-0 max-w-full flex-none rounded-lg border border-dashboard-border bg-dashboard-fill-faint transition-colors hover:border-dashboard-border-interactive hover:bg-dashboard-fill-hover focus-visible:outline-2 focus-visible:outline-dashboard-focus";
69
+
38
70
  /** Render one stored user or assistant attachment using the private read route. */
39
71
  function TranscriptAttachment(props: {
40
72
  attachment: MessageAttachment;
41
73
  conversationId: string;
42
74
  }) {
43
75
  const search = useTranscriptSearch();
44
- const href = attachmentUrl(props.conversationId, props.attachment.id);
76
+ const href = conversationAttachmentUrl(
77
+ props.conversationId,
78
+ props.attachment.id,
79
+ );
45
80
  const size = formatAttachmentBytes(props.attachment.bytes);
46
81
  const inline = INPUT_IMAGE_TYPES.some(
47
82
  (type) => type === props.attachment.contentType,
@@ -53,10 +88,10 @@ function TranscriptAttachment(props: {
53
88
  <ImageAttachment
54
89
  context={meta}
55
90
  filename={props.attachment.filename}
56
- imageClassName="max-h-48 w-auto max-w-full h-auto rounded-lg object-contain"
91
+ imageClassName={TRANSCRIPT_IMAGE_CLASS}
57
92
  loading="lazy"
58
93
  src={href}
59
- triggerClassName="block min-w-0 max-w-full flex-none rounded-lg border border-dashboard-border bg-dashboard-fill-faint transition-colors hover:border-dashboard-border-interactive hover:bg-dashboard-fill-hover focus-visible:outline-2 focus-visible:outline-dashboard-focus"
94
+ triggerClassName={TRANSCRIPT_IMAGE_TRIGGER_CLASS}
60
95
  />
61
96
  );
62
97
  }
@@ -1,8 +1,13 @@
1
1
  import { ArrowRight, ChevronRight } from "lucide-react";
2
- import { useId, useState } from "react";
2
+ import { useState } from "react";
3
3
  import type { ConversationDetailReport } from "@sentry/junior/api/schema";
4
4
 
5
5
  import { buttonVariants } from "../components/ui/button";
6
+ import {
7
+ Collapsible,
8
+ CollapsibleContent,
9
+ CollapsibleTrigger,
10
+ } from "../components/ui/collapsible";
6
11
  import { Link } from "react-router";
7
12
  import { ObjectIcon } from "../components/ObjectIcon";
8
13
  import { automationPath, formatRelativeTime } from "../format";
@@ -49,7 +54,6 @@ export function TranscriptAutomationTriggerView(props: {
49
54
  (part) => part.type === "text" && part.redacted,
50
55
  );
51
56
  const [open, setOpen] = useState(false);
52
- const promptId = useId();
53
57
  const automationId = props.sourceTask.id?.trim();
54
58
  const name = props.sourceTask.title?.trim() || "Automation";
55
59
  const details = [
@@ -86,30 +90,21 @@ export function TranscriptAutomationTriggerView(props: {
86
90
  </p>
87
91
  ) : null}
88
92
  {text ? (
89
- <div className="min-w-0">
90
- <button
91
- aria-controls={promptId}
92
- aria-expanded={open}
93
- className="-mx-1 inline-flex cursor-pointer items-center gap-1 rounded-sm border-0 bg-transparent px-1 py-0.5 font-sans text-xs font-medium text-dashboard-text-muted transition-colors hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dashboard-focus"
94
- onClick={() => setOpen((value) => !value)}
95
- type="button"
96
- >
93
+ <Collapsible className="min-w-0" onOpenChange={setOpen} open={open}>
94
+ <CollapsibleTrigger className="group -mx-1 inline-flex cursor-pointer items-center gap-1 rounded-sm border-0 bg-transparent px-1 py-0.5 font-sans text-xs font-medium text-dashboard-text-muted transition-colors hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dashboard-focus">
97
95
  <ChevronRight
98
96
  aria-hidden="true"
99
- className={cn("size-3 transition-transform", open && "rotate-90")}
97
+ className="size-3 transition-transform group-aria-expanded:rotate-90 motion-reduce:transition-none"
100
98
  strokeWidth={2.2}
101
99
  />
102
100
  {open ? "Hide prompt" : "Show prompt"}
103
- </button>
104
- {open ? (
105
- <pre
106
- className="mt-2 max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-lg border border-dashboard-border-subtle bg-dashboard-bg px-3 py-2 font-mono text-xs leading-relaxed text-dashboard-text-muted"
107
- id={promptId}
108
- >
101
+ </CollapsibleTrigger>
102
+ <CollapsibleContent>
103
+ <pre className="mt-2 max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-lg border border-dashboard-border-subtle bg-dashboard-bg px-3 py-2 font-mono text-xs leading-relaxed text-dashboard-text-muted">
109
104
  <HighlightText text={text} />
110
105
  </pre>
111
- ) : null}
112
- </div>
106
+ </CollapsibleContent>
107
+ </Collapsible>
113
108
  ) : redacted ? (
114
109
  <RedactedMarker />
115
110
  ) : null}
@@ -1,11 +1,14 @@
1
1
  import { ChevronRight } from "lucide-react";
2
+ import { useState } from "react";
2
3
 
4
+ import { Collapsible, CollapsibleContent } from "../components/ui/collapsible";
5
+ import { cn, dashboardDisclosureChevronClass } from "../styles";
3
6
  import { MentionText } from "./transcriptMentions";
4
7
  import { messageRawText } from "./transcriptRenderModel";
5
- import { HighlightText } from "./transcriptSearch";
8
+ import { HighlightText, useTranscriptSearch } from "./transcriptSearch";
6
9
  import { RedactedMarker } from "./TranscriptRedacted";
7
10
  import type { TranscriptViewMessage } from "../types";
8
- import { TranscriptSummary } from "./TranscriptSummary";
11
+ import { TranscriptSummaryTrigger } from "./TranscriptSummary";
9
12
 
10
13
  function EventTypeChip(props: { eventType: string }) {
11
14
  return (
@@ -25,6 +28,8 @@ function batchSummary(events: readonly { label?: string }[]): string {
25
28
 
26
29
  /** Render an Event Message whose heading expands its full text. */
27
30
  export function TranscriptEventView(props: { message: TranscriptViewMessage }) {
31
+ const { active: searchActive } = useTranscriptSearch();
32
+ const [open, setOpen] = useState(false);
28
33
  const text = messageRawText(props.message);
29
34
  const redacted = props.message.parts.some(
30
35
  (part) => part.type === "text" && part.redacted,
@@ -35,33 +40,38 @@ export function TranscriptEventView(props: { message: TranscriptViewMessage }) {
35
40
  const summary = isBatch
36
41
  ? batchSummary(events)
37
42
  : (props.message.trustedSummary ?? props.message.eventType ?? "");
43
+ // With text, the heading sits in the disclosure button, which cannot hold
44
+ // links.
45
+ const links = !text;
38
46
  const heading = (
39
47
  <span className="flex min-w-0 flex-col gap-1">
40
48
  <span className="flex min-h-6 min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
41
49
  <span className="min-w-0 break-words font-display text-sm font-semibold leading-6 text-dashboard-text">
42
- <MentionText mentions={mentions} text={summary} />
50
+ <MentionText links={links} mentions={mentions} text={summary} />
43
51
  </span>
44
52
  {!isBatch && props.message.eventType ? (
45
53
  <EventTypeChip eventType={props.message.eventType} />
46
54
  ) : null}
47
55
  </span>
48
56
  {isBatch ? (
49
- <ul className="flex min-w-0 flex-col gap-1">
57
+ <span className="flex min-w-0 flex-col gap-1" role="list">
50
58
  {events.map((event, index) => (
51
- <li
59
+ <span
52
60
  key={index}
61
+ role="listitem"
53
62
  className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs leading-5 text-dashboard-text-muted"
54
63
  >
55
64
  <span className="min-w-0 break-words">
56
65
  <MentionText
66
+ links={links}
57
67
  mentions={mentions}
58
68
  text={event.trustedSummary ?? event.eventType}
59
69
  />
60
70
  </span>
61
71
  <EventTypeChip eventType={event.eventType} />
62
- </li>
72
+ </span>
63
73
  ))}
64
- </ul>
74
+ </span>
65
75
  ) : null}
66
76
  </span>
67
77
  );
@@ -69,22 +79,31 @@ export function TranscriptEventView(props: { message: TranscriptViewMessage }) {
69
79
  return (
70
80
  <div className="min-w-0 py-1">
71
81
  {text ? (
72
- <details className="group/event min-w-0">
73
- <TranscriptSummary
82
+ <Collapsible
83
+ className="min-w-0"
84
+ // Search holds the text open, so the heading button is off until
85
+ // search ends.
86
+ disabled={searchActive}
87
+ onOpenChange={setOpen}
88
+ open={searchActive || open}
89
+ >
90
+ <TranscriptSummaryTrigger
74
91
  flush
75
- className="flex items-start gap-2 text-dashboard-text-muted"
92
+ className="group flex items-start gap-2 text-dashboard-text-muted"
76
93
  >
77
94
  {heading}
78
95
  <ChevronRight
79
96
  aria-hidden="true"
80
- className="mt-1.5 size-3 shrink-0 transition-transform group-open/event:rotate-90"
97
+ className={cn(dashboardDisclosureChevronClass, "mt-1.5")}
81
98
  strokeWidth={2.2}
82
99
  />
83
- </TranscriptSummary>
84
- <pre className="mt-2 max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-lg border border-dashboard-border bg-dashboard-surface-panel px-3 py-2 font-sans text-xs leading-relaxed text-dashboard-text-muted">
85
- <HighlightText text={text} />
86
- </pre>
87
- </details>
100
+ </TranscriptSummaryTrigger>
101
+ <CollapsibleContent hiddenUntilFound>
102
+ <pre className="mt-2 max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-lg border border-dashboard-border bg-dashboard-surface-panel px-3 py-2 font-sans text-xs leading-relaxed text-dashboard-text-muted">
103
+ <HighlightText text={text} />
104
+ </pre>
105
+ </CollapsibleContent>
106
+ </Collapsible>
88
107
  ) : (
89
108
  <>
90
109
  {heading}
@@ -4,6 +4,7 @@ import type {
4
4
  ConversationTurnFailureReason,
5
5
  } from "@sentry/junior/api/schema";
6
6
 
7
+ import { Alert, AlertDescription, AlertTitle } from "../components/ui/alert";
7
8
  import { formatMessageTimestamp } from "../format";
8
9
  import {
9
10
  transcriptFailureDescription,
@@ -35,25 +36,21 @@ export function TranscriptFailureView(props: {
35
36
  ) : null;
36
37
 
37
38
  return (
38
- <div
39
- className="grid min-w-0 grid-cols-[auto_minmax(0,1fr)_auto] items-start gap-3 rounded-lg bg-rose-300/[0.1] px-4 py-3 text-rose-100 max-md:grid-cols-[auto_minmax(0,1fr)]"
39
+ <Alert
40
+ className="min-w-0 gap-3 rounded-lg border-0 bg-rose-300/[0.1] px-4 py-3 text-rose-100 *:[svg]:row-span-1 has-[>svg]:grid-cols-[auto_minmax(0,1fr)_auto] has-[>svg]:gap-x-3 max-md:has-[>svg]:grid-cols-[auto_minmax(0,1fr)] *:[svg]:text-rose-300"
40
41
  data-transcript-failure={props.failureCode}
41
42
  data-transcript-failure-event-id={props.eventId}
42
43
  data-transcript-failure-reason={props.failureReason}
43
- role="alert"
44
+ variant="destructive"
44
45
  >
45
- <CircleAlert
46
- aria-hidden="true"
47
- className="mt-0.5 text-rose-300"
48
- size={16}
49
- />
46
+ <CircleAlert aria-hidden="true" size={16} />
50
47
  <div className="min-w-0">
51
- <div className="font-display text-base font-semibold leading-tight">
48
+ <AlertTitle className="font-display text-base font-semibold leading-tight">
52
49
  {transcriptFailureTitle(props.failureCode, props.failureReason)}
53
- </div>
54
- <div className="mt-1 text-sm leading-relaxed text-rose-100/70">
50
+ </AlertTitle>
51
+ <AlertDescription className="mt-1 leading-relaxed text-rose-100/70">
55
52
  {transcriptFailureDescription(props.failureCode, props.failureReason)}
56
- </div>
53
+ </AlertDescription>
57
54
  {eventReference ? (
58
55
  <div className="mt-2 text-xs leading-relaxed text-rose-100/65">
59
56
  Reference: {eventReference}
@@ -65,6 +62,6 @@ export function TranscriptFailureView(props: {
65
62
  {timestamp}
66
63
  </span>
67
64
  ) : null}
68
- </div>
65
+ </Alert>
69
66
  );
70
67
  }