@sentry/junior-dashboard 0.60.0 → 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.
@@ -10,6 +10,8 @@ export declare function formatTime(value: string | undefined): string;
10
10
  export declare function formatRelativeTime(value: string | undefined): string;
11
11
  /** Format millisecond durations for compact transcript metadata. */
12
12
  export declare function formatMs(value: number | undefined): string;
13
+ /** Format aggregate runtime across turn summaries when duration data exists. */
14
+ export declare function formatDurationTotal(durations: Array<number | undefined>): string;
13
15
  /** Format transcript event timestamps independently from turn start offsets. */
14
16
  export declare function formatMessageTimestamp(value: number | undefined): string;
15
17
  /** Format a transcript event as an offset from the current turn start. */
@@ -22,6 +24,8 @@ export declare function turnMessageCount(turn: ConversationTurn): number;
22
24
  export declare function turnToolCallCount(turn: ConversationTurn): number;
23
25
  /** Format known token counters without estimating per-message usage. */
24
26
  export declare function formatTokenTotal(usage: TurnUsage | undefined): string;
27
+ /** Format the aggregate token count across conversation turns. */
28
+ export declare function formatUsageTotal(usages: Array<TurnUsage | undefined>): string;
25
29
  /** Format known token counters with available input/output detail. */
26
30
  export declare function formatUsage(usage: TurnUsage | undefined): string;
27
31
  /** Format a conversation span from first turn start to latest activity. */
@@ -48,8 +52,18 @@ export declare function unavailableTranscriptLabel(turn: ConversationTurn): stri
48
52
  export declare function conversationPath(conversationId: string): string;
49
53
  /** Detect the syntax highlighter language for raw transcript blocks. */
50
54
  export declare function detectLanguage(text: string): BundledLanguage;
51
- /** Decide whether a fenced block can use the interactive markup renderer. */
52
- 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;
53
67
  /** Parse markdown into renderable code blocks while preserving plain text blocks. */
54
68
  export declare function parseMarkdownBlocks(text: string): CodeBlock[];
55
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
@@ -15891,12 +15891,12 @@ var require_use_sync_external_store_shim_production = __commonJS({
15891
15891
  return x2 === y2 && (0 !== x2 || 1 / x2 === 1 / y2) || x2 !== x2 && y2 !== y2;
15892
15892
  }
15893
15893
  var objectIs = "function" === typeof Object.is ? Object.is : is4;
15894
- var useState21 = React53.useState;
15894
+ var useState23 = React53.useState;
15895
15895
  var useEffect27 = React53.useEffect;
15896
15896
  var useLayoutEffect12 = React53.useLayoutEffect;
15897
15897
  var useDebugValue2 = React53.useDebugValue;
15898
15898
  function useSyncExternalStore$2(subscribe, getSnapshot) {
15899
- var value = getSnapshot(), _useState = useState21({ inst: { value, getSnapshot } }), inst = _useState[0].inst, forceUpdate = _useState[1];
15899
+ var value = getSnapshot(), _useState = useState23({ inst: { value, getSnapshot } }), inst = _useState[0].inst, forceUpdate = _useState[1];
15900
15900
  useLayoutEffect12(
15901
15901
  function() {
15902
15902
  inst.value = value;
@@ -19462,7 +19462,7 @@ function useQuery(options, queryClient) {
19462
19462
  }
19463
19463
 
19464
19464
  // src/client.tsx
19465
- var import_react51 = __toESM(require_react(), 1);
19465
+ var import_react53 = __toESM(require_react(), 1);
19466
19466
  var import_client = __toESM(require_client(), 1);
19467
19467
 
19468
19468
  // ../../node_modules/.pnpm/react-router@7.16.0_react-dom@19.2.6_react@19.2.6__react@19.2.6/node_modules/react-router/dist/development/chunk-QUQL4437.mjs
@@ -30771,6 +30771,13 @@ function formatMs(value) {
30771
30771
  const remainingSeconds = Math.round(seconds2 % 60);
30772
30772
  return `${minutes}m ${remainingSeconds}s`;
30773
30773
  }
30774
+ function formatDurationTotal(durations) {
30775
+ const total = durations.reduce((sum, value) => {
30776
+ if (typeof value !== "number" || !Number.isFinite(value)) return sum;
30777
+ return (sum ?? 0) + Math.max(0, Math.floor(value));
30778
+ }, void 0);
30779
+ return total === void 0 ? "" : formatMs(total);
30780
+ }
30774
30781
  function formatMessageTimestamp(value) {
30775
30782
  if (typeof value !== "number" || !Number.isFinite(value))
30776
30783
  return "no timestamp";
@@ -30798,6 +30805,21 @@ function formatNumber(value) {
30798
30805
  const formatted = scaled >= 100 || Number.isInteger(scaled) ? Math.round(scaled).toString() : scaled >= 10 ? Math.round(scaled).toString() : (Math.floor(scaled * 10) / 10).toFixed(1).replace(/\.0$/, "");
30799
30806
  return `${formatted}${suffix2}`;
30800
30807
  }
30808
+ function getFiniteTokenCount(value) {
30809
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
30810
+ }
30811
+ function getUsageComponentTotal(usage) {
30812
+ return [
30813
+ usage.inputTokens,
30814
+ usage.outputTokens,
30815
+ usage.cachedInputTokens,
30816
+ usage.cacheCreationTokens
30817
+ ].reduce((sum, value) => {
30818
+ const count = getFiniteTokenCount(value);
30819
+ if (count === void 0) return sum;
30820
+ return (sum ?? 0) + count;
30821
+ }, void 0);
30822
+ }
30801
30823
  function formatBytes(value) {
30802
30824
  if (typeof value !== "number" || !Number.isFinite(value)) return "0b";
30803
30825
  const bytes = Math.max(0, Math.floor(value));
@@ -30814,8 +30836,28 @@ function formatBytes(value) {
30814
30836
  function transcriptSource(turn) {
30815
30837
  return turn.transcriptAvailable ? turn.transcript : turn.transcriptMetadata ?? [];
30816
30838
  }
30839
+ function isConversationMessageRole(role) {
30840
+ const normalized = role.toLowerCase();
30841
+ return normalized === "user" || normalized === "assistant";
30842
+ }
30843
+ function hasTextPart(message) {
30844
+ return message.parts.some((part) => {
30845
+ if (part.type !== "text") return false;
30846
+ if (part.redacted) return true;
30847
+ return typeof part.text === "string" && part.text.trim().length > 0;
30848
+ });
30849
+ }
30850
+ function isConversationMessage(message) {
30851
+ if (!isConversationMessageRole(message.role)) return false;
30852
+ if (message.role.toLowerCase() === "assistant") return hasTextPart(message);
30853
+ return message.parts.length > 0;
30854
+ }
30817
30855
  function turnMessageCount(turn) {
30818
- return turn.transcriptMessageCount ?? transcriptSource(turn).length;
30856
+ const source = transcriptSource(turn);
30857
+ if (source.length > 0) {
30858
+ return source.filter(isConversationMessage).length;
30859
+ }
30860
+ return turn.transcriptMessageCount ?? 0;
30819
30861
  }
30820
30862
  function turnToolCallCount(turn) {
30821
30863
  return transcriptSource(turn).reduce((count, message) => {
@@ -30824,23 +30866,20 @@ function turnToolCallCount(turn) {
30824
30866
  }
30825
30867
  function totalUsageTokens(usage) {
30826
30868
  if (!usage) return void 0;
30827
- if (typeof usage.totalTokens === "number" && Number.isFinite(usage.totalTokens)) {
30828
- return usage.totalTokens;
30829
- }
30830
- return [
30831
- usage.inputTokens,
30832
- usage.outputTokens,
30833
- usage.cachedInputTokens,
30834
- usage.cacheCreationTokens
30835
- ].reduce((sum, value) => {
30836
- if (typeof value !== "number" || !Number.isFinite(value)) return sum;
30837
- return (sum ?? 0) + Math.max(0, Math.floor(value));
30838
- }, void 0);
30869
+ return getUsageComponentTotal(usage) ?? getFiniteTokenCount(usage.totalTokens);
30839
30870
  }
30840
30871
  function formatTokenTotal(usage) {
30841
30872
  const total = totalUsageTokens(usage);
30842
30873
  return total === void 0 ? "" : `${formatNumber(total)} tokens`;
30843
30874
  }
30875
+ function formatUsageTotal(usages) {
30876
+ const total = usages.reduce((sum, usage) => {
30877
+ const tokens = totalUsageTokens(usage);
30878
+ if (tokens === void 0) return sum;
30879
+ return (sum ?? 0) + tokens;
30880
+ }, void 0);
30881
+ return total === void 0 ? "" : `${formatNumber(total)} tokens`;
30882
+ }
30844
30883
  function formatUsage(usage) {
30845
30884
  const total = totalUsageTokens(usage);
30846
30885
  if (total === void 0) return "";
@@ -31006,8 +31045,19 @@ function prettyJsonData(text2) {
31006
31045
  function formatCodeBlock(code, language) {
31007
31046
  return language === "json" ? prettyJsonData(code) ?? code : code;
31008
31047
  }
31009
- function canRenderStructuredMarkup(language) {
31010
- 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");
31011
31061
  }
31012
31062
  function parseMarkdownBlocks(text2) {
31013
31063
  const blocks = [];
@@ -31017,24 +31067,25 @@ function parseMarkdownBlocks(text2) {
31017
31067
  while (match = fence.exec(text2)) {
31018
31068
  const prose = text2.slice(cursor, match.index).trim();
31019
31069
  if (prose) {
31020
- const language3 = detectLanguage(prose);
31021
- blocks.push({ code: formatCodeBlock(prose, language3), language: language3 });
31070
+ const language3 = detectOutputLanguage(prose);
31071
+ blocks.push({ code: formatCodeBlock(prose, language3), fenced: false, language: language3 });
31022
31072
  }
31023
31073
  const language2 = normalizeLanguage(match[1]);
31024
31074
  blocks.push({
31025
31075
  code: formatCodeBlock(match[2] ?? "", language2),
31076
+ fenced: true,
31026
31077
  language: language2
31027
31078
  });
31028
31079
  cursor = match.index + match[0].length;
31029
31080
  }
31030
31081
  const rest = text2.slice(cursor).trim();
31031
31082
  if (rest) {
31032
- const language2 = detectLanguage(rest);
31033
- blocks.push({ code: formatCodeBlock(rest, language2), language: language2 });
31083
+ const language2 = detectOutputLanguage(rest);
31084
+ blocks.push({ code: formatCodeBlock(rest, language2), fenced: false, language: language2 });
31034
31085
  }
31035
31086
  if (blocks.length > 0) return blocks;
31036
- const language = detectLanguage(text2);
31037
- return [{ code: formatCodeBlock(text2, language), language }];
31087
+ const language = detectOutputLanguage(text2);
31088
+ return [{ code: formatCodeBlock(text2, language), fenced: false, language }];
31038
31089
  }
31039
31090
  function parseMarkupNodes(code, language) {
31040
31091
  const parser = new DOMParser();
@@ -31250,13 +31301,24 @@ function Stat(props) {
31250
31301
  // src/client/components/ConversationRowStats.tsx
31251
31302
  var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
31252
31303
  function ConversationRowStats(props) {
31304
+ const tokens = formatUsageTotal(
31305
+ props.conversation.turns.map((turn) => turn.cumulativeUsage)
31306
+ );
31307
+ const runtime = formatDurationTotal(
31308
+ props.conversation.turns.map((turn) => turn.cumulativeDurationMs)
31309
+ );
31310
+ const primaryStats = [
31311
+ `${props.conversation.turns.length} turns`,
31312
+ tokens,
31313
+ runtime ? `${runtime} runtime` : void 0
31314
+ ].filter(Boolean);
31315
+ const secondaryStats = [
31316
+ props.timeLabel,
31317
+ slackLocationLabel(props.conversation, { includeId: false })
31318
+ ].filter(Boolean);
31253
31319
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "grid min-w-0 justify-items-end gap-1 text-right max-md:justify-items-start max-md:text-left", children: [
31254
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "text-[0.84rem] leading-relaxed text-[#b8b8b8]", children: [
31255
- props.conversation.turns.length,
31256
- " turns \xB7 ",
31257
- props.timeLabel
31258
- ] }),
31259
- props.conversation.channel ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "max-w-full break-words text-[0.84rem] leading-relaxed text-[#888] md:truncate", children: slackLocationLabel(props.conversation, { includeId: false }) }) : null
31320
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "text-[0.84rem] leading-relaxed text-[#b8b8b8]", children: primaryStats.join(" \xB7 ") }),
31321
+ secondaryStats.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "max-w-full break-words text-[0.84rem] leading-relaxed text-[#888] md:truncate", children: secondaryStats.join(" \xB7 ") }) : null
31260
31322
  ] });
31261
31323
  }
31262
31324
 
@@ -54640,7 +54702,7 @@ function CommandCenter(props) {
54640
54702
  }
54641
54703
 
54642
54704
  // src/client/components/Transcript.tsx
54643
- var import_react50 = __toESM(require_react(), 1);
54705
+ var import_react52 = __toESM(require_react(), 1);
54644
54706
 
54645
54707
  // src/client/components/TranscriptHeader.tsx
54646
54708
  var import_jsx_runtime16 = __toESM(require_jsx_runtime(), 1);
@@ -54674,10 +54736,14 @@ function TranscriptViewToggle(props) {
54674
54736
  );
54675
54737
  }
54676
54738
 
54739
+ // src/client/components/TranscriptTurn.tsx
54740
+ var import_react51 = __toESM(require_react(), 1);
54741
+
54677
54742
  // src/client/code.tsx
54743
+ var import_react50 = __toESM(require_react(), 1);
54678
54744
  var import_jsx_runtime17 = __toESM(require_jsx_runtime(), 1);
54679
54745
  function countStructuredBlockChildren(block) {
54680
- if (!canRenderStructuredMarkup(block.language)) return 1;
54746
+ if (!canRenderStructuredMarkup(block)) return 1;
54681
54747
  const rootCount = parseMarkupNodes(block.code, block.language).length;
54682
54748
  return rootCount > 0 ? rootCount : 1;
54683
54749
  }
@@ -54711,8 +54777,12 @@ function MarkupNodeView(props) {
54711
54777
  if (props.node.type === "text") {
54712
54778
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "min-w-0 whitespace-pre-wrap break-words text-white", children: props.node.text.trim() });
54713
54779
  }
54780
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(MarkupElementView, { defaultOpen: props.defaultOpen, node: props.node });
54781
+ }
54782
+ function MarkupElementView(props) {
54714
54783
  const children = props.node.children;
54715
54784
  const hasChildren = children.length > 0;
54785
+ const [open, setOpen] = (0, import_react50.useState)(props.defaultOpen ?? true);
54716
54786
  const attributes = props.node.attributes.map(([name, value]) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "ml-1.5 text-[#b8b8b8]", children: [
54717
54787
  name,
54718
54788
  "=",
@@ -54733,12 +54803,15 @@ function MarkupNodeView(props) {
54733
54803
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
54734
54804
  "details",
54735
54805
  {
54736
- className: "group min-w-0 break-words",
54737
- open: props.defaultOpen ?? true,
54806
+ className: "min-w-0 break-words",
54807
+ onToggle: (event) => {
54808
+ if (event.currentTarget !== event.target) return;
54809
+ setOpen(event.currentTarget.open);
54810
+ },
54811
+ open,
54738
54812
  children: [
54739
54813
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("summary", { className: "-ml-1 flex w-full max-w-full cursor-pointer list-none flex-wrap items-baseline px-1 py-0.5 transition-colors hover:bg-white/[0.05] hover:text-white [&::-webkit-details-marker]:hidden", children: [
54740
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "mr-1 w-2 text-white group-open:hidden", children: "+" }),
54741
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "mr-1 hidden w-2 text-white group-open:inline", children: "-" }),
54814
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "mr-1 w-2 text-white", children: open ? "-" : "+" }),
54742
54815
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-[#888]", children: "<" }),
54743
54816
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "font-bold text-white", children: props.node.tagName }),
54744
54817
  attributes,
@@ -54827,7 +54900,7 @@ function TranscriptText(props) {
54827
54900
  const firstChildIndex = seenChildren;
54828
54901
  const childCount = countStructuredBlockChildren(block);
54829
54902
  seenChildren += childCount;
54830
- if (!canRenderStructuredMarkup(block.language)) {
54903
+ if (!canRenderStructuredMarkup(block)) {
54831
54904
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
54832
54905
  HighlightedCode,
54833
54906
  {
@@ -55437,20 +55510,7 @@ function TranscriptPartView(props) {
55437
55510
  }
55438
55511
  const value = part.output;
55439
55512
  if (part.type === "thinking") {
55440
- const rendered2 = stringifyPartValue(value);
55441
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("details", { className: "border border-[#beaaff]/20 bg-white/[0.03] transition-colors hover:border-[#beaaff]/45 hover:bg-[rgba(190,170,255,0.06)]", children: [
55442
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("summary", { className: "grid cursor-pointer grid-cols-[auto_minmax(0,1fr)] items-center gap-3 px-3 py-2 font-mono text-[0.8rem] leading-tight text-[#888] hover:bg-[rgba(190,170,255,0.07)] max-md:grid-cols-1 max-md:gap-1", children: [
55443
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "uppercase text-[#b8b8b8]", children: "thinking" }),
55444
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-0 truncate", children: previewToolValue(value) })
55445
- ] }),
55446
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
55447
- HighlightedCode,
55448
- {
55449
- code: rendered2 || "{}",
55450
- language: detectLanguage(rendered2)
55451
- }
55452
- )
55453
- ] });
55513
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ThinkingPartView, { value });
55454
55514
  }
55455
55515
  const rendered = stringifyPartValue(value);
55456
55516
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("details", { className: toolFrameClass(), children: [
@@ -55462,6 +55522,34 @@ function TranscriptPartView(props) {
55462
55522
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(HighlightedCode, { code: rendered || "{}", language: "json" })
55463
55523
  ] });
55464
55524
  }
55525
+ function ThinkingPartView(props) {
55526
+ const [open, setOpen] = (0, import_react51.useState)(false);
55527
+ const rendered = stringifyPartValue(props.value);
55528
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
55529
+ "details",
55530
+ {
55531
+ className: "border border-[#beaaff]/20 bg-white/[0.03] transition-colors hover:border-[#beaaff]/45 hover:bg-[rgba(190,170,255,0.06)]",
55532
+ onToggle: (event) => {
55533
+ if (event.currentTarget !== event.target) return;
55534
+ setOpen(event.currentTarget.open);
55535
+ },
55536
+ open,
55537
+ children: [
55538
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("summary", { className: "grid cursor-pointer grid-cols-[auto_minmax(0,1fr)] items-center gap-3 px-3 py-2 font-mono text-[0.8rem] leading-tight text-[#888] hover:bg-[rgba(190,170,255,0.07)] max-md:grid-cols-1 max-md:gap-1", children: [
55539
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "uppercase text-[#b8b8b8]", children: "thinking" }),
55540
+ open ? null : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-0 truncate", children: previewToolValue(props.value) })
55541
+ ] }),
55542
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "border-t border-[#beaaff]/15 px-3 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
55543
+ HighlightedCode,
55544
+ {
55545
+ code: rendered || "{}",
55546
+ language: detectOutputLanguage(rendered)
55547
+ }
55548
+ ) })
55549
+ ]
55550
+ }
55551
+ );
55552
+ }
55465
55553
  function isString3(value) {
55466
55554
  return typeof value === "string" && value.length > 0;
55467
55555
  }
@@ -55469,7 +55557,7 @@ function isString3(value) {
55469
55557
  // src/client/components/Transcript.tsx
55470
55558
  var import_jsx_runtime22 = __toESM(require_jsx_runtime(), 1);
55471
55559
  function Transcript(props) {
55472
- const [view, setView] = (0, import_react50.useState)("rich");
55560
+ const [view, setView] = (0, import_react52.useState)("rich");
55473
55561
  const hasRedactedTurns = props.turns.some((turn) => turn.transcriptRedacted);
55474
55562
  if (props.turns.length === 0) {
55475
55563
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: transcriptEmptyClass(), children: "No transcript is available for this conversation." });
@@ -55573,11 +55661,17 @@ function ConversationStats(props) {
55573
55661
  (count, turn) => count + turnToolCallCount(turn),
55574
55662
  0
55575
55663
  ) : void 0;
55664
+ const tokens = formatUsageTotal(
55665
+ (props.detail?.turns ?? props.conversation.turns).map(
55666
+ (turn) => turn.cumulativeUsage
55667
+ )
55668
+ );
55576
55669
  const stats = [
55577
55670
  slackLocationLabel(props.conversation, { includeId: false }),
55578
55671
  `${props.conversation.turns.length} turns`,
55579
55672
  messages === void 0 ? "messages loading" : `${messages} messages`,
55580
55673
  toolCalls === void 0 ? "tool calls loading" : `${toolCalls} tool calls`,
55674
+ tokens,
55581
55675
  formatConversationDuration(props.conversation),
55582
55676
  `started ${formatTime(props.conversation.startedAt)}`
55583
55677
  ].filter(Boolean);
@@ -55865,7 +55959,7 @@ function LegacyConversationRedirect() {
55865
55959
 
55866
55960
  // src/client.tsx
55867
55961
  var import_jsx_runtime29 = __toESM(require_jsx_runtime(), 1);
55868
- var DashboardErrorBoundary = class extends import_react51.Component {
55962
+ var DashboardErrorBoundary = class extends import_react53.Component {
55869
55963
  state = { error: null };
55870
55964
  static getDerivedStateFromError(error) {
55871
55965
  return { error };
package/dist/tailwind.css CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, "system-ui", "Segoe UI", Roboto, Oxygen, Ubuntu, sans-serif;--font-mono:"Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-300:oklch(81.1% .111 293.571);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-lg:32rem;--container-3xl:48rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-normal:0em;--leading-tight:1.25;--leading-snug:1.375;--leading-relaxed:1.625;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.relative{position:relative}.static{position:static}.sticky{position:sticky}.top-0{top:calc(var(--spacing) * 0)}.bottom-0{bottom:calc(var(--spacing) * 0)}.left-0{left:calc(var(--spacing) * 0)}.z-10{z-index:10}.z-\[1\]{z-index:1}.col-span-full{grid-column:1/-1}.m-0{margin:calc(var(--spacing) * 0)}.my-4{margin-block:calc(var(--spacing) * 4)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mr-3{margin-right:calc(var(--spacing) * 3)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.-ml-1{margin-left:calc(var(--spacing) * -1)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.size-1\.5{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5)}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.size-2\.5{width:calc(var(--spacing) * 2.5);height:calc(var(--spacing) * 2.5)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.max-h-\[60vh\]{max-height:60vh}.min-h-16{min-height:calc(var(--spacing) * 16)}.min-h-28{min-height:calc(var(--spacing) * 28)}.min-h-48{min-height:calc(var(--spacing) * 48)}.min-h-\[4\.5rem\]{min-height:4.5rem}.min-h-\[calc\(100vh-5rem\)\]{min-height:calc(100vh - 5rem)}.min-h-screen{min-height:100vh}.w-1{width:calc(var(--spacing) * 1)}.w-2{width:calc(var(--spacing) * 2)}.w-fit{width:fit-content}.w-full{width:100%}.w-px{width:1px}.max-w-3xl{max-width:var(--container-3xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-64{min-width:calc(var(--spacing) * 64)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.basis-1\/2{flex-basis:50%}.animate-pulse{animation:var(--animate-pulse)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.grid-cols-\[0\.875rem_minmax\(0\,1fr\)\]{grid-template-columns:.875rem minmax(0,1fr)}.grid-cols-\[auto_minmax\(0\,1fr\)\]{grid-template-columns:auto minmax(0,1fr)}.grid-cols-\[auto_minmax\(0\,1fr\)_auto\]{grid-template-columns:auto minmax(0,1fr) auto}.grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.grid-cols-\[minmax\(0\,1fr\)_minmax\(12rem\,max-content\)\]{grid-template-columns:minmax(0,1fr) minmax(12rem,max-content)}.grid-cols-\[minmax\(13rem\,1\.7fr\)_minmax\(13rem\,1fr\)\]{grid-template-columns:minmax(13rem,1.7fr) minmax(13rem,1fr)}.grid-rows-\[auto_1fr\]{grid-template-rows:auto 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-end{justify-content:flex-end}.justify-items-end{justify-items:end}.gap-0{gap:calc(var(--spacing) * 0)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-x-1{column-gap:calc(var(--spacing) * 1)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-1{row-gap:calc(var(--spacing) * 1)}.self-start{align-self:flex-start}.justify-self-end{justify-self:flex-end}.justify-self-start{justify-self:flex-start}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-4{border-bottom-style:var(--tw-border-style);border-bottom-width:4px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-\[\#beaaff\]\/20{border-color:oklab(78.6663% .0494663 -.109708/.2)}.border-\[\#beaaff\]\/70{border-color:oklab(78.6663% .0494663 -.109708/.7)}.border-amber-300{border-color:var(--color-amber-300)}.border-amber-400\/25{border-color:#fcbb0040}@supports (color:color-mix(in lab, red, red)){.border-amber-400\/25{border-color:color-mix(in oklab, var(--color-amber-400) 25%, transparent)}}.border-emerald-300{border-color:var(--color-emerald-300)}.border-emerald-400\/25{border-color:#00d29440}@supports (color:color-mix(in lab, red, red)){.border-emerald-400\/25{border-color:color-mix(in oklab, var(--color-emerald-400) 25%, transparent)}}.border-rose-300{border-color:var(--color-rose-300)}.border-rose-400{border-color:var(--color-rose-400)}.border-rose-400\/25{border-color:#ff667f40}@supports (color:color-mix(in lab, red, red)){.border-rose-400\/25{border-color:color-mix(in oklab, var(--color-rose-400) 25%, transparent)}}.border-rose-400\/50{border-color:#ff667f80}@supports (color:color-mix(in lab, red, red)){.border-rose-400\/50{border-color:color-mix(in oklab, var(--color-rose-400) 50%, transparent)}}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-white\/10{border-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.border-white\/15{border-color:#ffffff26}@supports (color:color-mix(in lab, red, red)){.border-white\/15{border-color:color-mix(in oklab, var(--color-white) 15%, transparent)}}.border-white\/30{border-color:#ffffff4d}@supports (color:color-mix(in lab, red, red)){.border-white\/30{border-color:color-mix(in oklab, var(--color-white) 30%, transparent)}}.border-b-\[\#beaaff\]{border-bottom-color:#beaaff}.border-b-transparent{border-bottom-color:#0000}.border-b-white\/10{border-bottom-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-b-white\/10{border-bottom-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.border-l-\[\#888\]{border-left-color:#888}.border-l-\[\#beaaff\]\/60{border-left-color:oklab(78.6663% .0494663 -.109708/.6)}.border-l-amber-300{border-left-color:var(--color-amber-300)}.border-l-amber-400{border-left-color:var(--color-amber-400)}.border-l-emerald-400{border-left-color:var(--color-emerald-400)}.border-l-rose-400{border-left-color:var(--color-rose-400)}.border-l-violet-300{border-left-color:var(--color-violet-300)}.border-l-white{border-left-color:var(--color-white)}.border-l-white\/25{border-left-color:#ffffff40}@supports (color:color-mix(in lab, red, red)){.border-l-white\/25{border-left-color:color-mix(in oklab, var(--color-white) 25%, transparent)}}.border-l-white\/35{border-left-color:#ffffff59}@supports (color:color-mix(in lab, red, red)){.border-l-white\/35{border-left-color:color-mix(in oklab, var(--color-white) 35%, transparent)}}.border-l-white\/70{border-left-color:#ffffffb3}@supports (color:color-mix(in lab, red, red)){.border-l-white\/70{border-left-color:color-mix(in oklab, var(--color-white) 70%, transparent)}}.bg-\[\#0b0b0b\]{background-color:#0b0b0b}.bg-\[\#111\]{background-color:#111}.bg-\[\#050505\]{background-color:#050505}.bg-\[\#050505\]\/95{background-color:oklab(11.4918% 7.45058e-9 7.45058e-9/.95)}.bg-\[\#b8b8b8\]{background-color:#b8b8b8}.bg-\[\#beaaff\]\/20{background-color:oklab(78.6663% .0494663 -.109708/.2)}.bg-\[\#beaaff\]\/50{background-color:oklab(78.6663% .0494663 -.109708/.5)}.bg-\[rgba\(190\,170\,255\,0\.08\)\]{background-color:#beaaff14}.bg-amber-300{background-color:var(--color-amber-300)}.bg-amber-300\/\[0\.06\]{background-color:#ffd2360f}@supports (color:color-mix(in lab, red, red)){.bg-amber-300\/\[0\.06\]{background-color:color-mix(in oklab, var(--color-amber-300) 6%, transparent)}}.bg-amber-400{background-color:var(--color-amber-400)}.bg-amber-400\/10{background-color:#fcbb001a}@supports (color:color-mix(in lab, red, red)){.bg-amber-400\/10{background-color:color-mix(in oklab, var(--color-amber-400) 10%, transparent)}}.bg-black{background-color:var(--color-black)}.bg-emerald-300{background-color:var(--color-emerald-300)}.bg-emerald-400\/10{background-color:#00d2941a}@supports (color:color-mix(in lab, red, red)){.bg-emerald-400\/10{background-color:color-mix(in oklab, var(--color-emerald-400) 10%, transparent)}}.bg-rose-300{background-color:var(--color-rose-300)}.bg-rose-400{background-color:var(--color-rose-400)}.bg-rose-400\/10{background-color:#ff667f1a}@supports (color:color-mix(in lab, red, red)){.bg-rose-400\/10{background-color:color-mix(in oklab, var(--color-rose-400) 10%, transparent)}}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-white\/20{background-color:#fff3}@supports (color:color-mix(in lab, red, red)){.bg-white\/20{background-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.bg-white\/35{background-color:#ffffff59}@supports (color:color-mix(in lab, red, red)){.bg-white\/35{background-color:color-mix(in oklab, var(--color-white) 35%, transparent)}}.bg-white\/\[0\.03\]{background-color:#ffffff08}@supports (color:color-mix(in lab, red, red)){.bg-white\/\[0\.03\]{background-color:color-mix(in oklab, var(--color-white) 3%, transparent)}}.bg-white\/\[0\.04\]{background-color:#ffffff0a}@supports (color:color-mix(in lab, red, red)){.bg-white\/\[0\.04\]{background-color:color-mix(in oklab, var(--color-white) 4%, transparent)}}.p-0{padding:calc(var(--spacing) * 0)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-8{padding:calc(var(--spacing) * 8)}.px-0\.5{padding-inline:calc(var(--spacing) * .5)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-8{padding-block:calc(var(--spacing) * 8)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pb-1\.5{padding-bottom:calc(var(--spacing) * 1.5)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.text-left{text-align:left}.text-right{text-align:right}.font-\[inherit\]{font-family:inherit}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.8rem\]{font-size:.8rem}.text-\[0\.9rem\]{font-size:.9rem}.text-\[0\.68rem\]{font-size:.68rem}.text-\[0\.72rem\]{font-size:.72rem}.text-\[0\.76rem\]{font-size:.76rem}.text-\[0\.78rem\]{font-size:.78rem}.text-\[0\.82rem\]{font-size:.82rem}.text-\[0\.84rem\]{font-size:.84rem}.text-\[0\.86rem\]{font-size:.86rem}.text-\[0\.88rem\]{font-size:.88rem}.text-\[0\.92rem\]{font-size:.92rem}.text-\[0\.98rem\]{font-size:.98rem}.text-\[1\.04rem\]{font-size:1.04rem}.text-\[1\.05rem\]{font-size:1.05rem}.text-\[1\.75rem\]{font-size:1.75rem}.leading-\[1\.45\]{--tw-leading:1.45;line-height:1.45}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#666\]{color:#666}.text-\[\#777\]{color:#777}.text-\[\#888\]{color:#888}.text-\[\#b8b8b8\]{color:#b8b8b8}.text-\[\#d6d6d6\]{color:#d6d6d6}.text-\[\#d8ccff\]{color:#d8ccff}.text-\[\#f4f4f4\]{color:#f4f4f4}.text-amber-200{color:var(--color-amber-200)}.text-amber-300{color:var(--color-amber-300)}.text-black{color:var(--color-black)}.text-emerald-300{color:var(--color-emerald-300)}.text-inherit{color:inherit}.text-rose-300{color:var(--color-rose-300)}.text-violet-200{color:var(--color-violet-200)}.text-white{color:var(--color-white)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.decoration-white{-webkit-text-decoration-color:var(--color-white);-webkit-text-decoration-color:var(--color-white);text-decoration-color:var(--color-white)}.underline-offset-4{text-underline-offset:4px}.\[color-scheme\:dark\]{color-scheme:dark}.saturate-50{--tw-saturate:saturate(50%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-\[filter\,stroke\,stroke-width\]{transition-property:filter,stroke,stroke-width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.group-open\:hidden:is(:where(.group):is([open],:popover-open,:open) *){display:none}.group-open\:inline:is(:where(.group):is([open],:popover-open,:open) *){display:inline}.first\:border-t-0:first-child{border-top-style:var(--tw-border-style);border-top-width:0}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.hover\:border-\[\#beaaff\]\/45:hover{border-color:oklab(78.6663% .0494663 -.109708/.45)}.hover\:border-white:hover{border-color:var(--color-white)}.hover\:border-white\/25:hover{border-color:#ffffff40}@supports (color:color-mix(in lab, red, red)){.hover\:border-white\/25:hover{border-color:color-mix(in oklab, var(--color-white) 25%, transparent)}}.hover\:border-white\/30:hover{border-color:#ffffff4d}@supports (color:color-mix(in lab, red, red)){.hover\:border-white\/30:hover{border-color:color-mix(in oklab, var(--color-white) 30%, transparent)}}.hover\:border-b-white\/45:hover{border-bottom-color:#ffffff73}@supports (color:color-mix(in lab, red, red)){.hover\:border-b-white\/45:hover{border-bottom-color:color-mix(in oklab, var(--color-white) 45%, transparent)}}.hover\:bg-\[\#151515\]:hover{background-color:#151515}.hover\:bg-\[rgba\(190\,170\,255\,0\.06\)\]:hover{background-color:#beaaff0f}.hover\:bg-\[rgba\(190\,170\,255\,0\.07\)\]:hover{background-color:#beaaff12}.hover\:bg-white\/\[0\.04\]:hover{background-color:#ffffff0a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/\[0\.04\]:hover{background-color:color-mix(in oklab, var(--color-white) 4%, transparent)}}.hover\:bg-white\/\[0\.05\]:hover{background-color:#ffffff0d}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/\[0\.05\]:hover{background-color:color-mix(in oklab, var(--color-white) 5%, transparent)}}.hover\:text-white:hover{color:var(--color-white)}.hover\:underline:hover{text-decoration-line:underline}.hover\:brightness-125:hover{--tw-brightness:brightness(125%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}}.focus-visible\:stroke-emerald-400:focus-visible{stroke:var(--color-emerald-400)}.focus-visible\:stroke-2:focus-visible{stroke-width:2px}@media not all and (min-width:48rem){.max-md\:hidden{display:none}.max-md\:w-full{width:100%}.max-md\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.max-md\:flex-col{flex-direction:column}.max-md\:flex-wrap{flex-wrap:wrap}.max-md\:items-stretch{align-items:stretch}.max-md\:justify-between{justify-content:space-between}.max-md\:justify-items-start{justify-items:start}.max-md\:gap-1{gap:calc(var(--spacing) * 1)}.max-md\:px-4{padding-inline:calc(var(--spacing) * 4)}.max-md\:py-4{padding-block:calc(var(--spacing) * 4)}.max-md\:text-left{text-align:left}}@media (min-width:40rem){.sm\:basis-1\/3{flex-basis:33.3333%}}@media (min-width:48rem){.md\:grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.md\:truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md\:px-8{padding-inline:calc(var(--spacing) * 8)}.md\:text-right{text-align:right}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}}@media (min-width:64rem){.lg\:grid-cols-\[minmax\(21rem\,0\.32fr\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(21rem,.32fr) minmax(0,1fr)}}.\[\&_code\]\:break-words code{overflow-wrap:break-word}.\[\&_code\]\:whitespace-pre-wrap code{white-space:pre-wrap}.\[\&_pre\]\:\!m-0 pre{margin:calc(var(--spacing) * 0)!important}.\[\&_pre\]\:\!overflow-visible pre{overflow:visible!important}.\[\&_pre\]\:\!bg-transparent pre{background-color:#0000!important}.\[\&_pre\]\:\!p-0 pre{padding:calc(var(--spacing) * 0)!important}.\[\&_pre\]\:font-mono pre{font-family:var(--font-mono)}.\[\&_pre\]\:text-\[0\.86rem\] pre{font-size:.86rem}.\[\&_pre\]\:leading-relaxed pre{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.\[\&_pre\]\:break-words pre{overflow-wrap:break-word}.\[\&_pre\]\:whitespace-pre-wrap pre{white-space:pre-wrap}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, "system-ui", "Segoe UI", Roboto, Oxygen, Ubuntu, sans-serif;--font-mono:"Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-300:oklch(81.1% .111 293.571);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-lg:32rem;--container-3xl:48rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-normal:0em;--leading-tight:1.25;--leading-snug:1.375;--leading-relaxed:1.625;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.relative{position:relative}.static{position:static}.sticky{position:sticky}.top-0{top:calc(var(--spacing) * 0)}.bottom-0{bottom:calc(var(--spacing) * 0)}.left-0{left:calc(var(--spacing) * 0)}.z-10{z-index:10}.z-\[1\]{z-index:1}.col-span-full{grid-column:1/-1}.m-0{margin:calc(var(--spacing) * 0)}.my-4{margin-block:calc(var(--spacing) * 4)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mr-3{margin-right:calc(var(--spacing) * 3)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.-ml-1{margin-left:calc(var(--spacing) * -1)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.size-1\.5{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5)}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.size-2\.5{width:calc(var(--spacing) * 2.5);height:calc(var(--spacing) * 2.5)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.max-h-\[60vh\]{max-height:60vh}.min-h-16{min-height:calc(var(--spacing) * 16)}.min-h-28{min-height:calc(var(--spacing) * 28)}.min-h-48{min-height:calc(var(--spacing) * 48)}.min-h-\[4\.5rem\]{min-height:4.5rem}.min-h-\[calc\(100vh-5rem\)\]{min-height:calc(100vh - 5rem)}.min-h-screen{min-height:100vh}.w-1{width:calc(var(--spacing) * 1)}.w-2{width:calc(var(--spacing) * 2)}.w-fit{width:fit-content}.w-full{width:100%}.w-px{width:1px}.max-w-3xl{max-width:var(--container-3xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-64{min-width:calc(var(--spacing) * 64)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.basis-1\/2{flex-basis:50%}.animate-pulse{animation:var(--animate-pulse)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.grid-cols-\[0\.875rem_minmax\(0\,1fr\)\]{grid-template-columns:.875rem minmax(0,1fr)}.grid-cols-\[auto_minmax\(0\,1fr\)\]{grid-template-columns:auto minmax(0,1fr)}.grid-cols-\[auto_minmax\(0\,1fr\)_auto\]{grid-template-columns:auto minmax(0,1fr) auto}.grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.grid-cols-\[minmax\(0\,1fr\)_minmax\(12rem\,max-content\)\]{grid-template-columns:minmax(0,1fr) minmax(12rem,max-content)}.grid-cols-\[minmax\(13rem\,1\.7fr\)_minmax\(13rem\,1fr\)\]{grid-template-columns:minmax(13rem,1.7fr) minmax(13rem,1fr)}.grid-rows-\[auto_1fr\]{grid-template-rows:auto 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-end{justify-content:flex-end}.justify-items-end{justify-items:end}.gap-0{gap:calc(var(--spacing) * 0)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-x-1{column-gap:calc(var(--spacing) * 1)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-1{row-gap:calc(var(--spacing) * 1)}.self-start{align-self:flex-start}.justify-self-end{justify-self:flex-end}.justify-self-start{justify-self:flex-start}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-4{border-bottom-style:var(--tw-border-style);border-bottom-width:4px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-\[\#beaaff\]\/15{border-color:oklab(78.6663% .0494663 -.109708/.15)}.border-\[\#beaaff\]\/20{border-color:oklab(78.6663% .0494663 -.109708/.2)}.border-\[\#beaaff\]\/70{border-color:oklab(78.6663% .0494663 -.109708/.7)}.border-amber-300{border-color:var(--color-amber-300)}.border-amber-400\/25{border-color:#fcbb0040}@supports (color:color-mix(in lab, red, red)){.border-amber-400\/25{border-color:color-mix(in oklab, var(--color-amber-400) 25%, transparent)}}.border-emerald-300{border-color:var(--color-emerald-300)}.border-emerald-400\/25{border-color:#00d29440}@supports (color:color-mix(in lab, red, red)){.border-emerald-400\/25{border-color:color-mix(in oklab, var(--color-emerald-400) 25%, transparent)}}.border-rose-300{border-color:var(--color-rose-300)}.border-rose-400{border-color:var(--color-rose-400)}.border-rose-400\/25{border-color:#ff667f40}@supports (color:color-mix(in lab, red, red)){.border-rose-400\/25{border-color:color-mix(in oklab, var(--color-rose-400) 25%, transparent)}}.border-rose-400\/50{border-color:#ff667f80}@supports (color:color-mix(in lab, red, red)){.border-rose-400\/50{border-color:color-mix(in oklab, var(--color-rose-400) 50%, transparent)}}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-white\/10{border-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.border-white\/15{border-color:#ffffff26}@supports (color:color-mix(in lab, red, red)){.border-white\/15{border-color:color-mix(in oklab, var(--color-white) 15%, transparent)}}.border-white\/30{border-color:#ffffff4d}@supports (color:color-mix(in lab, red, red)){.border-white\/30{border-color:color-mix(in oklab, var(--color-white) 30%, transparent)}}.border-b-\[\#beaaff\]{border-bottom-color:#beaaff}.border-b-transparent{border-bottom-color:#0000}.border-b-white\/10{border-bottom-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-b-white\/10{border-bottom-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.border-l-\[\#888\]{border-left-color:#888}.border-l-\[\#beaaff\]\/60{border-left-color:oklab(78.6663% .0494663 -.109708/.6)}.border-l-amber-300{border-left-color:var(--color-amber-300)}.border-l-amber-400{border-left-color:var(--color-amber-400)}.border-l-emerald-400{border-left-color:var(--color-emerald-400)}.border-l-rose-400{border-left-color:var(--color-rose-400)}.border-l-violet-300{border-left-color:var(--color-violet-300)}.border-l-white{border-left-color:var(--color-white)}.border-l-white\/25{border-left-color:#ffffff40}@supports (color:color-mix(in lab, red, red)){.border-l-white\/25{border-left-color:color-mix(in oklab, var(--color-white) 25%, transparent)}}.border-l-white\/35{border-left-color:#ffffff59}@supports (color:color-mix(in lab, red, red)){.border-l-white\/35{border-left-color:color-mix(in oklab, var(--color-white) 35%, transparent)}}.border-l-white\/70{border-left-color:#ffffffb3}@supports (color:color-mix(in lab, red, red)){.border-l-white\/70{border-left-color:color-mix(in oklab, var(--color-white) 70%, transparent)}}.bg-\[\#0b0b0b\]{background-color:#0b0b0b}.bg-\[\#111\]{background-color:#111}.bg-\[\#050505\]{background-color:#050505}.bg-\[\#050505\]\/95{background-color:oklab(11.4918% 7.45058e-9 7.45058e-9/.95)}.bg-\[\#b8b8b8\]{background-color:#b8b8b8}.bg-\[\#beaaff\]\/20{background-color:oklab(78.6663% .0494663 -.109708/.2)}.bg-\[\#beaaff\]\/50{background-color:oklab(78.6663% .0494663 -.109708/.5)}.bg-\[rgba\(190\,170\,255\,0\.08\)\]{background-color:#beaaff14}.bg-amber-300{background-color:var(--color-amber-300)}.bg-amber-300\/\[0\.06\]{background-color:#ffd2360f}@supports (color:color-mix(in lab, red, red)){.bg-amber-300\/\[0\.06\]{background-color:color-mix(in oklab, var(--color-amber-300) 6%, transparent)}}.bg-amber-400{background-color:var(--color-amber-400)}.bg-amber-400\/10{background-color:#fcbb001a}@supports (color:color-mix(in lab, red, red)){.bg-amber-400\/10{background-color:color-mix(in oklab, var(--color-amber-400) 10%, transparent)}}.bg-black{background-color:var(--color-black)}.bg-emerald-300{background-color:var(--color-emerald-300)}.bg-emerald-400\/10{background-color:#00d2941a}@supports (color:color-mix(in lab, red, red)){.bg-emerald-400\/10{background-color:color-mix(in oklab, var(--color-emerald-400) 10%, transparent)}}.bg-rose-300{background-color:var(--color-rose-300)}.bg-rose-400{background-color:var(--color-rose-400)}.bg-rose-400\/10{background-color:#ff667f1a}@supports (color:color-mix(in lab, red, red)){.bg-rose-400\/10{background-color:color-mix(in oklab, var(--color-rose-400) 10%, transparent)}}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-white\/20{background-color:#fff3}@supports (color:color-mix(in lab, red, red)){.bg-white\/20{background-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.bg-white\/35{background-color:#ffffff59}@supports (color:color-mix(in lab, red, red)){.bg-white\/35{background-color:color-mix(in oklab, var(--color-white) 35%, transparent)}}.bg-white\/\[0\.03\]{background-color:#ffffff08}@supports (color:color-mix(in lab, red, red)){.bg-white\/\[0\.03\]{background-color:color-mix(in oklab, var(--color-white) 3%, transparent)}}.bg-white\/\[0\.04\]{background-color:#ffffff0a}@supports (color:color-mix(in lab, red, red)){.bg-white\/\[0\.04\]{background-color:color-mix(in oklab, var(--color-white) 4%, transparent)}}.p-0{padding:calc(var(--spacing) * 0)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-8{padding:calc(var(--spacing) * 8)}.px-0\.5{padding-inline:calc(var(--spacing) * .5)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-8{padding-block:calc(var(--spacing) * 8)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pb-1\.5{padding-bottom:calc(var(--spacing) * 1.5)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.text-left{text-align:left}.text-right{text-align:right}.font-\[inherit\]{font-family:inherit}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.8rem\]{font-size:.8rem}.text-\[0\.9rem\]{font-size:.9rem}.text-\[0\.68rem\]{font-size:.68rem}.text-\[0\.72rem\]{font-size:.72rem}.text-\[0\.76rem\]{font-size:.76rem}.text-\[0\.78rem\]{font-size:.78rem}.text-\[0\.82rem\]{font-size:.82rem}.text-\[0\.84rem\]{font-size:.84rem}.text-\[0\.86rem\]{font-size:.86rem}.text-\[0\.88rem\]{font-size:.88rem}.text-\[0\.92rem\]{font-size:.92rem}.text-\[0\.98rem\]{font-size:.98rem}.text-\[1\.04rem\]{font-size:1.04rem}.text-\[1\.05rem\]{font-size:1.05rem}.text-\[1\.75rem\]{font-size:1.75rem}.leading-\[1\.45\]{--tw-leading:1.45;line-height:1.45}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#666\]{color:#666}.text-\[\#777\]{color:#777}.text-\[\#888\]{color:#888}.text-\[\#b8b8b8\]{color:#b8b8b8}.text-\[\#d6d6d6\]{color:#d6d6d6}.text-\[\#d8ccff\]{color:#d8ccff}.text-\[\#f4f4f4\]{color:#f4f4f4}.text-amber-200{color:var(--color-amber-200)}.text-amber-300{color:var(--color-amber-300)}.text-black{color:var(--color-black)}.text-emerald-300{color:var(--color-emerald-300)}.text-inherit{color:inherit}.text-rose-300{color:var(--color-rose-300)}.text-violet-200{color:var(--color-violet-200)}.text-white{color:var(--color-white)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.decoration-white{-webkit-text-decoration-color:var(--color-white);-webkit-text-decoration-color:var(--color-white);text-decoration-color:var(--color-white)}.underline-offset-4{text-underline-offset:4px}.\[color-scheme\:dark\]{color-scheme:dark}.saturate-50{--tw-saturate:saturate(50%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-\[filter\,stroke\,stroke-width\]{transition-property:filter,stroke,stroke-width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.first\:border-t-0:first-child{border-top-style:var(--tw-border-style);border-top-width:0}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.hover\:border-\[\#beaaff\]\/45:hover{border-color:oklab(78.6663% .0494663 -.109708/.45)}.hover\:border-white:hover{border-color:var(--color-white)}.hover\:border-white\/25:hover{border-color:#ffffff40}@supports (color:color-mix(in lab, red, red)){.hover\:border-white\/25:hover{border-color:color-mix(in oklab, var(--color-white) 25%, transparent)}}.hover\:border-white\/30:hover{border-color:#ffffff4d}@supports (color:color-mix(in lab, red, red)){.hover\:border-white\/30:hover{border-color:color-mix(in oklab, var(--color-white) 30%, transparent)}}.hover\:border-b-white\/45:hover{border-bottom-color:#ffffff73}@supports (color:color-mix(in lab, red, red)){.hover\:border-b-white\/45:hover{border-bottom-color:color-mix(in oklab, var(--color-white) 45%, transparent)}}.hover\:bg-\[\#151515\]:hover{background-color:#151515}.hover\:bg-\[rgba\(190\,170\,255\,0\.06\)\]:hover{background-color:#beaaff0f}.hover\:bg-\[rgba\(190\,170\,255\,0\.07\)\]:hover{background-color:#beaaff12}.hover\:bg-white\/\[0\.04\]:hover{background-color:#ffffff0a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/\[0\.04\]:hover{background-color:color-mix(in oklab, var(--color-white) 4%, transparent)}}.hover\:bg-white\/\[0\.05\]:hover{background-color:#ffffff0d}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/\[0\.05\]:hover{background-color:color-mix(in oklab, var(--color-white) 5%, transparent)}}.hover\:text-white:hover{color:var(--color-white)}.hover\:underline:hover{text-decoration-line:underline}.hover\:brightness-125:hover{--tw-brightness:brightness(125%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}}.focus-visible\:stroke-emerald-400:focus-visible{stroke:var(--color-emerald-400)}.focus-visible\:stroke-2:focus-visible{stroke-width:2px}@media not all and (min-width:48rem){.max-md\:hidden{display:none}.max-md\:w-full{width:100%}.max-md\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.max-md\:flex-col{flex-direction:column}.max-md\:flex-wrap{flex-wrap:wrap}.max-md\:items-stretch{align-items:stretch}.max-md\:justify-between{justify-content:space-between}.max-md\:justify-items-start{justify-items:start}.max-md\:gap-1{gap:calc(var(--spacing) * 1)}.max-md\:px-4{padding-inline:calc(var(--spacing) * 4)}.max-md\:py-4{padding-block:calc(var(--spacing) * 4)}.max-md\:text-left{text-align:left}}@media (min-width:40rem){.sm\:basis-1\/3{flex-basis:33.3333%}}@media (min-width:48rem){.md\:grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.md\:truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md\:px-8{padding-inline:calc(var(--spacing) * 8)}.md\:text-right{text-align:right}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}}@media (min-width:64rem){.lg\:grid-cols-\[minmax\(21rem\,0\.32fr\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(21rem,.32fr) minmax(0,1fr)}}.\[\&_code\]\:break-words code{overflow-wrap:break-word}.\[\&_code\]\:whitespace-pre-wrap code{white-space:pre-wrap}.\[\&_pre\]\:\!m-0 pre{margin:calc(var(--spacing) * 0)!important}.\[\&_pre\]\:\!overflow-visible pre{overflow:visible!important}.\[\&_pre\]\:\!bg-transparent pre{background-color:#0000!important}.\[\&_pre\]\:\!p-0 pre{padding:calc(var(--spacing) * 0)!important}.\[\&_pre\]\:font-mono pre{font-family:var(--font-mono)}.\[\&_pre\]\:text-\[0\.86rem\] pre{font-size:.86rem}.\[\&_pre\]\:leading-relaxed pre{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.\[\&_pre\]\:break-words pre{overflow-wrap:break-word}.\[\&_pre\]\:whitespace-pre-wrap pre{white-space:pre-wrap}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-dashboard",
3
- "version": "0.60.0",
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.0"
42
+ "@sentry/junior": "0.61.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@tailwindcss/cli": "^4.3.0",
@@ -1,3 +1,4 @@
1
+ import { useState } from "react";
1
2
  import { useQuery } from "@tanstack/react-query";
2
3
  import { codeToHtml, type BundledLanguage } from "shiki/bundle/web";
3
4
 
@@ -6,7 +7,7 @@ import type { CodeBlock, MarkupNode } from "./types";
6
7
 
7
8
  /** Count rendered children so transcripts can decide which markup node expands. */
8
9
  export function countStructuredBlockChildren(block: CodeBlock): number {
9
- if (!canRenderStructuredMarkup(block.language)) return 1;
10
+ if (!canRenderStructuredMarkup(block)) return 1;
10
11
  const rootCount = parseMarkupNodes(block.code, block.language).length;
11
12
  return rootCount > 0 ? rootCount : 1;
12
13
  }
@@ -53,8 +54,18 @@ function MarkupNodeView(props: { defaultOpen?: boolean; node: MarkupNode }) {
53
54
  );
54
55
  }
55
56
 
57
+ return (
58
+ <MarkupElementView defaultOpen={props.defaultOpen} node={props.node} />
59
+ );
60
+ }
61
+
62
+ function MarkupElementView(props: {
63
+ defaultOpen?: boolean;
64
+ node: Extract<MarkupNode, { type: "element" }>;
65
+ }) {
56
66
  const children = props.node.children;
57
67
  const hasChildren = children.length > 0;
68
+ const [open, setOpen] = useState(props.defaultOpen ?? true);
58
69
  const attributes = props.node.attributes.map(([name, value]) => (
59
70
  <span className="ml-1.5 text-[#b8b8b8]" key={name}>
60
71
  {name}=<span className="text-white">"{value}"</span>
@@ -74,12 +85,15 @@ function MarkupNodeView(props: { defaultOpen?: boolean; node: MarkupNode }) {
74
85
 
75
86
  return (
76
87
  <details
77
- className="group min-w-0 break-words"
78
- open={props.defaultOpen ?? true}
88
+ className="min-w-0 break-words"
89
+ onToggle={(event) => {
90
+ if (event.currentTarget !== event.target) return;
91
+ setOpen(event.currentTarget.open);
92
+ }}
93
+ open={open}
79
94
  >
80
95
  <summary className="-ml-1 flex w-full max-w-full cursor-pointer list-none flex-wrap items-baseline px-1 py-0.5 transition-colors hover:bg-white/[0.05] hover:text-white [&::-webkit-details-marker]:hidden">
81
- <span className="mr-1 w-2 text-white group-open:hidden">+</span>
82
- <span className="mr-1 hidden w-2 text-white group-open:inline">-</span>
96
+ <span className="mr-1 w-2 text-white">{open ? "-" : "+"}</span>
83
97
  <span className="text-[#888]">&lt;</span>
84
98
  <span className="font-bold text-white">{props.node.tagName}</span>
85
99
  {attributes}
@@ -1,4 +1,8 @@
1
- import { slackLocationLabel } from "../format";
1
+ import {
2
+ formatDurationTotal,
3
+ formatUsageTotal,
4
+ slackLocationLabel,
5
+ } from "../format";
2
6
  import type { Conversation } from "../types";
3
7
 
4
8
  /** Render compact conversation metadata aligned to row context. */
@@ -6,14 +10,30 @@ export function ConversationRowStats(props: {
6
10
  conversation: Conversation;
7
11
  timeLabel: string;
8
12
  }) {
13
+ const tokens = formatUsageTotal(
14
+ props.conversation.turns.map((turn) => turn.cumulativeUsage),
15
+ );
16
+ const runtime = formatDurationTotal(
17
+ props.conversation.turns.map((turn) => turn.cumulativeDurationMs),
18
+ );
19
+ const primaryStats = [
20
+ `${props.conversation.turns.length} turns`,
21
+ tokens,
22
+ runtime ? `${runtime} runtime` : undefined,
23
+ ].filter(Boolean);
24
+ const secondaryStats = [
25
+ props.timeLabel,
26
+ slackLocationLabel(props.conversation, { includeId: false }),
27
+ ].filter(Boolean);
28
+
9
29
  return (
10
30
  <div className="grid min-w-0 justify-items-end gap-1 text-right max-md:justify-items-start max-md:text-left">
11
31
  <div className="text-[0.84rem] leading-relaxed text-[#b8b8b8]">
12
- {props.conversation.turns.length} turns · {props.timeLabel}
32
+ {primaryStats.join(" · ")}
13
33
  </div>
14
- {props.conversation.channel ? (
34
+ {secondaryStats.length > 0 ? (
15
35
  <div className="max-w-full break-words text-[0.84rem] leading-relaxed text-[#888] md:truncate">
16
- {slackLocationLabel(props.conversation, { includeId: false })}
36
+ {secondaryStats.join(" · ")}
17
37
  </div>
18
38
  ) : null}
19
39
  </div>
@@ -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}
@@ -1,8 +1,9 @@
1
- import type { ClipboardEventHandler, ReactNode } from "react";
1
+ import { useState, type ClipboardEventHandler, type ReactNode } from "react";
2
2
 
3
3
  import { HighlightedCode } from "../code";
4
4
  import {
5
5
  detectLanguage,
6
+ detectOutputLanguage,
6
7
  formatBytes,
7
8
  formatMessageOffset,
8
9
  formatMessageTimestamp,
@@ -450,19 +451,7 @@ function TranscriptPartView(props: {
450
451
 
451
452
  const value = part.output;
452
453
  if (part.type === "thinking") {
453
- const rendered = stringifyPartValue(value);
454
- return (
455
- <details className="border border-[#beaaff]/20 bg-white/[0.03] transition-colors hover:border-[#beaaff]/45 hover:bg-[rgba(190,170,255,0.06)]">
456
- <summary className="grid cursor-pointer grid-cols-[auto_minmax(0,1fr)] items-center gap-3 px-3 py-2 font-mono text-[0.8rem] leading-tight text-[#888] hover:bg-[rgba(190,170,255,0.07)] max-md:grid-cols-1 max-md:gap-1">
457
- <span className="uppercase text-[#b8b8b8]">thinking</span>
458
- <span className="min-w-0 truncate">{previewToolValue(value)}</span>
459
- </summary>
460
- <HighlightedCode
461
- code={rendered || "{}"}
462
- language={detectLanguage(rendered)}
463
- />
464
- </details>
465
- );
454
+ return <ThinkingPartView value={value} />;
466
455
  }
467
456
 
468
457
  const rendered = stringifyPartValue(value);
@@ -482,6 +471,37 @@ function TranscriptPartView(props: {
482
471
  );
483
472
  }
484
473
 
474
+ function ThinkingPartView(props: { value: unknown }) {
475
+ const [open, setOpen] = useState(false);
476
+ const rendered = stringifyPartValue(props.value);
477
+
478
+ return (
479
+ <details
480
+ className="border border-[#beaaff]/20 bg-white/[0.03] transition-colors hover:border-[#beaaff]/45 hover:bg-[rgba(190,170,255,0.06)]"
481
+ onToggle={(event) => {
482
+ if (event.currentTarget !== event.target) return;
483
+ setOpen(event.currentTarget.open);
484
+ }}
485
+ open={open}
486
+ >
487
+ <summary className="grid cursor-pointer grid-cols-[auto_minmax(0,1fr)] items-center gap-3 px-3 py-2 font-mono text-[0.8rem] leading-tight text-[#888] hover:bg-[rgba(190,170,255,0.07)] max-md:grid-cols-1 max-md:gap-1">
488
+ <span className="uppercase text-[#b8b8b8]">thinking</span>
489
+ {open ? null : (
490
+ <span className="min-w-0 truncate">
491
+ {previewToolValue(props.value)}
492
+ </span>
493
+ )}
494
+ </summary>
495
+ <div className="border-t border-[#beaaff]/15 px-3 py-3">
496
+ <HighlightedCode
497
+ code={rendered || "{}"}
498
+ language={detectOutputLanguage(rendered)}
499
+ />
500
+ </div>
501
+ </details>
502
+ );
503
+ }
504
+
485
505
  function isString(value: string | undefined): value is string {
486
506
  return typeof value === "string" && value.length > 0;
487
507
  }
@@ -104,6 +104,17 @@ export function formatMs(value: number | undefined): string {
104
104
  return `${minutes}m ${remainingSeconds}s`;
105
105
  }
106
106
 
107
+ /** Format aggregate runtime across turn summaries when duration data exists. */
108
+ export function formatDurationTotal(
109
+ durations: Array<number | undefined>,
110
+ ): string {
111
+ const total = durations.reduce<number | undefined>((sum, value) => {
112
+ if (typeof value !== "number" || !Number.isFinite(value)) return sum;
113
+ return (sum ?? 0) + Math.max(0, Math.floor(value));
114
+ }, undefined);
115
+ return total === undefined ? "" : formatMs(total);
116
+ }
117
+
107
118
  /** Format transcript event timestamps independently from turn start offsets. */
108
119
  export function formatMessageTimestamp(value: number | undefined): string {
109
120
  if (typeof value !== "number" || !Number.isFinite(value))
@@ -151,6 +162,25 @@ function formatNumber(value: number | undefined): string {
151
162
  return `${formatted}${suffix}`;
152
163
  }
153
164
 
165
+ function getFiniteTokenCount(value: unknown): number | undefined {
166
+ return typeof value === "number" && Number.isFinite(value)
167
+ ? Math.max(0, Math.floor(value))
168
+ : undefined;
169
+ }
170
+
171
+ function getUsageComponentTotal(usage: TurnUsage): number | undefined {
172
+ return [
173
+ usage.inputTokens,
174
+ usage.outputTokens,
175
+ usage.cachedInputTokens,
176
+ usage.cacheCreationTokens,
177
+ ].reduce<number | undefined>((sum, value) => {
178
+ const count = getFiniteTokenCount(value);
179
+ if (count === undefined) return sum;
180
+ return (sum ?? 0) + count;
181
+ }, undefined);
182
+ }
183
+
154
184
  /** Format byte counts in lowercase compact units for transcript metadata. */
155
185
  export function formatBytes(value: number | undefined): string {
156
186
  if (typeof value !== "number" || !Number.isFinite(value)) return "0b";
@@ -174,9 +204,36 @@ function transcriptSource(turn: ConversationTurn) {
174
204
  : (turn.transcriptMetadata ?? []);
175
205
  }
176
206
 
207
+ function isConversationMessageRole(role: string): boolean {
208
+ const normalized = role.toLowerCase();
209
+ return normalized === "user" || normalized === "assistant";
210
+ }
211
+
212
+ function hasTextPart(
213
+ message: Pick<ConversationTurn["transcript"][number], "parts" | "role">,
214
+ ): boolean {
215
+ return message.parts.some((part) => {
216
+ if (part.type !== "text") return false;
217
+ if (part.redacted) return true;
218
+ return typeof part.text === "string" && part.text.trim().length > 0;
219
+ });
220
+ }
221
+
222
+ function isConversationMessage(
223
+ message: Pick<ConversationTurn["transcript"][number], "parts" | "role">,
224
+ ): boolean {
225
+ if (!isConversationMessageRole(message.role)) return false;
226
+ if (message.role.toLowerCase() === "assistant") return hasTextPart(message);
227
+ return message.parts.length > 0;
228
+ }
229
+
177
230
  /** Count visible or redacted message records for a turn. */
178
231
  export function turnMessageCount(turn: ConversationTurn): number {
179
- return turn.transcriptMessageCount ?? transcriptSource(turn).length;
232
+ const source = transcriptSource(turn);
233
+ if (source.length > 0) {
234
+ return source.filter(isConversationMessage).length;
235
+ }
236
+ return turn.transcriptMessageCount ?? 0;
180
237
  }
181
238
 
182
239
  /** Count tool calls from visible transcripts or safe redacted metadata. */
@@ -190,21 +247,9 @@ export function turnToolCallCount(turn: ConversationTurn): number {
190
247
 
191
248
  function totalUsageTokens(usage: TurnUsage | undefined): number | undefined {
192
249
  if (!usage) return undefined;
193
- if (
194
- typeof usage.totalTokens === "number" &&
195
- Number.isFinite(usage.totalTokens)
196
- ) {
197
- return usage.totalTokens;
198
- }
199
- return [
200
- usage.inputTokens,
201
- usage.outputTokens,
202
- usage.cachedInputTokens,
203
- usage.cacheCreationTokens,
204
- ].reduce<number | undefined>((sum, value) => {
205
- if (typeof value !== "number" || !Number.isFinite(value)) return sum;
206
- return (sum ?? 0) + Math.max(0, Math.floor(value));
207
- }, undefined);
250
+ return (
251
+ getUsageComponentTotal(usage) ?? getFiniteTokenCount(usage.totalTokens)
252
+ );
208
253
  }
209
254
 
210
255
  /** Format known token counters without estimating per-message usage. */
@@ -213,6 +258,16 @@ export function formatTokenTotal(usage: TurnUsage | undefined): string {
213
258
  return total === undefined ? "" : `${formatNumber(total)} tokens`;
214
259
  }
215
260
 
261
+ /** Format the aggregate token count across conversation turns. */
262
+ export function formatUsageTotal(usages: Array<TurnUsage | undefined>): string {
263
+ const total = usages.reduce<number | undefined>((sum, usage) => {
264
+ const tokens = totalUsageTokens(usage);
265
+ if (tokens === undefined) return sum;
266
+ return (sum ?? 0) + tokens;
267
+ }, undefined);
268
+ return total === undefined ? "" : `${formatNumber(total)} tokens`;
269
+ }
270
+
216
271
  /** Format known token counters with available input/output detail. */
217
272
  export function formatUsage(usage: TurnUsage | undefined): string {
218
273
  const total = totalUsageTokens(usage);
@@ -456,9 +511,34 @@ function formatCodeBlock(code: string, language: BundledLanguage): string {
456
511
  return language === "json" ? (prettyJsonData(code) ?? code) : code;
457
512
  }
458
513
 
459
- /** Decide whether a fenced block can use the interactive markup renderer. */
460
- export function canRenderStructuredMarkup(language: BundledLanguage): boolean {
461
- 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
+ );
462
542
  }
463
543
 
464
544
  /** Parse markdown into renderable code blocks while preserving plain text blocks. */
@@ -470,24 +550,25 @@ export function parseMarkdownBlocks(text: string): CodeBlock[] {
470
550
  while ((match = fence.exec(text))) {
471
551
  const prose = text.slice(cursor, match.index).trim();
472
552
  if (prose) {
473
- const language = detectLanguage(prose);
474
- blocks.push({ code: formatCodeBlock(prose, language), language });
553
+ const language = detectOutputLanguage(prose);
554
+ blocks.push({ code: formatCodeBlock(prose, language), fenced: false, language });
475
555
  }
476
556
  const language = normalizeLanguage(match[1]);
477
557
  blocks.push({
478
558
  code: formatCodeBlock(match[2] ?? "", language),
559
+ fenced: true,
479
560
  language,
480
561
  });
481
562
  cursor = match.index + match[0].length;
482
563
  }
483
564
  const rest = text.slice(cursor).trim();
484
565
  if (rest) {
485
- const language = detectLanguage(rest);
486
- blocks.push({ code: formatCodeBlock(rest, language), language });
566
+ const language = detectOutputLanguage(rest);
567
+ blocks.push({ code: formatCodeBlock(rest, language), fenced: false, language });
487
568
  }
488
569
  if (blocks.length > 0) return blocks;
489
- const language = detectLanguage(text);
490
- return [{ code: formatCodeBlock(text, language), language }];
570
+ const language = detectOutputLanguage(text);
571
+ return [{ code: formatCodeBlock(text, language), fenced: false, language }];
491
572
  }
492
573
 
493
574
  /** Parse XML/HTML-ish fragments for the collapsible transcript renderer. */
@@ -8,6 +8,7 @@ import {
8
8
  formatConversationDuration,
9
9
  formatRelativeTime,
10
10
  formatTime,
11
+ formatUsageTotal,
11
12
  slackLocationLabel,
12
13
  turnMessageCount,
13
14
  turnToolCallCount,
@@ -123,11 +124,17 @@ function ConversationStats(props: {
123
124
  0,
124
125
  )
125
126
  : undefined;
127
+ const tokens = formatUsageTotal(
128
+ (props.detail?.turns ?? props.conversation.turns).map(
129
+ (turn) => turn.cumulativeUsage,
130
+ ),
131
+ );
126
132
  const stats = [
127
133
  slackLocationLabel(props.conversation, { includeId: false }),
128
134
  `${props.conversation.turns.length} turns`,
129
135
  messages === undefined ? "messages loading" : `${messages} messages`,
130
136
  toolCalls === undefined ? "tool calls loading" : `${toolCalls} tool calls`,
137
+ tokens,
131
138
  formatConversationDuration(props.conversation),
132
139
  `started ${formatTime(props.conversation.startedAt)}`,
133
140
  ].filter(Boolean);
@@ -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
  | {