@flamingo-stack/openframe-frontend-core 0.0.508 → 0.0.509

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 (306) 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 +216 -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 +506 -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 +490 -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-3SQ5KXHQ.cjs → chunk-2T4GTV25.cjs} +9 -9
  22. package/dist/{chunk-3SQ5KXHQ.cjs.map → chunk-2T4GTV25.cjs.map} +1 -1
  23. package/dist/{chunk-CWAOV2FK.cjs → chunk-3CGZPAGR.cjs} +3 -3
  24. package/dist/{chunk-CWAOV2FK.cjs.map → chunk-3CGZPAGR.cjs.map} +1 -1
  25. package/dist/{chunk-TLMJHMXJ.js → chunk-3QSR22IC.js} +12 -12
  26. package/dist/chunk-3QSR22IC.js.map +1 -0
  27. package/dist/{chunk-TWYNR4TZ.cjs → chunk-4G5TLHJD.cjs} +7141 -4799
  28. package/dist/chunk-4G5TLHJD.cjs.map +1 -0
  29. package/dist/{chunk-ADPMHWOE.js → chunk-72XAID7Y.js} +4 -4
  30. package/dist/{chunk-PAGKRNWK.js → chunk-7WZHBQ4J.js} +2 -2
  31. package/dist/{chunk-OCEKO5CW.js → chunk-AEELJC4N.js} +12106 -9764
  32. package/dist/chunk-AEELJC4N.js.map +1 -0
  33. package/dist/{chunk-Z42BGM6Q.cjs → chunk-BT2WWDFP.cjs} +5 -5
  34. package/dist/{chunk-Z42BGM6Q.cjs.map → chunk-BT2WWDFP.cjs.map} +1 -1
  35. package/dist/{chunk-7LEL3RIX.cjs → chunk-C7OY3PQP.cjs} +11 -11
  36. package/dist/{chunk-7LEL3RIX.cjs.map → chunk-C7OY3PQP.cjs.map} +1 -1
  37. package/dist/{chunk-RHGGEKPQ.cjs → chunk-CIOLOQKQ.cjs} +4 -4
  38. package/dist/{chunk-RHGGEKPQ.cjs.map → chunk-CIOLOQKQ.cjs.map} +1 -1
  39. package/dist/{chunk-TSZHM74B.js → chunk-DC2TKS7C.js} +2 -2
  40. package/dist/{chunk-M2GOR3XQ.cjs → chunk-DYPYR7FR.cjs} +87 -87
  41. package/dist/{chunk-M2GOR3XQ.cjs.map → chunk-DYPYR7FR.cjs.map} +1 -1
  42. package/dist/{chunk-I64ABCDX.js → chunk-I2B6X77L.js} +2 -2
  43. package/dist/{chunk-NIZDKTGL.cjs → chunk-K5MSCMDK.cjs} +37 -37
  44. package/dist/{chunk-NIZDKTGL.cjs.map → chunk-K5MSCMDK.cjs.map} +1 -1
  45. package/dist/{chunk-DTYRYB2N.js → chunk-K6QTZ7CZ.js} +2 -2
  46. package/dist/{chunk-4LTMXDUS.js → chunk-KBN7PMFT.js} +2 -2
  47. package/dist/{chunk-TBMV7I5N.js → chunk-KXZO2WZX.js} +2 -2
  48. package/dist/{chunk-QTYZMP6D.cjs → chunk-LBAKLT6T.cjs} +62 -62
  49. package/dist/chunk-LBAKLT6T.cjs.map +1 -0
  50. package/dist/{chunk-KEBYLU3U.js → chunk-LFUPI7VX.js} +2 -2
  51. package/dist/{chunk-LDTR4IVZ.cjs → chunk-M37PBG4N.cjs} +31 -31
  52. package/dist/{chunk-LDTR4IVZ.cjs.map → chunk-M37PBG4N.cjs.map} +1 -1
  53. package/dist/{chunk-WA7RR64F.js → chunk-PO5O654H.js} +6 -5
  54. package/dist/chunk-PO5O654H.js.map +1 -0
  55. package/dist/{chunk-WK4N5VBX.cjs → chunk-QJJISLPA.cjs} +28 -27
  56. package/dist/chunk-QJJISLPA.cjs.map +1 -0
  57. package/dist/{chunk-2F44PLTV.cjs → chunk-T7MCSA55.cjs} +26 -26
  58. package/dist/{chunk-2F44PLTV.cjs.map → chunk-T7MCSA55.cjs.map} +1 -1
  59. package/dist/{chunk-WJQHJD7J.js → chunk-TWSKTJHW.js} +2 -2
  60. package/dist/{chunk-ZSQHZYCO.cjs → chunk-UQDMJ6DH.cjs} +13 -13
  61. package/dist/{chunk-ZSQHZYCO.cjs.map → chunk-UQDMJ6DH.cjs.map} +1 -1
  62. package/dist/{chunk-TZRUCD56.js → chunk-V3ZAAFE3.js} +5 -5
  63. package/dist/{chunk-LRGHJPET.js → chunk-XGIFOHKE.js} +2 -2
  64. package/dist/{chunk-POOMO3PA.cjs → chunk-YQEPRYT5.cjs} +7 -7
  65. package/dist/{chunk-POOMO3PA.cjs.map → chunk-YQEPRYT5.cjs.map} +1 -1
  66. package/dist/components/case-studies/index.cjs +8 -8
  67. package/dist/components/case-studies/index.js +2 -2
  68. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  69. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  70. package/dist/components/chat/hooks/index.d.ts +0 -1
  71. package/dist/components/chat/hooks/index.d.ts.map +1 -1
  72. package/dist/components/chat/hooks/use-chat.d.ts.map +1 -1
  73. package/dist/components/chat/hooks/use-chunk-catchup.d.ts.map +1 -1
  74. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +15 -48
  75. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  76. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +10 -57
  77. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  78. package/dist/components/chat/index.cjs +10 -2
  79. package/dist/components/chat/index.cjs.map +1 -1
  80. package/dist/components/chat/index.d.ts +1 -0
  81. package/dist/components/chat/index.d.ts.map +1 -1
  82. package/dist/components/chat/index.js +21 -13
  83. package/dist/components/chat/stream/chat-dialog-store.d.ts +184 -0
  84. package/dist/components/chat/stream/chat-dialog-store.d.ts.map +1 -0
  85. package/dist/components/chat/stream/chat-stream-reducer.d.ts +380 -0
  86. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -0
  87. package/dist/components/chat/stream/delta-batcher.d.ts +51 -0
  88. package/dist/components/chat/stream/delta-batcher.d.ts.map +1 -0
  89. package/dist/components/chat/stream/index.d.ts +17 -0
  90. package/dist/components/chat/stream/index.d.ts.map +1 -0
  91. package/dist/components/chat/stream/message-mutations.d.ts +77 -0
  92. package/dist/components/chat/stream/message-mutations.d.ts.map +1 -0
  93. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts +22 -0
  94. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts.map +1 -0
  95. package/dist/components/chat/types/api.types.d.ts +1 -81
  96. package/dist/components/chat/types/api.types.d.ts.map +1 -1
  97. package/dist/components/chat/types/processing.types.d.ts +2 -93
  98. package/dist/components/chat/types/processing.types.d.ts.map +1 -1
  99. package/dist/components/chat/types/unified-chat-state.types.d.ts +15 -0
  100. package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
  101. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts +34 -6
  102. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
  103. package/dist/components/chat/utils/history-merge.d.ts.map +1 -1
  104. package/dist/components/chat/utils/index.d.ts +2 -3
  105. package/dist/components/chat/utils/index.d.ts.map +1 -1
  106. package/dist/components/chat/utils/process-historical-messages.d.ts +59 -11
  107. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  108. package/dist/components/contact/index.cjs +3 -3
  109. package/dist/components/contact/index.js +2 -2
  110. package/dist/components/docs/index.cjs +5 -5
  111. package/dist/components/docs/index.js +4 -4
  112. package/dist/components/embeds/index.cjs +3 -3
  113. package/dist/components/embeds/index.js +2 -2
  114. package/dist/components/faq/index.cjs +3 -3
  115. package/dist/components/faq/index.js +2 -2
  116. package/dist/components/features/index.cjs +2 -2
  117. package/dist/components/features/index.js +1 -1
  118. package/dist/components/help-center-pages/index.cjs +22 -22
  119. package/dist/components/help-center-pages/index.js +13 -13
  120. package/dist/components/index.cjs +174 -132
  121. package/dist/components/index.cjs.map +1 -1
  122. package/dist/components/index.js +65 -23
  123. package/dist/components/index.js.map +1 -1
  124. package/dist/components/meeting-scheduler/index.cjs +34 -34
  125. package/dist/components/meeting-scheduler/index.js +3 -3
  126. package/dist/components/navigation/index.cjs +2 -2
  127. package/dist/components/navigation/index.js +1 -1
  128. package/dist/components/onboarding-guides/index.cjs +5 -5
  129. package/dist/components/onboarding-guides/index.js +4 -4
  130. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  131. package/dist/components/related-content/index.cjs +3 -3
  132. package/dist/components/related-content/index.js +2 -2
  133. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  134. package/dist/components/tickets/index.cjs +6 -6
  135. package/dist/components/tickets/index.js +5 -5
  136. package/dist/components/ui/index.cjs +44 -2
  137. package/dist/components/ui/index.cjs.map +1 -1
  138. package/dist/components/ui/index.d.ts +1 -2
  139. package/dist/components/ui/index.d.ts.map +1 -1
  140. package/dist/components/ui/index.js +55 -13
  141. package/dist/components/ui/markdown/base-components.d.ts +51 -0
  142. package/dist/components/ui/markdown/base-components.d.ts.map +1 -0
  143. package/dist/components/ui/markdown/engine.d.ts +99 -0
  144. package/dist/components/ui/markdown/engine.d.ts.map +1 -0
  145. package/dist/components/ui/markdown/heading-ids.d.ts +65 -0
  146. package/dist/components/ui/markdown/heading-ids.d.ts.map +1 -0
  147. package/dist/components/ui/markdown/index.d.ts +18 -0
  148. package/dist/components/ui/markdown/index.d.ts.map +1 -0
  149. package/dist/components/ui/markdown/mermaid-diagram.d.ts +79 -0
  150. package/dist/components/ui/markdown/mermaid-diagram.d.ts.map +1 -0
  151. package/dist/components/ui/markdown/rich/embed-overrides.d.ts +14 -0
  152. package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +1 -0
  153. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts +40 -0
  154. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts.map +1 -0
  155. package/dist/components/ui/markdown/rich/shortcodes.d.ts +8 -0
  156. package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +1 -0
  157. package/dist/components/ui/markdown/sanitize.d.ts +153 -0
  158. package/dist/components/ui/markdown/sanitize.d.ts.map +1 -0
  159. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts +23 -0
  160. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts.map +1 -0
  161. package/dist/components/ui/markdown/streaming.d.ts +78 -0
  162. package/dist/components/ui/markdown/streaming.d.ts.map +1 -0
  163. package/dist/components/ui/markdown/text-size.d.ts +34 -0
  164. package/dist/components/ui/markdown/text-size.d.ts.map +1 -0
  165. package/dist/index.cjs +60 -2
  166. package/dist/index.cjs.map +1 -1
  167. package/dist/index.js +71 -13
  168. package/dist/utils/index.cjs +169 -40
  169. package/dist/utils/index.cjs.map +1 -1
  170. package/dist/utils/index.d.ts +1 -0
  171. package/dist/utils/index.d.ts.map +1 -1
  172. package/dist/utils/index.js +162 -41
  173. package/dist/utils/index.js.map +1 -1
  174. package/dist/utils/markdown-fences.d.ts +42 -0
  175. package/dist/utils/markdown-fences.d.ts.map +1 -0
  176. package/dist/utils/markdown-heading-id.d.ts +85 -0
  177. package/dist/utils/markdown-heading-id.d.ts.map +1 -0
  178. package/dist/utils/markdown-section-extractor.d.ts.map +1 -1
  179. package/package.json +7 -1
  180. package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +319 -0
  181. package/src/chat-protocol/__tests__/chat-protocol.test.ts +516 -0
  182. package/src/chat-protocol/__tests__/env-flag.test.ts +52 -0
  183. package/src/chat-protocol/__tests__/ip-normalize.test.ts +136 -0
  184. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +237 -0
  185. package/src/chat-protocol/decode.ts +278 -0
  186. package/src/chat-protocol/encode.ts +71 -0
  187. package/src/chat-protocol/env-flag.ts +39 -0
  188. package/src/chat-protocol/events.ts +254 -0
  189. package/src/chat-protocol/frames.ts +245 -0
  190. package/src/chat-protocol/index.ts +21 -0
  191. package/src/chat-protocol/ip-normalize.ts +146 -0
  192. package/src/chat-protocol/nats-decoder.ts +264 -0
  193. package/src/components/chat/.chat-message-list.md +10 -6
  194. package/src/components/chat/__tests__/chat-message-list.test.tsx +450 -9
  195. package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +207 -0
  196. package/src/components/chat/__tests__/chat-pending-turn.test.tsx +250 -0
  197. package/src/components/chat/chat-message-enhanced.tsx +122 -20
  198. package/src/components/chat/chat-message-list.tsx +515 -133
  199. package/src/components/chat/embeddable-chat.tsx +6 -0
  200. package/src/components/chat/hooks/.index.md +30 -33
  201. package/src/components/chat/hooks/.use-nats-chat-adapter.md +36 -55
  202. package/src/components/chat/hooks/__tests__/__snapshots__/conversation-id-persistence-golden.test.ts.snap +37 -0
  203. package/src/components/chat/hooks/__tests__/__snapshots__/sse-stream-golden.test.ts.snap +0 -0
  204. package/src/components/chat/hooks/__tests__/chunk-catchup-dialog-staleness.test.ts +92 -0
  205. package/src/components/chat/hooks/__tests__/conversation-id-persistence-golden.test.ts +438 -0
  206. package/src/components/chat/hooks/__tests__/sse-stream-golden.test.ts +318 -0
  207. package/src/components/chat/hooks/index.ts +0 -1
  208. package/src/components/chat/hooks/use-chat.ts +8 -0
  209. package/src/components/chat/hooks/use-chunk-catchup.ts +26 -1
  210. package/src/components/chat/hooks/use-nats-chat-adapter.ts +254 -825
  211. package/src/components/chat/hooks/use-sse-chat-adapter.ts +380 -684
  212. package/src/components/chat/index.ts +5 -0
  213. package/src/components/chat/stream/__tests__/__snapshots__/chat-stream-reducer-golden.test.ts.snap +944 -0
  214. package/src/components/chat/stream/__tests__/chat-dialog-store.test.ts +806 -0
  215. package/src/components/chat/stream/__tests__/chat-stream-reducer-golden.test.ts +467 -0
  216. package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +1330 -0
  217. package/src/components/chat/stream/__tests__/delta-batcher.test.ts +255 -0
  218. package/src/components/chat/stream/__tests__/use-chat-stream-reducer.test.ts +105 -0
  219. package/src/components/chat/stream/chat-dialog-store.ts +536 -0
  220. package/src/components/chat/stream/chat-stream-reducer.ts +1928 -0
  221. package/src/components/chat/stream/delta-batcher.ts +168 -0
  222. package/src/components/chat/stream/index.ts +55 -0
  223. package/src/components/chat/stream/message-mutations.ts +340 -0
  224. package/src/components/chat/stream/use-chat-stream-reducer.ts +126 -0
  225. package/src/components/chat/thinking-display.tsx +1 -1
  226. package/src/components/chat/types/.api.types.md +39 -49
  227. package/src/components/chat/types/.processing.types.md +29 -58
  228. package/src/components/chat/types/.unified-chat-state.types.md +2 -2
  229. package/src/components/chat/types/api.types.ts +6 -73
  230. package/src/components/chat/types/processing.types.ts +11 -53
  231. package/src/components/chat/types/unified-chat-state.types.ts +16 -0
  232. package/src/components/chat/utils/.extract-incomplete-message-state.md +21 -3
  233. package/src/components/chat/utils/.history-merge.md +44 -3
  234. package/src/components/chat/utils/.index.md +31 -40
  235. package/src/components/chat/utils/__tests__/__snapshots__/process-historical-messages-golden.test.ts.snap +420 -0
  236. package/src/components/chat/utils/__tests__/__snapshots__/segment-accumulator-golden.test.ts.snap +605 -0
  237. package/src/components/chat/utils/__tests__/extract-incomplete-message-state.test.ts +106 -0
  238. package/src/components/chat/utils/__tests__/history-merge.test.ts +160 -0
  239. package/src/components/chat/utils/__tests__/process-historical-messages-approvals.test.ts +61 -0
  240. package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +394 -0
  241. package/src/components/chat/utils/__tests__/segment-accumulator-golden.test.ts +270 -0
  242. package/src/components/chat/utils/extract-incomplete-message-state.ts +93 -6
  243. package/src/components/chat/utils/history-merge.ts +85 -5
  244. package/src/components/chat/utils/index.ts +5 -8
  245. package/src/components/chat/utils/process-historical-messages.ts +361 -385
  246. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +22 -4
  247. package/src/components/shared/legal-document/legal-document-page.tsx +1 -1
  248. package/src/components/shared/product-release/release-detail-page.tsx +19 -4
  249. package/src/components/ui/__tests__/__snapshots__/markdown-parity.test.tsx.snap +2420 -0
  250. package/src/components/ui/__tests__/markdown-parity.test.tsx +2540 -0
  251. package/src/components/ui/index.ts +1 -2
  252. package/src/components/ui/markdown/__tests__/mermaid-security.test.ts +176 -0
  253. package/src/components/ui/markdown/__tests__/mermaid-stale-render.test.tsx +151 -0
  254. package/src/components/ui/markdown/__tests__/sanitize-invariant.test.ts +354 -0
  255. package/src/components/ui/markdown/__tests__/sanitize-render.test.tsx +118 -0
  256. package/src/components/ui/markdown/__tests__/streaming.test.tsx +436 -0
  257. package/src/components/ui/markdown/base-components.tsx +360 -0
  258. package/src/components/ui/markdown/engine.tsx +315 -0
  259. package/src/components/ui/markdown/heading-ids.ts +239 -0
  260. package/src/components/ui/markdown/index.ts +49 -0
  261. package/src/components/ui/markdown/mermaid-diagram.tsx +323 -0
  262. package/src/components/ui/markdown/rich/embed-overrides.tsx +199 -0
  263. package/src/components/ui/markdown/rich/rich-markdown-renderer.tsx +184 -0
  264. package/src/components/ui/markdown/rich/shortcodes.ts +170 -0
  265. package/src/components/ui/markdown/sanitize.ts +3584 -0
  266. package/src/components/ui/markdown/simple-markdown-renderer.tsx +28 -0
  267. package/src/components/ui/markdown/streaming.ts +390 -0
  268. package/src/components/ui/markdown/text-size.ts +106 -0
  269. package/src/components/ui/release-changelog-section.tsx +1 -1
  270. package/src/components/ui/ticket-info-section.tsx +1 -1
  271. package/src/utils/index.ts +1 -0
  272. package/src/utils/markdown-fences.ts +127 -0
  273. package/src/utils/markdown-heading-id.ts +348 -0
  274. package/src/utils/markdown-section-extractor.ts +41 -56
  275. package/dist/chunk-OCEKO5CW.js.map +0 -1
  276. package/dist/chunk-QTYZMP6D.cjs.map +0 -1
  277. package/dist/chunk-TLMJHMXJ.js.map +0 -1
  278. package/dist/chunk-TWYNR4TZ.cjs.map +0 -1
  279. package/dist/chunk-WA7RR64F.js.map +0 -1
  280. package/dist/chunk-WK4N5VBX.cjs.map +0 -1
  281. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts +0 -6
  282. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +0 -1
  283. package/dist/components/chat/utils/chunk-parser.d.ts +0 -25
  284. package/dist/components/chat/utils/chunk-parser.d.ts.map +0 -1
  285. package/dist/components/ui/rich-markdown-renderer.d.ts +0 -34
  286. package/dist/components/ui/rich-markdown-renderer.d.ts.map +0 -1
  287. package/dist/components/ui/simple-markdown-renderer.d.ts +0 -73
  288. package/dist/components/ui/simple-markdown-renderer.d.ts.map +0 -1
  289. package/src/components/chat/hooks/.use-realtime-chunk-processor.md +0 -77
  290. package/src/components/chat/hooks/use-realtime-chunk-processor.ts +0 -489
  291. package/src/components/chat/utils/.chunk-parser.md +0 -62
  292. package/src/components/chat/utils/chunk-parser.ts +0 -262
  293. package/src/components/ui/.simple-markdown-renderer.md +0 -52
  294. package/src/components/ui/rich-markdown-renderer.tsx +0 -1223
  295. package/src/components/ui/simple-markdown-renderer.tsx +0 -964
  296. /package/dist/{chunk-ADPMHWOE.js.map → chunk-72XAID7Y.js.map} +0 -0
  297. /package/dist/{chunk-PAGKRNWK.js.map → chunk-7WZHBQ4J.js.map} +0 -0
  298. /package/dist/{chunk-TSZHM74B.js.map → chunk-DC2TKS7C.js.map} +0 -0
  299. /package/dist/{chunk-I64ABCDX.js.map → chunk-I2B6X77L.js.map} +0 -0
  300. /package/dist/{chunk-DTYRYB2N.js.map → chunk-K6QTZ7CZ.js.map} +0 -0
  301. /package/dist/{chunk-4LTMXDUS.js.map → chunk-KBN7PMFT.js.map} +0 -0
  302. /package/dist/{chunk-TBMV7I5N.js.map → chunk-KXZO2WZX.js.map} +0 -0
  303. /package/dist/{chunk-KEBYLU3U.js.map → chunk-LFUPI7VX.js.map} +0 -0
  304. /package/dist/{chunk-WJQHJD7J.js.map → chunk-TWSKTJHW.js.map} +0 -0
  305. /package/dist/{chunk-TZRUCD56.js.map → chunk-V3ZAAFE3.js.map} +0 -0
  306. /package/dist/{chunk-LRGHJPET.js.map → chunk-XGIFOHKE.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,323 @@
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: [
56
+ 'securityLevel',
57
+ 'htmlLabels',
58
+ 'secure',
59
+ 'startOnLoad',
60
+ 'maxTextSize',
61
+ 'maxEdges',
62
+ // `themeCSS` is RAW CSS that mermaid emits into a `<style>` inside the
63
+ // SVG this component injects with `dangerouslySetInnerHTML`. Locking
64
+ // `securityLevel`/`htmlLabels` closes the HTML surface but left CSS wide
65
+ // open, and mermaid's own directive sanitizer does not help: `themeCSS`
66
+ // is a normal config key, and `sanitizeCss` only brace-balances it.
67
+ // Two demonstrated payloads (verified against real mermaid 11.14.0):
68
+ // - `%%{init:{"themeCSS":"position:fixed;top:0;left:0;width:100vw;
69
+ // height:100vh;z-index:2147483647;background:#fff"}}%%` — bare
70
+ // declarations land on the `#svgId` selector itself, so the diagram
71
+ // becomes an opaque, top-most, viewport-filling overlay whose visible
72
+ // text the author controls via node labels (UI redress / phishing);
73
+ // - `themeCSS: "@font-face{src:url(https://evil.example/f.woff)}"` —
74
+ // at-rules escape the `#svgId` prefix entirely and land
75
+ // document-global, i.e. an unconditional outbound request to an
76
+ // attacker host on every render.
77
+ // The diagram source on the chat path is MODEL output, so both are
78
+ // reachable from untrusted input. Nothing in this repo authors `themeCSS`.
79
+ 'themeCSS',
80
+ ],
81
+ } satisfies Pick<MermaidConfig, 'htmlLabels' | 'securityLevel' | 'secure'>;
82
+
83
+ /** Upper bound on a single `mermaid.render`. Generous enough that no honest
84
+ * diagram hits it; short enough that a wedged render becomes a visible error
85
+ * instead of a permanent skeleton. */
86
+ export const MERMAID_RENDER_TIMEOUT_MS = 15_000;
87
+
88
+ /** `Promise.race` with a rejecting timer, timer always cleared. Kept local —
89
+ * the only caller is the render below. */
90
+ async function withRenderTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
91
+ let timer: ReturnType<typeof setTimeout> | undefined;
92
+ try {
93
+ return await Promise.race([
94
+ promise,
95
+ new Promise<never>((_resolve, reject) => {
96
+ timer = setTimeout(() => reject(new Error(`Diagram rendering timed out after ${ms}ms`)), ms);
97
+ }),
98
+ ]);
99
+ } finally {
100
+ if (timer !== undefined) clearTimeout(timer);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * ODS-TOKENS FLAG (ODS_TOKEN_RULES §Colors / §Typography / §General):
106
+ * `mermaidStyles` below and the `themeVariables` / `fontFamily` / `fontSize`
107
+ * block in `MermaidDiagram` carry RAW hex colors, a literal font-family and
108
+ * literal px font sizes.
109
+ *
110
+ * Reason: mermaid's configuration is not CSS — `themeVariables` is a
111
+ * JavaScript API whose values are baked into the generated SVG as literal
112
+ * attribute strings, so a `var(--color-…)` reference resolves to nothing
113
+ * there. On top of that, ODS has NO 10-step categorical color ramp, and
114
+ * mermaid's `cScale0…cScale9` requires exactly one. The palette is carried
115
+ * over VERBATIM from the pre-unification RichMarkdownRenderer, so this is
116
+ * parity, not new divergence; the missing categorical-ramp tokens are
117
+ * flagged here for addition to ODS.
118
+ *
119
+ * Scope: this exemption covers THIS file only, and only the mermaid config
120
+ * surface. Do NOT copy this pattern — every other style in the markdown
121
+ * module uses ODS semantic classes.
122
+ */
123
+ export const mermaidStyles = `
124
+ .mermaid-svg-container svg {
125
+ max-width: 100% !important;
126
+ height: auto !important;
127
+ min-height: 200px;
128
+ font-family: 'DM Sans', sans-serif !important;
129
+ font-size: 14px !important;
130
+ }
131
+ @media (min-width: 1520px) {
132
+ .mermaid-svg-container svg {
133
+ max-width: 900px !important;
134
+ max-height: 700px !important;
135
+ min-height: 300px;
136
+ font-size: 16px !important;
137
+ }
138
+ }
139
+ @media (min-width: 768px) and (max-width: 1519px) {
140
+ .mermaid-svg-container svg {
141
+ max-width: 700px !important;
142
+ max-height: 600px !important;
143
+ min-height: 250px;
144
+ font-size: 15px !important;
145
+ }
146
+ }
147
+ @media (max-width: 767px) {
148
+ .mermaid-svg-container svg {
149
+ max-width: 90vw !important;
150
+ max-height: 400px !important;
151
+ min-height: 200px;
152
+ font-size: 13px !important;
153
+ }
154
+ }
155
+ .mermaid-svg-container svg[width] { width: 100% !important; }
156
+ .mermaid-svg-container .node rect,
157
+ .mermaid-svg-container .node circle,
158
+ .mermaid-svg-container .node ellipse,
159
+ .mermaid-svg-container .node polygon { stroke-width: 2px !important; }
160
+ .mermaid-svg-container .edgePath path { stroke-width: 2px !important; }
161
+ @media (min-width: 768px) {
162
+ .mermaid-svg-container .node text,
163
+ .mermaid-svg-container .edgeLabel text { font-size: 14px !important; }
164
+ }
165
+ @media (min-width: 1520px) {
166
+ .mermaid-svg-container .node text,
167
+ .mermaid-svg-container .edgeLabel text { font-size: 16px !important; }
168
+ }
169
+ `;
170
+
171
+ /** Monotonic render id. `Date.now()` was ambiguous: two renders started in the
172
+ * same millisecond (routine while a diagram streams in) share an id, and
173
+ * mermaid removes any pre-existing element with that id at the start of a
174
+ * render — so one render would delete the other's working container. */
175
+ let mermaidRenderSeq = 0;
176
+
177
+ export const MermaidDiagram: React.FC<{ chart: string }> = ({ chart }) => {
178
+ const [svg, setSvg] = useState<string>('');
179
+ const [error, setError] = useState<string>('');
180
+ const [isLoading, setIsLoading] = useState<boolean>(true);
181
+ const [mounted, setMounted] = useState(false);
182
+
183
+ useEffect(() => { setMounted(true); }, []);
184
+
185
+ useEffect(() => {
186
+ // This effect re-runs on every `chart` change, and during STREAMING the
187
+ // chart text grows chunk by chunk — so several `mermaid.render` calls are
188
+ // in flight at once. Without this flag a slower EARLIER render can resolve
189
+ // last and overwrite the newer output (or paint an error for a chart that
190
+ // is no longer displayed). The timeout above bounds that window at 15s but
191
+ // cannot close it: `Promise.race` rejects, it does not abort the render.
192
+ let cancelled = false;
193
+ const renderId = `mermaid-${(mermaidRenderSeq += 1)}`;
194
+
195
+ const renderMermaid = async () => {
196
+ try {
197
+ setIsLoading(true);
198
+ // `mermaid` is a declared `dependencies` entry and ships its own
199
+ // types, so this specifier resolves — the suppression that used to sit
200
+ // here (`@ts-ignore`, justified as "optional runtime dependency") was
201
+ // dead AND factually wrong. It is kept as a DYNAMIC import purely for
202
+ // bundle size: neither the chat nor the content bundle should pay for
203
+ // mermaid unless a diagram actually renders. Import failures surface
204
+ // through the surrounding try/catch as the diagram error state.
205
+ const { default: mermaid } = await import('mermaid');
206
+
207
+ mermaid.initialize({
208
+ startOnLoad: false,
209
+ theme: 'dark' as const,
210
+ themeVariables: {
211
+ primaryColor: '#FFC008',
212
+ primaryTextColor: '#FAFAFA',
213
+ primaryBorderColor: '#3A3A3A',
214
+ lineColor: '#888888',
215
+ secondaryColor: '#212121',
216
+ tertiaryColor: '#2A2A2A',
217
+ background: 'transparent',
218
+ mainBkg: 'transparent',
219
+ secondBkg: 'transparent',
220
+ tertiaryBkg: 'transparent',
221
+ cScale0: '#FFC008', cScale1: '#4ECDC4', cScale2: '#45B7D1',
222
+ cScale3: '#96CEB4', cScale4: '#FFEAA7', cScale5: '#DDA0DD',
223
+ cScale6: '#98D8C8', cScale7: '#F7DC6F', cScale8: '#BB8FCE',
224
+ cScale9: '#85C1E9',
225
+ taskTextColor: '#FAFAFA',
226
+ taskTextOutsideColor: '#FAFAFA',
227
+ activeTaskTextColor: '#1A1A1A',
228
+ nodeTextColor: '#FAFAFA',
229
+ },
230
+ flowchart: { useMaxWidth: true, rankSpacing: 50, nodeSpacing: 30, curve: 'basis' },
231
+ sequence: { useMaxWidth: true, width: 150 },
232
+ pie: { useMaxWidth: true, useWidth: undefined },
233
+ fontFamily: 'DM Sans, sans-serif',
234
+ fontSize: 14,
235
+ // SECURITY: single source of truth, shared with the fixture.
236
+ // See MERMAID_SECURITY_OPTIONS above and
237
+ // ./__tests__/mermaid-security.test.ts.
238
+ ...MERMAID_SECURITY_OPTIONS,
239
+ });
240
+
241
+ const { svg: renderedSvg } = await withRenderTimeout(
242
+ mermaid.render(renderId, chart),
243
+ MERMAID_RENDER_TIMEOUT_MS,
244
+ );
245
+ if (cancelled) return;
246
+ setSvg(renderedSvg);
247
+ // CLEAR the previous failure. The render body checks `error` BEFORE
248
+ // `svg`, so a stale message pins the "Diagram Error" card forever and
249
+ // the diagram that just rendered successfully never appears. Transient
250
+ // failures are ROUTINE on this component's own hot path: every
251
+ // streaming frame re-renders a partial chart, and a partial chart is a
252
+ // mermaid parse error until the closing tokens arrive.
253
+ setError('');
254
+ setIsLoading(false);
255
+ } catch (err) {
256
+ if (cancelled) return;
257
+ console.error('Mermaid rendering error:', err);
258
+ setError(`Failed to render diagram: ${err instanceof Error ? err.message : 'Unknown error'}`);
259
+ setIsLoading(false);
260
+ }
261
+ };
262
+
263
+ if (mounted) { renderMermaid(); }
264
+ return () => {
265
+ cancelled = true;
266
+ // An abandoned render (superseded chart, or one wedged past the timeout)
267
+ // keeps mermaid's temporary `#d<id>` working container attached to
268
+ // <body> forever, since mermaid only removes it on a successful finish.
269
+ // Cheap to reap here because the id is ours. If the render is still live
270
+ // it will fail on the missing node — caught, and discarded by `cancelled`.
271
+ document.getElementById(`d${renderId}`)?.remove();
272
+ document.getElementById(renderId)?.remove();
273
+ };
274
+ }, [chart, mounted]);
275
+
276
+ if (error) {
277
+ return (
278
+ <div className="error-state bg-ods-card border border-ods-border rounded-lg p-6 my-6">
279
+ <div className="error-icon flex justify-center mb-4">
280
+ <AlertCircleIcon className="w-12 h-12 text-ods-error" />
281
+ </div>
282
+ <div className="error-title text-center font-sans font-semibold text-lg text-ods-error mb-2">
283
+ Diagram Error
284
+ </div>
285
+ <div className="error-description text-center font-sans text-sm text-ods-text-secondary mb-4 break-words overflow-hidden max-w-full">
286
+ <div className="overflow-x-auto">
287
+ <pre className="whitespace-pre-wrap break-words text-xs">{error}</pre>
288
+ </div>
289
+ </div>
290
+ </div>
291
+ );
292
+ }
293
+
294
+ if (isLoading || !svg) {
295
+ return (
296
+ <div className="skeleton-code bg-ods-card border border-ods-border rounded-lg p-6 min-h-[120px] flex items-center justify-center">
297
+ <div className="animate-pulse text-ods-text-tertiary font-sans">
298
+ {isLoading ? 'Loading diagram renderer...' : 'Rendering diagram...'}
299
+ </div>
300
+ </div>
301
+ );
302
+ }
303
+
304
+ return (
305
+ <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">
306
+ {/* Scoped to `.mermaid-svg-container`, so it only needs to exist when a
307
+ diagram is actually mounted. The engine used to emit this once per
308
+ instance — i.e. once per chat segment, almost never with a diagram. */}
309
+ <style dangerouslySetInnerHTML={{ __html: mermaidStyles }} />
310
+ <div className="flex justify-center items-center w-full min-h-[200px] md:min-h-[250px] lg:min-h-[300px]">
311
+ <div
312
+ className="mermaid-svg-container w-full flex justify-center max-w-full"
313
+ style={{ fontSize: '14px' }}
314
+ dangerouslySetInnerHTML={{
315
+ __html: svg.replace(/<svg[^>]*>/, (match) =>
316
+ match.replace(/width="[^"]*"/, 'width="100%"').replace(/height="[^"]*"/, 'height="auto"')
317
+ ),
318
+ }}
319
+ />
320
+ </div>
321
+ </div>
322
+ );
323
+ };