@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,199 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Rich-composition component overrides: the embed layer (Video SSOT,
5
+ * Reddit/Twitter/LinkedIn embeds, OG link previews, Figma, MarkdownImage).
6
+ *
7
+ * These imports are deliberately isolated in this file — keeping them out
8
+ * of the engine and the simple composition keeps the CHAT bundle free of
9
+ * embed code (a runtime `variant` prop could not be tree-shaken; a
10
+ * separate composition module can).
11
+ */
12
+ import React from 'react';
13
+ import type { Components } from 'react-markdown';
14
+ import { RedditEmbedClient } from '../../../embeds/reddit-embed-client';
15
+ import { TwitterEmbedClient } from '../../../embeds/twitter-embed-client';
16
+ import { LinkedInEmbedClient } from '../../../embeds/linkedin-embed-client';
17
+ import { Video } from '../../../features/video';
18
+ import { OGLinkPreview, OGLinkErrorBoundary } from '../../../embeds/og-link-preview';
19
+ import { FigmaEmbed } from '../../../embeds/figma-embed';
20
+ import { MarkdownImage } from '../../../embeds/markdown-image';
21
+ import { buildStandardLeafRenderers, hasRenderableSrc } from '../base-components';
22
+ import type { TextSizeElement } from '../text-size';
23
+
24
+ /** Depth-first search of a hast node for the first `<a href>` matching `hostRe`. */
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ function findFirstHref(node: any, hostRe: RegExp): string | null {
27
+ if (!node) return null;
28
+ if (node.tagName === 'a') {
29
+ const href = node.properties?.href;
30
+ if (typeof href === 'string' && hostRe.test(href)) return href;
31
+ }
32
+ for (const child of node.children ?? []) {
33
+ const found = findFirstHref(child, hostRe);
34
+ if (found) return found;
35
+ }
36
+ return null;
37
+ }
38
+
39
+ export interface BuildRichEmbedOverridesOptions {
40
+ ogApiBaseUrl: string;
41
+ ogEndpointPath: string;
42
+ textSizes: Record<TextSizeElement, string>;
43
+ }
44
+
45
+ /**
46
+ * Overrides spread LAST onto the engine's base map: `code` (fence-language
47
+ * embeds), `div` (shortcode-expanded embeds), `img` (MarkdownImage with
48
+ * runtime transformImageSrc), `video` (raw tags → Video SSOT).
49
+ */
50
+ export function buildRichEmbedOverrides({
51
+ ogApiBaseUrl,
52
+ ogEndpointPath,
53
+ textSizes,
54
+ }: BuildRichEmbedOverridesOptions): Partial<Components> {
55
+ // Standard `code` / `blockquote` / `div` come from the base SSOT — this
56
+ // module owns ONLY the embed special cases and delegates every
57
+ // fall-through, so the shared markup can never drift between the two
58
+ // compositions.
59
+ //
60
+ // The delegations below CALL those renderers as plain functions rather
61
+ // than rendering them as components (that is what the `as any` casts are
62
+ // for). That is only sound because `buildStandardLeafRenderers` is
63
+ // documented HOOK-FREE — if a hook ever appears in one of them, these
64
+ // call sites must become real elements (`<Standard.code {...props} />`)
65
+ // first.
66
+ const standard = buildStandardLeafRenderers({ textSizes });
67
+
68
+ return {
69
+ // Fence-language embeds (```youtube-embed etc.); everything else
70
+ // (mermaid, highlighted blocks, inline code) falls through to `standard`.
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
+ code: (codeProps: any) => {
73
+ const { inline, className: codeClassName, children } = codeProps;
74
+ const match = /language-(\w+)/.exec(codeClassName || '');
75
+ const language = match ? match[1] : '';
76
+ const fenceText = () => String(children).replace(/\n$/, '').trim();
77
+
78
+ if (!inline && language === 'youtube-embed') {
79
+ return <Video kind="youtube" url={fenceText()} />;
80
+ }
81
+ if (!inline && language === 'reddit-embed') {
82
+ return <RedditEmbedClient url={fenceText()} />;
83
+ }
84
+ if (!inline && language === 'tweet-embed') {
85
+ return <TwitterEmbedClient url={fenceText()} />;
86
+ }
87
+ if (!inline && language === 'link-preview') {
88
+ return (
89
+ <OGLinkPreview
90
+ url={fenceText()}
91
+ variant="compact"
92
+ enablePlaceholder={false}
93
+ apiBaseUrl={ogApiBaseUrl}
94
+ ogEndpointPath={ogEndpointPath}
95
+ />
96
+ );
97
+ }
98
+ if (!inline && language === 'figma-embed') {
99
+ return <FigmaEmbed url={fenceText()} height="70vh" />;
100
+ }
101
+ if (!inline && language === 'linkedin-embed') {
102
+ return <LinkedInEmbedClient url={fenceText()} />;
103
+ }
104
+
105
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
106
+ return (standard.code as any)(codeProps);
107
+ },
108
+
109
+ // Shortcode-expanded embeds: <div class="youtube-embed" data-video-id>…
110
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
+ div: (divProps: any) => {
112
+ const { className, ...props } = divProps;
113
+ if (className === 'youtube-embed') {
114
+ return <Video kind="youtube" url={props['data-video-id']} />;
115
+ }
116
+ if (className === 'reddit-embed') {
117
+ return <RedditEmbedClient url={props['data-post-url']} />;
118
+ }
119
+ if (className === 'tweet-embed') {
120
+ return <TwitterEmbedClient url={props['data-tweet-url']} />;
121
+ }
122
+ if (className === 'link-preview') {
123
+ const url = props['data-url'];
124
+ if (!url || typeof url !== 'string') {
125
+ console.warn('Invalid URL for link preview:', url);
126
+ return <div className="text-ods-text-secondary text-sm">Invalid link</div>;
127
+ }
128
+ try {
129
+ new URL(url);
130
+ return (
131
+ <OGLinkErrorBoundary fallback={<div className="text-ods-text-secondary text-sm">Link preview unavailable</div>}>
132
+ <OGLinkPreview
133
+ url={url}
134
+ variant="compact"
135
+ enablePlaceholder={false}
136
+ apiBaseUrl={ogApiBaseUrl}
137
+ ogEndpointPath={ogEndpointPath}
138
+ />
139
+ </OGLinkErrorBoundary>
140
+ );
141
+ } catch (e) {
142
+ console.warn('Malformed URL for link preview:', url, e);
143
+ return <div className="text-ods-text-secondary text-sm">Malformed URL: {url}</div>;
144
+ }
145
+ }
146
+ if (className === 'figma-embed') {
147
+ return <FigmaEmbed url={props['data-figma-url']} height="70vh" />;
148
+ }
149
+ if (className === 'linkedin-embed') {
150
+ return <LinkedInEmbedClient url={props['data-post-url']} />;
151
+ }
152
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
153
+ return (standard.div as any)(divProps);
154
+ },
155
+
156
+ // Reddit's OWN embed markup lives in 58 published blog posts as
157
+ // `<blockquote class="reddit-embed-bq">…<a href="post-url">` paired with
158
+ // a `<script src="embed.reddit.com/widgets.js">` loader. The sanitize
159
+ // stack strips the script (correctly), so the composition rehydrates the
160
+ // blockquote itself: extract the post URL from the first reddit link and
161
+ // render the RedditEmbedClient SSOT. Non-reddit blockquotes fall through
162
+ // to the engine's base blockquote.
163
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
164
+ blockquote: (bqProps: any) => {
165
+ const { node, className } = bqProps;
166
+ const classNames: string = Array.isArray(className) ? className.join(' ') : (className ?? '');
167
+ if (classNames.includes('reddit-embed-bq')) {
168
+ const postUrl = findFirstHref(node, /reddit\.com/);
169
+ if (postUrl) return <RedditEmbedClient url={postUrl} />;
170
+ }
171
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
+ return (standard.blockquote as any)(bqProps);
173
+ },
174
+
175
+ // In-article images: MarkdownImage reads `transformImageSrc` from the
176
+ // rich-markdown runtime (Supabase optimization on the hub, identity
177
+ // elsewhere). Guard against empty `![]()`.
178
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
179
+ img: ({ src, alt }: any) => {
180
+ // Empty-`![]()` guard shared with the base `img` renderer.
181
+ if (!hasRenderableSrc(src)) return null;
182
+ return <MarkdownImage src={src.trim()} alt={alt} />;
183
+ },
184
+
185
+ // Raw <video> tags in stored content (blog publisher injection) route
186
+ // to the <Video> SSOT (MuxPlayer) so HLS manifests play everywhere.
187
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
188
+ video: ({ src, poster, className }: any) => {
189
+ if (!hasRenderableSrc(src)) return null;
190
+ return (
191
+ <div className={`overflow-hidden ${className || 'w-full my-8 rounded-lg'}`}>
192
+ <div className="w-full aspect-video">
193
+ <Video kind="file" url={src.trim()} poster={typeof poster === 'string' ? poster : undefined} />
194
+ </div>
195
+ </div>
196
+ );
197
+ },
198
+ };
199
+ }
@@ -0,0 +1,184 @@
1
+ "use client";
2
+
3
+ /**
4
+ * RichMarkdownRenderer — the content composition over the unified
5
+ * `MarkdownEngine`: RuntimeProvider + shortcodes + embed overrides +
6
+ * fetch-based internal-link resolver + `article` typography.
7
+ *
8
+ * This IS the real component (the old 1223-line standalone implementation
9
+ * was deleted in the unification). Public prop surface is unchanged, so
10
+ * every hub/app call site compiles untouched. Deltas vs the old
11
+ * implementation are the ones reviewed in the unification plan: the
12
+ * sanitize stack + tag-escape pre-pass now apply to content surfaces
13
+ * (`extraAllowedHtmlTags={['video','source']}` keeps authored video
14
+ * working), the engine is memoized, and the dead light-mode branches are
15
+ * gone.
16
+ */
17
+ import React, { memo, useCallback, useMemo } from 'react';
18
+ import { MarkdownEngine, NO_BROKEN_LINKS, type MarkdownEngineProps } from '../engine';
19
+ import type { ResolveLinkResult } from '../../../../types/doc-source';
20
+ import {
21
+ RichMarkdownRuntimeProvider,
22
+ useRichMarkdownRuntime,
23
+ type RichMarkdownRuntime,
24
+ } from '../../../embeds/rich-markdown-runtime';
25
+ import { resolveTextSizeConfig } from '../text-size';
26
+ import { buildRichEmbedOverrides } from './embed-overrides';
27
+ import { processShortcodes } from './shortcodes';
28
+
29
+ /** Raw-HTML tags authored content needs beyond the chat-safe baseline. */
30
+ const RICH_EXTRA_TAGS = ['video', 'source'];
31
+
32
+ /**
33
+ * The engine props this composition OWNS and therefore does not forward:
34
+ * shortcode preprocessing, the embed component map, the `article` text
35
+ * preset, the fetch link resolver, the rich tag allowlist, and streaming
36
+ * (authored content is never streamed).
37
+ */
38
+ type RichOwnedEngineProps =
39
+ | 'extraAllowedHtmlTags'
40
+ | 'preprocessContent'
41
+ | 'componentOverrides'
42
+ | 'textSize'
43
+ | 'onResolveLink'
44
+ | 'additionalRemarkPlugins'
45
+ | 'streaming';
46
+
47
+ /**
48
+ * Derived from `MarkdownEngineProps` rather than hand-copied. The previous
49
+ * shape restated 7 engine props here and a THIRD time in `InnerProps`, so
50
+ * every engine prop doc/type change had to be mirrored in three places (and
51
+ * a missed mirror type-checks fine while silently dropping the prop).
52
+ */
53
+ export interface RichMarkdownRendererProps
54
+ extends Partial<RichMarkdownRuntime>,
55
+ Omit<MarkdownEngineProps, RichOwnedEngineProps> {
56
+ /** Source for resolving internal links (default: 'openframe-docs'). Registry id from DOC_SOURCES. */
57
+ resolveSource?: string;
58
+ /** Path of the internal link-resolver endpoint. Default '/api/docs/resolve-link'. */
59
+ resolveLinkEndpointUrl?: string;
60
+ }
61
+
62
+ const RichMarkdownRendererImpl: React.FC<RichMarkdownRendererProps> = ({
63
+ content,
64
+ className = '',
65
+ sectionIds,
66
+ onInternalLinkClick,
67
+ brokenLinks = NO_BROKEN_LINKS,
68
+ currentPath,
69
+ resolveSource = 'openframe-docs',
70
+ resolveLinkEndpointUrl = '/api/docs/resolve-link',
71
+ demoteMarkdownH1ToH2 = false,
72
+ // Runtime overrides; provider fills the defaults
73
+ redditProxyUrl,
74
+ twitterProxyUrl,
75
+ ogScraperUrl,
76
+ transformImageSrc,
77
+ }) => {
78
+ return (
79
+ <RichMarkdownRuntimeProvider
80
+ redditProxyUrl={redditProxyUrl}
81
+ twitterProxyUrl={twitterProxyUrl}
82
+ ogScraperUrl={ogScraperUrl}
83
+ transformImageSrc={transformImageSrc}
84
+ >
85
+ <RichMarkdownInner
86
+ content={content}
87
+ className={className}
88
+ sectionIds={sectionIds}
89
+ onInternalLinkClick={onInternalLinkClick}
90
+ brokenLinks={brokenLinks}
91
+ currentPath={currentPath}
92
+ resolveSource={resolveSource}
93
+ resolveLinkEndpointUrl={resolveLinkEndpointUrl}
94
+ demoteMarkdownH1ToH2={demoteMarkdownH1ToH2}
95
+ />
96
+ </RichMarkdownRuntimeProvider>
97
+ );
98
+ };
99
+
100
+ /**
101
+ * "The outer component already applied these defaults", ENCODED IN THE TYPE:
102
+ * the runtime props are consumed by the provider (so they are gone here),
103
+ * and the three defaulted props are `Required` — a missed default upstream
104
+ * is a type error rather than a silent `undefined`.
105
+ */
106
+ type InnerProps = Omit<RichMarkdownRendererProps, keyof RichMarkdownRuntime> &
107
+ Required<
108
+ Pick<
109
+ RichMarkdownRendererProps,
110
+ 'resolveSource' | 'resolveLinkEndpointUrl' | 'demoteMarkdownH1ToH2'
111
+ >
112
+ >;
113
+
114
+ const RichMarkdownInner: React.FC<InnerProps> = ({
115
+ content,
116
+ className = '',
117
+ sectionIds,
118
+ onInternalLinkClick,
119
+ brokenLinks,
120
+ currentPath,
121
+ resolveSource,
122
+ resolveLinkEndpointUrl,
123
+ demoteMarkdownH1ToH2,
124
+ }) => {
125
+ const { ogScraperUrl } = useRichMarkdownRuntime();
126
+
127
+ // The OG link-preview endpoint is `${apiBaseUrl}${ogEndpointPath}` —
128
+ // split the runtime URL once. Full URLs route through the cross-origin
129
+ // proxy; path-only values are used as the path with an empty base.
130
+ const { ogApiBaseUrl, ogEndpointPath } = useMemo(() => {
131
+ try {
132
+ const u = new URL(ogScraperUrl);
133
+ return {
134
+ ogApiBaseUrl: `${u.protocol}//${u.host}`,
135
+ ogEndpointPath: u.pathname,
136
+ };
137
+ } catch {
138
+ return { ogApiBaseUrl: '', ogEndpointPath: ogScraperUrl };
139
+ }
140
+ }, [ogScraperUrl]);
141
+
142
+ // ONE link-resolution code path: the engine's callback seam. This is the
143
+ // fetch implementation the old Rich renderer had inline in its <a>
144
+ // handler — the engine's base `a` component now drives it.
145
+ const onResolveLink = useCallback(
146
+ async (href: string, path: string): Promise<ResolveLinkResult> => {
147
+ const response = await fetch(resolveLinkEndpointUrl, {
148
+ method: 'POST',
149
+ headers: { 'Content-Type': 'application/json' },
150
+ body: JSON.stringify({ link: href, currentPath: path, source: resolveSource }),
151
+ });
152
+ return response.json();
153
+ },
154
+ [resolveLinkEndpointUrl, resolveSource],
155
+ );
156
+
157
+ const textSizes = useMemo(() => resolveTextSizeConfig('article'), []);
158
+
159
+ const componentOverrides = useMemo(
160
+ () => buildRichEmbedOverrides({ ogApiBaseUrl, ogEndpointPath, textSizes }),
161
+ [ogApiBaseUrl, ogEndpointPath, textSizes],
162
+ );
163
+
164
+ return (
165
+ <MarkdownEngine
166
+ content={content}
167
+ className={className}
168
+ sectionIds={sectionIds}
169
+ demoteMarkdownH1ToH2={demoteMarkdownH1ToH2}
170
+ brokenLinks={brokenLinks}
171
+ currentPath={currentPath}
172
+ onInternalLinkClick={onInternalLinkClick}
173
+ onResolveLink={onResolveLink}
174
+ preprocessContent={processShortcodes}
175
+ componentOverrides={componentOverrides}
176
+ textSize="article"
177
+ extraAllowedHtmlTags={RICH_EXTRA_TAGS}
178
+ />
179
+ );
180
+ };
181
+
182
+ /** Memoized — see the engine's memo rationale. */
183
+ export const RichMarkdownRenderer = memo(RichMarkdownRendererImpl);
184
+
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Shortcode + auto-URL-embed preprocessing for the rich (content)
3
+ * composition. Moved verbatim from the old RichMarkdownRenderer — the
4
+ * `{{youtube:...}}` / `{% youtube %}` / thumbnail-link / auto-URL grammar
5
+ * is authored-content SSOT; chat surfaces never run this.
6
+ */
7
+
8
+ export const processShortcodes = (content: string): string => {
9
+ let processedContent = content;
10
+
11
+ // Escape values interpolated into the raw HTML `data-*` attributes generated below.
12
+ // With rehypeRaw enabled, an unescaped `"`/`<`/`>` in a URL or id could break out of
13
+ // the attribute and inject markup, so every interpolated embed value goes through this.
14
+ const escapeAttr = (value: string) =>
15
+ value
16
+ .replace(/&/g, '&amp;')
17
+ .replace(/"/g, '&quot;')
18
+ .replace(/</g, '&lt;')
19
+ .replace(/>/g, '&gt;');
20
+
21
+ // First, process explicit shortcodes
22
+ processedContent = processedContent
23
+ // YouTube embeds: {{youtube:VIDEO_ID}}
24
+ .replace(/\{\{youtube:([^}]+)\}\}/g, (match, videoId) => {
25
+ return `\n\n<div class="youtube-embed" data-video-id="${escapeAttr(videoId.trim())}"></div>\n\n`;
26
+ })
27
+ // Markdoc-style YouTube: {% youtube id="VIDEO_ID" /%} or {% youtube id="VIDEO_ID" title="..." /%}
28
+ .replace(/\{%\s*youtube\s+id="([^"]+)"(?:\s+title="[^"]*")?\s*\/?%\}/g, (match, videoId) => {
29
+ return `\n\n<div class="youtube-embed" data-video-id="${escapeAttr(videoId.trim())}"></div>\n\n`;
30
+ })
31
+ /**
32
+ * SHORTCODE: YouTube Thumbnail Link (RECOMMENDED - GitHub + Flamingo Compatible)
33
+ *
34
+ * SYNTAX: [![Title](https://img.youtube.com/vi/VIDEO_ID/QUALITY.jpg)](https://www.youtube.com/watch?v=VIDEO_ID)
35
+ * - On GitHub: renders as a clickable thumbnail image linking to YouTube
36
+ * - On Flamingo: converts to a full embedded YouTube player
37
+ * Use this format for docs that must work on BOTH GitHub and Flamingo;
38
+ * {{youtube:ID}} / {% youtube id="ID" /%} are Flamingo-only.
39
+ */
40
+ .replace(/\[!\[([^\]]*)\]\(https?:\/\/img\.youtube\.com\/vi\/([a-zA-Z0-9_-]+)\/[^)]+\)\]\(https?:\/\/(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]+)[^)]*\)/g,
41
+ (match, altText, thumbId, videoId) => {
42
+ return `\n\n<div class="youtube-embed" data-video-id="${videoId}"></div>\n\n`;
43
+ })
44
+ // Reddit embeds: {{reddit:POST_URL}}
45
+ .replace(/\{\{reddit:([^}]+)\}\}/g, (match, urlOrId) => {
46
+ const postUrl = urlOrId.trim();
47
+ const fullUrl = postUrl.startsWith('http') ? postUrl : `https://reddit.com/r/${postUrl}`;
48
+ return `\n\n<div class="reddit-embed" data-post-url="${escapeAttr(fullUrl)}"></div>\n\n`;
49
+ })
50
+ // Twitter/X embeds: {{tweet:TWEET_URL}} or {{twitter:TWEET_URL}}
51
+ .replace(/\{\{(?:tweet|twitter):([^}]+)\}\}/g, (match, urlOrId) => {
52
+ const tweetInput = urlOrId.trim();
53
+ const tweetUrl = tweetInput.startsWith('http')
54
+ ? tweetInput
55
+ : `https://twitter.com/twitter/status/${tweetInput}`;
56
+ return `\n\n<div class="tweet-embed" data-tweet-url="${escapeAttr(tweetUrl)}"></div>\n\n`;
57
+ })
58
+ // Figma embeds: {{figma:URL}}
59
+ .replace(/\{\{figma:([^}]+)\}\}/g, (match, url) => {
60
+ return `\n\n<div class="figma-embed" data-figma-url="${escapeAttr(url.trim())}"></div>\n\n`;
61
+ })
62
+ // LinkedIn embeds: {{linkedin:POST_URL}}
63
+ .replace(/\{\{linkedin:([^}]+)\}\}/g, (match, url) => {
64
+ return `\n\n<div class="linkedin-embed" data-post-url="${escapeAttr(url.trim())}"></div>\n\n`;
65
+ })
66
+ // Link previews: {{link:URL}}
67
+ .replace(/\{\{link:([^}]+)\}\}/g, (match, url) => {
68
+ return `\n\n<div class="link-preview" data-url="${escapeAttr(url.trim())}"></div>\n\n`;
69
+ });
70
+
71
+ // Next, auto-detect standalone URLs (but NOT those already in markdown links or code blocks)
72
+
73
+ // Step 1: Temporarily replace code blocks to protect them
74
+ const codeBlocks: string[] = [];
75
+ processedContent = processedContent.replace(/```[\s\S]*?```|`[^`]+`/g, (match) => {
76
+ const placeholder = `__CODE_BLOCK_${codeBlocks.length}__`;
77
+ codeBlocks.push(match);
78
+ return placeholder;
79
+ });
80
+
81
+ // Step 2: Temporarily replace markdown links to protect them
82
+ const markdownLinks: string[] = [];
83
+ processedContent = processedContent.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match) => {
84
+ const placeholder = `__MARKDOWN_LINK_${markdownLinks.length}__`;
85
+ markdownLinks.push(match);
86
+ return placeholder;
87
+ });
88
+
89
+ // Step 2.5: Temporarily replace table rows to protect URLs inside tables
90
+ const tableRows: string[] = [];
91
+ processedContent = processedContent.replace(/^\|.+\|$/gm, (match) => {
92
+ const placeholder = `__TABLE_ROW_${tableRows.length}__`;
93
+ tableRows.push(match);
94
+ return placeholder;
95
+ });
96
+
97
+ // Step 3: Auto-detect standalone URLs and convert to appropriate embeds
98
+ processedContent = processedContent
99
+ // YouTube URLs (standalone only)
100
+ .replace(/(?:^|\s)(https?:\/\/(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]+))(?:\s|$)/g,
101
+ (match, fullUrl, videoId) => {
102
+ return match.replace(fullUrl, `\n\n<div class="youtube-embed" data-video-id="${videoId}"></div>\n\n`);
103
+ })
104
+ // Reddit URLs (standalone only)
105
+ .replace(/(?:^|\s)(https?:\/\/(?:www\.)?reddit\.com\/[^\s]+)(?:\s|$)/g,
106
+ (match, redditUrl) => {
107
+ return match.replace(redditUrl, `\n\n<div class="reddit-embed" data-post-url="${escapeAttr(redditUrl)}"></div>\n\n`);
108
+ })
109
+ // Twitter/X URLs (standalone only)
110
+ .replace(/(?:^|\s)(https?:\/\/(?:www\.)?(?:twitter\.com|x\.com)\/[^\/\s]+\/status\/\d+)(?:\s|$)/g,
111
+ (match, tweetUrl) => {
112
+ return match.replace(tweetUrl, `\n\n<div class="tweet-embed" data-tweet-url="${escapeAttr(tweetUrl)}"></div>\n\n`);
113
+ })
114
+ // Figma URLs (standalone only) - design/file/proto/board/deck/slides → interactive embed
115
+ .replace(/(?:^|\s)(https?:\/\/(?:www\.|embed\.)?figma\.com\/(?:design|file|proto|board|deck|slides)\/[^\s]+)(?:\s|$)/g,
116
+ (match, figmaUrl) => {
117
+ return match.replace(figmaUrl, `\n\n<div class="figma-embed" data-figma-url="${escapeAttr(figmaUrl)}"></div>\n\n`);
118
+ })
119
+ // LinkedIn post URLs (standalone only) → native post embed
120
+ .replace(/(?:^|\s)(https?:\/\/(?:www\.)?linkedin\.com\/(?:posts|feed\/update|embed\/feed\/update)\/[^\s]+)(?:\s|$)/g,
121
+ (match, liUrl) => {
122
+ return match.replace(liUrl, `\n\n<div class="linkedin-embed" data-post-url="${escapeAttr(liUrl)}"></div>\n\n`);
123
+ })
124
+ // Other external URLs (standalone only) - convert to link previews
125
+ .replace(/(?:^|\s)(https?:\/\/[^\s]+)(?:\s|$)/g,
126
+ (match, url) => {
127
+ try {
128
+ const urlObj = new URL(url);
129
+ const hostname = urlObj.hostname.toLowerCase();
130
+
131
+ // Exact host match (or a subdomain of it) — substring checks like
132
+ // `hostname.includes('x.com')` false-positive on "zabbix.com".
133
+ const hostIs = (domain: string) =>
134
+ hostname === domain || hostname.endsWith(`.${domain}`);
135
+ // Non-video YouTube URLs (channels, playlists, `@handle`) fall
136
+ // through to the og-scraper; only video URLs become inline players.
137
+ const isYouTubeVideo =
138
+ (hostIs('youtube.com') && urlObj.searchParams.has('v')) || hostIs('youtu.be');
139
+ // LinkedIn non-post URLs (profiles, companies) fall through to the
140
+ // og-scraper too; only interactive post embeds have handlers above.
141
+ if (isYouTubeVideo ||
142
+ hostIs('reddit.com') || hostIs('twitter.com') || hostIs('x.com') ||
143
+ hostIs('figma.com')) {
144
+ return match;
145
+ }
146
+
147
+ return match.replace(url, `\n\n<div class="link-preview" data-url="${escapeAttr(url)}"></div>\n\n`);
148
+ } catch (e) {
149
+ console.warn('Failed to parse URL for link preview:', url, e);
150
+ return match;
151
+ }
152
+ });
153
+
154
+ // Step 3.5: Restore table rows
155
+ tableRows.forEach((row, index) => {
156
+ processedContent = processedContent.replace(`__TABLE_ROW_${index}__`, row);
157
+ });
158
+
159
+ // Step 4: Restore markdown links
160
+ markdownLinks.forEach((link, index) => {
161
+ processedContent = processedContent.replace(`__MARKDOWN_LINK_${index}__`, link);
162
+ });
163
+
164
+ // Step 5: Restore code blocks (MUST be last to prevent link preview in code)
165
+ codeBlocks.forEach((block, index) => {
166
+ processedContent = processedContent.replace(`__CODE_BLOCK_${index}__`, block);
167
+ });
168
+
169
+ return processedContent;
170
+ };