a4890569a3
StreamingMd previously split in-flight text into one memoized stable-prefix <Md> plus a re-parsed tail. Every time the stable boundary advanced, the prefix string changed, its memo key missed, and the entire prefix re-tokenized — O(blocks^2) parse work across a long reply — and finding the boundary rescanned fence state from position 0 on every delta. Replace the monolithic prefix with an append-only array of settled top-level blocks, each rendered as its own <Md> memoized on text that never changes once committed (every block parses exactly once for the life of the stream), plus a persistent scanner that keeps fence/math open-state and scan position across deltas so each delta only scans the newly arrived complete lines. Boundaries stay at "\n\n" outside code/math fences; partial trailing lines stay in the tail until their newline arrives so a growing "```" can't be misjudged. Replaying a newline-terminated block-heavy stream at width 80 through a real Ink render (one process per strategy so the parse LRU and GC pressure can't cross-contaminate): | Blocks | Appends | naive full Md | monolithic prefix | per-block | |--------|---------|---------------|-------------------|-----------| | 32 | 135 | 279.7 ms | 221.1 ms | 104.7 ms | | 128 | 543 | 3.03 s | 2.39 s | 317.8 ms | | 512 | 2175 | 56.28 s | 35.14 s | 3.05 s | Remaining per-block cost is Ink layout of the growing tree, not parsing. Bench: ui-tui/scripts/bench-streaming-md.tsx.