@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,436 @@
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
+ /**
247
+ * The `textOf` comparison above cannot see this class of divergence: when a
248
+ * list is cut apart, every remaining item becomes its own `<ol>`/`<ul>` with
249
+ * no `start`, so the TEXT is identical while the visible ordinals restart at
250
+ * "1." for every item. The assertion therefore has to be on list STRUCTURE.
251
+ *
252
+ * The shape is the single most common assistant answer — numbered steps whose
253
+ * items carry a code fence, a second paragraph, a table, or a nested list — and
254
+ * the broken units were `memoizable`, so the wrong render was CACHED for the
255
+ * rest of the turn rather than self-healing on the next token.
256
+ */
257
+ const LIST_CONTINUATION_CASES: Record<string, string> = {
258
+ 'ordered list with per-item code fences':
259
+ '1. Install:\n\n ```bash\n npm i\n ```\n\n2. Run it.\n\n3. Done.\n',
260
+ 'ordered list with 4-space-indented fences':
261
+ '1. Install:\n\n ```bash\n npm i\n ```\n\n2. Run it.\n',
262
+ 'list item with a second paragraph': '- item\n\n second para of item\n\n- item2\n',
263
+ 'list item with a table continuation':
264
+ '1. Options:\n\n | a | b |\n |---|---|\n | 1 | 2 |\n\n2. Next step.\n',
265
+ 'list item with a nested sublist': '- item\n - nested\n\n- item2\n',
266
+ 'a list that follows an unrelated paragraph':
267
+ '- one\n\n- two\n\nplain para\n\n- three\n\n cont\n\n- four\n',
268
+ }
269
+
270
+ for (const [name, md] of Object.entries(LIST_CONTINUATION_CASES)) {
271
+ it(`never cuts a list apart mid-stream: ${name}`, async () => {
272
+ const streamingHtml = await renderHtml(<MarkdownEngine content={md} streaming />)
273
+ const wholeHtml = await renderHtml(<MarkdownEngine content={md} />)
274
+ const listCount = (html: string) => (html.match(/<[ou]l[\s>]/g) ?? []).length
275
+ expect(listCount(streamingHtml)).toBe(listCount(wholeHtml))
276
+ expect(textOf(streamingHtml)).toBe(textOf(wholeHtml))
277
+ })
278
+ }
279
+
280
+ it('splits a list with a continuation block into ONE unit', () => {
281
+ const md = '1. Install:\n\n ```bash\n npm i\n ```\n\n2. Run it.\n\n3. Done.\n'
282
+ expect(splitStreamingBlocks(md)).toHaveLength(1)
283
+ })
284
+
285
+ it('still cuts between a list and a genuinely separate following block', () => {
286
+ // The guard must not become "never cut once a list appeared" — a paragraph
287
+ // after a list is a real boundary and its unit must stay memoizable.
288
+ const blocks = splitStreamingBlocks('- one\n\n- two\n\nplain para\n\ntail para\n')
289
+ expect(blocks.length).toBeGreaterThan(1)
290
+ expect(blocks[0].text).toContain('- one')
291
+ expect(blocks[0].memoizable).toBe(true)
292
+ })
293
+
294
+ it('streaming wrapper is aria-live polite', async () => {
295
+ const html = await renderHtml(<MarkdownEngine content={'hello'} streaming />)
296
+ expect(html).toContain('aria-live="polite"')
297
+ const done = await renderHtml(<MarkdownEngine content={'hello'} />)
298
+ expect(done).not.toContain('aria-live')
299
+ })
300
+
301
+ it('raw HTML block renders identically mid-stream and completed', async () => {
302
+ const md = '<details>\n<summary>More</summary>\n\nbody text\n\n</details>\n\ntail'
303
+ const streamingHtml = await renderHtml(<MarkdownEngine content={md} streaming />)
304
+ const wholeHtml = await renderHtml(<MarkdownEngine content={md} />)
305
+ // Strip only the streaming aria-live wrapper; the disclosure widget and
306
+ // its body must be structurally identical (body INSIDE <details>, the
307
+ // closing tag not dropped).
308
+ const stripWrapper = (html: string) =>
309
+ html
310
+ .replace('<div aria-live="polite" aria-relevant="additions text">', '')
311
+ .replace('</div></article>', '</article>')
312
+ expect(streamingHtml).toContain('<details')
313
+ expect(streamingHtml).toContain('body text')
314
+ expect(stripWrapper(streamingHtml).replace(/\s+/g, '')).toBe(
315
+ stripWrapper(wholeHtml).replace(/\s+/g, ''),
316
+ )
317
+ })
318
+
319
+ it('unterminated fence at the tail renders as code, not swallowed prose', async () => {
320
+ const html = await renderHtml(
321
+ <MarkdownEngine content={'intro\n\n```js\nconst partial ='} streaming />,
322
+ )
323
+ expect(html).toContain('code-block-container')
324
+ // hljs wraps tokens in spans — compare tag-stripped text.
325
+ expect(html.replace(/<[^>]+>/g, '')).toContain('const partial =')
326
+ })
327
+ })
328
+
329
+ // ---------------------------------------------------------------------------
330
+ // Atomic-block memo (regression: the memo never hit)
331
+ // ---------------------------------------------------------------------------
332
+ describe('streaming atomic-block memoization', () => {
333
+ /**
334
+ * Module-scope so the override map's IDENTITY is stable across rerenders —
335
+ * otherwise the test would be measuring its own instability instead of the
336
+ * engine's. The counter proves memo HITS: without it, DOM reconciliation
337
+ * reuses the same nodes either way, so node identity alone cannot fail.
338
+ */
339
+ let paragraphRenders = 0
340
+ const COUNTING_OVERRIDES: Partial<Components> = {
341
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
342
+ p: ({ children }: any) => {
343
+ paragraphRenders++
344
+ return <p>{children}</p>
345
+ },
346
+ }
347
+
348
+ it('does NOT re-render completed blocks when the tail grows', async () => {
349
+ const base = 'alpha para\n\nbeta para\n\ngamma tail'
350
+ paragraphRenders = 0
351
+
352
+ let container!: HTMLElement
353
+ let rerender!: (ui: React.ReactElement) => void
354
+ await act(async () => {
355
+ const result = render(
356
+ <MarkdownEngine content={base} streaming componentOverrides={COUNTING_OVERRIDES} />,
357
+ )
358
+ container = result.container
359
+ rerender = result.rerender
360
+ await new Promise((r) => setTimeout(r, 0))
361
+ })
362
+
363
+ const firstParagraph = container.querySelector('p')
364
+ expect(firstParagraph?.textContent).toBe('alpha para')
365
+ expect(paragraphRenders).toBe(3)
366
+
367
+ const before = paragraphRenders
368
+ await act(async () => {
369
+ rerender(
370
+ <MarkdownEngine
371
+ content={`${base} and more`}
372
+ streaming
373
+ componentOverrides={COUNTING_OVERRIDES}
374
+ />,
375
+ )
376
+ await new Promise((r) => setTimeout(r, 0))
377
+ })
378
+
379
+ // Only the live tail may re-parse. Before the NO_BROKEN_LINKS fix the
380
+ // `components` memo re-created every render, so all THREE re-rendered.
381
+ expect(paragraphRenders - before).toBe(1)
382
+ // …and the completed block's DOM node is the very same element.
383
+ expect(Object.is(container.querySelector('p'), firstParagraph)).toBe(true)
384
+ })
385
+
386
+ it('survives heading ids: appending prose does not bust the block memo', async () => {
387
+ // The heading-id map is rebuilt on EVERY token (its input is the growing
388
+ // `processedContent`). It therefore reaches the heading renderers via
389
+ // CONTEXT, never through the `components` memo — otherwise its changing
390
+ // identity would re-create `components` per token and every completed
391
+ // block's `memo` would bail, exactly the regression above.
392
+ const base = '## Setup\n\nalpha para\n\nbeta para\n\ngamma tail'
393
+ paragraphRenders = 0
394
+
395
+ let container!: HTMLElement
396
+ let rerender!: (ui: React.ReactElement) => void
397
+ await act(async () => {
398
+ const result = render(
399
+ <MarkdownEngine content={base} streaming componentOverrides={COUNTING_OVERRIDES} />,
400
+ )
401
+ container = result.container
402
+ rerender = result.rerender
403
+ await new Promise((r) => setTimeout(r, 0))
404
+ })
405
+ const firstParagraph = container.querySelector('p')
406
+ const before = paragraphRenders
407
+
408
+ await act(async () => {
409
+ rerender(
410
+ <MarkdownEngine
411
+ content={`${base} and more`}
412
+ streaming
413
+ componentOverrides={COUNTING_OVERRIDES}
414
+ />,
415
+ )
416
+ await new Promise((r) => setTimeout(r, 0))
417
+ })
418
+ expect(paragraphRenders - before).toBe(1)
419
+ expect(Object.is(container.querySelector('p'), firstParagraph)).toBe(true)
420
+
421
+ // A tail that DOES add a heading still gets correct, unique ids.
422
+ await act(async () => {
423
+ rerender(
424
+ <MarkdownEngine
425
+ content={`${base}\n\n## Setup\n\nsecond`}
426
+ streaming
427
+ componentOverrides={COUNTING_OVERRIDES}
428
+ />,
429
+ )
430
+ await new Promise((r) => setTimeout(r, 0))
431
+ })
432
+ const ids = Array.from(container.querySelectorAll('h2')).map((el) => el.id)
433
+ expect(ids).toEqual(['setup', 'setup-2'])
434
+ expect(new Set(ids).size).toBe(ids.length)
435
+ })
436
+ })