@sentry/junior-dashboard 0.60.1 → 0.61.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.
@@ -52,8 +52,18 @@ export declare function unavailableTranscriptLabel(turn: ConversationTurn): stri
52
52
  export declare function conversationPath(conversationId: string): string;
53
53
  /** Detect the syntax highlighter language for raw transcript blocks. */
54
54
  export declare function detectLanguage(text: string): BundledLanguage;
55
- /** Decide whether a fenced block can use the interactive markup renderer. */
56
- export declare function canRenderStructuredMarkup(language: BundledLanguage): boolean;
55
+ /**
56
+ * Detect the language for LLM text output prose: json if the text is valid
57
+ * JSON or JSONL, markdown otherwise. Never auto-detects XML, HTML, TypeScript,
58
+ * or shell — those heuristics are unreliable for rendered assistant output.
59
+ */
60
+ export declare function detectOutputLanguage(text: string): BundledLanguage;
61
+ /**
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.
65
+ */
66
+ export declare function canRenderStructuredMarkup(block: CodeBlock): boolean;
57
67
  /** Parse markdown into renderable code blocks while preserving plain text blocks. */
58
68
  export declare function parseMarkdownBlocks(text: string): CodeBlock[];
59
69
  /** Parse XML/HTML-ish fragments for the collapsible transcript renderer. */
@@ -140,6 +140,7 @@ export type SessionFilter = "active" | "recent" | "hung" | "failed" | "all";
140
140
  export type VisualStatus = "active" | "failed" | "hung" | "idle";
141
141
  export type CodeBlock = {
142
142
  code: string;
143
+ fenced?: boolean;
143
144
  language: BundledLanguage;
144
145
  };
145
146
  export type MarkupNode = {
package/dist/client.js CHANGED
@@ -31045,8 +31045,19 @@ function prettyJsonData(text2) {
31045
31045
  function formatCodeBlock(code, language) {
31046
31046
  return language === "json" ? prettyJsonData(code) ?? code : code;
31047
31047
  }
31048
- function canRenderStructuredMarkup(language) {
31049
- return language === "xml" || language === "html";
31048
+ function detectOutputLanguage(text2) {
31049
+ const trimmed = text2.trim();
31050
+ if (!trimmed) return "markdown";
31051
+ try {
31052
+ JSON.parse(trimmed);
31053
+ return "json";
31054
+ } catch {
31055
+ }
31056
+ if (prettyJsonl(trimmed)) return "json";
31057
+ return "markdown";
31058
+ }
31059
+ function canRenderStructuredMarkup(block) {
31060
+ return block.fenced === true && (block.language === "xml" || block.language === "html");
31050
31061
  }
31051
31062
  function parseMarkdownBlocks(text2) {
31052
31063
  const blocks = [];
@@ -31056,24 +31067,25 @@ function parseMarkdownBlocks(text2) {
31056
31067
  while (match = fence.exec(text2)) {
31057
31068
  const prose = text2.slice(cursor, match.index).trim();
31058
31069
  if (prose) {
31059
- const language3 = detectLanguage(prose);
31060
- blocks.push({ code: formatCodeBlock(prose, language3), language: language3 });
31070
+ const language3 = detectOutputLanguage(prose);
31071
+ blocks.push({ code: formatCodeBlock(prose, language3), fenced: false, language: language3 });
31061
31072
  }
31062
31073
  const language2 = normalizeLanguage(match[1]);
31063
31074
  blocks.push({
31064
31075
  code: formatCodeBlock(match[2] ?? "", language2),
31076
+ fenced: true,
31065
31077
  language: language2
31066
31078
  });
31067
31079
  cursor = match.index + match[0].length;
31068
31080
  }
31069
31081
  const rest = text2.slice(cursor).trim();
31070
31082
  if (rest) {
31071
- const language2 = detectLanguage(rest);
31072
- blocks.push({ code: formatCodeBlock(rest, language2), language: language2 });
31083
+ const language2 = detectOutputLanguage(rest);
31084
+ blocks.push({ code: formatCodeBlock(rest, language2), fenced: false, language: language2 });
31073
31085
  }
31074
31086
  if (blocks.length > 0) return blocks;
31075
- const language = detectLanguage(text2);
31076
- return [{ code: formatCodeBlock(text2, language), language }];
31087
+ const language = detectOutputLanguage(text2);
31088
+ return [{ code: formatCodeBlock(text2, language), fenced: false, language }];
31077
31089
  }
31078
31090
  function parseMarkupNodes(code, language) {
31079
31091
  const parser = new DOMParser();
@@ -54731,7 +54743,7 @@ var import_react51 = __toESM(require_react(), 1);
54731
54743
  var import_react50 = __toESM(require_react(), 1);
54732
54744
  var import_jsx_runtime17 = __toESM(require_jsx_runtime(), 1);
54733
54745
  function countStructuredBlockChildren(block) {
54734
- if (!canRenderStructuredMarkup(block.language)) return 1;
54746
+ if (!canRenderStructuredMarkup(block)) return 1;
54735
54747
  const rootCount = parseMarkupNodes(block.code, block.language).length;
54736
54748
  return rootCount > 0 ? rootCount : 1;
54737
54749
  }
@@ -54888,7 +54900,7 @@ function TranscriptText(props) {
54888
54900
  const firstChildIndex = seenChildren;
54889
54901
  const childCount = countStructuredBlockChildren(block);
54890
54902
  seenChildren += childCount;
54891
- if (!canRenderStructuredMarkup(block.language)) {
54903
+ if (!canRenderStructuredMarkup(block)) {
54892
54904
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
54893
54905
  HighlightedCode,
54894
54906
  {
@@ -55531,7 +55543,7 @@ function ThinkingPartView(props) {
55531
55543
  HighlightedCode,
55532
55544
  {
55533
55545
  code: rendered || "{}",
55534
- language: detectLanguage(rendered)
55546
+ language: detectOutputLanguage(rendered)
55535
55547
  }
55536
55548
  ) })
55537
55549
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-dashboard",
3
- "version": "0.60.1",
3
+ "version": "0.61.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.60.1"
42
+ "@sentry/junior": "0.61.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@tailwindcss/cli": "^4.3.0",
@@ -7,7 +7,7 @@ import type { CodeBlock, MarkupNode } from "./types";
7
7
 
8
8
  /** Count rendered children so transcripts can decide which markup node expands. */
9
9
  export function countStructuredBlockChildren(block: CodeBlock): number {
10
- if (!canRenderStructuredMarkup(block.language)) return 1;
10
+ if (!canRenderStructuredMarkup(block)) return 1;
11
11
  const rootCount = parseMarkupNodes(block.code, block.language).length;
12
12
  return rootCount > 0 ? rootCount : 1;
13
13
  }
@@ -21,7 +21,7 @@ export function TranscriptText(props: {
21
21
  const childCount = countStructuredBlockChildren(block);
22
22
  seenChildren += childCount;
23
23
 
24
- if (!canRenderStructuredMarkup(block.language)) {
24
+ if (!canRenderStructuredMarkup(block)) {
25
25
  return (
26
26
  <HighlightedCode
27
27
  code={block.code}
@@ -3,6 +3,7 @@ import { useState, type ClipboardEventHandler, type ReactNode } from "react";
3
3
  import { HighlightedCode } from "../code";
4
4
  import {
5
5
  detectLanguage,
6
+ detectOutputLanguage,
6
7
  formatBytes,
7
8
  formatMessageOffset,
8
9
  formatMessageTimestamp,
@@ -494,7 +495,7 @@ function ThinkingPartView(props: { value: unknown }) {
494
495
  <div className="border-t border-[#beaaff]/15 px-3 py-3">
495
496
  <HighlightedCode
496
497
  code={rendered || "{}"}
497
- language={detectLanguage(rendered)}
498
+ language={detectOutputLanguage(rendered)}
498
499
  />
499
500
  </div>
500
501
  </details>
@@ -511,9 +511,34 @@ function formatCodeBlock(code: string, language: BundledLanguage): string {
511
511
  return language === "json" ? (prettyJsonData(code) ?? code) : code;
512
512
  }
513
513
 
514
- /** Decide whether a fenced block can use the interactive markup renderer. */
515
- export function canRenderStructuredMarkup(language: BundledLanguage): boolean {
516
- return language === "xml" || language === "html";
514
+ /**
515
+ * Detect the language for LLM text output prose: json if the text is valid
516
+ * JSON or JSONL, markdown otherwise. Never auto-detects XML, HTML, TypeScript,
517
+ * or shell — those heuristics are unreliable for rendered assistant output.
518
+ */
519
+ export function detectOutputLanguage(text: string): BundledLanguage {
520
+ const trimmed = text.trim();
521
+ if (!trimmed) return "markdown";
522
+ try {
523
+ JSON.parse(trimmed);
524
+ return "json";
525
+ } catch {
526
+ // continue
527
+ }
528
+ if (prettyJsonl(trimmed)) return "json";
529
+ return "markdown";
530
+ }
531
+
532
+ /**
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.
536
+ */
537
+ export function canRenderStructuredMarkup(block: CodeBlock): boolean {
538
+ return (
539
+ block.fenced === true &&
540
+ (block.language === "xml" || block.language === "html")
541
+ );
517
542
  }
518
543
 
519
544
  /** Parse markdown into renderable code blocks while preserving plain text blocks. */
@@ -525,24 +550,25 @@ export function parseMarkdownBlocks(text: string): CodeBlock[] {
525
550
  while ((match = fence.exec(text))) {
526
551
  const prose = text.slice(cursor, match.index).trim();
527
552
  if (prose) {
528
- const language = detectLanguage(prose);
529
- blocks.push({ code: formatCodeBlock(prose, language), language });
553
+ const language = detectOutputLanguage(prose);
554
+ blocks.push({ code: formatCodeBlock(prose, language), fenced: false, language });
530
555
  }
531
556
  const language = normalizeLanguage(match[1]);
532
557
  blocks.push({
533
558
  code: formatCodeBlock(match[2] ?? "", language),
559
+ fenced: true,
534
560
  language,
535
561
  });
536
562
  cursor = match.index + match[0].length;
537
563
  }
538
564
  const rest = text.slice(cursor).trim();
539
565
  if (rest) {
540
- const language = detectLanguage(rest);
541
- blocks.push({ code: formatCodeBlock(rest, language), language });
566
+ const language = detectOutputLanguage(rest);
567
+ blocks.push({ code: formatCodeBlock(rest, language), fenced: false, language });
542
568
  }
543
569
  if (blocks.length > 0) return blocks;
544
- const language = detectLanguage(text);
545
- return [{ code: formatCodeBlock(text, language), language }];
570
+ const language = detectOutputLanguage(text);
571
+ return [{ code: formatCodeBlock(text, language), fenced: false, language }];
546
572
  }
547
573
 
548
574
  /** Parse XML/HTML-ish fragments for the collapsible transcript renderer. */
@@ -141,7 +141,7 @@ export type SessionFilter = "active" | "recent" | "hung" | "failed" | "all";
141
141
 
142
142
  export type VisualStatus = "active" | "failed" | "hung" | "idle";
143
143
 
144
- export type CodeBlock = { code: string; language: BundledLanguage };
144
+ export type CodeBlock = { code: string; fenced?: boolean; language: BundledLanguage };
145
145
 
146
146
  export type MarkupNode =
147
147
  | {