@mastra/playground-ui 53.0.0-alpha.9 → 53.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/dist/{code-DRGJVroV.js → code-D4Vw_fYq.js} +22 -16
  2. package/dist/code-D4Vw_fYq.js.map +1 -0
  3. package/dist/{code-BJMC6T9D.cjs → code-DhrdtZce.cjs} +22 -15
  4. package/dist/code-DhrdtZce.cjs.map +1 -0
  5. package/dist/{code-block-CBJhDEeB.js → code-block-DeksY23p.js} +2 -2
  6. package/dist/{code-block-CBJhDEeB.js.map → code-block-DeksY23p.js.map} +1 -1
  7. package/dist/{code-block-fmv8bHoT.cjs → code-block-DgHrBuR8.cjs} +2 -2
  8. package/dist/{code-block-fmv8bHoT.cjs.map → code-block-DgHrBuR8.cjs.map} +1 -1
  9. package/dist/{command-DWFd0d4c.js → command-Be-KrIAX.js} +2 -2
  10. package/dist/{command-DWFd0d4c.js.map → command-Be-KrIAX.js.map} +1 -1
  11. package/dist/{command-CUev1UxH.cjs → command-DsTBCmQq.cjs} +2 -2
  12. package/dist/{command-CUev1UxH.cjs.map → command-DsTBCmQq.cjs.map} +1 -1
  13. package/dist/components/ChatShell.cjs.js +20 -1
  14. package/dist/components/ChatShell.cjs.js.map +1 -1
  15. package/dist/components/ChatShell.es.js +20 -1
  16. package/dist/components/ChatShell.es.js.map +1 -1
  17. package/dist/components/Code.cjs.js +3 -1
  18. package/dist/components/Code.cjs.js.map +1 -1
  19. package/dist/components/Code.es.js +1 -1
  20. package/dist/components/CodeBlock.cjs.js +1 -1
  21. package/dist/components/CodeBlock.es.js +1 -1
  22. package/dist/components/CodeEditor.cjs.js +2 -1
  23. package/dist/components/CodeEditor.cjs.js.map +1 -1
  24. package/dist/components/CodeEditor.es.js +1 -1
  25. package/dist/components/Command.cjs.js +1 -1
  26. package/dist/components/Command.es.js +1 -1
  27. package/dist/components/CommandPalette.cjs.js +1 -1
  28. package/dist/components/CommandPalette.es.js +1 -1
  29. package/dist/components/DataCodeSection.cjs.js +1 -1
  30. package/dist/components/DataCodeSection.es.js +1 -1
  31. package/dist/components/DataPanel.cjs.js +1 -1
  32. package/dist/components/DataPanel.es.js +1 -1
  33. package/dist/components/Dialog.cjs.js +181 -2
  34. package/dist/components/Dialog.cjs.js.map +1 -1
  35. package/dist/components/Dialog.es.js +183 -1
  36. package/dist/components/Dialog.es.js.map +1 -1
  37. package/dist/components/HorizontalBars.cjs.js +1 -1
  38. package/dist/components/HorizontalBars.es.js +1 -1
  39. package/dist/components/IntegrationDialog.cjs.js +105 -0
  40. package/dist/components/IntegrationDialog.cjs.js.map +1 -0
  41. package/dist/components/IntegrationDialog.d.ts +2 -0
  42. package/dist/components/IntegrationDialog.es.js +101 -0
  43. package/dist/components/IntegrationDialog.es.js.map +1 -0
  44. package/dist/components/MarkdownRenderer.cjs.js +1 -1
  45. package/dist/components/MarkdownRenderer.es.js +1 -1
  46. package/dist/components/ThreadRail.cjs.js +19 -0
  47. package/dist/components/ThreadRail.cjs.js.map +1 -1
  48. package/dist/components/ThreadRail.es.js +19 -1
  49. package/dist/components/ThreadRail.es.js.map +1 -1
  50. package/dist/components/ai/plan.cjs.js +1 -1
  51. package/dist/components/ai/plan.es.js +1 -1
  52. package/dist/components/ai/tool-call.cjs.js +165 -0
  53. package/dist/components/ai/tool-call.cjs.js.map +1 -1
  54. package/dist/components/ai/tool-call.es.js +159 -3
  55. package/dist/components/ai/tool-call.es.js.map +1 -1
  56. package/dist/{data-code-section-D5I0m1cS.cjs → data-code-section-B_lg_J_0.cjs} +2 -2
  57. package/dist/{data-code-section-D5I0m1cS.cjs.map → data-code-section-B_lg_J_0.cjs.map} +1 -1
  58. package/dist/{data-code-section-d3NffL3V.js → data-code-section-Cm_W_XhO.js} +2 -2
  59. package/dist/{data-code-section-d3NffL3V.js.map → data-code-section-Cm_W_XhO.js.map} +1 -1
  60. package/dist/{data-panel-Bo2HCylZ.cjs → data-panel-Cv9j_IGQ.cjs} +2 -2
  61. package/dist/{data-panel-Bo2HCylZ.cjs.map → data-panel-Cv9j_IGQ.cjs.map} +1 -1
  62. package/dist/{data-panel-T3vp7wTD.js → data-panel-DXxeSnL1.js} +2 -2
  63. package/dist/{data-panel-T3vp7wTD.js.map → data-panel-DXxeSnL1.js.map} +1 -1
  64. package/dist/dialog-CZMPDVUj.js +259 -0
  65. package/dist/dialog-CZMPDVUj.js.map +1 -0
  66. package/dist/dialog-D-7Hkb3j.cjs +293 -0
  67. package/dist/dialog-D-7Hkb3j.cjs.map +1 -0
  68. package/dist/domains/metrics/components/token-usage-by-agent-card-view.cjs.js +1 -1
  69. package/dist/domains/metrics/components/token-usage-by-agent-card-view.es.js +1 -1
  70. package/dist/domains/metrics/components/traces-volume-card-view.cjs.js +1 -1
  71. package/dist/domains/metrics/components/traces-volume-card-view.es.js +1 -1
  72. package/dist/domains/traces/components/span-data-panel-view.cjs.js +1 -1
  73. package/dist/domains/traces/components/span-data-panel-view.es.js +1 -1
  74. package/dist/domains/traces/components/trace-columns-menu.cjs.js +1 -1
  75. package/dist/domains/traces/components/trace-columns-menu.es.js +1 -1
  76. package/dist/domains/traces/components/trace-data-panel-view.cjs.js +1 -1
  77. package/dist/domains/traces/components/trace-data-panel-view.es.js +1 -1
  78. package/dist/domains/traces/hooks/use-branch.cjs.js +2 -6
  79. package/dist/domains/traces/hooks/use-branch.cjs.js.map +1 -1
  80. package/dist/domains/traces/hooks/use-branch.es.js +2 -6
  81. package/dist/domains/traces/hooks/use-branch.es.js.map +1 -1
  82. package/dist/domains/traces/hooks/use-download-trace-json.cjs.js +4 -1
  83. package/dist/domains/traces/hooks/use-download-trace-json.cjs.js.map +1 -1
  84. package/dist/domains/traces/hooks/use-download-trace-json.es.js +4 -1
  85. package/dist/domains/traces/hooks/use-download-trace-json.es.js.map +1 -1
  86. package/dist/domains/traces/hooks/use-trace-spans.cjs.js +10 -7
  87. package/dist/domains/traces/hooks/use-trace-spans.cjs.js.map +1 -1
  88. package/dist/domains/traces/hooks/use-trace-spans.es.js +10 -7
  89. package/dist/domains/traces/hooks/use-trace-spans.es.js.map +1 -1
  90. package/dist/ee/signals/settings/signal-definition-form-dialog.cjs.js +1 -1
  91. package/dist/ee/signals/settings/signal-definition-form-dialog.es.js +1 -1
  92. package/dist/{highlight-CSJjjoqh.js → highlight-BmpoELR4.js} +6 -2
  93. package/dist/{highlight-CSJjjoqh.js.map → highlight-BmpoELR4.js.map} +1 -1
  94. package/dist/{highlight-CEt2vhD8.cjs → highlight-DK1dn-Fh.cjs} +6 -1
  95. package/dist/{highlight-CEt2vhD8.cjs.map → highlight-DK1dn-Fh.cjs.map} +1 -1
  96. package/dist/{horizontal-bars-DZ5J4TKv.js → horizontal-bars-CyP-B-BX.js} +37 -4
  97. package/dist/horizontal-bars-CyP-B-BX.js.map +1 -0
  98. package/dist/{horizontal-bars-CE1O-Y6G.cjs → horizontal-bars-Dgq3PYsg.cjs} +37 -4
  99. package/dist/horizontal-bars-Dgq3PYsg.cjs.map +1 -0
  100. package/dist/{markdown-renderer-DiJBbnYG.js → markdown-renderer-CaFpZTFS.js} +2 -2
  101. package/dist/{markdown-renderer-DiJBbnYG.js.map → markdown-renderer-CaFpZTFS.js.map} +1 -1
  102. package/dist/{markdown-renderer-DGIzNAQY.cjs → markdown-renderer-GVtZLunG.cjs} +2 -2
  103. package/dist/{markdown-renderer-DGIzNAQY.cjs.map → markdown-renderer-GVtZLunG.cjs.map} +1 -1
  104. package/dist/src/domains/traces/hooks/use-trace-spans.d.ts +3 -1
  105. package/dist/src/ds/components/ChatShell/chat-shell.d.ts +19 -2
  106. package/dist/src/ds/components/ChatShell/index.d.ts +3 -2
  107. package/dist/src/ds/components/Code/index.d.ts +1 -0
  108. package/dist/src/ds/components/Code/use-highlight.d.ts +10 -0
  109. package/dist/src/ds/components/CodeEditor/highlight.d.ts +1 -0
  110. package/dist/src/ds/components/CodeEditor/index.d.ts +1 -1
  111. package/dist/src/ds/components/Dialog/dialog-action.d.ts +9 -0
  112. package/dist/src/ds/components/Dialog/dialog-context.d.ts +16 -0
  113. package/dist/src/ds/components/Dialog/dialog-new-variant.stories.d.ts +64 -0
  114. package/dist/src/ds/components/Dialog/dialog.d.ts +15 -6
  115. package/dist/src/ds/components/Dialog/index.d.ts +2 -0
  116. package/dist/src/ds/components/HorizontalBars/horizontal-bars.stories.d.ts +2 -0
  117. package/dist/src/ds/components/IntegrationDialog/__fixtures__/integrations-catalog.d.ts +2 -0
  118. package/dist/src/ds/components/IntegrationDialog/index.d.ts +2 -0
  119. package/dist/src/ds/components/IntegrationDialog/integration-dialog.d.ts +28 -0
  120. package/dist/src/ds/components/IntegrationDialog/integration-dialog.stories.d.ts +24 -0
  121. package/dist/src/ds/components/ThreadRail/index.d.ts +1 -0
  122. package/dist/src/ds/components/ThreadRail/turn-groups.d.ts +19 -0
  123. package/dist/src/ds/components/ai/tool-call/ansi.d.ts +2 -0
  124. package/dist/src/ds/components/ai/tool-call/group-consecutive.d.ts +17 -0
  125. package/dist/src/ds/components/ai/tool-call/index.d.ts +4 -0
  126. package/dist/src/ds/components/ai/tool-call/tool-call-edit.d.ts +4 -0
  127. package/dist/src/ds/components/ai/tool-call/tool-call-group.d.ts +14 -0
  128. package/dist/src/ds/components/ai/tool-call/tool-call.d.ts +4 -0
  129. package/dist/src/ds/components/ai/tool-call/tool-presentation.d.ts +10 -0
  130. package/dist/style.css +138 -0
  131. package/package.json +8 -8
  132. package/dist/code-BJMC6T9D.cjs.map +0 -1
  133. package/dist/code-DRGJVroV.js.map +0 -1
  134. package/dist/dialog-CESxLg8S.js +0 -76
  135. package/dist/dialog-CESxLg8S.js.map +0 -1
  136. package/dist/dialog-K0q0KOOW.cjs +0 -107
  137. package/dist/dialog-K0q0KOOW.cjs.map +0 -1
  138. package/dist/horizontal-bars-CE1O-Y6G.cjs.map +0 -1
  139. package/dist/horizontal-bars-DZ5J4TKv.js.map +0 -1
@@ -5,6 +5,20 @@ const scrollArea = require('./scroll-area-B1vdOFuC.cjs');
5
5
  const tooltip = require('./tooltip-DIi_oOxO.cjs');
6
6
  const utils_cn = require('./cn-DCPcv4xv.cjs');
7
7
 
8
+ function hexLuminance(color) {
9
+ const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color.trim())?.[1];
10
+ if (!hex) return void 0;
11
+ const full = hex.length === 3 ? [...hex].map((c) => c + c).join("") : hex;
12
+ const linear = (offset) => {
13
+ const channel = parseInt(full.slice(offset, offset + 2), 16) / 255;
14
+ return channel <= 0.03928 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
15
+ };
16
+ return 0.2126 * linear(0) + 0.7152 * linear(2) + 0.0722 * linear(4);
17
+ }
18
+ function needsDarkLabel(color) {
19
+ const luminance = color ? hexLuminance(color) : void 0;
20
+ return luminance !== void 0 && luminance > 0.25;
21
+ }
8
22
  function HorizontalBars({
9
23
  data,
10
24
  segments,
@@ -29,14 +43,16 @@ function HorizontalBars({
29
43
  ] }),
30
44
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-3.5", children: sorted.map((d) => {
31
45
  const total = d.values.reduce((s, v) => s + v, 0);
46
+ const barWidth = `${maxVal > 0 ? total / maxVal * 100 : 0}%`;
47
+ const darkLabelOnFill = needsDarkLabel(segments[d.values.findIndex((v) => v > 0)]?.color);
32
48
  const rowBody = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
33
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-full min-w-0 flex-1", children: [
49
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-full min-w-0 flex-1", style: { "--bar-width": barWidth }, children: [
34
50
  /* @__PURE__ */ jsxRuntime.jsxs(tooltip.Tooltip, { children: [
35
51
  /* @__PURE__ */ jsxRuntime.jsx(tooltip.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
36
52
  "div",
37
53
  {
38
54
  className: utils_cn.cn("absolute inset-y-0 left-0", d.href ? "cursor-pointer" : "cursor-default"),
39
- style: { width: `${maxVal > 0 ? total / maxVal * 100 : 0}%` },
55
+ style: { width: barWidth },
40
56
  children: segments.map((seg, si) => {
41
57
  const val = d.values[si] ?? 0;
42
58
  const pct = total > 0 ? val / total * 100 : 0;
@@ -81,7 +97,24 @@ function HorizontalBars({
81
97
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto pl-3", children: fmt(d.values[si] ?? 0) })
82
98
  ] }, seg.label)) }) })
83
99
  ] }),
84
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui-sm text-neutral4 pointer-events-none absolute inset-y-0 left-2.5 z-10 flex items-center truncate", children: d.name })
100
+ /* @__PURE__ */ jsxRuntime.jsx(
101
+ "div",
102
+ {
103
+ className: utils_cn.cn(
104
+ "pointer-events-none absolute inset-0 z-10",
105
+ darkLabelOnFill && "dark:[clip-path:inset(0_0_0_var(--bar-width))]"
106
+ ),
107
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui-sm text-neutral4 absolute inset-y-0 left-2.5 flex items-center truncate", children: d.name })
108
+ }
109
+ ),
110
+ darkLabelOnFill && /* @__PURE__ */ jsxRuntime.jsx(
111
+ "div",
112
+ {
113
+ "aria-hidden": true,
114
+ className: "pointer-events-none absolute inset-y-0 left-0 z-10 hidden w-(--bar-width) overflow-hidden dark:block",
115
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui-sm text-neutral1 absolute inset-y-0 left-2.5 flex items-center whitespace-nowrap", children: d.name })
116
+ }
117
+ )
85
118
  ] }),
86
119
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui-md text-neutral4 shrink-0 pr-3 tabular-nums", children: fmt(total) })
87
120
  ] });
@@ -105,4 +138,4 @@ function Wrapper({ children }) {
105
138
  }
106
139
 
107
140
  exports.HorizontalBars = HorizontalBars;
108
- //# sourceMappingURL=horizontal-bars-CE1O-Y6G.cjs.map
141
+ //# sourceMappingURL=horizontal-bars-Dgq3PYsg.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"horizontal-bars-Dgq3PYsg.cjs","sources":["../src/ds/components/HorizontalBars/horizontal-bars.tsx"],"sourcesContent":["import type { CSSProperties, ReactNode } from 'react';\nimport { ScrollArea } from '@/ds/components/ScrollArea/scroll-area';\nimport { Tooltip, TooltipTrigger, TooltipContent } from '@/ds/components/Tooltip';\nimport type { LinkComponent } from '@/ds/types/link-component';\nimport { cn } from '@/lib/utils';\n\n/** Relative luminance (WCAG) of a hex color, or `undefined` for anything else (e.g. `var(--token)`). */\nfunction hexLuminance(color: string): number | undefined {\n const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color.trim())?.[1];\n if (!hex) return undefined;\n const full = hex.length === 3 ? [...hex].map(c => c + c).join('') : hex;\n const linear = (offset: number) => {\n const channel = parseInt(full.slice(offset, offset + 2), 16) / 255;\n return channel <= 0.03928 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * linear(0) + 0.7152 * linear(2) + 0.0722 * linear(4);\n}\n\n/** Bright fills (yellow, orange, green, red) swallow the light label; dark text reads better on them. */\nfunction needsDarkLabel(color: string | undefined): boolean {\n const luminance = color ? hexLuminance(color) : undefined;\n return luminance !== undefined && luminance > 0.25;\n}\n\ntype Segment = { label: string; color: string };\n\ntype HorizontalBarRow = {\n name: string;\n values: number[];\n /** If present, the whole row is rendered as a link to this URL. */\n href?: string;\n /** If present, an individual segment becomes its own link. Indices align with `segments`. */\n hrefs?: Array<string | undefined>;\n};\n\nexport function HorizontalBars({\n data,\n segments,\n maxVal,\n fmt,\n className,\n LinkComponent = 'a',\n}: {\n data: Array<HorizontalBarRow>;\n segments: Segment[];\n maxVal: number;\n fmt: (v: number) => string;\n className?: string;\n /** Override how links produced by `href` / `hrefs` are rendered. Receives `href`,\n * `className`, `aria-label`, and `children`. Defaults to a plain `<a>` element;\n * consumers using a router should pass an adapter that maps `href` to their\n * navigation primitive (e.g. react-router `<Link to={href} />`). */\n LinkComponent?: LinkComponent;\n}) {\n const sorted = [...data].sort((a, b) => {\n const totalB = b.values.reduce((s, v) => s + v, 0);\n const totalA = a.values.reduce((s, v) => s + v, 0);\n return totalB - totalA;\n });\n\n const isStacked = segments.length > 1;\n\n return (\n <ScrollArea className={cn('size-full', className)}>\n <div className=\"mt-2 mb-4 flex items-center gap-3\">\n <div className=\"flex flex-1 items-center gap-4\">\n {segments.map(seg => (\n <div key={seg.label} className=\"flex items-center gap-2\">\n <div className=\"size-2 rounded-full\" style={{ backgroundColor: seg.color }} />\n <span className=\"text-ui-sm text-neutral3\">{seg.label}</span>\n </div>\n ))}\n </div>\n <span className=\"text-ui-sm text-neutral2 shrink-0 pr-2\">Total</span>\n </div>\n <div className=\"grid gap-3.5\">\n {sorted.map(d => {\n const total = d.values.reduce((s, v) => s + v, 0);\n const barWidth = `${maxVal > 0 ? (total / maxVal) * 100 : 0}%`;\n // The label starts over the first filled segment; that fill decides the label tone in dark mode.\n // Light mode fades every fill to 40% (see below), so the default label already reads there.\n const darkLabelOnFill = needsDarkLabel(segments[d.values.findIndex(v => v > 0)]?.color);\n const rowBody = (\n <>\n <div className=\"relative h-full min-w-0 flex-1\" style={{ '--bar-width': barWidth } as CSSProperties}>\n <Tooltip>\n <TooltipTrigger asChild>\n <div\n className={cn('absolute inset-y-0 left-0', d.href ? 'cursor-pointer' : 'cursor-default')}\n style={{ width: barWidth }}\n >\n {segments.map((seg, si) => {\n const val = d.values[si] ?? 0;\n const pct = total > 0 ? (val / total) * 100 : 0;\n const left = d.values.slice(0, si).reduce((s, v) => s + (total > 0 ? (v / total) * 100 : 0), 0);\n const isLastWithValue = d.values.slice(si + 1).every(v => !v);\n // Only honor segment-level links when the row itself is not an anchor.\n // Otherwise we'd render <a> nested inside <a>, which is invalid HTML.\n const segHref = d.href ? undefined : d.hrefs?.[si];\n\n const segmentNode = (\n <div\n className={cn(\n 'absolute inset-y-0 opacity-40 dark:opacity-100',\n isStacked && si === 0 && 'rounded-l',\n isStacked && isLastWithValue && 'rounded-r',\n !isStacked && 'rounded',\n segHref && 'cursor-pointer transition-opacity hover:opacity-70',\n )}\n style={{\n left: isStacked ? `${left}%` : 0,\n width: isStacked ? `${pct}%` : `${pct}%`,\n backgroundColor: seg.color,\n }}\n />\n );\n\n if (segHref) {\n return (\n <LinkComponent\n key={seg.label}\n href={segHref}\n aria-label={`${d.name} — ${seg.label}`}\n className=\"contents\"\n >\n {segmentNode}\n </LinkComponent>\n );\n }\n return <Wrapper key={seg.label}>{segmentNode}</Wrapper>;\n })}\n </div>\n </TooltipTrigger>\n <TooltipContent side=\"top\" className=\"font-mono\">\n <div className=\"grid gap-1\">\n {segments.map((seg, si) => (\n <div key={seg.label} className=\"flex items-center gap-2\">\n <span>{seg.label}</span>\n <span className=\"ml-auto pl-3\">{fmt(d.values[si] ?? 0)}</span>\n </div>\n ))}\n </div>\n </TooltipContent>\n </Tooltip>\n <div\n className={cn(\n 'pointer-events-none absolute inset-0 z-10',\n darkLabelOnFill && 'dark:[clip-path:inset(0_0_0_var(--bar-width))]',\n )}\n >\n <span className=\"text-ui-sm text-neutral4 absolute inset-y-0 left-2.5 flex items-center truncate\">\n {d.name}\n </span>\n </div>\n {darkLabelOnFill && (\n <div\n aria-hidden\n className=\"pointer-events-none absolute inset-y-0 left-0 z-10 hidden w-(--bar-width) overflow-hidden dark:block\"\n >\n <span className=\"text-ui-sm text-neutral1 absolute inset-y-0 left-2.5 flex items-center whitespace-nowrap\">\n {d.name}\n </span>\n </div>\n )}\n </div>\n <span className=\"text-ui-md text-neutral4 shrink-0 pr-3 tabular-nums\">{fmt(total)}</span>\n </>\n );\n\n if (d.href) {\n return (\n <LinkComponent\n key={d.name}\n href={d.href}\n className=\"hover:bg-surface3 focus-visible:bg-surface3 flex h-6 cursor-pointer items-center gap-14 rounded transition-colors outline-none\"\n >\n {rowBody}\n </LinkComponent>\n );\n }\n return (\n <div key={d.name} className=\"flex h-6 items-center gap-14\">\n {rowBody}\n </div>\n );\n })}\n </div>\n </ScrollArea>\n );\n}\n\nfunction Wrapper({ children }: { children: ReactNode }) {\n return <>{children}</>;\n}\n"],"names":["ScrollArea","cn","jsxs","jsx","Fragment","Tooltip","TooltipTrigger","TooltipContent"],"mappings":";;;;;;;AAOA,SAAS,aAAa,KAAA,EAAmC;AACvD,EAAA,MAAM,MAAM,+BAAA,CAAgC,IAAA,CAAK,MAAM,IAAA,EAAM,IAAI,CAAC,CAAA;AAClE,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,MAAA,KAAW,CAAA,GAAI,CAAC,GAAG,GAAG,CAAA,CAAE,GAAA,CAAI,OAAK,CAAA,GAAI,CAAC,CAAA,CAAE,IAAA,CAAK,EAAE,CAAA,GAAI,GAAA;AACpE,EAAA,MAAM,MAAA,GAAS,CAAC,MAAA,KAAmB;AACjC,IAAA,MAAM,OAAA,GAAU,SAAS,IAAA,CAAK,KAAA,CAAM,QAAQ,MAAA,GAAS,CAAC,CAAA,EAAG,EAAE,CAAA,GAAI,GAAA;AAC/D,IAAA,OAAO,WAAW,OAAA,GAAU,OAAA,GAAU,KAAA,GAAA,CAAA,CAAU,OAAA,GAAU,SAAS,KAAA,KAAU,GAAA;AAAA,EAC/E,CAAA;AACA,EAAA,OAAO,MAAA,GAAS,MAAA,CAAO,CAAC,CAAA,GAAI,MAAA,GAAS,OAAO,CAAC,CAAA,GAAI,MAAA,GAAS,MAAA,CAAO,CAAC,CAAA;AACpE;AAGA,SAAS,eAAe,KAAA,EAAoC;AAC1D,EAAA,MAAM,SAAA,GAAY,KAAA,GAAQ,YAAA,CAAa,KAAK,CAAA,GAAI,MAAA;AAChD,EAAA,OAAO,SAAA,KAAc,UAAa,SAAA,GAAY,IAAA;AAChD;AAaO,SAAS,cAAA,CAAe;AAAA,EAC7B,IAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,GAAA;AAAA,EACA,SAAA;AAAA,EACA,aAAA,GAAgB;AAClB,CAAA,EAWG;AACD,EAAA,MAAM,MAAA,GAAS,CAAC,GAAG,IAAI,EAAE,IAAA,CAAK,CAAC,GAAG,CAAA,KAAM;AACtC,IAAA,MAAM,MAAA,GAAS,EAAE,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,GAAI,CAAA,EAAG,CAAC,CAAA;AACjD,IAAA,MAAM,MAAA,GAAS,EAAE,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,GAAI,CAAA,EAAG,CAAC,CAAA;AACjD,IAAA,OAAO,MAAA,GAAS,MAAA;AAAA,EAClB,CAAC,CAAA;AAED,EAAA,MAAM,SAAA,GAAY,SAAS,MAAA,GAAS,CAAA;AAEpC,EAAA,uCACGA,qBAAA,EAAA,EAAW,SAAA,EAAWC,WAAA,CAAG,WAAA,EAAa,SAAS,CAAA,EAC9C,QAAA,EAAA;AAAA,oBAAAC,eAAA,CAAC,KAAA,EAAA,EAAI,WAAU,mCAAA,EACb,QAAA,EAAA;AAAA,sBAAAC,cAAA,CAAC,KAAA,EAAA,EAAI,WAAU,gCAAA,EACZ,QAAA,EAAA,QAAA,CAAS,IAAI,CAAA,GAAA,qBACZD,eAAA,CAAC,KAAA,EAAA,EAAoB,SAAA,EAAU,yBAAA,EAC7B,QAAA,EAAA;AAAA,wBAAAC,cAAA,CAAC,KAAA,EAAA,EAAI,WAAU,qBAAA,EAAsB,KAAA,EAAO,EAAE,eAAA,EAAiB,GAAA,CAAI,OAAM,EAAG,CAAA;AAAA,wBAC5EA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,0BAAA,EAA4B,cAAI,KAAA,EAAM;AAAA,OAAA,EAAA,EAF9C,GAAA,CAAI,KAGd,CACD,CAAA,EACH,CAAA;AAAA,sBACAA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,wCAAA,EAAyC,QAAA,EAAA,OAAA,EAAK;AAAA,KAAA,EAChE,CAAA;AAAA,mCACC,KAAA,EAAA,EAAI,SAAA,EAAU,cAAA,EACZ,QAAA,EAAA,MAAA,CAAO,IAAI,CAAA,CAAA,KAAK;AACf,MAAA,MAAM,KAAA,GAAQ,EAAE,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,GAAI,CAAA,EAAG,CAAC,CAAA;AAChD,MAAA,MAAM,WAAW,CAAA,EAAG,MAAA,GAAS,IAAK,KAAA,GAAQ,MAAA,GAAU,MAAM,CAAC,CAAA,CAAA,CAAA;AAG3D,MAAA,MAAM,eAAA,GAAkB,cAAA,CAAe,QAAA,CAAS,CAAA,CAAE,MAAA,CAAO,SAAA,CAAU,CAAA,CAAA,KAAK,CAAA,GAAI,CAAC,CAAC,CAAA,EAAG,KAAK,CAAA;AACtF,MAAA,MAAM,0BACJD,eAAA,CAAAE,mBAAA,EAAA,EACE,QAAA,EAAA;AAAA,wBAAAF,eAAA,CAAC,SAAI,SAAA,EAAU,gCAAA,EAAiC,OAAO,EAAE,aAAA,EAAe,UAAS,EAC/E,QAAA,EAAA;AAAA,0BAAAA,eAAA,CAACG,eAAA,EAAA,EACC,QAAA,EAAA;AAAA,4BAAAF,cAAA,CAACG,sBAAA,EAAA,EAAe,SAAO,IAAA,EACrB,QAAA,kBAAAH,cAAA;AAAA,cAAC,KAAA;AAAA,cAAA;AAAA,gBACC,WAAWF,WAAA,CAAG,2BAAA,EAA6B,CAAA,CAAE,IAAA,GAAO,mBAAmB,gBAAgB,CAAA;AAAA,gBACvF,KAAA,EAAO,EAAE,KAAA,EAAO,QAAA,EAAS;AAAA,gBAExB,QAAA,EAAA,QAAA,CAAS,GAAA,CAAI,CAAC,GAAA,EAAK,EAAA,KAAO;AACzB,kBAAA,MAAM,GAAA,GAAM,CAAA,CAAE,MAAA,CAAO,EAAE,CAAA,IAAK,CAAA;AAC5B,kBAAA,MAAM,GAAA,GAAM,KAAA,GAAQ,CAAA,GAAK,GAAA,GAAM,QAAS,GAAA,GAAM,CAAA;AAC9C,kBAAA,MAAM,OAAO,CAAA,CAAE,MAAA,CAAO,MAAM,CAAA,EAAG,EAAE,EAAE,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,KAAM,KAAK,KAAA,GAAQ,CAAA,GAAK,IAAI,KAAA,GAAS,GAAA,GAAM,IAAI,CAAC,CAAA;AAC9F,kBAAA,MAAM,eAAA,GAAkB,CAAA,CAAE,MAAA,CAAO,KAAA,CAAM,EAAA,GAAK,CAAC,CAAA,CAAE,KAAA,CAAM,CAAA,CAAA,KAAK,CAAC,CAAC,CAAA;AAG5D,kBAAA,MAAM,UAAU,CAAA,CAAE,IAAA,GAAO,MAAA,GAAY,CAAA,CAAE,QAAQ,EAAE,CAAA;AAEjD,kBAAA,MAAM,WAAA,mBACJE,cAAA;AAAA,oBAAC,KAAA;AAAA,oBAAA;AAAA,sBACC,SAAA,EAAWF,WAAA;AAAA,wBACT,gDAAA;AAAA,wBACA,SAAA,IAAa,OAAO,CAAA,IAAK,WAAA;AAAA,wBACzB,aAAa,eAAA,IAAmB,WAAA;AAAA,wBAChC,CAAC,SAAA,IAAa,SAAA;AAAA,wBACd,OAAA,IAAW;AAAA,uBACb;AAAA,sBACA,KAAA,EAAO;AAAA,wBACL,IAAA,EAAM,SAAA,GAAY,CAAA,EAAG,IAAI,CAAA,CAAA,CAAA,GAAM,CAAA;AAAA,wBAC/B,OAAO,SAAA,GAAY,CAAA,EAAG,GAAG,CAAA,CAAA,CAAA,GAAM,GAAG,GAAG,CAAA,CAAA,CAAA;AAAA,wBACrC,iBAAiB,GAAA,CAAI;AAAA;AACvB;AAAA,mBACF;AAGF,kBAAA,IAAI,OAAA,EAAS;AACX,oBAAA,uBACEE,cAAA;AAAA,sBAAC,aAAA;AAAA,sBAAA;AAAA,wBAEC,IAAA,EAAM,OAAA;AAAA,wBACN,cAAY,CAAA,EAAG,CAAA,CAAE,IAAI,CAAA,GAAA,EAAM,IAAI,KAAK,CAAA,CAAA;AAAA,wBACpC,SAAA,EAAU,UAAA;AAAA,wBAET,QAAA,EAAA;AAAA,uBAAA;AAAA,sBALI,GAAA,CAAI;AAAA,qBAMX;AAAA,kBAEJ;AACA,kBAAA,uBAAOA,cAAA,CAAC,OAAA,EAAA,EAAyB,QAAA,EAAA,WAAA,EAAA,EAAZ,GAAA,CAAI,KAAoB,CAAA;AAAA,gBAC/C,CAAC;AAAA;AAAA,aACH,EACF,CAAA;AAAA,2CACCI,sBAAA,EAAA,EAAe,IAAA,EAAK,OAAM,SAAA,EAAU,WAAA,EACnC,yCAAC,KAAA,EAAA,EAAI,SAAA,EAAU,YAAA,EACZ,QAAA,EAAA,QAAA,CAAS,IAAI,CAAC,GAAA,EAAK,uBAClBL,eAAA,CAAC,KAAA,EAAA,EAAoB,WAAU,yBAAA,EAC7B,QAAA,EAAA;AAAA,8BAAAC,cAAA,CAAC,MAAA,EAAA,EAAM,cAAI,KAAA,EAAM,CAAA;AAAA,8BACjBA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,cAAA,EAAgB,QAAA,EAAA,GAAA,CAAI,EAAE,MAAA,CAAO,EAAE,CAAA,IAAK,CAAC,CAAA,EAAE;AAAA,aAAA,EAAA,EAF/C,GAAA,CAAI,KAGd,CACD,CAAA,EACH,CAAA,EACF;AAAA,WAAA,EACF,CAAA;AAAA,0BACAA,cAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,SAAA,EAAWF,WAAA;AAAA,gBACT,2CAAA;AAAA,gBACA,eAAA,IAAmB;AAAA,eACrB;AAAA,cAEA,QAAA,kBAAAE,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,iFAAA,EACb,YAAE,IAAA,EACL;AAAA;AAAA,WACF;AAAA,UACC,eAAA,oBACCA,cAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,aAAA,EAAW,IAAA;AAAA,cACX,SAAA,EAAU,sGAAA;AAAA,cAEV,QAAA,kBAAAA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,0FAAA,EACb,YAAE,IAAA,EACL;AAAA;AAAA;AACF,SAAA,EAEJ,CAAA;AAAA,uCACC,MAAA,EAAA,EAAK,SAAA,EAAU,qDAAA,EAAuD,QAAA,EAAA,GAAA,CAAI,KAAK,CAAA,EAAE;AAAA,OAAA,EACpF,CAAA;AAGF,MAAA,IAAI,EAAE,IAAA,EAAM;AACV,QAAA,uBACEA,cAAA;AAAA,UAAC,aAAA;AAAA,UAAA;AAAA,YAEC,MAAM,CAAA,CAAE,IAAA;AAAA,YACR,SAAA,EAAU,gIAAA;AAAA,YAET,QAAA,EAAA;AAAA,WAAA;AAAA,UAJI,CAAA,CAAE;AAAA,SAKT;AAAA,MAEJ;AACA,MAAA,sCACG,KAAA,EAAA,EAAiB,SAAA,EAAU,8BAAA,EACzB,QAAA,EAAA,OAAA,EAAA,EADO,EAAE,IAEZ,CAAA;AAAA,IAEJ,CAAC,CAAA,EACH;AAAA,GAAA,EACF,CAAA;AAEJ;AAEA,SAAS,OAAA,CAAQ,EAAE,QAAA,EAAS,EAA4B;AACtD,EAAA,6DAAU,QAAA,EAAS,CAAA;AACrB;;;;"}
@@ -5,7 +5,7 @@ import remarkGfm from 'remark-gfm';
5
5
  import remend from 'remend';
6
6
  import { ARRIVING_CLASS, ARRIVING_MS } from './tokens.es.js';
7
7
  import { a as useWatched } from './use-watched-CG9KoWl3.js';
8
- import { C as CodeBlock } from './code-block-CBJhDEeB.js';
8
+ import { C as CodeBlock } from './code-block-DeksY23p.js';
9
9
  import { c as cn } from './cn-B4oPhFx9.js';
10
10
 
11
11
  import './markdown-renderer.css';const UNBROKEN = /* @__PURE__ */ new Set(["pre", "code"]);
@@ -238,4 +238,4 @@ const WINDOW_COMPONENTS = {
238
238
  };
239
239
 
240
240
  export { MarkdownRenderer as M };
241
- //# sourceMappingURL=markdown-renderer-DiJBbnYG.js.map
241
+ //# sourceMappingURL=markdown-renderer-CaFpZTFS.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"markdown-renderer-DiJBbnYG.js","sources":["../src/ds/components/MarkdownRenderer/arriving.ts","../src/ds/components/MarkdownRenderer/blocks.ts","../src/ds/components/MarkdownRenderer/use-settled.ts","../src/ds/components/MarkdownRenderer/markdown-renderer.tsx"],"sourcesContent":["import type { ExtraProps } from 'react-markdown';\n\nimport { ARRIVING_CLASS } from '@/ds/tokens';\n\nimport '@/ds/components/Arrival/arrival.css';\n\ntype MarkdownElement = NonNullable<ExtraProps['node']>;\ntype MarkdownChild = MarkdownElement['children'][number];\n\n/** Fades whole: a fence renders through `CodeBlock`, and inline code carries a background. */\nconst UNBROKEN = new Set(['pre', 'code']);\n\nconst wrap = (value: string, arriving: boolean): MarkdownChild => ({\n type: 'element',\n tagName: 'span',\n properties: arriving ? { className: [ARRIVING_CLASS] } : {},\n children: [{ type: 'text', value }],\n});\n\nfunction markArriving(node: MarkdownElement): void {\n const classes = node.properties.className;\n\n node.properties.className = Array.isArray(classes) ? [...classes, ARRIVING_CLASS] : [ARRIVING_CLASS];\n}\n\nfunction rebuild(nodes: MarkdownChild[], arriving: () => boolean): MarkdownChild[] {\n const out: MarkdownChild[] = [];\n\n for (const node of nodes) {\n if (node.type === 'text') {\n for (const piece of node.value.match(/\\S+|\\s+/g) ?? []) {\n if (/\\S/.test(piece)) out.push(wrap(piece, arriving()));\n else out.push({ type: 'text', value: piece });\n }\n\n continue;\n }\n\n out.push(node);\n\n if (node.type !== 'element') continue;\n\n if (!UNBROKEN.has(node.tagName)) node.children = rebuild(node.children, arriving);\n else if (arriving()) markArriving(node);\n }\n\n return out;\n}\n\n/**\n * Gives every word of a block an element of its own, and the entrance class to\n * those past the first `settled` — the words that were not on screen yet when\n * the reader joined this reply.\n *\n * Every word, not just the arriving ones: react-markdown parses afresh on each\n * landing and React matches the children it already mounted by position, so a\n * wrapper that came and went would land where a bare text node used to be and\n * remount half the paragraph, replaying entrances that had long finished.\n * Wrapping the lot keeps every position fixed — a new word only ever appends —\n * and a word never gains the class it did not mount with, so nothing replays.\n */\nexport function rehypeArriving(settled: number) {\n return function attach() {\n return (tree: { children: MarkdownChild[] }) => {\n let word = 0;\n\n tree.children = rebuild(tree.children, () => word++ >= settled);\n };\n };\n}\n","const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/;\nconst FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/;\nconst LIST_ITEM = /^ {0,3}(?:[-*+]|\\d{1,9}[.)])(?:[ \\t]|$)/;\nconst INDENTED = /^(?:\\t| {2,})/;\nconst DEFINITION = /^ {0,3}\\[[^\\]]+\\]:/;\n\nfunction fenceOpenedBy(line: string): string | undefined {\n return FENCE_OPEN.exec(line)?.[1];\n}\n\n// Both are runs of one character, so `startsWith` is \"same marker, at least as long\".\nfunction closesFence(line: string, marker: string): boolean {\n return FENCE_CLOSE.exec(line)?.[1]?.startsWith(marker) ?? false;\n}\n\nfunction startsBlock(line: string, inList: boolean): boolean {\n if (INDENTED.test(line)) return false;\n\n return !(inList && LIST_ITEM.test(line));\n}\n\n/**\n * Cuts markdown into top-level blocks so a growing reply re-parses its last\n * block instead of all of them. Rejoining the result returns the input, and\n * every block renders as it would have inside the whole.\n *\n * Each rule only ever merges: over-merging costs a re-parse, while cutting a\n * construct in two changes what it renders as. Link reference and footnote\n * definitions resolve across blank lines, which no line-level rule can see, so\n * reaching one outside a fence abandons the split for the whole text.\n */\nexport function splitBlocks(markdown: string): string[] {\n const lines = markdown.split('\\n');\n const starts = [0];\n let fence: string | undefined;\n let started = false;\n let broken = false;\n let inList = false;\n\n for (const [index, line] of lines.entries()) {\n if (fence) {\n if (closesFence(line, fence)) fence = undefined;\n continue;\n }\n\n if (line.trim() === '') {\n broken = started;\n continue;\n }\n\n if (DEFINITION.test(line)) return [markdown];\n\n if (broken && startsBlock(line, inList)) {\n starts.push(index);\n started = false;\n }\n\n if (!started) inList = LIST_ITEM.test(line);\n\n started = true;\n broken = false;\n fence = fenceOpenedBy(line);\n }\n\n return starts.map((start, position) => {\n const end = starts[position + 1];\n const block = lines.slice(start, end ?? lines.length).join('\\n');\n\n return end === undefined ? block : `${block}\\n`;\n });\n}\n","import { useEffect, useRef, useState } from 'react';\n\nimport { useWatched } from '@/ds/components/Arrival';\nimport { ARRIVING_MS } from '@/ds/tokens';\n\ninterface Landing {\n words: number;\n at: number;\n}\n\n/**\n * How many of a reply's words have been on screen long enough that their entrance is over.\n *\n * The entrance plays on whatever mounts carrying the class, so it replays every time a\n * word's element is rebuilt — and markdown rebuilds a growing tail constantly: a `-`\n * turns a paragraph into a list item, a closing `**` splits it around a `<strong>`, the\n * stream ends and the mend comes off. Each of those swaps the element a settled word\n * lives in, which is what makes a word already read fade in again.\n *\n * Ageing them out is the whole answer: a word carries the class only while it is new,\n * so however often the tail is rebuilt, only what is genuinely arriving animates.\n *\n * What counts as new at mount is not this hook's call. A reply the reader was handed\n * is already there, and a block that mounts complete — a card's output, an expanded\n * body — enters with its container. Only a passage born streaming under the reader's\n * eyes is new from its first word: the reveal often lands several words in its first\n * commit, and they enter like every word after them.\n */\nexport function useSettledWords(words: number, streaming: boolean): number {\n const watched = useWatched();\n const [settled, setSettled] = useState(() => (watched && streaming ? 0 : words));\n const landings = useRef<Landing[]>([]);\n const recorded = useRef(settled);\n\n useEffect(() => {\n if (recorded.current !== words) {\n landings.current.push({ words, at: performance.now() });\n recorded.current = words;\n }\n\n const waiting = landings.current[0];\n if (!waiting) return;\n\n const timer = setTimeout(\n () => {\n landings.current.shift();\n setSettled(waiting.words);\n },\n Math.max(0, waiting.at + ARRIVING_MS - performance.now()),\n );\n\n return () => clearTimeout(timer);\n }, [settled, words]);\n\n // A shorter reply replacing the one on screen settles at once: nothing of it is new.\n return Math.min(settled, words);\n}\n","import { memo, useMemo } from 'react';\nimport type { MouseEvent, MouseEventHandler, ReactNode } from 'react';\nimport Markdown from 'react-markdown';\nimport type { Components, ExtraProps, Options } from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport remend from 'remend';\n\nimport { rehypeArriving } from './arriving';\nimport { splitBlocks } from './blocks';\nimport { useSettledWords } from './use-settled';\nimport { CodeBlock } from '@/ds/components/CodeBlock';\nimport { cn } from '@/lib/utils';\n\nimport './markdown-renderer.css';\n\nexport type MarkdownExternalLinkTarget = 'tab' | 'window';\n\nexport interface MarkdownRendererProps {\n children: string;\n className?: string;\n externalLinkTarget?: MarkdownExternalLinkTarget;\n /** The text is a prefix of one still being written: close the markers the stream has not reached. */\n streaming?: boolean;\n}\n\n/**\n * Renders a markdown string. Agent output can carry attacker-influenced text\n * (file contents, tool output, web pages): react-markdown escapes raw HTML and\n * drops dangerous link schemes, so nothing here reaches the DOM as markup.\n *\n * react-markdown re-parses on every render, and a streaming reply re-renders\n * its whole transcript on every delta. Memoizing spares the settled messages;\n * rendering block by block — streaming or not — spares every block of the live\n * one but the last, and lets a reply settle without remounting what is already\n * on screen.\n *\n * The text is drawn as given: pacing a stream is `useRevealedText`, and belongs to\n * whoever owns the whole of what is being laid down — a reply is prose, tool rows\n * and cards, and they have to arrive in the order they were written. Only what\n * lands after the reader joined plays an entrance, and only while it is still new:\n * a reply opened part-written is already there, and fading in what someone is\n * halfway through reading would be both a lie and a screenful of animations at\n * once.\n */\nexport const MarkdownRenderer = memo(function MarkdownRenderer({\n children,\n className,\n externalLinkTarget = 'tab',\n streaming = false,\n}: MarkdownRendererProps) {\n const shown = decodeEscapedNewlines(children);\n const blocks = useMemo(() => splitBlocks(shown), [shown]);\n const last = blocks.length - 1;\n const components = externalLinkTarget === 'window' ? WINDOW_COMPONENTS : COMPONENTS;\n\n const spans = wordSpans(blocks);\n const settled = useSettledWords(spans.at(-1)?.end ?? 0, streaming);\n\n // A block whose words have all finished their entrance has nothing left to play,\n // which is what `undefined` says: leave it as plain text, no spans at all.\n const settledWords = (span: WordSpan | undefined): number | undefined =>\n !span || settled >= span.end ? undefined : Math.max(0, settled - span.start);\n\n const tail = blocks[last] ?? '';\n const mended = useMemo(() => (streaming ? remend(tail, REMEND_OPTIONS) : tail), [streaming, tail]);\n\n return (\n <div className={cn('mastra-markdown', className)}>\n {blocks.map((block, index) => (\n <MarkdownBlock\n key={index}\n content={index === last ? mended : block}\n settledWords={settledWords(spans[index])}\n components={components}\n />\n ))}\n </div>\n );\n});\n\n/** Keyed by position at the call site: a content key remounts on every character. */\nconst MarkdownBlock = memo(function MarkdownBlock({\n components,\n content,\n settledWords,\n}: {\n components: Components;\n content: string;\n settledWords?: number;\n}) {\n const rehypePlugins = useMemo(\n () => (settledWords === undefined ? SETTLED : [rehypeArriving(settledWords)]),\n [settledWords],\n );\n\n return (\n <Markdown remarkPlugins={REMARK_PLUGINS} rehypePlugins={rehypePlugins} components={components}>\n {content}\n </Markdown>\n );\n});\n\nconst SETTLED: Options['rehypePlugins'] = [];\n\ninterface WordSpan {\n start: number;\n end: number;\n}\n\n/**\n * Where each block's words fall in the reply's own count, so one settled count covers\n * them all. Counting the source counts its markers too, so the boundary only ever errs\n * towards leaving a word unanimated.\n */\nfunction wordSpans(blocks: string[]): WordSpan[] {\n let read = 0;\n\n return blocks.map(block => {\n const span = { start: read, end: read + countWords(block) };\n read = span.end;\n\n return span;\n });\n}\n\nfunction countWords(text: string): number {\n return text.match(/\\S+/g)?.length ?? 0;\n}\n\n// Agent networks emit their text with literal `\\n`. Only unescape when the text\n// has no real newline, otherwise a `\"a\\nb\"` inside a code fence gets shredded.\nfunction decodeEscapedNewlines(text: string): string {\n return text.includes('\\n') ? text : text.replace(/\\\\n/g, '\\n');\n}\n\ntype MarkdownNode = NonNullable<ExtraProps['node']>;\n\nfunction languageOf(node: MarkdownNode): string | undefined {\n const classNames = node.properties.className;\n if (!Array.isArray(classNames)) return undefined;\n\n const language = classNames.find(entry => typeof entry === 'string' && entry.startsWith('language-'));\n return typeof language === 'string' ? language.slice('language-'.length) : undefined;\n}\n\nfunction fencedCode(node: MarkdownNode | undefined): { code: string; language?: string } | undefined {\n const child = node?.children.find(entry => entry.type === 'element' && entry.tagName === 'code');\n if (child?.type !== 'element') return undefined;\n\n const code = child.children.map(entry => (entry.type === 'text' ? entry.value : '')).join('');\n return { code: code.replace(/\\n$/, ''), language: languageOf(child) };\n}\n\nfunction MarkdownCodeBlock({\n node,\n children,\n className,\n}: {\n node?: MarkdownNode;\n children?: ReactNode;\n className?: string;\n}) {\n const fenced = fencedCode(node);\n if (!fenced) return <pre className={className}>{children}</pre>;\n\n return (\n <CodeBlock\n code={fenced.code}\n lang={fenced.language}\n overflow=\"scroll\"\n className={cn('my-3 bg-surface1', className)}\n copyMessage=\"Copied code to clipboard\"\n />\n );\n}\n\nconst POPUP_WINDOW_FEATURES = 'popup=yes,width=720,height=800,resizable=yes,scrollbars=yes';\n\nfunction isPlainLeftClick(event: MouseEvent): boolean {\n return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;\n}\n\nfunction markdownLink(externalLinkTarget: MarkdownExternalLinkTarget): NonNullable<Components['a']> {\n return function MarkdownLink({ href, title, children }) {\n const isExternal = /^https?:\\/\\//i.test(href ?? '');\n\n const openInPopup: MouseEventHandler<HTMLAnchorElement> = event => {\n if (!isExternal || externalLinkTarget !== 'window' || !isPlainLeftClick(event)) return;\n\n const popup = window.open(href, '_blank', POPUP_WINDOW_FEATURES);\n if (!popup) return;\n\n popup.opener = null;\n event.preventDefault();\n };\n\n return (\n <a\n href={href}\n title={title}\n onClick={openInPopup}\n target={isExternal ? '_blank' : undefined}\n rel={isExternal ? 'noopener noreferrer' : undefined}\n >\n {children}\n </a>\n );\n };\n}\n\nconst REMARK_PLUGINS = [remarkGfm];\n\n// Links stay text until their URL lands: remend's placeholder href would render\n// a live anchor to nowhere. No math is rendered here, so pairing `$$` would only\n// turn one literal into another.\nconst REMEND_OPTIONS = { katex: false, linkMode: 'text-only' } as const;\n\n// Elements are listed one by one: react-markdown also passes its `node`, which\n// React would forward to the DOM as a stray attribute. Everything else is\n// styled from markdown-renderer.css.\nconst COMPONENTS: Components = {\n pre: MarkdownCodeBlock,\n a: markdownLink('tab'),\n};\n\nconst WINDOW_COMPONENTS: Components = {\n ...COMPONENTS,\n a: markdownLink('window'),\n};\n"],"names":["MarkdownRenderer","MarkdownBlock"],"mappings":"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"}
1
+ {"version":3,"file":"markdown-renderer-CaFpZTFS.js","sources":["../src/ds/components/MarkdownRenderer/arriving.ts","../src/ds/components/MarkdownRenderer/blocks.ts","../src/ds/components/MarkdownRenderer/use-settled.ts","../src/ds/components/MarkdownRenderer/markdown-renderer.tsx"],"sourcesContent":["import type { ExtraProps } from 'react-markdown';\n\nimport { ARRIVING_CLASS } from '@/ds/tokens';\n\nimport '@/ds/components/Arrival/arrival.css';\n\ntype MarkdownElement = NonNullable<ExtraProps['node']>;\ntype MarkdownChild = MarkdownElement['children'][number];\n\n/** Fades whole: a fence renders through `CodeBlock`, and inline code carries a background. */\nconst UNBROKEN = new Set(['pre', 'code']);\n\nconst wrap = (value: string, arriving: boolean): MarkdownChild => ({\n type: 'element',\n tagName: 'span',\n properties: arriving ? { className: [ARRIVING_CLASS] } : {},\n children: [{ type: 'text', value }],\n});\n\nfunction markArriving(node: MarkdownElement): void {\n const classes = node.properties.className;\n\n node.properties.className = Array.isArray(classes) ? [...classes, ARRIVING_CLASS] : [ARRIVING_CLASS];\n}\n\nfunction rebuild(nodes: MarkdownChild[], arriving: () => boolean): MarkdownChild[] {\n const out: MarkdownChild[] = [];\n\n for (const node of nodes) {\n if (node.type === 'text') {\n for (const piece of node.value.match(/\\S+|\\s+/g) ?? []) {\n if (/\\S/.test(piece)) out.push(wrap(piece, arriving()));\n else out.push({ type: 'text', value: piece });\n }\n\n continue;\n }\n\n out.push(node);\n\n if (node.type !== 'element') continue;\n\n if (!UNBROKEN.has(node.tagName)) node.children = rebuild(node.children, arriving);\n else if (arriving()) markArriving(node);\n }\n\n return out;\n}\n\n/**\n * Gives every word of a block an element of its own, and the entrance class to\n * those past the first `settled` — the words that were not on screen yet when\n * the reader joined this reply.\n *\n * Every word, not just the arriving ones: react-markdown parses afresh on each\n * landing and React matches the children it already mounted by position, so a\n * wrapper that came and went would land where a bare text node used to be and\n * remount half the paragraph, replaying entrances that had long finished.\n * Wrapping the lot keeps every position fixed — a new word only ever appends —\n * and a word never gains the class it did not mount with, so nothing replays.\n */\nexport function rehypeArriving(settled: number) {\n return function attach() {\n return (tree: { children: MarkdownChild[] }) => {\n let word = 0;\n\n tree.children = rebuild(tree.children, () => word++ >= settled);\n };\n };\n}\n","const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/;\nconst FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/;\nconst LIST_ITEM = /^ {0,3}(?:[-*+]|\\d{1,9}[.)])(?:[ \\t]|$)/;\nconst INDENTED = /^(?:\\t| {2,})/;\nconst DEFINITION = /^ {0,3}\\[[^\\]]+\\]:/;\n\nfunction fenceOpenedBy(line: string): string | undefined {\n return FENCE_OPEN.exec(line)?.[1];\n}\n\n// Both are runs of one character, so `startsWith` is \"same marker, at least as long\".\nfunction closesFence(line: string, marker: string): boolean {\n return FENCE_CLOSE.exec(line)?.[1]?.startsWith(marker) ?? false;\n}\n\nfunction startsBlock(line: string, inList: boolean): boolean {\n if (INDENTED.test(line)) return false;\n\n return !(inList && LIST_ITEM.test(line));\n}\n\n/**\n * Cuts markdown into top-level blocks so a growing reply re-parses its last\n * block instead of all of them. Rejoining the result returns the input, and\n * every block renders as it would have inside the whole.\n *\n * Each rule only ever merges: over-merging costs a re-parse, while cutting a\n * construct in two changes what it renders as. Link reference and footnote\n * definitions resolve across blank lines, which no line-level rule can see, so\n * reaching one outside a fence abandons the split for the whole text.\n */\nexport function splitBlocks(markdown: string): string[] {\n const lines = markdown.split('\\n');\n const starts = [0];\n let fence: string | undefined;\n let started = false;\n let broken = false;\n let inList = false;\n\n for (const [index, line] of lines.entries()) {\n if (fence) {\n if (closesFence(line, fence)) fence = undefined;\n continue;\n }\n\n if (line.trim() === '') {\n broken = started;\n continue;\n }\n\n if (DEFINITION.test(line)) return [markdown];\n\n if (broken && startsBlock(line, inList)) {\n starts.push(index);\n started = false;\n }\n\n if (!started) inList = LIST_ITEM.test(line);\n\n started = true;\n broken = false;\n fence = fenceOpenedBy(line);\n }\n\n return starts.map((start, position) => {\n const end = starts[position + 1];\n const block = lines.slice(start, end ?? lines.length).join('\\n');\n\n return end === undefined ? block : `${block}\\n`;\n });\n}\n","import { useEffect, useRef, useState } from 'react';\n\nimport { useWatched } from '@/ds/components/Arrival';\nimport { ARRIVING_MS } from '@/ds/tokens';\n\ninterface Landing {\n words: number;\n at: number;\n}\n\n/**\n * How many of a reply's words have been on screen long enough that their entrance is over.\n *\n * The entrance plays on whatever mounts carrying the class, so it replays every time a\n * word's element is rebuilt — and markdown rebuilds a growing tail constantly: a `-`\n * turns a paragraph into a list item, a closing `**` splits it around a `<strong>`, the\n * stream ends and the mend comes off. Each of those swaps the element a settled word\n * lives in, which is what makes a word already read fade in again.\n *\n * Ageing them out is the whole answer: a word carries the class only while it is new,\n * so however often the tail is rebuilt, only what is genuinely arriving animates.\n *\n * What counts as new at mount is not this hook's call. A reply the reader was handed\n * is already there, and a block that mounts complete — a card's output, an expanded\n * body — enters with its container. Only a passage born streaming under the reader's\n * eyes is new from its first word: the reveal often lands several words in its first\n * commit, and they enter like every word after them.\n */\nexport function useSettledWords(words: number, streaming: boolean): number {\n const watched = useWatched();\n const [settled, setSettled] = useState(() => (watched && streaming ? 0 : words));\n const landings = useRef<Landing[]>([]);\n const recorded = useRef(settled);\n\n useEffect(() => {\n if (recorded.current !== words) {\n landings.current.push({ words, at: performance.now() });\n recorded.current = words;\n }\n\n const waiting = landings.current[0];\n if (!waiting) return;\n\n const timer = setTimeout(\n () => {\n landings.current.shift();\n setSettled(waiting.words);\n },\n Math.max(0, waiting.at + ARRIVING_MS - performance.now()),\n );\n\n return () => clearTimeout(timer);\n }, [settled, words]);\n\n // A shorter reply replacing the one on screen settles at once: nothing of it is new.\n return Math.min(settled, words);\n}\n","import { memo, useMemo } from 'react';\nimport type { MouseEvent, MouseEventHandler, ReactNode } from 'react';\nimport Markdown from 'react-markdown';\nimport type { Components, ExtraProps, Options } from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport remend from 'remend';\n\nimport { rehypeArriving } from './arriving';\nimport { splitBlocks } from './blocks';\nimport { useSettledWords } from './use-settled';\nimport { CodeBlock } from '@/ds/components/CodeBlock';\nimport { cn } from '@/lib/utils';\n\nimport './markdown-renderer.css';\n\nexport type MarkdownExternalLinkTarget = 'tab' | 'window';\n\nexport interface MarkdownRendererProps {\n children: string;\n className?: string;\n externalLinkTarget?: MarkdownExternalLinkTarget;\n /** The text is a prefix of one still being written: close the markers the stream has not reached. */\n streaming?: boolean;\n}\n\n/**\n * Renders a markdown string. Agent output can carry attacker-influenced text\n * (file contents, tool output, web pages): react-markdown escapes raw HTML and\n * drops dangerous link schemes, so nothing here reaches the DOM as markup.\n *\n * react-markdown re-parses on every render, and a streaming reply re-renders\n * its whole transcript on every delta. Memoizing spares the settled messages;\n * rendering block by block — streaming or not — spares every block of the live\n * one but the last, and lets a reply settle without remounting what is already\n * on screen.\n *\n * The text is drawn as given: pacing a stream is `useRevealedText`, and belongs to\n * whoever owns the whole of what is being laid down — a reply is prose, tool rows\n * and cards, and they have to arrive in the order they were written. Only what\n * lands after the reader joined plays an entrance, and only while it is still new:\n * a reply opened part-written is already there, and fading in what someone is\n * halfway through reading would be both a lie and a screenful of animations at\n * once.\n */\nexport const MarkdownRenderer = memo(function MarkdownRenderer({\n children,\n className,\n externalLinkTarget = 'tab',\n streaming = false,\n}: MarkdownRendererProps) {\n const shown = decodeEscapedNewlines(children);\n const blocks = useMemo(() => splitBlocks(shown), [shown]);\n const last = blocks.length - 1;\n const components = externalLinkTarget === 'window' ? WINDOW_COMPONENTS : COMPONENTS;\n\n const spans = wordSpans(blocks);\n const settled = useSettledWords(spans.at(-1)?.end ?? 0, streaming);\n\n // A block whose words have all finished their entrance has nothing left to play,\n // which is what `undefined` says: leave it as plain text, no spans at all.\n const settledWords = (span: WordSpan | undefined): number | undefined =>\n !span || settled >= span.end ? undefined : Math.max(0, settled - span.start);\n\n const tail = blocks[last] ?? '';\n const mended = useMemo(() => (streaming ? remend(tail, REMEND_OPTIONS) : tail), [streaming, tail]);\n\n return (\n <div className={cn('mastra-markdown', className)}>\n {blocks.map((block, index) => (\n <MarkdownBlock\n key={index}\n content={index === last ? mended : block}\n settledWords={settledWords(spans[index])}\n components={components}\n />\n ))}\n </div>\n );\n});\n\n/** Keyed by position at the call site: a content key remounts on every character. */\nconst MarkdownBlock = memo(function MarkdownBlock({\n components,\n content,\n settledWords,\n}: {\n components: Components;\n content: string;\n settledWords?: number;\n}) {\n const rehypePlugins = useMemo(\n () => (settledWords === undefined ? SETTLED : [rehypeArriving(settledWords)]),\n [settledWords],\n );\n\n return (\n <Markdown remarkPlugins={REMARK_PLUGINS} rehypePlugins={rehypePlugins} components={components}>\n {content}\n </Markdown>\n );\n});\n\nconst SETTLED: Options['rehypePlugins'] = [];\n\ninterface WordSpan {\n start: number;\n end: number;\n}\n\n/**\n * Where each block's words fall in the reply's own count, so one settled count covers\n * them all. Counting the source counts its markers too, so the boundary only ever errs\n * towards leaving a word unanimated.\n */\nfunction wordSpans(blocks: string[]): WordSpan[] {\n let read = 0;\n\n return blocks.map(block => {\n const span = { start: read, end: read + countWords(block) };\n read = span.end;\n\n return span;\n });\n}\n\nfunction countWords(text: string): number {\n return text.match(/\\S+/g)?.length ?? 0;\n}\n\n// Agent networks emit their text with literal `\\n`. Only unescape when the text\n// has no real newline, otherwise a `\"a\\nb\"` inside a code fence gets shredded.\nfunction decodeEscapedNewlines(text: string): string {\n return text.includes('\\n') ? text : text.replace(/\\\\n/g, '\\n');\n}\n\ntype MarkdownNode = NonNullable<ExtraProps['node']>;\n\nfunction languageOf(node: MarkdownNode): string | undefined {\n const classNames = node.properties.className;\n if (!Array.isArray(classNames)) return undefined;\n\n const language = classNames.find(entry => typeof entry === 'string' && entry.startsWith('language-'));\n return typeof language === 'string' ? language.slice('language-'.length) : undefined;\n}\n\nfunction fencedCode(node: MarkdownNode | undefined): { code: string; language?: string } | undefined {\n const child = node?.children.find(entry => entry.type === 'element' && entry.tagName === 'code');\n if (child?.type !== 'element') return undefined;\n\n const code = child.children.map(entry => (entry.type === 'text' ? entry.value : '')).join('');\n return { code: code.replace(/\\n$/, ''), language: languageOf(child) };\n}\n\nfunction MarkdownCodeBlock({\n node,\n children,\n className,\n}: {\n node?: MarkdownNode;\n children?: ReactNode;\n className?: string;\n}) {\n const fenced = fencedCode(node);\n if (!fenced) return <pre className={className}>{children}</pre>;\n\n return (\n <CodeBlock\n code={fenced.code}\n lang={fenced.language}\n overflow=\"scroll\"\n className={cn('my-3 bg-surface1', className)}\n copyMessage=\"Copied code to clipboard\"\n />\n );\n}\n\nconst POPUP_WINDOW_FEATURES = 'popup=yes,width=720,height=800,resizable=yes,scrollbars=yes';\n\nfunction isPlainLeftClick(event: MouseEvent): boolean {\n return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;\n}\n\nfunction markdownLink(externalLinkTarget: MarkdownExternalLinkTarget): NonNullable<Components['a']> {\n return function MarkdownLink({ href, title, children }) {\n const isExternal = /^https?:\\/\\//i.test(href ?? '');\n\n const openInPopup: MouseEventHandler<HTMLAnchorElement> = event => {\n if (!isExternal || externalLinkTarget !== 'window' || !isPlainLeftClick(event)) return;\n\n const popup = window.open(href, '_blank', POPUP_WINDOW_FEATURES);\n if (!popup) return;\n\n popup.opener = null;\n event.preventDefault();\n };\n\n return (\n <a\n href={href}\n title={title}\n onClick={openInPopup}\n target={isExternal ? '_blank' : undefined}\n rel={isExternal ? 'noopener noreferrer' : undefined}\n >\n {children}\n </a>\n );\n };\n}\n\nconst REMARK_PLUGINS = [remarkGfm];\n\n// Links stay text until their URL lands: remend's placeholder href would render\n// a live anchor to nowhere. No math is rendered here, so pairing `$$` would only\n// turn one literal into another.\nconst REMEND_OPTIONS = { katex: false, linkMode: 'text-only' } as const;\n\n// Elements are listed one by one: react-markdown also passes its `node`, which\n// React would forward to the DOM as a stray attribute. Everything else is\n// styled from markdown-renderer.css.\nconst COMPONENTS: Components = {\n pre: MarkdownCodeBlock,\n a: markdownLink('tab'),\n};\n\nconst WINDOW_COMPONENTS: Components = {\n ...COMPONENTS,\n a: markdownLink('window'),\n};\n"],"names":["MarkdownRenderer","MarkdownBlock"],"mappings":";;;;;;;;;;AAUA,MAAM,2BAAW,IAAI,GAAA,CAAI,CAAC,KAAA,EAAO,MAAM,CAAC,CAAA;AAExC,MAAM,IAAA,GAAO,CAAC,KAAA,EAAe,QAAA,MAAsC;AAAA,EACjE,IAAA,EAAM,SAAA;AAAA,EACN,OAAA,EAAS,MAAA;AAAA,EACT,UAAA,EAAY,WAAW,EAAE,SAAA,EAAW,CAAC,cAAc,CAAA,KAAM,EAAC;AAAA,EAC1D,UAAU,CAAC,EAAE,IAAA,EAAM,MAAA,EAAQ,OAAO;AACpC,CAAA,CAAA;AAEA,SAAS,aAAa,IAAA,EAA6B;AACjD,EAAA,MAAM,OAAA,GAAU,KAAK,UAAA,CAAW,SAAA;AAEhC,EAAA,IAAA,CAAK,UAAA,CAAW,SAAA,GAAY,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,GAAI,CAAC,GAAG,OAAA,EAAS,cAAc,CAAA,GAAI,CAAC,cAAc,CAAA;AACrG;AAEA,SAAS,OAAA,CAAQ,OAAwB,QAAA,EAA0C;AACjF,EAAA,MAAM,MAAuB,EAAC;AAE9B,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,IAAA,CAAK,SAAS,MAAA,EAAQ;AACxB,MAAA,KAAA,MAAW,SAAS,IAAA,CAAK,KAAA,CAAM,MAAM,UAAU,CAAA,IAAK,EAAC,EAAG;AACtD,QAAA,IAAI,IAAA,CAAK,IAAA,CAAK,KAAK,CAAA,EAAG,GAAA,CAAI,KAAK,IAAA,CAAK,KAAA,EAAO,QAAA,EAAU,CAAC,CAAA;AAAA,iBAC7C,IAAA,CAAK,EAAE,MAAM,MAAA,EAAQ,KAAA,EAAO,OAAO,CAAA;AAAA,MAC9C;AAEA,MAAA;AAAA,IACF;AAEA,IAAA,GAAA,CAAI,KAAK,IAAI,CAAA;AAEb,IAAA,IAAI,IAAA,CAAK,SAAS,SAAA,EAAW;AAE7B,IAAA,IAAI,CAAC,QAAA,CAAS,GAAA,CAAI,IAAA,CAAK,OAAO,CAAA,EAAG,IAAA,CAAK,QAAA,GAAW,OAAA,CAAQ,IAAA,CAAK,QAAA,EAAU,QAAQ,CAAA;AAAA,SAAA,IACvE,QAAA,EAAS,EAAG,YAAA,CAAa,IAAI,CAAA;AAAA,EACxC;AAEA,EAAA,OAAO,GAAA;AACT;AAcO,SAAS,eAAe,OAAA,EAAiB;AAC9C,EAAA,OAAO,SAAS,MAAA,GAAS;AACvB,IAAA,OAAO,CAAC,IAAA,KAAwC;AAC9C,MAAA,IAAI,IAAA,GAAO,CAAA;AAEX,MAAA,IAAA,CAAK,WAAW,OAAA,CAAQ,IAAA,CAAK,QAAA,EAAU,MAAM,UAAU,OAAO,CAAA;AAAA,IAChE,CAAA;AAAA,EACF,CAAA;AACF;;ACrEA,MAAM,UAAA,GAAa,sBAAA;AACnB,MAAM,WAAA,GAAc,6BAAA;AACpB,MAAM,SAAA,GAAY,yCAAA;AAClB,MAAM,QAAA,GAAW,eAAA;AACjB,MAAM,UAAA,GAAa,oBAAA;AAEnB,SAAS,cAAc,IAAA,EAAkC;AACvD,EAAA,OAAO,UAAA,CAAW,IAAA,CAAK,IAAI,CAAA,GAAI,CAAC,CAAA;AAClC;AAGA,SAAS,WAAA,CAAY,MAAc,MAAA,EAAyB;AAC1D,EAAA,OAAO,WAAA,CAAY,KAAK,IAAI,CAAA,GAAI,CAAC,CAAA,EAAG,UAAA,CAAW,MAAM,CAAA,IAAK,KAAA;AAC5D;AAEA,SAAS,WAAA,CAAY,MAAc,MAAA,EAA0B;AAC3D,EAAA,IAAI,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA,EAAG,OAAO,KAAA;AAEhC,EAAA,OAAO,EAAE,MAAA,IAAU,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA,CAAA;AACxC;AAYO,SAAS,YAAY,QAAA,EAA4B;AACtD,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA;AACjC,EAAA,MAAM,MAAA,GAAS,CAAC,CAAC,CAAA;AACjB,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,IAAI,MAAA,GAAS,KAAA;AACb,EAAA,IAAI,MAAA,GAAS,KAAA;AAEb,EAAA,KAAA,MAAW,CAAC,KAAA,EAAO,IAAI,CAAA,IAAK,KAAA,CAAM,SAAQ,EAAG;AAC3C,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,IAAI,WAAA,CAAY,IAAA,EAAM,KAAK,CAAA,EAAG,KAAA,GAAQ,MAAA;AACtC,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,IAAA,CAAK,IAAA,EAAK,KAAM,EAAA,EAAI;AACtB,MAAA,MAAA,GAAS,OAAA;AACT,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,WAAW,IAAA,CAAK,IAAI,CAAA,EAAG,OAAO,CAAC,QAAQ,CAAA;AAE3C,IAAA,IAAI,MAAA,IAAU,WAAA,CAAY,IAAA,EAAM,MAAM,CAAA,EAAG;AACvC,MAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AACjB,MAAA,OAAA,GAAU,KAAA;AAAA,IACZ;AAEA,IAAA,IAAI,CAAC,OAAA,EAAS,MAAA,GAAS,SAAA,CAAU,KAAK,IAAI,CAAA;AAE1C,IAAA,OAAA,GAAU,IAAA;AACV,IAAA,MAAA,GAAS,KAAA;AACT,IAAA,KAAA,GAAQ,cAAc,IAAI,CAAA;AAAA,EAC5B;AAEA,EAAA,OAAO,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,EAAO,QAAA,KAAa;AACrC,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,QAAA,GAAW,CAAC,CAAA;AAC/B,IAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,CAAM,KAAA,EAAO,OAAO,KAAA,CAAM,MAAM,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAE/D,IAAA,OAAO,GAAA,KAAQ,MAAA,GAAY,KAAA,GAAQ,CAAA,EAAG,KAAK;AAAA,CAAA;AAAA,EAC7C,CAAC,CAAA;AACH;;AC1CO,SAAS,eAAA,CAAgB,OAAe,SAAA,EAA4B;AACzE,EAAA,MAAM,UAAU,UAAA,EAAW;AAC3B,EAAA,MAAM,CAAC,SAAS,UAAU,CAAA,GAAI,SAAS,MAAO,OAAA,IAAW,SAAA,GAAY,CAAA,GAAI,KAAM,CAAA;AAC/E,EAAA,MAAM,QAAA,GAAW,MAAA,CAAkB,EAAE,CAAA;AACrC,EAAA,MAAM,QAAA,GAAW,OAAO,OAAO,CAAA;AAE/B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,QAAA,CAAS,YAAY,KAAA,EAAO;AAC9B,MAAA,QAAA,CAAS,OAAA,CAAQ,KAAK,EAAE,KAAA,EAAO,IAAI,WAAA,CAAY,GAAA,IAAO,CAAA;AACtD,MAAA,QAAA,CAAS,OAAA,GAAU,KAAA;AAAA,IACrB;AAEA,IAAA,MAAM,OAAA,GAAU,QAAA,CAAS,OAAA,CAAQ,CAAC,CAAA;AAClC,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,MAAM,KAAA,GAAQ,UAAA;AAAA,MACZ,MAAM;AACJ,QAAA,QAAA,CAAS,QAAQ,KAAA,EAAM;AACvB,QAAA,UAAA,CAAW,QAAQ,KAAK,CAAA;AAAA,MAC1B,CAAA;AAAA,MACA,IAAA,CAAK,IAAI,CAAA,EAAG,OAAA,CAAQ,KAAK,WAAA,GAAc,WAAA,CAAY,KAAK;AAAA,KAC1D;AAEA,IAAA,OAAO,MAAM,aAAa,KAAK,CAAA;AAAA,EACjC,CAAA,EAAG,CAAC,OAAA,EAAS,KAAK,CAAC,CAAA;AAGnB,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,KAAK,CAAA;AAChC;;ACZO,MAAM,gBAAA,GAAmB,IAAA,CAAK,SAASA,iBAAAA,CAAiB;AAAA,EAC7D,QAAA;AAAA,EACA,SAAA;AAAA,EACA,kBAAA,GAAqB,KAAA;AAAA,EACrB,SAAA,GAAY;AACd,CAAA,EAA0B;AACxB,EAAA,MAAM,KAAA,GAAQ,sBAAsB,QAAQ,CAAA;AAC5C,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAM,WAAA,CAAY,KAAK,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AACxD,EAAA,MAAM,IAAA,GAAO,OAAO,MAAA,GAAS,CAAA;AAC7B,EAAA,MAAM,UAAA,GAAa,kBAAA,KAAuB,QAAA,GAAW,iBAAA,GAAoB,UAAA;AAEzE,EAAA,MAAM,KAAA,GAAQ,UAAU,MAAM,CAAA;AAC9B,EAAA,MAAM,OAAA,GAAU,gBAAgB,KAAA,CAAM,EAAA,CAAG,EAAE,CAAA,EAAG,GAAA,IAAO,GAAG,SAAS,CAAA;AAIjE,EAAA,MAAM,YAAA,GAAe,CAAC,IAAA,KACpB,CAAC,QAAQ,OAAA,IAAW,IAAA,CAAK,GAAA,GAAM,MAAA,GAAY,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,OAAA,GAAU,KAAK,KAAK,CAAA;AAE7E,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,IAAI,CAAA,IAAK,EAAA;AAC7B,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAO,SAAA,GAAY,MAAA,CAAO,IAAA,EAAM,cAAc,CAAA,GAAI,IAAA,EAAO,CAAC,SAAA,EAAW,IAAI,CAAC,CAAA;AAEjG,EAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,EAAA,CAAG,iBAAA,EAAmB,SAAS,CAAA,EAC5C,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,EAAO,KAAA,qBAClB,GAAA;AAAA,IAAC,aAAA;AAAA,IAAA;AAAA,MAEC,OAAA,EAAS,KAAA,KAAU,IAAA,GAAO,MAAA,GAAS,KAAA;AAAA,MACnC,YAAA,EAAc,YAAA,CAAa,KAAA,CAAM,KAAK,CAAC,CAAA;AAAA,MACvC;AAAA,KAAA;AAAA,IAHK;AAAA,GAKR,CAAA,EACH,CAAA;AAEJ,CAAC;AAGD,MAAM,aAAA,GAAgB,IAAA,CAAK,SAASC,cAAAA,CAAc;AAAA,EAChD,UAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAIG;AACD,EAAA,MAAM,aAAA,GAAgB,OAAA;AAAA,IACpB,MAAO,YAAA,KAAiB,MAAA,GAAY,UAAU,CAAC,cAAA,CAAe,YAAY,CAAC,CAAA;AAAA,IAC3E,CAAC,YAAY;AAAA,GACf;AAEA,EAAA,2BACG,QAAA,EAAA,EAAS,aAAA,EAAe,cAAA,EAAgB,aAAA,EAA8B,YACpE,QAAA,EAAA,OAAA,EACH,CAAA;AAEJ,CAAC,CAAA;AAED,MAAM,UAAoC,EAAC;AAY3C,SAAS,UAAU,MAAA,EAA8B;AAC/C,EAAA,IAAI,IAAA,GAAO,CAAA;AAEX,EAAA,OAAO,MAAA,CAAO,IAAI,CAAA,KAAA,KAAS;AACzB,IAAA,MAAM,IAAA,GAAO,EAAE,KAAA,EAAO,IAAA,EAAM,KAAK,IAAA,GAAO,UAAA,CAAW,KAAK,CAAA,EAAE;AAC1D,IAAA,IAAA,GAAO,IAAA,CAAK,GAAA;AAEZ,IAAA,OAAO,IAAA;AAAA,EACT,CAAC,CAAA;AACH;AAEA,SAAS,WAAW,IAAA,EAAsB;AACxC,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA,EAAG,MAAA,IAAU,CAAA;AACvC;AAIA,SAAS,sBAAsB,IAAA,EAAsB;AACnD,EAAA,OAAO,IAAA,CAAK,SAAS,IAAI,CAAA,GAAI,OAAO,IAAA,CAAK,OAAA,CAAQ,QAAQ,IAAI,CAAA;AAC/D;AAIA,SAAS,WAAW,IAAA,EAAwC;AAC1D,EAAA,MAAM,UAAA,GAAa,KAAK,UAAA,CAAW,SAAA;AACnC,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,UAAU,GAAG,OAAO,MAAA;AAEvC,EAAA,MAAM,QAAA,GAAW,UAAA,CAAW,IAAA,CAAK,CAAA,KAAA,KAAS,OAAO,UAAU,QAAA,IAAY,KAAA,CAAM,UAAA,CAAW,WAAW,CAAC,CAAA;AACpG,EAAA,OAAO,OAAO,QAAA,KAAa,QAAA,GAAW,SAAS,KAAA,CAAM,WAAA,CAAY,MAAM,CAAA,GAAI,MAAA;AAC7E;AAEA,SAAS,WAAW,IAAA,EAAiF;AACnG,EAAA,MAAM,KAAA,GAAQ,IAAA,EAAM,QAAA,CAAS,IAAA,CAAK,CAAA,KAAA,KAAS,MAAM,IAAA,KAAS,SAAA,IAAa,KAAA,CAAM,OAAA,KAAY,MAAM,CAAA;AAC/F,EAAA,IAAI,KAAA,EAAO,IAAA,KAAS,SAAA,EAAW,OAAO,MAAA;AAEtC,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,QAAA,CAAS,GAAA,CAAI,CAAA,KAAA,KAAU,KAAA,CAAM,IAAA,KAAS,MAAA,GAAS,KAAA,CAAM,KAAA,GAAQ,EAAG,CAAA,CAAE,KAAK,EAAE,CAAA;AAC5F,EAAA,OAAO,EAAE,IAAA,EAAM,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA,EAAG,QAAA,EAAU,UAAA,CAAW,KAAK,CAAA,EAAE;AACtE;AAEA,SAAS,iBAAA,CAAkB;AAAA,EACzB,IAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAIG;AACD,EAAA,MAAM,MAAA,GAAS,WAAW,IAAI,CAAA;AAC9B,EAAA,IAAI,CAAC,MAAA,EAAQ,uBAAO,GAAA,CAAC,KAAA,EAAA,EAAI,WAAuB,QAAA,EAAS,CAAA;AAEzD,EAAA,uBACE,GAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACC,MAAM,MAAA,CAAO,IAAA;AAAA,MACb,MAAM,MAAA,CAAO,QAAA;AAAA,MACb,QAAA,EAAS,QAAA;AAAA,MACT,SAAA,EAAW,EAAA,CAAG,kBAAA,EAAoB,SAAS,CAAA;AAAA,MAC3C,WAAA,EAAY;AAAA;AAAA,GACd;AAEJ;AAEA,MAAM,qBAAA,GAAwB,6DAAA;AAE9B,SAAS,iBAAiB,KAAA,EAA4B;AACpD,EAAA,OAAO,KAAA,CAAM,MAAA,KAAW,CAAA,IAAK,CAAC,KAAA,CAAM,OAAA,IAAW,CAAC,KAAA,CAAM,OAAA,IAAW,CAAC,KAAA,CAAM,QAAA,IAAY,CAAC,KAAA,CAAM,MAAA;AAC7F;AAEA,SAAS,aAAa,kBAAA,EAA8E;AAClG,EAAA,OAAO,SAAS,YAAA,CAAa,EAAE,IAAA,EAAM,KAAA,EAAO,UAAS,EAAG;AACtD,IAAA,MAAM,UAAA,GAAa,eAAA,CAAgB,IAAA,CAAK,IAAA,IAAQ,EAAE,CAAA;AAElD,IAAA,MAAM,cAAoD,CAAA,KAAA,KAAS;AACjE,MAAA,IAAI,CAAC,UAAA,IAAc,kBAAA,KAAuB,YAAY,CAAC,gBAAA,CAAiB,KAAK,CAAA,EAAG;AAEhF,MAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,IAAA,EAAM,UAAU,qBAAqB,CAAA;AAC/D,MAAA,IAAI,CAAC,KAAA,EAAO;AAEZ,MAAA,KAAA,CAAM,MAAA,GAAS,IAAA;AACf,MAAA,KAAA,CAAM,cAAA,EAAe;AAAA,IACvB,CAAA;AAEA,IAAA,uBACE,GAAA;AAAA,MAAC,GAAA;AAAA,MAAA;AAAA,QACC,IAAA;AAAA,QACA,KAAA;AAAA,QACA,OAAA,EAAS,WAAA;AAAA,QACT,MAAA,EAAQ,aAAa,QAAA,GAAW,MAAA;AAAA,QAChC,GAAA,EAAK,aAAa,qBAAA,GAAwB,MAAA;AAAA,QAEzC;AAAA;AAAA,KACH;AAAA,EAEJ,CAAA;AACF;AAEA,MAAM,cAAA,GAAiB,CAAC,SAAS,CAAA;AAKjC,MAAM,cAAA,GAAiB,EAAE,KAAA,EAAO,KAAA,EAAO,UAAU,WAAA,EAAY;AAK7D,MAAM,UAAA,GAAyB;AAAA,EAC7B,GAAA,EAAK,iBAAA;AAAA,EACL,CAAA,EAAG,aAAa,KAAK;AACvB,CAAA;AAEA,MAAM,iBAAA,GAAgC;AAAA,EACpC,GAAG,UAAA;AAAA,EACH,CAAA,EAAG,aAAa,QAAQ;AAC1B,CAAA;;;;"}
@@ -7,7 +7,7 @@ const remarkGfm = require('remark-gfm');
7
7
  const remend = require('remend');
8
8
  const tokens = require('./tokens.cjs.js');
9
9
  const useWatched = require('./use-watched-BixmYWp0.cjs');
10
- const codeBlock = require('./code-block-fmv8bHoT.cjs');
10
+ const codeBlock = require('./code-block-DgHrBuR8.cjs');
11
11
  const utils_cn = require('./cn-DCPcv4xv.cjs');
12
12
 
13
13
  const UNBROKEN = /* @__PURE__ */ new Set(["pre", "code"]);
@@ -240,4 +240,4 @@ const WINDOW_COMPONENTS = {
240
240
  };
241
241
 
242
242
  exports.MarkdownRenderer = MarkdownRenderer;
243
- //# sourceMappingURL=markdown-renderer-DGIzNAQY.cjs.map
243
+ //# sourceMappingURL=markdown-renderer-GVtZLunG.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"markdown-renderer-DGIzNAQY.cjs","sources":["../src/ds/components/MarkdownRenderer/arriving.ts","../src/ds/components/MarkdownRenderer/blocks.ts","../src/ds/components/MarkdownRenderer/use-settled.ts","../src/ds/components/MarkdownRenderer/markdown-renderer.tsx"],"sourcesContent":["import type { ExtraProps } from 'react-markdown';\n\nimport { ARRIVING_CLASS } from '@/ds/tokens';\n\nimport '@/ds/components/Arrival/arrival.css';\n\ntype MarkdownElement = NonNullable<ExtraProps['node']>;\ntype MarkdownChild = MarkdownElement['children'][number];\n\n/** Fades whole: a fence renders through `CodeBlock`, and inline code carries a background. */\nconst UNBROKEN = new Set(['pre', 'code']);\n\nconst wrap = (value: string, arriving: boolean): MarkdownChild => ({\n type: 'element',\n tagName: 'span',\n properties: arriving ? { className: [ARRIVING_CLASS] } : {},\n children: [{ type: 'text', value }],\n});\n\nfunction markArriving(node: MarkdownElement): void {\n const classes = node.properties.className;\n\n node.properties.className = Array.isArray(classes) ? [...classes, ARRIVING_CLASS] : [ARRIVING_CLASS];\n}\n\nfunction rebuild(nodes: MarkdownChild[], arriving: () => boolean): MarkdownChild[] {\n const out: MarkdownChild[] = [];\n\n for (const node of nodes) {\n if (node.type === 'text') {\n for (const piece of node.value.match(/\\S+|\\s+/g) ?? []) {\n if (/\\S/.test(piece)) out.push(wrap(piece, arriving()));\n else out.push({ type: 'text', value: piece });\n }\n\n continue;\n }\n\n out.push(node);\n\n if (node.type !== 'element') continue;\n\n if (!UNBROKEN.has(node.tagName)) node.children = rebuild(node.children, arriving);\n else if (arriving()) markArriving(node);\n }\n\n return out;\n}\n\n/**\n * Gives every word of a block an element of its own, and the entrance class to\n * those past the first `settled` — the words that were not on screen yet when\n * the reader joined this reply.\n *\n * Every word, not just the arriving ones: react-markdown parses afresh on each\n * landing and React matches the children it already mounted by position, so a\n * wrapper that came and went would land where a bare text node used to be and\n * remount half the paragraph, replaying entrances that had long finished.\n * Wrapping the lot keeps every position fixed — a new word only ever appends —\n * and a word never gains the class it did not mount with, so nothing replays.\n */\nexport function rehypeArriving(settled: number) {\n return function attach() {\n return (tree: { children: MarkdownChild[] }) => {\n let word = 0;\n\n tree.children = rebuild(tree.children, () => word++ >= settled);\n };\n };\n}\n","const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/;\nconst FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/;\nconst LIST_ITEM = /^ {0,3}(?:[-*+]|\\d{1,9}[.)])(?:[ \\t]|$)/;\nconst INDENTED = /^(?:\\t| {2,})/;\nconst DEFINITION = /^ {0,3}\\[[^\\]]+\\]:/;\n\nfunction fenceOpenedBy(line: string): string | undefined {\n return FENCE_OPEN.exec(line)?.[1];\n}\n\n// Both are runs of one character, so `startsWith` is \"same marker, at least as long\".\nfunction closesFence(line: string, marker: string): boolean {\n return FENCE_CLOSE.exec(line)?.[1]?.startsWith(marker) ?? false;\n}\n\nfunction startsBlock(line: string, inList: boolean): boolean {\n if (INDENTED.test(line)) return false;\n\n return !(inList && LIST_ITEM.test(line));\n}\n\n/**\n * Cuts markdown into top-level blocks so a growing reply re-parses its last\n * block instead of all of them. Rejoining the result returns the input, and\n * every block renders as it would have inside the whole.\n *\n * Each rule only ever merges: over-merging costs a re-parse, while cutting a\n * construct in two changes what it renders as. Link reference and footnote\n * definitions resolve across blank lines, which no line-level rule can see, so\n * reaching one outside a fence abandons the split for the whole text.\n */\nexport function splitBlocks(markdown: string): string[] {\n const lines = markdown.split('\\n');\n const starts = [0];\n let fence: string | undefined;\n let started = false;\n let broken = false;\n let inList = false;\n\n for (const [index, line] of lines.entries()) {\n if (fence) {\n if (closesFence(line, fence)) fence = undefined;\n continue;\n }\n\n if (line.trim() === '') {\n broken = started;\n continue;\n }\n\n if (DEFINITION.test(line)) return [markdown];\n\n if (broken && startsBlock(line, inList)) {\n starts.push(index);\n started = false;\n }\n\n if (!started) inList = LIST_ITEM.test(line);\n\n started = true;\n broken = false;\n fence = fenceOpenedBy(line);\n }\n\n return starts.map((start, position) => {\n const end = starts[position + 1];\n const block = lines.slice(start, end ?? lines.length).join('\\n');\n\n return end === undefined ? block : `${block}\\n`;\n });\n}\n","import { useEffect, useRef, useState } from 'react';\n\nimport { useWatched } from '@/ds/components/Arrival';\nimport { ARRIVING_MS } from '@/ds/tokens';\n\ninterface Landing {\n words: number;\n at: number;\n}\n\n/**\n * How many of a reply's words have been on screen long enough that their entrance is over.\n *\n * The entrance plays on whatever mounts carrying the class, so it replays every time a\n * word's element is rebuilt — and markdown rebuilds a growing tail constantly: a `-`\n * turns a paragraph into a list item, a closing `**` splits it around a `<strong>`, the\n * stream ends and the mend comes off. Each of those swaps the element a settled word\n * lives in, which is what makes a word already read fade in again.\n *\n * Ageing them out is the whole answer: a word carries the class only while it is new,\n * so however often the tail is rebuilt, only what is genuinely arriving animates.\n *\n * What counts as new at mount is not this hook's call. A reply the reader was handed\n * is already there, and a block that mounts complete — a card's output, an expanded\n * body — enters with its container. Only a passage born streaming under the reader's\n * eyes is new from its first word: the reveal often lands several words in its first\n * commit, and they enter like every word after them.\n */\nexport function useSettledWords(words: number, streaming: boolean): number {\n const watched = useWatched();\n const [settled, setSettled] = useState(() => (watched && streaming ? 0 : words));\n const landings = useRef<Landing[]>([]);\n const recorded = useRef(settled);\n\n useEffect(() => {\n if (recorded.current !== words) {\n landings.current.push({ words, at: performance.now() });\n recorded.current = words;\n }\n\n const waiting = landings.current[0];\n if (!waiting) return;\n\n const timer = setTimeout(\n () => {\n landings.current.shift();\n setSettled(waiting.words);\n },\n Math.max(0, waiting.at + ARRIVING_MS - performance.now()),\n );\n\n return () => clearTimeout(timer);\n }, [settled, words]);\n\n // A shorter reply replacing the one on screen settles at once: nothing of it is new.\n return Math.min(settled, words);\n}\n","import { memo, useMemo } from 'react';\nimport type { MouseEvent, MouseEventHandler, ReactNode } from 'react';\nimport Markdown from 'react-markdown';\nimport type { Components, ExtraProps, Options } from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport remend from 'remend';\n\nimport { rehypeArriving } from './arriving';\nimport { splitBlocks } from './blocks';\nimport { useSettledWords } from './use-settled';\nimport { CodeBlock } from '@/ds/components/CodeBlock';\nimport { cn } from '@/lib/utils';\n\nimport './markdown-renderer.css';\n\nexport type MarkdownExternalLinkTarget = 'tab' | 'window';\n\nexport interface MarkdownRendererProps {\n children: string;\n className?: string;\n externalLinkTarget?: MarkdownExternalLinkTarget;\n /** The text is a prefix of one still being written: close the markers the stream has not reached. */\n streaming?: boolean;\n}\n\n/**\n * Renders a markdown string. Agent output can carry attacker-influenced text\n * (file contents, tool output, web pages): react-markdown escapes raw HTML and\n * drops dangerous link schemes, so nothing here reaches the DOM as markup.\n *\n * react-markdown re-parses on every render, and a streaming reply re-renders\n * its whole transcript on every delta. Memoizing spares the settled messages;\n * rendering block by block — streaming or not — spares every block of the live\n * one but the last, and lets a reply settle without remounting what is already\n * on screen.\n *\n * The text is drawn as given: pacing a stream is `useRevealedText`, and belongs to\n * whoever owns the whole of what is being laid down — a reply is prose, tool rows\n * and cards, and they have to arrive in the order they were written. Only what\n * lands after the reader joined plays an entrance, and only while it is still new:\n * a reply opened part-written is already there, and fading in what someone is\n * halfway through reading would be both a lie and a screenful of animations at\n * once.\n */\nexport const MarkdownRenderer = memo(function MarkdownRenderer({\n children,\n className,\n externalLinkTarget = 'tab',\n streaming = false,\n}: MarkdownRendererProps) {\n const shown = decodeEscapedNewlines(children);\n const blocks = useMemo(() => splitBlocks(shown), [shown]);\n const last = blocks.length - 1;\n const components = externalLinkTarget === 'window' ? WINDOW_COMPONENTS : COMPONENTS;\n\n const spans = wordSpans(blocks);\n const settled = useSettledWords(spans.at(-1)?.end ?? 0, streaming);\n\n // A block whose words have all finished their entrance has nothing left to play,\n // which is what `undefined` says: leave it as plain text, no spans at all.\n const settledWords = (span: WordSpan | undefined): number | undefined =>\n !span || settled >= span.end ? undefined : Math.max(0, settled - span.start);\n\n const tail = blocks[last] ?? '';\n const mended = useMemo(() => (streaming ? remend(tail, REMEND_OPTIONS) : tail), [streaming, tail]);\n\n return (\n <div className={cn('mastra-markdown', className)}>\n {blocks.map((block, index) => (\n <MarkdownBlock\n key={index}\n content={index === last ? mended : block}\n settledWords={settledWords(spans[index])}\n components={components}\n />\n ))}\n </div>\n );\n});\n\n/** Keyed by position at the call site: a content key remounts on every character. */\nconst MarkdownBlock = memo(function MarkdownBlock({\n components,\n content,\n settledWords,\n}: {\n components: Components;\n content: string;\n settledWords?: number;\n}) {\n const rehypePlugins = useMemo(\n () => (settledWords === undefined ? SETTLED : [rehypeArriving(settledWords)]),\n [settledWords],\n );\n\n return (\n <Markdown remarkPlugins={REMARK_PLUGINS} rehypePlugins={rehypePlugins} components={components}>\n {content}\n </Markdown>\n );\n});\n\nconst SETTLED: Options['rehypePlugins'] = [];\n\ninterface WordSpan {\n start: number;\n end: number;\n}\n\n/**\n * Where each block's words fall in the reply's own count, so one settled count covers\n * them all. Counting the source counts its markers too, so the boundary only ever errs\n * towards leaving a word unanimated.\n */\nfunction wordSpans(blocks: string[]): WordSpan[] {\n let read = 0;\n\n return blocks.map(block => {\n const span = { start: read, end: read + countWords(block) };\n read = span.end;\n\n return span;\n });\n}\n\nfunction countWords(text: string): number {\n return text.match(/\\S+/g)?.length ?? 0;\n}\n\n// Agent networks emit their text with literal `\\n`. Only unescape when the text\n// has no real newline, otherwise a `\"a\\nb\"` inside a code fence gets shredded.\nfunction decodeEscapedNewlines(text: string): string {\n return text.includes('\\n') ? text : text.replace(/\\\\n/g, '\\n');\n}\n\ntype MarkdownNode = NonNullable<ExtraProps['node']>;\n\nfunction languageOf(node: MarkdownNode): string | undefined {\n const classNames = node.properties.className;\n if (!Array.isArray(classNames)) return undefined;\n\n const language = classNames.find(entry => typeof entry === 'string' && entry.startsWith('language-'));\n return typeof language === 'string' ? language.slice('language-'.length) : undefined;\n}\n\nfunction fencedCode(node: MarkdownNode | undefined): { code: string; language?: string } | undefined {\n const child = node?.children.find(entry => entry.type === 'element' && entry.tagName === 'code');\n if (child?.type !== 'element') return undefined;\n\n const code = child.children.map(entry => (entry.type === 'text' ? entry.value : '')).join('');\n return { code: code.replace(/\\n$/, ''), language: languageOf(child) };\n}\n\nfunction MarkdownCodeBlock({\n node,\n children,\n className,\n}: {\n node?: MarkdownNode;\n children?: ReactNode;\n className?: string;\n}) {\n const fenced = fencedCode(node);\n if (!fenced) return <pre className={className}>{children}</pre>;\n\n return (\n <CodeBlock\n code={fenced.code}\n lang={fenced.language}\n overflow=\"scroll\"\n className={cn('my-3 bg-surface1', className)}\n copyMessage=\"Copied code to clipboard\"\n />\n );\n}\n\nconst POPUP_WINDOW_FEATURES = 'popup=yes,width=720,height=800,resizable=yes,scrollbars=yes';\n\nfunction isPlainLeftClick(event: MouseEvent): boolean {\n return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;\n}\n\nfunction markdownLink(externalLinkTarget: MarkdownExternalLinkTarget): NonNullable<Components['a']> {\n return function MarkdownLink({ href, title, children }) {\n const isExternal = /^https?:\\/\\//i.test(href ?? '');\n\n const openInPopup: MouseEventHandler<HTMLAnchorElement> = event => {\n if (!isExternal || externalLinkTarget !== 'window' || !isPlainLeftClick(event)) return;\n\n const popup = window.open(href, '_blank', POPUP_WINDOW_FEATURES);\n if (!popup) return;\n\n popup.opener = null;\n event.preventDefault();\n };\n\n return (\n <a\n href={href}\n title={title}\n onClick={openInPopup}\n target={isExternal ? '_blank' : undefined}\n rel={isExternal ? 'noopener noreferrer' : undefined}\n >\n {children}\n </a>\n );\n };\n}\n\nconst REMARK_PLUGINS = [remarkGfm];\n\n// Links stay text until their URL lands: remend's placeholder href would render\n// a live anchor to nowhere. No math is rendered here, so pairing `$$` would only\n// turn one literal into another.\nconst REMEND_OPTIONS = { katex: false, linkMode: 'text-only' } as const;\n\n// Elements are listed one by one: react-markdown also passes its `node`, which\n// React would forward to the DOM as a stray attribute. Everything else is\n// styled from markdown-renderer.css.\nconst COMPONENTS: Components = {\n pre: MarkdownCodeBlock,\n a: markdownLink('tab'),\n};\n\nconst WINDOW_COMPONENTS: Components = {\n ...COMPONENTS,\n a: markdownLink('window'),\n};\n"],"names":["ARRIVING_CLASS","useWatched","useState","useRef","useEffect","ARRIVING_MS","memo","MarkdownRenderer","useMemo","jsx","cn","MarkdownBlock","CodeBlock"],"mappings":"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"}
1
+ {"version":3,"file":"markdown-renderer-GVtZLunG.cjs","sources":["../src/ds/components/MarkdownRenderer/arriving.ts","../src/ds/components/MarkdownRenderer/blocks.ts","../src/ds/components/MarkdownRenderer/use-settled.ts","../src/ds/components/MarkdownRenderer/markdown-renderer.tsx"],"sourcesContent":["import type { ExtraProps } from 'react-markdown';\n\nimport { ARRIVING_CLASS } from '@/ds/tokens';\n\nimport '@/ds/components/Arrival/arrival.css';\n\ntype MarkdownElement = NonNullable<ExtraProps['node']>;\ntype MarkdownChild = MarkdownElement['children'][number];\n\n/** Fades whole: a fence renders through `CodeBlock`, and inline code carries a background. */\nconst UNBROKEN = new Set(['pre', 'code']);\n\nconst wrap = (value: string, arriving: boolean): MarkdownChild => ({\n type: 'element',\n tagName: 'span',\n properties: arriving ? { className: [ARRIVING_CLASS] } : {},\n children: [{ type: 'text', value }],\n});\n\nfunction markArriving(node: MarkdownElement): void {\n const classes = node.properties.className;\n\n node.properties.className = Array.isArray(classes) ? [...classes, ARRIVING_CLASS] : [ARRIVING_CLASS];\n}\n\nfunction rebuild(nodes: MarkdownChild[], arriving: () => boolean): MarkdownChild[] {\n const out: MarkdownChild[] = [];\n\n for (const node of nodes) {\n if (node.type === 'text') {\n for (const piece of node.value.match(/\\S+|\\s+/g) ?? []) {\n if (/\\S/.test(piece)) out.push(wrap(piece, arriving()));\n else out.push({ type: 'text', value: piece });\n }\n\n continue;\n }\n\n out.push(node);\n\n if (node.type !== 'element') continue;\n\n if (!UNBROKEN.has(node.tagName)) node.children = rebuild(node.children, arriving);\n else if (arriving()) markArriving(node);\n }\n\n return out;\n}\n\n/**\n * Gives every word of a block an element of its own, and the entrance class to\n * those past the first `settled` — the words that were not on screen yet when\n * the reader joined this reply.\n *\n * Every word, not just the arriving ones: react-markdown parses afresh on each\n * landing and React matches the children it already mounted by position, so a\n * wrapper that came and went would land where a bare text node used to be and\n * remount half the paragraph, replaying entrances that had long finished.\n * Wrapping the lot keeps every position fixed — a new word only ever appends —\n * and a word never gains the class it did not mount with, so nothing replays.\n */\nexport function rehypeArriving(settled: number) {\n return function attach() {\n return (tree: { children: MarkdownChild[] }) => {\n let word = 0;\n\n tree.children = rebuild(tree.children, () => word++ >= settled);\n };\n };\n}\n","const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/;\nconst FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/;\nconst LIST_ITEM = /^ {0,3}(?:[-*+]|\\d{1,9}[.)])(?:[ \\t]|$)/;\nconst INDENTED = /^(?:\\t| {2,})/;\nconst DEFINITION = /^ {0,3}\\[[^\\]]+\\]:/;\n\nfunction fenceOpenedBy(line: string): string | undefined {\n return FENCE_OPEN.exec(line)?.[1];\n}\n\n// Both are runs of one character, so `startsWith` is \"same marker, at least as long\".\nfunction closesFence(line: string, marker: string): boolean {\n return FENCE_CLOSE.exec(line)?.[1]?.startsWith(marker) ?? false;\n}\n\nfunction startsBlock(line: string, inList: boolean): boolean {\n if (INDENTED.test(line)) return false;\n\n return !(inList && LIST_ITEM.test(line));\n}\n\n/**\n * Cuts markdown into top-level blocks so a growing reply re-parses its last\n * block instead of all of them. Rejoining the result returns the input, and\n * every block renders as it would have inside the whole.\n *\n * Each rule only ever merges: over-merging costs a re-parse, while cutting a\n * construct in two changes what it renders as. Link reference and footnote\n * definitions resolve across blank lines, which no line-level rule can see, so\n * reaching one outside a fence abandons the split for the whole text.\n */\nexport function splitBlocks(markdown: string): string[] {\n const lines = markdown.split('\\n');\n const starts = [0];\n let fence: string | undefined;\n let started = false;\n let broken = false;\n let inList = false;\n\n for (const [index, line] of lines.entries()) {\n if (fence) {\n if (closesFence(line, fence)) fence = undefined;\n continue;\n }\n\n if (line.trim() === '') {\n broken = started;\n continue;\n }\n\n if (DEFINITION.test(line)) return [markdown];\n\n if (broken && startsBlock(line, inList)) {\n starts.push(index);\n started = false;\n }\n\n if (!started) inList = LIST_ITEM.test(line);\n\n started = true;\n broken = false;\n fence = fenceOpenedBy(line);\n }\n\n return starts.map((start, position) => {\n const end = starts[position + 1];\n const block = lines.slice(start, end ?? lines.length).join('\\n');\n\n return end === undefined ? block : `${block}\\n`;\n });\n}\n","import { useEffect, useRef, useState } from 'react';\n\nimport { useWatched } from '@/ds/components/Arrival';\nimport { ARRIVING_MS } from '@/ds/tokens';\n\ninterface Landing {\n words: number;\n at: number;\n}\n\n/**\n * How many of a reply's words have been on screen long enough that their entrance is over.\n *\n * The entrance plays on whatever mounts carrying the class, so it replays every time a\n * word's element is rebuilt — and markdown rebuilds a growing tail constantly: a `-`\n * turns a paragraph into a list item, a closing `**` splits it around a `<strong>`, the\n * stream ends and the mend comes off. Each of those swaps the element a settled word\n * lives in, which is what makes a word already read fade in again.\n *\n * Ageing them out is the whole answer: a word carries the class only while it is new,\n * so however often the tail is rebuilt, only what is genuinely arriving animates.\n *\n * What counts as new at mount is not this hook's call. A reply the reader was handed\n * is already there, and a block that mounts complete — a card's output, an expanded\n * body — enters with its container. Only a passage born streaming under the reader's\n * eyes is new from its first word: the reveal often lands several words in its first\n * commit, and they enter like every word after them.\n */\nexport function useSettledWords(words: number, streaming: boolean): number {\n const watched = useWatched();\n const [settled, setSettled] = useState(() => (watched && streaming ? 0 : words));\n const landings = useRef<Landing[]>([]);\n const recorded = useRef(settled);\n\n useEffect(() => {\n if (recorded.current !== words) {\n landings.current.push({ words, at: performance.now() });\n recorded.current = words;\n }\n\n const waiting = landings.current[0];\n if (!waiting) return;\n\n const timer = setTimeout(\n () => {\n landings.current.shift();\n setSettled(waiting.words);\n },\n Math.max(0, waiting.at + ARRIVING_MS - performance.now()),\n );\n\n return () => clearTimeout(timer);\n }, [settled, words]);\n\n // A shorter reply replacing the one on screen settles at once: nothing of it is new.\n return Math.min(settled, words);\n}\n","import { memo, useMemo } from 'react';\nimport type { MouseEvent, MouseEventHandler, ReactNode } from 'react';\nimport Markdown from 'react-markdown';\nimport type { Components, ExtraProps, Options } from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport remend from 'remend';\n\nimport { rehypeArriving } from './arriving';\nimport { splitBlocks } from './blocks';\nimport { useSettledWords } from './use-settled';\nimport { CodeBlock } from '@/ds/components/CodeBlock';\nimport { cn } from '@/lib/utils';\n\nimport './markdown-renderer.css';\n\nexport type MarkdownExternalLinkTarget = 'tab' | 'window';\n\nexport interface MarkdownRendererProps {\n children: string;\n className?: string;\n externalLinkTarget?: MarkdownExternalLinkTarget;\n /** The text is a prefix of one still being written: close the markers the stream has not reached. */\n streaming?: boolean;\n}\n\n/**\n * Renders a markdown string. Agent output can carry attacker-influenced text\n * (file contents, tool output, web pages): react-markdown escapes raw HTML and\n * drops dangerous link schemes, so nothing here reaches the DOM as markup.\n *\n * react-markdown re-parses on every render, and a streaming reply re-renders\n * its whole transcript on every delta. Memoizing spares the settled messages;\n * rendering block by block — streaming or not — spares every block of the live\n * one but the last, and lets a reply settle without remounting what is already\n * on screen.\n *\n * The text is drawn as given: pacing a stream is `useRevealedText`, and belongs to\n * whoever owns the whole of what is being laid down — a reply is prose, tool rows\n * and cards, and they have to arrive in the order they were written. Only what\n * lands after the reader joined plays an entrance, and only while it is still new:\n * a reply opened part-written is already there, and fading in what someone is\n * halfway through reading would be both a lie and a screenful of animations at\n * once.\n */\nexport const MarkdownRenderer = memo(function MarkdownRenderer({\n children,\n className,\n externalLinkTarget = 'tab',\n streaming = false,\n}: MarkdownRendererProps) {\n const shown = decodeEscapedNewlines(children);\n const blocks = useMemo(() => splitBlocks(shown), [shown]);\n const last = blocks.length - 1;\n const components = externalLinkTarget === 'window' ? WINDOW_COMPONENTS : COMPONENTS;\n\n const spans = wordSpans(blocks);\n const settled = useSettledWords(spans.at(-1)?.end ?? 0, streaming);\n\n // A block whose words have all finished their entrance has nothing left to play,\n // which is what `undefined` says: leave it as plain text, no spans at all.\n const settledWords = (span: WordSpan | undefined): number | undefined =>\n !span || settled >= span.end ? undefined : Math.max(0, settled - span.start);\n\n const tail = blocks[last] ?? '';\n const mended = useMemo(() => (streaming ? remend(tail, REMEND_OPTIONS) : tail), [streaming, tail]);\n\n return (\n <div className={cn('mastra-markdown', className)}>\n {blocks.map((block, index) => (\n <MarkdownBlock\n key={index}\n content={index === last ? mended : block}\n settledWords={settledWords(spans[index])}\n components={components}\n />\n ))}\n </div>\n );\n});\n\n/** Keyed by position at the call site: a content key remounts on every character. */\nconst MarkdownBlock = memo(function MarkdownBlock({\n components,\n content,\n settledWords,\n}: {\n components: Components;\n content: string;\n settledWords?: number;\n}) {\n const rehypePlugins = useMemo(\n () => (settledWords === undefined ? SETTLED : [rehypeArriving(settledWords)]),\n [settledWords],\n );\n\n return (\n <Markdown remarkPlugins={REMARK_PLUGINS} rehypePlugins={rehypePlugins} components={components}>\n {content}\n </Markdown>\n );\n});\n\nconst SETTLED: Options['rehypePlugins'] = [];\n\ninterface WordSpan {\n start: number;\n end: number;\n}\n\n/**\n * Where each block's words fall in the reply's own count, so one settled count covers\n * them all. Counting the source counts its markers too, so the boundary only ever errs\n * towards leaving a word unanimated.\n */\nfunction wordSpans(blocks: string[]): WordSpan[] {\n let read = 0;\n\n return blocks.map(block => {\n const span = { start: read, end: read + countWords(block) };\n read = span.end;\n\n return span;\n });\n}\n\nfunction countWords(text: string): number {\n return text.match(/\\S+/g)?.length ?? 0;\n}\n\n// Agent networks emit their text with literal `\\n`. Only unescape when the text\n// has no real newline, otherwise a `\"a\\nb\"` inside a code fence gets shredded.\nfunction decodeEscapedNewlines(text: string): string {\n return text.includes('\\n') ? text : text.replace(/\\\\n/g, '\\n');\n}\n\ntype MarkdownNode = NonNullable<ExtraProps['node']>;\n\nfunction languageOf(node: MarkdownNode): string | undefined {\n const classNames = node.properties.className;\n if (!Array.isArray(classNames)) return undefined;\n\n const language = classNames.find(entry => typeof entry === 'string' && entry.startsWith('language-'));\n return typeof language === 'string' ? language.slice('language-'.length) : undefined;\n}\n\nfunction fencedCode(node: MarkdownNode | undefined): { code: string; language?: string } | undefined {\n const child = node?.children.find(entry => entry.type === 'element' && entry.tagName === 'code');\n if (child?.type !== 'element') return undefined;\n\n const code = child.children.map(entry => (entry.type === 'text' ? entry.value : '')).join('');\n return { code: code.replace(/\\n$/, ''), language: languageOf(child) };\n}\n\nfunction MarkdownCodeBlock({\n node,\n children,\n className,\n}: {\n node?: MarkdownNode;\n children?: ReactNode;\n className?: string;\n}) {\n const fenced = fencedCode(node);\n if (!fenced) return <pre className={className}>{children}</pre>;\n\n return (\n <CodeBlock\n code={fenced.code}\n lang={fenced.language}\n overflow=\"scroll\"\n className={cn('my-3 bg-surface1', className)}\n copyMessage=\"Copied code to clipboard\"\n />\n );\n}\n\nconst POPUP_WINDOW_FEATURES = 'popup=yes,width=720,height=800,resizable=yes,scrollbars=yes';\n\nfunction isPlainLeftClick(event: MouseEvent): boolean {\n return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;\n}\n\nfunction markdownLink(externalLinkTarget: MarkdownExternalLinkTarget): NonNullable<Components['a']> {\n return function MarkdownLink({ href, title, children }) {\n const isExternal = /^https?:\\/\\//i.test(href ?? '');\n\n const openInPopup: MouseEventHandler<HTMLAnchorElement> = event => {\n if (!isExternal || externalLinkTarget !== 'window' || !isPlainLeftClick(event)) return;\n\n const popup = window.open(href, '_blank', POPUP_WINDOW_FEATURES);\n if (!popup) return;\n\n popup.opener = null;\n event.preventDefault();\n };\n\n return (\n <a\n href={href}\n title={title}\n onClick={openInPopup}\n target={isExternal ? '_blank' : undefined}\n rel={isExternal ? 'noopener noreferrer' : undefined}\n >\n {children}\n </a>\n );\n };\n}\n\nconst REMARK_PLUGINS = [remarkGfm];\n\n// Links stay text until their URL lands: remend's placeholder href would render\n// a live anchor to nowhere. No math is rendered here, so pairing `$$` would only\n// turn one literal into another.\nconst REMEND_OPTIONS = { katex: false, linkMode: 'text-only' } as const;\n\n// Elements are listed one by one: react-markdown also passes its `node`, which\n// React would forward to the DOM as a stray attribute. Everything else is\n// styled from markdown-renderer.css.\nconst COMPONENTS: Components = {\n pre: MarkdownCodeBlock,\n a: markdownLink('tab'),\n};\n\nconst WINDOW_COMPONENTS: Components = {\n ...COMPONENTS,\n a: markdownLink('window'),\n};\n"],"names":["ARRIVING_CLASS","useWatched","useState","useRef","useEffect","ARRIVING_MS","memo","MarkdownRenderer","useMemo","jsx","cn","MarkdownBlock","CodeBlock"],"mappings":";;;;;;;;;;;;AAUA,MAAM,2BAAW,IAAI,GAAA,CAAI,CAAC,KAAA,EAAO,MAAM,CAAC,CAAA;AAExC,MAAM,IAAA,GAAO,CAAC,KAAA,EAAe,QAAA,MAAsC;AAAA,EACjE,IAAA,EAAM,SAAA;AAAA,EACN,OAAA,EAAS,MAAA;AAAA,EACT,UAAA,EAAY,WAAW,EAAE,SAAA,EAAW,CAACA,qBAAc,CAAA,KAAM,EAAC;AAAA,EAC1D,UAAU,CAAC,EAAE,IAAA,EAAM,MAAA,EAAQ,OAAO;AACpC,CAAA,CAAA;AAEA,SAAS,aAAa,IAAA,EAA6B;AACjD,EAAA,MAAM,OAAA,GAAU,KAAK,UAAA,CAAW,SAAA;AAEhC,EAAA,IAAA,CAAK,UAAA,CAAW,SAAA,GAAY,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,GAAI,CAAC,GAAG,OAAA,EAASA,qBAAc,CAAA,GAAI,CAACA,qBAAc,CAAA;AACrG;AAEA,SAAS,OAAA,CAAQ,OAAwB,QAAA,EAA0C;AACjF,EAAA,MAAM,MAAuB,EAAC;AAE9B,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,IAAA,CAAK,SAAS,MAAA,EAAQ;AACxB,MAAA,KAAA,MAAW,SAAS,IAAA,CAAK,KAAA,CAAM,MAAM,UAAU,CAAA,IAAK,EAAC,EAAG;AACtD,QAAA,IAAI,IAAA,CAAK,IAAA,CAAK,KAAK,CAAA,EAAG,GAAA,CAAI,KAAK,IAAA,CAAK,KAAA,EAAO,QAAA,EAAU,CAAC,CAAA;AAAA,iBAC7C,IAAA,CAAK,EAAE,MAAM,MAAA,EAAQ,KAAA,EAAO,OAAO,CAAA;AAAA,MAC9C;AAEA,MAAA;AAAA,IACF;AAEA,IAAA,GAAA,CAAI,KAAK,IAAI,CAAA;AAEb,IAAA,IAAI,IAAA,CAAK,SAAS,SAAA,EAAW;AAE7B,IAAA,IAAI,CAAC,QAAA,CAAS,GAAA,CAAI,IAAA,CAAK,OAAO,CAAA,EAAG,IAAA,CAAK,QAAA,GAAW,OAAA,CAAQ,IAAA,CAAK,QAAA,EAAU,QAAQ,CAAA;AAAA,SAAA,IACvE,QAAA,EAAS,EAAG,YAAA,CAAa,IAAI,CAAA;AAAA,EACxC;AAEA,EAAA,OAAO,GAAA;AACT;AAcO,SAAS,eAAe,OAAA,EAAiB;AAC9C,EAAA,OAAO,SAAS,MAAA,GAAS;AACvB,IAAA,OAAO,CAAC,IAAA,KAAwC;AAC9C,MAAA,IAAI,IAAA,GAAO,CAAA;AAEX,MAAA,IAAA,CAAK,WAAW,OAAA,CAAQ,IAAA,CAAK,QAAA,EAAU,MAAM,UAAU,OAAO,CAAA;AAAA,IAChE,CAAA;AAAA,EACF,CAAA;AACF;;ACrEA,MAAM,UAAA,GAAa,sBAAA;AACnB,MAAM,WAAA,GAAc,6BAAA;AACpB,MAAM,SAAA,GAAY,yCAAA;AAClB,MAAM,QAAA,GAAW,eAAA;AACjB,MAAM,UAAA,GAAa,oBAAA;AAEnB,SAAS,cAAc,IAAA,EAAkC;AACvD,EAAA,OAAO,UAAA,CAAW,IAAA,CAAK,IAAI,CAAA,GAAI,CAAC,CAAA;AAClC;AAGA,SAAS,WAAA,CAAY,MAAc,MAAA,EAAyB;AAC1D,EAAA,OAAO,WAAA,CAAY,KAAK,IAAI,CAAA,GAAI,CAAC,CAAA,EAAG,UAAA,CAAW,MAAM,CAAA,IAAK,KAAA;AAC5D;AAEA,SAAS,WAAA,CAAY,MAAc,MAAA,EAA0B;AAC3D,EAAA,IAAI,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA,EAAG,OAAO,KAAA;AAEhC,EAAA,OAAO,EAAE,MAAA,IAAU,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA,CAAA;AACxC;AAYO,SAAS,YAAY,QAAA,EAA4B;AACtD,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA;AACjC,EAAA,MAAM,MAAA,GAAS,CAAC,CAAC,CAAA;AACjB,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,IAAI,MAAA,GAAS,KAAA;AACb,EAAA,IAAI,MAAA,GAAS,KAAA;AAEb,EAAA,KAAA,MAAW,CAAC,KAAA,EAAO,IAAI,CAAA,IAAK,KAAA,CAAM,SAAQ,EAAG;AAC3C,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,IAAI,WAAA,CAAY,IAAA,EAAM,KAAK,CAAA,EAAG,KAAA,GAAQ,MAAA;AACtC,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,IAAA,CAAK,IAAA,EAAK,KAAM,EAAA,EAAI;AACtB,MAAA,MAAA,GAAS,OAAA;AACT,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,WAAW,IAAA,CAAK,IAAI,CAAA,EAAG,OAAO,CAAC,QAAQ,CAAA;AAE3C,IAAA,IAAI,MAAA,IAAU,WAAA,CAAY,IAAA,EAAM,MAAM,CAAA,EAAG;AACvC,MAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AACjB,MAAA,OAAA,GAAU,KAAA;AAAA,IACZ;AAEA,IAAA,IAAI,CAAC,OAAA,EAAS,MAAA,GAAS,SAAA,CAAU,KAAK,IAAI,CAAA;AAE1C,IAAA,OAAA,GAAU,IAAA;AACV,IAAA,MAAA,GAAS,KAAA;AACT,IAAA,KAAA,GAAQ,cAAc,IAAI,CAAA;AAAA,EAC5B;AAEA,EAAA,OAAO,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,EAAO,QAAA,KAAa;AACrC,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,QAAA,GAAW,CAAC,CAAA;AAC/B,IAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,CAAM,KAAA,EAAO,OAAO,KAAA,CAAM,MAAM,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAE/D,IAAA,OAAO,GAAA,KAAQ,MAAA,GAAY,KAAA,GAAQ,CAAA,EAAG,KAAK;AAAA,CAAA;AAAA,EAC7C,CAAC,CAAA;AACH;;AC1CO,SAAS,eAAA,CAAgB,OAAe,SAAA,EAA4B;AACzE,EAAA,MAAM,UAAUC,qBAAA,EAAW;AAC3B,EAAA,MAAM,CAAC,SAAS,UAAU,CAAA,GAAIC,eAAS,MAAO,OAAA,IAAW,SAAA,GAAY,CAAA,GAAI,KAAM,CAAA;AAC/E,EAAA,MAAM,QAAA,GAAWC,YAAA,CAAkB,EAAE,CAAA;AACrC,EAAA,MAAM,QAAA,GAAWA,aAAO,OAAO,CAAA;AAE/B,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,QAAA,CAAS,YAAY,KAAA,EAAO;AAC9B,MAAA,QAAA,CAAS,OAAA,CAAQ,KAAK,EAAE,KAAA,EAAO,IAAI,WAAA,CAAY,GAAA,IAAO,CAAA;AACtD,MAAA,QAAA,CAAS,OAAA,GAAU,KAAA;AAAA,IACrB;AAEA,IAAA,MAAM,OAAA,GAAU,QAAA,CAAS,OAAA,CAAQ,CAAC,CAAA;AAClC,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,MAAM,KAAA,GAAQ,UAAA;AAAA,MACZ,MAAM;AACJ,QAAA,QAAA,CAAS,QAAQ,KAAA,EAAM;AACvB,QAAA,UAAA,CAAW,QAAQ,KAAK,CAAA;AAAA,MAC1B,CAAA;AAAA,MACA,IAAA,CAAK,IAAI,CAAA,EAAG,OAAA,CAAQ,KAAKC,kBAAA,GAAc,WAAA,CAAY,KAAK;AAAA,KAC1D;AAEA,IAAA,OAAO,MAAM,aAAa,KAAK,CAAA;AAAA,EACjC,CAAA,EAAG,CAAC,OAAA,EAAS,KAAK,CAAC,CAAA;AAGnB,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,KAAK,CAAA;AAChC;;ACZO,MAAM,gBAAA,GAAmBC,UAAA,CAAK,SAASC,iBAAAA,CAAiB;AAAA,EAC7D,QAAA;AAAA,EACA,SAAA;AAAA,EACA,kBAAA,GAAqB,KAAA;AAAA,EACrB,SAAA,GAAY;AACd,CAAA,EAA0B;AACxB,EAAA,MAAM,KAAA,GAAQ,sBAAsB,QAAQ,CAAA;AAC5C,EAAA,MAAM,MAAA,GAASC,cAAQ,MAAM,WAAA,CAAY,KAAK,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AACxD,EAAA,MAAM,IAAA,GAAO,OAAO,MAAA,GAAS,CAAA;AAC7B,EAAA,MAAM,UAAA,GAAa,kBAAA,KAAuB,QAAA,GAAW,iBAAA,GAAoB,UAAA;AAEzE,EAAA,MAAM,KAAA,GAAQ,UAAU,MAAM,CAAA;AAC9B,EAAA,MAAM,OAAA,GAAU,gBAAgB,KAAA,CAAM,EAAA,CAAG,EAAE,CAAA,EAAG,GAAA,IAAO,GAAG,SAAS,CAAA;AAIjE,EAAA,MAAM,YAAA,GAAe,CAAC,IAAA,KACpB,CAAC,QAAQ,OAAA,IAAW,IAAA,CAAK,GAAA,GAAM,MAAA,GAAY,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,OAAA,GAAU,KAAK,KAAK,CAAA;AAE7E,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,IAAI,CAAA,IAAK,EAAA;AAC7B,EAAA,MAAM,MAAA,GAASA,aAAA,CAAQ,MAAO,SAAA,GAAY,MAAA,CAAO,IAAA,EAAM,cAAc,CAAA,GAAI,IAAA,EAAO,CAAC,SAAA,EAAW,IAAI,CAAC,CAAA;AAEjG,EAAA,uBACEC,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAWC,WAAA,CAAG,iBAAA,EAAmB,SAAS,CAAA,EAC5C,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,EAAO,KAAA,qBAClBD,cAAA;AAAA,IAAC,aAAA;AAAA,IAAA;AAAA,MAEC,OAAA,EAAS,KAAA,KAAU,IAAA,GAAO,MAAA,GAAS,KAAA;AAAA,MACnC,YAAA,EAAc,YAAA,CAAa,KAAA,CAAM,KAAK,CAAC,CAAA;AAAA,MACvC;AAAA,KAAA;AAAA,IAHK;AAAA,GAKR,CAAA,EACH,CAAA;AAEJ,CAAC;AAGD,MAAM,aAAA,GAAgBH,UAAA,CAAK,SAASK,cAAAA,CAAc;AAAA,EAChD,UAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAIG;AACD,EAAA,MAAM,aAAA,GAAgBH,aAAA;AAAA,IACpB,MAAO,YAAA,KAAiB,MAAA,GAAY,UAAU,CAAC,cAAA,CAAe,YAAY,CAAC,CAAA;AAAA,IAC3E,CAAC,YAAY;AAAA,GACf;AAEA,EAAA,sCACG,QAAA,EAAA,EAAS,aAAA,EAAe,cAAA,EAAgB,aAAA,EAA8B,YACpE,QAAA,EAAA,OAAA,EACH,CAAA;AAEJ,CAAC,CAAA;AAED,MAAM,UAAoC,EAAC;AAY3C,SAAS,UAAU,MAAA,EAA8B;AAC/C,EAAA,IAAI,IAAA,GAAO,CAAA;AAEX,EAAA,OAAO,MAAA,CAAO,IAAI,CAAA,KAAA,KAAS;AACzB,IAAA,MAAM,IAAA,GAAO,EAAE,KAAA,EAAO,IAAA,EAAM,KAAK,IAAA,GAAO,UAAA,CAAW,KAAK,CAAA,EAAE;AAC1D,IAAA,IAAA,GAAO,IAAA,CAAK,GAAA;AAEZ,IAAA,OAAO,IAAA;AAAA,EACT,CAAC,CAAA;AACH;AAEA,SAAS,WAAW,IAAA,EAAsB;AACxC,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA,EAAG,MAAA,IAAU,CAAA;AACvC;AAIA,SAAS,sBAAsB,IAAA,EAAsB;AACnD,EAAA,OAAO,IAAA,CAAK,SAAS,IAAI,CAAA,GAAI,OAAO,IAAA,CAAK,OAAA,CAAQ,QAAQ,IAAI,CAAA;AAC/D;AAIA,SAAS,WAAW,IAAA,EAAwC;AAC1D,EAAA,MAAM,UAAA,GAAa,KAAK,UAAA,CAAW,SAAA;AACnC,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,UAAU,GAAG,OAAO,MAAA;AAEvC,EAAA,MAAM,QAAA,GAAW,UAAA,CAAW,IAAA,CAAK,CAAA,KAAA,KAAS,OAAO,UAAU,QAAA,IAAY,KAAA,CAAM,UAAA,CAAW,WAAW,CAAC,CAAA;AACpG,EAAA,OAAO,OAAO,QAAA,KAAa,QAAA,GAAW,SAAS,KAAA,CAAM,WAAA,CAAY,MAAM,CAAA,GAAI,MAAA;AAC7E;AAEA,SAAS,WAAW,IAAA,EAAiF;AACnG,EAAA,MAAM,KAAA,GAAQ,IAAA,EAAM,QAAA,CAAS,IAAA,CAAK,CAAA,KAAA,KAAS,MAAM,IAAA,KAAS,SAAA,IAAa,KAAA,CAAM,OAAA,KAAY,MAAM,CAAA;AAC/F,EAAA,IAAI,KAAA,EAAO,IAAA,KAAS,SAAA,EAAW,OAAO,MAAA;AAEtC,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,QAAA,CAAS,GAAA,CAAI,CAAA,KAAA,KAAU,KAAA,CAAM,IAAA,KAAS,MAAA,GAAS,KAAA,CAAM,KAAA,GAAQ,EAAG,CAAA,CAAE,KAAK,EAAE,CAAA;AAC5F,EAAA,OAAO,EAAE,IAAA,EAAM,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA,EAAG,QAAA,EAAU,UAAA,CAAW,KAAK,CAAA,EAAE;AACtE;AAEA,SAAS,iBAAA,CAAkB;AAAA,EACzB,IAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAIG;AACD,EAAA,MAAM,MAAA,GAAS,WAAW,IAAI,CAAA;AAC9B,EAAA,IAAI,CAAC,MAAA,EAAQ,uBAAOC,cAAA,CAAC,KAAA,EAAA,EAAI,WAAuB,QAAA,EAAS,CAAA;AAEzD,EAAA,uBACEA,cAAA;AAAA,IAACG,mBAAA;AAAA,IAAA;AAAA,MACC,MAAM,MAAA,CAAO,IAAA;AAAA,MACb,MAAM,MAAA,CAAO,QAAA;AAAA,MACb,QAAA,EAAS,QAAA;AAAA,MACT,SAAA,EAAWF,WAAA,CAAG,kBAAA,EAAoB,SAAS,CAAA;AAAA,MAC3C,WAAA,EAAY;AAAA;AAAA,GACd;AAEJ;AAEA,MAAM,qBAAA,GAAwB,6DAAA;AAE9B,SAAS,iBAAiB,KAAA,EAA4B;AACpD,EAAA,OAAO,KAAA,CAAM,MAAA,KAAW,CAAA,IAAK,CAAC,KAAA,CAAM,OAAA,IAAW,CAAC,KAAA,CAAM,OAAA,IAAW,CAAC,KAAA,CAAM,QAAA,IAAY,CAAC,KAAA,CAAM,MAAA;AAC7F;AAEA,SAAS,aAAa,kBAAA,EAA8E;AAClG,EAAA,OAAO,SAAS,YAAA,CAAa,EAAE,IAAA,EAAM,KAAA,EAAO,UAAS,EAAG;AACtD,IAAA,MAAM,UAAA,GAAa,eAAA,CAAgB,IAAA,CAAK,IAAA,IAAQ,EAAE,CAAA;AAElD,IAAA,MAAM,cAAoD,CAAA,KAAA,KAAS;AACjE,MAAA,IAAI,CAAC,UAAA,IAAc,kBAAA,KAAuB,YAAY,CAAC,gBAAA,CAAiB,KAAK,CAAA,EAAG;AAEhF,MAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,IAAA,EAAM,UAAU,qBAAqB,CAAA;AAC/D,MAAA,IAAI,CAAC,KAAA,EAAO;AAEZ,MAAA,KAAA,CAAM,MAAA,GAAS,IAAA;AACf,MAAA,KAAA,CAAM,cAAA,EAAe;AAAA,IACvB,CAAA;AAEA,IAAA,uBACED,cAAA;AAAA,MAAC,GAAA;AAAA,MAAA;AAAA,QACC,IAAA;AAAA,QACA,KAAA;AAAA,QACA,OAAA,EAAS,WAAA;AAAA,QACT,MAAA,EAAQ,aAAa,QAAA,GAAW,MAAA;AAAA,QAChC,GAAA,EAAK,aAAa,qBAAA,GAAwB,MAAA;AAAA,QAEzC;AAAA;AAAA,KACH;AAAA,EAEJ,CAAA;AACF;AAEA,MAAM,cAAA,GAAiB,CAAC,SAAS,CAAA;AAKjC,MAAM,cAAA,GAAiB,EAAE,KAAA,EAAO,KAAA,EAAO,UAAU,WAAA,EAAY;AAK7D,MAAM,UAAA,GAAyB;AAAA,EAC7B,GAAA,EAAK,iBAAA;AAAA,EACL,CAAA,EAAG,aAAa,KAAK;AACvB,CAAA;AAEA,MAAM,iBAAA,GAAgC;AAAA,EACpC,GAAG,UAAA;AAAA,EACH,CAAA,EAAG,aAAa,QAAQ;AAC1B,CAAA;;;;"}
@@ -207,7 +207,9 @@ export declare const traceSpansQueryOptions: (client: MastraClient, traceId: str
207
207
  * these spans -- `input`, `output` and `attributes` included -- so the
208
208
  * projection would only hide content the reader is looking at.
209
209
  */
210
- export declare function useTraceSpans(traceId: string | null | undefined): UseQueryResult<{
210
+ export declare function useTraceSpans(traceId: string | null | undefined, { passive }?: {
211
+ passive?: boolean;
212
+ }): UseQueryResult<{
211
213
  traceId: string;
212
214
  spans: SearchableSpan[];
213
215
  } | null>;
@@ -1,4 +1,4 @@
1
- import { ComponentPropsWithoutRef } from 'react';
1
+ import { ComponentProps, ComponentPropsWithoutRef } from 'react';
2
2
  import { MessageScrollerButtonProps, MessageScrollerContentProps, MessageScrollerProviderProps, MessageScrollerViewportProps } from '../MessageScroller';
3
3
  export type ChatShellProps = ComponentPropsWithoutRef<'div'> & {
4
4
  scroller?: Omit<MessageScrollerProviderProps, 'children'>;
@@ -29,7 +29,24 @@ export declare function ChatShellViewport({ className, children, ...props }: Mes
29
29
  /** Scrolling content. The air above the composer belongs to the dock's fade band. */
30
30
  export declare function ChatShellContent({ className, ...props }: MessageScrollerContentProps): import("react").JSX.Element;
31
31
  /** The shared reading column. Every chat region must go through it. */
32
- export declare function ChatShellColumn({ className, ...props }: ComponentPropsWithoutRef<'div'>): import("react").JSX.Element;
32
+ export declare function ChatShellColumn({ className, ...props }: ComponentProps<'div'>): import("react").JSX.Element;
33
+ export interface ChatShellTurnProps extends ComponentPropsWithoutRef<'div'> {
34
+ /** Opened by a user message: the turn reserves room and releases it through a slow drift. */
35
+ opensTurn?: boolean;
36
+ /** Holds the reply's share of the screen open under the live turn while the run answers. */
37
+ holdsRoom?: boolean;
38
+ /** Restored mid-run: the room is already due, so it opens with no transition. */
39
+ restored?: boolean;
40
+ }
41
+ /**
42
+ * One user turn and the reply under it. The room is the answer's share of the
43
+ * screen: the scroller parks the sent message exactly one room above the end of
44
+ * the box, so wherever the composer ends the message rests with this much space
45
+ * under it, and the scroller has nothing to follow until the answer outgrows it.
46
+ * A full screen parked the message against the very top; 70 leaves it breathing.
47
+ * Opening must outrun the scroller's trip; closing is the conversation settling.
48
+ */
49
+ export declare function ChatShellTurn({ opensTurn, holdsRoom, restored, className, ...props }: ChatShellTurnProps): import("react").JSX.Element;
33
50
  /**
34
51
  * Composer region, sticky but left in flow: its own height reserves the room the
35
52
  * transcript scrolls behind, so nothing measures it and a composer growing under
@@ -1,5 +1,5 @@
1
- import { ChatShellBar, ChatShellColumn, ChatShellContent, ChatShellDock, ChatShellRoot, ChatShellScrollButton, ChatShellStage, ChatShellViewport } from './chat-shell';
2
- export type { ChatShellProps } from './chat-shell';
1
+ import { ChatShellBar, ChatShellColumn, ChatShellContent, ChatShellDock, ChatShellRoot, ChatShellScrollButton, ChatShellStage, ChatShellTurn, ChatShellViewport } from './chat-shell';
2
+ export type { ChatShellProps, ChatShellTurnProps } from './chat-shell';
3
3
  export declare const ChatShell: typeof ChatShellRoot & {
4
4
  Bar: typeof ChatShellBar;
5
5
  Column: typeof ChatShellColumn;
@@ -7,5 +7,6 @@ export declare const ChatShell: typeof ChatShellRoot & {
7
7
  Dock: typeof ChatShellDock;
8
8
  ScrollButton: typeof ChatShellScrollButton;
9
9
  Stage: typeof ChatShellStage;
10
+ Turn: typeof ChatShellTurn;
10
11
  Viewport: typeof ChatShellViewport;
11
12
  };
@@ -1 +1,2 @@
1
1
  export * from './code';
2
+ export * from './use-highlight';
@@ -0,0 +1,10 @@
1
+ import { CSSProperties } from 'react';
2
+ import { ThemedToken } from 'shiki/core';
3
+ export interface Highlighted {
4
+ code: string;
5
+ lang: string;
6
+ tokens: ThemedToken[][];
7
+ }
8
+ /** Tokens land a pass behind the code, so the value may still describe the previous code. */
9
+ export declare function useHighlight(code: string, lang: string | undefined): Highlighted | null;
10
+ export declare function tokenStyle(token: ThemedToken): CSSProperties | undefined;
@@ -1,2 +1,3 @@
1
1
  import { ThemedToken } from 'shiki/core';
2
2
  export declare function highlight(code: string, language: string): Promise<ThemedToken[][] | null>;
3
+ export declare function languageForPath(path: string | undefined): string | undefined;
@@ -1,6 +1,6 @@
1
1
  export * from './code-editor';
2
2
  export { codeLanguages } from './code-languages';
3
- export { highlight } from './highlight';
3
+ export { highlight, languageForPath } from './highlight';
4
4
  export { variableHighlight, VARIABLE_PATTERN } from './variable-highlight-extension';
5
5
  export { createVariableAutocomplete } from './variable-autocomplete-extension';
6
6
  export { flattenSchemaToVariables, type VariableCompletion } from './schema-to-variables';
@@ -0,0 +1,9 @@
1
+ import { ComponentProps } from 'react';
2
+ import { Button, TextButtonSize } from '../Button';
3
+ export type DialogActionProps = Omit<ComponentProps<typeof Button>, 'onClick' | 'variant' | 'as' | 'type' | 'size'> & {
4
+ size?: TextButtonSize;
5
+ onConfirm: () => void;
6
+ confirmation?: 'click' | 'hold';
7
+ holdSeconds?: number;
8
+ };
9
+ export declare const DialogAction: import('react').ForwardRefExoticComponent<Omit<DialogActionProps, "ref"> & import('react').RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,16 @@
1
+ export type DialogVariant = 'default' | 'new';
2
+ export type DialogIntent = 'default' | 'destructive';
3
+ export type DialogContextValue = {
4
+ variant: DialogVariant;
5
+ intent: DialogIntent;
6
+ pending: boolean;
7
+ };
8
+ export declare const DialogContext: import('react').Context<DialogContextValue>;
9
+ export declare function useDialogContext(): DialogContextValue;
10
+ export declare const dialogActionSizeClasses: {
11
+ readonly xs: "min-h-form-xs leading-ui-xs";
12
+ readonly sm: "min-h-form-sm leading-ui-sm";
13
+ readonly md: "min-h-form-md leading-ui-md";
14
+ readonly lg: "min-h-form-default leading-ui-md";
15
+ };
16
+ export declare const dialogActionLayoutClasses = "h-auto min-w-0 max-w-full whitespace-normal wrap-anywhere max-[22rem]:w-full";
@@ -0,0 +1,64 @@
1
+ import { StoryObj } from '@storybook/react-vite';
2
+ import { TextButtonSize } from '../Button';
3
+ declare function ConfirmationExample({ holdSeconds, buttonSize, intent, confirmation, title, description, actionLabel, cancelLabel, failFirst, longBody, }: {
4
+ holdSeconds?: number;
5
+ buttonSize?: TextButtonSize;
6
+ intent?: 'default' | 'destructive';
7
+ confirmation?: 'click' | 'hold';
8
+ title?: string;
9
+ description?: string;
10
+ actionLabel?: string;
11
+ cancelLabel?: string;
12
+ failFirst?: boolean;
13
+ longBody?: boolean;
14
+ }): import("react").JSX.Element;
15
+ declare const meta: {
16
+ title: string;
17
+ component: typeof ConfirmationExample;
18
+ parameters: {
19
+ layout: string;
20
+ docs: {
21
+ description: {
22
+ component: string;
23
+ };
24
+ };
25
+ };
26
+ argTypes: {
27
+ holdSeconds: {
28
+ control: {
29
+ type: "number";
30
+ min: number;
31
+ step: number;
32
+ };
33
+ };
34
+ buttonSize: {
35
+ control: "inline-radio";
36
+ options: string[];
37
+ };
38
+ intent: {
39
+ control: "inline-radio";
40
+ options: string[];
41
+ };
42
+ confirmation: {
43
+ control: "inline-radio";
44
+ options: string[];
45
+ };
46
+ };
47
+ args: {
48
+ holdSeconds: number;
49
+ buttonSize: "md";
50
+ intent: "default";
51
+ confirmation: "click";
52
+ failFirst: false;
53
+ longBody: false;
54
+ };
55
+ };
56
+ export default meta;
57
+ type Story = StoryObj<typeof meta>;
58
+ export declare const Default: Story;
59
+ export declare const Destructive: Story;
60
+ export declare const PressAndHold: Story;
61
+ export declare const LongTitleAndLabels: Story;
62
+ export declare const ScrollingBody: Story;
63
+ export declare const ErrorAndRetry: Story;
64
+ export declare const WithForm: Story;
@@ -1,6 +1,14 @@
1
1
  import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';
2
+ import { DialogAction } from './dialog-action';
3
+ import { DialogIntent, DialogVariant } from './dialog-context';
4
+ import { TextButtonSize } from '../Button';
2
5
  import * as React from 'react';
3
- declare const Dialog: typeof DialogPrimitive.Root;
6
+ export type DialogProps = DialogPrimitive.Root.Props & {
7
+ variant?: DialogVariant;
8
+ intent?: DialogIntent;
9
+ pending?: boolean;
10
+ };
11
+ declare function Dialog({ variant, intent, pending, onOpenChange, ...props }: DialogProps): React.JSX.Element;
4
12
  type DialogTriggerProps = DialogPrimitive.Trigger.Props & {
5
13
  /** @deprecated Use Base UI's native `render` prop instead for stronger composition typing. */
6
14
  asChild?: boolean;
@@ -30,10 +38,7 @@ declare const DialogFooter: {
30
38
  ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
31
39
  displayName: string;
32
40
  };
33
- declare const DialogBody: {
34
- ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
35
- displayName: string;
36
- };
41
+ declare const DialogBody: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
37
42
  type DialogTitleProps = Omit<DialogPrimitive.Title.Props, 'className'> & {
38
43
  className?: string;
39
44
  };
@@ -42,4 +47,8 @@ type DialogDescriptionProps = Omit<DialogPrimitive.Description.Props, 'className
42
47
  className?: string;
43
48
  };
44
49
  declare const DialogDescription: React.ForwardRefExoticComponent<Omit<DialogDescriptionProps, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
45
- export { Dialog, DialogPortal, DialogOverlay, DialogTrigger, DialogClose, DialogContent, DialogHeader, DialogFooter, DialogBody, DialogTitle, DialogDescription, };
50
+ type DialogCancelProps = DialogPrimitive.Close.Props & {
51
+ size?: TextButtonSize;
52
+ };
53
+ declare const DialogCancel: React.ForwardRefExoticComponent<Omit<DialogCancelProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
54
+ export { Dialog, DialogPortal, DialogOverlay, DialogTrigger, DialogClose, DialogContent, DialogHeader, DialogFooter, DialogBody, DialogTitle, DialogDescription, DialogCancel, DialogAction, };
@@ -1 +1,3 @@
1
1
  export * from './dialog';
2
+ export type { DialogActionProps } from './dialog-action';
3
+ export type { DialogIntent, DialogVariant } from './dialog-context';
@@ -5,3 +5,5 @@ export default meta;
5
5
  type Story = StoryObj<typeof HorizontalBars>;
6
6
  export declare const Default: Story;
7
7
  export declare const Stacked: Story;
8
+ /** Bright fills (yellow, orange, red) render at full opacity in dark mode; the label must stay readable on them. */
9
+ export declare const BrightSegments: Story;