@sentry/junior-dashboard 0.275.0 → 0.277.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 (68) hide show
  1. package/dist/app.js +194 -40
  2. package/dist/client.js +120 -92
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +189 -38
  5. package/dist/tailwind.css +1 -1
  6. package/package.json +5 -4
  7. package/src/api/schema.ts +20 -2
  8. package/src/app.ts +88 -15
  9. package/src/auth.ts +5 -1
  10. package/src/client/components/FormNotice.tsx +11 -14
  11. package/src/client/components/ProfileMenu.tsx +5 -6
  12. package/src/client/conversations/AutoArchiveToggle.tsx +35 -0
  13. package/src/client/conversations/ConversationArchiveNotices.tsx +1 -1
  14. package/src/client/conversations/ConversationChildAgents.tsx +2 -1
  15. package/src/client/conversations/ConversationComposer.tsx +42 -26
  16. package/src/client/conversations/ConversationDetails.tsx +66 -35
  17. package/src/client/conversations/ConversationHeader.tsx +19 -2
  18. package/src/client/conversations/ConversationHeaderActions.tsx +23 -0
  19. package/src/client/conversations/ConversationHomeList.tsx +1 -1
  20. package/src/client/conversations/ConversationListStatusIcon.tsx +16 -11
  21. package/src/client/conversations/ConversationMemories.tsx +114 -77
  22. package/src/client/conversations/ConversationMeta.tsx +47 -42
  23. package/src/client/conversations/ConversationPage.tsx +183 -60
  24. package/src/client/conversations/ConversationSidebar.tsx +80 -24
  25. package/src/client/conversations/ConversationTranscript.tsx +37 -8
  26. package/src/client/conversations/ConversationWatches.tsx +42 -26
  27. package/src/client/conversations/ConversationWorkspace.tsx +4 -4
  28. package/src/client/conversations/DetailsList.tsx +8 -14
  29. package/src/client/conversations/MemoryIcons.tsx +23 -0
  30. package/src/client/conversations/PendingMailboxStack.tsx +11 -1
  31. package/src/client/conversations/QuickQuestion.tsx +476 -0
  32. package/src/client/conversations/TelemetryMetrics.tsx +2 -1
  33. package/src/client/conversations/TranscriptActivityGroup.tsx +5 -0
  34. package/src/client/conversations/TranscriptEventView.tsx +25 -2
  35. package/src/client/conversations/TranscriptMemoryPopover.tsx +174 -0
  36. package/src/client/conversations/TranscriptMessageView.tsx +46 -16
  37. package/src/client/conversations/TranscriptRows.tsx +9 -2
  38. package/src/client/conversations/TranscriptTurnContextView.tsx +9 -5
  39. package/src/client/conversations/UsageCard.tsx +3 -3
  40. package/src/client/conversations/autoArchiveQueries.ts +53 -0
  41. package/src/client/conversations/conversationArchive.ts +255 -0
  42. package/src/client/conversations/conversationMemory.ts +139 -0
  43. package/src/client/conversations/conversationSections.ts +24 -18
  44. package/src/client/conversations/eventTranscript.ts +8 -0
  45. package/src/client/conversations/queries.ts +48 -238
  46. package/src/client/conversations/usePinConversation.ts +88 -0
  47. package/src/client/display-text/DisplayText.tsx +7 -2
  48. package/src/client/display-text/codeBlocks.ts +92 -0
  49. package/src/client/format.ts +3 -67
  50. package/src/client/pages/code/RepositoryTabs.tsx +2 -2
  51. package/src/client/pages/settings/AutoArchiveSettings.tsx +145 -0
  52. package/src/client/pages/settings/SettingsPage.tsx +2 -0
  53. package/src/client/review-desk/ConversationReviewItems.tsx +7 -9
  54. package/src/client/review-desk/ReviewItemActions.tsx +1 -1
  55. package/src/client/review-desk/ReviewItemChangeRow.tsx +85 -0
  56. package/src/client/review-desk/ReviewItemContext.tsx +41 -23
  57. package/src/client/review-desk/queries.ts +2 -1
  58. package/src/client/review-desk/reviewItemFormat.ts +16 -0
  59. package/src/client/sentry.ts +77 -0
  60. package/src/client/types.ts +7 -0
  61. package/src/client.tsx +3 -0
  62. package/src/conversation-cache.ts +1 -1
  63. package/src/mock-reporting/fixtures.ts +103 -27
  64. package/src/mock-reporting/routes.ts +47 -0
  65. package/src/mock-reporting/watches.ts +2 -0
  66. package/src/shell.ts +27 -2
  67. package/src/tailwind.css +8 -19
  68. package/src/client/components/ActiveIndicator.tsx +0 -14
@@ -28,6 +28,11 @@ import { TranscriptToolView } from "./TranscriptToolView";
28
28
  import { TranscriptReasoningView } from "./TranscriptReasoningView";
29
29
  import { TranscriptAttachmentsDeliveredView } from "./TranscriptAttachmentsDeliveredView";
30
30
  import { TranscriptStructuredEventView } from "./TranscriptStructuredEventView";
31
+ import {
32
+ TranscriptCapturedMemories,
33
+ TranscriptRecalledMemories,
34
+ } from "./TranscriptMemoryPopover";
35
+ import { isMemoryCaptureEvent } from "./conversationMemory";
31
36
  import { TranscriptFailureView } from "./TranscriptFailureView";
32
37
  import {
33
38
  structuredEventIcon,
@@ -248,16 +253,23 @@ const VisibleTranscriptEntries = memo(function VisibleTranscriptEntries(props: {
248
253
  renderMessage={(entry) =>
249
254
  props.conversation.sourceTask &&
250
255
  isAutomationTriggerMessage(props.conversation, entry.message) ? (
251
- <TranscriptAutomationTriggerView
252
- message={entry.message}
253
- sourceTask={props.conversation.sourceTask}
254
- />
256
+ <div className="grid min-w-0 gap-2">
257
+ <TranscriptAutomationTriggerView
258
+ message={entry.message}
259
+ sourceTask={props.conversation.sourceTask}
260
+ />
261
+ <TranscriptRecalledMemories message={entry.message} />
262
+ </div>
255
263
  ) : entry.message.eventType ? (
256
264
  <TranscriptRailEvent
257
265
  kind="event"
258
266
  objectType={entry.message.eventObjectType}
259
267
  >
260
268
  <TranscriptEventView message={entry.message} />
269
+ <TranscriptRecalledMemories
270
+ className="mt-1"
271
+ message={entry.message}
272
+ />
261
273
  </TranscriptRailEvent>
262
274
  ) : entry.message.context ? (
263
275
  <TranscriptRailEvent kind="message_context">
@@ -265,6 +277,10 @@ const VisibleTranscriptEntries = memo(function VisibleTranscriptEntries(props: {
265
277
  message={entry.message}
266
278
  conversation={props.conversation}
267
279
  />
280
+ <TranscriptRecalledMemories
281
+ className="mt-2"
282
+ message={entry.message}
283
+ />
268
284
  </TranscriptRailEvent>
269
285
  ) : (
270
286
  <TranscriptMessageView
@@ -285,10 +301,23 @@ const VisibleTranscriptEntries = memo(function VisibleTranscriptEntries(props: {
285
301
  icon={structuredEventIcon(entry.part.presentation.icon)}
286
302
  kind="structured_event"
287
303
  >
288
- <TranscriptStructuredEventView
289
- part={entry.part}
290
- timestamp={entry.timestamp}
291
- />
304
+ {isMemoryCaptureEvent(entry.part) ? (
305
+ <TranscriptCapturedMemories
306
+ fallback={
307
+ <TranscriptStructuredEventView
308
+ part={entry.part}
309
+ timestamp={entry.timestamp}
310
+ />
311
+ }
312
+ timestamp={entry.timestamp}
313
+ turnId={entry.part.turnId}
314
+ />
315
+ ) : (
316
+ <TranscriptStructuredEventView
317
+ part={entry.part}
318
+ timestamp={entry.timestamp}
319
+ />
320
+ )}
292
321
  </TranscriptRailEvent>
293
322
  )}
294
323
  renderSubagent={(entry) => (
@@ -15,7 +15,7 @@ import {
15
15
  Timer,
16
16
  Users,
17
17
  } from "lucide-react";
18
- import { useState, type ReactNode } from "react";
18
+ import type { ReactNode } from "react";
19
19
  import {
20
20
  conversationWatchesReportSchema,
21
21
  type ConversationWatch,
@@ -24,6 +24,12 @@ import {
24
24
  import { DisplayText } from "../display-text/DisplayText";
25
25
  import { formatTime } from "../format";
26
26
  import { fetchDashboardJson } from "../http";
27
+ import { conversationSideReadQueryKey } from "./queries";
28
+ import {
29
+ Collapsible,
30
+ CollapsibleContent,
31
+ CollapsibleTrigger,
32
+ } from "../components/ui/collapsible";
27
33
  import { dashboardDisclosureChevronClass } from "../styles";
28
34
  import { DetailsList, DetailsRow } from "./DetailsList";
29
35
 
@@ -36,7 +42,7 @@ export function useConversationWatches(conversationId: string) {
36
42
  `/api/conversations/${encodeURIComponent(conversationId)}/watches`,
37
43
  signal,
38
44
  ),
39
- queryKey: ["conversation", conversationId, "watches"],
45
+ queryKey: conversationSideReadQueryKey(conversationId, "watches"),
40
46
  retry: false,
41
47
  });
42
48
  }
@@ -69,13 +75,13 @@ const resourceIcons: Partial<Record<string, LucideIcon>> = {
69
75
  };
70
76
 
71
77
  /**
72
- * Show one Watch as a single line with its resource. Clicking the line shows
73
- * why the Watch exists and which events wake the Conversation. The expiry is
74
- * an internal limit, so the row does not show it.
78
+ * Show one Watch as a single line with its resource. When the resource has a
79
+ * web page, the line opens it and a chevron shows the details. Otherwise the
80
+ * line itself shows why the Watch exists and which events wake the
81
+ * Conversation. The expiry is an internal limit, so the row does not show it.
75
82
  */
76
83
  function WatchRow(props: { watch: ConversationWatch }) {
77
84
  const { watch } = props;
78
- const [expanded, setExpanded] = useState(false);
79
85
  const Icon = watch.redacted
80
86
  ? LockKeyhole
81
87
  : (resourceIcons[watch.resourceType] ?? Eye);
@@ -83,29 +89,39 @@ function WatchRow(props: { watch: ConversationWatch }) {
83
89
  // A timer has one event, and its fire time already says when it wakes.
84
90
  const events = watch.firesAt ? [] : watch.events;
85
91
  const filters = Object.entries(watch.match ?? {});
86
- const row = (
87
- <DetailsRow
88
- expanded={expanded}
89
- icon={<Icon aria-hidden="true" className="size-3.5" />}
90
- meta={
91
- <span className="grid size-5 place-items-center">
92
- <ChevronRight
93
- aria-hidden="true"
94
- className={dashboardDisclosureChevronClass}
95
- />
96
- </span>
97
- }
98
- onToggle={() => setExpanded((value) => !value)}
99
- title={watch.label ?? `Private ${resource}`}
100
- />
92
+ const icon = <Icon aria-hidden="true" className="size-3.5" />;
93
+ const title = watch.label ?? `Private ${resource}`;
94
+ const chevron = (
95
+ <span className="grid size-5 place-items-center">
96
+ <ChevronRight
97
+ aria-hidden="true"
98
+ className={dashboardDisclosureChevronClass}
99
+ />
100
+ </span>
101
+ );
102
+ const row = watch.url ? (
103
+ // The row opens the watched resource, like a linked work row. The
104
+ // chevron beside it shows the details.
105
+ <div className="flex min-w-0 items-start gap-0.5">
106
+ <div className="min-w-0 flex-1">
107
+ <DetailsRow href={watch.url} icon={icon} title={title} />
108
+ </div>
109
+ <CollapsibleTrigger
110
+ aria-label={`Details for ${title}`}
111
+ className="group grid size-8 shrink-0 place-items-center rounded-md text-dashboard-text-muted transition-colors hover:bg-dashboard-surface-hover hover:text-dashboard-text focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus"
112
+ >
113
+ {chevron}
114
+ </CollapsibleTrigger>
115
+ </div>
116
+ ) : (
117
+ <DetailsRow disclosure icon={icon} meta={chevron} title={title} />
101
118
  );
102
- if (!expanded) return row;
103
119
 
104
120
  return (
105
- <>
121
+ <Collapsible className="min-w-0">
106
122
  {row}
107
123
  {/* Indent under the title: icon column, column gap, and row padding. */}
108
- <div className="grid min-w-0 gap-1.5 pb-2 pl-9 pr-1.5 pt-0.5 text-xs leading-4 text-dashboard-text-muted">
124
+ <CollapsibleContent className="grid min-w-0 gap-1.5 pb-2 pl-9 pr-1.5 pt-0.5 text-xs leading-4 text-dashboard-text-muted">
109
125
  {watch.intent ? (
110
126
  <p className="m-0 break-words">
111
127
  <DisplayText inline text={watch.intent} />
@@ -144,8 +160,8 @@ function WatchRow(props: { watch: ConversationWatch }) {
144
160
  </span>
145
161
  </p>
146
162
  ) : null}
147
- </div>
148
- </>
163
+ </CollapsibleContent>
164
+ </Collapsible>
149
165
  );
150
166
  }
151
167
 
@@ -11,14 +11,14 @@ import { pageCount, pageItems, PagePagination } from "../components/Pagination";
11
11
  import { ConversationHomeList } from "./ConversationHomeList";
12
12
  import { ConversationComposer } from "./ConversationComposer";
13
13
  import {
14
- useCreateConversation,
15
14
  usePendingArchiveConversationUpdates,
16
15
  type PendingArchiveConversationUpdate,
17
- } from "./queries";
16
+ } from "./conversationArchive";
17
+ import { useCreateConversation } from "./queries";
18
18
  import { conversationPath, NEW_CONVERSATION_PATH } from "./conversationRoutes";
19
19
  import {
20
20
  attentionConversationIds,
21
- attentionFirst,
21
+ pinnedAndAttentionFirst,
22
22
  } from "./conversationSections";
23
23
  import { buildConversations, getDashboardTimeZone } from "../format";
24
24
  import { useDebouncedValue } from "../debouncedValue";
@@ -73,7 +73,7 @@ export function ConversationWorkspace() {
73
73
  const pagedConversations = useMemo(
74
74
  () =>
75
75
  pageItems(
76
- attentionFirst(
76
+ pinnedAndAttentionFirst(
77
77
  conversations,
78
78
  attentionConversationIds(
79
79
  waitingConversationIds,
@@ -1,6 +1,7 @@
1
1
  import { ChevronRight } from "lucide-react";
2
2
  import { useState, type ReactNode } from "react";
3
3
 
4
+ import { CollapsibleTrigger } from "../components/ui/collapsible";
4
5
  import { cn, dashboardDisclosureChevronClass } from "../styles";
5
6
 
6
7
  /**
@@ -87,21 +88,20 @@ export function DetailsList<T>(props: {
87
88
  /**
88
89
  * Lay out one details list row: an icon, a title, and short trailing text on
89
90
  * the first line, then optional lines under the title. A row with `href`
90
- * opens that link in a new tab. A row with `onToggle` is a disclosure button
91
- * that shows or hides content the caller renders under it.
91
+ * opens that link in a new tab. A `disclosure` row is the trigger of the
92
+ * enclosing `Collapsible`, so it shows or hides the content under it.
92
93
  */
93
94
  export function DetailsRow(props: {
94
95
  children?: ReactNode;
95
- expanded?: boolean;
96
+ disclosure?: boolean;
96
97
  href?: string;
97
98
  icon: ReactNode;
98
99
  meta?: ReactNode;
99
- onToggle?(): void;
100
100
  title: ReactNode;
101
101
  /** Native hover text with detail that the row does not show. */
102
102
  tooltip?: string;
103
103
  }) {
104
- const interactive = props.href !== undefined || props.onToggle !== undefined;
104
+ const interactive = props.href !== undefined || props.disclosure === true;
105
105
  const className = cn(
106
106
  "grid w-full min-w-0 grid-cols-[auto_minmax(0,1fr)_auto] items-start gap-x-2.5 gap-y-1 rounded-md px-1.5 py-1.5 text-left text-inherit no-underline",
107
107
  interactive &&
@@ -134,17 +134,11 @@ export function DetailsRow(props: {
134
134
  </a>
135
135
  );
136
136
  }
137
- if (props.onToggle) {
137
+ if (props.disclosure) {
138
138
  return (
139
- <button
140
- aria-expanded={props.expanded ?? false}
141
- className={className}
142
- onClick={props.onToggle}
143
- title={props.tooltip}
144
- type="button"
145
- >
139
+ <CollapsibleTrigger className={className} title={props.tooltip}>
146
140
  {content}
147
- </button>
141
+ </CollapsibleTrigger>
148
142
  );
149
143
  }
150
144
  return (
@@ -0,0 +1,23 @@
1
+ import { ArrowUpRight, Lock } from "lucide-react";
2
+
3
+ /** Mark a memory that only its owner can see. */
4
+ export function PrivateMemoryIcon() {
5
+ return <Lock aria-label="Private" role="img" size={13} strokeWidth={1.8} />;
6
+ }
7
+
8
+ /**
9
+ * End a memory row that links to the memory's page. Its parent link needs
10
+ * the `group` class, so the arrow follows the row hover.
11
+ */
12
+ export function MemoryLinkIcons(props: { private: boolean }) {
13
+ return (
14
+ <span className="flex items-center gap-1.5 pt-1 text-dashboard-text-faint">
15
+ {props.private ? <PrivateMemoryIcon /> : null}
16
+ <ArrowUpRight
17
+ aria-hidden="true"
18
+ className="transition-colors group-hover:text-dashboard-text"
19
+ size={14}
20
+ />
21
+ </span>
22
+ );
23
+ }
@@ -42,6 +42,7 @@ import {
42
42
  } from "./TranscriptHeadingRow";
43
43
  import { SlackMark } from "./SlackMark";
44
44
  import { DisplayText } from "../display-text/DisplayText";
45
+ import { ReviewItemChangeLabel } from "../review-desk/ReviewItemChangeRow";
45
46
  import { displayPlainText } from "../display-text/plainText";
46
47
 
47
48
  // Rows are one line each, so the stack can show five before it collapses.
@@ -349,7 +350,16 @@ function PendingRow(props: {
349
350
  className="m-0 min-w-0 truncate font-sans text-sm leading-snug text-dashboard-text-subtle"
350
351
  title={displayPlainText(text, mentions)}
351
352
  >
352
- <DisplayText inline mentions={mentions} text={text} />
353
+ {props.message.reviewItemChange ? (
354
+ <ReviewItemChangeLabel
355
+ fallback={
356
+ <DisplayText inline mentions={mentions} text={text} />
357
+ }
358
+ record={props.message.reviewItemChange}
359
+ />
360
+ ) : (
361
+ <DisplayText inline mentions={mentions} text={text} />
362
+ )}
353
363
  </p>
354
364
  ) : null}
355
365
  </>