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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/dist/chat-protocol/decode.d.ts +58 -0
  2. package/dist/chat-protocol/decode.d.ts.map +1 -0
  3. package/dist/chat-protocol/encode.d.ts +44 -0
  4. package/dist/chat-protocol/encode.d.ts.map +1 -0
  5. package/dist/chat-protocol/env-flag.d.ts +35 -0
  6. package/dist/chat-protocol/env-flag.d.ts.map +1 -0
  7. package/dist/chat-protocol/events.d.ts +196 -0
  8. package/dist/chat-protocol/events.d.ts.map +1 -0
  9. package/dist/chat-protocol/frames.d.ts +213 -0
  10. package/dist/chat-protocol/frames.d.ts.map +1 -0
  11. package/dist/chat-protocol/index.cjs +494 -0
  12. package/dist/chat-protocol/index.cjs.map +1 -0
  13. package/dist/chat-protocol/index.d.ts +18 -0
  14. package/dist/chat-protocol/index.d.ts.map +1 -0
  15. package/dist/chat-protocol/index.js +478 -0
  16. package/dist/chat-protocol/index.js.map +1 -0
  17. package/dist/chat-protocol/ip-normalize.d.ts +44 -0
  18. package/dist/chat-protocol/ip-normalize.d.ts.map +1 -0
  19. package/dist/chat-protocol/nats-decoder.d.ts +26 -0
  20. package/dist/chat-protocol/nats-decoder.d.ts.map +1 -0
  21. package/dist/{chunk-AJNDDXKC.js → chunk-4NWARV5I.js} +3 -3
  22. package/dist/{chunk-GQX6LFFA.cjs → chunk-5CJR2GZ4.cjs} +42 -42
  23. package/dist/{chunk-GQX6LFFA.cjs.map → chunk-5CJR2GZ4.cjs.map} +1 -1
  24. package/dist/{chunk-EUYIFALO.cjs → chunk-5EYRXSY5.cjs} +14 -14
  25. package/dist/{chunk-EUYIFALO.cjs.map → chunk-5EYRXSY5.cjs.map} +1 -1
  26. package/dist/{chunk-5RXKOGH6.cjs → chunk-5LMGFI4J.cjs} +7011 -4777
  27. package/dist/chunk-5LMGFI4J.cjs.map +1 -0
  28. package/dist/{chunk-KFQXO7QB.js → chunk-6GT557KN.js} +1 -1
  29. package/dist/chunk-6GT557KN.js.map +1 -0
  30. package/dist/{chunk-77DFPD35.js → chunk-BE6DK7QY.js} +12267 -10033
  31. package/dist/chunk-BE6DK7QY.js.map +1 -0
  32. package/dist/{chunk-A7IPON2V.cjs → chunk-BIKOJ4FH.cjs} +5 -5
  33. package/dist/{chunk-A7IPON2V.cjs.map → chunk-BIKOJ4FH.cjs.map} +1 -1
  34. package/dist/{chunk-HQYJINFV.js → chunk-CORIBKHG.js} +4 -4
  35. package/dist/{chunk-YEEHB4T6.cjs → chunk-CSYSYJCF.cjs} +40 -40
  36. package/dist/{chunk-YEEHB4T6.cjs.map → chunk-CSYSYJCF.cjs.map} +1 -1
  37. package/dist/{chunk-SIKCXKGB.cjs → chunk-DSDZ5T4P.cjs} +7 -7
  38. package/dist/{chunk-SIKCXKGB.cjs.map → chunk-DSDZ5T4P.cjs.map} +1 -1
  39. package/dist/{chunk-PNXNWGBY.js → chunk-GTWO5NAY.js} +2 -2
  40. package/dist/{chunk-SCCTJ2UI.js → chunk-HSACATCD.js} +4 -4
  41. package/dist/{chunk-PECGZWQR.cjs → chunk-KP5KRRD6.cjs} +3 -3
  42. package/dist/{chunk-PECGZWQR.cjs.map → chunk-KP5KRRD6.cjs.map} +1 -1
  43. package/dist/{chunk-2B6773QM.cjs → chunk-LILQJ3UZ.cjs} +1 -1
  44. package/dist/chunk-LILQJ3UZ.cjs.map +1 -0
  45. package/dist/{chunk-RZSA6MZL.cjs → chunk-NWX7MMF5.cjs} +15 -15
  46. package/dist/{chunk-RZSA6MZL.cjs.map → chunk-NWX7MMF5.cjs.map} +1 -1
  47. package/dist/{chunk-V3CNNMY3.cjs → chunk-P4WTZU6L.cjs} +18 -18
  48. package/dist/{chunk-V3CNNMY3.cjs.map → chunk-P4WTZU6L.cjs.map} +1 -1
  49. package/dist/{chunk-KLYHZRAD.js → chunk-PB3BTZDU.js} +2 -2
  50. package/dist/{chunk-FZOBQG6M.cjs → chunk-Q6E7VJ4W.cjs} +37 -36
  51. package/dist/chunk-Q6E7VJ4W.cjs.map +1 -0
  52. package/dist/{chunk-EYEIVYYF.cjs → chunk-QSCHCZL3.cjs} +7 -7
  53. package/dist/{chunk-EYEIVYYF.cjs.map → chunk-QSCHCZL3.cjs.map} +1 -1
  54. package/dist/{chunk-YJSKQ2ZV.js → chunk-RQLBHKXN.js} +3 -3
  55. package/dist/{chunk-CWQACI4Z.js → chunk-RW2LPL72.js} +2 -2
  56. package/dist/{chunk-DX6AOHNU.cjs → chunk-S2NHJDKQ.cjs} +77 -77
  57. package/dist/{chunk-DX6AOHNU.cjs.map → chunk-S2NHJDKQ.cjs.map} +1 -1
  58. package/dist/{chunk-AHDJMEEN.js → chunk-T2EGSZFN.js} +9 -8
  59. package/dist/chunk-T2EGSZFN.js.map +1 -0
  60. package/dist/{chunk-S52LJNMU.js → chunk-TKN5667N.js} +13 -13
  61. package/dist/chunk-TKN5667N.js.map +1 -0
  62. package/dist/{chunk-3BFX6HJM.js → chunk-UUYRJPGJ.js} +6 -6
  63. package/dist/{chunk-F6BYUO5U.js → chunk-VHG6AX5V.js} +3 -3
  64. package/dist/{chunk-FOQV6RCO.cjs → chunk-VQ523ORK.cjs} +29 -29
  65. package/dist/{chunk-FOQV6RCO.cjs.map → chunk-VQ523ORK.cjs.map} +1 -1
  66. package/dist/{chunk-PXF5J24T.js → chunk-XQ2B74SI.js} +2 -2
  67. package/dist/{chunk-H7JX2BG7.cjs → chunk-YHOIAA2E.cjs} +76 -76
  68. package/dist/chunk-YHOIAA2E.cjs.map +1 -0
  69. package/dist/{chunk-LLWBPPZ4.js → chunk-YZ2OIYQ3.js} +6 -6
  70. package/dist/components/case-studies/index.cjs +10 -10
  71. package/dist/components/case-studies/index.js +4 -4
  72. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  73. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  74. package/dist/components/chat/hooks/index.d.ts +0 -1
  75. package/dist/components/chat/hooks/index.d.ts.map +1 -1
  76. package/dist/components/chat/hooks/use-chat-history-hydration.d.ts +47 -0
  77. package/dist/components/chat/hooks/use-chat-history-hydration.d.ts.map +1 -0
  78. package/dist/components/chat/hooks/use-chat.d.ts +1 -0
  79. package/dist/components/chat/hooks/use-chat.d.ts.map +1 -1
  80. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +15 -48
  81. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  82. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +10 -53
  83. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  84. package/dist/components/chat/index.cjs +12 -4
  85. package/dist/components/chat/index.cjs.map +1 -1
  86. package/dist/components/chat/index.d.ts +1 -0
  87. package/dist/components/chat/index.d.ts.map +1 -1
  88. package/dist/components/chat/index.js +23 -15
  89. package/dist/components/chat/stream/chat-dialog-store.d.ts +184 -0
  90. package/dist/components/chat/stream/chat-dialog-store.d.ts.map +1 -0
  91. package/dist/components/chat/stream/chat-stream-reducer.d.ts +367 -0
  92. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -0
  93. package/dist/components/chat/stream/delta-batcher.d.ts +51 -0
  94. package/dist/components/chat/stream/delta-batcher.d.ts.map +1 -0
  95. package/dist/components/chat/stream/index.d.ts +17 -0
  96. package/dist/components/chat/stream/index.d.ts.map +1 -0
  97. package/dist/components/chat/stream/message-mutations.d.ts +77 -0
  98. package/dist/components/chat/stream/message-mutations.d.ts.map +1 -0
  99. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts +22 -0
  100. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts.map +1 -0
  101. package/dist/components/chat/types/api.types.d.ts +1 -81
  102. package/dist/components/chat/types/api.types.d.ts.map +1 -1
  103. package/dist/components/chat/types/processing.types.d.ts +2 -90
  104. package/dist/components/chat/types/processing.types.d.ts.map +1 -1
  105. package/dist/components/chat/types/unified-chat-state.types.d.ts +5 -0
  106. package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
  107. package/dist/components/chat/utils/chat-conversation-storage.d.ts +34 -0
  108. package/dist/components/chat/utils/chat-conversation-storage.d.ts.map +1 -0
  109. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts +34 -6
  110. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
  111. package/dist/components/chat/utils/index.d.ts +2 -3
  112. package/dist/components/chat/utils/index.d.ts.map +1 -1
  113. package/dist/components/chat/utils/process-historical-messages.d.ts +59 -11
  114. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  115. package/dist/components/contact/index.cjs +5 -5
  116. package/dist/components/contact/index.js +4 -4
  117. package/dist/components/docs/index.cjs +7 -7
  118. package/dist/components/docs/index.js +6 -6
  119. package/dist/components/embeds/index.cjs +5 -5
  120. package/dist/components/embeds/index.js +4 -4
  121. package/dist/components/faq/index.cjs +6 -6
  122. package/dist/components/faq/index.js +5 -5
  123. package/dist/components/features/index.cjs +4 -4
  124. package/dist/components/features/index.js +3 -3
  125. package/dist/components/help-center-pages/index.cjs +23 -23
  126. package/dist/components/help-center-pages/index.js +14 -14
  127. package/dist/components/index.cjs +182 -140
  128. package/dist/components/index.cjs.map +1 -1
  129. package/dist/components/index.js +66 -24
  130. package/dist/components/index.js.map +1 -1
  131. package/dist/components/navigation/index.cjs +4 -4
  132. package/dist/components/navigation/index.js +3 -3
  133. package/dist/components/onboarding-guides/index.cjs +8 -8
  134. package/dist/components/onboarding-guides/index.js +7 -7
  135. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  136. package/dist/components/related-content/index.cjs +6 -6
  137. package/dist/components/related-content/index.js +5 -5
  138. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  139. package/dist/components/tickets/index.cjs +7 -7
  140. package/dist/components/tickets/index.js +6 -6
  141. package/dist/components/ui/index.cjs +46 -4
  142. package/dist/components/ui/index.cjs.map +1 -1
  143. package/dist/components/ui/index.d.ts +1 -2
  144. package/dist/components/ui/index.d.ts.map +1 -1
  145. package/dist/components/ui/index.js +57 -15
  146. package/dist/components/ui/markdown/base-components.d.ts +51 -0
  147. package/dist/components/ui/markdown/base-components.d.ts.map +1 -0
  148. package/dist/components/ui/markdown/engine.d.ts +99 -0
  149. package/dist/components/ui/markdown/engine.d.ts.map +1 -0
  150. package/dist/components/ui/markdown/heading-ids.d.ts +65 -0
  151. package/dist/components/ui/markdown/heading-ids.d.ts.map +1 -0
  152. package/dist/components/ui/markdown/index.d.ts +18 -0
  153. package/dist/components/ui/markdown/index.d.ts.map +1 -0
  154. package/dist/components/ui/markdown/mermaid-diagram.d.ts +79 -0
  155. package/dist/components/ui/markdown/mermaid-diagram.d.ts.map +1 -0
  156. package/dist/components/ui/markdown/rich/embed-overrides.d.ts +14 -0
  157. package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +1 -0
  158. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts +40 -0
  159. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts.map +1 -0
  160. package/dist/components/ui/markdown/rich/shortcodes.d.ts +8 -0
  161. package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +1 -0
  162. package/dist/components/ui/markdown/sanitize.d.ts +153 -0
  163. package/dist/components/ui/markdown/sanitize.d.ts.map +1 -0
  164. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts +23 -0
  165. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts.map +1 -0
  166. package/dist/components/ui/markdown/streaming.d.ts +78 -0
  167. package/dist/components/ui/markdown/streaming.d.ts.map +1 -0
  168. package/dist/components/ui/markdown/text-size.d.ts +34 -0
  169. package/dist/components/ui/markdown/text-size.d.ts.map +1 -0
  170. package/dist/contexts/chat-runtime-context.d.ts +8 -0
  171. package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
  172. package/dist/contexts/index.cjs +2 -2
  173. package/dist/contexts/index.js +1 -1
  174. package/dist/hooks/index.cjs +3 -3
  175. package/dist/hooks/index.js +2 -2
  176. package/dist/index.cjs +62 -4
  177. package/dist/index.cjs.map +1 -1
  178. package/dist/index.js +73 -15
  179. package/dist/utils/index.cjs +169 -40
  180. package/dist/utils/index.cjs.map +1 -1
  181. package/dist/utils/index.d.ts +1 -0
  182. package/dist/utils/index.d.ts.map +1 -1
  183. package/dist/utils/index.js +162 -41
  184. package/dist/utils/index.js.map +1 -1
  185. package/dist/utils/markdown-fences.d.ts +42 -0
  186. package/dist/utils/markdown-fences.d.ts.map +1 -0
  187. package/dist/utils/markdown-heading-id.d.ts +85 -0
  188. package/dist/utils/markdown-heading-id.d.ts.map +1 -0
  189. package/dist/utils/markdown-section-extractor.d.ts.map +1 -1
  190. package/package.json +7 -1
  191. package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +307 -0
  192. package/src/chat-protocol/__tests__/chat-protocol.test.ts +516 -0
  193. package/src/chat-protocol/__tests__/env-flag.test.ts +52 -0
  194. package/src/chat-protocol/__tests__/ip-normalize.test.ts +136 -0
  195. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +228 -0
  196. package/src/chat-protocol/decode.ts +278 -0
  197. package/src/chat-protocol/encode.ts +71 -0
  198. package/src/chat-protocol/env-flag.ts +39 -0
  199. package/src/chat-protocol/events.ts +232 -0
  200. package/src/chat-protocol/frames.ts +245 -0
  201. package/src/chat-protocol/index.ts +21 -0
  202. package/src/chat-protocol/ip-normalize.ts +146 -0
  203. package/src/chat-protocol/nats-decoder.ts +252 -0
  204. package/src/components/chat/__tests__/chat-message-list.test.tsx +293 -5
  205. package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +207 -0
  206. package/src/components/chat/__tests__/chat-pending-turn.test.tsx +209 -0
  207. package/src/components/chat/chat-message-enhanced.tsx +111 -17
  208. package/src/components/chat/chat-message-list.tsx +358 -109
  209. package/src/components/chat/embeddable-chat.tsx +6 -0
  210. package/src/components/chat/hooks/.index.md +30 -33
  211. package/src/components/chat/hooks/.use-nats-chat-adapter.md +36 -55
  212. package/src/components/chat/hooks/__tests__/__snapshots__/conversation-id-persistence-golden.test.ts.snap +37 -0
  213. package/src/components/chat/hooks/__tests__/__snapshots__/sse-stream-golden.test.ts.snap +0 -0
  214. package/src/components/chat/hooks/__tests__/conversation-id-persistence-golden.test.ts +438 -0
  215. package/src/components/chat/hooks/__tests__/sse-stream-golden.test.ts +318 -0
  216. package/src/components/chat/hooks/index.ts +0 -1
  217. package/src/components/chat/hooks/use-chat-history-hydration.ts +142 -0
  218. package/src/components/chat/hooks/use-chat.ts +20 -0
  219. package/src/components/chat/hooks/use-nats-chat-adapter.ts +243 -823
  220. package/src/components/chat/hooks/use-sse-chat-adapter.ts +417 -774
  221. package/src/components/chat/index.ts +5 -0
  222. package/src/components/chat/stream/__tests__/__snapshots__/chat-stream-reducer-golden.test.ts.snap +940 -0
  223. package/src/components/chat/stream/__tests__/chat-dialog-store.test.ts +756 -0
  224. package/src/components/chat/stream/__tests__/chat-stream-reducer-golden.test.ts +460 -0
  225. package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +894 -0
  226. package/src/components/chat/stream/__tests__/delta-batcher.test.ts +255 -0
  227. package/src/components/chat/stream/__tests__/use-chat-stream-reducer.test.ts +98 -0
  228. package/src/components/chat/stream/chat-dialog-store.ts +536 -0
  229. package/src/components/chat/stream/chat-stream-reducer.ts +1796 -0
  230. package/src/components/chat/stream/delta-batcher.ts +159 -0
  231. package/src/components/chat/stream/index.ts +55 -0
  232. package/src/components/chat/stream/message-mutations.ts +338 -0
  233. package/src/components/chat/stream/use-chat-stream-reducer.ts +126 -0
  234. package/src/components/chat/thinking-display.tsx +1 -1
  235. package/src/components/chat/types/.api.types.md +39 -49
  236. package/src/components/chat/types/.processing.types.md +29 -58
  237. package/src/components/chat/types/api.types.ts +6 -73
  238. package/src/components/chat/types/processing.types.ts +11 -52
  239. package/src/components/chat/types/unified-chat-state.types.ts +6 -0
  240. package/src/components/chat/utils/.index.md +31 -40
  241. package/src/components/chat/utils/__tests__/__snapshots__/process-historical-messages-golden.test.ts.snap +420 -0
  242. package/src/components/chat/utils/__tests__/__snapshots__/segment-accumulator-golden.test.ts.snap +605 -0
  243. package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +317 -0
  244. package/src/components/chat/utils/__tests__/segment-accumulator-golden.test.ts +270 -0
  245. package/src/components/chat/utils/chat-conversation-storage.ts +83 -0
  246. package/src/components/chat/utils/extract-incomplete-message-state.ts +63 -5
  247. package/src/components/chat/utils/index.ts +5 -8
  248. package/src/components/chat/utils/process-historical-messages.ts +352 -384
  249. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +22 -4
  250. package/src/components/shared/legal-document/legal-document-page.tsx +1 -1
  251. package/src/components/shared/product-release/release-detail-page.tsx +19 -4
  252. package/src/components/ui/__tests__/__snapshots__/markdown-parity.test.tsx.snap +2420 -0
  253. package/src/components/ui/__tests__/markdown-parity.test.tsx +2528 -0
  254. package/src/components/ui/index.ts +1 -2
  255. package/src/components/ui/markdown/__tests__/mermaid-security.test.ts +115 -0
  256. package/src/components/ui/markdown/__tests__/mermaid-stale-render.test.tsx +95 -0
  257. package/src/components/ui/markdown/__tests__/sanitize-invariant.test.ts +309 -0
  258. package/src/components/ui/markdown/__tests__/streaming.test.tsx +388 -0
  259. package/src/components/ui/markdown/base-components.tsx +360 -0
  260. package/src/components/ui/markdown/engine.tsx +315 -0
  261. package/src/components/ui/markdown/heading-ids.ts +239 -0
  262. package/src/components/ui/markdown/index.ts +49 -0
  263. package/src/components/ui/markdown/mermaid-diagram.tsx +291 -0
  264. package/src/components/ui/markdown/rich/embed-overrides.tsx +199 -0
  265. package/src/components/ui/markdown/rich/rich-markdown-renderer.tsx +184 -0
  266. package/src/components/ui/markdown/rich/shortcodes.ts +170 -0
  267. package/src/components/ui/markdown/sanitize.ts +3499 -0
  268. package/src/components/ui/markdown/simple-markdown-renderer.tsx +28 -0
  269. package/src/components/ui/markdown/streaming.ts +362 -0
  270. package/src/components/ui/markdown/text-size.ts +106 -0
  271. package/src/components/ui/release-changelog-section.tsx +1 -1
  272. package/src/components/ui/ticket-info-section.tsx +1 -1
  273. package/src/contexts/chat-runtime-context.tsx +8 -0
  274. package/src/utils/index.ts +1 -0
  275. package/src/utils/markdown-fences.ts +127 -0
  276. package/src/utils/markdown-heading-id.ts +348 -0
  277. package/src/utils/markdown-section-extractor.ts +41 -56
  278. package/dist/chunk-2B6773QM.cjs.map +0 -1
  279. package/dist/chunk-5RXKOGH6.cjs.map +0 -1
  280. package/dist/chunk-77DFPD35.js.map +0 -1
  281. package/dist/chunk-AHDJMEEN.js.map +0 -1
  282. package/dist/chunk-FZOBQG6M.cjs.map +0 -1
  283. package/dist/chunk-H7JX2BG7.cjs.map +0 -1
  284. package/dist/chunk-KFQXO7QB.js.map +0 -1
  285. package/dist/chunk-S52LJNMU.js.map +0 -1
  286. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts +0 -6
  287. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +0 -1
  288. package/dist/components/chat/utils/chunk-parser.d.ts +0 -25
  289. package/dist/components/chat/utils/chunk-parser.d.ts.map +0 -1
  290. package/dist/components/ui/rich-markdown-renderer.d.ts +0 -34
  291. package/dist/components/ui/rich-markdown-renderer.d.ts.map +0 -1
  292. package/dist/components/ui/simple-markdown-renderer.d.ts +0 -73
  293. package/dist/components/ui/simple-markdown-renderer.d.ts.map +0 -1
  294. package/src/components/chat/hooks/.use-realtime-chunk-processor.md +0 -77
  295. package/src/components/chat/hooks/use-realtime-chunk-processor.ts +0 -479
  296. package/src/components/chat/utils/.chunk-parser.md +0 -62
  297. package/src/components/chat/utils/chunk-parser.ts +0 -256
  298. package/src/components/ui/.simple-markdown-renderer.md +0 -52
  299. package/src/components/ui/rich-markdown-renderer.tsx +0 -1223
  300. package/src/components/ui/simple-markdown-renderer.tsx +0 -964
  301. /package/dist/{chunk-AJNDDXKC.js.map → chunk-4NWARV5I.js.map} +0 -0
  302. /package/dist/{chunk-HQYJINFV.js.map → chunk-CORIBKHG.js.map} +0 -0
  303. /package/dist/{chunk-PNXNWGBY.js.map → chunk-GTWO5NAY.js.map} +0 -0
  304. /package/dist/{chunk-SCCTJ2UI.js.map → chunk-HSACATCD.js.map} +0 -0
  305. /package/dist/{chunk-KLYHZRAD.js.map → chunk-PB3BTZDU.js.map} +0 -0
  306. /package/dist/{chunk-YJSKQ2ZV.js.map → chunk-RQLBHKXN.js.map} +0 -0
  307. /package/dist/{chunk-CWQACI4Z.js.map → chunk-RW2LPL72.js.map} +0 -0
  308. /package/dist/{chunk-3BFX6HJM.js.map → chunk-UUYRJPGJ.js.map} +0 -0
  309. /package/dist/{chunk-F6BYUO5U.js.map → chunk-VHG6AX5V.js.map} +0 -0
  310. /package/dist/{chunk-PXF5J24T.js.map → chunk-XQ2B74SI.js.map} +0 -0
  311. /package/dist/{chunk-LLWBPPZ4.js.map → chunk-YZ2OIYQ3.js.map} +0 -0
@@ -1,11 +1,11 @@
1
1
  "use client"
2
2
 
3
- import { useRef, useEffect, useLayoutEffect, useImperativeHandle, forwardRef } from "react"
3
+ import { useRef, useState, useEffect, useLayoutEffect, useImperativeHandle, forwardRef } from "react"
4
4
  import { useStickToBottom } from "use-stick-to-bottom"
5
5
  import { cn } from "../../utils/cn"
6
6
  import { ChatMessageEnhanced } from "./chat-message-enhanced"
7
7
  import { ChatMessageListSkeleton } from "./chat-message-skeleton"
8
- import { DotsLoaderIcon } from "../icons-v2-generated"
8
+ import { DotsLoaderIcon, Arrow02DownIcon } from "../icons-v2-generated"
9
9
  import { CyclingPhrase } from "./cycling-phrase"
10
10
  import type { ChatMessageListProps } from "./types"
11
11
  import { SCROLL_ANCHOR } from "./types/message.types"
@@ -27,6 +27,11 @@ const STREAMING_WORDS = [
27
27
  'Riffing',
28
28
  ] as const
29
29
 
30
+ /** Distance from the bottom (px) still counted as "at the bottom".
31
+ * Mirrors `use-stick-to-bottom`'s internal `STICK_TO_BOTTOM_OFFSET_PX`
32
+ * so the jump-to-bottom button and the library agree on the boundary. */
33
+ const BOTTOM_THRESHOLD_PX = 70
34
+
30
35
  /*
31
36
  * Stick-to-bottom: `use-stick-to-bottom` (stackblitz-labs)
32
37
  *
@@ -53,6 +58,9 @@ const STREAMING_WORDS = [
53
58
  * chat / sent a message; show me the bottom immediately").
54
59
  * - **Load-more sentinel** (top-of-list IntersectionObserver) for
55
60
  * infinite-scroll-UP. Distinct concern from stick-to-bottom.
61
+ * - **Bottom-follow intent** (see `followBottomRef` below). The
62
+ * library's follow is gated on its own `isAtBottom` flag, which this
63
+ * list's layout can lose silently; we own the INTENT and re-assert.
56
64
  *
57
65
  * `useStickToBottom`'s returned refs are MUTABLE refs + ref callbacks
58
66
  * — pass them directly as `ref={scrollRef}` / `ref={contentRef}`.
@@ -142,6 +150,45 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
142
150
  const dialogIdRef = useRef(dialogId)
143
151
  dialogIdRef.current = dialogId
144
152
 
153
+ // ---- Bottom-follow intent (list-owned lock) ----------------------
154
+ // The library follows the bottom only while ITS OWN `isAtBottom` flag
155
+ // is true, and this list's layout loses that flag silently in two
156
+ // ways:
157
+ //
158
+ // 1. **The scroller's own box changes.** The library's
159
+ // ResizeObserver watches `contentRef` (INSIDE the scroller) only.
160
+ // The streaming loader unmounting and the host's source-chip row
161
+ // mounting — both siblings BELOW the scroller (see render) —
162
+ // change the scroller's `clientHeight`, which moves the bottom
163
+ // without emitting anything the library can observe.
164
+ // 2. **Resize-induced scroll events read as a user gesture.**
165
+ // Fetch-mode entity cards mount as a skeleton and settle to a
166
+ // different height; a shrink clamps `scrollTop` down, and the
167
+ // library's scroll handler can see that as a scroll-UP and
168
+ // permanently escape the lock (its `resizeDifference` guard is a
169
+ // `setTimeout(…, 1)` race, so this is intermittent).
170
+ //
171
+ // Either way the thread stops following mid-turn. It bites hardest on
172
+ // card-heavy answers — a slash-command listing (`/getting-started`)
173
+ // renders a dozen fetch-mode cards with cover images that keep
174
+ // resolving for SECONDS after the stream ends, so everything after
175
+ // the lock was lost lands below the fold.
176
+ //
177
+ // Fix: this list owns the INTENT. Sending a message (or opening a
178
+ // dialog) arms it; only a real user scroll-up gesture — or a
179
+ // top-anchored turn, which deliberately parks elsewhere — disarms it.
180
+ // While armed, ANY geometry change re-asserts `scrollToBottom`, and
181
+ // because that call also re-sets the library's `isAtBottom`, a lost
182
+ // lock self-heals instead of being permanent.
183
+ const followBottomRef = useRef(false)
184
+
185
+ // Drives the jump-to-bottom affordance. Derived from GEOMETRY, not
186
+ // from the library's `isAtBottom`: that flag is the one this list
187
+ // loses silently (see above), and a stale `true` would hide the
188
+ // button in exactly the situation it exists for. Threshold mirrors
189
+ // the library's own `STICK_TO_BOTTOM_OFFSET_PX`.
190
+ const [atBottom, setAtBottom] = useState(true)
191
+
145
192
  // Tracks previous render state for explicit "force-stick" decisions
146
193
  // (dialog change, first message, new user message). The library
147
194
  // covers ongoing streaming; these are about INTENT transitions.
@@ -178,11 +225,13 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
178
225
 
179
226
  if (dialogChanged) {
180
227
  // Opening a different chat — instant snap to bottom.
228
+ followBottomRef.current = true
181
229
  void scrollToBottom({ animation: 'instant', ignoreEscapes: true })
182
230
  return
183
231
  }
184
232
  if (newCount > 0 && prevCount === 0) {
185
233
  // First message arrived in an empty chat — instant snap.
234
+ followBottomRef.current = true
186
235
  void scrollToBottom({ animation: 'instant', ignoreEscapes: true })
187
236
  return
188
237
  }
@@ -207,54 +256,188 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
207
256
  const newSlice = prevLastIdx >= 0 ? messages.slice(prevLastIdx + 1) : messages.slice(prevCount)
208
257
  const hasNewUser = newSlice.some((m) => m.role === 'user')
209
258
  if (hasNewUser) {
259
+ // Arms the follow intent for the WHOLE turn: the answer's
260
+ // async growth (attachment images, streamed tokens, entity
261
+ // cards resolving out of their skeletons long after the
262
+ // stream ends) is re-asserted by the follow effect below.
263
+ // This replaces the old per-`<img>` `load`-handler hack —
264
+ // same defect class, but the ResizeObserver sees every
265
+ // source of growth, not just images present at send time.
266
+ followBottomRef.current = true
210
267
  void scrollToBottom({ animation: 'instant', ignoreEscapes: true })
211
-
212
- // Image-attachment growth follow-up: if the new user message
213
- // carries inline `<img>` elements (chat-attachment markdown
214
- // emits `![alt](/api/storage/view/...)` which the markdown
215
- // renderer turns into `<img>` / Next.js `<Image>`), those
216
- // images load AFTER the initial paint. Each image-load grows
217
- // the bubble height, but `useStickToBottom`'s `resize:
218
- // 'smooth'` spring sometimes loses anchor under rapid
219
- // resize events from Next.js Image's progressive
220
- // dimensioning (observed: scroll lands 50-100px short of
221
- // bottom — exactly one image-load short).
222
- //
223
- // Fix: after the initial scrollToBottom snaps, scan the
224
- // content area for `<img>` elements that haven't loaded
225
- // yet, and attach one-time `load` handlers that re-trigger
226
- // `scrollToBottom`. Idempotent — runs for any image,
227
- // affects only the new-user-message diff (effect dep is
228
- // `messages`).
229
- //
230
- // `requestAnimationFrame` so the DOM has the new bubble
231
- // before we query. Without rAF the new-message `<img>`s
232
- // haven't been committed yet — querySelectorAll returns
233
- // an empty set.
234
- requestAnimationFrame(() => {
235
- const el = contentRef.current
236
- if (!el) return
237
- const imgs = el.querySelectorAll('img')
238
- imgs.forEach((img) => {
239
- if (img.complete) return
240
- const onLoad = () => {
241
- img.removeEventListener('load', onLoad)
242
- img.removeEventListener('error', onLoad)
243
- // `ignoreEscapes: true` because the user may have
244
- // accidentally moved scroll during the image-load
245
- // window; we want their fresh send to be visible.
246
- void scrollToBottom({ animation: 'instant', ignoreEscapes: true })
247
- }
248
- img.addEventListener('load', onLoad, { once: true })
249
- img.addEventListener('error', onLoad, { once: true })
250
- })
251
- })
252
268
  }
253
269
  // Assistant-only new messages → the library's resize-watch
254
270
  // already keeps the bottom locked when the user hasn't
255
271
  // escaped. No explicit call needed; spring animation runs.
256
272
  }
257
- }, [autoScroll, messages, dialogId, scrollToBottom, contentRef])
273
+ }, [autoScroll, messages, dialogId, scrollToBottom])
274
+
275
+ // ---- Follow the bottom while the intent is armed -----------------
276
+ // Installs once per mount (all deps are stable). See the
277
+ // `followBottomRef` comment above for WHY this exists.
278
+ useEffect(() => {
279
+ if (!autoScroll) return
280
+ const scroller = scrollRef.current
281
+ const content = contentRef.current
282
+ if (!scroller || !content) return
283
+
284
+ // Re-assert on ANY geometry change. Deliberately WITHOUT
285
+ // `ignoreEscapes`: the library must stay free to hand control back
286
+ // on a real gesture mid-animation. Re-asserting is precisely what
287
+ // re-sets its `isAtBottom`, so a silently-lost lock recovers on the
288
+ // next resize instead of staying broken for the rest of the turn.
289
+ // Cheap to call repeatedly — the library returns the in-flight
290
+ // promise when an animation with the same behaviour is running.
291
+ const measure = () => {
292
+ const distance = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight
293
+ setAtBottom(distance <= BOTTOM_THRESHOLD_PX)
294
+ }
295
+
296
+ const reassert = () => {
297
+ if (followBottomRef.current) void scrollToBottom({ animation: 'smooth' })
298
+ // Measured on every geometry change, follow armed or not: a
299
+ // sibling growing below the scroller moves the bottom without
300
+ // any scroll event, and the button has to notice.
301
+ measure()
302
+ }
303
+
304
+ const ro = new ResizeObserver(reassert)
305
+ ro.observe(content) // tokens, entity cards, images
306
+ ro.observe(scroller) // streaming loader / source chips below it
307
+
308
+ // --- user-intent disarm ---------------------------------------
309
+ // Only a deliberate move AWAY from the bottom releases the lock.
310
+ // Resize-derived scroll events are NOT gestures, which is exactly
311
+ // the distinction the library's heuristic gets wrong.
312
+ const disarm = () => {
313
+ followBottomRef.current = false
314
+ }
315
+ const onWheel = (e: WheelEvent) => {
316
+ if (e.deltaY < 0) disarm()
317
+ }
318
+ const onKeyDown = (e: KeyboardEvent) => {
319
+ if (e.key === 'ArrowUp' || e.key === 'PageUp' || e.key === 'Home') disarm()
320
+ }
321
+ let touchY: number | null = null
322
+ const onTouchStart = (e: TouchEvent) => {
323
+ touchY = e.touches[0]?.clientY ?? null
324
+ }
325
+ const onTouchMove = (e: TouchEvent) => {
326
+ const y = e.touches[0]?.clientY
327
+ if (y == null || touchY == null) return
328
+ if (y > touchY) disarm() // finger travels DOWN → content moves up
329
+ touchY = y
330
+ }
331
+ // Scrollbar drag emits neither wheel nor touch — a scrollTop
332
+ // DECREASE while the pointer is held down is the same intent.
333
+ //
334
+ // ALL pointer listeners are on `window`, deliberately. The scroller is a
335
+ // native `overflow-y-auto` div (OverlayScrollbars was reverted in #1512),
336
+ // so its native scrollbar IS part of `scroller` and a `pointerdown` on the
337
+ // thumb does reach it — but `window` is a safe superset that also survives
338
+ // any future host component (or a return to a custom-scrollbar library
339
+ // that renders the thumb OUTSIDE the viewport) without stranding
340
+ // `pointerDown` at `false` and reintroducing the yank-back-mid-drag bug.
341
+ //
342
+ // The POINTER listeners use CAPTURE phase, so the guarantee is structural
343
+ // rather than contingent on nothing between us and `window` ever calling
344
+ // `stopPropagation` on the pointer path.
345
+ //
346
+ // `blur` deliberately does NOT use capture. Focus events don't bubble
347
+ // but they DO capture, so a capture listener on `window` fires for
348
+ // EVERY element blur in the document (the focus-delegation idiom) —
349
+ // any focus change during a hold, e.g. the chat input blurring as the
350
+ // user presses down on the scrollbar, would clear `pointerDown` and
351
+ // re-open the very yank-back bug this block prevents. A TRUE window
352
+ // blur targets `window` itself, so an at-target (bubble-phase)
353
+ // listener still receives it: capture buys nothing and costs
354
+ // correctness. Verified in jsdom: descendant blur reaches the capture
355
+ // listener only; window blur reaches both.
356
+ //
357
+ // `pointerDown` must be cleared by every way a drag can END, not just
358
+ // `pointerup`: a button released OUTSIDE the window, or the tab losing
359
+ // focus mid-drag, delivers no `pointerup` at all and would leave the
360
+ // flag stuck true. From then on ANY resize-induced scrollTop clamp — a
361
+ // fetch-mode card settling from skeleton to a shorter height, i.e. the
362
+ // exact case this whole block exists for — reads as a scroll-up gesture
363
+ // and drops the lock. Same for merely HOLDING the button down (text
364
+ // selection) while a card shrinks. Hence `pointercancel` + window
365
+ // `blur`.
366
+ let pointerDown = false
367
+ let lastScrollTop = scroller.scrollTop
368
+ const onPointerDown = () => {
369
+ pointerDown = true
370
+ }
371
+ // A drag that RELEASES at the live end expresses the same intent as
372
+ // clicking jump-to-bottom. The scroll events during that drag can't
373
+ // re-arm (see the gate in `onScroll`), and releasing the button emits
374
+ // no further `scroll`, so the re-arm is evaluated once here, when the
375
+ // gesture is actually over.
376
+ const rearmIfAtBottom = () => {
377
+ if (followBottomRef.current) return
378
+ const distance = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight
379
+ if (distance <= BOTTOM_THRESHOLD_PX) followBottomRef.current = true
380
+ }
381
+ const endPointer = () => {
382
+ pointerDown = false
383
+ rearmIfAtBottom()
384
+ }
385
+ const onScroll = () => {
386
+ const st = scroller.scrollTop
387
+ if (pointerDown && st < lastScrollTop) disarm()
388
+ lastScrollTop = st
389
+ // Landing at the bottom by hand is the SAME expressed intent as
390
+ // clicking jump-to-bottom, so it re-arms a lock an earlier scroll-up
391
+ // released. Without it the thread sits at-bottom (button hidden,
392
+ // because `atBottom` is true) and then silently drifts away as the
393
+ // answer grows.
394
+ //
395
+ // Gated on the drag being OVER (`!pointerDown`). A mid-drag `scroll`
396
+ // is not a gesture that has ended, and a short scrollbar drag that
397
+ // starts at the live end stays inside the 70px band — so re-arming
398
+ // here would undo the `disarm()` two lines above on the very same
399
+ // event and yank the reader back down mid-drag. Releasing the button
400
+ // at the bottom still re-arms on the next scroll (or, if none
401
+ // follows, on the next deliberate return to the end).
402
+ if (
403
+ !pointerDown &&
404
+ !followBottomRef.current &&
405
+ scroller.scrollHeight - st - scroller.clientHeight <= BOTTOM_THRESHOLD_PX
406
+ ) {
407
+ followBottomRef.current = true
408
+ }
409
+ measure()
410
+ }
411
+ measure()
412
+
413
+ const winCapture = { capture: true, passive: true } as const
414
+
415
+ scroller.addEventListener('wheel', onWheel, { passive: true })
416
+ scroller.addEventListener('keydown', onKeyDown)
417
+ scroller.addEventListener('touchstart', onTouchStart, { passive: true })
418
+ scroller.addEventListener('touchmove', onTouchMove, { passive: true })
419
+ window.addEventListener('pointerdown', onPointerDown, winCapture)
420
+ scroller.addEventListener('scroll', onScroll, { passive: true })
421
+ window.addEventListener('pointerup', endPointer, winCapture)
422
+ window.addEventListener('pointercancel', endPointer, winCapture)
423
+ // Bubble phase, NOT capture — see the note above.
424
+ window.addEventListener('blur', endPointer, { passive: true })
425
+
426
+ return () => {
427
+ ro.disconnect()
428
+ scroller.removeEventListener('wheel', onWheel)
429
+ scroller.removeEventListener('keydown', onKeyDown)
430
+ scroller.removeEventListener('touchstart', onTouchStart)
431
+ scroller.removeEventListener('touchmove', onTouchMove)
432
+ // `capture` must match the add call or the listener is not removed.
433
+ window.removeEventListener('pointerdown', onPointerDown, { capture: true })
434
+ scroller.removeEventListener('scroll', onScroll)
435
+ window.removeEventListener('pointerup', endPointer, { capture: true })
436
+ window.removeEventListener('pointercancel', endPointer, { capture: true })
437
+ // Added WITHOUT capture — the remove must match.
438
+ window.removeEventListener('blur', endPointer)
439
+ }
440
+ }, [autoScroll, scrollRef, contentRef, scrollToBottom])
258
441
 
259
442
  // ---- Passive-demo hard pin (pinBottom) ---------------------------
260
443
  // Scripted in-page replays (the Fae/Mingo demos) have no human at the
@@ -384,7 +567,10 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
384
567
  // `stopScroll` MUST precede scrollIntoView — the imperative scrollTop
385
568
  // write is what the library's geometry tracker uses to flip
386
569
  // `escapedFromLock`, so any in-flight scrollToBottom spring is
387
- // first cancelled.
570
+ // first cancelled. The list-owned follow intent is released for the
571
+ // same reason: this turn deliberately parks at the message TOP, so
572
+ // its own async growth must NOT drag the reader to the bottom.
573
+ followBottomRef.current = false
388
574
  stopScroll()
389
575
  // Instant (not smooth): the markdown body contains async <img>
390
576
  // children whose loads race a 300ms smooth animation and leave
@@ -528,75 +714,138 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
528
714
  !(pendingApprovals && pendingApprovals.length > 0)
529
715
 
530
716
  return (
531
- <div className="relative flex-1 min-h-0 flex flex-col">
532
- <div
533
- ref={setScrollRef}
534
- className={cn(
535
- "flex h-full w-full flex-col overflow-y-auto overflow-x-hidden flex-1",
536
- // `overscroll-contain` (default ON, opt-out via `overscrollContain={false}`):
537
- // reaching the top/bottom of the thread must NOT chain the wheel/
538
- // touch scroll to the page behind the chat — e.g. the company-hub
539
- // deck (native body scroll + sticky slide panels) would otherwise
540
- // advance slides while you scroll the chat (fixed in #1501; kept as
541
- // the prop default here). Passive in-page DEMO chats disable it so
542
- // the surrounding page keeps scrolling normally when the pointer is
543
- // over the (non-interactive) thread.
544
- overscrollContain && "overscroll-contain",
545
- "scrollbar-thin scrollbar-track-transparent scrollbar-thumb-ods-border/30 hover:scrollbar-thumb-ods-text-secondary/30",
546
- className,
547
- )}
548
- {...props}
549
- >
717
+ <div className="flex-1 min-h-0 flex flex-col">
718
+ {/* Positioning box for the jump-to-bottom button: it wraps ONLY the
719
+ scroller, so `absolute bottom-…` really is the scroller's lower
720
+ edge. The outer flex column also holds the streaming loader and the
721
+ approvals bar, and anchoring against THAT box put the button over
722
+ the loader for the whole of every stream — precisely when it is
723
+ most likely to be visible. */}
724
+ <div className="relative flex-1 min-h-0 flex flex-col">
550
725
  <div
551
- ref={setContentRef}
726
+ ref={setScrollRef}
552
727
  className={cn(
553
- // `fullWidth=true` drops the centered-narrow column for
554
- // side-panel hosts (e.g. multi-platform-hub Mingo). Same
555
- // semantics as ChatHeader / ChatInput / ChatFooter.
556
- fullWidth
557
- ? "flex w-full flex-col pb-[var(--spacing-system-xs)] min-w-0"
558
- : "mx-auto flex w-full max-w-ods-content-narrow flex-col pb-[var(--spacing-system-xs)] min-w-0",
559
- contentClassName ?? "px-[var(--spacing-system-m)]",
728
+ "flex h-full w-full flex-col overflow-y-auto overflow-x-hidden flex-1",
729
+ // `overscroll-contain` (default ON, opt-out via `overscrollContain={false}`):
730
+ // reaching the top/bottom of the thread must NOT chain the wheel/
731
+ // touch scroll to the page behind the chat — e.g. the company-hub
732
+ // deck (native body scroll + sticky slide panels) would otherwise
733
+ // advance slides while you scroll the chat (fixed in #1501; kept as
734
+ // the prop default here). Passive in-page DEMO chats disable it so
735
+ // the surrounding page keeps scrolling normally when the pointer is
736
+ // over the (non-interactive) thread.
737
+ overscrollContain && "overscroll-contain",
738
+ "scrollbar-thin scrollbar-track-transparent scrollbar-thumb-ods-border/30 hover:scrollbar-thumb-ods-text-secondary/30",
739
+ className,
560
740
  )}
561
- style={{ minHeight: '100%' }}
741
+ {...props}
562
742
  >
563
- {hasNextPage && (
564
- <div ref={sentinelRef} className="h-px" />
565
- )}
566
- <div className="flex-1" />
567
- {messages.map((message, index) => {
568
- // Hidden messages (synthetic continuation prompts the host
569
- // injects after an approval card) are part of the API
570
- // conversation history but never render. Skipping here
571
- // keeps the visible thread coherent see
572
- // `Message.hidden` doc-comment in message.types.ts.
573
- if (message.hidden) return null
574
- return (
575
- <ChatMessageEnhanced
576
- key={message.id}
577
- ref={getRegisterMessageEl(message.id)}
578
- role={message.role}
579
- name={message.name}
580
- content={message.content}
581
- timestamp={message.timestamp}
582
- isTyping={index === messages.length - 1 && isTyping && message.role === 'assistant'}
583
- avatar={showAvatars ? message.avatar : null}
584
- showAvatar={showAvatars}
585
- assistantType={message.assistantType || assistantType}
586
- approvalVariant={approvalVariant}
587
- authorType={message.authorType}
588
- assistantIcon={message.role !== 'user' ? assistantIcon : undefined}
589
- chatRefs={message.chatRefs}
590
- contextItems={message.contextItems}
591
- resolveContextIcon={resolveContextIcon}
592
- renderContextItem={renderContextItem}
593
- renderMention={renderMention}
594
- renderEntityCard={renderEntityCard}
595
- NavLinkAnchor={NavLinkAnchor}
596
- />
597
- )
598
- })}
743
+ <div
744
+ ref={setContentRef}
745
+ className={cn(
746
+ // `fullWidth=true` drops the centered-narrow column for
747
+ // side-panel hosts (e.g. multi-platform-hub Mingo). Same
748
+ // semantics as ChatHeader / ChatInput / ChatFooter.
749
+ fullWidth
750
+ ? "flex w-full flex-col pb-[var(--spacing-system-xs)] min-w-0"
751
+ : "mx-auto flex w-full max-w-ods-content-narrow flex-col pb-[var(--spacing-system-xs)] min-w-0",
752
+ contentClassName ?? "px-[var(--spacing-system-m)]",
753
+ )}
754
+ style={{ minHeight: '100%' }}
755
+ >
756
+ {hasNextPage && (
757
+ <div ref={sentinelRef} className="h-px" />
758
+ )}
759
+ <div className="flex-1" />
760
+ {messages.map((message, index) => {
761
+ // Hidden messages (synthetic continuation prompts the host
762
+ // injects after an approval card) are part of the API
763
+ // conversation history but never render. Skipping here
764
+ // keeps the visible thread coherent — see
765
+ // `Message.hidden` doc-comment in message.types.ts.
766
+ if (message.hidden) return null
767
+ // ONE OWNER for the pending turn. Both send paths mint an empty
768
+ // assistant placeholder (the accumulation target — it MUST stay
769
+ // in state) and flip the phase to 'thinking'. Rendering it here
770
+ // too would put an author label with no body in the transcript
771
+ // WHILE the footer loader below already represents the same
772
+ // pending turn — two owners, and an `aria-live` log that
773
+ // announces a bare "Mingo". The footer loader wins (it is
774
+ // already `role="status" aria-live="polite"`, sits outside the
775
+ // scroller so it can't jitter the thread, and cycles the
776
+ // progress phrase), so the placeholder is not rendered AT ALL
777
+ // until it carries something visible — removed from the live
778
+ // region, not merely hidden. Mirrors `endSseTurn`, which prunes
779
+ // an empty trailing assistant AFTER a turn; this closes the same
780
+ // window BEFORE the first token. Any non-text segment (tool
781
+ // execution, approval card, thinking) counts as visible.
782
+ const isEmptyPendingTurn =
783
+ index === messages.length - 1 &&
784
+ isTyping &&
785
+ message.role === 'assistant' &&
786
+ !hasNonEmptyContent(message.content) &&
787
+ (!Array.isArray(message.content) ||
788
+ message.content.every((s) => s.type === 'text'))
789
+ if (isEmptyPendingTurn) return null
790
+ return (
791
+ <ChatMessageEnhanced
792
+ key={message.id}
793
+ ref={getRegisterMessageEl(message.id)}
794
+ role={message.role}
795
+ name={message.name}
796
+ content={message.content}
797
+ timestamp={message.timestamp}
798
+ isTyping={index === messages.length - 1 && isTyping && message.role === 'assistant'}
799
+ avatar={showAvatars ? message.avatar : null}
800
+ showAvatar={showAvatars}
801
+ assistantType={message.assistantType || assistantType}
802
+ approvalVariant={approvalVariant}
803
+ authorType={message.authorType}
804
+ assistantIcon={message.role !== 'user' ? assistantIcon : undefined}
805
+ chatRefs={message.chatRefs}
806
+ contextItems={message.contextItems}
807
+ resolveContextIcon={resolveContextIcon}
808
+ renderContextItem={renderContextItem}
809
+ renderMention={renderMention}
810
+ renderEntityCard={renderEntityCard}
811
+ NavLinkAnchor={NavLinkAnchor}
812
+ />
813
+ )
814
+ })}
815
+ </div>
599
816
  </div>
817
+
818
+ {/* Jump to bottom. The manual escape hatch from auto-scroll that
819
+ every 2026 chat surface ships (AI Elements calls it
820
+ `ConversationScrollButton`) and the WCAG 2.2.2 counterpart to
821
+ moving content: the reader can always get back to the live
822
+ end without hunting for it. Positioned inside the scroller's
823
+ OWN `relative` wrapper above, so `bottom` really is the
824
+ scroller's lower edge and not the outer column's (which also
825
+ carries the streaming loader). It costs the thread no layout
826
+ height either way — a sibling would shrink the scroller and
827
+ move the very bottom it points at. Hidden while at the bottom
828
+ and in passive demo hosts. */}
829
+ {autoScroll && !atBottom && messages.length > 0 && (
830
+ <button
831
+ type="button"
832
+ onClick={() => {
833
+ // Clicking IS the intent to return to the live end, so it
834
+ // re-arms the follow lock a scroll-up had released.
835
+ followBottomRef.current = true
836
+ void scrollToBottom({ animation: 'smooth' })
837
+ }}
838
+ aria-label="Scroll to latest message"
839
+ className={cn(
840
+ "absolute bottom-[var(--spacing-system-s)] left-1/2 -translate-x-1/2 z-10",
841
+ "flex items-center justify-center size-9 rounded-full",
842
+ "bg-ods-card text-ods-text-primary border border-ods-border shadow-lg",
843
+ "hover:bg-ods-bg-hover transition-colors",
844
+ )}
845
+ >
846
+ <Arrow02DownIcon size={16} />
847
+ </button>
848
+ )}
600
849
  </div>
601
850
 
602
851
  {/* Footer-pinned streaming loader — outside the scroller so it
@@ -1531,6 +1531,12 @@ function EmbeddableChatInner({
1531
1531
  content: m.segments && m.segments.length > 0 ? m.segments : m.content,
1532
1532
  timestamp,
1533
1533
  assistantType: m.role === 'assistant' ? ('mingo' as const) : undefined,
1534
+ // `hidden` is load-bearing, NOT cosmetic: it carries synthetic rows
1535
+ // (e.g. an auto-continuation directive) that the LLM must see but the
1536
+ // reader must not. Dropping it here made the raw directive text render
1537
+ // as an ordinary bubble. This field-by-field rebuild has to forward it
1538
+ // explicitly — see `Message.hidden` and `chat-message-list`'s skip.
1539
+ ...(m.hidden ? { hidden: true } : {}),
1534
1540
  ...(m.chatRefs ? { chatRefs: m.chatRefs } : {}),
1535
1541
  ...(m.scrollAnchor ? { scrollAnchor: m.scrollAnchor } : {}),
1536
1542
  // Forward attached context items so the user bubble renders its chips.
@@ -1,49 +1,46 @@
1
- <!-- source-hash: 31ada6e53cd71ce93d35361e48559a56 -->
2
- Barrel export file (`'use client'`) that re-exports all React hooks from the chat hooks module, providing a single import surface for consumers.
1
+ <!-- source-hash: d1cebe46eff04115f214c16b75a69467 -->
2
+ Barrel export file for the chat hooks module, re-exporting all public hooks from their individual files into a single `'use client'` entry point.
3
3
 
4
4
  ## Key Components
5
5
 
6
- ### Streaming & Real-time Hooks
7
- - **`useChunkCatchup`** — Replays missed chunks after reconnection
8
- - **`useScriptedStream`** Drives scripted/mock streaming sequences
9
- - **`useRealtimeChunkProcessor`** Processes incoming real-time message chunks
10
- - **`useJetstreamDialogSubscription`** / **`useNatsDialogSubscription`** NATS/JetStream transport subscriptions
11
- - **`useCollapsible`** Collapsible UI state management
12
-
13
- ### Chat Core Hooks
14
- - **`useChat`** / **`useSSE`** — Core chat and Server-Sent Events hooks (ported from hub, without `MockChatService`/`useChatConfig`)
15
- - **`useSlashCommands`** — Slash command parsing and dispatch
16
- - **`useEmptyStateConfig`** — Empty state configuration
17
- - **`useChatAttachments`** / **`useChatAttachmentImageGallery`** — File attachment handling and image gallery
18
- - **`useChatIdentity`** — Chat user/agent identity resolution
19
- - **`useChatCardItem`** — Chat card rendering state
20
- - **`useCloseOnNavigation`** — Dismisses dialogs/panels on route change
21
-
22
- ### Unified Chat Surface
23
- - **`useUnifiedChat`** — Primary public hook; selects transport by `activeMode`
24
- - **`useSSEChatAdapter`** / **`useNatsChatAdapter`** — Transport adapters (SSE and NATS)
25
- - **`useEmbeddedChat`** — Legacy SSE alias for embedded surfaces
26
-
27
- ### Utilities
28
- - **`useProxiedImageUrl`** — Resolves proxied image URLs from `ChatRuntime.endpoints`
6
+ | Export Group | Hooks |
7
+ |---|---|
8
+ | **Realtime / Streaming** | `useChunkCatchup`, `useJetstreamDialogSubscription`, `useNatsDialogSubscription` |
9
+ | **UI Utilities** | `useCollapsible`, `useCloseOnNavigation`, `useChatCardItem` |
10
+ | **Chat Core** | `useChat`, `useSSE`, `useChatIdentity`, `useChatAttachments`, `useChatAttachmentImageGallery`, `useSlashCommands` |
11
+ | **Transport Adapters** | `useSSEChatAdapter`, `useNatsChatAdapter` |
12
+ | **Unified Surface** | `useUnifiedChat` (primary), `useEmbeddedChat` (legacy SSE alias) |
13
+ | **Image Proxy** | `useProxiedImageUrl` |
29
14
 
30
15
  ## Usage Example
31
16
 
32
17
  ```typescript
33
- // Preferred: import everything from the barrel
18
+ // Typical consumer — import directly from the module index
34
19
  import {
35
20
  useUnifiedChat,
36
21
  useChatAttachments,
37
- useProxiedImageUrl,
38
- } from '@openframe/chat-hooks'
22
+ useSlashCommands,
23
+ } from '@flamingo/chat-hooks'
39
24
 
40
25
  function ChatPanel({ modes, activeMode }) {
41
- const { messages, send } = useUnifiedChat({ modes, activeMode })
42
- const { attachments, addAttachment } = useChatAttachments()
43
- const proxied = useProxiedImageUrl(rawImageUrl)
26
+ const chat = useUnifiedChat({ modes, activeMode })
27
+ const attachments = useChatAttachments()
28
+ const slashCommands = useSlashCommands()
44
29
 
45
- return <ChatUI messages={messages} onSend={send} />
30
+ // ...
31
+ }
32
+
33
+ // Advanced: direct transport access
34
+ import { useNatsChatAdapter } from '@flamingo/chat-hooks'
35
+
36
+ function CustomTransportPanel() {
37
+ const nats = useNatsChatAdapter({ /* config */ })
38
+ // ...
46
39
  }
47
40
  ```
48
41
 
49
- > **Note:** For standard usage, prefer `useUnifiedChat`. The adapter hooks (`useSSEChatAdapter`, `useNatsChatAdapter`) are exposed for advanced cases requiring direct transport access.
42
+ ## Notes
43
+
44
+ - Marked `'use client'` — safe for React Server Component trees; hooks run only on the client.
45
+ - `useUnifiedChat` is the recommended entry point for new consumers; `useEmbeddedChat` is the legacy SSE alias kept for backward compatibility.
46
+ - `useProxiedImageUrl` reads proxy config from `ChatRuntime.endpoints`, keeping image-proxy resolution consistent between hub and embedder environments.