@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
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SimpleMarkdownRenderer — the chat/doc composition over the unified
|
|
5
|
+
* `MarkdownEngine`. This IS the real component (not a compat alias): the
|
|
6
|
+
* old 966-line standalone implementation was deleted in the unification;
|
|
7
|
+
* its entire prop surface maps 1:1 onto the engine. Chat consumers keep
|
|
8
|
+
* layering `additionalRemarkPlugins` (card/mention links) and
|
|
9
|
+
* `componentOverrides` exactly as before.
|
|
10
|
+
*/
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { MarkdownEngine, type MarkdownEngineProps } from './engine';
|
|
13
|
+
import type { ResolveLinkResult } from '../../../types/doc-source';
|
|
14
|
+
|
|
15
|
+
export type { ResolveLinkResult };
|
|
16
|
+
|
|
17
|
+
export interface SimpleMarkdownRendererProps
|
|
18
|
+
extends Omit<MarkdownEngineProps, 'extraAllowedHtmlTags'> {}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A type-narrowed ALIAS of the engine, not a wrapper: the composition adds
|
|
22
|
+
* no behavior (it only hides `extraAllowedHtmlTags` from the prop surface),
|
|
23
|
+
* and `MarkdownEngine` is already `memo`'d. Wrapping it in a second `memo`
|
|
24
|
+
* bought nothing and cost an extra shallow-compare pass plus a fiber per
|
|
25
|
+
* chat segment.
|
|
26
|
+
*/
|
|
27
|
+
export const SimpleMarkdownRenderer =
|
|
28
|
+
MarkdownEngine as React.NamedExoticComponent<SimpleMarkdownRendererProps>;
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming helpers for the unified markdown engine (pure, no React).
|
|
3
|
+
*
|
|
4
|
+
* Design contract (unification plan §D1 "Streaming rendering performance"):
|
|
5
|
+
* - Markdown is NOT context-free across blocks. The splitter only cuts at
|
|
6
|
+
* boundaries it can PROVE are atomic; when atomicity is unprovable the
|
|
7
|
+
* content is left in one unit (unmemoized) — correctness beats cache hits.
|
|
8
|
+
* - Blocks containing `card://` / `mention://` markers are never memoized:
|
|
9
|
+
* their text is identical before/after the entity ref resolves, so a
|
|
10
|
+
* text-keyed cache would serve the stale unresolved card mid-stream.
|
|
11
|
+
* - On stream completion the engine discards all of this and does ONE
|
|
12
|
+
* authoritative whole-document parse — streaming can never permanently
|
|
13
|
+
* diverge (late reference defs, footnotes, list renumbering self-heal).
|
|
14
|
+
* - Tail completion auto-closes ONLY unterminated fenced code blocks
|
|
15
|
+
* (fence-count based — unambiguous). Inline emphasis/links are NOT
|
|
16
|
+
* auto-closed: a stray `*` in "2 * 3" or a non-link `[` would be
|
|
17
|
+
* mis-wrapped, which is worse than the brief flicker it prevents.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { createFenceTracker, isBlankLine, type FenceTracker } from '../../../utils/markdown-fences'
|
|
21
|
+
|
|
22
|
+
export interface StreamingBlock {
|
|
23
|
+
/** Raw markdown source of this unit (including trailing blank lines). */
|
|
24
|
+
text: string
|
|
25
|
+
/** Position index — cache key component so identical blocks never alias. */
|
|
26
|
+
index: number
|
|
27
|
+
/**
|
|
28
|
+
* 1-based line of this unit's first line WITHIN the whole document.
|
|
29
|
+
* Each unit is parsed by its own `ReactMarkdown`, so hast positions are
|
|
30
|
+
* unit-relative; the engine adds `startLine - 1` back to look ids up in
|
|
31
|
+
* the document-wide heading-id map (see ./heading-ids.ts).
|
|
32
|
+
*/
|
|
33
|
+
startLine: number
|
|
34
|
+
/**
|
|
35
|
+
* True when this unit may be render-cached: it is complete (not the
|
|
36
|
+
* trailing unit), atomic, and free of cross-block / late-resolving
|
|
37
|
+
* constructs (card/mention markers, reference definitions/uses,
|
|
38
|
+
* footnotes).
|
|
39
|
+
*/
|
|
40
|
+
memoizable: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Constructs that resolve across blocks or after the text settles. */
|
|
44
|
+
const NON_MEMOIZABLE_RE = /card:\/\/|mention:\/\/|^\s*\[[^\]]*\]:\s|\]\[|\[\^/m
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Raw HTML block-level containers. A blank line inside an OPEN one of these
|
|
48
|
+
* is not a block boundary: cutting there hands react-markdown an unbalanced
|
|
49
|
+
* fragment, so the container's body renders outside the container and the
|
|
50
|
+
* stray close tag is dropped (`<details>` disclosure bodies escaping the
|
|
51
|
+
* widget mid-stream — and the wrong render getting cached).
|
|
52
|
+
*/
|
|
53
|
+
const RAW_BLOCK_TAGS = new Set([
|
|
54
|
+
'details', 'div', 'table', 'section', 'article', 'aside', 'blockquote',
|
|
55
|
+
'figure', 'form', 'fieldset', 'header', 'footer', 'main', 'nav', 'dl',
|
|
56
|
+
'ol', 'ul', 'pre', 'video', 'audio', 'picture', 'iframe', 'template',
|
|
57
|
+
'svg',
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
/** `<tag …>` / `</tag>` occurrences, used for raw-HTML depth tracking. */
|
|
61
|
+
const HTML_TAG_RE = /<(\/?)([a-zA-Z][a-zA-Z0-9-]*)([^>]*)>/g
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A start tag whose `>` has NOT arrived yet on this line
|
|
65
|
+
* (`<div\n class="x">`). Only the trailing occurrence matters, and only
|
|
66
|
+
* for RAW_BLOCK_TAGS — restricting it there keeps prose like `a <b` from
|
|
67
|
+
* latching the scanner into "inside a tag" forever.
|
|
68
|
+
*/
|
|
69
|
+
const DANGLING_OPEN_TAG_RE = /<([a-zA-Z][a-zA-Z0-9-]*)(?:\s[^>]*)?$/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A line that may START a raw HTML block. CommonMark puts the `<` at column
|
|
73
|
+
* 0..3, but a raw HTML block nested in a list item is indented to the item's
|
|
74
|
+
* CONTENT column (`1. step` → 4, and up to 7 for a wide ordered marker).
|
|
75
|
+
* The old column-0..3 gate missed exactly those, so an unbalanced raw-HTML
|
|
76
|
+
* container nested in a list item never shifted the scanner's depth and its
|
|
77
|
+
* unit was marked memoizable — contradicting the "unbalanced raw-HTML units
|
|
78
|
+
* are never cached" policy this module documents.
|
|
79
|
+
*/
|
|
80
|
+
const HTML_BLOCK_START_RE = /^\s{0,7}<\/?[a-zA-Z]/
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Cap on how long the raw-HTML latch may suppress cutting. An unclosed
|
|
84
|
+
* `<div>` (or a start tag whose `>` never arrives) would otherwise pin
|
|
85
|
+
* `htmlDepth > 0` for the ENTIRE remaining message, collapsing it into one
|
|
86
|
+
* ever-growing non-memoizable unit and disabling the atomic-block
|
|
87
|
+
* optimization outright.
|
|
88
|
+
*
|
|
89
|
+
* THE TRADEOFF, STATED HONESTLY: past the cap the splitter resumes cutting
|
|
90
|
+
* even though a container is still open, so a genuinely >200-line authored
|
|
91
|
+
* `<div>` / `<table>` wrapper IS cut mid-container and visibly breaks apart
|
|
92
|
+
* while streaming (its body renders outside the wrapper, the stray close tag
|
|
93
|
+
* is dropped). Those units are still marked non-memoizable, so nothing WRONG
|
|
94
|
+
* is ever cached and the stream-completion whole-document reparse restores
|
|
95
|
+
* the correct structure — but during the stream the render is wrong, not
|
|
96
|
+
* merely uncached. (An earlier comment here claimed "never a wrong render";
|
|
97
|
+
* that was only true below the cap.)
|
|
98
|
+
*
|
|
99
|
+
* The cap is kept because the alternative — never cutting while latched —
|
|
100
|
+
* makes ONE unbounded unit out of every message containing an unclosed tag,
|
|
101
|
+
* which is the common case (an LLM mid-emission always has one) and costs a
|
|
102
|
+
* full reparse of the entire message on every token.
|
|
103
|
+
*/
|
|
104
|
+
const HTML_LATCH_LINE_LIMIT = 200
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Two properties of the fence tracker, stated so they stop being accidents.
|
|
108
|
+
* A reviewer confirmed both hold today, but neither was written down, so a
|
|
109
|
+
* future change could quietly break them.
|
|
110
|
+
*
|
|
111
|
+
* 1. EOF ON THE OPENER LINE EMITS AN EMPTY PAIR. When the stream stops exactly
|
|
112
|
+
* on a fence opener, `completeStreamingTail` closes it at EOF and the render
|
|
113
|
+
* contains a bare ```/``` — a momentarily empty `<pre>`. It is cosmetic and
|
|
114
|
+
* self-heals on the next delta. Do NOT "fix" it by suppressing the close:
|
|
115
|
+
* that leaks an unterminated fence into the parse instead, which is worse.
|
|
116
|
+
*
|
|
117
|
+
* 2. THIS SCANNER AND THE SANITIZER'S MASK CAN DISAGREE ABOUT WHICH FENCE IS
|
|
118
|
+
* OPEN, AND BOTH MUST FAIL CLOSED. `scanLine`'s `pendingOpenTag` early
|
|
119
|
+
* return skips `fences.push`, so a fence opened on a line consumed by an
|
|
120
|
+
* unclosed HTML tag is never recorded here — while `sanitize.ts`'s
|
|
121
|
+
* `blankFencedRegions` runs its own tracker over the same text and may see
|
|
122
|
+
* it. The divergence is safe ONLY because each consumer degrades toward
|
|
123
|
+
* doing less: `completeStreamingTail` closes nothing it did not see open,
|
|
124
|
+
* and `blankFencedRegions` blanks nothing it cannot locate, so the output
|
|
125
|
+
* degrades to "render as-is" rather than to a corrupted document. Any
|
|
126
|
+
* change to the early return, to `fences` bookkeeping, or to either
|
|
127
|
+
* consumer must preserve fail-closed behaviour in BOTH directions
|
|
128
|
+
* (scanner-ahead and scanner-behind).
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Line-scanner state shared by `splitStreamingBlocks` and
|
|
133
|
+
* `completeStreamingTail` — ONE implementation of "where are we in the
|
|
134
|
+
* document", so the splitter and the tail-completer can never disagree
|
|
135
|
+
* about which fence is open.
|
|
136
|
+
*/
|
|
137
|
+
interface ScanState {
|
|
138
|
+
/**
|
|
139
|
+
* Fenced-code state machine. NOT a local copy: `createFenceTracker` lives
|
|
140
|
+
* in the server-safe `utils/markdown-fences` and is the SAME instance
|
|
141
|
+
* shape the heading scanner uses, so "is this line inside a fence" has
|
|
142
|
+
* exactly one definition. The previous duplicate here (a verbatim
|
|
143
|
+
* `FENCE_RE` plus its own open/close state machine, cross-referenced by a
|
|
144
|
+
* comment) is precisely the drift that produced the `~~~` bug.
|
|
145
|
+
*/
|
|
146
|
+
fences: FenceTracker
|
|
147
|
+
/** Depth of currently open raw HTML block containers. */
|
|
148
|
+
htmlDepth: number
|
|
149
|
+
/** Raw-block tag name of a start tag still waiting for its `>`, or null. */
|
|
150
|
+
pendingOpenTag: string | null
|
|
151
|
+
/** Consecutive lines spent with the raw-HTML latch engaged. */
|
|
152
|
+
htmlLatchLines: number
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function createScanState(): ScanState {
|
|
156
|
+
return {
|
|
157
|
+
fences: createFenceTracker(),
|
|
158
|
+
htmlDepth: 0,
|
|
159
|
+
pendingOpenTag: null,
|
|
160
|
+
htmlLatchLines: 0,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Raw-HTML state is unbalanced (a container or a start tag is still open). */
|
|
165
|
+
function htmlLatched(state: ScanState): boolean {
|
|
166
|
+
return state.htmlDepth > 0 || state.pendingOpenTag !== null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** True while raw-HTML state forbids cutting (latch cap not yet reached). */
|
|
170
|
+
function htmlBlocksCut(state: ScanState): boolean {
|
|
171
|
+
return htmlLatched(state) && state.htmlLatchLines <= HTML_LATCH_LINE_LIMIT
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Scan `line` from `from` for complete tags, then record any dangling opener. */
|
|
175
|
+
function scanTags(state: ScanState, line: string, from: number): void {
|
|
176
|
+
HTML_TAG_RE.lastIndex = from
|
|
177
|
+
let m: RegExpExecArray | null
|
|
178
|
+
let end = from
|
|
179
|
+
while ((m = HTML_TAG_RE.exec(line)) !== null) {
|
|
180
|
+
end = HTML_TAG_RE.lastIndex
|
|
181
|
+
const tag = m[2].toLowerCase()
|
|
182
|
+
if (!RAW_BLOCK_TAGS.has(tag)) continue
|
|
183
|
+
if (m[1] === '/') {
|
|
184
|
+
if (state.htmlDepth > 0) state.htmlDepth--
|
|
185
|
+
} else if (!m[3].trimEnd().endsWith('/')) {
|
|
186
|
+
state.htmlDepth++
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const dangling = DANGLING_OPEN_TAG_RE.exec(line.slice(end))
|
|
190
|
+
if (dangling && RAW_BLOCK_TAGS.has(dangling[1].toLowerCase())) {
|
|
191
|
+
state.pendingOpenTag = dangling[1].toLowerCase()
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Advance `state` across one raw source line (mutates). */
|
|
196
|
+
function scanLine(state: ScanState, line: string): void {
|
|
197
|
+
// A start tag spanning lines wins over everything: its continuation lines
|
|
198
|
+
// are attribute soup, not markdown.
|
|
199
|
+
if (state.pendingOpenTag !== null) {
|
|
200
|
+
const gt = line.indexOf('>')
|
|
201
|
+
if (gt === -1) {
|
|
202
|
+
state.htmlLatchLines++
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
const selfClosing = line.slice(0, gt).trimEnd().endsWith('/')
|
|
206
|
+
const tag = state.pendingOpenTag
|
|
207
|
+
state.pendingOpenTag = null
|
|
208
|
+
if (!selfClosing && RAW_BLOCK_TAGS.has(tag)) state.htmlDepth++
|
|
209
|
+
scanTags(state, line, gt + 1)
|
|
210
|
+
state.htmlLatchLines = htmlLatched(state) ? state.htmlLatchLines + 1 : 0
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Fence delimiters and fence content are both opaque to the HTML scanner —
|
|
215
|
+
// only `text` lines carry markup that can shift raw-HTML depth.
|
|
216
|
+
if (state.fences.push(line) !== 'text') return
|
|
217
|
+
|
|
218
|
+
// Depth > 0 means we are INSIDE a container, where the closing tag may sit
|
|
219
|
+
// anywhere on a line (`some text </details>`). Scanning only column-0..3
|
|
220
|
+
// lines there meant such a close never decremented, so the latch stuck.
|
|
221
|
+
// At depth 0 only a line that can BEGIN a raw HTML block is considered, so
|
|
222
|
+
// `a < b` prose can't shift depth.
|
|
223
|
+
if (state.htmlDepth === 0 && !HTML_BLOCK_START_RE.test(line)) return
|
|
224
|
+
scanTags(state, line, 0)
|
|
225
|
+
state.htmlLatchLines = htmlLatched(state) ? state.htmlLatchLines + 1 : 0
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Split streaming markdown into atomic units at blank lines that are
|
|
230
|
+
* provably block boundaries. A blank line does NOT split when:
|
|
231
|
+
* - inside a fenced code block,
|
|
232
|
+
* - the next non-blank line is indented (loose-list / blockquote
|
|
233
|
+
* continuation cannot be ruled out),
|
|
234
|
+
* - the next non-blank line starts a list item or blockquote AND the
|
|
235
|
+
* previous unit ended in a list item or blockquote (loose list /
|
|
236
|
+
* multi-paragraph quote continuation).
|
|
237
|
+
* The FINAL unit is always the live tail (never memoizable).
|
|
238
|
+
*
|
|
239
|
+
* BLANK LINES COME FROM `isBlankLine` (utils/markdown-fences), never from
|
|
240
|
+
* `String.prototype.trim()`. This file is the THIRD consumer of that module
|
|
241
|
+
* (it already instantiates `createFenceTracker`) and makes exactly the
|
|
242
|
+
* CommonMark blank-line decision the helper was minted for, yet it kept four
|
|
243
|
+
* hand-rolled `trim() === ''` tests — so the module's "every blank-line test"
|
|
244
|
+
* SSOT claim was false here. `trim()` strips the full Unicode White_Space set
|
|
245
|
+
* (U+00A0 NBSP, U+000B VT, U+000C FF, U+FEFF BOM), none of which CommonMark
|
|
246
|
+
* treats as blank: `splitStreamingBlocks('foo\n<NBSP>\nbar\n\ntail')` returned
|
|
247
|
+
* THREE units where the non-blank filler control (`foo\nx\nbar\n\ntail`)
|
|
248
|
+
* returns TWO — one NBSP line cut a paragraph into two `ReactMarkdown` units on
|
|
249
|
+
* the STREAMING path, rendering two `<p>` where the non-streaming and SEO twins
|
|
250
|
+
* render one. Not a security hole (the fence tracker and `htmlBlocksCut` still
|
|
251
|
+
* gate the cut), but a live divergence. It survived because the swallow sweep
|
|
252
|
+
* drove only `SimpleMarkdownRenderer`; the sweep now drives this splitter too,
|
|
253
|
+
* in both the exotic-blank and CRLF spellings.
|
|
254
|
+
*/
|
|
255
|
+
export function splitStreamingBlocks(content: string): StreamingBlock[] {
|
|
256
|
+
const lines = content.split('\n')
|
|
257
|
+
const units: string[][] = []
|
|
258
|
+
const unitTouchedHtml: boolean[] = []
|
|
259
|
+
let current: string[] = []
|
|
260
|
+
let currentTouchedHtml = false
|
|
261
|
+
const state = createScanState()
|
|
262
|
+
|
|
263
|
+
const isListOrQuote = (line: string) =>
|
|
264
|
+
/^\s{0,3}(?:[-+*]\s|\d{1,9}[.)]\s|>)/.test(line)
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* FIRST non-blank line of the accumulated unit — i.e. what this unit IS.
|
|
268
|
+
*
|
|
269
|
+
* The continuation guard below used the LAST non-blank line instead, which
|
|
270
|
+
* silently failed for every list item carrying a second block. Take the
|
|
271
|
+
* canonical shape of an assistant answer:
|
|
272
|
+
*
|
|
273
|
+
* 1. Install:
|
|
274
|
+
*
|
|
275
|
+
* ```bash
|
|
276
|
+
* npm i
|
|
277
|
+
* ```
|
|
278
|
+
*
|
|
279
|
+
* 2. Run it.
|
|
280
|
+
*
|
|
281
|
+
* At the blank line before `2.` the unit's last non-blank line is the fence
|
|
282
|
+
* closer, not a list marker — so the guard let the cut through and every
|
|
283
|
+
* remaining item became its OWN `<ol>` with no `start` attribute. The visible
|
|
284
|
+
* result is "1. / 1. / 1." while the extracted TEXT stays identical (which is
|
|
285
|
+
* why the equivalence tests missed it), and because those units are complete
|
|
286
|
+
* and atomic they were `memoizable`, so the wrong render was CACHED for the
|
|
287
|
+
* rest of the turn instead of self-healing on the next token.
|
|
288
|
+
*
|
|
289
|
+
* Asking whether the unit STARTS with a marker is the right question: it stays
|
|
290
|
+
* true across items while the list accumulates (each declined cut keeps the
|
|
291
|
+
* same first line), and it is false for a paragraph that merely happens to be
|
|
292
|
+
* followed by a list — so a genuinely new list after prose still cuts.
|
|
293
|
+
*/
|
|
294
|
+
const firstNonBlank = (arr: string[]): string | undefined => {
|
|
295
|
+
for (const line of arr) {
|
|
296
|
+
if (!isBlankLine(line)) return line
|
|
297
|
+
}
|
|
298
|
+
return undefined
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
for (let i = 0; i < lines.length; i++) {
|
|
302
|
+
const line = lines[i]
|
|
303
|
+
const depthBefore = state.htmlDepth
|
|
304
|
+
const pendingBefore = state.pendingOpenTag
|
|
305
|
+
scanLine(state, line)
|
|
306
|
+
if (
|
|
307
|
+
state.htmlDepth !== depthBefore ||
|
|
308
|
+
state.pendingOpenTag !== pendingBefore ||
|
|
309
|
+
htmlLatched(state)
|
|
310
|
+
)
|
|
311
|
+
currentTouchedHtml = true
|
|
312
|
+
current.push(line)
|
|
313
|
+
|
|
314
|
+
const isBlank = isBlankLine(line)
|
|
315
|
+
// Never cut inside an open fence, nor inside an unbalanced raw HTML
|
|
316
|
+
// block container / a start tag still waiting for its `>`.
|
|
317
|
+
if (!isBlank || state.fences.openChar() !== null || htmlBlocksCut(state)) continue
|
|
318
|
+
|
|
319
|
+
// Candidate boundary at a blank line outside fences. Look ahead to the
|
|
320
|
+
// next non-blank line to decide whether cutting here is provably safe.
|
|
321
|
+
let j = i + 1
|
|
322
|
+
while (j < lines.length && isBlankLine(lines[j])) j++
|
|
323
|
+
if (j >= lines.length) continue // trailing blanks stay with the tail
|
|
324
|
+
|
|
325
|
+
const next = lines[j]
|
|
326
|
+
if (/^\s/.test(next)) continue // indented continuation — unprovable
|
|
327
|
+
const unitOpener = firstNonBlank(current)
|
|
328
|
+
if (isListOrQuote(next) && unitOpener !== undefined && isListOrQuote(unitOpener)) {
|
|
329
|
+
// Loose list / multi-paragraph blockquote continuation — do not cut.
|
|
330
|
+
continue
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// A unit whose entire content is whitespace is not a block — with 2+
|
|
334
|
+
// blank lines between blocks the loop reaches a cut point with nothing
|
|
335
|
+
// but blanks accumulated, and emitting it costs a `ReactMarkdown`
|
|
336
|
+
// instance (and a React key) that renders nothing. Leave `current`
|
|
337
|
+
// accumulating so the blanks fold into the NEXT unit; line numbering
|
|
338
|
+
// (and therefore `startLine`) is unaffected either way.
|
|
339
|
+
if (current.every(isBlankLine)) continue
|
|
340
|
+
|
|
341
|
+
units.push(current)
|
|
342
|
+
unitTouchedHtml.push(currentTouchedHtml)
|
|
343
|
+
current = []
|
|
344
|
+
currentTouchedHtml = false
|
|
345
|
+
}
|
|
346
|
+
if (current.length) {
|
|
347
|
+
units.push(current)
|
|
348
|
+
unitTouchedHtml.push(currentTouchedHtml)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let cursorLine = 1
|
|
352
|
+
return units.map((unitLines, idx) => {
|
|
353
|
+
const text = unitLines.join('\n')
|
|
354
|
+
const isTail = idx === units.length - 1
|
|
355
|
+
const startLine = cursorLine
|
|
356
|
+
cursorLine += unitLines.length
|
|
357
|
+
return {
|
|
358
|
+
text,
|
|
359
|
+
index: idx,
|
|
360
|
+
startLine,
|
|
361
|
+
// Units carrying UNBALANCED raw HTML are never cached: an open
|
|
362
|
+
// container (or a start tag still awaiting its `>`) is exactly the
|
|
363
|
+
// case where rehype-raw's reparse makes a stale cached render
|
|
364
|
+
// structurally wrong rather than merely late. A raw container that
|
|
365
|
+
// opens AND closes within the unit (`<div>x</div>`) leaves the
|
|
366
|
+
// scanner's depth unchanged, so it stays memoizable — correctly: its
|
|
367
|
+
// parse cannot be invalidated by later tokens.
|
|
368
|
+
memoizable: !isTail && !unitTouchedHtml[idx] && !NON_MEMOIZABLE_RE.test(text),
|
|
369
|
+
}
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Auto-close an unterminated fenced code block at the streaming tail so the
|
|
375
|
+
* half-open fence doesn't flip the rest of the message into code while
|
|
376
|
+
* tokens arrive.
|
|
377
|
+
*
|
|
378
|
+
* Uses the SAME scanner as `splitStreamingBlocks` (never a parallel
|
|
379
|
+
* fence-count heuristic — a naive count treats ``` and ~~~ as
|
|
380
|
+
* interchangeable and appends a spurious closer to `` ```\n~~~\n``` ``),
|
|
381
|
+
* and appends the RECORDED opener so a `~~~` block is closed with `~~~`.
|
|
382
|
+
*/
|
|
383
|
+
export function completeStreamingTail(content: string): string {
|
|
384
|
+
const state = createScanState()
|
|
385
|
+
for (const line of content.split('\n')) scanLine(state, line)
|
|
386
|
+
const openChar = state.fences.openChar()
|
|
387
|
+
if (openChar === null) return content
|
|
388
|
+
const closer = openChar.repeat(state.fences.openLength())
|
|
389
|
+
return content.endsWith('\n') ? `${content}${closer}` : `${content}\n${closer}`
|
|
390
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-size presets for the unified markdown engine.
|
|
3
|
+
*
|
|
4
|
+
* `default` / `compact` / `large` are carried over verbatim from the old
|
|
5
|
+
* `SimpleMarkdownRenderer`. `article` is NEW — it reproduces the exact
|
|
6
|
+
* typography the old `RichMarkdownRenderer` hardcoded per element, so
|
|
7
|
+
* content surfaces (blog, case studies, releases, legal, docs) keep
|
|
8
|
+
* byte-identical classes through the unification.
|
|
9
|
+
*
|
|
10
|
+
* ODS-TOKENS FLAG (ODS_TOKEN_RULES §Typography / §General): the `article`
|
|
11
|
+
* preset carries raw px classes (`text-[32px] md:text-[40px] …`) because
|
|
12
|
+
* ODS currently has NO composite tokens for long-form prose — `text-h1`/
|
|
13
|
+
* `text-h2` are Azeret Mono UI-label scale and `text-h5` is uppercase,
|
|
14
|
+
* so mapping prose headings onto them would visibly regress articles.
|
|
15
|
+
* The `code` entry of EVERY preset (`text-[13px]` / `text-[14px]` /
|
|
16
|
+
* `text-[16px]`), not just `article`'s, is raw px for the same reason: ODS
|
|
17
|
+
* has no code/mono type scale. `base-components.tsx` carries the matching
|
|
18
|
+
* flag for inline code's `text-[0.9em]` (the mono FAMILY is no longer an
|
|
19
|
+
* exemption — both code surfaces now use `font-mono`).
|
|
20
|
+
* Per the unification plan, the missing prose- and code-typography tokens
|
|
21
|
+
* are FLAGGED here for addition to ODS; the raw-px carry-over is the
|
|
22
|
+
* documented, review-gated exemption recorded in the unification PR.
|
|
23
|
+
* Do NOT copy these px classes anywhere else.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export type TextSizeElement =
|
|
27
|
+
| 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
|
28
|
+
| 'p' | 'li' | 'blockquote' | 'code' | 'th' | 'td';
|
|
29
|
+
|
|
30
|
+
export type TextSizeClassMap = Partial<Record<TextSizeElement, string>>;
|
|
31
|
+
export type TextSizePreset = 'default' | 'compact' | 'large' | 'article';
|
|
32
|
+
export type TextSizeConfig =
|
|
33
|
+
| TextSizePreset
|
|
34
|
+
| TextSizeClassMap
|
|
35
|
+
| { preset: TextSizePreset; overrides: TextSizeClassMap };
|
|
36
|
+
|
|
37
|
+
export const TEXT_SIZE_PRESETS: Record<TextSizePreset, Record<TextSizeElement, string>> = {
|
|
38
|
+
default: {
|
|
39
|
+
h1: 'text-heading-1',
|
|
40
|
+
h2: 'text-heading-2',
|
|
41
|
+
h3: 'text-2xl md:text-3xl',
|
|
42
|
+
h4: 'text-xl',
|
|
43
|
+
h5: 'text-lg md:text-xl',
|
|
44
|
+
h6: 'text-base md:text-lg',
|
|
45
|
+
p: 'md:text-h4 lg:text-h4',
|
|
46
|
+
li: 'text-base md:text-lg',
|
|
47
|
+
blockquote: 'text-lg',
|
|
48
|
+
code: 'text-[14px]',
|
|
49
|
+
th: 'text-xs md:text-sm',
|
|
50
|
+
td: 'text-xs md:text-sm',
|
|
51
|
+
},
|
|
52
|
+
compact: {
|
|
53
|
+
h1: 'text-heading-2',
|
|
54
|
+
h2: 'text-heading-3',
|
|
55
|
+
h3: 'text-xl md:text-2xl',
|
|
56
|
+
h4: 'text-lg md:text-xl',
|
|
57
|
+
h5: 'text-base md:text-lg',
|
|
58
|
+
h6: 'text-sm md:text-base',
|
|
59
|
+
p: 'text-base md:text-lg',
|
|
60
|
+
li: 'text-base md:text-lg',
|
|
61
|
+
blockquote: 'text-base md:text-lg',
|
|
62
|
+
code: 'text-[13px]',
|
|
63
|
+
th: 'text-xs md:text-sm',
|
|
64
|
+
td: 'text-xs md:text-sm',
|
|
65
|
+
},
|
|
66
|
+
large: {
|
|
67
|
+
h1: 'text-heading-1',
|
|
68
|
+
h2: 'text-heading-1',
|
|
69
|
+
h3: 'text-heading-2',
|
|
70
|
+
h4: 'text-2xl md:text-3xl',
|
|
71
|
+
h5: 'text-xl md:text-2xl',
|
|
72
|
+
h6: 'text-lg md:text-xl',
|
|
73
|
+
p: 'text-h3',
|
|
74
|
+
li: 'text-lg md:text-xl',
|
|
75
|
+
blockquote: 'text-xl md:text-2xl',
|
|
76
|
+
code: 'text-[16px]',
|
|
77
|
+
th: 'text-sm md:text-base',
|
|
78
|
+
td: 'text-sm md:text-base',
|
|
79
|
+
},
|
|
80
|
+
// Old RichMarkdownRenderer typography, byte-for-byte (see header note).
|
|
81
|
+
article: {
|
|
82
|
+
h1: 'text-[32px] md:text-[40px] lg:text-[48px] leading-[1.25]',
|
|
83
|
+
h2: 'text-[28px] md:text-[32px]',
|
|
84
|
+
h3: 'text-[24px] md:text-[28px]',
|
|
85
|
+
h4: 'text-[20px] md:text-[22px]',
|
|
86
|
+
h5: 'text-[18px] md:text-[20px]',
|
|
87
|
+
h6: 'text-[16px] md:text-[18px]',
|
|
88
|
+
// Full class string incl. spacing/leading — `cn` (tailwind-merge)
|
|
89
|
+
// resolves these OVER the base map's `leading-relaxed mb-4`, keeping
|
|
90
|
+
// Rich's article rhythm (`my-4`, `leading-[1.6]`, `font-sans`).
|
|
91
|
+
p: 'font-sans text-[16px] md:text-[18px] lg:text-[20px] leading-[1.6] my-4',
|
|
92
|
+
li: 'text-[16px] md:text-[18px]',
|
|
93
|
+
blockquote: 'text-[1.125em]',
|
|
94
|
+
code: 'text-[14px]',
|
|
95
|
+
th: 'text-xs md:text-sm',
|
|
96
|
+
td: 'text-xs md:text-sm',
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export function resolveTextSizeConfig(config?: TextSizeConfig): Record<TextSizeElement, string> {
|
|
101
|
+
const defaultSizes = TEXT_SIZE_PRESETS.default;
|
|
102
|
+
if (!config) return defaultSizes;
|
|
103
|
+
if (typeof config === 'string') return TEXT_SIZE_PRESETS[config];
|
|
104
|
+
if ('preset' in config) return { ...TEXT_SIZE_PRESETS[config.preset], ...config.overrides };
|
|
105
|
+
return { ...defaultSizes, ...config };
|
|
106
|
+
}
|
|
@@ -4,7 +4,7 @@ import React, { useState } from 'react';
|
|
|
4
4
|
import { Badge } from './badge';
|
|
5
5
|
import { ChevronDown } from 'lucide-react';
|
|
6
6
|
import { FadePreview } from './fade-preview';
|
|
7
|
-
import { RichMarkdownRenderer } from './
|
|
7
|
+
import { RichMarkdownRenderer } from './markdown';
|
|
8
8
|
import type { ChangelogEntry } from '../../types/product-release';
|
|
9
9
|
|
|
10
10
|
interface ReleaseChangelogSectionProps {
|
|
@@ -12,7 +12,7 @@ import { TicketDetailSection } from "./ticket-detail-section"
|
|
|
12
12
|
import { type TicketAttachment, TicketAttachmentsList } from "./ticket-attachments-list"
|
|
13
13
|
import type { TicketNote } from "./ticket-note-card"
|
|
14
14
|
import { TicketNotesSection } from "./ticket-notes-section"
|
|
15
|
-
import { SimpleMarkdownRenderer } from "./simple-markdown-renderer"
|
|
15
|
+
import { SimpleMarkdownRenderer } from "./markdown/simple-markdown-renderer"
|
|
16
16
|
|
|
17
17
|
export type { TicketAssigneeOption }
|
|
18
18
|
|
package/src/utils/index.ts
CHANGED
|
@@ -351,6 +351,7 @@ export * from './humanity-signals'
|
|
|
351
351
|
export * from './doc-path-utils'
|
|
352
352
|
export * from './doc-tree-nav'
|
|
353
353
|
export * from './tree-builder'
|
|
354
|
+
export * from './markdown-heading-id'
|
|
354
355
|
export * from './markdown-section-extractor'
|
|
355
356
|
export * from './markdown-to-plain'
|
|
356
357
|
export * from './embed-url-converters'
|