@sentry/junior-dashboard 0.290.0 → 0.291.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 (46) hide show
  1. package/dist/app.js +3 -3
  2. package/dist/client-manifest.json +1 -1
  3. package/dist/index.js +3 -3
  4. package/dist/public/chunks/{AdminPage-htjffg0n.js → AdminPage-nus5ycri.js} +1 -1
  5. package/dist/public/chunks/{Area-hr2tz6ad.js → Area-gt1sdk4c.js} +1 -1
  6. package/dist/public/chunks/{AutomationPage-eihr9acm.js → AutomationPage-gkkuw17m.js} +1 -1
  7. package/dist/public/chunks/{AutomationRunDetails-op6rfaji.js → AutomationRunDetails-najm4x1j.js} +1 -1
  8. package/dist/public/chunks/{AutomationRunsPage-sx9tdkxv.js → AutomationRunsPage-bx3nhkyu.js} +1 -1
  9. package/dist/public/chunks/{AutomationsPage-mnstyn9j.js → AutomationsPage-o1gvds5a.js} +1 -1
  10. package/dist/public/chunks/{BarChart-cg02osy4.js → BarChart-nm91dx0i.js} +1 -1
  11. package/dist/public/chunks/{BoardPage-mbypholh.js → BoardPage-bnp4bh2y.js} +1 -1
  12. package/dist/public/chunks/{CodeChangeRow-oyge0uo2.js → CodeChangeRow-l7msz02v.js} +1 -1
  13. package/dist/public/chunks/{CodePage-ojiokuht.js → CodePage-eb9xfehg.js} +1 -1
  14. package/dist/public/chunks/{CodeSummaryCards-gikkczl6.js → CodeSummaryCards-ko46g18h.js} +1 -1
  15. package/dist/public/chunks/{ConnectionsPage-l2d6sesm.js → ConnectionsPage-bs0trwlu.js} +1 -1
  16. package/dist/public/chunks/{DrawerContent-jt25bpic.js → DrawerContent-clt2v4h5.js} +1 -1
  17. package/dist/public/chunks/{FilterBar-h4ab22b1.js → FilterBar-k012tagh.js} +1 -1
  18. package/dist/public/chunks/{FormNotice-oukmcfaj.js → FormNotice-ndbxjxml.js} +1 -1
  19. package/dist/public/chunks/{LocationDetailPage-gldsmom2.js → LocationDetailPage-lmq3fzwb.js} +1 -1
  20. package/dist/public/chunks/{LocationsPage-glec25wm.js → LocationsPage-iuycwgrk.js} +1 -1
  21. package/dist/public/chunks/{McpSettingsPage-nfv7qia1.js → McpSettingsPage-jt621pk7.js} +1 -1
  22. package/dist/public/chunks/{PeoplePage-kd1ad63d.js → PeoplePage-cq7tlf7s.js} +1 -1
  23. package/dist/public/chunks/{PersonProfilePage-diqz6hmw.js → PersonProfilePage-k6z8ayic.js} +1 -1
  24. package/dist/public/chunks/{PersonalTokensPage-mwkwnlc7.js → PersonalTokensPage-f3zhm3px.js} +1 -1
  25. package/dist/public/chunks/{PluginReportRecordSet-ea6phh3k.js → PluginReportRecordSet-ovmfoi5x.js} +1 -1
  26. package/dist/public/chunks/{PluginUserPage-l7q0ylyz.js → PluginUserPage-mjr7t9js.js} +1 -1
  27. package/dist/public/chunks/{RepositoryPage-f61pzf0s.js → RepositoryPage-miaqb63z.js} +1 -1
  28. package/dist/public/chunks/{SettingsPage-oqgh66n1.js → SettingsPage-ocko5gzs.js} +1 -1
  29. package/dist/public/chunks/{SystemPage-mpp6dtwm.js → SystemPage-f9sgyr71.js} +1 -1
  30. package/dist/public/chunks/{WorkspaceFormPage-o74u0sno.js → WorkspaceFormPage-ovra0hwr.js} +1 -1
  31. package/dist/public/chunks/{WorkspacesPage-kfphmgpt.js → WorkspacesPage-m9o335es.js} +1 -1
  32. package/dist/public/chunks/{automationFormat-bhjz2hx6.js → automationFormat-f3vjb9y9.js} +1 -1
  33. package/dist/public/chunks/{chart-mxqu3c8e.js → chart-peynklgr.js} +1 -1
  34. package/dist/public/chunks/{code-highlighter-dd09ntlw.js → code-highlighter-jcl4p6jg.js} +1 -1
  35. package/dist/public/chunks/{format-eev8is9c.js → format-li35oiyp.js} +2 -2
  36. package/dist/public/chunks/{junior-btnqqkq5.js → junior-g9xga7oj.js} +2 -2
  37. package/dist/public/chunks/{workspaceQueries-i5xdl0za.js → workspaceQueries-ocke5763.js} +1 -1
  38. package/dist/public/{client-ll3qwj84.js → client-h0cvcz8x.js} +17 -17
  39. package/dist/public/styles-6XAAC7O5.css +2 -0
  40. package/dist/tailwind.css +1 -1
  41. package/package.json +4 -4
  42. package/src/client/conversations/ConversationMeta.tsx +127 -34
  43. package/src/client/conversations/conversationSections.ts +3 -2
  44. package/src/client/format.ts +1 -0
  45. package/src/client/types.ts +2 -0
  46. package/dist/public/styles-GFFU7LPU.css +0 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-dashboard",
3
- "version": "0.290.0",
3
+ "version": "0.291.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -45,10 +45,10 @@
45
45
  "remark-gfm": "^4.0.1",
46
46
  "shiki": "4.1.0",
47
47
  "zod": "^4.5.4",
48
- "@sentry/junior-plugin-api": "0.290.0"
48
+ "@sentry/junior-plugin-api": "0.291.0"
49
49
  },
50
50
  "peerDependencies": {
51
- "@sentry/junior": "0.290.0"
51
+ "@sentry/junior": "0.291.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@fontsource-variable/inter": "^5.3.0",
@@ -72,7 +72,7 @@
72
72
  "typescript": "^7.0.2",
73
73
  "unrun": "^0.3.1",
74
74
  "vitest": "^4.1.12",
75
- "@sentry/junior": "0.290.0"
75
+ "@sentry/junior": "0.291.0"
76
76
  },
77
77
  "scripts": {
78
78
  "build": "pnpm --filter @sentry/junior-plugin-api build && pnpm --filter @sentry/junior build && pnpm run build:css && pnpm run build:client && pnpm run build:server",
@@ -9,7 +9,13 @@ import {
9
9
  LockKeyhole,
10
10
  TriangleAlert,
11
11
  } from "lucide-react";
12
- import { useSyncExternalStore, type ReactNode } from "react";
12
+ import {
13
+ useLayoutEffect,
14
+ useRef,
15
+ useState,
16
+ useSyncExternalStore,
17
+ type ReactNode,
18
+ } from "react";
13
19
  import { Link } from "react-router";
14
20
  import type { ConversationDetailReport } from "@sentry/junior/api/schema";
15
21
 
@@ -184,50 +190,137 @@ function SidebarAnnotationIconFacepile(props: {
184
190
  <span
185
191
  className={cn(
186
192
  // Isolate stacking context so z-index only orders chips inside the pile.
187
- // Extra right pad keeps the last chip's cutout ring from clipping.
188
- "relative isolate inline-flex h-[18px] items-center pr-0.5",
193
+ "relative isolate inline-flex h-[18px] min-w-0 items-center",
189
194
  props.className,
190
195
  )}
191
196
  >
192
- {props.annotations.map((annotation, index) => (
193
- <SidebarAnnotationStatusChip
194
- annotation={annotation}
195
- cutoutColor={props.cutoutColor}
196
- key={annotation.key}
197
- // Each chip to the right layers above the chip before it.
198
- stacked={index > 0}
199
- zIndex={index + 1}
200
- />
201
- ))}
197
+ <FittingIcons
198
+ // The padding keeps the cutout rings inside the clipped row.
199
+ className="-m-0.5 h-[22px] min-w-[22px] p-0.5"
200
+ icons={props.annotations.map((annotation, index) => (
201
+ <SidebarAnnotationStatusChip
202
+ annotation={annotation}
203
+ cutoutColor={props.cutoutColor}
204
+ key={annotation.key}
205
+ // Each chip to the right layers above the chip before it.
206
+ stacked={index > 0}
207
+ zIndex={index + 1}
208
+ />
209
+ ))}
210
+ overflow={(count) => (
211
+ <span
212
+ aria-hidden="true"
213
+ className="relative -ml-0.5 box-border inline-flex h-[18px] min-w-[18px] shrink-0 items-center justify-center rounded-full border border-dashboard-border-strong bg-dashboard-control px-1 font-sans text-2xs tabular-nums leading-none text-dashboard-text-muted"
214
+ style={{
215
+ boxShadow: `0 0 0 2px ${props.cutoutColor}`,
216
+ zIndex: props.annotations.length + 1,
217
+ }}
218
+ >
219
+ +{count}
220
+ </span>
221
+ )}
222
+ />
202
223
  </span>
203
224
  );
204
225
  }
205
226
 
206
- /** One shared label with every status icon for that label. */
227
+ /**
228
+ * Show the icons that fit in the row and a +N count for the rest. Icons that
229
+ * do not fit wrap onto lines that the row clips, so the count follows the
230
+ * real width.
231
+ */
232
+ function FittingIcons(props: {
233
+ className: string;
234
+ icons: ReactNode[];
235
+ overflow(count: number): ReactNode;
236
+ }) {
237
+ const rowRef = useRef<HTMLSpanElement>(null);
238
+ const overflowRef = useRef<HTMLSpanElement>(null);
239
+ const total = props.icons.length;
240
+ const [shown, setShown] = useState(total);
241
+
242
+ useLayoutEffect(() => {
243
+ const row = rowRef.current;
244
+ if (!row) return;
245
+ const countFirstLine = () => {
246
+ const items = Array.from(row.children);
247
+ const bottom = items[0]?.getBoundingClientRect().bottom ?? 0;
248
+ return items.filter((item) => item.getBoundingClientRect().top < bottom)
249
+ .length;
250
+ };
251
+ const measure = () => {
252
+ // Measure without the +N first. Otherwise its own width keeps the
253
+ // last icons hidden after the row grows wide enough for all of them.
254
+ const overflow = overflowRef.current;
255
+ if (overflow) overflow.style.display = "none";
256
+ const fitting = countFirstLine();
257
+ if (overflow) overflow.style.display = "";
258
+ setShown(fitting === total || !overflow ? fitting : countFirstLine());
259
+ };
260
+ measure();
261
+ const observer = new ResizeObserver(measure);
262
+ observer.observe(row);
263
+ return () => observer.disconnect();
264
+ // Run again when `shown` changes: the +N that mounts takes width, and
265
+ // this pass counts it before paint. The observer would run after paint.
266
+ }, [shown, total]);
267
+
268
+ return (
269
+ <>
270
+ <span
271
+ className={cn(
272
+ // The row gap pushes wrapped lines fully outside the clip.
273
+ "flex flex-wrap items-center gap-y-6 overflow-hidden",
274
+ props.className,
275
+ )}
276
+ ref={rowRef}
277
+ >
278
+ {props.icons}
279
+ </span>
280
+ {shown < total ? (
281
+ <span className="contents" ref={overflowRef}>
282
+ {props.overflow(total - shown)}
283
+ </span>
284
+ ) : null}
285
+ </>
286
+ );
287
+ }
288
+
289
+ /** One shared label with the status icons that fit and a count of the rest. */
207
290
  function SidebarAnnotationGroupChip(props: {
208
291
  group: SidebarAnnotationBadgeGroup;
209
292
  }) {
210
293
  return (
211
- <span className="inline-flex h-5 min-w-0 max-w-60 shrink-0 items-center gap-1 truncate rounded-full border border-dashboard-border-strong bg-dashboard-control px-1.5 font-sans text-2xs leading-none text-dashboard-text-muted">
212
- {props.group.annotations.map((annotation) =>
213
- annotation.objectType ? (
214
- <ObjectIcon
215
- {...annotation}
216
- objectType={annotation.objectType}
217
- key={annotation.key}
218
- decorative
219
- size={14}
220
- />
221
- ) : annotation.icon ? (
222
- <SidebarAnnotationIcon
223
- decorative
224
- icon={annotation.icon}
225
- key={annotation.key}
226
- size={11}
227
- />
228
- ) : null,
229
- )}
230
- <span className="min-w-0 truncate whitespace-nowrap">
294
+ // No min-w-0: a chip shrinks only to one icon and its label, so the
295
+ // longest group gives up icons before a shorter one loses its label.
296
+ <span className="inline-flex h-5 items-center gap-1 rounded-full border border-dashboard-border-strong bg-dashboard-control px-1.5 font-sans text-2xs leading-none text-dashboard-text-muted">
297
+ <FittingIcons
298
+ className="h-3.5 min-w-3.5 gap-x-1"
299
+ icons={props.group.annotations.flatMap((annotation) =>
300
+ annotation.objectType ? (
301
+ <ObjectIcon
302
+ {...annotation}
303
+ objectType={annotation.objectType}
304
+ key={annotation.key}
305
+ decorative
306
+ size={14}
307
+ />
308
+ ) : annotation.icon ? (
309
+ <SidebarAnnotationIcon
310
+ decorative
311
+ icon={annotation.icon}
312
+ key={annotation.key}
313
+ size={11}
314
+ />
315
+ ) : (
316
+ []
317
+ ),
318
+ )}
319
+ overflow={(count) => (
320
+ <span className="shrink-0 tabular-nums">+{count}</span>
321
+ )}
322
+ />
323
+ <span className="max-w-40 shrink-0 truncate whitespace-nowrap">
231
324
  {props.group.label}
232
325
  </span>
233
326
  </span>
@@ -65,8 +65,8 @@ export function buildConversationSections(
65
65
 
66
66
  /**
67
67
  * True when the conversation waits on a person: its id is in `attentionIds`,
68
- * or it has unfinished work, such as an open pull request, while Junior is
69
- * not running in it.
68
+ * a Turn waits on a person to finish an authorization, or it has unfinished
69
+ * work, such as an open pull request, while Junior is not running in it.
70
70
  */
71
71
  function needsAttention(
72
72
  conversation: Conversation,
@@ -74,6 +74,7 @@ function needsAttention(
74
74
  ): boolean {
75
75
  return (
76
76
  attentionIds.has(conversation.id) ||
77
+ conversation.awaitingAuthorization === true ||
77
78
  (conversation.unfinishedWork === true && conversation.status !== "active")
78
79
  );
79
80
  }
@@ -910,6 +910,7 @@ export function buildConversations(
910
910
  parentConversationId: summary.parentConversationId,
911
911
  threads: summary.threads && buildConversations(summary.threads),
912
912
  unfinishedWork: summary.unfinishedWork,
913
+ awaitingAuthorization: summary.awaitingAuthorization,
913
914
  visibility: summary.visibility,
914
915
  }))
915
916
  .sort((a, b) => compareTimeDesc(a.lastSeenAt, b.lastSeenAt));
@@ -199,6 +199,8 @@ export type Conversation = {
199
199
  /** Visible Threads under this row, newest first. */
200
200
  threads?: Conversation[];
201
201
  unfinishedWork?: boolean;
202
+ /** A Turn waits on a person to finish an authorization. Server-owned. */
203
+ awaitingAuthorization?: boolean;
202
204
  visibility?: ConversationSummaryReport["visibility"];
203
205
  };
204
206