@sentry/junior-dashboard 0.276.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.
Files changed (73) hide show
  1. package/dist/app.js +196 -72
  2. package/dist/client.js +94 -89
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +191 -70
  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 +80 -12
  9. package/src/auth.ts +5 -1
  10. package/src/client/App.tsx +9 -4
  11. package/src/client/components/DetailsPanel.tsx +1 -1
  12. package/src/client/components/FormNotice.tsx +11 -14
  13. package/src/client/components/ProfileMenu.tsx +5 -6
  14. package/src/client/components/ui/resizable.tsx +50 -0
  15. package/src/client/conversations/AutoArchiveToggle.tsx +71 -0
  16. package/src/client/conversations/ConversationArchiveNotices.tsx +1 -1
  17. package/src/client/conversations/ConversationChildAgents.tsx +2 -1
  18. package/src/client/conversations/ConversationDetails.tsx +6 -3
  19. package/src/client/conversations/ConversationHeader.tsx +15 -2
  20. package/src/client/conversations/ConversationHeaderActions.tsx +27 -0
  21. package/src/client/conversations/ConversationHomeList.tsx +1 -1
  22. package/src/client/conversations/ConversationMemories.tsx +64 -67
  23. package/src/client/conversations/ConversationMeta.tsx +2 -7
  24. package/src/client/conversations/ConversationPage.tsx +175 -79
  25. package/src/client/conversations/ConversationSidebar.tsx +82 -25
  26. package/src/client/conversations/ConversationTranscript.tsx +11 -8
  27. package/src/client/conversations/ConversationWatches.tsx +15 -21
  28. package/src/client/conversations/ConversationWorkspace.tsx +132 -44
  29. package/src/client/conversations/ConversationWorkspaceLoading.tsx +17 -3
  30. package/src/client/conversations/DetailsList.tsx +8 -14
  31. package/src/client/conversations/EventDetails.tsx +2 -62
  32. package/src/client/conversations/MemoryIcons.tsx +23 -0
  33. package/src/client/conversations/TranscriptActivityGroup.tsx +36 -14
  34. package/src/client/conversations/TranscriptContextEventView.tsx +7 -18
  35. package/src/client/conversations/TranscriptMemoryPopover.tsx +20 -25
  36. package/src/client/conversations/TranscriptRailEvent.tsx +0 -8
  37. package/src/client/conversations/TranscriptTurnContextView.tsx +9 -5
  38. package/src/client/conversations/TranscriptView.tsx +89 -20
  39. package/src/client/conversations/autoArchiveQueries.ts +53 -0
  40. package/src/client/conversations/conversationArchive.ts +255 -0
  41. package/src/client/conversations/conversationMemory.ts +2 -1
  42. package/src/client/conversations/conversationSections.ts +24 -18
  43. package/src/client/conversations/conversationSidebarLayout.ts +61 -0
  44. package/src/client/conversations/eventLog.ts +2 -31
  45. package/src/client/conversations/eventTranscript.ts +9 -42
  46. package/src/client/conversations/queries.ts +121 -250
  47. package/src/client/conversations/transcript.ts +46 -16
  48. package/src/client/conversations/transcriptBottomPinning.ts +40 -24
  49. package/src/client/conversations/transcriptReadPosition.ts +31 -7
  50. package/src/client/conversations/transcriptRenderModel.ts +1 -14
  51. package/src/client/conversations/transcriptSearch.tsx +3 -11
  52. package/src/client/conversations/transcriptUnreadHistory.ts +248 -0
  53. package/src/client/conversations/usePinConversation.ts +88 -0
  54. package/src/client/display-text/DisplayText.tsx +7 -2
  55. package/src/client/display-text/codeBlocks.ts +92 -0
  56. package/src/client/format.ts +3 -67
  57. package/src/client/http.ts +5 -5
  58. package/src/client/markdownExport.ts +32 -42
  59. package/src/client/pages/code/RepositoryTabs.tsx +2 -2
  60. package/src/client/pages/memory/MemoryDetails.tsx +6 -33
  61. package/src/client/pages/memory/MemoryPage.tsx +7 -92
  62. package/src/client/pages/memory/MemoryTimeline.tsx +16 -32
  63. package/src/client/pages/memory/memoryDashboard.ts +2 -38
  64. package/src/client/pages/memory/memoryRecord.ts +1 -6
  65. package/src/client/pages/settings/AutoArchiveSettings.tsx +145 -0
  66. package/src/client/pages/settings/SettingsPage.tsx +2 -0
  67. package/src/client/review-desk/ConversationReviewItems.tsx +7 -9
  68. package/src/client/review-desk/queries.ts +2 -1
  69. package/src/client/types.ts +19 -33
  70. package/src/conversation-cache.ts +7 -9
  71. package/src/mock-reporting/fixtures.ts +126 -60
  72. package/src/mock-reporting/routes.ts +50 -1
  73. package/src/tailwind.css +30 -0
@@ -135,12 +135,36 @@ const DASHBOARD_QA_PLAN_CHECK_ID =
135
135
  "junior:internal:dashboard-qa:advisor-plan:claim-check";
136
136
  /** Process-local archive state so mock restore can be exercised in visual QA. */
137
137
  const mockArchivedConversationIds = new Set<string>([ARCHIVED_CONVERSATION_ID]);
138
+ /** Process-local pins so the Pinned section can be exercised in visual QA. */
139
+ const mockPinnedConversationIds = new Set<string>();
138
140
 
139
- /** Restore process-local archive state so tests sharing a worker do not leak state. */
140
- export function resetMockConversationArchiveState(): void {
141
+ /** Process-local auto-archive choices for mock visual QA. */
142
+ const mockAutoArchiveByConversationId = new Map<string, boolean>();
143
+
144
+ /**
145
+ * Restore process-local archive, pin, and auto-archive state so tests sharing
146
+ * a worker do not leak state.
147
+ */
148
+ export function resetMockConversationViewerState(): void {
141
149
  mockArchivedConversationIds.clear();
142
150
  mockArchivedConversationIds.add(ARCHIVED_CONVERSATION_ID);
151
+ mockPinnedConversationIds.clear();
152
+ mockAutoArchiveByConversationId.clear();
143
153
  }
154
+
155
+ /** Add the viewer's mock archive and pin times to one conversation. */
156
+ function withMockViewerState<T extends { conversationId: string }>(
157
+ report: T & { archivedAt?: string | null },
158
+ ): T & { archivedAt: string | undefined; pinnedAt: string | null } {
159
+ return {
160
+ ...report,
161
+ archivedAt: mockArchivedConversationIds.has(report.conversationId)
162
+ ? (report.archivedAt ?? iso(NOW_MS, -2 * 24 * 60 * 60_000))
163
+ : undefined,
164
+ pinnedAt: mockPinnedConversationIds.has(report.conversationId) ? NOW : null,
165
+ };
166
+ }
167
+
144
168
  const PEOPLE_ACTIVITY_DAYS = 90;
145
169
  const PEOPLE_PROFILE_ACTIVITY_DAYS = 365;
146
170
  const PUBLIC_MOCK_CHANNEL_IDS = new Set([
@@ -452,16 +476,27 @@ function activeConversation(nowMs: number): ConversationDetailReport {
452
476
  ],
453
477
  },
454
478
  }),
455
- reportEvent(3, iso(Date.parse(startedAt), 9_000), {
456
- type: "turn_routed",
457
- turnId: "active-turn",
458
- modelProfile: "handoff",
459
- modelId: "openai/gpt-5.6-sol",
460
- reasoningLevel: "high",
461
- confidence: 0.93,
462
- source: "router",
463
- costUsd: 0.0012,
464
- }),
479
+ reportEvent(
480
+ 3,
481
+ iso(Date.parse(startedAt), 9_000),
482
+ {
483
+ type: "assistant_message",
484
+ parts: [
485
+ {
486
+ type: "reasoning",
487
+ text: "Compare the latest deploy with the previous baseline.",
488
+ },
489
+ ],
490
+ },
491
+ {
492
+ model: { modelId: "openai/gpt-5.6-sol" },
493
+ modelCall: {
494
+ provider: "vercel-ai-gateway",
495
+ api: "openai-responses",
496
+ stopReason: "toolUse",
497
+ },
498
+ },
499
+ ),
465
500
  reportEvent(
466
501
  4,
467
502
  iso(Date.parse(startedAt), 10_000),
@@ -476,11 +511,7 @@ function activeConversation(nowMs: number): ConversationDetailReport {
476
511
  ],
477
512
  },
478
513
  {
479
- model: {
480
- modelId: "openai/gpt-5.6-sol",
481
- modelProfile: "handoff",
482
- reasoningLevel: "high",
483
- },
514
+ model: { modelId: "openai/gpt-5.6-sol" },
484
515
  modelCall: {
485
516
  provider: "vercel-ai-gateway",
486
517
  api: "openai-responses",
@@ -528,11 +559,7 @@ function activeConversation(nowMs: number): ConversationDetailReport {
528
559
  ],
529
560
  },
530
561
  {
531
- model: {
532
- modelId: "openai/gpt-5.6-sol",
533
- modelProfile: "handoff",
534
- reasoningLevel: "high",
535
- },
562
+ model: { modelId: "openai/gpt-5.6-sol" },
536
563
  },
537
564
  ),
538
565
  // Mixed markdown keeps font/legibility QA honest for long assistant replies.
@@ -889,7 +916,6 @@ function dashboardQaConversation(nowMs: number): ConversationDetailReport {
889
916
  }),
890
917
  reportEvent(11, iso(Date.parse(startedAt), 56_000), {
891
918
  type: "compaction",
892
- modelProfile: "standard",
893
919
  modelId: "openai/gpt-5.4",
894
920
  details: {
895
921
  reason: "capacity",
@@ -1095,7 +1121,6 @@ function longConversation(nowMs: number): ConversationDetailReport {
1095
1121
  events.push(
1096
1122
  reportEvent(nextSeq, iso(Date.parse(startedAt), 53_000), {
1097
1123
  type: "compaction",
1098
- modelProfile: "standard",
1099
1124
  modelId: "openai/gpt-5.4",
1100
1125
  summary:
1101
1126
  "The release is complete. Preserve the published version and continue by checking the update pull request.",
@@ -1112,17 +1137,6 @@ function longConversation(nowMs: number): ConversationDetailReport {
1112
1137
  }),
1113
1138
  );
1114
1139
  nextSeq += 1;
1115
- events.push(
1116
- reportEvent(nextSeq, iso(Date.parse(startedAt), 90_000), {
1117
- type: "handoff",
1118
- modelProfile: "fast",
1119
- modelId: "openai/gpt-5-mini",
1120
- reasoningLevel: "medium",
1121
- summary:
1122
- "Investigate the remaining deployment checks and report any actionable failure.",
1123
- }),
1124
- );
1125
- nextSeq += 1;
1126
1140
  events.push(
1127
1141
  reportEvent(nextSeq, iso(Date.parse(startedAt), 166_000), {
1128
1142
  type: "message",
@@ -1657,19 +1671,11 @@ function summaryFromConversation(
1657
1671
  ...summary,
1658
1672
  ...(activityPreview ? { activityPreview } : undefined),
1659
1673
  };
1660
- const withArchiveState = mockArchivedConversationIds.has(
1661
- conversation.conversationId,
1662
- )
1663
- ? {
1664
- ...summaryWithActivity,
1665
- archivedAt:
1666
- summaryWithActivity.archivedAt ?? iso(NOW_MS, -2 * 24 * 60 * 60_000),
1667
- }
1668
- : { ...summaryWithActivity, archivedAt: undefined };
1669
- return withArchiveState.channel &&
1670
- PUBLIC_MOCK_CHANNEL_IDS.has(withArchiveState.channel)
1671
- ? { ...withArchiveState, locationId: `mock:${withArchiveState.channel}` }
1672
- : withArchiveState;
1674
+ const withViewerState = withMockViewerState(summaryWithActivity);
1675
+ return withViewerState.channel &&
1676
+ PUBLIC_MOCK_CHANNEL_IDS.has(withViewerState.channel)
1677
+ ? { ...withViewerState, locationId: `mock:${withViewerState.channel}` }
1678
+ : withViewerState;
1673
1679
  }
1674
1680
 
1675
1681
  function mockConversationFeed(nowMs: number): ConversationFeed {
@@ -1924,11 +1930,70 @@ export function setMockConversationArchived(
1924
1930
  (conversation) => conversation.conversationId === conversationId,
1925
1931
  );
1926
1932
  if (!exists) return undefined;
1927
- if (archived) mockArchivedConversationIds.add(conversationId);
1928
- else mockArchivedConversationIds.delete(conversationId);
1933
+ if (archived) {
1934
+ mockArchivedConversationIds.add(conversationId);
1935
+ // Match the API: archive also unpins.
1936
+ mockPinnedConversationIds.delete(conversationId);
1937
+ } else {
1938
+ mockArchivedConversationIds.delete(conversationId);
1939
+ // Match the API: a manual restore turns auto-archive off.
1940
+ mockAutoArchiveByConversationId.set(conversationId, false);
1941
+ }
1929
1942
  return { archivedAt: archived ? NOW : null };
1930
1943
  }
1931
1944
 
1945
+ /** Pin or unpin one mock conversation for local dashboard visual QA. */
1946
+ export function setMockConversationPinned(
1947
+ conversationId: string,
1948
+ pinned: boolean,
1949
+ ): { pinnedAt: string | null } | undefined {
1950
+ const exists = mockConversations(NOW_MS).some(
1951
+ (conversation) => conversation.conversationId === conversationId,
1952
+ );
1953
+ if (!exists) return undefined;
1954
+ if (pinned) {
1955
+ mockPinnedConversationIds.add(conversationId);
1956
+ // Match the API: a pin also restores an archived conversation.
1957
+ mockArchivedConversationIds.delete(conversationId);
1958
+ } else {
1959
+ mockPinnedConversationIds.delete(conversationId);
1960
+ }
1961
+ return { pinnedAt: pinned ? NOW : null };
1962
+ }
1963
+
1964
+ /** Archive every done mock conversation once, like the archive-done API. */
1965
+ export function archiveMockDoneConversations(): { archived: number } {
1966
+ let archived = 0;
1967
+ for (const summary of mockConversationFeed(NOW_MS).conversations) {
1968
+ if (
1969
+ summary.archivedAt ||
1970
+ summary.pinnedAt ||
1971
+ !summary.assignedWork ||
1972
+ summary.unfinishedWork ||
1973
+ !summary.finishedWorkAt ||
1974
+ mockAutoArchiveByConversationId.get(summary.conversationId) === false
1975
+ ) {
1976
+ continue;
1977
+ }
1978
+ mockArchivedConversationIds.add(summary.conversationId);
1979
+ archived += 1;
1980
+ }
1981
+ return { archived };
1982
+ }
1983
+
1984
+ /** Save a mock auto-archive choice for local dashboard visual QA. */
1985
+ export function setMockConversationAutoArchive(
1986
+ conversationId: string,
1987
+ autoArchive: boolean,
1988
+ ): { autoArchive: boolean } | undefined {
1989
+ const exists = mockConversations(NOW_MS).some(
1990
+ (conversation) => conversation.conversationId === conversationId,
1991
+ );
1992
+ if (!exists) return undefined;
1993
+ mockAutoArchiveByConversationId.set(conversationId, autoArchive);
1994
+ return { autoArchive };
1995
+ }
1996
+
1932
1997
  /** Return accepted mailbox rows for local dashboard visual QA. */
1933
1998
  export function readMockConversationPendingMessages(
1934
1999
  conversationId: string,
@@ -2151,22 +2216,23 @@ export function readMockConversationDetail(
2151
2216
  canFork:
2152
2217
  !parentConversationId &&
2153
2218
  conversationDetail.eventHistory.status !== "redacted",
2219
+ ...(conversationDetail.isParticipant
2220
+ ? {
2221
+ autoArchive:
2222
+ mockAutoArchiveByConversationId.get(conversationId) ?? false,
2223
+ }
2224
+ : undefined),
2154
2225
  };
2155
- const withArchiveState = mockArchivedConversationIds.has(conversationId)
2156
- ? {
2157
- ...detail,
2158
- archivedAt: detail.archivedAt ?? iso(NOW_MS, -2 * 24 * 60 * 60_000),
2159
- }
2160
- : { ...detail, archivedAt: undefined };
2161
- const events = withArchiveState.events.slice(-limit);
2226
+ const withViewerState = withMockViewerState(detail);
2227
+ const events = withViewerState.events.slice(-limit);
2162
2228
  const bounded =
2163
- events.length < withArchiveState.events.length && events[0]
2229
+ events.length < withViewerState.events.length && events[0]
2164
2230
  ? {
2165
- ...withArchiveState,
2231
+ ...withViewerState,
2166
2232
  events,
2167
2233
  previousCursor: mockBeforeCursor(conversationId, events[0].seq),
2168
2234
  }
2169
- : withArchiveState;
2235
+ : withViewerState;
2170
2236
  return bounded.channel && PUBLIC_MOCK_CHANNEL_IDS.has(bounded.channel)
2171
2237
  ? { ...bounded, locationId: `mock:${bounded.channel}` }
2172
2238
  : bounded;
@@ -7,6 +7,10 @@ import {
7
7
  actorProfileReportSchema,
8
8
  archiveConversationBodySchema,
9
9
  archiveConversationResponseSchema,
10
+ pinConversationBodySchema,
11
+ pinConversationResponseSchema,
12
+ archiveDoneConversationsResponseSchema,
13
+ conversationAutoArchiveSchema,
10
14
  conversationChildAgentsReportSchema,
11
15
  conversationDetailQuerySchema,
12
16
  conversationDetailReportSchema,
@@ -66,6 +70,9 @@ import {
66
70
  readMockAutomationExecutions,
67
71
  readMockAutomationList,
68
72
  setMockConversationArchived,
73
+ setMockConversationPinned,
74
+ setMockConversationAutoArchive,
75
+ archiveMockDoneConversations,
69
76
  } from "./fixtures";
70
77
  import { readMockConversationWatches } from "./watches";
71
78
 
@@ -190,7 +197,9 @@ export function createMockReportingApi(): Hono<{
190
197
  if (!params.success) {
191
198
  return errorResponse("Invalid route parameters.", 400);
192
199
  }
193
- if (!query.success) {
200
+ // Mock details fit one page and have no unread cursor, so mock history
201
+ // only pages backward.
202
+ if (!query.success || !("before" in query.data)) {
194
203
  return errorResponse("Invalid query parameters.", 400);
195
204
  }
196
205
  if (!readMockConversationDetail(params.data.conversationId)) {
@@ -289,6 +298,46 @@ export function createMockReportingApi(): Hono<{
289
298
  ? jsonResponse(archiveConversationResponseSchema, result)
290
299
  : errorResponse("Conversation not found.", 404);
291
300
  });
301
+ app.patch("/conversations/:conversationId/pin", async (c) => {
302
+ const params = conversationParamsSchema.safeParse(c.req.param());
303
+ if (!params.success) {
304
+ return errorResponse("Invalid route parameters.", 400);
305
+ }
306
+ const body = pinConversationBodySchema.safeParse(await c.req.json());
307
+ if (!body.success) {
308
+ return errorResponse("Invalid request body.", 400);
309
+ }
310
+ const result = setMockConversationPinned(
311
+ params.data.conversationId,
312
+ body.data.pinned,
313
+ );
314
+ return result
315
+ ? jsonResponse(pinConversationResponseSchema, result)
316
+ : errorResponse("Conversation not found.", 404);
317
+ });
318
+ app.post("/conversations/archive-done", () =>
319
+ jsonResponse(
320
+ archiveDoneConversationsResponseSchema,
321
+ archiveMockDoneConversations(),
322
+ ),
323
+ );
324
+ app.patch("/conversations/:conversationId/auto-archive", async (c) => {
325
+ const params = conversationParamsSchema.safeParse(c.req.param());
326
+ if (!params.success) {
327
+ return errorResponse("Invalid route parameters.", 400);
328
+ }
329
+ const body = conversationAutoArchiveSchema.safeParse(await c.req.json());
330
+ if (!body.success) {
331
+ return errorResponse("Invalid request body.", 400);
332
+ }
333
+ const result = setMockConversationAutoArchive(
334
+ params.data.conversationId,
335
+ body.data.autoArchive,
336
+ );
337
+ return result
338
+ ? jsonResponse(conversationAutoArchiveSchema, result)
339
+ : errorResponse("Conversation not found.", 404);
340
+ });
292
341
  // Fixed bodies so dashboard mock can exercise image/file attachment cards.
293
342
  app.get("/conversations/:conversationId/attachments/:attachmentId", (c) => {
294
343
  const conversationId = c.req.param("conversationId");
package/src/tailwind.css CHANGED
@@ -327,6 +327,36 @@
327
327
  }
328
328
  }
329
329
 
330
+ @utility thin-scrollbar {
331
+ --thin-scrollbar-thumb: color-mix(
332
+ in srgb,
333
+ var(--color-dashboard-scrollbar-thumb) 80%,
334
+ transparent
335
+ );
336
+
337
+ &::-webkit-scrollbar {
338
+ width: 6px;
339
+ }
340
+
341
+ &::-webkit-scrollbar-track {
342
+ background: transparent;
343
+ }
344
+
345
+ &::-webkit-scrollbar-thumb {
346
+ border-inline: 1px solid transparent;
347
+ background: var(--thin-scrollbar-thumb) padding-box;
348
+ }
349
+
350
+ &::-webkit-scrollbar-thumb:hover {
351
+ background-color: var(--color-dashboard-scrollbar-thumb-hover);
352
+ }
353
+
354
+ @supports not selector(::-webkit-scrollbar-thumb) {
355
+ scrollbar-color: var(--thin-scrollbar-thumb) transparent;
356
+ scrollbar-width: thin;
357
+ }
358
+ }
359
+
330
360
  @source "./app.ts";
331
361
  @source "./dashboardLoader.ts";
332
362
  @source "./client.tsx";