@sentry/junior-dashboard 0.61.0 → 0.62.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.
@@ -2,5 +2,6 @@
2
2
  export declare function TranscriptText(props: {
3
3
  firstChildIndex: number;
4
4
  lastChildIndex: number;
5
+ role?: string;
5
6
  text: string;
6
7
  }): import("react/jsx-runtime").JSX.Element;
@@ -26,5 +26,5 @@ export declare function groupTranscriptMessages(messages: TranscriptMessage[]):
26
26
  /** Build the plain-text clipboard/raw view for one transcript message. */
27
27
  export declare function messageRawText(message: TranscriptMessage): string;
28
28
  /** Count rendered rows so structured transcript expansion opens the newest node. */
29
- export declare function countRenderedTranscriptChildren(part: RenderedTranscriptPart): number;
29
+ export declare function countRenderedTranscriptChildren(part: RenderedTranscriptPart, role?: string): number;
30
30
  export {};
@@ -18,6 +18,10 @@ export declare function formatMessageTimestamp(value: number | undefined): strin
18
18
  export declare function formatMessageOffset(turn: ConversationTurn, value: number | undefined): string | undefined;
19
19
  /** Format byte counts in lowercase compact units for transcript metadata. */
20
20
  export declare function formatBytes(value: number | undefined): string;
21
+ /** Normalized role category for transcript messages. */
22
+ export type TranscriptRoleKind = "assistant" | "other" | "system" | "tool" | "user";
23
+ /** Normalize a raw transcript role string to a canonical kind. */
24
+ export declare function transcriptRoleKind(role: string): TranscriptRoleKind;
21
25
  /** Count visible or redacted message records for a turn. */
22
26
  export declare function turnMessageCount(turn: ConversationTurn): number;
23
27
  /** Count tool calls from visible transcripts or safe redacted metadata. */
@@ -59,13 +63,24 @@ export declare function detectLanguage(text: string): BundledLanguage;
59
63
  */
60
64
  export declare function detectOutputLanguage(text: string): BundledLanguage;
61
65
  /**
62
- * Decide whether a fenced block can use the interactive markup renderer.
63
- * Structured XML/HTML rendering is only enabled for explicitly-fenced blocks;
64
- * auto-detected prose is never eligible regardless of inferred language.
66
+ * Decide whether a block can use the interactive markup renderer.
67
+ * Only xml/html language blocks qualify; fenced is tracked as metadata but
68
+ * does not gate eligibility caller controls whether XML detection runs.
65
69
  */
66
70
  export declare function canRenderStructuredMarkup(block: CodeBlock): boolean;
67
- /** Parse markdown into renderable code blocks while preserving plain text blocks. */
68
- export declare function parseMarkdownBlocks(text: string): CodeBlock[];
71
+ /**
72
+ * Parse markdown into renderable code blocks while preserving plain text blocks.
73
+ *
74
+ * `outputOnly` (default `false`): when `true`, prose sections use
75
+ * `detectOutputLanguage` (json or markdown only — no xml/html heuristics).
76
+ * Use `outputOnly: true` for LLM-generated text (assistant messages) to
77
+ * prevent Slack autolinks and HTML snippets from triggering the XML tree
78
+ * renderer. Leave `false` (default) for user/system messages that may
79
+ * contain genuine XML runtime context.
80
+ */
81
+ export declare function parseMarkdownBlocks(text: string, opts?: {
82
+ outputOnly?: boolean;
83
+ }): CodeBlock[];
69
84
  /** Parse XML/HTML-ish fragments for the collapsible transcript renderer. */
70
85
  export declare function parseMarkupNodes(code: string, language: BundledLanguage): MarkupNode[];
71
86
  /** Group recent turn summaries into conversation rows. */
package/dist/client.js CHANGED
@@ -30836,9 +30836,13 @@ function formatBytes(value) {
30836
30836
  function transcriptSource(turn) {
30837
30837
  return turn.transcriptAvailable ? turn.transcript : turn.transcriptMetadata ?? [];
30838
30838
  }
30839
- function isConversationMessageRole(role) {
30839
+ function transcriptRoleKind(role) {
30840
30840
  const normalized = role.toLowerCase();
30841
- return normalized === "user" || normalized === "assistant";
30841
+ if (normalized === "assistant") return "assistant";
30842
+ if (normalized === "user") return "user";
30843
+ if (normalized === "system") return "system";
30844
+ if (normalized.includes("tool")) return "tool";
30845
+ return "other";
30842
30846
  }
30843
30847
  function hasTextPart(message) {
30844
30848
  return message.parts.some((part) => {
@@ -30848,8 +30852,9 @@ function hasTextPart(message) {
30848
30852
  });
30849
30853
  }
30850
30854
  function isConversationMessage(message) {
30851
- if (!isConversationMessageRole(message.role)) return false;
30852
- if (message.role.toLowerCase() === "assistant") return hasTextPart(message);
30855
+ const kind = transcriptRoleKind(message.role);
30856
+ if (kind !== "user" && kind !== "assistant") return false;
30857
+ if (kind === "assistant") return hasTextPart(message);
30853
30858
  return message.parts.length > 0;
30854
30859
  }
30855
30860
  function turnMessageCount(turn) {
@@ -31057,9 +31062,10 @@ function detectOutputLanguage(text2) {
31057
31062
  return "markdown";
31058
31063
  }
31059
31064
  function canRenderStructuredMarkup(block) {
31060
- return block.fenced === true && (block.language === "xml" || block.language === "html");
31065
+ return block.language === "xml" || block.language === "html";
31061
31066
  }
31062
- function parseMarkdownBlocks(text2) {
31067
+ function parseMarkdownBlocks(text2, opts = {}) {
31068
+ const detectProse = opts.outputOnly ? detectOutputLanguage : detectLanguage;
31063
31069
  const blocks = [];
31064
31070
  const fence = /```([A-Za-z0-9_-]+)?\n([\s\S]*?)```/g;
31065
31071
  let cursor = 0;
@@ -31067,7 +31073,7 @@ function parseMarkdownBlocks(text2) {
31067
31073
  while (match = fence.exec(text2)) {
31068
31074
  const prose = text2.slice(cursor, match.index).trim();
31069
31075
  if (prose) {
31070
- const language3 = detectOutputLanguage(prose);
31076
+ const language3 = detectProse(prose);
31071
31077
  blocks.push({ code: formatCodeBlock(prose, language3), fenced: false, language: language3 });
31072
31078
  }
31073
31079
  const language2 = normalizeLanguage(match[1]);
@@ -31080,11 +31086,11 @@ function parseMarkdownBlocks(text2) {
31080
31086
  }
31081
31087
  const rest = text2.slice(cursor).trim();
31082
31088
  if (rest) {
31083
- const language2 = detectOutputLanguage(rest);
31089
+ const language2 = detectProse(rest);
31084
31090
  blocks.push({ code: formatCodeBlock(rest, language2), fenced: false, language: language2 });
31085
31091
  }
31086
31092
  if (blocks.length > 0) return blocks;
31087
- const language = detectOutputLanguage(text2);
31093
+ const language = detectProse(text2);
31088
31094
  return [{ code: formatCodeBlock(text2, language), fenced: false, language }];
31089
31095
  }
31090
31096
  function parseMarkupNodes(code, language) {
@@ -54894,7 +54900,9 @@ function toolHeaderClass(interactive) {
54894
54900
  // src/client/components/TranscriptText.tsx
54895
54901
  var import_jsx_runtime19 = __toESM(require_jsx_runtime(), 1);
54896
54902
  function TranscriptText(props) {
54897
- const blocks = parseMarkdownBlocks(props.text);
54903
+ const blocks = parseMarkdownBlocks(props.text, {
54904
+ outputOnly: transcriptRoleKind(props.role ?? "") === "assistant"
54905
+ });
54898
54906
  let seenChildren = props.firstChildIndex;
54899
54907
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "grid min-w-0 gap-2", children: blocks.map((block, index) => {
54900
54908
  const firstChildIndex = seenChildren;
@@ -55195,15 +55203,18 @@ function messageRawText(message) {
55195
55203
  return stringifyPartValue(part.output ?? part.input ?? part.text ?? part);
55196
55204
  }).filter((part) => part.trim().length > 0).join("\n\n");
55197
55205
  }
55198
- function countTextRenderedChildren(text2) {
55199
- return parseMarkdownBlocks(text2).reduce((count, block) => {
55206
+ function countTextRenderedChildren(text2, outputOnly) {
55207
+ return parseMarkdownBlocks(text2, { outputOnly }).reduce((count, block) => {
55200
55208
  return count + countStructuredBlockChildren(block);
55201
55209
  }, 0);
55202
55210
  }
55203
- function countRenderedTranscriptChildren(part) {
55211
+ function countRenderedTranscriptChildren(part, role) {
55204
55212
  if (part.kind === "tool") return 1;
55205
55213
  if (part.part.type === "text") {
55206
- return countTextRenderedChildren(part.part.text ?? "");
55214
+ return countTextRenderedChildren(
55215
+ part.part.text ?? "",
55216
+ transcriptRoleKind(role ?? "") === "assistant"
55217
+ );
55207
55218
  }
55208
55219
  return 1;
55209
55220
  }
@@ -55240,14 +55251,6 @@ function turnMarkerClass(status) {
55240
55251
  status === "idle" && "border-[#beaaff]/70 bg-[#beaaff]/50"
55241
55252
  );
55242
55253
  }
55243
- function transcriptRoleKind(role) {
55244
- const normalized = role.toLowerCase();
55245
- if (normalized === "assistant") return "assistant";
55246
- if (normalized === "user") return "user";
55247
- if (normalized === "system") return "system";
55248
- if (normalized.includes("tool")) return "tool";
55249
- return "other";
55250
- }
55251
55254
  function transcriptRoleLabel(role, turn) {
55252
55255
  const kind = transcriptRoleKind(role);
55253
55256
  if (kind === "assistant") return "Junior";
@@ -55450,8 +55453,9 @@ function TranscriptMessageView(props) {
55450
55453
  const offset = formatMessageOffset(props.turn, props.message.timestamp);
55451
55454
  const renderedParts = groupTranscriptParts(props.message.parts);
55452
55455
  const rawText = messageRawText(props.message);
55456
+ const role = props.message.role;
55453
55457
  const totalRenderedChildren = renderedParts.reduce(
55454
- (count, part) => count + countRenderedTranscriptChildren(part),
55458
+ (count, part) => count + countRenderedTranscriptChildren(part, role),
55455
55459
  0
55456
55460
  );
55457
55461
  let seenRenderedChildren = 0;
@@ -55478,13 +55482,14 @@ function TranscriptMessageView(props) {
55478
55482
  }
55479
55483
  ) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "grid min-w-0 gap-2", children: renderedParts.map((part, index) => {
55480
55484
  const firstChildIndex = seenRenderedChildren;
55481
- seenRenderedChildren += countRenderedTranscriptChildren(part);
55485
+ seenRenderedChildren += countRenderedTranscriptChildren(part, role);
55482
55486
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
55483
55487
  TranscriptPartView,
55484
55488
  {
55485
55489
  firstChildIndex,
55486
55490
  lastChildIndex: totalRenderedChildren - 1,
55487
- part
55491
+ part,
55492
+ role
55488
55493
  },
55489
55494
  index
55490
55495
  );
@@ -55504,6 +55509,7 @@ function TranscriptPartView(props) {
55504
55509
  {
55505
55510
  firstChildIndex: props.firstChildIndex,
55506
55511
  lastChildIndex: props.lastChildIndex,
55512
+ role: props.role,
55507
55513
  text: part.text ?? ""
55508
55514
  }
55509
55515
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-dashboard",
3
- "version": "0.61.0",
3
+ "version": "0.62.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -39,7 +39,7 @@
39
39
  "react-router": "^7.16.0",
40
40
  "recharts": "^3.8.1",
41
41
  "shiki": "4.1.0",
42
- "@sentry/junior": "0.61.0"
42
+ "@sentry/junior": "0.62.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@tailwindcss/cli": "^4.3.0",
@@ -3,15 +3,18 @@ import {
3
3
  HighlightedCode,
4
4
  StructuredMarkup,
5
5
  } from "../code";
6
- import { canRenderStructuredMarkup, parseMarkdownBlocks } from "../format";
6
+ import { canRenderStructuredMarkup, parseMarkdownBlocks, transcriptRoleKind } from "../format";
7
7
 
8
8
  /** Render transcript markdown/code blocks with structured markup expansion. */
9
9
  export function TranscriptText(props: {
10
10
  firstChildIndex: number;
11
11
  lastChildIndex: number;
12
+ role?: string;
12
13
  text: string;
13
14
  }) {
14
- const blocks = parseMarkdownBlocks(props.text);
15
+ const blocks = parseMarkdownBlocks(props.text, {
16
+ outputOnly: transcriptRoleKind(props.role ?? "") === "assistant",
17
+ });
15
18
  let seenChildren = props.firstChildIndex;
16
19
 
17
20
  return (
@@ -4,6 +4,8 @@ import { HighlightedCode } from "../code";
4
4
  import {
5
5
  detectLanguage,
6
6
  detectOutputLanguage,
7
+ transcriptRoleKind,
8
+ type TranscriptRoleKind,
7
9
  formatBytes,
8
10
  formatMessageOffset,
9
11
  formatMessageTimestamp,
@@ -72,17 +74,6 @@ function turnMarkerClass(
72
74
  );
73
75
  }
74
76
 
75
- type TranscriptRoleKind = "assistant" | "other" | "system" | "tool" | "user";
76
-
77
- function transcriptRoleKind(role: string): TranscriptRoleKind {
78
- const normalized = role.toLowerCase();
79
- if (normalized === "assistant") return "assistant";
80
- if (normalized === "user") return "user";
81
- if (normalized === "system") return "system";
82
- if (normalized.includes("tool")) return "tool";
83
- return "other";
84
- }
85
-
86
77
  function transcriptRoleLabel(role: string, turn: ConversationTurn): string {
87
78
  const kind = transcriptRoleKind(role);
88
79
  if (kind === "assistant") return "Junior";
@@ -376,8 +367,9 @@ function TranscriptMessageView(props: {
376
367
  const offset = formatMessageOffset(props.turn, props.message.timestamp);
377
368
  const renderedParts = groupTranscriptParts(props.message.parts);
378
369
  const rawText = messageRawText(props.message);
370
+ const role = props.message.role;
379
371
  const totalRenderedChildren = renderedParts.reduce(
380
- (count, part) => count + countRenderedTranscriptChildren(part),
372
+ (count, part) => count + countRenderedTranscriptChildren(part, role),
381
373
  0,
382
374
  );
383
375
  let seenRenderedChildren = 0;
@@ -411,13 +403,14 @@ function TranscriptMessageView(props: {
411
403
  <div className="grid min-w-0 gap-2">
412
404
  {renderedParts.map((part, index) => {
413
405
  const firstChildIndex = seenRenderedChildren;
414
- seenRenderedChildren += countRenderedTranscriptChildren(part);
406
+ seenRenderedChildren += countRenderedTranscriptChildren(part, role);
415
407
  return (
416
408
  <TranscriptPartView
417
409
  firstChildIndex={firstChildIndex}
418
410
  key={index}
419
411
  lastChildIndex={totalRenderedChildren - 1}
420
412
  part={part}
413
+ role={role}
421
414
  />
422
415
  );
423
416
  })}
@@ -431,6 +424,7 @@ function TranscriptPartView(props: {
431
424
  firstChildIndex: number;
432
425
  lastChildIndex: number;
433
426
  part: RenderedTranscriptPart;
427
+ role?: string;
434
428
  }) {
435
429
  if (props.part.kind === "tool") {
436
430
  return (
@@ -444,6 +438,7 @@ function TranscriptPartView(props: {
444
438
  <TranscriptText
445
439
  firstChildIndex={props.firstChildIndex}
446
440
  lastChildIndex={props.lastChildIndex}
441
+ role={props.role}
447
442
  text={part.text ?? ""}
448
443
  />
449
444
  );
@@ -1,5 +1,5 @@
1
1
  import { countStructuredBlockChildren } from "../code";
2
- import { parseMarkdownBlocks, stringifyPartValue } from "../format";
2
+ import { parseMarkdownBlocks, stringifyPartValue, transcriptRoleKind } from "../format";
3
3
  import type { TranscriptMessage, TranscriptPart } from "../types";
4
4
 
5
5
  export type RenderedTranscriptPart =
@@ -175,8 +175,8 @@ export function messageRawText(message: TranscriptMessage): string {
175
175
  .join("\n\n");
176
176
  }
177
177
 
178
- function countTextRenderedChildren(text: string): number {
179
- return parseMarkdownBlocks(text).reduce((count, block) => {
178
+ function countTextRenderedChildren(text: string, outputOnly: boolean): number {
179
+ return parseMarkdownBlocks(text, { outputOnly }).reduce((count, block) => {
180
180
  return count + countStructuredBlockChildren(block);
181
181
  }, 0);
182
182
  }
@@ -184,10 +184,14 @@ function countTextRenderedChildren(text: string): number {
184
184
  /** Count rendered rows so structured transcript expansion opens the newest node. */
185
185
  export function countRenderedTranscriptChildren(
186
186
  part: RenderedTranscriptPart,
187
+ role?: string,
187
188
  ): number {
188
189
  if (part.kind === "tool") return 1;
189
190
  if (part.part.type === "text") {
190
- return countTextRenderedChildren(part.part.text ?? "");
191
+ return countTextRenderedChildren(
192
+ part.part.text ?? "",
193
+ transcriptRoleKind(role ?? "") === "assistant",
194
+ );
191
195
  }
192
196
  return 1;
193
197
  }
@@ -204,9 +204,22 @@ function transcriptSource(turn: ConversationTurn) {
204
204
  : (turn.transcriptMetadata ?? []);
205
205
  }
206
206
 
207
- function isConversationMessageRole(role: string): boolean {
207
+ /** Normalized role category for transcript messages. */
208
+ export type TranscriptRoleKind =
209
+ | "assistant"
210
+ | "other"
211
+ | "system"
212
+ | "tool"
213
+ | "user";
214
+
215
+ /** Normalize a raw transcript role string to a canonical kind. */
216
+ export function transcriptRoleKind(role: string): TranscriptRoleKind {
208
217
  const normalized = role.toLowerCase();
209
- return normalized === "user" || normalized === "assistant";
218
+ if (normalized === "assistant") return "assistant";
219
+ if (normalized === "user") return "user";
220
+ if (normalized === "system") return "system";
221
+ if (normalized.includes("tool")) return "tool";
222
+ return "other";
210
223
  }
211
224
 
212
225
  function hasTextPart(
@@ -222,8 +235,9 @@ function hasTextPart(
222
235
  function isConversationMessage(
223
236
  message: Pick<ConversationTurn["transcript"][number], "parts" | "role">,
224
237
  ): boolean {
225
- if (!isConversationMessageRole(message.role)) return false;
226
- if (message.role.toLowerCase() === "assistant") return hasTextPart(message);
238
+ const kind = transcriptRoleKind(message.role);
239
+ if (kind !== "user" && kind !== "assistant") return false;
240
+ if (kind === "assistant") return hasTextPart(message);
227
241
  return message.parts.length > 0;
228
242
  }
229
243
 
@@ -530,19 +544,29 @@ export function detectOutputLanguage(text: string): BundledLanguage {
530
544
  }
531
545
 
532
546
  /**
533
- * Decide whether a fenced block can use the interactive markup renderer.
534
- * Structured XML/HTML rendering is only enabled for explicitly-fenced blocks;
535
- * auto-detected prose is never eligible regardless of inferred language.
547
+ * Decide whether a block can use the interactive markup renderer.
548
+ * Only xml/html language blocks qualify; fenced is tracked as metadata but
549
+ * does not gate eligibility caller controls whether XML detection runs.
536
550
  */
537
551
  export function canRenderStructuredMarkup(block: CodeBlock): boolean {
538
- return (
539
- block.fenced === true &&
540
- (block.language === "xml" || block.language === "html")
541
- );
552
+ return block.language === "xml" || block.language === "html";
542
553
  }
543
554
 
544
- /** Parse markdown into renderable code blocks while preserving plain text blocks. */
545
- export function parseMarkdownBlocks(text: string): CodeBlock[] {
555
+ /**
556
+ * Parse markdown into renderable code blocks while preserving plain text blocks.
557
+ *
558
+ * `outputOnly` (default `false`): when `true`, prose sections use
559
+ * `detectOutputLanguage` (json or markdown only — no xml/html heuristics).
560
+ * Use `outputOnly: true` for LLM-generated text (assistant messages) to
561
+ * prevent Slack autolinks and HTML snippets from triggering the XML tree
562
+ * renderer. Leave `false` (default) for user/system messages that may
563
+ * contain genuine XML runtime context.
564
+ */
565
+ export function parseMarkdownBlocks(
566
+ text: string,
567
+ opts: { outputOnly?: boolean } = {},
568
+ ): CodeBlock[] {
569
+ const detectProse = opts.outputOnly ? detectOutputLanguage : detectLanguage;
546
570
  const blocks: CodeBlock[] = [];
547
571
  const fence = /```([A-Za-z0-9_-]+)?\n([\s\S]*?)```/g;
548
572
  let cursor = 0;
@@ -550,7 +574,7 @@ export function parseMarkdownBlocks(text: string): CodeBlock[] {
550
574
  while ((match = fence.exec(text))) {
551
575
  const prose = text.slice(cursor, match.index).trim();
552
576
  if (prose) {
553
- const language = detectOutputLanguage(prose);
577
+ const language = detectProse(prose);
554
578
  blocks.push({ code: formatCodeBlock(prose, language), fenced: false, language });
555
579
  }
556
580
  const language = normalizeLanguage(match[1]);
@@ -563,11 +587,11 @@ export function parseMarkdownBlocks(text: string): CodeBlock[] {
563
587
  }
564
588
  const rest = text.slice(cursor).trim();
565
589
  if (rest) {
566
- const language = detectOutputLanguage(rest);
590
+ const language = detectProse(rest);
567
591
  blocks.push({ code: formatCodeBlock(rest, language), fenced: false, language });
568
592
  }
569
593
  if (blocks.length > 0) return blocks;
570
- const language = detectOutputLanguage(text);
594
+ const language = detectProse(text);
571
595
  return [{ code: formatCodeBlock(text, language), fenced: false, language }];
572
596
  }
573
597