@flamingo-stack/openframe-frontend-core 0.0.498-1506.4000.1 → 0.0.498

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 (290) hide show
  1. package/dist/{chunk-ZHC2BCXK.js → chunk-2OPD6KRB.js} +4 -4
  2. package/dist/{chunk-5BQWVNRS.cjs → chunk-2R25CGSO.cjs} +62 -62
  3. package/dist/{chunk-5BQWVNRS.cjs.map → chunk-2R25CGSO.cjs.map} +1 -1
  4. package/dist/{chunk-242K7LZY.cjs → chunk-4EHQEMN4.cjs} +11 -11
  5. package/dist/{chunk-242K7LZY.cjs.map → chunk-4EHQEMN4.cjs.map} +1 -1
  6. package/dist/{chunk-4KKGHPNE.cjs → chunk-4NIBKEED.cjs} +9 -9
  7. package/dist/{chunk-4KKGHPNE.cjs.map → chunk-4NIBKEED.cjs.map} +1 -1
  8. package/dist/{chunk-5QWYIPMH.js → chunk-5ZL7PA63.js} +2 -2
  9. package/dist/{chunk-3VGRZ62V.js → chunk-6GAJETAP.js} +2 -2
  10. package/dist/{chunk-EO35UHFT.cjs → chunk-A3Z63ZL5.cjs} +60 -60
  11. package/dist/chunk-A3Z63ZL5.cjs.map +1 -0
  12. package/dist/{chunk-34C2JV2C.js → chunk-G6ARA2HD.js} +2 -2
  13. package/dist/{chunk-WNXIG6YY.js → chunk-K5HHLTSG.js} +2 -2
  14. package/dist/{chunk-42NYDEHH.js → chunk-KK4M4URN.js} +2 -2
  15. package/dist/{chunk-42SMPKGY.js → chunk-MF3VJKHT.js} +2 -2
  16. package/dist/{chunk-EORFD5WB.cjs → chunk-MSB3PEPP.cjs} +15 -15
  17. package/dist/{chunk-EORFD5WB.cjs.map → chunk-MSB3PEPP.cjs.map} +1 -1
  18. package/dist/{chunk-F7XPMG25.cjs → chunk-MVORKLWX.cjs} +27 -28
  19. package/dist/chunk-MVORKLWX.cjs.map +1 -0
  20. package/dist/{chunk-D3LUKR2Y.js → chunk-OYPUQJJ2.js} +10044 -12292
  21. package/dist/chunk-OYPUQJJ2.js.map +1 -0
  22. package/dist/{chunk-62TM2RVZ.cjs → chunk-T67MZAQJ.cjs} +26 -26
  23. package/dist/{chunk-62TM2RVZ.cjs.map → chunk-T67MZAQJ.cjs.map} +1 -1
  24. package/dist/{chunk-BUBQEZ5J.js → chunk-VLWVM6ZS.js} +2 -2
  25. package/dist/{chunk-632XJ2ZT.js → chunk-VOOEX4IZ.js} +5 -6
  26. package/dist/chunk-VOOEX4IZ.js.map +1 -0
  27. package/dist/{chunk-5ZY5JM4Z.js → chunk-W4XZ3FBZ.js} +10 -10
  28. package/dist/chunk-W4XZ3FBZ.js.map +1 -0
  29. package/dist/{chunk-2LYA3RBT.cjs → chunk-XI44QDCD.cjs} +37 -37
  30. package/dist/{chunk-2LYA3RBT.cjs.map → chunk-XI44QDCD.cjs.map} +1 -1
  31. package/dist/{chunk-WZCQW4MW.js → chunk-XSNKSMB3.js} +4 -4
  32. package/dist/{chunk-J3NHQ4JB.cjs → chunk-ZBTIN7RC.cjs} +31 -31
  33. package/dist/{chunk-J3NHQ4JB.cjs.map → chunk-ZBTIN7RC.cjs.map} +1 -1
  34. package/dist/{chunk-SS3D3DHL.cjs → chunk-ZEZTITQZ.cjs} +4886 -7134
  35. package/dist/chunk-ZEZTITQZ.cjs.map +1 -0
  36. package/dist/{chunk-44C23SSC.cjs → chunk-ZHNWWFIM.cjs} +5 -5
  37. package/dist/{chunk-44C23SSC.cjs.map → chunk-ZHNWWFIM.cjs.map} +1 -1
  38. package/dist/{chunk-H6GFQ4AO.cjs → chunk-ZTE32A7C.cjs} +7 -7
  39. package/dist/{chunk-H6GFQ4AO.cjs.map → chunk-ZTE32A7C.cjs.map} +1 -1
  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-chunk-catchup.d.ts.map +1 -1
  48. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +48 -15
  49. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  50. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts +6 -0
  51. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +1 -0
  52. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +57 -10
  53. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  54. package/dist/components/chat/index.cjs +2 -10
  55. package/dist/components/chat/index.cjs.map +1 -1
  56. package/dist/components/chat/index.d.ts +0 -1
  57. package/dist/components/chat/index.d.ts.map +1 -1
  58. package/dist/components/chat/index.js +13 -21
  59. package/dist/components/chat/types/api.types.d.ts +81 -1
  60. package/dist/components/chat/types/api.types.d.ts.map +1 -1
  61. package/dist/components/chat/types/processing.types.d.ts +93 -2
  62. package/dist/components/chat/types/processing.types.d.ts.map +1 -1
  63. package/dist/components/chat/utils/chunk-parser.d.ts +25 -0
  64. package/dist/components/chat/utils/chunk-parser.d.ts.map +1 -0
  65. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts +6 -34
  66. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
  67. package/dist/components/chat/utils/index.d.ts +3 -2
  68. package/dist/components/chat/utils/index.d.ts.map +1 -1
  69. package/dist/components/chat/utils/process-historical-messages.d.ts +11 -59
  70. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  71. package/dist/components/contact/index.cjs +3 -3
  72. package/dist/components/contact/index.js +2 -2
  73. package/dist/components/docs/index.cjs +5 -5
  74. package/dist/components/docs/index.js +4 -4
  75. package/dist/components/embeds/index.cjs +3 -3
  76. package/dist/components/embeds/index.js +2 -2
  77. package/dist/components/faq/index.cjs +3 -3
  78. package/dist/components/faq/index.js +2 -2
  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/header.d.ts.map +1 -1
  88. package/dist/components/navigation/index.cjs +2 -2
  89. package/dist/components/navigation/index.js +1 -1
  90. package/dist/components/navigation/mingo-ai-button.d.ts.map +1 -1
  91. package/dist/components/onboarding-guides/index.cjs +5 -5
  92. package/dist/components/onboarding-guides/index.js +4 -4
  93. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  94. package/dist/components/related-content/index.cjs +3 -3
  95. package/dist/components/related-content/index.js +2 -2
  96. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  97. package/dist/components/tickets/index.cjs +5 -5
  98. package/dist/components/tickets/index.js +4 -4
  99. package/dist/components/ui/index.cjs +2 -44
  100. package/dist/components/ui/index.cjs.map +1 -1
  101. package/dist/components/ui/index.d.ts +2 -1
  102. package/dist/components/ui/index.d.ts.map +1 -1
  103. package/dist/components/ui/index.js +13 -55
  104. package/dist/components/ui/rich-markdown-renderer.d.ts +34 -0
  105. package/dist/components/ui/rich-markdown-renderer.d.ts.map +1 -0
  106. package/dist/components/ui/simple-markdown-renderer.d.ts +73 -0
  107. package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -0
  108. package/dist/index.cjs +2 -60
  109. package/dist/index.cjs.map +1 -1
  110. package/dist/index.js +13 -71
  111. package/dist/utils/index.cjs +40 -169
  112. package/dist/utils/index.cjs.map +1 -1
  113. package/dist/utils/index.d.ts +0 -1
  114. package/dist/utils/index.d.ts.map +1 -1
  115. package/dist/utils/index.js +41 -162
  116. package/dist/utils/index.js.map +1 -1
  117. package/dist/utils/markdown-section-extractor.d.ts.map +1 -1
  118. package/package.json +1 -7
  119. package/src/components/chat/__tests__/chat-message-list.test.tsx +6 -381
  120. package/src/components/chat/chat-message-enhanced.tsx +20 -122
  121. package/src/components/chat/chat-message-list.tsx +132 -409
  122. package/src/components/chat/embeddable-chat.tsx +0 -6
  123. package/src/components/chat/hooks/.index.md +33 -30
  124. package/src/components/chat/hooks/.use-nats-chat-adapter.md +55 -36
  125. package/src/components/chat/hooks/.use-realtime-chunk-processor.md +77 -0
  126. package/src/components/chat/hooks/index.ts +1 -0
  127. package/src/components/chat/hooks/use-chat.ts +0 -8
  128. package/src/components/chat/hooks/use-chunk-catchup.ts +1 -26
  129. package/src/components/chat/hooks/use-nats-chat-adapter.ts +825 -254
  130. package/src/components/chat/hooks/use-realtime-chunk-processor.ts +489 -0
  131. package/src/components/chat/hooks/use-sse-chat-adapter.ts +684 -380
  132. package/src/components/chat/index.ts +0 -5
  133. package/src/components/chat/thinking-display.tsx +1 -1
  134. package/src/components/chat/types/.api.types.md +49 -39
  135. package/src/components/chat/types/.processing.types.md +58 -29
  136. package/src/components/chat/types/api.types.ts +73 -6
  137. package/src/components/chat/types/processing.types.ts +53 -11
  138. package/src/components/chat/utils/.chunk-parser.md +62 -0
  139. package/src/components/chat/utils/.index.md +40 -31
  140. package/src/components/chat/utils/__tests__/process-historical-messages-approvals.test.ts +0 -61
  141. package/src/components/chat/utils/chunk-parser.ts +262 -0
  142. package/src/components/chat/utils/extract-incomplete-message-state.ts +5 -63
  143. package/src/components/chat/utils/index.ts +8 -5
  144. package/src/components/chat/utils/process-historical-messages.ts +386 -358
  145. package/src/components/navigation/header.tsx +2 -1
  146. package/src/components/navigation/mingo-ai-button.tsx +4 -1
  147. package/src/components/navigation/mobile-nav-panel.tsx +5 -5
  148. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +4 -22
  149. package/src/components/shared/legal-document/legal-document-page.tsx +1 -1
  150. package/src/components/shared/product-release/release-detail-page.tsx +4 -19
  151. package/src/components/ui/.simple-markdown-renderer.md +52 -0
  152. package/src/components/ui/index.ts +2 -1
  153. package/src/components/ui/release-changelog-section.tsx +1 -1
  154. package/src/components/ui/rich-markdown-renderer.tsx +1223 -0
  155. package/src/components/ui/simple-markdown-renderer.tsx +964 -0
  156. package/src/components/ui/ticket-info-section.tsx +1 -1
  157. package/src/utils/index.ts +0 -1
  158. package/src/utils/markdown-section-extractor.ts +56 -41
  159. package/dist/chat-protocol/decode.d.ts +0 -58
  160. package/dist/chat-protocol/decode.d.ts.map +0 -1
  161. package/dist/chat-protocol/encode.d.ts +0 -44
  162. package/dist/chat-protocol/encode.d.ts.map +0 -1
  163. package/dist/chat-protocol/env-flag.d.ts +0 -35
  164. package/dist/chat-protocol/env-flag.d.ts.map +0 -1
  165. package/dist/chat-protocol/events.d.ts +0 -207
  166. package/dist/chat-protocol/events.d.ts.map +0 -1
  167. package/dist/chat-protocol/frames.d.ts +0 -213
  168. package/dist/chat-protocol/frames.d.ts.map +0 -1
  169. package/dist/chat-protocol/index.cjs +0 -500
  170. package/dist/chat-protocol/index.cjs.map +0 -1
  171. package/dist/chat-protocol/index.d.ts +0 -18
  172. package/dist/chat-protocol/index.d.ts.map +0 -1
  173. package/dist/chat-protocol/index.js +0 -484
  174. package/dist/chat-protocol/index.js.map +0 -1
  175. package/dist/chat-protocol/ip-normalize.d.ts +0 -44
  176. package/dist/chat-protocol/ip-normalize.d.ts.map +0 -1
  177. package/dist/chat-protocol/nats-decoder.d.ts +0 -26
  178. package/dist/chat-protocol/nats-decoder.d.ts.map +0 -1
  179. package/dist/chunk-5ZY5JM4Z.js.map +0 -1
  180. package/dist/chunk-632XJ2ZT.js.map +0 -1
  181. package/dist/chunk-D3LUKR2Y.js.map +0 -1
  182. package/dist/chunk-EO35UHFT.cjs.map +0 -1
  183. package/dist/chunk-F7XPMG25.cjs.map +0 -1
  184. package/dist/chunk-SS3D3DHL.cjs.map +0 -1
  185. package/dist/components/chat/stream/chat-dialog-store.d.ts +0 -184
  186. package/dist/components/chat/stream/chat-dialog-store.d.ts.map +0 -1
  187. package/dist/components/chat/stream/chat-stream-reducer.d.ts +0 -367
  188. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +0 -1
  189. package/dist/components/chat/stream/delta-batcher.d.ts +0 -51
  190. package/dist/components/chat/stream/delta-batcher.d.ts.map +0 -1
  191. package/dist/components/chat/stream/index.d.ts +0 -17
  192. package/dist/components/chat/stream/index.d.ts.map +0 -1
  193. package/dist/components/chat/stream/message-mutations.d.ts +0 -77
  194. package/dist/components/chat/stream/message-mutations.d.ts.map +0 -1
  195. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts +0 -22
  196. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts.map +0 -1
  197. package/dist/components/ui/markdown/base-components.d.ts +0 -51
  198. package/dist/components/ui/markdown/base-components.d.ts.map +0 -1
  199. package/dist/components/ui/markdown/engine.d.ts +0 -99
  200. package/dist/components/ui/markdown/engine.d.ts.map +0 -1
  201. package/dist/components/ui/markdown/heading-ids.d.ts +0 -65
  202. package/dist/components/ui/markdown/heading-ids.d.ts.map +0 -1
  203. package/dist/components/ui/markdown/index.d.ts +0 -18
  204. package/dist/components/ui/markdown/index.d.ts.map +0 -1
  205. package/dist/components/ui/markdown/mermaid-diagram.d.ts +0 -79
  206. package/dist/components/ui/markdown/mermaid-diagram.d.ts.map +0 -1
  207. package/dist/components/ui/markdown/rich/embed-overrides.d.ts +0 -14
  208. package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +0 -1
  209. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts +0 -40
  210. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts.map +0 -1
  211. package/dist/components/ui/markdown/rich/shortcodes.d.ts +0 -8
  212. package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +0 -1
  213. package/dist/components/ui/markdown/sanitize.d.ts +0 -153
  214. package/dist/components/ui/markdown/sanitize.d.ts.map +0 -1
  215. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts +0 -23
  216. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts.map +0 -1
  217. package/dist/components/ui/markdown/streaming.d.ts +0 -78
  218. package/dist/components/ui/markdown/streaming.d.ts.map +0 -1
  219. package/dist/components/ui/markdown/text-size.d.ts +0 -34
  220. package/dist/components/ui/markdown/text-size.d.ts.map +0 -1
  221. package/dist/utils/markdown-fences.d.ts +0 -42
  222. package/dist/utils/markdown-fences.d.ts.map +0 -1
  223. package/dist/utils/markdown-heading-id.d.ts +0 -85
  224. package/dist/utils/markdown-heading-id.d.ts.map +0 -1
  225. package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +0 -317
  226. package/src/chat-protocol/__tests__/chat-protocol.test.ts +0 -516
  227. package/src/chat-protocol/__tests__/env-flag.test.ts +0 -52
  228. package/src/chat-protocol/__tests__/ip-normalize.test.ts +0 -136
  229. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +0 -232
  230. package/src/chat-protocol/decode.ts +0 -278
  231. package/src/chat-protocol/encode.ts +0 -71
  232. package/src/chat-protocol/env-flag.ts +0 -39
  233. package/src/chat-protocol/events.ts +0 -245
  234. package/src/chat-protocol/frames.ts +0 -245
  235. package/src/chat-protocol/index.ts +0 -21
  236. package/src/chat-protocol/ip-normalize.ts +0 -146
  237. package/src/chat-protocol/nats-decoder.ts +0 -258
  238. package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +0 -207
  239. package/src/components/chat/__tests__/chat-pending-turn.test.tsx +0 -250
  240. package/src/components/chat/hooks/__tests__/__snapshots__/conversation-id-persistence-golden.test.ts.snap +0 -37
  241. package/src/components/chat/hooks/__tests__/__snapshots__/sse-stream-golden.test.ts.snap +0 -0
  242. package/src/components/chat/hooks/__tests__/chunk-catchup-dialog-staleness.test.ts +0 -92
  243. package/src/components/chat/hooks/__tests__/conversation-id-persistence-golden.test.ts +0 -438
  244. package/src/components/chat/hooks/__tests__/sse-stream-golden.test.ts +0 -318
  245. package/src/components/chat/stream/__tests__/__snapshots__/chat-stream-reducer-golden.test.ts.snap +0 -940
  246. package/src/components/chat/stream/__tests__/chat-dialog-store.test.ts +0 -806
  247. package/src/components/chat/stream/__tests__/chat-stream-reducer-golden.test.ts +0 -467
  248. package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +0 -1248
  249. package/src/components/chat/stream/__tests__/delta-batcher.test.ts +0 -255
  250. package/src/components/chat/stream/__tests__/use-chat-stream-reducer.test.ts +0 -105
  251. package/src/components/chat/stream/chat-dialog-store.ts +0 -536
  252. package/src/components/chat/stream/chat-stream-reducer.ts +0 -1876
  253. package/src/components/chat/stream/delta-batcher.ts +0 -168
  254. package/src/components/chat/stream/index.ts +0 -55
  255. package/src/components/chat/stream/message-mutations.ts +0 -340
  256. package/src/components/chat/stream/use-chat-stream-reducer.ts +0 -126
  257. package/src/components/chat/utils/__tests__/__snapshots__/process-historical-messages-golden.test.ts.snap +0 -420
  258. package/src/components/chat/utils/__tests__/__snapshots__/segment-accumulator-golden.test.ts.snap +0 -605
  259. package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +0 -394
  260. package/src/components/chat/utils/__tests__/segment-accumulator-golden.test.ts +0 -270
  261. package/src/components/ui/__tests__/__snapshots__/markdown-parity.test.tsx.snap +0 -2420
  262. package/src/components/ui/__tests__/markdown-parity.test.tsx +0 -2540
  263. package/src/components/ui/markdown/__tests__/mermaid-security.test.ts +0 -176
  264. package/src/components/ui/markdown/__tests__/mermaid-stale-render.test.tsx +0 -151
  265. package/src/components/ui/markdown/__tests__/sanitize-invariant.test.ts +0 -354
  266. package/src/components/ui/markdown/__tests__/sanitize-render.test.tsx +0 -118
  267. package/src/components/ui/markdown/__tests__/streaming.test.tsx +0 -436
  268. package/src/components/ui/markdown/base-components.tsx +0 -360
  269. package/src/components/ui/markdown/engine.tsx +0 -315
  270. package/src/components/ui/markdown/heading-ids.ts +0 -239
  271. package/src/components/ui/markdown/index.ts +0 -49
  272. package/src/components/ui/markdown/mermaid-diagram.tsx +0 -323
  273. package/src/components/ui/markdown/rich/embed-overrides.tsx +0 -199
  274. package/src/components/ui/markdown/rich/rich-markdown-renderer.tsx +0 -184
  275. package/src/components/ui/markdown/rich/shortcodes.ts +0 -170
  276. package/src/components/ui/markdown/sanitize.ts +0 -3584
  277. package/src/components/ui/markdown/simple-markdown-renderer.tsx +0 -28
  278. package/src/components/ui/markdown/streaming.ts +0 -390
  279. package/src/components/ui/markdown/text-size.ts +0 -106
  280. package/src/utils/markdown-fences.ts +0 -127
  281. package/src/utils/markdown-heading-id.ts +0 -348
  282. /package/dist/{chunk-ZHC2BCXK.js.map → chunk-2OPD6KRB.js.map} +0 -0
  283. /package/dist/{chunk-5QWYIPMH.js.map → chunk-5ZL7PA63.js.map} +0 -0
  284. /package/dist/{chunk-3VGRZ62V.js.map → chunk-6GAJETAP.js.map} +0 -0
  285. /package/dist/{chunk-34C2JV2C.js.map → chunk-G6ARA2HD.js.map} +0 -0
  286. /package/dist/{chunk-WNXIG6YY.js.map → chunk-K5HHLTSG.js.map} +0 -0
  287. /package/dist/{chunk-42NYDEHH.js.map → chunk-KK4M4URN.js.map} +0 -0
  288. /package/dist/{chunk-42SMPKGY.js.map → chunk-MF3VJKHT.js.map} +0 -0
  289. /package/dist/{chunk-BUBQEZ5J.js.map → chunk-VLWVM6ZS.js.map} +0 -0
  290. /package/dist/{chunk-WZCQW4MW.js.map → chunk-XSNKSMB3.js.map} +0 -0
@@ -1,250 +0,0 @@
1
- /**
2
- * Pending-turn + hidden-row contracts.
3
- *
4
- * Two reader-visible defects this pins against:
5
- *
6
- * 1. Approving a tool call minted an author-label row with NO body
7
- * ("Michael Assraf:" then nothing). The approval decision is
8
- * out-of-band metadata, so the reducer must not materialize a user
9
- * row for it at all — fixing it at render would leave dead state.
10
- * 2. While the model was thinking, the transcript showed a second,
11
- * empty "Mingo:" row ABOVE the footer progress indicator — two
12
- * owners for one pending turn, and an `aria-live` log announcing a
13
- * bare "Mingo". Exactly one affordance must be present.
14
- *
15
- * Plus the two idioms that must SURVIVE both fixes: a hidden row that
16
- * carries real text (auto-continuation directive) stays in state while
17
- * never rendering, and the SYSTEM name-only row still renders.
18
- */
19
-
20
- import { render } from '@testing-library/react'
21
- import { describe, expect, it, vi } from 'vitest'
22
- import { createChatStreamReducer } from '../stream/chat-stream-reducer'
23
- import {
24
- AUTO_CONTINUATION_DIRECTIVE_PREFIX,
25
- buildAutoContinuationDirective,
26
- } from '../utils/auto-continuation-directive'
27
- import type { Message } from '../types/message.types'
28
-
29
- // ─── Render harness (mirrors chat-message-list.test.tsx) ────────────────────
30
-
31
- const scrollToBottom = vi.fn()
32
- const stopScroll = vi.fn()
33
-
34
- function makeRef() {
35
- const fn = ((el: HTMLElement | null) => {
36
- ;(fn as { current: HTMLElement | null }).current = el
37
- }) as ((el: HTMLElement | null) => void) & { current: HTMLElement | null }
38
- fn.current = null
39
- return fn
40
- }
41
-
42
- vi.mock('use-stick-to-bottom', () => ({
43
- useStickToBottom: () => ({
44
- scrollRef: makeRef(),
45
- contentRef: makeRef(),
46
- scrollToBottom,
47
- stopScroll,
48
- }),
49
- }))
50
-
51
- // Row stub that renders the author label the way the real component does
52
- // (`Name:`), so "labeled row with no body" is directly assertable.
53
- vi.mock('../chat-message-enhanced', () => ({
54
- ChatMessageEnhanced: ({ name, content }: { name?: string; content: unknown }) => (
55
- <div data-testid="row">
56
- <span data-testid="row-name">{name ?? ''}</span>
57
- <span data-testid="row-body">
58
- {typeof content === 'string'
59
- ? content
60
- : Array.isArray(content)
61
- ? content
62
- .map((s) => (s && (s as { type?: string }).type === 'text' ? (s as { text: string }).text : '[seg]'))
63
- .join('')
64
- : ''}
65
- </span>
66
- </div>
67
- ),
68
- }))
69
-
70
- import { ChatMessageList } from '../chat-message-list'
71
-
72
- class ObserverStub {
73
- observe() {}
74
- unobserve() {}
75
- disconnect() {}
76
- }
77
- vi.stubGlobal('IntersectionObserver', ObserverStub)
78
- vi.stubGlobal('ResizeObserver', ObserverStub)
79
-
80
- const names = (c: HTMLElement) =>
81
- Array.from(c.querySelectorAll('[data-testid="row-name"]')).map((n) => n.textContent)
82
- const bodies = (c: HTMLElement) =>
83
- Array.from(c.querySelectorAll('[data-testid="row-body"]')).map((n) => n.textContent)
84
-
85
- // ─── State-level contracts ─────────────────────────────────────────────────
86
-
87
- describe('approval sends mint no visible user row', () => {
88
- it('beginSseSend with empty text pushes only the assistant placeholder', () => {
89
- const r = createChatStreamReducer({ transport: 'sse' })
90
- // What `cardApprove` triggers: ('', { hidden: true, approvalAction }).
91
- r.beginSseSend({ text: '', hidden: true, assistantName: 'Mingo AI' })
92
-
93
- expect(r.state.messages).toHaveLength(1)
94
- expect(r.state.messages[0].role).toBe('assistant')
95
- expect(r.state.messages.some((m) => m.role === 'user')).toBe(false)
96
- expect(r.state.streamingPhase).toBe('thinking')
97
- })
98
-
99
- it('pushOptimisticSend with empty text likewise mints no user row', () => {
100
- const r = createChatStreamReducer({ transport: 'nats' })
101
- r.pushOptimisticSend('', true)
102
-
103
- expect(r.state.messages).toHaveLength(1)
104
- expect(r.state.messages[0].role).toBe('assistant')
105
- })
106
-
107
- it('a normal send still mints the user row', () => {
108
- const r = createChatStreamReducer({ transport: 'sse' })
109
- r.beginSseSend({ text: 'hello', assistantName: 'Mingo AI' })
110
-
111
- expect(r.state.messages).toHaveLength(2)
112
- expect(r.state.messages[0]).toMatchObject({ role: 'user', content: 'hello' })
113
- expect(r.state.messages[0].hidden).toBeUndefined()
114
- })
115
- })
116
-
117
- describe('hidden rows that carry text', () => {
118
- it('an auto-continuation directive IS state (the model sees it) but is flagged hidden', () => {
119
- const directive = buildAutoContinuationDirective('create_ticket', { ticketId: '42' })
120
- expect(directive.startsWith(AUTO_CONTINUATION_DIRECTIVE_PREFIX)).toBe(true)
121
-
122
- const r = createChatStreamReducer({ transport: 'sse' })
123
- r.beginSseSend({ text: directive, hidden: true, assistantName: 'Mingo AI' })
124
-
125
- const userRow = r.state.messages.find((m) => m.role === 'user')
126
- expect(userRow?.content).toBe(directive)
127
- expect(userRow?.hidden).toBe(true)
128
- })
129
-
130
- it('never renders — the raw directive text must not reach the transcript', () => {
131
- const directive = buildAutoContinuationDirective('create_ticket', { ticketId: '42' })
132
- const messages: Message[] = [
133
- { id: 'u1', role: 'user', name: 'Michael Assraf', content: 'my laptop is dead' },
134
- { id: 'h1', role: 'user', name: 'Michael Assraf', content: directive, hidden: true },
135
- { id: 'a1', role: 'assistant', name: 'Mingo', content: 'Opened a ticket.' },
136
- ]
137
- const { container } = render(<ChatMessageList dialogId="d" messages={messages} />)
138
-
139
- expect(container.textContent).not.toContain(AUTO_CONTINUATION_DIRECTIVE_PREFIX)
140
- expect(bodies(container)).toEqual(['my laptop is dead', 'Opened a ticket.'])
141
- })
142
- })
143
-
144
- // ─── Render-level contracts ────────────────────────────────────────────────
145
-
146
- describe('a thinking turn has exactly ONE pending affordance', () => {
147
- const pending: Message[] = [
148
- { id: 'u1', role: 'user', name: 'Michael Assraf', content: 'what is broken?' },
149
- // `Message` carries its body in `content` — there is no `segments` field on
150
- // it (that is `UnifiedChatMessage`), and the render path never reads one.
151
- // An empty `content` IS the empty pending turn.
152
- { id: 'a1', role: 'assistant', name: 'Mingo', content: '' },
153
- ]
154
-
155
- it('does not render the empty labeled assistant row while typing', () => {
156
- const { container } = render(
157
- <ChatMessageList dialogId="d" messages={pending} isTyping />,
158
- )
159
- // The bare "Mingo" label must be gone from the transcript entirely —
160
- // removed from the DOM, not visually hidden (the list is an aria-live
161
- // log; a zero-height node would still be announced).
162
- expect(names(container)).toEqual(['Michael Assraf'])
163
- expect(bodies(container)).toEqual(['what is broken?'])
164
- // …and the single remaining owner is the polite status indicator.
165
- expect(container.querySelectorAll('[role="status"]')).toHaveLength(1)
166
- })
167
-
168
- it('renders the assistant row as soon as it carries text', () => {
169
- const withText: Message[] = [
170
- pending[0],
171
- { id: 'a1', role: 'assistant', name: 'Mingo', content: [{ type: 'text', text: 'Chec' }] },
172
- ]
173
- const { container } = render(
174
- <ChatMessageList dialogId="d" messages={withText} isTyping />,
175
- )
176
- expect(names(container)).toEqual(['Michael Assraf', 'Mingo'])
177
- })
178
-
179
- it('renders the assistant row when it carries a NON-text segment (tool call)', () => {
180
- const withTool: Message[] = [
181
- pending[0],
182
- {
183
- id: 'a1',
184
- role: 'assistant',
185
- name: 'Mingo',
186
- // The REAL `ToolExecutionSegment` shape — `{ type, data }`, not flat
187
- // fields. The previous fixture put `toolExecutionRequestId` / `status`
188
- // at the top level and needed an `as` cast to compile at all (the cast
189
- // was the tell: the two types did not overlap). It passed only because
190
- // the empty-pending-turn predicate looks at segment TYPE; the moment it
191
- // inspected `data`, this test would have kept passing while the real
192
- // path broke.
193
- content: [
194
- {
195
- type: 'tool_execution',
196
- data: {
197
- type: 'EXECUTING_TOOL',
198
- integratedToolType: 'SHELL',
199
- toolFunction: 'run_command',
200
- toolTitle: 'Run command',
201
- parameters: { cmd: 'ls' },
202
- toolExecutionRequestId: 'e1',
203
- },
204
- },
205
- ],
206
- },
207
- ]
208
- const { container } = render(
209
- <ChatMessageList dialogId="d" messages={withTool} isTyping />,
210
- )
211
- expect(names(container)).toEqual(['Michael Assraf', 'Mingo'])
212
- })
213
-
214
- it('renders the assistant row when it carries ONLY a guide segment', () => {
215
- // A `guide` body is the whole visible answer for a how-to question. If
216
- // the empty-pending-turn predicate ever counted it as "nothing visible"
217
- // (e.g. by widening its text-only clause), a guide-only turn would render
218
- // NOTHING until the turn ended — the footer loader would be the sole
219
- // affordance while a fully-formed guide card sat in state.
220
- const withGuide: Message[] = [
221
- pending[0],
222
- {
223
- id: 'a1',
224
- role: 'assistant',
225
- name: 'Mingo',
226
- content: [{ type: 'guide', text: '## Enroll a device' }],
227
- },
228
- ]
229
- const { container } = render(
230
- <ChatMessageList dialogId="d" messages={withGuide} isTyping />,
231
- )
232
- expect(names(container)).toEqual(['Michael Assraf', 'Mingo'])
233
- })
234
-
235
- it('renders a completed empty-ish assistant row when NOT typing', () => {
236
- const { container } = render(<ChatMessageList dialogId="d" messages={pending} />)
237
- expect(names(container)).toEqual(['Michael Assraf', 'Mingo'])
238
- })
239
- })
240
-
241
- describe('the SYSTEM name-only row is a legitimate label-without-body', () => {
242
- it('still renders', () => {
243
- const messages: Message[] = [
244
- { id: 'u1', role: 'user', name: 'Michael Assraf', content: 'hi' },
245
- { id: 's1', role: 'user', name: 'Technician joined', content: '', authorType: 'system' },
246
- ]
247
- const { container } = render(<ChatMessageList dialogId="d" messages={messages} isTyping />)
248
- expect(names(container)).toContain('Technician joined')
249
- })
250
- })
@@ -1,37 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
3
- exports[`server-history hydration (materializes through the reducer) > rebuilds messages + sendIdx-keyed refs + send counter from the transcript 1`] = `
4
- [
5
- {
6
- "content": "open a ticket for the broken printer",
7
- "id": "hydrated-1",
8
- "role": "user",
9
- },
10
- {
11
- "chatRefs": {
12
- "ticket:77": {
13
- "id": "77",
14
- "metadata": {
15
- "status": "OPEN",
16
- },
17
- "title": "Broken printer",
18
- "type": "ticket",
19
- "url": null,
20
- },
21
- },
22
- "content": "✅ Approved — ticket created: [card://ticket:77]",
23
- "id": "hydrated-2",
24
- "role": "assistant",
25
- },
26
- {
27
- "content": "what is the pricing?",
28
- "id": "hydrated-3",
29
- "role": "user",
30
- },
31
- {
32
- "content": "Pricing starts at $99/month per site.",
33
- "id": "hydrated-4",
34
- "role": "assistant",
35
- },
36
- ]
37
- `;
@@ -1,92 +0,0 @@
1
- /**
2
- * A catch-up fetch is async and the active dialog can change while it is in
3
- * flight — switching dialogs is the ordinary interaction, not an edge case.
4
- *
5
- * `useChunkCatchup` already knows about staleness, but only in its `finally`:
6
- * the processing loop ran FIRST and unconditionally. Everything downstream of
7
- * it is bound to the CURRENT dialog — `onChunkReceived` is read through a ref
8
- * the adapter reassigns every render, and the seq bookkeeping refs were just
9
- * re-armed by `resetChunkTracking` for the newly-entered dialog — so a fetch
10
- * that resolved late wrote dialog A's transcript into dialog B, and pushed B's
11
- * seq cursor to A's JetStream sequence. Since the reducer drops anything at or
12
- * below `lastAppliedSeq`, and A's stream position is unrelated to B's, that can
13
- * silently discard B's own live chunks for the rest of the session.
14
- *
15
- * The same applies to the flags: finalizing `hasCompletedInitialCatchup` /
16
- * `bufferUntilInitialCatchupComplete` on a stale fetch ends the NEW dialog's
17
- * buffering window early, and draining `chunkBuffer` hands B's buffered live
18
- * deliveries to A's stale batch.
19
- */
20
- import { describe, it, expect, vi } from 'vitest'
21
- import { renderHook, act } from '@testing-library/react'
22
- import { useChunkCatchup } from '../use-chunk-catchup'
23
- import type { ChunkData } from '../../types'
24
-
25
- type Deferred = {
26
- promise: Promise<ChunkData[]>
27
- resolve: (chunks: ChunkData[]) => void
28
- }
29
-
30
- function deferred(): Deferred {
31
- let resolve!: (chunks: ChunkData[]) => void
32
- const promise = new Promise<ChunkData[]>((r) => {
33
- resolve = r
34
- })
35
- return { promise, resolve }
36
- }
37
-
38
- const textChunk = (text: string, streamSeq: number, sequenceId: number): ChunkData =>
39
- ({ type: 'TEXT', text, streamSeq, sequenceId }) as unknown as ChunkData
40
-
41
- describe('useChunkCatchup — dialog staleness', () => {
42
- it('does not deliver a catchup that resolved after the dialog changed', async () => {
43
- const onChunkReceived = vi.fn()
44
- const pending = deferred()
45
- const fetchChunks = vi.fn((dialogId: string) =>
46
- dialogId === 'A' ? pending.promise : Promise.resolve([]),
47
- )
48
-
49
- const { result, rerender } = renderHook(
50
- ({ dialogId }: { dialogId: string }) =>
51
- useChunkCatchup({ dialogId, onChunkReceived, fetchChunks }),
52
- { initialProps: { dialogId: 'A' } },
53
- )
54
-
55
- // Dialog A starts its catch-up…
56
- let catchup!: Promise<unknown>
57
- await act(async () => {
58
- catchup = result.current.catchUpChunks()
59
- })
60
- expect(fetchChunks).toHaveBeenCalledWith('A', expect.anything(), undefined)
61
-
62
- // …the user switches to B while A's fetch is still open…
63
- rerender({ dialogId: 'B' })
64
-
65
- // …and only then does A's fetch land, carrying A's own transcript.
66
- await act(async () => {
67
- pending.resolve([textChunk('SECRET FROM DIALOG A', 100001, 100001)])
68
- await catchup
69
- })
70
-
71
- expect(onChunkReceived).not.toHaveBeenCalled()
72
- })
73
-
74
- it('still delivers a catchup for the dialog that is current when it lands', () => {
75
- const onChunkReceived = vi.fn()
76
- const fetchChunks = vi.fn(() =>
77
- Promise.resolve([textChunk('B REAL CONTENT', 50001, 50001)]),
78
- )
79
-
80
- const { result } = renderHook(() =>
81
- useChunkCatchup({ dialogId: 'B', onChunkReceived, fetchChunks }),
82
- )
83
-
84
- return act(async () => {
85
- await result.current.catchUpChunks()
86
- }).then(() => {
87
- // Positive control: the guard must not swallow the normal path.
88
- expect(onChunkReceived).toHaveBeenCalledTimes(1)
89
- expect(onChunkReceived.mock.calls[0][0]).toMatchObject({ text: 'B REAL CONTENT' })
90
- })
91
- })
92
- })