@flamingo-stack/openframe-frontend-core 0.0.488 → 0.0.490-1506.3944.1

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 (311) hide show
  1. package/dist/chat-protocol/decode.d.ts +58 -0
  2. package/dist/chat-protocol/decode.d.ts.map +1 -0
  3. package/dist/chat-protocol/encode.d.ts +44 -0
  4. package/dist/chat-protocol/encode.d.ts.map +1 -0
  5. package/dist/chat-protocol/env-flag.d.ts +35 -0
  6. package/dist/chat-protocol/env-flag.d.ts.map +1 -0
  7. package/dist/chat-protocol/events.d.ts +196 -0
  8. package/dist/chat-protocol/events.d.ts.map +1 -0
  9. package/dist/chat-protocol/frames.d.ts +213 -0
  10. package/dist/chat-protocol/frames.d.ts.map +1 -0
  11. package/dist/chat-protocol/index.cjs +494 -0
  12. package/dist/chat-protocol/index.cjs.map +1 -0
  13. package/dist/chat-protocol/index.d.ts +18 -0
  14. package/dist/chat-protocol/index.d.ts.map +1 -0
  15. package/dist/chat-protocol/index.js +478 -0
  16. package/dist/chat-protocol/index.js.map +1 -0
  17. package/dist/chat-protocol/ip-normalize.d.ts +44 -0
  18. package/dist/chat-protocol/ip-normalize.d.ts.map +1 -0
  19. package/dist/chat-protocol/nats-decoder.d.ts +26 -0
  20. package/dist/chat-protocol/nats-decoder.d.ts.map +1 -0
  21. package/dist/{chunk-AJNDDXKC.js → chunk-4NWARV5I.js} +3 -3
  22. package/dist/{chunk-GQX6LFFA.cjs → chunk-5CJR2GZ4.cjs} +42 -42
  23. package/dist/{chunk-GQX6LFFA.cjs.map → chunk-5CJR2GZ4.cjs.map} +1 -1
  24. package/dist/{chunk-EUYIFALO.cjs → chunk-5EYRXSY5.cjs} +14 -14
  25. package/dist/{chunk-EUYIFALO.cjs.map → chunk-5EYRXSY5.cjs.map} +1 -1
  26. package/dist/{chunk-5RXKOGH6.cjs → chunk-5LMGFI4J.cjs} +7011 -4777
  27. package/dist/chunk-5LMGFI4J.cjs.map +1 -0
  28. package/dist/{chunk-KFQXO7QB.js → chunk-6GT557KN.js} +1 -1
  29. package/dist/chunk-6GT557KN.js.map +1 -0
  30. package/dist/{chunk-77DFPD35.js → chunk-BE6DK7QY.js} +12267 -10033
  31. package/dist/chunk-BE6DK7QY.js.map +1 -0
  32. package/dist/{chunk-A7IPON2V.cjs → chunk-BIKOJ4FH.cjs} +5 -5
  33. package/dist/{chunk-A7IPON2V.cjs.map → chunk-BIKOJ4FH.cjs.map} +1 -1
  34. package/dist/{chunk-HQYJINFV.js → chunk-CORIBKHG.js} +4 -4
  35. package/dist/{chunk-YEEHB4T6.cjs → chunk-CSYSYJCF.cjs} +40 -40
  36. package/dist/{chunk-YEEHB4T6.cjs.map → chunk-CSYSYJCF.cjs.map} +1 -1
  37. package/dist/{chunk-SIKCXKGB.cjs → chunk-DSDZ5T4P.cjs} +7 -7
  38. package/dist/{chunk-SIKCXKGB.cjs.map → chunk-DSDZ5T4P.cjs.map} +1 -1
  39. package/dist/{chunk-PNXNWGBY.js → chunk-GTWO5NAY.js} +2 -2
  40. package/dist/{chunk-SCCTJ2UI.js → chunk-HSACATCD.js} +4 -4
  41. package/dist/{chunk-PECGZWQR.cjs → chunk-KP5KRRD6.cjs} +3 -3
  42. package/dist/{chunk-PECGZWQR.cjs.map → chunk-KP5KRRD6.cjs.map} +1 -1
  43. package/dist/{chunk-2B6773QM.cjs → chunk-LILQJ3UZ.cjs} +1 -1
  44. package/dist/chunk-LILQJ3UZ.cjs.map +1 -0
  45. package/dist/{chunk-RZSA6MZL.cjs → chunk-NWX7MMF5.cjs} +15 -15
  46. package/dist/{chunk-RZSA6MZL.cjs.map → chunk-NWX7MMF5.cjs.map} +1 -1
  47. package/dist/{chunk-V3CNNMY3.cjs → chunk-P4WTZU6L.cjs} +18 -18
  48. package/dist/{chunk-V3CNNMY3.cjs.map → chunk-P4WTZU6L.cjs.map} +1 -1
  49. package/dist/{chunk-KLYHZRAD.js → chunk-PB3BTZDU.js} +2 -2
  50. package/dist/{chunk-FZOBQG6M.cjs → chunk-Q6E7VJ4W.cjs} +37 -36
  51. package/dist/chunk-Q6E7VJ4W.cjs.map +1 -0
  52. package/dist/{chunk-EYEIVYYF.cjs → chunk-QSCHCZL3.cjs} +7 -7
  53. package/dist/{chunk-EYEIVYYF.cjs.map → chunk-QSCHCZL3.cjs.map} +1 -1
  54. package/dist/{chunk-YJSKQ2ZV.js → chunk-RQLBHKXN.js} +3 -3
  55. package/dist/{chunk-CWQACI4Z.js → chunk-RW2LPL72.js} +2 -2
  56. package/dist/{chunk-DX6AOHNU.cjs → chunk-S2NHJDKQ.cjs} +77 -77
  57. package/dist/{chunk-DX6AOHNU.cjs.map → chunk-S2NHJDKQ.cjs.map} +1 -1
  58. package/dist/{chunk-AHDJMEEN.js → chunk-T2EGSZFN.js} +9 -8
  59. package/dist/chunk-T2EGSZFN.js.map +1 -0
  60. package/dist/{chunk-S52LJNMU.js → chunk-TKN5667N.js} +13 -13
  61. package/dist/chunk-TKN5667N.js.map +1 -0
  62. package/dist/{chunk-3BFX6HJM.js → chunk-UUYRJPGJ.js} +6 -6
  63. package/dist/{chunk-F6BYUO5U.js → chunk-VHG6AX5V.js} +3 -3
  64. package/dist/{chunk-FOQV6RCO.cjs → chunk-VQ523ORK.cjs} +29 -29
  65. package/dist/{chunk-FOQV6RCO.cjs.map → chunk-VQ523ORK.cjs.map} +1 -1
  66. package/dist/{chunk-PXF5J24T.js → chunk-XQ2B74SI.js} +2 -2
  67. package/dist/{chunk-H7JX2BG7.cjs → chunk-YHOIAA2E.cjs} +76 -76
  68. package/dist/chunk-YHOIAA2E.cjs.map +1 -0
  69. package/dist/{chunk-LLWBPPZ4.js → chunk-YZ2OIYQ3.js} +6 -6
  70. package/dist/components/case-studies/index.cjs +10 -10
  71. package/dist/components/case-studies/index.js +4 -4
  72. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  73. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  74. package/dist/components/chat/hooks/index.d.ts +0 -1
  75. package/dist/components/chat/hooks/index.d.ts.map +1 -1
  76. package/dist/components/chat/hooks/use-chat-history-hydration.d.ts +47 -0
  77. package/dist/components/chat/hooks/use-chat-history-hydration.d.ts.map +1 -0
  78. package/dist/components/chat/hooks/use-chat.d.ts +1 -0
  79. package/dist/components/chat/hooks/use-chat.d.ts.map +1 -1
  80. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +15 -48
  81. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  82. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +10 -53
  83. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  84. package/dist/components/chat/index.cjs +12 -4
  85. package/dist/components/chat/index.cjs.map +1 -1
  86. package/dist/components/chat/index.d.ts +1 -0
  87. package/dist/components/chat/index.d.ts.map +1 -1
  88. package/dist/components/chat/index.js +23 -15
  89. package/dist/components/chat/stream/chat-dialog-store.d.ts +184 -0
  90. package/dist/components/chat/stream/chat-dialog-store.d.ts.map +1 -0
  91. package/dist/components/chat/stream/chat-stream-reducer.d.ts +367 -0
  92. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -0
  93. package/dist/components/chat/stream/delta-batcher.d.ts +51 -0
  94. package/dist/components/chat/stream/delta-batcher.d.ts.map +1 -0
  95. package/dist/components/chat/stream/index.d.ts +17 -0
  96. package/dist/components/chat/stream/index.d.ts.map +1 -0
  97. package/dist/components/chat/stream/message-mutations.d.ts +77 -0
  98. package/dist/components/chat/stream/message-mutations.d.ts.map +1 -0
  99. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts +22 -0
  100. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts.map +1 -0
  101. package/dist/components/chat/types/api.types.d.ts +1 -81
  102. package/dist/components/chat/types/api.types.d.ts.map +1 -1
  103. package/dist/components/chat/types/processing.types.d.ts +2 -90
  104. package/dist/components/chat/types/processing.types.d.ts.map +1 -1
  105. package/dist/components/chat/types/unified-chat-state.types.d.ts +5 -0
  106. package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
  107. package/dist/components/chat/utils/chat-conversation-storage.d.ts +34 -0
  108. package/dist/components/chat/utils/chat-conversation-storage.d.ts.map +1 -0
  109. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts +34 -6
  110. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
  111. package/dist/components/chat/utils/index.d.ts +2 -3
  112. package/dist/components/chat/utils/index.d.ts.map +1 -1
  113. package/dist/components/chat/utils/process-historical-messages.d.ts +59 -11
  114. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  115. package/dist/components/contact/index.cjs +5 -5
  116. package/dist/components/contact/index.js +4 -4
  117. package/dist/components/docs/index.cjs +7 -7
  118. package/dist/components/docs/index.js +6 -6
  119. package/dist/components/embeds/index.cjs +5 -5
  120. package/dist/components/embeds/index.js +4 -4
  121. package/dist/components/faq/index.cjs +6 -6
  122. package/dist/components/faq/index.js +5 -5
  123. package/dist/components/features/index.cjs +4 -4
  124. package/dist/components/features/index.js +3 -3
  125. package/dist/components/help-center-pages/index.cjs +23 -23
  126. package/dist/components/help-center-pages/index.js +14 -14
  127. package/dist/components/index.cjs +182 -140
  128. package/dist/components/index.cjs.map +1 -1
  129. package/dist/components/index.js +66 -24
  130. package/dist/components/index.js.map +1 -1
  131. package/dist/components/navigation/index.cjs +4 -4
  132. package/dist/components/navigation/index.js +3 -3
  133. package/dist/components/onboarding-guides/index.cjs +8 -8
  134. package/dist/components/onboarding-guides/index.js +7 -7
  135. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  136. package/dist/components/related-content/index.cjs +6 -6
  137. package/dist/components/related-content/index.js +5 -5
  138. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  139. package/dist/components/tickets/index.cjs +7 -7
  140. package/dist/components/tickets/index.js +6 -6
  141. package/dist/components/ui/index.cjs +46 -4
  142. package/dist/components/ui/index.cjs.map +1 -1
  143. package/dist/components/ui/index.d.ts +1 -2
  144. package/dist/components/ui/index.d.ts.map +1 -1
  145. package/dist/components/ui/index.js +57 -15
  146. package/dist/components/ui/markdown/base-components.d.ts +51 -0
  147. package/dist/components/ui/markdown/base-components.d.ts.map +1 -0
  148. package/dist/components/ui/markdown/engine.d.ts +99 -0
  149. package/dist/components/ui/markdown/engine.d.ts.map +1 -0
  150. package/dist/components/ui/markdown/heading-ids.d.ts +65 -0
  151. package/dist/components/ui/markdown/heading-ids.d.ts.map +1 -0
  152. package/dist/components/ui/markdown/index.d.ts +18 -0
  153. package/dist/components/ui/markdown/index.d.ts.map +1 -0
  154. package/dist/components/ui/markdown/mermaid-diagram.d.ts +79 -0
  155. package/dist/components/ui/markdown/mermaid-diagram.d.ts.map +1 -0
  156. package/dist/components/ui/markdown/rich/embed-overrides.d.ts +14 -0
  157. package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +1 -0
  158. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts +40 -0
  159. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts.map +1 -0
  160. package/dist/components/ui/markdown/rich/shortcodes.d.ts +8 -0
  161. package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +1 -0
  162. package/dist/components/ui/markdown/sanitize.d.ts +153 -0
  163. package/dist/components/ui/markdown/sanitize.d.ts.map +1 -0
  164. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts +23 -0
  165. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts.map +1 -0
  166. package/dist/components/ui/markdown/streaming.d.ts +78 -0
  167. package/dist/components/ui/markdown/streaming.d.ts.map +1 -0
  168. package/dist/components/ui/markdown/text-size.d.ts +34 -0
  169. package/dist/components/ui/markdown/text-size.d.ts.map +1 -0
  170. package/dist/contexts/chat-runtime-context.d.ts +8 -0
  171. package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
  172. package/dist/contexts/index.cjs +2 -2
  173. package/dist/contexts/index.js +1 -1
  174. package/dist/hooks/index.cjs +3 -3
  175. package/dist/hooks/index.js +2 -2
  176. package/dist/index.cjs +62 -4
  177. package/dist/index.cjs.map +1 -1
  178. package/dist/index.js +73 -15
  179. package/dist/utils/index.cjs +169 -40
  180. package/dist/utils/index.cjs.map +1 -1
  181. package/dist/utils/index.d.ts +1 -0
  182. package/dist/utils/index.d.ts.map +1 -1
  183. package/dist/utils/index.js +162 -41
  184. package/dist/utils/index.js.map +1 -1
  185. package/dist/utils/markdown-fences.d.ts +42 -0
  186. package/dist/utils/markdown-fences.d.ts.map +1 -0
  187. package/dist/utils/markdown-heading-id.d.ts +85 -0
  188. package/dist/utils/markdown-heading-id.d.ts.map +1 -0
  189. package/dist/utils/markdown-section-extractor.d.ts.map +1 -1
  190. package/package.json +7 -1
  191. package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +307 -0
  192. package/src/chat-protocol/__tests__/chat-protocol.test.ts +516 -0
  193. package/src/chat-protocol/__tests__/env-flag.test.ts +52 -0
  194. package/src/chat-protocol/__tests__/ip-normalize.test.ts +136 -0
  195. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +228 -0
  196. package/src/chat-protocol/decode.ts +278 -0
  197. package/src/chat-protocol/encode.ts +71 -0
  198. package/src/chat-protocol/env-flag.ts +39 -0
  199. package/src/chat-protocol/events.ts +232 -0
  200. package/src/chat-protocol/frames.ts +245 -0
  201. package/src/chat-protocol/index.ts +21 -0
  202. package/src/chat-protocol/ip-normalize.ts +146 -0
  203. package/src/chat-protocol/nats-decoder.ts +252 -0
  204. package/src/components/chat/__tests__/chat-message-list.test.tsx +293 -5
  205. package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +207 -0
  206. package/src/components/chat/__tests__/chat-pending-turn.test.tsx +209 -0
  207. package/src/components/chat/chat-message-enhanced.tsx +111 -17
  208. package/src/components/chat/chat-message-list.tsx +358 -109
  209. package/src/components/chat/embeddable-chat.tsx +6 -0
  210. package/src/components/chat/hooks/.index.md +30 -33
  211. package/src/components/chat/hooks/.use-nats-chat-adapter.md +36 -55
  212. package/src/components/chat/hooks/__tests__/__snapshots__/conversation-id-persistence-golden.test.ts.snap +37 -0
  213. package/src/components/chat/hooks/__tests__/__snapshots__/sse-stream-golden.test.ts.snap +0 -0
  214. package/src/components/chat/hooks/__tests__/conversation-id-persistence-golden.test.ts +438 -0
  215. package/src/components/chat/hooks/__tests__/sse-stream-golden.test.ts +318 -0
  216. package/src/components/chat/hooks/index.ts +0 -1
  217. package/src/components/chat/hooks/use-chat-history-hydration.ts +142 -0
  218. package/src/components/chat/hooks/use-chat.ts +20 -0
  219. package/src/components/chat/hooks/use-nats-chat-adapter.ts +243 -823
  220. package/src/components/chat/hooks/use-sse-chat-adapter.ts +417 -774
  221. package/src/components/chat/index.ts +5 -0
  222. package/src/components/chat/stream/__tests__/__snapshots__/chat-stream-reducer-golden.test.ts.snap +940 -0
  223. package/src/components/chat/stream/__tests__/chat-dialog-store.test.ts +756 -0
  224. package/src/components/chat/stream/__tests__/chat-stream-reducer-golden.test.ts +460 -0
  225. package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +894 -0
  226. package/src/components/chat/stream/__tests__/delta-batcher.test.ts +255 -0
  227. package/src/components/chat/stream/__tests__/use-chat-stream-reducer.test.ts +98 -0
  228. package/src/components/chat/stream/chat-dialog-store.ts +536 -0
  229. package/src/components/chat/stream/chat-stream-reducer.ts +1796 -0
  230. package/src/components/chat/stream/delta-batcher.ts +159 -0
  231. package/src/components/chat/stream/index.ts +55 -0
  232. package/src/components/chat/stream/message-mutations.ts +338 -0
  233. package/src/components/chat/stream/use-chat-stream-reducer.ts +126 -0
  234. package/src/components/chat/thinking-display.tsx +1 -1
  235. package/src/components/chat/types/.api.types.md +39 -49
  236. package/src/components/chat/types/.processing.types.md +29 -58
  237. package/src/components/chat/types/api.types.ts +6 -73
  238. package/src/components/chat/types/processing.types.ts +11 -52
  239. package/src/components/chat/types/unified-chat-state.types.ts +6 -0
  240. package/src/components/chat/utils/.index.md +31 -40
  241. package/src/components/chat/utils/__tests__/__snapshots__/process-historical-messages-golden.test.ts.snap +420 -0
  242. package/src/components/chat/utils/__tests__/__snapshots__/segment-accumulator-golden.test.ts.snap +605 -0
  243. package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +317 -0
  244. package/src/components/chat/utils/__tests__/segment-accumulator-golden.test.ts +270 -0
  245. package/src/components/chat/utils/chat-conversation-storage.ts +83 -0
  246. package/src/components/chat/utils/extract-incomplete-message-state.ts +63 -5
  247. package/src/components/chat/utils/index.ts +5 -8
  248. package/src/components/chat/utils/process-historical-messages.ts +352 -384
  249. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +22 -4
  250. package/src/components/shared/legal-document/legal-document-page.tsx +1 -1
  251. package/src/components/shared/product-release/release-detail-page.tsx +19 -4
  252. package/src/components/ui/__tests__/__snapshots__/markdown-parity.test.tsx.snap +2420 -0
  253. package/src/components/ui/__tests__/markdown-parity.test.tsx +2528 -0
  254. package/src/components/ui/index.ts +1 -2
  255. package/src/components/ui/markdown/__tests__/mermaid-security.test.ts +115 -0
  256. package/src/components/ui/markdown/__tests__/mermaid-stale-render.test.tsx +95 -0
  257. package/src/components/ui/markdown/__tests__/sanitize-invariant.test.ts +309 -0
  258. package/src/components/ui/markdown/__tests__/streaming.test.tsx +388 -0
  259. package/src/components/ui/markdown/base-components.tsx +360 -0
  260. package/src/components/ui/markdown/engine.tsx +315 -0
  261. package/src/components/ui/markdown/heading-ids.ts +239 -0
  262. package/src/components/ui/markdown/index.ts +49 -0
  263. package/src/components/ui/markdown/mermaid-diagram.tsx +291 -0
  264. package/src/components/ui/markdown/rich/embed-overrides.tsx +199 -0
  265. package/src/components/ui/markdown/rich/rich-markdown-renderer.tsx +184 -0
  266. package/src/components/ui/markdown/rich/shortcodes.ts +170 -0
  267. package/src/components/ui/markdown/sanitize.ts +3499 -0
  268. package/src/components/ui/markdown/simple-markdown-renderer.tsx +28 -0
  269. package/src/components/ui/markdown/streaming.ts +362 -0
  270. package/src/components/ui/markdown/text-size.ts +106 -0
  271. package/src/components/ui/release-changelog-section.tsx +1 -1
  272. package/src/components/ui/ticket-info-section.tsx +1 -1
  273. package/src/contexts/chat-runtime-context.tsx +8 -0
  274. package/src/utils/index.ts +1 -0
  275. package/src/utils/markdown-fences.ts +127 -0
  276. package/src/utils/markdown-heading-id.ts +348 -0
  277. package/src/utils/markdown-section-extractor.ts +41 -56
  278. package/dist/chunk-2B6773QM.cjs.map +0 -1
  279. package/dist/chunk-5RXKOGH6.cjs.map +0 -1
  280. package/dist/chunk-77DFPD35.js.map +0 -1
  281. package/dist/chunk-AHDJMEEN.js.map +0 -1
  282. package/dist/chunk-FZOBQG6M.cjs.map +0 -1
  283. package/dist/chunk-H7JX2BG7.cjs.map +0 -1
  284. package/dist/chunk-KFQXO7QB.js.map +0 -1
  285. package/dist/chunk-S52LJNMU.js.map +0 -1
  286. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts +0 -6
  287. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +0 -1
  288. package/dist/components/chat/utils/chunk-parser.d.ts +0 -25
  289. package/dist/components/chat/utils/chunk-parser.d.ts.map +0 -1
  290. package/dist/components/ui/rich-markdown-renderer.d.ts +0 -34
  291. package/dist/components/ui/rich-markdown-renderer.d.ts.map +0 -1
  292. package/dist/components/ui/simple-markdown-renderer.d.ts +0 -73
  293. package/dist/components/ui/simple-markdown-renderer.d.ts.map +0 -1
  294. package/src/components/chat/hooks/.use-realtime-chunk-processor.md +0 -77
  295. package/src/components/chat/hooks/use-realtime-chunk-processor.ts +0 -479
  296. package/src/components/chat/utils/.chunk-parser.md +0 -62
  297. package/src/components/chat/utils/chunk-parser.ts +0 -256
  298. package/src/components/ui/.simple-markdown-renderer.md +0 -52
  299. package/src/components/ui/rich-markdown-renderer.tsx +0 -1223
  300. package/src/components/ui/simple-markdown-renderer.tsx +0 -964
  301. /package/dist/{chunk-AJNDDXKC.js.map → chunk-4NWARV5I.js.map} +0 -0
  302. /package/dist/{chunk-HQYJINFV.js.map → chunk-CORIBKHG.js.map} +0 -0
  303. /package/dist/{chunk-PNXNWGBY.js.map → chunk-GTWO5NAY.js.map} +0 -0
  304. /package/dist/{chunk-SCCTJ2UI.js.map → chunk-HSACATCD.js.map} +0 -0
  305. /package/dist/{chunk-KLYHZRAD.js.map → chunk-PB3BTZDU.js.map} +0 -0
  306. /package/dist/{chunk-YJSKQ2ZV.js.map → chunk-RQLBHKXN.js.map} +0 -0
  307. /package/dist/{chunk-CWQACI4Z.js.map → chunk-RW2LPL72.js.map} +0 -0
  308. /package/dist/{chunk-3BFX6HJM.js.map → chunk-UUYRJPGJ.js.map} +0 -0
  309. /package/dist/{chunk-F6BYUO5U.js.map → chunk-VHG6AX5V.js.map} +0 -0
  310. /package/dist/{chunk-PXF5J24T.js.map → chunk-XQ2B74SI.js.map} +0 -0
  311. /package/dist/{chunk-LLWBPPZ4.js.map → chunk-YZ2OIYQ3.js.map} +0 -0
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Heading-id derivation for the unified markdown engine.
3
+ *
4
+ * PURE BY CONSTRUCTION. The previous design was a stateful generator whose
5
+ * per-pass counters were reset in the engine's render body, and it was
6
+ * unsound in two independent ways:
7
+ * - memoized streaming blocks do NOT re-render on the pass that resets the
8
+ * counter, so mid-stream a document with two `## Setup` headings emitted
9
+ * `id="setup"` TWICE (block 0 kept its id; the re-rendered tail
10
+ * re-derived from an empty counter) — invalid HTML, and `#setup`
11
+ * resolved to the wrong node for the rest of the stream;
12
+ * - React StrictMode double-invokes render, and heading renderers are
13
+ * element types re-rendered independently of the parent that did the
14
+ * reset, so every id gained a `-2` suffix in dev and extractor↔renderer
15
+ * parity broke.
16
+ *
17
+ * Instead the ids are computed ONCE from the processed markdown source, in a
18
+ * `useMemo`, into a `line → id` map. The heading renderer is a pure lookup
19
+ * by `node.position.start.line`; nothing is derived from render order, so
20
+ * memo bails and StrictMode double-invocation are both no-ops.
21
+ *
22
+ * The slug algorithm itself lives in the server-safe SSOT
23
+ * `utils/markdown-heading-id` (shared with `utils/markdown-section-extractor`,
24
+ * the producer of `sectionIds`); the title normalization mirrors the
25
+ * extractor's `stripInlineMarkdown` so `## **Setup**` slugs identically on
26
+ * both sides.
27
+ */
28
+ import { createContext, useContext, useMemo, useRef } from 'react'
29
+ import {
30
+ createHeadingIdDeduper,
31
+ scanHeadings,
32
+ stripHeadingEmojis,
33
+ slugifyHeadingText,
34
+ } from '../../../utils/markdown-heading-id'
35
+ import { stripInlineMarkdown } from '../../../utils/markdown-to-plain'
36
+
37
+ export interface HeadingSection {
38
+ id: string
39
+ title: string
40
+ level: number
41
+ }
42
+
43
+ /** `line (1-based, document-wide) → heading id`. */
44
+ export type HeadingIdMap = ReadonlyMap<number, string>
45
+
46
+ /**
47
+ * Heading levels the section extractor sees (`extractSections`'s default
48
+ * `maxLevel`). The `section-N` fallback ordinal counts only these, so
49
+ * renderer and extractor agree on the fallback slug for symbol-only
50
+ * headings (see the fallback comment below).
51
+ */
52
+ const EXTRACTOR_MAX_LEVEL = 2
53
+
54
+ /**
55
+ * Line offset added to unit-relative hast positions. Non-zero only on the
56
+ * streaming path, where each atomic unit is parsed by its own
57
+ * `ReactMarkdown` (see `StreamingBlock.startLine`).
58
+ */
59
+ export const HeadingLineOffsetContext = createContext(0)
60
+
61
+ const EMPTY_HEADING_IDS: HeadingIdMap = new Map()
62
+
63
+ /**
64
+ * The document's heading-id map, delivered by CONTEXT rather than baked into
65
+ * the react-markdown `components` map on purpose: the map is rebuilt on every
66
+ * streamed token (its input is the growing `processedContent`), so listing it
67
+ * as a `components` memo dep would change that map's identity each token and
68
+ * make every completed streaming block's `memo` bail — the exact regression
69
+ * `NO_BROKEN_LINKS` was introduced to fix. Through context only the heading
70
+ * renderers re-render.
71
+ */
72
+ export const HeadingIdMapContext = createContext<HeadingIdMap>(EMPTY_HEADING_IDS)
73
+
74
+ /** Look up the pre-computed id for a heading hast node. Pure. */
75
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
+ export function useHeadingId(node: any): string | undefined {
77
+ const map = useContext(HeadingIdMapContext)
78
+ const offset = useContext(HeadingLineOffsetContext)
79
+ const line = node?.position?.start?.line
80
+ return typeof line === 'number' ? map.get(line + offset) : undefined
81
+ }
82
+
83
+ /**
84
+ * Build the document's `line → id` map. Pure: same content + same
85
+ * `sectionIds` always yields the same map.
86
+ */
87
+ export function buildHeadingIdMap(
88
+ content: string,
89
+ sectionIds?: HeadingSection[],
90
+ ): HeadingIdMap {
91
+ const sectionIdMap = new Map<string, string>()
92
+ if (sectionIds) {
93
+ for (const section of sectionIds) {
94
+ sectionIdMap.set(section.title, section.id)
95
+ sectionIdMap.set(section.title.toLowerCase(), section.id)
96
+ sectionIdMap.set(stripHeadingEmojis(section.title).toLowerCase(), section.id)
97
+ }
98
+ }
99
+
100
+ // Deduper AND scanner are the shared SSOT primitives the extractor also
101
+ // instantiates — see utils/markdown-heading-id.ts.
102
+ const dedupe = createHeadingIdDeduper()
103
+
104
+ const map = new Map<number, string>()
105
+ let sectionOrdinal = 0
106
+ let headingOrdinal = 0
107
+
108
+ for (const { line, level, text: rawText } of scanHeadings(content)) {
109
+ // Match what `extractText(children)` yields from the RENDERED inline
110
+ // nodes: emphasis/code/link syntax is markup, not text. (The extractor
111
+ // applies the identical strip via `stripFormattingMarkers`.)
112
+ const text = stripInlineMarkdown(rawText).trim()
113
+ headingOrdinal++
114
+ if (level <= EXTRACTOR_MAX_LEVEL) sectionOrdinal++
115
+
116
+ // Backend-provided ids win for H1/H2 (deep-link anchors). They go
117
+ // through the SAME dedup rather than trusting the backend to own
118
+ // uniqueness: `sectionIdMap` is keyed by heading TITLE, so two identical
119
+ // `## Setup` headings look up the same entry and would emit duplicate
120
+ // DOM ids. The dedup makes the second one `setup-2`, exactly matching
121
+ // what `extractSections` produces for the same document.
122
+ let assigned: string | undefined
123
+ if (sectionIds && level <= EXTRACTOR_MAX_LEVEL) {
124
+ for (const variation of [
125
+ text,
126
+ text.toLowerCase(),
127
+ stripHeadingEmojis(text),
128
+ stripHeadingEmojis(text).toLowerCase(),
129
+ ]) {
130
+ const id = sectionIdMap.get(variation)
131
+ if (id) {
132
+ assigned = dedupe(id)
133
+ break
134
+ }
135
+ }
136
+ }
137
+
138
+ if (assigned === undefined) {
139
+ const baseId = slugifyHeadingText(text)
140
+ // Symbol-only heading (slug collapses to ''). `extractSections` falls
141
+ // back to `section-${sections.length + 1}` — its ordinal among the
142
+ // headings it SEES, i.e. levels ≤ maxLevel. Mirror that exactly for
143
+ // H1/H2 so extractor↔renderer slugs stay in total agreement; deeper
144
+ // headings, which the extractor never emits, get their own
145
+ // `heading-N` namespace so they can never collide with a `section-N`.
146
+ assigned = dedupe(
147
+ baseId ||
148
+ (level <= EXTRACTOR_MAX_LEVEL
149
+ ? `section-${sectionOrdinal}`
150
+ : `heading-${headingOrdinal}`),
151
+ )
152
+ }
153
+
154
+ map.set(line, assigned)
155
+ }
156
+
157
+ return map
158
+ }
159
+
160
+ /** True when two id maps have identical entries (order-insensitive). */
161
+ function sameHeadingIdMap(a: HeadingIdMap, b: HeadingIdMap): boolean {
162
+ if (a === b) return true
163
+ if (a.size !== b.size) return false
164
+ for (const [line, id] of a) {
165
+ if (b.get(line) !== id) return false
166
+ }
167
+ return true
168
+ }
169
+
170
+ /**
171
+ * Memoized `buildHeadingIdMap` for the engine, with the map's IDENTITY held
172
+ * stable across content changes that don't touch any heading.
173
+ *
174
+ * `content` changes on every streamed token, so the `useMemo` alone rebuilt
175
+ * a fresh `Map` per token; that map is the context value, so EVERY heading
176
+ * consumer in every already-completed block re-rendered on every token —
177
+ * the same identity-churn class `NO_BROKEN_LINKS` exists to prevent, just
178
+ * one layer down. Rebuilding is cheap; re-rendering the consumers is not, so
179
+ * the newly built map is compared to the previous one and the previous
180
+ * object is returned when the entries match.
181
+ */
182
+ export function useHeadingIdMap(
183
+ content: string,
184
+ sectionIds?: HeadingSection[],
185
+ ): HeadingIdMap {
186
+ const previous = useRef<HeadingIdMap | null>(null)
187
+ return useMemo(() => {
188
+ const next = buildHeadingIdMap(content, sectionIds)
189
+ if (previous.current && sameHeadingIdMap(previous.current, next)) return previous.current
190
+ previous.current = next
191
+ return next
192
+ }, [content, sectionIds])
193
+ }
194
+
195
+ /**
196
+ * Fallback id for a heading the source scan could NOT see — in practice only
197
+ * a heading synthesized by a caller remark/rehype plugin, which carries no
198
+ * source position. (Setext, blockquote/list-nested ATX and multiple raw
199
+ * `<hN>`s on one line are all scanned now, so they no longer land here.)
200
+ *
201
+ * Routed through the SAME dedupe shape as the map so a fallback id can never
202
+ * collide with an assigned one. It stays PURE — `taken` is derived from the
203
+ * map, not from render order — which is why two position-less headings with
204
+ * IDENTICAL text still collide with each other: disambiguating those would
205
+ * require the shared mutable counter this module deleted.
206
+ */
207
+ export function resolveFallbackHeadingId(base: string, taken: ReadonlySet<string>): string {
208
+ if (!base || !taken.has(base)) return base
209
+ for (let n = 2; ; n++) {
210
+ const candidate = `${base}-${n}`
211
+ if (!taken.has(candidate)) return candidate
212
+ }
213
+ }
214
+
215
+ /** Cached `Set` of a map's assigned ids (one per map identity). */
216
+ const TAKEN_IDS_CACHE = new WeakMap<object, ReadonlySet<string>>()
217
+
218
+ /**
219
+ * The ids already assigned by the document's heading-id map. Cached per map
220
+ * IDENTITY, which `useHeadingIdMap` now keeps stable across heading-free
221
+ * token growth — so this is O(headings) once, not per heading per token.
222
+ */
223
+ export function useAssignedHeadingIds(): ReadonlySet<string> {
224
+ const map = useContext(HeadingIdMapContext)
225
+ const cached = TAKEN_IDS_CACHE.get(map as object)
226
+ if (cached) return cached
227
+ const set = new Set(map.values())
228
+ TAKEN_IDS_CACHE.set(map as object, set)
229
+ return set
230
+ }
231
+
232
+ /** Extract plain text from React children (headings receive mixed nodes). */
233
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
234
+ export function extractText(node: any): string {
235
+ if (typeof node === 'string') return node
236
+ if (Array.isArray(node)) return node.map(extractText).join('')
237
+ if (node?.props?.children) return extractText(node.props.children)
238
+ return ''
239
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Unified markdown module — public surface.
3
+ *
4
+ * ONE engine (`MarkdownEngine`), two named compositions:
5
+ * - `SimpleMarkdownRenderer` — chat/doc surfaces (embed-free bundle)
6
+ * - `RichMarkdownRenderer` — authored-content surfaces (shortcodes,
7
+ * social embeds, Video SSOT, OG previews)
8
+ */
9
+ export { MarkdownEngine, type MarkdownEngineProps } from './engine';
10
+ export {
11
+ SimpleMarkdownRenderer,
12
+ type SimpleMarkdownRendererProps,
13
+ type ResolveLinkResult,
14
+ } from './simple-markdown-renderer';
15
+ export {
16
+ RichMarkdownRenderer,
17
+ type RichMarkdownRendererProps,
18
+ } from './rich/rich-markdown-renderer';
19
+ export { processShortcodes } from './rich/shortcodes';
20
+ export {
21
+ SAFE_HTML_TAGS,
22
+ SVG_TAGS,
23
+ buildEffectiveTagSet,
24
+ buildSanitizeSchema,
25
+ cardAwareUrlTransform,
26
+ escapeUnknownHtmlTags,
27
+ rehypeStripUnsafe,
28
+ } from './sanitize';
29
+ export {
30
+ TEXT_SIZE_PRESETS,
31
+ resolveTextSizeConfig,
32
+ type TextSizeConfig,
33
+ type TextSizeClassMap,
34
+ type TextSizeElement,
35
+ type TextSizePreset,
36
+ } from './text-size';
37
+ export { MermaidDiagram } from './mermaid-diagram';
38
+ export {
39
+ buildHeadingIdMap,
40
+ useHeadingIdMap,
41
+ extractText,
42
+ type HeadingSection,
43
+ type HeadingIdMap,
44
+ } from './heading-ids';
45
+ // NOT exported: `mermaidStyles` (single-consumer, owned by MermaidDiagram)
46
+ // and `buildStandardLeafRenderers` (an internal SSOT — exporting it invites
47
+ // exactly the renderer-map fork this module exists to prevent; the rich
48
+ // composition imports it directly from ./base-components).
49
+ export { splitStreamingBlocks, completeStreamingTail, type StreamingBlock } from './streaming';
@@ -0,0 +1,291 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Single MermaidDiagram for the unified markdown engine (dark theme only —
5
+ * the old RichMarkdownRenderer's light-theme branch was dead code behind a
6
+ * hardcoded `isDarkMode = true` and is deleted, not carried over).
7
+ *
8
+ * `mermaid` stays a dynamic import so neither chat nor content bundles pay
9
+ * for it unless a diagram is actually rendered.
10
+ */
11
+ import React, { useEffect, useState } from 'react';
12
+ import type { MermaidConfig } from 'mermaid';
13
+ import { AlertCircleIcon } from '../../icons-v2-generated';
14
+
15
+ /**
16
+ * SECURITY SSOT for the mermaid renderer — the ONLY place these knobs are
17
+ * written. `./__tests__/mermaid-security.test.ts` imports THIS constant and
18
+ * spreads it into its own `mermaid.initialize`, so the fixture and the
19
+ * component can no longer drift: flipping `securityLevel` to `'loose'` here
20
+ * fails the suite (verified by doing exactly that).
21
+ *
22
+ * This renderer sits on the CHAT path, so the diagram source is MODEL output
23
+ * — untrusted by construction — and the rendered SVG goes through
24
+ * `dangerouslySetInnerHTML`. The pre-unification renderer used `'loose'`,
25
+ * which permits raw HTML inside labels and enables mermaid's `click`
26
+ * interaction directive: a node label like `A["<img src=x onerror=...>"]`
27
+ * would have produced LIVE HTML.
28
+ *
29
+ * - `securityLevel: 'strict'` encodes HTML tags in text and disables click
30
+ * handlers.
31
+ * - `htmlLabels: false` renders labels as SVG `<text>` instead of a
32
+ * foreignObject HTML subtree, so there is no HTML surface at all. Verified:
33
+ * no authored diagram in this repo or in the consuming hub's markdown uses
34
+ * HTML labels (not even `<br/>`), so 'antiscript' (which still allows tags)
35
+ * is not needed. (`htmlLabels` is set at the ROOT — `flowchart.htmlLabels`
36
+ * is deprecated in mermaid 11 and the root value takes precedence.)
37
+ * - `secure` is the allowlist of config keys a `%%{init}%%` directive in the
38
+ * diagram SOURCE may NOT override. mermaid's default list covers
39
+ * `securityLevel` but NOT `htmlLabels`, so model-authored source could
40
+ * otherwise re-enable HTML labels while `securityLevel` stayed locked.
41
+ * Adding `htmlLabels` (plus `secure` itself and the resource limits) closes
42
+ * that hole.
43
+ *
44
+ * jsdom NOTE: with `htmlLabels` unlocked, a `%%{init: {"htmlLabels": true}}%%`
45
+ * directive was observed to make `mermaid.render` never settle under jsdom
46
+ * (>60s, against a passing two-render control). This was NOT reproduced in a
47
+ * real browser and may well be an artifact of jsdom having no layout — do not
48
+ * read it as a confirmed browser DoS. Either way the render below is wrapped
49
+ * in a timeout so a non-settling render surfaces the error state instead of
50
+ * sitting on "Rendering diagram…" forever.
51
+ */
52
+ export const MERMAID_SECURITY_OPTIONS = {
53
+ htmlLabels: false,
54
+ securityLevel: 'strict',
55
+ secure: ['securityLevel', 'htmlLabels', 'secure', 'startOnLoad', 'maxTextSize', 'maxEdges'],
56
+ } satisfies Pick<MermaidConfig, 'htmlLabels' | 'securityLevel' | 'secure'>;
57
+
58
+ /** Upper bound on a single `mermaid.render`. Generous enough that no honest
59
+ * diagram hits it; short enough that a wedged render becomes a visible error
60
+ * instead of a permanent skeleton. */
61
+ export const MERMAID_RENDER_TIMEOUT_MS = 15_000;
62
+
63
+ /** `Promise.race` with a rejecting timer, timer always cleared. Kept local —
64
+ * the only caller is the render below. */
65
+ async function withRenderTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
66
+ let timer: ReturnType<typeof setTimeout> | undefined;
67
+ try {
68
+ return await Promise.race([
69
+ promise,
70
+ new Promise<never>((_resolve, reject) => {
71
+ timer = setTimeout(() => reject(new Error(`Diagram rendering timed out after ${ms}ms`)), ms);
72
+ }),
73
+ ]);
74
+ } finally {
75
+ if (timer !== undefined) clearTimeout(timer);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * ODS-TOKENS FLAG (ODS_TOKEN_RULES §Colors / §Typography / §General):
81
+ * `mermaidStyles` below and the `themeVariables` / `fontFamily` / `fontSize`
82
+ * block in `MermaidDiagram` carry RAW hex colors, a literal font-family and
83
+ * literal px font sizes.
84
+ *
85
+ * Reason: mermaid's configuration is not CSS — `themeVariables` is a
86
+ * JavaScript API whose values are baked into the generated SVG as literal
87
+ * attribute strings, so a `var(--color-…)` reference resolves to nothing
88
+ * there. On top of that, ODS has NO 10-step categorical color ramp, and
89
+ * mermaid's `cScale0…cScale9` requires exactly one. The palette is carried
90
+ * over VERBATIM from the pre-unification RichMarkdownRenderer, so this is
91
+ * parity, not new divergence; the missing categorical-ramp tokens are
92
+ * flagged here for addition to ODS.
93
+ *
94
+ * Scope: this exemption covers THIS file only, and only the mermaid config
95
+ * surface. Do NOT copy this pattern — every other style in the markdown
96
+ * module uses ODS semantic classes.
97
+ */
98
+ export const mermaidStyles = `
99
+ .mermaid-svg-container svg {
100
+ max-width: 100% !important;
101
+ height: auto !important;
102
+ min-height: 200px;
103
+ font-family: 'DM Sans', sans-serif !important;
104
+ font-size: 14px !important;
105
+ }
106
+ @media (min-width: 1520px) {
107
+ .mermaid-svg-container svg {
108
+ max-width: 900px !important;
109
+ max-height: 700px !important;
110
+ min-height: 300px;
111
+ font-size: 16px !important;
112
+ }
113
+ }
114
+ @media (min-width: 768px) and (max-width: 1519px) {
115
+ .mermaid-svg-container svg {
116
+ max-width: 700px !important;
117
+ max-height: 600px !important;
118
+ min-height: 250px;
119
+ font-size: 15px !important;
120
+ }
121
+ }
122
+ @media (max-width: 767px) {
123
+ .mermaid-svg-container svg {
124
+ max-width: 90vw !important;
125
+ max-height: 400px !important;
126
+ min-height: 200px;
127
+ font-size: 13px !important;
128
+ }
129
+ }
130
+ .mermaid-svg-container svg[width] { width: 100% !important; }
131
+ .mermaid-svg-container .node rect,
132
+ .mermaid-svg-container .node circle,
133
+ .mermaid-svg-container .node ellipse,
134
+ .mermaid-svg-container .node polygon { stroke-width: 2px !important; }
135
+ .mermaid-svg-container .edgePath path { stroke-width: 2px !important; }
136
+ @media (min-width: 768px) {
137
+ .mermaid-svg-container .node text,
138
+ .mermaid-svg-container .edgeLabel text { font-size: 14px !important; }
139
+ }
140
+ @media (min-width: 1520px) {
141
+ .mermaid-svg-container .node text,
142
+ .mermaid-svg-container .edgeLabel text { font-size: 16px !important; }
143
+ }
144
+ `;
145
+
146
+ /** Monotonic render id. `Date.now()` was ambiguous: two renders started in the
147
+ * same millisecond (routine while a diagram streams in) share an id, and
148
+ * mermaid removes any pre-existing element with that id at the start of a
149
+ * render — so one render would delete the other's working container. */
150
+ let mermaidRenderSeq = 0;
151
+
152
+ export const MermaidDiagram: React.FC<{ chart: string }> = ({ chart }) => {
153
+ const [svg, setSvg] = useState<string>('');
154
+ const [error, setError] = useState<string>('');
155
+ const [isLoading, setIsLoading] = useState<boolean>(true);
156
+ const [mounted, setMounted] = useState(false);
157
+
158
+ useEffect(() => { setMounted(true); }, []);
159
+
160
+ useEffect(() => {
161
+ // This effect re-runs on every `chart` change, and during STREAMING the
162
+ // chart text grows chunk by chunk — so several `mermaid.render` calls are
163
+ // in flight at once. Without this flag a slower EARLIER render can resolve
164
+ // last and overwrite the newer output (or paint an error for a chart that
165
+ // is no longer displayed). The timeout above bounds that window at 15s but
166
+ // cannot close it: `Promise.race` rejects, it does not abort the render.
167
+ let cancelled = false;
168
+ const renderId = `mermaid-${(mermaidRenderSeq += 1)}`;
169
+
170
+ const renderMermaid = async () => {
171
+ try {
172
+ setIsLoading(true);
173
+ // `mermaid` is a declared `dependencies` entry and ships its own
174
+ // types, so this specifier resolves — the suppression that used to sit
175
+ // here (`@ts-ignore`, justified as "optional runtime dependency") was
176
+ // dead AND factually wrong. It is kept as a DYNAMIC import purely for
177
+ // bundle size: neither the chat nor the content bundle should pay for
178
+ // mermaid unless a diagram actually renders. Import failures surface
179
+ // through the surrounding try/catch as the diagram error state.
180
+ const { default: mermaid } = await import('mermaid');
181
+
182
+ mermaid.initialize({
183
+ startOnLoad: false,
184
+ theme: 'dark' as const,
185
+ themeVariables: {
186
+ primaryColor: '#FFC008',
187
+ primaryTextColor: '#FAFAFA',
188
+ primaryBorderColor: '#3A3A3A',
189
+ lineColor: '#888888',
190
+ secondaryColor: '#212121',
191
+ tertiaryColor: '#2A2A2A',
192
+ background: 'transparent',
193
+ mainBkg: 'transparent',
194
+ secondBkg: 'transparent',
195
+ tertiaryBkg: 'transparent',
196
+ cScale0: '#FFC008', cScale1: '#4ECDC4', cScale2: '#45B7D1',
197
+ cScale3: '#96CEB4', cScale4: '#FFEAA7', cScale5: '#DDA0DD',
198
+ cScale6: '#98D8C8', cScale7: '#F7DC6F', cScale8: '#BB8FCE',
199
+ cScale9: '#85C1E9',
200
+ taskTextColor: '#FAFAFA',
201
+ taskTextOutsideColor: '#FAFAFA',
202
+ activeTaskTextColor: '#1A1A1A',
203
+ nodeTextColor: '#FAFAFA',
204
+ },
205
+ flowchart: { useMaxWidth: true, rankSpacing: 50, nodeSpacing: 30, curve: 'basis' },
206
+ sequence: { useMaxWidth: true, width: 150 },
207
+ pie: { useMaxWidth: true, useWidth: undefined },
208
+ fontFamily: 'DM Sans, sans-serif',
209
+ fontSize: 14,
210
+ // SECURITY: single source of truth, shared with the fixture.
211
+ // See MERMAID_SECURITY_OPTIONS above and
212
+ // ./__tests__/mermaid-security.test.ts.
213
+ ...MERMAID_SECURITY_OPTIONS,
214
+ });
215
+
216
+ const { svg: renderedSvg } = await withRenderTimeout(
217
+ mermaid.render(renderId, chart),
218
+ MERMAID_RENDER_TIMEOUT_MS,
219
+ );
220
+ if (cancelled) return;
221
+ setSvg(renderedSvg);
222
+ setIsLoading(false);
223
+ } catch (err) {
224
+ if (cancelled) return;
225
+ console.error('Mermaid rendering error:', err);
226
+ setError(`Failed to render diagram: ${err instanceof Error ? err.message : 'Unknown error'}`);
227
+ setIsLoading(false);
228
+ }
229
+ };
230
+
231
+ if (mounted) { renderMermaid(); }
232
+ return () => {
233
+ cancelled = true;
234
+ // An abandoned render (superseded chart, or one wedged past the timeout)
235
+ // keeps mermaid's temporary `#d<id>` working container attached to
236
+ // <body> forever, since mermaid only removes it on a successful finish.
237
+ // Cheap to reap here because the id is ours. If the render is still live
238
+ // it will fail on the missing node — caught, and discarded by `cancelled`.
239
+ document.getElementById(`d${renderId}`)?.remove();
240
+ document.getElementById(renderId)?.remove();
241
+ };
242
+ }, [chart, mounted]);
243
+
244
+ if (error) {
245
+ return (
246
+ <div className="error-state bg-ods-card border border-ods-border rounded-lg p-6 my-6">
247
+ <div className="error-icon flex justify-center mb-4">
248
+ <AlertCircleIcon className="w-12 h-12 text-ods-error" />
249
+ </div>
250
+ <div className="error-title text-center font-sans font-semibold text-lg text-ods-error mb-2">
251
+ Diagram Error
252
+ </div>
253
+ <div className="error-description text-center font-sans text-sm text-ods-text-secondary mb-4 break-words overflow-hidden max-w-full">
254
+ <div className="overflow-x-auto">
255
+ <pre className="whitespace-pre-wrap break-words text-xs">{error}</pre>
256
+ </div>
257
+ </div>
258
+ </div>
259
+ );
260
+ }
261
+
262
+ if (isLoading || !svg) {
263
+ return (
264
+ <div className="skeleton-code bg-ods-card border border-ods-border rounded-lg p-6 min-h-[120px] flex items-center justify-center">
265
+ <div className="animate-pulse text-ods-text-tertiary font-sans">
266
+ {isLoading ? 'Loading diagram renderer...' : 'Rendering diagram...'}
267
+ </div>
268
+ </div>
269
+ );
270
+ }
271
+
272
+ return (
273
+ <div className="mermaid-container rounded-lg p-4 md:p-6 lg:p-8 my-6 overflow-x-auto bg-ods-card border border-ods-border">
274
+ {/* Scoped to `.mermaid-svg-container`, so it only needs to exist when a
275
+ diagram is actually mounted. The engine used to emit this once per
276
+ instance — i.e. once per chat segment, almost never with a diagram. */}
277
+ <style dangerouslySetInnerHTML={{ __html: mermaidStyles }} />
278
+ <div className="flex justify-center items-center w-full min-h-[200px] md:min-h-[250px] lg:min-h-[300px]">
279
+ <div
280
+ className="mermaid-svg-container w-full flex justify-center max-w-full"
281
+ style={{ fontSize: '14px' }}
282
+ dangerouslySetInnerHTML={{
283
+ __html: svg.replace(/<svg[^>]*>/, (match) =>
284
+ match.replace(/width="[^"]*"/, 'width="100%"').replace(/height="[^"]*"/, 'height="auto"')
285
+ ),
286
+ }}
287
+ />
288
+ </div>
289
+ </div>
290
+ );
291
+ };