/** * Streaming helpers for the unified markdown engine (pure, no React). * * Design contract (unification plan §D1 "Streaming rendering performance"): * - Markdown is NOT context-free across blocks. The splitter only cuts at * boundaries it can PROVE are atomic; when atomicity is unprovable the * content is left in one unit (unmemoized) — correctness beats cache hits. * - Blocks containing `card://` / `mention://` markers are never memoized: * their text is identical before/after the entity ref resolves, so a * text-keyed cache would serve the stale unresolved card mid-stream. * - On stream completion the engine discards all of this and does ONE * authoritative whole-document parse — streaming can never permanently * diverge (late reference defs, footnotes, list renumbering self-heal). * - Tail completion auto-closes ONLY unterminated fenced code blocks * (fence-count based — unambiguous). Inline emphasis/links are NOT * auto-closed: a stray `*` in "2 * 3" or a non-link `[` would be * mis-wrapped, which is worse than the brief flicker it prevents. */ export interface StreamingBlock { /** Raw markdown source of this unit (including trailing blank lines). */ text: string; /** Position index — cache key component so identical blocks never alias. */ index: number; /** * 1-based line of this unit's first line WITHIN the whole document. * Each unit is parsed by its own `ReactMarkdown`, so hast positions are * unit-relative; the engine adds `startLine - 1` back to look ids up in * the document-wide heading-id map (see ./heading-ids.ts). */ startLine: number; /** * True when this unit may be render-cached: it is complete (not the * trailing unit), atomic, and free of cross-block / late-resolving * constructs (card/mention markers, reference definitions/uses, * footnotes). */ memoizable: boolean; } /** * Split streaming markdown into atomic units at blank lines that are * provably block boundaries. A blank line does NOT split when: * - inside a fenced code block, * - the next non-blank line is indented (loose-list / blockquote * continuation cannot be ruled out), * - the next non-blank line starts a list item or blockquote AND the * previous unit ended in a list item or blockquote (loose list / * multi-paragraph quote continuation). * The FINAL unit is always the live tail (never memoizable). * * BLANK LINES COME FROM `isBlankLine` (utils/markdown-fences), never from * `String.prototype.trim()`. This file is the THIRD consumer of that module * (it already instantiates `createFenceTracker`) and makes exactly the * CommonMark blank-line decision the helper was minted for, yet it kept four * hand-rolled `trim() === ''` tests — so the module's "every blank-line test" * SSOT claim was false here. `trim()` strips the full Unicode White_Space set * (U+00A0 NBSP, U+000B VT, U+000C FF, U+FEFF BOM), none of which CommonMark * treats as blank: `splitStreamingBlocks('foo\n\nbar\n\ntail')` returned * THREE units where the non-blank filler control (`foo\nx\nbar\n\ntail`) * returns TWO — one NBSP line cut a paragraph into two `ReactMarkdown` units on * the STREAMING path, rendering two `

` where the non-streaming and SEO twins * render one. Not a security hole (the fence tracker and `htmlBlocksCut` still * gate the cut), but a live divergence. It survived because the swallow sweep * drove only `SimpleMarkdownRenderer`; the sweep now drives this splitter too, * in both the exotic-blank and CRLF spellings. */ export declare function splitStreamingBlocks(content: string): StreamingBlock[]; /** * Auto-close an unterminated fenced code block at the streaming tail so the * half-open fence doesn't flip the rest of the message into code while * tokens arrive. * * Uses the SAME scanner as `splitStreamingBlocks` (never a parallel * fence-count heuristic — a naive count treats ``` and ~~~ as * interchangeable and appends a spurious closer to `` ```\n~~~\n``` ``), * and appends the RECORDED opener so a `~~~` block is closed with `~~~`. */ export declare function completeStreamingTail(content: string): string; //# sourceMappingURL=streaming.d.ts.map