@sentry/junior-dashboard 0.265.0 → 0.266.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 (47) hide show
  1. package/dist/app.js +2 -2
  2. package/dist/client.js +48 -48
  3. package/dist/index.js +2 -2
  4. package/dist/tailwind.css +1 -1
  5. package/package.json +3 -3
  6. package/src/client/components/ActorAvatar.tsx +10 -22
  7. package/src/client/components/ConnectionBanner.tsx +4 -3
  8. package/src/client/components/CopyButton.tsx +3 -4
  9. package/src/client/components/EmptyTelemetry.tsx +5 -3
  10. package/src/client/components/ImageAttachment.tsx +49 -63
  11. package/src/client/components/InlineError.tsx +8 -4
  12. package/src/client/components/Notice.tsx +19 -26
  13. package/src/client/components/Pagination.tsx +26 -26
  14. package/src/client/components/ParticipantAvatarStack.tsx +10 -10
  15. package/src/client/components/README.md +17 -1
  16. package/src/client/components/SearchInput.tsx +51 -41
  17. package/src/client/components/VersionDriftBanner.tsx +9 -7
  18. package/src/client/components/charts/CacheWrites.tsx +52 -43
  19. package/src/client/components/ui/alert.tsx +64 -0
  20. package/src/client/components/ui/avatar.tsx +53 -0
  21. package/src/client/components/ui/button.tsx +2 -1
  22. package/src/client/components/ui/dialog.tsx +66 -0
  23. package/src/client/components/ui/empty.tsx +32 -0
  24. package/src/client/components/ui/input-group.tsx +96 -0
  25. package/src/client/components/ui/pagination.tsx +60 -0
  26. package/src/client/components/ui/spinner.tsx +21 -0
  27. package/src/client/components/ui/table.tsx +22 -1
  28. package/src/client/components/ui/tooltip.tsx +1 -1
  29. package/src/client/conversations/ConversationChildAgents.tsx +5 -5
  30. package/src/client/conversations/ConversationHeader.tsx +5 -4
  31. package/src/client/conversations/ConversationHeaderActions.tsx +7 -11
  32. package/src/client/conversations/ConversationSidebar.tsx +5 -4
  33. package/src/client/conversations/ConversationUsageBreakdown.tsx +108 -65
  34. package/src/client/conversations/ConversationWatches.tsx +67 -59
  35. package/src/client/conversations/DetailsList.tsx +44 -29
  36. package/src/client/conversations/EventUsage.tsx +99 -74
  37. package/src/client/conversations/PendingMailboxStack.tsx +5 -10
  38. package/src/client/conversations/README.md +3 -0
  39. package/src/client/conversations/ShareConversationButton.tsx +3 -4
  40. package/src/client/conversations/TranscriptEventView.tsx +35 -16
  41. package/src/client/conversations/TranscriptMessageContextView.tsx +31 -20
  42. package/src/client/conversations/TranscriptStructuredEventView.tsx +25 -15
  43. package/src/client/conversations/TranscriptSummary.tsx +34 -2
  44. package/src/client/conversations/TranscriptTurnContextView.tsx +45 -38
  45. package/src/client/conversations/eventLog.ts +1 -0
  46. package/src/client/conversations/transcriptMentions.tsx +18 -5
  47. package/src/mock-reporting/watches.ts +1 -1
@@ -1,8 +1,9 @@
1
+ import { Collapsible, CollapsibleContent } from "../components/ui/collapsible";
1
2
  import { formatMessageTimestamp } from "../format";
2
3
  import type { TranscriptViewStructuredEventPart } from "../types";
3
4
  import { MentionText } from "./transcriptMentions";
4
5
  import { HighlightText, useTranscriptSearch } from "./transcriptSearch";
5
- import { TranscriptSummary } from "./TranscriptSummary";
6
+ import { TranscriptSummaryTrigger } from "./TranscriptSummary";
6
7
 
7
8
  type TranscriptPresentationEventPart = TranscriptViewStructuredEventPart;
8
9
 
@@ -58,27 +59,34 @@ export function TranscriptStructuredEventView(props: {
58
59
  ))}
59
60
  </div>
60
61
  ) : null;
62
+ const collapsible = details.length > 0 && !search.active;
63
+ // The header can sit inside the row button, so it uses only inline elements
64
+ // and no links.
61
65
  const header = (
62
- <div className="grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-start gap-2 max-md:grid-cols-[minmax(0,1fr)]">
63
- <div className="min-w-0">
64
- <div className="text-sm font-medium leading-6 text-dashboard-text">
66
+ <span className="grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-start gap-2 max-md:grid-cols-[minmax(0,1fr)]">
67
+ <span className="min-w-0">
68
+ <span className="block text-sm font-medium leading-6 text-dashboard-text">
65
69
  <HighlightText text={presentation.title} />
66
- </div>
70
+ </span>
67
71
  {presentation.preview ? (
68
- <div className="mt-0.5 truncate text-xs text-dashboard-text-muted">
69
- <MentionText mentions={mentions} text={presentation.preview} />
70
- </div>
72
+ <span className="mt-0.5 block truncate text-xs text-dashboard-text-muted">
73
+ <MentionText
74
+ links={!collapsible}
75
+ mentions={mentions}
76
+ text={presentation.preview}
77
+ />
78
+ </span>
71
79
  ) : null}
72
- </div>
80
+ </span>
73
81
  {timestamp ? (
74
82
  <span className="text-xs leading-6 tabular-nums text-dashboard-text-muted max-md:hidden">
75
83
  {timestamp}
76
84
  </span>
77
85
  ) : null}
78
- </div>
86
+ </span>
79
87
  );
80
88
 
81
- if (details.length === 0 || search.active) {
89
+ if (!collapsible) {
82
90
  return (
83
91
  <div className={surfaceClass}>
84
92
  {header}
@@ -87,9 +95,11 @@ export function TranscriptStructuredEventView(props: {
87
95
  );
88
96
  }
89
97
  return (
90
- <details className={`group/plugin-event ${surfaceClass}`}>
91
- <TranscriptSummary flush>{header}</TranscriptSummary>
92
- {body}
93
- </details>
98
+ <Collapsible className={surfaceClass}>
99
+ <TranscriptSummaryTrigger flush className="block">
100
+ {header}
101
+ </TranscriptSummaryTrigger>
102
+ <CollapsibleContent hiddenUntilFound>{body}</CollapsibleContent>
103
+ </Collapsible>
94
104
  );
95
105
  }
@@ -1,7 +1,11 @@
1
- import type { ComponentPropsWithoutRef } from "react";
1
+ import type { ComponentProps, ComponentPropsWithoutRef } from "react";
2
2
 
3
+ import { CollapsibleTrigger } from "../components/ui/collapsible";
3
4
  import { cn } from "../styles";
4
5
 
6
+ const summaryClass =
7
+ "cursor-pointer rounded-md px-2 py-1.5 transition-colors hover:bg-dashboard-fill-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus";
8
+
5
9
  /** Keep hover and keyboard focus around a padded transcript cell. */
6
10
  export function TranscriptSummary({
7
11
  className,
@@ -15,10 +19,38 @@ export function TranscriptSummary({
15
19
  <summary
16
20
  {...props}
17
21
  className={cn(
18
- "cursor-pointer list-none rounded-md px-2 py-1.5 transition-colors hover:bg-dashboard-fill-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus [&::-webkit-details-marker]:hidden",
22
+ summaryClass,
23
+ "list-none [&::-webkit-details-marker]:hidden",
19
24
  flush && "-mx-2 -my-1.5",
20
25
  className,
21
26
  )}
22
27
  />
23
28
  );
24
29
  }
30
+
31
+ /**
32
+ * Give a `Collapsible` trigger the same padding, hover, and focus as
33
+ * `TranscriptSummary`. The caller sets the display. Put only inline elements,
34
+ * such as `span`, inside the trigger.
35
+ */
36
+ export function TranscriptSummaryTrigger({
37
+ className,
38
+ flush = false,
39
+ ...props
40
+ }: Omit<ComponentProps<typeof CollapsibleTrigger>, "className"> & {
41
+ className?: string;
42
+ /** Extend padding around text that already sits on the transcript edge. */
43
+ flush?: boolean;
44
+ }) {
45
+ return (
46
+ <CollapsibleTrigger
47
+ {...props}
48
+ className={cn(
49
+ summaryClass,
50
+ "text-left disabled:cursor-default disabled:hover:bg-transparent",
51
+ flush ? "-mx-2 -my-1.5 w-[calc(100%+1rem)]" : "w-full",
52
+ className,
53
+ )}
54
+ />
55
+ );
56
+ }
@@ -9,13 +9,14 @@ import {
9
9
  import { createPortal } from "react-dom";
10
10
 
11
11
  import { DetailsPanel } from "../components/DetailsPanel";
12
+ import { Collapsible, CollapsibleContent } from "../components/ui/collapsible";
12
13
  import { Drawer } from "../components/Drawer";
13
14
  import type { TranscriptViewTurnContext } from "../types";
14
15
  import { formatMessageTimestamp, formatTime } from "../format";
15
16
  import { memoryRecallContent, type MemoryRecallContent } from "./turnContext";
16
- import { cn } from "../styles";
17
+ import { cn, dashboardDisclosureChevronClass } from "../styles";
17
18
  import { HighlightText } from "./transcriptSearch";
18
- import { TranscriptSummary } from "./TranscriptSummary";
19
+ import { TranscriptSummaryTrigger } from "./TranscriptSummary";
19
20
 
20
21
  /** The turn context that a page panel shows, keyed by its message. */
21
22
  export type TurnContextSelection = {
@@ -189,15 +190,17 @@ function MemoryRecall(props: {
189
190
  return (
190
191
  <div className="overflow-hidden rounded-lg border border-dashboard-border-strong">
191
192
  {props.memories.map((memory, index) => (
192
- <details
193
- className="group/memory border-t border-dashboard-border-strong first:border-t-0"
193
+ <Collapsible
194
+ className="border-t border-dashboard-border-strong first:border-t-0"
194
195
  key={memory.id}
195
196
  >
196
- <TranscriptSummary className="flex items-start gap-2.5">
197
+ <TranscriptSummaryTrigger className="group flex items-start gap-2.5">
197
198
  <ChevronRight
198
199
  aria-hidden="true"
199
- className="mt-0.5 shrink-0 text-dashboard-text-muted transition-transform group-open/memory:rotate-90"
200
- size={15}
200
+ className={cn(
201
+ dashboardDisclosureChevronClass,
202
+ "mt-0.5 text-dashboard-text-muted",
203
+ )}
201
204
  />
202
205
  <span className="min-w-0 flex-1">
203
206
  <span className="flex items-center justify-between gap-3">
@@ -212,39 +215,43 @@ function MemoryRecall(props: {
212
215
  <HighlightText text={memory.content} />
213
216
  </span>
214
217
  </span>
215
- </TranscriptSummary>
216
-
217
- <div className="border-t border-dashboard-border bg-dashboard-fill-faint px-4 py-4">
218
- <div className="whitespace-pre-wrap text-sm leading-6 text-dashboard-text">
219
- <HighlightText text={memory.content} />
220
- </div>
218
+ </TranscriptSummaryTrigger>
221
219
 
222
- <dl className="mt-4 grid gap-2 text-xs">
223
- <div className="grid grid-cols-[5rem_minmax(0,1fr)] items-start gap-3">
224
- <dt className="text-dashboard-text-muted">Memory ID</dt>
225
- <dd className="m-0 break-all font-mono text-dashboard-text-muted">
226
- <HighlightText text={memory.id} />
227
- </dd>
228
- </div>
229
- <div className="grid grid-cols-[5rem_minmax(0,1fr)] items-start gap-3">
230
- <dt className="text-dashboard-text-muted">Observed</dt>
231
- <dd className="m-0 text-dashboard-text-muted">
232
- {formatTime(new Date(memory.observedAtMs).toISOString())}
233
- </dd>
234
- </div>
235
- <div className="grid grid-cols-[5rem_minmax(0,1fr)] items-start gap-3">
236
- <dt className="text-dashboard-text-muted">Scope</dt>
237
- <dd className="m-0 text-dashboard-text-muted">
238
- {memory.scope}
239
- </dd>
240
- </div>
241
- <div className="grid grid-cols-[5rem_minmax(0,1fr)] items-start gap-3">
242
- <dt className="text-dashboard-text-muted">Kind</dt>
243
- <dd className="m-0 text-dashboard-text-muted">{memory.kind}</dd>
220
+ <CollapsibleContent hiddenUntilFound>
221
+ <div className="border-t border-dashboard-border bg-dashboard-fill-faint px-4 py-4">
222
+ <div className="whitespace-pre-wrap text-sm leading-6 text-dashboard-text">
223
+ <HighlightText text={memory.content} />
244
224
  </div>
245
- </dl>
246
- </div>
247
- </details>
225
+
226
+ <dl className="mt-4 grid gap-2 text-xs">
227
+ <div className="grid grid-cols-[5rem_minmax(0,1fr)] items-start gap-3">
228
+ <dt className="text-dashboard-text-muted">Memory ID</dt>
229
+ <dd className="m-0 break-all font-mono text-dashboard-text-muted">
230
+ <HighlightText text={memory.id} />
231
+ </dd>
232
+ </div>
233
+ <div className="grid grid-cols-[5rem_minmax(0,1fr)] items-start gap-3">
234
+ <dt className="text-dashboard-text-muted">Observed</dt>
235
+ <dd className="m-0 text-dashboard-text-muted">
236
+ {formatTime(new Date(memory.observedAtMs).toISOString())}
237
+ </dd>
238
+ </div>
239
+ <div className="grid grid-cols-[5rem_minmax(0,1fr)] items-start gap-3">
240
+ <dt className="text-dashboard-text-muted">Scope</dt>
241
+ <dd className="m-0 text-dashboard-text-muted">
242
+ {memory.scope}
243
+ </dd>
244
+ </div>
245
+ <div className="grid grid-cols-[5rem_minmax(0,1fr)] items-start gap-3">
246
+ <dt className="text-dashboard-text-muted">Kind</dt>
247
+ <dd className="m-0 text-dashboard-text-muted">
248
+ {memory.kind}
249
+ </dd>
250
+ </div>
251
+ </dl>
252
+ </div>
253
+ </CollapsibleContent>
254
+ </Collapsible>
248
255
  ))}
249
256
  <p className="m-0 border-t border-dashboard-border-strong px-3 py-2 text-xs text-dashboard-text-muted">
250
257
  {props.memories.length}{" "}
@@ -111,6 +111,7 @@ export function eventLogTone(data: ConversationReportEventData): string {
111
111
  }
112
112
  if (
113
113
  (data.type === "guardian_action_reviewed" && data.decision === "ask") ||
114
+ (data.type === "turn_lifecycle" && data.state === "blocked") ||
114
115
  (data.type === "subagent" &&
115
116
  (data.status === "aborted" || data.status === "blocked"))
116
117
  ) {
@@ -43,12 +43,17 @@ export function MentionPeopleProvider(props: {
43
43
 
44
44
  /** Render one line of display text with the people the API resolved for it. */
45
45
  export function MentionText(props: {
46
+ /**
47
+ * Link people to their page. Set false inside a button, which cannot hold
48
+ * a link.
49
+ */
50
+ links?: boolean;
46
51
  mentions: readonly ActorIdentity[];
47
52
  text: string;
48
53
  }) {
49
54
  return (
50
55
  <MentionPeopleProvider mentions={props.mentions}>
51
- {renderMentionText(props.text, "mention-text")}
56
+ {renderMentionText(props.text, "mention-text", props.links)}
52
57
  </MentionPeopleProvider>
53
58
  );
54
59
  }
@@ -57,7 +62,11 @@ export function MentionText(props: {
57
62
  * Render text with Slack `<@U123>` and `<#C123|name>` tokens as readable
58
63
  * mentions. It reads people from the nearest `MentionPeopleProvider`.
59
64
  */
60
- export function renderMentionText(text: string, keyBase: string): ReactNode[] {
65
+ export function renderMentionText(
66
+ text: string,
67
+ keyBase: string,
68
+ links = true,
69
+ ): ReactNode[] {
61
70
  const nodes: ReactNode[] = [];
62
71
  let cursor = 0;
63
72
  for (const mention of findMentions(text)) {
@@ -70,7 +79,11 @@ export function renderMentionText(text: string, keyBase: string): ReactNode[] {
70
79
  );
71
80
  }
72
81
  nodes.push(
73
- <MentionView key={`${keyBase}-m-${mention.start}`} mention={mention} />,
82
+ <MentionView
83
+ key={`${keyBase}-m-${mention.start}`}
84
+ links={links}
85
+ mention={mention}
86
+ />,
74
87
  );
75
88
  cursor = mention.end;
76
89
  }
@@ -95,13 +108,13 @@ function findMentions(text: string): TranscriptMention[] {
95
108
  }));
96
109
  }
97
110
 
98
- function MentionView(props: { mention: TranscriptMention }) {
111
+ function MentionView(props: { links: boolean; mention: TranscriptMention }) {
99
112
  const people = useContext(TranscriptMentionPeople);
100
113
  const { id, label, sigil } = props.mention;
101
114
  const person = sigil === "@" ? people.get(id) : undefined;
102
115
  const name = person?.fullName ?? person?.slackUserName ?? label ?? id;
103
116
  const text = <HighlightText text={`${sigil}${name.replace(/^[@#]/, "")}`} />;
104
- if (person?.email) {
117
+ if (props.links && person?.email) {
105
118
  return (
106
119
  <Link
107
120
  // Previews sit in cards that disable pointer events on their text.
@@ -8,7 +8,7 @@ const HOUR_MS = 60 * 60_000;
8
8
  const DAY_MS = 24 * HOUR_MS;
9
9
 
10
10
  // Watch times are relative to the real clock, not the fixed fixture time, so
11
- // time-left labels stay readable.
11
+ // the timer fire time stays in the future.
12
12
  function iso(offsetMs: number): string {
13
13
  return new Date(Date.now() + offsetMs).toISOString();
14
14
  }