@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.
- package/dist/chat-protocol/decode.d.ts +58 -0
- package/dist/chat-protocol/decode.d.ts.map +1 -0
- package/dist/chat-protocol/encode.d.ts +44 -0
- package/dist/chat-protocol/encode.d.ts.map +1 -0
- package/dist/chat-protocol/env-flag.d.ts +35 -0
- package/dist/chat-protocol/env-flag.d.ts.map +1 -0
- package/dist/chat-protocol/events.d.ts +216 -0
- package/dist/chat-protocol/events.d.ts.map +1 -0
- package/dist/chat-protocol/frames.d.ts +213 -0
- package/dist/chat-protocol/frames.d.ts.map +1 -0
- package/dist/chat-protocol/index.cjs +506 -0
- package/dist/chat-protocol/index.cjs.map +1 -0
- package/dist/chat-protocol/index.d.ts +18 -0
- package/dist/chat-protocol/index.d.ts.map +1 -0
- package/dist/chat-protocol/index.js +490 -0
- package/dist/chat-protocol/index.js.map +1 -0
- package/dist/chat-protocol/ip-normalize.d.ts +44 -0
- package/dist/chat-protocol/ip-normalize.d.ts.map +1 -0
- package/dist/chat-protocol/nats-decoder.d.ts +26 -0
- package/dist/chat-protocol/nats-decoder.d.ts.map +1 -0
- package/dist/{chunk-3SQ5KXHQ.cjs → chunk-2T4GTV25.cjs} +9 -9
- package/dist/{chunk-3SQ5KXHQ.cjs.map → chunk-2T4GTV25.cjs.map} +1 -1
- package/dist/{chunk-CWAOV2FK.cjs → chunk-3CGZPAGR.cjs} +3 -3
- package/dist/{chunk-CWAOV2FK.cjs.map → chunk-3CGZPAGR.cjs.map} +1 -1
- package/dist/{chunk-TLMJHMXJ.js → chunk-3QSR22IC.js} +12 -12
- package/dist/chunk-3QSR22IC.js.map +1 -0
- package/dist/{chunk-TWYNR4TZ.cjs → chunk-4G5TLHJD.cjs} +7141 -4799
- package/dist/chunk-4G5TLHJD.cjs.map +1 -0
- package/dist/{chunk-ADPMHWOE.js → chunk-72XAID7Y.js} +4 -4
- package/dist/{chunk-PAGKRNWK.js → chunk-7WZHBQ4J.js} +2 -2
- package/dist/{chunk-OCEKO5CW.js → chunk-AEELJC4N.js} +12106 -9764
- package/dist/chunk-AEELJC4N.js.map +1 -0
- package/dist/{chunk-Z42BGM6Q.cjs → chunk-BT2WWDFP.cjs} +5 -5
- package/dist/{chunk-Z42BGM6Q.cjs.map → chunk-BT2WWDFP.cjs.map} +1 -1
- package/dist/{chunk-7LEL3RIX.cjs → chunk-C7OY3PQP.cjs} +11 -11
- package/dist/{chunk-7LEL3RIX.cjs.map → chunk-C7OY3PQP.cjs.map} +1 -1
- package/dist/{chunk-RHGGEKPQ.cjs → chunk-CIOLOQKQ.cjs} +4 -4
- package/dist/{chunk-RHGGEKPQ.cjs.map → chunk-CIOLOQKQ.cjs.map} +1 -1
- package/dist/{chunk-TSZHM74B.js → chunk-DC2TKS7C.js} +2 -2
- package/dist/{chunk-M2GOR3XQ.cjs → chunk-DYPYR7FR.cjs} +87 -87
- package/dist/{chunk-M2GOR3XQ.cjs.map → chunk-DYPYR7FR.cjs.map} +1 -1
- package/dist/{chunk-I64ABCDX.js → chunk-I2B6X77L.js} +2 -2
- package/dist/{chunk-NIZDKTGL.cjs → chunk-K5MSCMDK.cjs} +37 -37
- package/dist/{chunk-NIZDKTGL.cjs.map → chunk-K5MSCMDK.cjs.map} +1 -1
- package/dist/{chunk-DTYRYB2N.js → chunk-K6QTZ7CZ.js} +2 -2
- package/dist/{chunk-4LTMXDUS.js → chunk-KBN7PMFT.js} +2 -2
- package/dist/{chunk-TBMV7I5N.js → chunk-KXZO2WZX.js} +2 -2
- package/dist/{chunk-QTYZMP6D.cjs → chunk-LBAKLT6T.cjs} +62 -62
- package/dist/chunk-LBAKLT6T.cjs.map +1 -0
- package/dist/{chunk-KEBYLU3U.js → chunk-LFUPI7VX.js} +2 -2
- package/dist/{chunk-LDTR4IVZ.cjs → chunk-M37PBG4N.cjs} +31 -31
- package/dist/{chunk-LDTR4IVZ.cjs.map → chunk-M37PBG4N.cjs.map} +1 -1
- package/dist/{chunk-WA7RR64F.js → chunk-PO5O654H.js} +6 -5
- package/dist/chunk-PO5O654H.js.map +1 -0
- package/dist/{chunk-WK4N5VBX.cjs → chunk-QJJISLPA.cjs} +28 -27
- package/dist/chunk-QJJISLPA.cjs.map +1 -0
- package/dist/{chunk-2F44PLTV.cjs → chunk-T7MCSA55.cjs} +26 -26
- package/dist/{chunk-2F44PLTV.cjs.map → chunk-T7MCSA55.cjs.map} +1 -1
- package/dist/{chunk-WJQHJD7J.js → chunk-TWSKTJHW.js} +2 -2
- package/dist/{chunk-ZSQHZYCO.cjs → chunk-UQDMJ6DH.cjs} +13 -13
- package/dist/{chunk-ZSQHZYCO.cjs.map → chunk-UQDMJ6DH.cjs.map} +1 -1
- package/dist/{chunk-TZRUCD56.js → chunk-V3ZAAFE3.js} +5 -5
- package/dist/{chunk-LRGHJPET.js → chunk-XGIFOHKE.js} +2 -2
- package/dist/{chunk-POOMO3PA.cjs → chunk-YQEPRYT5.cjs} +7 -7
- package/dist/{chunk-POOMO3PA.cjs.map → chunk-YQEPRYT5.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/chat-message-list.d.ts.map +1 -1
- package/dist/components/chat/hooks/index.d.ts +0 -1
- package/dist/components/chat/hooks/index.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-chat.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-chunk-catchup.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +15 -48
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +10 -57
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +10 -2
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.d.ts +1 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +21 -13
- package/dist/components/chat/stream/chat-dialog-store.d.ts +184 -0
- package/dist/components/chat/stream/chat-dialog-store.d.ts.map +1 -0
- package/dist/components/chat/stream/chat-stream-reducer.d.ts +380 -0
- package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -0
- package/dist/components/chat/stream/delta-batcher.d.ts +51 -0
- package/dist/components/chat/stream/delta-batcher.d.ts.map +1 -0
- package/dist/components/chat/stream/index.d.ts +17 -0
- package/dist/components/chat/stream/index.d.ts.map +1 -0
- package/dist/components/chat/stream/message-mutations.d.ts +77 -0
- package/dist/components/chat/stream/message-mutations.d.ts.map +1 -0
- package/dist/components/chat/stream/use-chat-stream-reducer.d.ts +22 -0
- package/dist/components/chat/stream/use-chat-stream-reducer.d.ts.map +1 -0
- package/dist/components/chat/types/api.types.d.ts +1 -81
- package/dist/components/chat/types/api.types.d.ts.map +1 -1
- package/dist/components/chat/types/processing.types.d.ts +2 -93
- package/dist/components/chat/types/processing.types.d.ts.map +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +15 -0
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts +34 -6
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
- package/dist/components/chat/utils/history-merge.d.ts.map +1 -1
- package/dist/components/chat/utils/index.d.ts +2 -3
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts +59 -11
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/help-center-pages/index.cjs +22 -22
- package/dist/components/help-center-pages/index.js +13 -13
- package/dist/components/index.cjs +174 -132
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +65 -23
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +34 -34
- package/dist/components/meeting-scheduler/index.js +3 -3
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +6 -6
- package/dist/components/tickets/index.js +5 -5
- package/dist/components/ui/index.cjs +44 -2
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +1 -2
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +55 -13
- package/dist/components/ui/markdown/base-components.d.ts +51 -0
- package/dist/components/ui/markdown/base-components.d.ts.map +1 -0
- package/dist/components/ui/markdown/engine.d.ts +99 -0
- package/dist/components/ui/markdown/engine.d.ts.map +1 -0
- package/dist/components/ui/markdown/heading-ids.d.ts +65 -0
- package/dist/components/ui/markdown/heading-ids.d.ts.map +1 -0
- package/dist/components/ui/markdown/index.d.ts +18 -0
- package/dist/components/ui/markdown/index.d.ts.map +1 -0
- package/dist/components/ui/markdown/mermaid-diagram.d.ts +79 -0
- package/dist/components/ui/markdown/mermaid-diagram.d.ts.map +1 -0
- package/dist/components/ui/markdown/rich/embed-overrides.d.ts +14 -0
- package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +1 -0
- package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts +40 -0
- package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts.map +1 -0
- package/dist/components/ui/markdown/rich/shortcodes.d.ts +8 -0
- package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +1 -0
- package/dist/components/ui/markdown/sanitize.d.ts +153 -0
- package/dist/components/ui/markdown/sanitize.d.ts.map +1 -0
- package/dist/components/ui/markdown/simple-markdown-renderer.d.ts +23 -0
- package/dist/components/ui/markdown/simple-markdown-renderer.d.ts.map +1 -0
- package/dist/components/ui/markdown/streaming.d.ts +78 -0
- package/dist/components/ui/markdown/streaming.d.ts.map +1 -0
- package/dist/components/ui/markdown/text-size.d.ts +34 -0
- package/dist/components/ui/markdown/text-size.d.ts.map +1 -0
- package/dist/index.cjs +60 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +71 -13
- package/dist/utils/index.cjs +169 -40
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +162 -41
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/markdown-fences.d.ts +42 -0
- package/dist/utils/markdown-fences.d.ts.map +1 -0
- package/dist/utils/markdown-heading-id.d.ts +85 -0
- package/dist/utils/markdown-heading-id.d.ts.map +1 -0
- package/dist/utils/markdown-section-extractor.d.ts.map +1 -1
- package/package.json +7 -1
- package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +319 -0
- package/src/chat-protocol/__tests__/chat-protocol.test.ts +516 -0
- package/src/chat-protocol/__tests__/env-flag.test.ts +52 -0
- package/src/chat-protocol/__tests__/ip-normalize.test.ts +136 -0
- package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +237 -0
- package/src/chat-protocol/decode.ts +278 -0
- package/src/chat-protocol/encode.ts +71 -0
- package/src/chat-protocol/env-flag.ts +39 -0
- package/src/chat-protocol/events.ts +254 -0
- package/src/chat-protocol/frames.ts +245 -0
- package/src/chat-protocol/index.ts +21 -0
- package/src/chat-protocol/ip-normalize.ts +146 -0
- package/src/chat-protocol/nats-decoder.ts +264 -0
- package/src/components/chat/.chat-message-list.md +10 -6
- package/src/components/chat/__tests__/chat-message-list.test.tsx +450 -9
- package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +207 -0
- package/src/components/chat/__tests__/chat-pending-turn.test.tsx +250 -0
- package/src/components/chat/chat-message-enhanced.tsx +122 -20
- package/src/components/chat/chat-message-list.tsx +515 -133
- package/src/components/chat/embeddable-chat.tsx +6 -0
- package/src/components/chat/hooks/.index.md +30 -33
- package/src/components/chat/hooks/.use-nats-chat-adapter.md +36 -55
- package/src/components/chat/hooks/__tests__/__snapshots__/conversation-id-persistence-golden.test.ts.snap +37 -0
- package/src/components/chat/hooks/__tests__/__snapshots__/sse-stream-golden.test.ts.snap +0 -0
- package/src/components/chat/hooks/__tests__/chunk-catchup-dialog-staleness.test.ts +92 -0
- package/src/components/chat/hooks/__tests__/conversation-id-persistence-golden.test.ts +438 -0
- package/src/components/chat/hooks/__tests__/sse-stream-golden.test.ts +318 -0
- package/src/components/chat/hooks/index.ts +0 -1
- package/src/components/chat/hooks/use-chat.ts +8 -0
- package/src/components/chat/hooks/use-chunk-catchup.ts +26 -1
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +254 -825
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +380 -684
- package/src/components/chat/index.ts +5 -0
- package/src/components/chat/stream/__tests__/__snapshots__/chat-stream-reducer-golden.test.ts.snap +944 -0
- package/src/components/chat/stream/__tests__/chat-dialog-store.test.ts +806 -0
- package/src/components/chat/stream/__tests__/chat-stream-reducer-golden.test.ts +467 -0
- package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +1330 -0
- package/src/components/chat/stream/__tests__/delta-batcher.test.ts +255 -0
- package/src/components/chat/stream/__tests__/use-chat-stream-reducer.test.ts +105 -0
- package/src/components/chat/stream/chat-dialog-store.ts +536 -0
- package/src/components/chat/stream/chat-stream-reducer.ts +1928 -0
- package/src/components/chat/stream/delta-batcher.ts +168 -0
- package/src/components/chat/stream/index.ts +55 -0
- package/src/components/chat/stream/message-mutations.ts +340 -0
- package/src/components/chat/stream/use-chat-stream-reducer.ts +126 -0
- package/src/components/chat/thinking-display.tsx +1 -1
- package/src/components/chat/types/.api.types.md +39 -49
- package/src/components/chat/types/.processing.types.md +29 -58
- package/src/components/chat/types/.unified-chat-state.types.md +2 -2
- package/src/components/chat/types/api.types.ts +6 -73
- package/src/components/chat/types/processing.types.ts +11 -53
- package/src/components/chat/types/unified-chat-state.types.ts +16 -0
- package/src/components/chat/utils/.extract-incomplete-message-state.md +21 -3
- package/src/components/chat/utils/.history-merge.md +44 -3
- package/src/components/chat/utils/.index.md +31 -40
- package/src/components/chat/utils/__tests__/__snapshots__/process-historical-messages-golden.test.ts.snap +420 -0
- package/src/components/chat/utils/__tests__/__snapshots__/segment-accumulator-golden.test.ts.snap +605 -0
- package/src/components/chat/utils/__tests__/extract-incomplete-message-state.test.ts +106 -0
- package/src/components/chat/utils/__tests__/history-merge.test.ts +160 -0
- package/src/components/chat/utils/__tests__/process-historical-messages-approvals.test.ts +61 -0
- package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +394 -0
- package/src/components/chat/utils/__tests__/segment-accumulator-golden.test.ts +270 -0
- package/src/components/chat/utils/extract-incomplete-message-state.ts +93 -6
- package/src/components/chat/utils/history-merge.ts +85 -5
- package/src/components/chat/utils/index.ts +5 -8
- package/src/components/chat/utils/process-historical-messages.ts +361 -385
- package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +22 -4
- package/src/components/shared/legal-document/legal-document-page.tsx +1 -1
- package/src/components/shared/product-release/release-detail-page.tsx +19 -4
- package/src/components/ui/__tests__/__snapshots__/markdown-parity.test.tsx.snap +2420 -0
- package/src/components/ui/__tests__/markdown-parity.test.tsx +2540 -0
- package/src/components/ui/index.ts +1 -2
- package/src/components/ui/markdown/__tests__/mermaid-security.test.ts +176 -0
- package/src/components/ui/markdown/__tests__/mermaid-stale-render.test.tsx +151 -0
- package/src/components/ui/markdown/__tests__/sanitize-invariant.test.ts +354 -0
- package/src/components/ui/markdown/__tests__/sanitize-render.test.tsx +118 -0
- package/src/components/ui/markdown/__tests__/streaming.test.tsx +436 -0
- package/src/components/ui/markdown/base-components.tsx +360 -0
- package/src/components/ui/markdown/engine.tsx +315 -0
- package/src/components/ui/markdown/heading-ids.ts +239 -0
- package/src/components/ui/markdown/index.ts +49 -0
- package/src/components/ui/markdown/mermaid-diagram.tsx +323 -0
- package/src/components/ui/markdown/rich/embed-overrides.tsx +199 -0
- package/src/components/ui/markdown/rich/rich-markdown-renderer.tsx +184 -0
- package/src/components/ui/markdown/rich/shortcodes.ts +170 -0
- package/src/components/ui/markdown/sanitize.ts +3584 -0
- package/src/components/ui/markdown/simple-markdown-renderer.tsx +28 -0
- package/src/components/ui/markdown/streaming.ts +390 -0
- package/src/components/ui/markdown/text-size.ts +106 -0
- package/src/components/ui/release-changelog-section.tsx +1 -1
- package/src/components/ui/ticket-info-section.tsx +1 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/markdown-fences.ts +127 -0
- package/src/utils/markdown-heading-id.ts +348 -0
- package/src/utils/markdown-section-extractor.ts +41 -56
- package/dist/chunk-OCEKO5CW.js.map +0 -1
- package/dist/chunk-QTYZMP6D.cjs.map +0 -1
- package/dist/chunk-TLMJHMXJ.js.map +0 -1
- package/dist/chunk-TWYNR4TZ.cjs.map +0 -1
- package/dist/chunk-WA7RR64F.js.map +0 -1
- package/dist/chunk-WK4N5VBX.cjs.map +0 -1
- package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts +0 -6
- package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +0 -1
- package/dist/components/chat/utils/chunk-parser.d.ts +0 -25
- package/dist/components/chat/utils/chunk-parser.d.ts.map +0 -1
- package/dist/components/ui/rich-markdown-renderer.d.ts +0 -34
- package/dist/components/ui/rich-markdown-renderer.d.ts.map +0 -1
- package/dist/components/ui/simple-markdown-renderer.d.ts +0 -73
- package/dist/components/ui/simple-markdown-renderer.d.ts.map +0 -1
- package/src/components/chat/hooks/.use-realtime-chunk-processor.md +0 -77
- package/src/components/chat/hooks/use-realtime-chunk-processor.ts +0 -489
- package/src/components/chat/utils/.chunk-parser.md +0 -62
- package/src/components/chat/utils/chunk-parser.ts +0 -262
- package/src/components/ui/.simple-markdown-renderer.md +0 -52
- package/src/components/ui/rich-markdown-renderer.tsx +0 -1223
- package/src/components/ui/simple-markdown-renderer.tsx +0 -964
- /package/dist/{chunk-ADPMHWOE.js.map → chunk-72XAID7Y.js.map} +0 -0
- /package/dist/{chunk-PAGKRNWK.js.map → chunk-7WZHBQ4J.js.map} +0 -0
- /package/dist/{chunk-TSZHM74B.js.map → chunk-DC2TKS7C.js.map} +0 -0
- /package/dist/{chunk-I64ABCDX.js.map → chunk-I2B6X77L.js.map} +0 -0
- /package/dist/{chunk-DTYRYB2N.js.map → chunk-K6QTZ7CZ.js.map} +0 -0
- /package/dist/{chunk-4LTMXDUS.js.map → chunk-KBN7PMFT.js.map} +0 -0
- /package/dist/{chunk-TBMV7I5N.js.map → chunk-KXZO2WZX.js.map} +0 -0
- /package/dist/{chunk-KEBYLU3U.js.map → chunk-LFUPI7VX.js.map} +0 -0
- /package/dist/{chunk-WJQHJD7J.js.map → chunk-TWSKTJHW.js.map} +0 -0
- /package/dist/{chunk-TZRUCD56.js.map → chunk-V3ZAAFE3.js.map} +0 -0
- /package/dist/{chunk-LRGHJPET.js.map → chunk-XGIFOHKE.js.map} +0 -0
|
@@ -178,7 +178,6 @@ export * from './ticket-attachments-list'
|
|
|
178
178
|
export * from './ticket-note-card'
|
|
179
179
|
export * from './ticket-notes-section'
|
|
180
180
|
export * from './marquee-wall'
|
|
181
|
-
export * from './
|
|
182
|
-
export { RichMarkdownRenderer, type RichMarkdownRendererProps } from './rich-markdown-renderer'
|
|
181
|
+
export * from './markdown'
|
|
183
182
|
export * from './filter-pill-row'
|
|
184
183
|
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mermaid renders MODEL-authored diagram source on the chat path, and the
|
|
3
|
+
* result is injected with `dangerouslySetInnerHTML`. This fixture pins the
|
|
4
|
+
* hardening in ../mermaid-diagram.tsx: a hostile node label must NOT survive
|
|
5
|
+
* as live HTML.
|
|
6
|
+
*
|
|
7
|
+
* The test drives `mermaid` with the SAME security knobs the component sets,
|
|
8
|
+
* by IMPORTING them: `MERMAID_SECURITY_OPTIONS` is the single source of truth
|
|
9
|
+
* and is spread into both `mermaid.initialize` calls. There are no literals
|
|
10
|
+
* here to drift from the component — flipping `securityLevel` to `'loose'` in
|
|
11
|
+
* the constant fails this suite (verified).
|
|
12
|
+
*
|
|
13
|
+
* `secure` is part of that constant: it is the allowlist of config keys a
|
|
14
|
+
* `%%{init}%%` directive inside the diagram SOURCE may not override. mermaid's
|
|
15
|
+
* default list omits `htmlLabels`, so untrusted source could otherwise
|
|
16
|
+
* re-enable HTML labels while `securityLevel` stayed locked.
|
|
17
|
+
*
|
|
18
|
+
* jsdom NOTE (recorded, not proven): before `htmlLabels` was added to `secure`,
|
|
19
|
+
* a `%%{init: {"htmlLabels": true}}%%` directive was observed to make
|
|
20
|
+
* `mermaid.render` never settle under jsdom (>60s, against a passing
|
|
21
|
+
* two-render control). That was NOT reproduced in a real browser and may be an
|
|
22
|
+
* artifact of jsdom having no layout — it is NOT a confirmed browser DoS. The
|
|
23
|
+
* component wraps its render in a timeout regardless.
|
|
24
|
+
*/
|
|
25
|
+
import { beforeAll, describe, expect, it } from 'vitest'
|
|
26
|
+
import { MERMAID_SECURITY_OPTIONS } from '../mermaid-diagram'
|
|
27
|
+
|
|
28
|
+
// jsdom implements no SVG layout, and mermaid's dagre pass measures text.
|
|
29
|
+
// These stubs are only about making the renderer RUN; they do not touch the
|
|
30
|
+
// sanitization path under test.
|
|
31
|
+
function installSvgLayoutStubs(): void {
|
|
32
|
+
const proto = (globalThis as unknown as { SVGElement?: { prototype: Record<string, unknown> } }).SVGElement?.prototype
|
|
33
|
+
if (!proto) return
|
|
34
|
+
proto.getBBox = function getBBox() {
|
|
35
|
+
return { x: 0, y: 0, width: 100, height: 20 }
|
|
36
|
+
}
|
|
37
|
+
proto.getComputedTextLength = function getComputedTextLength() {
|
|
38
|
+
return 100
|
|
39
|
+
}
|
|
40
|
+
proto.getScreenCTM = function getScreenCTM() {
|
|
41
|
+
return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0, inverse: () => ({ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }) }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Second tuple slot: text that MUST be present in the rendered output — a
|
|
46
|
+
// positive control proving the diagram was actually drawn, so the negative
|
|
47
|
+
// assertions below can't pass on empty output. Note the two payloads are
|
|
48
|
+
// neutralized differently: the `<img>` survives as ESCAPED label text, while
|
|
49
|
+
// the `<script>` is dropped outright, so its control is the sibling node.
|
|
50
|
+
const HOSTILE_LABELS: ReadonlyArray<readonly [chart: string, expectedText: string]> = [
|
|
51
|
+
['graph TD\n A["<img src=x onerror=alert(1)>"] --> B["ok"]', '<img'],
|
|
52
|
+
['graph TD\n A["<script>alert(1)</script>"] --> B["ok"]', 'ok'],
|
|
53
|
+
// A `%%{init}%%` directive trying to unlock the two knobs. Both keys are in
|
|
54
|
+
// `secure`, so the override is rejected and the payload stays escaped.
|
|
55
|
+
[
|
|
56
|
+
'%%{init:{"securityLevel":"loose"}}%%\ngraph TD\n A["<img src=x onerror=alert(1)>"] --> B["ok"]',
|
|
57
|
+
'<img',
|
|
58
|
+
],
|
|
59
|
+
[
|
|
60
|
+
'%%{init:{"htmlLabels":true}}%%\ngraph TD\n A["<img src=x onerror=alert(1)>"] --> B["ok"]',
|
|
61
|
+
'<img',
|
|
62
|
+
],
|
|
63
|
+
// YAML front-matter config — the other in-source config channel.
|
|
64
|
+
[
|
|
65
|
+
'---\nconfig:\n securityLevel: loose\n---\ngraph TD\n A["<img src=x onerror=alert(1)>"] --> B["ok"]',
|
|
66
|
+
'<img',
|
|
67
|
+
],
|
|
68
|
+
// `click` interaction directive: disabled outright by 'strict'.
|
|
69
|
+
['graph TD\n A["ok"] --> B["b"]\n click A "javascript:alert(1)"', 'ok'],
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
/** Initialize with the COMPONENT's security options — imported, never
|
|
73
|
+
* re-typed — so the fixture cannot drift from the renderer. */
|
|
74
|
+
function initializeLikeComponent(mermaid: { initialize: (c: Record<string, unknown>) => void }): void {
|
|
75
|
+
mermaid.initialize({
|
|
76
|
+
startOnLoad: false,
|
|
77
|
+
theme: 'dark',
|
|
78
|
+
flowchart: { useMaxWidth: true },
|
|
79
|
+
...MERMAID_SECURITY_OPTIONS,
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe('mermaid hostile-label hardening', () => {
|
|
84
|
+
beforeAll(() => {
|
|
85
|
+
installSvgLayoutStubs()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it.each(HOSTILE_LABELS)('does not emit live HTML for %j', async (chart, expectedText) => {
|
|
89
|
+
const { default: mermaid } = await import('mermaid')
|
|
90
|
+
|
|
91
|
+
initializeLikeComponent(mermaid)
|
|
92
|
+
|
|
93
|
+
const { svg } = await mermaid.render(`mermaid-security-${Math.random().toString(36).slice(2)}`, chart)
|
|
94
|
+
|
|
95
|
+
// No live element, and no event-handler attribute, anywhere in the output.
|
|
96
|
+
expect(svg).not.toMatch(/<img[\s>]/i)
|
|
97
|
+
expect(svg).not.toMatch(/<script[\s>]/i)
|
|
98
|
+
expect(svg).not.toMatch(/\son[a-z]+\s*=/i)
|
|
99
|
+
// `htmlLabels: false` means no HTML subtree is minted for labels at all.
|
|
100
|
+
expect(svg).not.toMatch(/<foreignObject/i)
|
|
101
|
+
// 'strict' drops `click` bindings, so no javascript: URL is ever emitted.
|
|
102
|
+
expect(svg).not.toMatch(/javascript:/i)
|
|
103
|
+
|
|
104
|
+
// And it stays inert once parsed as HTML: the hostile markup must appear
|
|
105
|
+
// as TEXT (escaped), never as an element node.
|
|
106
|
+
const host = document.createElement('div')
|
|
107
|
+
host.innerHTML = svg
|
|
108
|
+
expect(host.querySelector('img')).toBeNull()
|
|
109
|
+
expect(host.querySelector('script')).toBeNull()
|
|
110
|
+
const text = host.textContent ?? ''
|
|
111
|
+
expect(text).toContain(expectedText)
|
|
112
|
+
// The script payload never survives in any form.
|
|
113
|
+
expect(text).not.toContain('alert(1)')
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* CSS is the OTHER injection surface, and the HTML hardening above does not
|
|
119
|
+
* cover it. `themeCSS` is raw CSS that mermaid emits into a `<style>` inside
|
|
120
|
+
* the SVG the component hands to `dangerouslySetInnerHTML`, and mermaid's
|
|
121
|
+
* directive sanitizer does not defend it: it is an ordinary config key whose
|
|
122
|
+
* value only gets brace-balanced. Before `themeCSS` was added to `secure`,
|
|
123
|
+
* both payloads below reproduced against real mermaid 11.14.0 — the first
|
|
124
|
+
* emitted `#svgId{position:fixed;…;z-index:2147483647;}` (an opaque top-most
|
|
125
|
+
* viewport-filling overlay whose visible text the author writes in the node
|
|
126
|
+
* labels), the second emitted a document-global `@font-face` fetching from an
|
|
127
|
+
* attacker host, because at-rules escape the `#svgId` prefix entirely.
|
|
128
|
+
*
|
|
129
|
+
* Diagram source on the chat path is MODEL output, so this is reachable from
|
|
130
|
+
* untrusted input. These cases fail if `themeCSS` leaves `secure`.
|
|
131
|
+
*/
|
|
132
|
+
const HOSTILE_THEME_CSS: ReadonlyArray<readonly [label: string, chart: string]> = [
|
|
133
|
+
[
|
|
134
|
+
'viewport-overlay via bare declarations',
|
|
135
|
+
// The label is deliberately neutral: in the real attack it reads
|
|
136
|
+
// "Session expired, sign in at <attacker host>", but putting a hostname in
|
|
137
|
+
// the label would trip this suite's own `not.toContain('evil.example')`
|
|
138
|
+
// assertion on legitimate TEXT rather than on injected CSS.
|
|
139
|
+
'%%{init:{"themeCSS":"position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:2147483647;background:#fff"}}%%\ngraph TD\n A["Session expired. Sign in again."] --> B["ok"]',
|
|
140
|
+
],
|
|
141
|
+
[
|
|
142
|
+
'document-global at-rule escaping the #svgId scope',
|
|
143
|
+
'%%{init:{"themeCSS":"@font-face { font-family: e; src: url(https://evil.example/f.woff) }"}}%%\ngraph TD\n A["ok"] --> B["b"]',
|
|
144
|
+
],
|
|
145
|
+
[
|
|
146
|
+
'the YAML front-matter config channel',
|
|
147
|
+
'---\nconfig:\n themeCSS: "position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:2147483647"\n---\ngraph TD\n A["ok"] --> B["b"]',
|
|
148
|
+
],
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
describe('mermaid themeCSS hardening', () => {
|
|
152
|
+
beforeAll(() => {
|
|
153
|
+
installSvgLayoutStubs()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it.each(HOSTILE_THEME_CSS)('rejects a themeCSS override — %s', async (_label, chart) => {
|
|
157
|
+
const { default: mermaid } = await import('mermaid')
|
|
158
|
+
|
|
159
|
+
initializeLikeComponent(mermaid)
|
|
160
|
+
|
|
161
|
+
const { svg } = await mermaid.render(
|
|
162
|
+
`mermaid-themecss-${Math.random().toString(36).slice(2)}`,
|
|
163
|
+
chart,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
expect(svg).not.toMatch(/position\s*:\s*fixed/i)
|
|
167
|
+
expect(svg).not.toContain('2147483647')
|
|
168
|
+
expect(svg).not.toMatch(/@font-face/i)
|
|
169
|
+
expect(svg).not.toContain('evil.example')
|
|
170
|
+
// Positive control: the diagram still rendered, so the assertions above
|
|
171
|
+
// cannot be passing on empty output.
|
|
172
|
+
const host = document.createElement('div')
|
|
173
|
+
host.innerHTML = svg
|
|
174
|
+
expect(host.textContent ?? '').toContain('ok')
|
|
175
|
+
})
|
|
176
|
+
})
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MermaidDiagram's render effect re-runs on every `chart` change, and during
|
|
3
|
+
* STREAMING the chart text grows chunk by chunk — so several renders are in
|
|
4
|
+
* flight at once. Without a per-effect cancellation guard a slower EARLIER
|
|
5
|
+
* render can resolve last and overwrite the newer output (and, on the error
|
|
6
|
+
* path, paint an error for a chart that is no longer displayed).
|
|
7
|
+
*
|
|
8
|
+
* The 15s render timeout does not close this: it rejects the race, but does
|
|
9
|
+
* not abort the underlying `mermaid.render`, so the stale write is merely
|
|
10
|
+
* bounded, not prevented. These fixtures pin the guard.
|
|
11
|
+
*
|
|
12
|
+
* `mermaid` is mocked with a hand-settled deferred so resolution ORDER is
|
|
13
|
+
* under the test's control. (The real-mermaid security fixture lives in
|
|
14
|
+
* ./mermaid-security.test.ts and must stay unmocked, hence a separate file.)
|
|
15
|
+
*/
|
|
16
|
+
import { render, act } from '@testing-library/react'
|
|
17
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
18
|
+
|
|
19
|
+
interface Deferred {
|
|
20
|
+
chart: string
|
|
21
|
+
resolve: (value: { svg: string }) => void
|
|
22
|
+
reject: (err: Error) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const renders: Deferred[] = []
|
|
26
|
+
|
|
27
|
+
vi.mock('mermaid', () => ({
|
|
28
|
+
default: {
|
|
29
|
+
initialize: vi.fn(),
|
|
30
|
+
render: vi.fn(
|
|
31
|
+
(_id: string, chart: string) =>
|
|
32
|
+
new Promise<{ svg: string }>((resolve, reject) => {
|
|
33
|
+
renders.push({ chart, resolve, reject })
|
|
34
|
+
}),
|
|
35
|
+
),
|
|
36
|
+
},
|
|
37
|
+
}))
|
|
38
|
+
|
|
39
|
+
import { MermaidDiagram } from '../mermaid-diagram'
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Let the mocked dynamic import + the resolved promise chain flush.
|
|
43
|
+
*
|
|
44
|
+
* MACROTASK-based and BOUNDED-POLLING on purpose. A fixed number of
|
|
45
|
+
* `await Promise.resolve()` turns is NOT a settle condition here: the component
|
|
46
|
+
* reaches `mermaid.render` through `mount effect → setMounted → re-render →
|
|
47
|
+
* effect → await import('mermaid')`, and how many microtask turns a dynamic
|
|
48
|
+
* import takes to resolve differs across Node/vitest releases. Three turns
|
|
49
|
+
* happened to be enough on Node 22 and are NOT on Node 25, where the second
|
|
50
|
+
* `mermaid.render` had not been called yet when the assertions ran — the suite
|
|
51
|
+
* failed with `renders.length === 1` (and, once the real module leaked in, with
|
|
52
|
+
* a genuine mermaid parse error). Poll for the condition instead of guessing a
|
|
53
|
+
* tick count.
|
|
54
|
+
*/
|
|
55
|
+
const flushUntil = async (done: () => boolean, what: string) => {
|
|
56
|
+
for (let i = 0; i < 50; i++) {
|
|
57
|
+
await act(async () => {
|
|
58
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
59
|
+
})
|
|
60
|
+
if (done()) return
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`timed out waiting for ${what}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Settle pending state updates when there is no specific condition to await. */
|
|
66
|
+
const flush = async () => {
|
|
67
|
+
await act(async () => {
|
|
68
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Wait until `mermaid.render` has been called exactly `n` times. */
|
|
73
|
+
const flushRenders = (n: number) =>
|
|
74
|
+
flushUntil(() => renders.length >= n, `${n} mermaid.render call(s)`)
|
|
75
|
+
|
|
76
|
+
const svgOf = (container: HTMLElement) =>
|
|
77
|
+
container.querySelector('.mermaid-svg-container')?.innerHTML ?? ''
|
|
78
|
+
|
|
79
|
+
describe('MermaidDiagram stale-render guard', () => {
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
renders.length = 0
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('ignores an earlier render that resolves after a newer chart', async () => {
|
|
85
|
+
const view = render(<MermaidDiagram chart="graph TD\n A-->B" />)
|
|
86
|
+
await flushRenders(1)
|
|
87
|
+
view.rerender(<MermaidDiagram chart="graph TD\n A-->B\n B-->C" />)
|
|
88
|
+
await flushRenders(2)
|
|
89
|
+
|
|
90
|
+
// Newer render lands first, then the abandoned earlier one.
|
|
91
|
+
await act(async () => {
|
|
92
|
+
renders[1]!.resolve({ svg: '<svg id="new"></svg>' })
|
|
93
|
+
})
|
|
94
|
+
await act(async () => {
|
|
95
|
+
renders[0]!.resolve({ svg: '<svg id="stale"></svg>' })
|
|
96
|
+
})
|
|
97
|
+
await flush()
|
|
98
|
+
|
|
99
|
+
expect(svgOf(view.container)).toContain('id="new"')
|
|
100
|
+
expect(svgOf(view.container)).not.toContain('id="stale"')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('ignores a late failure from an abandoned render', async () => {
|
|
104
|
+
const view = render(<MermaidDiagram chart="graph TD\n A-->B" />)
|
|
105
|
+
await flushRenders(1)
|
|
106
|
+
view.rerender(<MermaidDiagram chart="graph TD\n A-->B\n B-->C" />)
|
|
107
|
+
await flushRenders(2)
|
|
108
|
+
|
|
109
|
+
await act(async () => {
|
|
110
|
+
renders[1]!.resolve({ svg: '<svg id="new"></svg>' })
|
|
111
|
+
})
|
|
112
|
+
await act(async () => {
|
|
113
|
+
renders[0]!.reject(new Error('Diagram rendering timed out after 15000ms'))
|
|
114
|
+
})
|
|
115
|
+
await flush()
|
|
116
|
+
|
|
117
|
+
expect(view.container.textContent).not.toContain('Diagram Error')
|
|
118
|
+
expect(svgOf(view.container)).toContain('id="new"')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The error state must not be STICKY. This is the streaming hot path, not an
|
|
123
|
+
* exotic case: the chart text grows chunk by chunk, every intermediate
|
|
124
|
+
* prefix is an invalid diagram, and mermaid rejects each one. The render body
|
|
125
|
+
* evaluates `error` BEFORE `svg`, so an uncleared message keeps the "Diagram
|
|
126
|
+
* Error" card on screen for the component's whole lifetime — the finished
|
|
127
|
+
* diagram never appears even though its render succeeded.
|
|
128
|
+
*/
|
|
129
|
+
it('recovers from a transient failure once a later render succeeds', async () => {
|
|
130
|
+
const view = render(<MermaidDiagram chart="graph TD" />)
|
|
131
|
+
await flushRenders(1)
|
|
132
|
+
|
|
133
|
+
// Partial chart → parse error, exactly what a mid-stream prefix produces.
|
|
134
|
+
await act(async () => {
|
|
135
|
+
renders[0]!.reject(new Error('Parse error on line 1'))
|
|
136
|
+
})
|
|
137
|
+
await flush()
|
|
138
|
+
expect(view.container.textContent).toContain('Diagram Error')
|
|
139
|
+
|
|
140
|
+
// The next delta completes the chart and renders cleanly.
|
|
141
|
+
view.rerender(<MermaidDiagram chart="graph TD\n A-->B" />)
|
|
142
|
+
await flushRenders(2)
|
|
143
|
+
await act(async () => {
|
|
144
|
+
renders[1]!.resolve({ svg: '<svg id="settled"></svg>' })
|
|
145
|
+
})
|
|
146
|
+
await flush()
|
|
147
|
+
|
|
148
|
+
expect(view.container.textContent).not.toContain('Diagram Error')
|
|
149
|
+
expect(svgOf(view.container)).toContain('id="settled"')
|
|
150
|
+
})
|
|
151
|
+
})
|