@flamingo-stack/openframe-frontend-core 0.0.492-1506.3956.1 → 0.0.492

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 (296) hide show
  1. package/dist/{chunk-KX3K2EH4.cjs → chunk-2XAAFAQT.cjs} +11 -11
  2. package/dist/{chunk-KX3K2EH4.cjs.map → chunk-2XAAFAQT.cjs.map} +1 -1
  3. package/dist/{chunk-JHEIOXXZ.js → chunk-4AWRPTKE.js} +5 -6
  4. package/dist/chunk-4AWRPTKE.js.map +1 -0
  5. package/dist/{chunk-PTXZB2KW.js → chunk-4J3CB4KO.js} +2 -2
  6. package/dist/{chunk-7N7YZCHR.cjs → chunk-4MLOMMDX.cjs} +5519 -7555
  7. package/dist/chunk-4MLOMMDX.cjs.map +1 -0
  8. package/dist/{chunk-MEMRQTOI.js → chunk-56J7RLMM.js} +10434 -12470
  9. package/dist/chunk-56J7RLMM.js.map +1 -0
  10. package/dist/{chunk-MEIGDK7M.cjs → chunk-5ICPQAUI.cjs} +60 -60
  11. package/dist/chunk-5ICPQAUI.cjs.map +1 -0
  12. package/dist/{chunk-BEEPU44B.js → chunk-5RHJVPL7.js} +10 -10
  13. package/dist/chunk-5RHJVPL7.js.map +1 -0
  14. package/dist/{chunk-XOFE2RT4.js → chunk-BTIRYGDT.js} +4 -4
  15. package/dist/{chunk-JSEJ5MH5.cjs → chunk-DY6AJNVK.cjs} +5 -5
  16. package/dist/{chunk-JSEJ5MH5.cjs.map → chunk-DY6AJNVK.cjs.map} +1 -1
  17. package/dist/{chunk-PLB6CQSO.js → chunk-FG5ALORQ.js} +2 -2
  18. package/dist/{chunk-QG2EBIH5.cjs → chunk-J2J36PEN.cjs} +31 -31
  19. package/dist/{chunk-QG2EBIH5.cjs.map → chunk-J2J36PEN.cjs.map} +1 -1
  20. package/dist/{chunk-A6FPYAUD.cjs → chunk-LIRXKEOE.cjs} +15 -15
  21. package/dist/{chunk-A6FPYAUD.cjs.map → chunk-LIRXKEOE.cjs.map} +1 -1
  22. package/dist/{chunk-MSL5NE3N.cjs → chunk-LVDI7R7T.cjs} +37 -37
  23. package/dist/{chunk-MSL5NE3N.cjs.map → chunk-LVDI7R7T.cjs.map} +1 -1
  24. package/dist/{chunk-MPUQFRJG.cjs → chunk-M4SCYPCR.cjs} +7 -7
  25. package/dist/{chunk-MPUQFRJG.cjs.map → chunk-M4SCYPCR.cjs.map} +1 -1
  26. package/dist/{chunk-QDGB2DKY.cjs → chunk-O6S6Q52W.cjs} +26 -26
  27. package/dist/{chunk-QDGB2DKY.cjs.map → chunk-O6S6Q52W.cjs.map} +1 -1
  28. package/dist/{chunk-W56JYDSC.cjs → chunk-OFFYASUY.cjs} +27 -28
  29. package/dist/chunk-OFFYASUY.cjs.map +1 -0
  30. package/dist/{chunk-NCAN6VUF.js → chunk-P3ITKTUE.js} +2 -2
  31. package/dist/{chunk-M6FEGPQJ.cjs → chunk-PI5BMKV3.cjs} +62 -62
  32. package/dist/{chunk-M6FEGPQJ.cjs.map → chunk-PI5BMKV3.cjs.map} +1 -1
  33. package/dist/{chunk-34TDA22K.js → chunk-QHRNQTW5.js} +2 -2
  34. package/dist/{chunk-OL3MJKUI.js → chunk-SZWMJIH3.js} +2 -2
  35. package/dist/{chunk-MZRIRQYV.js → chunk-U5LU56JE.js} +2 -2
  36. package/dist/{chunk-C65NCIMV.cjs → chunk-VML66FJQ.cjs} +9 -9
  37. package/dist/{chunk-C65NCIMV.cjs.map → chunk-VML66FJQ.cjs.map} +1 -1
  38. package/dist/{chunk-3LKB722U.js → chunk-WKWZ7LVC.js} +2 -2
  39. package/dist/{chunk-J3VBVR4W.js → chunk-WPFLV4QJ.js} +4 -4
  40. package/dist/components/case-studies/index.cjs +8 -8
  41. package/dist/components/case-studies/index.js +2 -2
  42. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  43. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  44. package/dist/components/chat/hooks/index.d.ts +1 -0
  45. package/dist/components/chat/hooks/index.d.ts.map +1 -1
  46. package/dist/components/chat/hooks/use-chat.d.ts.map +1 -1
  47. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +48 -15
  48. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  49. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts +6 -0
  50. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +1 -0
  51. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +57 -10
  52. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  53. package/dist/components/chat/index.cjs +2 -10
  54. package/dist/components/chat/index.cjs.map +1 -1
  55. package/dist/components/chat/index.d.ts +0 -1
  56. package/dist/components/chat/index.d.ts.map +1 -1
  57. package/dist/components/chat/index.js +13 -21
  58. package/dist/components/chat/types/api.types.d.ts +81 -1
  59. package/dist/components/chat/types/api.types.d.ts.map +1 -1
  60. package/dist/components/chat/types/processing.types.d.ts +93 -2
  61. package/dist/components/chat/types/processing.types.d.ts.map +1 -1
  62. package/dist/components/chat/utils/chunk-parser.d.ts +25 -0
  63. package/dist/components/chat/utils/chunk-parser.d.ts.map +1 -0
  64. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts +6 -34
  65. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
  66. package/dist/components/chat/utils/index.d.ts +3 -2
  67. package/dist/components/chat/utils/index.d.ts.map +1 -1
  68. package/dist/components/chat/utils/process-historical-messages.d.ts +11 -59
  69. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  70. package/dist/components/contact/index.cjs +3 -3
  71. package/dist/components/contact/index.js +2 -2
  72. package/dist/components/docs/index.cjs +5 -5
  73. package/dist/components/docs/index.js +4 -4
  74. package/dist/components/embeds/index.cjs +3 -3
  75. package/dist/components/embeds/index.js +2 -2
  76. package/dist/components/faq/index.cjs +3 -3
  77. package/dist/components/faq/index.js +2 -2
  78. package/dist/components/features/floating-walkthrough-video.d.ts.map +1 -1
  79. package/dist/components/features/index.cjs +2 -2
  80. package/dist/components/features/index.js +1 -1
  81. package/dist/components/help-center-pages/index.cjs +20 -20
  82. package/dist/components/help-center-pages/index.js +11 -11
  83. package/dist/components/index.cjs +130 -172
  84. package/dist/components/index.cjs.map +1 -1
  85. package/dist/components/index.js +21 -63
  86. package/dist/components/index.js.map +1 -1
  87. package/dist/components/navigation/index.cjs +2 -2
  88. package/dist/components/navigation/index.js +1 -1
  89. package/dist/components/navigation/navigation-sidebar-item.d.ts +23 -1
  90. package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
  91. package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
  92. package/dist/components/onboarding-guides/index.cjs +5 -5
  93. package/dist/components/onboarding-guides/index.js +4 -4
  94. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  95. package/dist/components/related-content/index.cjs +3 -3
  96. package/dist/components/related-content/index.js +2 -2
  97. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  98. package/dist/components/tickets/index.cjs +5 -5
  99. package/dist/components/tickets/index.js +4 -4
  100. package/dist/components/ui/data-table/data-table-column-filter.d.ts.map +1 -1
  101. package/dist/components/ui/data-table/data-table-header.d.ts.map +1 -1
  102. package/dist/components/ui/data-table/data-table-skeleton.d.ts.map +1 -1
  103. package/dist/components/ui/index.cjs +2 -44
  104. package/dist/components/ui/index.cjs.map +1 -1
  105. package/dist/components/ui/index.d.ts +2 -1
  106. package/dist/components/ui/index.d.ts.map +1 -1
  107. package/dist/components/ui/index.js +13 -55
  108. package/dist/components/ui/rich-markdown-renderer.d.ts +34 -0
  109. package/dist/components/ui/rich-markdown-renderer.d.ts.map +1 -0
  110. package/dist/components/ui/simple-markdown-renderer.d.ts +73 -0
  111. package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -0
  112. package/dist/components/ui/tab-navigation.d.ts.map +1 -1
  113. package/dist/index.cjs +2 -60
  114. package/dist/index.cjs.map +1 -1
  115. package/dist/index.js +13 -71
  116. package/dist/utils/index.cjs +40 -169
  117. package/dist/utils/index.cjs.map +1 -1
  118. package/dist/utils/index.d.ts +0 -1
  119. package/dist/utils/index.d.ts.map +1 -1
  120. package/dist/utils/index.js +41 -162
  121. package/dist/utils/index.js.map +1 -1
  122. package/dist/utils/markdown-section-extractor.d.ts.map +1 -1
  123. package/package.json +1 -7
  124. package/src/components/chat/__tests__/chat-message-list.test.tsx +5 -293
  125. package/src/components/chat/chat-message-enhanced.tsx +20 -122
  126. package/src/components/chat/chat-message-list.tsx +109 -358
  127. package/src/components/chat/embeddable-chat.tsx +0 -6
  128. package/src/components/chat/hooks/.index.md +33 -30
  129. package/src/components/chat/hooks/.use-nats-chat-adapter.md +55 -36
  130. package/src/components/chat/hooks/.use-realtime-chunk-processor.md +77 -0
  131. package/src/components/chat/hooks/index.ts +1 -0
  132. package/src/components/chat/hooks/use-chat.ts +0 -8
  133. package/src/components/chat/hooks/use-nats-chat-adapter.ts +825 -243
  134. package/src/components/chat/hooks/use-realtime-chunk-processor.ts +489 -0
  135. package/src/components/chat/hooks/use-sse-chat-adapter.ts +684 -380
  136. package/src/components/chat/index.ts +0 -5
  137. package/src/components/chat/thinking-display.tsx +1 -1
  138. package/src/components/chat/types/.api.types.md +49 -39
  139. package/src/components/chat/types/.processing.types.md +58 -29
  140. package/src/components/chat/types/api.types.ts +73 -6
  141. package/src/components/chat/types/processing.types.ts +53 -11
  142. package/src/components/chat/utils/.chunk-parser.md +62 -0
  143. package/src/components/chat/utils/.index.md +40 -31
  144. package/src/components/chat/utils/chunk-parser.ts +262 -0
  145. package/src/components/chat/utils/extract-incomplete-message-state.ts +5 -63
  146. package/src/components/chat/utils/index.ts +8 -5
  147. package/src/components/chat/utils/process-historical-messages.ts +386 -358
  148. package/src/components/features/floating-walkthrough-video.tsx +3 -1
  149. package/src/components/navigation/navigation-sidebar-item.tsx +80 -30
  150. package/src/components/navigation/navigation-sidebar.tsx +106 -46
  151. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +4 -22
  152. package/src/components/shared/legal-document/legal-document-page.tsx +1 -1
  153. package/src/components/shared/product-release/release-detail-page.tsx +4 -19
  154. package/src/components/ui/.simple-markdown-renderer.md +52 -0
  155. package/src/components/ui/__tests__/tab-navigation.test.tsx +159 -0
  156. package/src/components/ui/data-table/data-table-column-filter.tsx +7 -1
  157. package/src/components/ui/data-table/data-table-header.tsx +4 -1
  158. package/src/components/ui/data-table/data-table-skeleton.tsx +7 -0
  159. package/src/components/ui/index.ts +2 -1
  160. package/src/components/ui/release-changelog-section.tsx +1 -1
  161. package/src/components/ui/rich-markdown-renderer.tsx +1223 -0
  162. package/src/components/ui/simple-markdown-renderer.tsx +964 -0
  163. package/src/components/ui/tab-navigation.tsx +370 -171
  164. package/src/components/ui/ticket-info-section.tsx +1 -1
  165. package/src/utils/index.ts +0 -1
  166. package/src/utils/markdown-section-extractor.ts +56 -41
  167. package/dist/chat-protocol/decode.d.ts +0 -58
  168. package/dist/chat-protocol/decode.d.ts.map +0 -1
  169. package/dist/chat-protocol/encode.d.ts +0 -44
  170. package/dist/chat-protocol/encode.d.ts.map +0 -1
  171. package/dist/chat-protocol/env-flag.d.ts +0 -35
  172. package/dist/chat-protocol/env-flag.d.ts.map +0 -1
  173. package/dist/chat-protocol/events.d.ts +0 -207
  174. package/dist/chat-protocol/events.d.ts.map +0 -1
  175. package/dist/chat-protocol/frames.d.ts +0 -213
  176. package/dist/chat-protocol/frames.d.ts.map +0 -1
  177. package/dist/chat-protocol/index.cjs +0 -500
  178. package/dist/chat-protocol/index.cjs.map +0 -1
  179. package/dist/chat-protocol/index.d.ts +0 -18
  180. package/dist/chat-protocol/index.d.ts.map +0 -1
  181. package/dist/chat-protocol/index.js +0 -484
  182. package/dist/chat-protocol/index.js.map +0 -1
  183. package/dist/chat-protocol/ip-normalize.d.ts +0 -44
  184. package/dist/chat-protocol/ip-normalize.d.ts.map +0 -1
  185. package/dist/chat-protocol/nats-decoder.d.ts +0 -26
  186. package/dist/chat-protocol/nats-decoder.d.ts.map +0 -1
  187. package/dist/chunk-7N7YZCHR.cjs.map +0 -1
  188. package/dist/chunk-BEEPU44B.js.map +0 -1
  189. package/dist/chunk-JHEIOXXZ.js.map +0 -1
  190. package/dist/chunk-MEIGDK7M.cjs.map +0 -1
  191. package/dist/chunk-MEMRQTOI.js.map +0 -1
  192. package/dist/chunk-W56JYDSC.cjs.map +0 -1
  193. package/dist/components/chat/stream/chat-dialog-store.d.ts +0 -184
  194. package/dist/components/chat/stream/chat-dialog-store.d.ts.map +0 -1
  195. package/dist/components/chat/stream/chat-stream-reducer.d.ts +0 -367
  196. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +0 -1
  197. package/dist/components/chat/stream/delta-batcher.d.ts +0 -51
  198. package/dist/components/chat/stream/delta-batcher.d.ts.map +0 -1
  199. package/dist/components/chat/stream/index.d.ts +0 -17
  200. package/dist/components/chat/stream/index.d.ts.map +0 -1
  201. package/dist/components/chat/stream/message-mutations.d.ts +0 -77
  202. package/dist/components/chat/stream/message-mutations.d.ts.map +0 -1
  203. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts +0 -22
  204. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts.map +0 -1
  205. package/dist/components/ui/markdown/base-components.d.ts +0 -51
  206. package/dist/components/ui/markdown/base-components.d.ts.map +0 -1
  207. package/dist/components/ui/markdown/engine.d.ts +0 -99
  208. package/dist/components/ui/markdown/engine.d.ts.map +0 -1
  209. package/dist/components/ui/markdown/heading-ids.d.ts +0 -65
  210. package/dist/components/ui/markdown/heading-ids.d.ts.map +0 -1
  211. package/dist/components/ui/markdown/index.d.ts +0 -18
  212. package/dist/components/ui/markdown/index.d.ts.map +0 -1
  213. package/dist/components/ui/markdown/mermaid-diagram.d.ts +0 -79
  214. package/dist/components/ui/markdown/mermaid-diagram.d.ts.map +0 -1
  215. package/dist/components/ui/markdown/rich/embed-overrides.d.ts +0 -14
  216. package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +0 -1
  217. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts +0 -40
  218. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts.map +0 -1
  219. package/dist/components/ui/markdown/rich/shortcodes.d.ts +0 -8
  220. package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +0 -1
  221. package/dist/components/ui/markdown/sanitize.d.ts +0 -153
  222. package/dist/components/ui/markdown/sanitize.d.ts.map +0 -1
  223. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts +0 -23
  224. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts.map +0 -1
  225. package/dist/components/ui/markdown/streaming.d.ts +0 -78
  226. package/dist/components/ui/markdown/streaming.d.ts.map +0 -1
  227. package/dist/components/ui/markdown/text-size.d.ts +0 -34
  228. package/dist/components/ui/markdown/text-size.d.ts.map +0 -1
  229. package/dist/utils/markdown-fences.d.ts +0 -42
  230. package/dist/utils/markdown-fences.d.ts.map +0 -1
  231. package/dist/utils/markdown-heading-id.d.ts +0 -85
  232. package/dist/utils/markdown-heading-id.d.ts.map +0 -1
  233. package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +0 -317
  234. package/src/chat-protocol/__tests__/chat-protocol.test.ts +0 -516
  235. package/src/chat-protocol/__tests__/env-flag.test.ts +0 -52
  236. package/src/chat-protocol/__tests__/ip-normalize.test.ts +0 -136
  237. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +0 -232
  238. package/src/chat-protocol/decode.ts +0 -278
  239. package/src/chat-protocol/encode.ts +0 -71
  240. package/src/chat-protocol/env-flag.ts +0 -39
  241. package/src/chat-protocol/events.ts +0 -245
  242. package/src/chat-protocol/frames.ts +0 -245
  243. package/src/chat-protocol/index.ts +0 -21
  244. package/src/chat-protocol/ip-normalize.ts +0 -146
  245. package/src/chat-protocol/nats-decoder.ts +0 -258
  246. package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +0 -207
  247. package/src/components/chat/__tests__/chat-pending-turn.test.tsx +0 -230
  248. package/src/components/chat/hooks/__tests__/__snapshots__/conversation-id-persistence-golden.test.ts.snap +0 -37
  249. package/src/components/chat/hooks/__tests__/__snapshots__/sse-stream-golden.test.ts.snap +0 -0
  250. package/src/components/chat/hooks/__tests__/conversation-id-persistence-golden.test.ts +0 -438
  251. package/src/components/chat/hooks/__tests__/sse-stream-golden.test.ts +0 -318
  252. package/src/components/chat/stream/__tests__/__snapshots__/chat-stream-reducer-golden.test.ts.snap +0 -940
  253. package/src/components/chat/stream/__tests__/chat-dialog-store.test.ts +0 -756
  254. package/src/components/chat/stream/__tests__/chat-stream-reducer-golden.test.ts +0 -460
  255. package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +0 -973
  256. package/src/components/chat/stream/__tests__/delta-batcher.test.ts +0 -255
  257. package/src/components/chat/stream/__tests__/use-chat-stream-reducer.test.ts +0 -98
  258. package/src/components/chat/stream/chat-dialog-store.ts +0 -536
  259. package/src/components/chat/stream/chat-stream-reducer.ts +0 -1808
  260. package/src/components/chat/stream/delta-batcher.ts +0 -168
  261. package/src/components/chat/stream/index.ts +0 -55
  262. package/src/components/chat/stream/message-mutations.ts +0 -340
  263. package/src/components/chat/stream/use-chat-stream-reducer.ts +0 -126
  264. package/src/components/chat/utils/__tests__/__snapshots__/process-historical-messages-golden.test.ts.snap +0 -420
  265. package/src/components/chat/utils/__tests__/__snapshots__/segment-accumulator-golden.test.ts.snap +0 -605
  266. package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +0 -394
  267. package/src/components/chat/utils/__tests__/segment-accumulator-golden.test.ts +0 -270
  268. package/src/components/ui/__tests__/__snapshots__/markdown-parity.test.tsx.snap +0 -2420
  269. package/src/components/ui/__tests__/markdown-parity.test.tsx +0 -2528
  270. package/src/components/ui/markdown/__tests__/mermaid-security.test.ts +0 -115
  271. package/src/components/ui/markdown/__tests__/mermaid-stale-render.test.tsx +0 -95
  272. package/src/components/ui/markdown/__tests__/sanitize-invariant.test.ts +0 -309
  273. package/src/components/ui/markdown/__tests__/streaming.test.tsx +0 -388
  274. package/src/components/ui/markdown/base-components.tsx +0 -360
  275. package/src/components/ui/markdown/engine.tsx +0 -315
  276. package/src/components/ui/markdown/heading-ids.ts +0 -239
  277. package/src/components/ui/markdown/index.ts +0 -49
  278. package/src/components/ui/markdown/mermaid-diagram.tsx +0 -291
  279. package/src/components/ui/markdown/rich/embed-overrides.tsx +0 -199
  280. package/src/components/ui/markdown/rich/rich-markdown-renderer.tsx +0 -184
  281. package/src/components/ui/markdown/rich/shortcodes.ts +0 -170
  282. package/src/components/ui/markdown/sanitize.ts +0 -3499
  283. package/src/components/ui/markdown/simple-markdown-renderer.tsx +0 -28
  284. package/src/components/ui/markdown/streaming.ts +0 -362
  285. package/src/components/ui/markdown/text-size.ts +0 -106
  286. package/src/utils/markdown-fences.ts +0 -127
  287. package/src/utils/markdown-heading-id.ts +0 -348
  288. /package/dist/{chunk-PTXZB2KW.js.map → chunk-4J3CB4KO.js.map} +0 -0
  289. /package/dist/{chunk-XOFE2RT4.js.map → chunk-BTIRYGDT.js.map} +0 -0
  290. /package/dist/{chunk-PLB6CQSO.js.map → chunk-FG5ALORQ.js.map} +0 -0
  291. /package/dist/{chunk-NCAN6VUF.js.map → chunk-P3ITKTUE.js.map} +0 -0
  292. /package/dist/{chunk-34TDA22K.js.map → chunk-QHRNQTW5.js.map} +0 -0
  293. /package/dist/{chunk-OL3MJKUI.js.map → chunk-SZWMJIH3.js.map} +0 -0
  294. /package/dist/{chunk-MZRIRQYV.js.map → chunk-U5LU56JE.js.map} +0 -0
  295. /package/dist/{chunk-3LKB722U.js.map → chunk-WKWZ7LVC.js.map} +0 -0
  296. /package/dist/{chunk-J3VBVR4W.js.map → chunk-WPFLV4QJ.js.map} +0 -0
@@ -1,388 +0,0 @@
1
- /**
2
- * Streaming-path tests for the unified markdown engine (plan §D1):
3
- * atomic-block splitting, fence tail-completion, card-block cache
4
- * exclusion, and — critically — that the COMPLETION render (streaming
5
- * flipped off) equals the whole-document parse for every cross-block
6
- * construct (late reference defs, loose-list numbering, footnote-ish
7
- * splits, blockquote-internal blank lines, streamed tables).
8
- */
9
- import React from 'react'
10
- import { describe, it, expect, vi } from 'vitest'
11
- import type { Components } from 'react-markdown'
12
- import { render, act } from '@testing-library/react'
13
- import { splitStreamingBlocks, completeStreamingTail } from '../streaming'
14
- import { MarkdownEngine } from '../engine'
15
-
16
- vi.mock('mermaid', () => ({
17
- default: {
18
- initialize: vi.fn(),
19
- render: vi.fn(async () => ({ svg: '<svg data-mock-mermaid="true"></svg>' })),
20
- },
21
- }))
22
-
23
- async function renderHtml(ui: React.ReactElement): Promise<string> {
24
- let container: HTMLElement
25
- await act(async () => {
26
- const result = render(ui)
27
- container = result.container
28
- await new Promise((r) => setTimeout(r, 0))
29
- })
30
- return container!.innerHTML
31
- }
32
-
33
- describe('splitStreamingBlocks', () => {
34
- it('splits plain paragraphs at blank lines', () => {
35
- const blocks = splitStreamingBlocks('para one\n\npara two\n\npara three')
36
- expect(blocks.map((b) => b.memoizable)).toEqual([true, true, false])
37
- expect(blocks[0].text).toContain('para one')
38
- expect(blocks[2].text).toContain('para three')
39
- })
40
-
41
- it('never cuts inside a fenced code block', () => {
42
- const blocks = splitStreamingBlocks('```js\ncode\n\nmore code\n```\n\nafter')
43
- expect(blocks).toHaveLength(2)
44
- expect(blocks[0].text).toContain('more code')
45
- })
46
-
47
- it('never cuts a loose ordered list apart', () => {
48
- const md = '1. first\n\n2. second\n\n3. third'
49
- const blocks = splitStreamingBlocks(md)
50
- expect(blocks).toHaveLength(1)
51
- })
52
-
53
- it('never cuts a multi-paragraph blockquote apart', () => {
54
- const md = '> first para\n\n> second para'
55
- expect(splitStreamingBlocks(md)).toHaveLength(1)
56
- })
57
-
58
- it('does not cut before an indented continuation line', () => {
59
- const md = '- item one\n\n continuation of item one\n\nnext para'
60
- const blocks = splitStreamingBlocks(md)
61
- expect(blocks[0].text).toContain('continuation')
62
- })
63
-
64
- it('marks card:// and mention:// blocks non-memoizable (stale-card fix)', () => {
65
- const blocks = splitStreamingBlocks(
66
- 'plain text block\n\nSee [card://blog:x] here\n\nCheck @device:r1 via mention\n\ntail',
67
- )
68
- expect(blocks.map((b) => b.memoizable)).toEqual([true, false, true, false])
69
- // mention text without mention:// scheme stays memoizable; the scheme
70
- // appears post-remark, so the splitter keys on the raw marker too:
71
- const withScheme = splitStreamingBlocks('a [x](mention://device:r1) link\n\ntail')
72
- expect(withScheme[0].memoizable).toBe(false)
73
- })
74
-
75
- it('marks reference-definition/use and footnote blocks non-memoizable', () => {
76
- const blocks = splitStreamingBlocks(
77
- 'See [text][ref] please\n\n[ref]: https://example.com\n\nA footnote[^1] here\n\ntail',
78
- )
79
- expect(blocks.map((b) => b.memoizable)).toEqual([false, false, false, false])
80
- })
81
-
82
- it('keys blocks by position so identical blocks never alias', () => {
83
- const blocks = splitStreamingBlocks('---\n\n---\n\nend')
84
- expect(blocks.map((b) => b.index)).toEqual([0, 1, 2])
85
- })
86
-
87
- // --- fence-marker fidelity (reviewer-reported failures) ---
88
- it('does not let ~~~ close a ``` fence', () => {
89
- const md = '```text\nline\n~~~\n\nstill in code\n```\n\nafter'
90
- const blocks = splitStreamingBlocks(md)
91
- // The whole code block stays ONE unit; only the trailing prose splits.
92
- expect(blocks).toHaveLength(2)
93
- expect(blocks[0].text).toContain('still in code')
94
- expect(blocks[1].text.trim()).toBe('after')
95
- })
96
-
97
- it('does not let ``` close a ~~~ fence', () => {
98
- const blocks = splitStreamingBlocks('~~~text\nline\n```\n\nstill in code\n~~~\n\nafter')
99
- expect(blocks).toHaveLength(2)
100
- expect(blocks[0].text).toContain('still in code')
101
- })
102
-
103
- it('ignores fences inside 4-space-indented code (CommonMark caps indent at 3)', () => {
104
- const blocks = splitStreamingBlocks(' ```\n not a fence\n\nreal para\n\ntail')
105
- expect(blocks.length).toBeGreaterThan(1)
106
- expect(blocks.some((b) => b.text.includes('real para'))).toBe(true)
107
- })
108
-
109
- it('requires the closer to be at least as long as the opener', () => {
110
- // The inner ``` is CONTENT of the ````-opened block.
111
- const blocks = splitStreamingBlocks('````\n```\n\ninner\n````\n\nafter')
112
- expect(blocks).toHaveLength(2)
113
- expect(blocks[0].text).toContain('inner')
114
- })
115
-
116
- // --- raw HTML block integrity ---
117
- it('never cuts inside an unbalanced raw HTML block', () => {
118
- const blocks = splitStreamingBlocks(
119
- '<details>\n<summary>More</summary>\n\nbody text\n\n</details>\n\ntail',
120
- )
121
- expect(blocks).toHaveLength(2)
122
- expect(blocks[0].text).toContain('body text')
123
- expect(blocks[0].text).toContain('</details>')
124
- // Raw HTML units are never cached.
125
- expect(blocks.every((b) => !b.memoizable)).toBe(true)
126
- })
127
-
128
- it('never cuts inside a MULTI-LINE start tag', () => {
129
- // `HTML_TAG_RE` needs `>` on the same line, so `<div\n class="x">` never
130
- // opened the container: the splitter cut at every blank line and marked
131
- // the fragments memoizable — the container's body escaped the div and
132
- // the broken render got cached.
133
- const blocks = splitStreamingBlocks('<div\n class="x">\n\nbody\n\n</div>\n\nafter')
134
- expect(blocks).toHaveLength(2)
135
- expect(blocks[0].text).toContain('body')
136
- expect(blocks[0].text).toContain('</div>')
137
- expect(blocks.every((b) => !b.memoizable)).toBe(true)
138
- })
139
-
140
- it('releases the latch when a container closes MID-LINE', () => {
141
- // A close tag not at column 0..3 (`some text </details>`) never
142
- // decremented, so `htmlDepth` latched >0 and the whole rest of the
143
- // message collapsed into ONE non-memoizable unit for the rest of the
144
- // stream.
145
- const blocks = splitStreamingBlocks(
146
- '<details>\nstuff\n\nsome text </details>\n\nafter\n\nmore\n\ntail',
147
- )
148
- expect(blocks).toHaveLength(4)
149
- expect(blocks[0].text).toContain('</details>')
150
- expect(blocks[0].memoizable).toBe(false)
151
- // Cutting resumed — the plain paragraphs after the container cache again.
152
- expect(blocks.map((b) => b.memoizable)).toEqual([false, true, true, false])
153
- })
154
-
155
- it('sees raw HTML indented to a list item content column', () => {
156
- // The old column-0..3 gate missed this entirely, so the unit was marked
157
- // memoizable despite containing raw HTML.
158
- const blocks = splitStreamingBlocks(
159
- '1. step\n\n <details>\n <summary>x</summary>\n </details>\n\ntail',
160
- )
161
- expect(blocks[0].text).toContain('<details>')
162
- expect(blocks[0].memoizable).toBe(false)
163
- })
164
-
165
- it('caps the latch so an unclosed container cannot disable cutting forever', () => {
166
- const paras = Array.from({ length: 300 }, (_, i) => `para ${i}`).join('\n\n')
167
- const blocks = splitStreamingBlocks(`<div>\n\n${paras}`)
168
- // Past the cap the splitter resumes cutting…
169
- expect(blocks.length).toBeGreaterThan(1)
170
- // …but nothing inside the still-open container is ever cached.
171
- expect(blocks.every((b) => !b.memoizable)).toBe(true)
172
- })
173
-
174
- it('reports each unit’s document-wide startLine', () => {
175
- const blocks = splitStreamingBlocks('one\n\ntwo\n\nthree')
176
- expect(blocks.map((b) => b.startLine)).toEqual([1, 3, 5])
177
- })
178
- })
179
-
180
- describe('completeStreamingTail', () => {
181
- it('closes an unterminated fence', () => {
182
- expect(completeStreamingTail('text\n\n```js\nconst x =')).toMatch(/```$/)
183
- })
184
- it('leaves balanced fences alone', () => {
185
- const md = '```js\ncode\n```'
186
- expect(completeStreamingTail(md)).toBe(md)
187
- })
188
- it('does NOT touch stray emphasis/link openers (no false-positive close)', () => {
189
- for (const md of ['2 * 3 is six', 'a lone _ under', 'an open [ bracket', 'tick ` alone']) {
190
- expect(completeStreamingTail(md)).toBe(md)
191
- }
192
- })
193
-
194
- it('does not append a bogus fence when ``` and ~~~ both appear (closed block)', () => {
195
- const md = '```\n~~~\n```'
196
- expect(completeStreamingTail(md)).toBe(md)
197
- })
198
-
199
- it('does not append a bogus fence to a ```-block containing a ~~~ line', () => {
200
- const md = '```text\nline\n~~~\n\nstill in code\n```\n\nafter'
201
- expect(completeStreamingTail(md)).toBe(md)
202
- })
203
-
204
- it('closes an unterminated ~~~ fence with ~~~ (the RECORDED opener)', () => {
205
- expect(completeStreamingTail('~~~py\nx =')).toBe('~~~py\nx =\n~~~')
206
- })
207
-
208
- it('closes with a run at least as long as the opener', () => {
209
- expect(completeStreamingTail('````\nstuff')).toBe('````\nstuff\n````')
210
- })
211
- })
212
-
213
- describe('MarkdownEngine streaming vs completion equivalence', () => {
214
- const textOf = (html: string) =>
215
- html
216
- .replace(/<style[\s\S]*?<\/style>/, '')
217
- .replace(/<[^>]+>/g, '')
218
- .replace(/\s+/g, '')
219
-
220
- const CASES: Record<string, { md: string; transientTextDivergence?: boolean }> = {
221
- 'loose list keeps numbering': { md: '1. first\n\n2. second\n\n3. third' },
222
- // Reference links are the DOCUMENTED acceptable mid-stream transient:
223
- // the use renders literally until the def arrives; completion self-heals.
224
- 'late reference definition resolves': {
225
- md: 'See [the docs][ref] now.\n\n[ref]: https://example.com/late',
226
- transientTextDivergence: true,
227
- },
228
- 'blockquote with internal blank line': { md: '> first\n>\n> second' },
229
- 'gfm table': { md: '| A | B |\n|---|---|\n| 1 | 2 |' },
230
- 'multi-block document': { md: '# Title\n\npara one\n\n- a\n- b\n\npara two' },
231
- }
232
-
233
- for (const [name, { md, transientTextDivergence }] of Object.entries(CASES)) {
234
- it(`completion render equals whole-document parse: ${name}`, async () => {
235
- const streamingHtml = await renderHtml(<MarkdownEngine content={md} streaming />)
236
- const completedHtml = await renderHtml(<MarkdownEngine content={md} streaming={false} />)
237
- const wholeHtml = await renderHtml(<MarkdownEngine content={md} />)
238
- // Completion IS the authoritative whole-document parse — always.
239
- expect(completedHtml).toBe(wholeHtml)
240
- if (!transientTextDivergence) {
241
- expect(textOf(streamingHtml)).toBe(textOf(wholeHtml))
242
- }
243
- })
244
- }
245
-
246
- it('streaming wrapper is aria-live polite', async () => {
247
- const html = await renderHtml(<MarkdownEngine content={'hello'} streaming />)
248
- expect(html).toContain('aria-live="polite"')
249
- const done = await renderHtml(<MarkdownEngine content={'hello'} />)
250
- expect(done).not.toContain('aria-live')
251
- })
252
-
253
- it('raw HTML block renders identically mid-stream and completed', async () => {
254
- const md = '<details>\n<summary>More</summary>\n\nbody text\n\n</details>\n\ntail'
255
- const streamingHtml = await renderHtml(<MarkdownEngine content={md} streaming />)
256
- const wholeHtml = await renderHtml(<MarkdownEngine content={md} />)
257
- // Strip only the streaming aria-live wrapper; the disclosure widget and
258
- // its body must be structurally identical (body INSIDE <details>, the
259
- // closing tag not dropped).
260
- const stripWrapper = (html: string) =>
261
- html
262
- .replace('<div aria-live="polite" aria-relevant="additions text">', '')
263
- .replace('</div></article>', '</article>')
264
- expect(streamingHtml).toContain('<details')
265
- expect(streamingHtml).toContain('body text')
266
- expect(stripWrapper(streamingHtml).replace(/\s+/g, '')).toBe(
267
- stripWrapper(wholeHtml).replace(/\s+/g, ''),
268
- )
269
- })
270
-
271
- it('unterminated fence at the tail renders as code, not swallowed prose', async () => {
272
- const html = await renderHtml(
273
- <MarkdownEngine content={'intro\n\n```js\nconst partial ='} streaming />,
274
- )
275
- expect(html).toContain('code-block-container')
276
- // hljs wraps tokens in spans — compare tag-stripped text.
277
- expect(html.replace(/<[^>]+>/g, '')).toContain('const partial =')
278
- })
279
- })
280
-
281
- // ---------------------------------------------------------------------------
282
- // Atomic-block memo (regression: the memo never hit)
283
- // ---------------------------------------------------------------------------
284
- describe('streaming atomic-block memoization', () => {
285
- /**
286
- * Module-scope so the override map's IDENTITY is stable across rerenders —
287
- * otherwise the test would be measuring its own instability instead of the
288
- * engine's. The counter proves memo HITS: without it, DOM reconciliation
289
- * reuses the same nodes either way, so node identity alone cannot fail.
290
- */
291
- let paragraphRenders = 0
292
- const COUNTING_OVERRIDES: Partial<Components> = {
293
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
294
- p: ({ children }: any) => {
295
- paragraphRenders++
296
- return <p>{children}</p>
297
- },
298
- }
299
-
300
- it('does NOT re-render completed blocks when the tail grows', async () => {
301
- const base = 'alpha para\n\nbeta para\n\ngamma tail'
302
- paragraphRenders = 0
303
-
304
- let container!: HTMLElement
305
- let rerender!: (ui: React.ReactElement) => void
306
- await act(async () => {
307
- const result = render(
308
- <MarkdownEngine content={base} streaming componentOverrides={COUNTING_OVERRIDES} />,
309
- )
310
- container = result.container
311
- rerender = result.rerender
312
- await new Promise((r) => setTimeout(r, 0))
313
- })
314
-
315
- const firstParagraph = container.querySelector('p')
316
- expect(firstParagraph?.textContent).toBe('alpha para')
317
- expect(paragraphRenders).toBe(3)
318
-
319
- const before = paragraphRenders
320
- await act(async () => {
321
- rerender(
322
- <MarkdownEngine
323
- content={`${base} and more`}
324
- streaming
325
- componentOverrides={COUNTING_OVERRIDES}
326
- />,
327
- )
328
- await new Promise((r) => setTimeout(r, 0))
329
- })
330
-
331
- // Only the live tail may re-parse. Before the NO_BROKEN_LINKS fix the
332
- // `components` memo re-created every render, so all THREE re-rendered.
333
- expect(paragraphRenders - before).toBe(1)
334
- // …and the completed block's DOM node is the very same element.
335
- expect(Object.is(container.querySelector('p'), firstParagraph)).toBe(true)
336
- })
337
-
338
- it('survives heading ids: appending prose does not bust the block memo', async () => {
339
- // The heading-id map is rebuilt on EVERY token (its input is the growing
340
- // `processedContent`). It therefore reaches the heading renderers via
341
- // CONTEXT, never through the `components` memo — otherwise its changing
342
- // identity would re-create `components` per token and every completed
343
- // block's `memo` would bail, exactly the regression above.
344
- const base = '## Setup\n\nalpha para\n\nbeta para\n\ngamma tail'
345
- paragraphRenders = 0
346
-
347
- let container!: HTMLElement
348
- let rerender!: (ui: React.ReactElement) => void
349
- await act(async () => {
350
- const result = render(
351
- <MarkdownEngine content={base} streaming componentOverrides={COUNTING_OVERRIDES} />,
352
- )
353
- container = result.container
354
- rerender = result.rerender
355
- await new Promise((r) => setTimeout(r, 0))
356
- })
357
- const firstParagraph = container.querySelector('p')
358
- const before = paragraphRenders
359
-
360
- await act(async () => {
361
- rerender(
362
- <MarkdownEngine
363
- content={`${base} and more`}
364
- streaming
365
- componentOverrides={COUNTING_OVERRIDES}
366
- />,
367
- )
368
- await new Promise((r) => setTimeout(r, 0))
369
- })
370
- expect(paragraphRenders - before).toBe(1)
371
- expect(Object.is(container.querySelector('p'), firstParagraph)).toBe(true)
372
-
373
- // A tail that DOES add a heading still gets correct, unique ids.
374
- await act(async () => {
375
- rerender(
376
- <MarkdownEngine
377
- content={`${base}\n\n## Setup\n\nsecond`}
378
- streaming
379
- componentOverrides={COUNTING_OVERRIDES}
380
- />,
381
- )
382
- await new Promise((r) => setTimeout(r, 0))
383
- })
384
- const ids = Array.from(container.querySelectorAll('h2')).map((el) => el.id)
385
- expect(ids).toEqual(['setup', 'setup-2'])
386
- expect(new Set(ids).size).toBe(ids.length)
387
- })
388
- })