Repository navigation
feat(core): prepare streamed markdown in one core session - #1141
Merged
Merged
Conversation
This was referenced Nov 9, 2026
hryhoriiK97
force-pushed
the
perf/stream-session
branch
from
November 11, 2026 19:28
43bfc40 to
2f95f96
Compare
Every streaming consumer (the iOS and Android views, their view-free measurement and the JS block collector) now gets the same snapshot from a core StreamingSession behind DocumentStore: the blocks that can no longer change stay as they are, the filter withholds trailing blocks still arriving, and repair, now compiled into every native build, handles inline syntax still being typed. The commonmark flavor gets the same handling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
finishedBlocksEnd stopped at the first HTML block that may hold blank lines (a comment, <pre>, <script>, <style>, <textarea>, <?, <!), closed or not. After one such block every update filtered, repaired and parsed the whole document again. It now skips the block until the text that closes it, as md4c ends it. Tag names compare ignoring case and end at a space, > or the line's end: <PRE> opened a block the boundary didn't see, and <prefix> one it did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A trailing `$` run was always withheld as a possible opener, so finished math at the very end (`$O(n)$`, a `$$` block without its newline) and currency like `5$` vanished until the next character. The run now shows when it closes math opened earlier in its scope or follows a character after which no math opens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parser's flags reached repair but not the filter or finishedBlocksEnd, so with latexMath off a `$$` line still cut everything after it and held the finished boundary there until the stream ended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hole Every streamed prefix of the fixtures now also compares the snapshot with repairing the whole filtered document, and runs once with latexMath off. The HTML block case is left out of the repair comparison: repair reads raw HTML as Markdown, so only the tail repairs it right. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A later reference definition still turns an earlier `[docs][r]` into a link, so the finished part is fixed text, not fixed blocks. Also drops HTML from a comment where startsTopLevelBlock already rules it out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The "hidden" check was written out at three places: both views' updateProps and the view-free measurement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The modes only shape the streaming snapshot, so a change while not streaming re-rendered the same text. Android already skips it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every parse and streaming snapshot looked up the class and ten field ids again. The ids are now cached on first use, and a missing field clears its NoSuchFieldError instead of leaving it pending for later JNI calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hryhoriiK97
force-pushed
the
perf/stream-session
branch
from
November 11, 2026 19:50
2f95f96 to
704ce8a
Compare
6 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What/Why?
While a document streams with
streamingAnimation, half-written Markdown reaches the screen:**bold teshows its asterisks,[docs](https://exashows as raw text, and$O(n \logshows raw LaTeX until the closing$arrives. The streaming filter only knew about trailing blocks (tables, fences, media,$$), and the repair module that handles inline syntax (#830, #1092) wasn't compiled into any native build. Thecommonmarkflavor had no streaming handling at all.This is the second PR of the streaming stack, stacked on #1140. It adds the core
StreamingSessionfrom the streaming plan: one place that prepares a streamed document for every consumer.The streaming stack
streaming,streamingConfigpresets,append/reset.How
One session in core.
streamingSnapshot(source, options)inpackages/core/cpp/parser/StreamingSession.{hpp,cpp}:finishedBlocksEnd()finds where the blocks that later text can no longer change end. That is the last line that starts a top-level block after a blank line, outside fenced code,$$math and HTML blocks that may hold blank lines. It uses the filter's own fence and quote rules, and only grows as the stream grows.$$that haven't finished arriving.streamingRepairOptions).$waits.The work after step 1 is proportional to the unfinished tail. On an 80 KB answer, a snapshot costs about 30 µs, against about 800 µs to filter and repair the whole document.
Same snapshot everywhere.
DocumentStore::filterbecomesDocumentStore::snapshot, keyed by the source, the block modes and the parser flags. The iOS and Android views, their view-free measurement and the JS block collector all go through it, so measurement, rendering and slot collection agree.The filter owns blocks, repair owns inline syntax. The filter's inline link cut is gone. Repair takes over the cases the cut handled:
<...>destination or an open title, and prose when other text follows the destination.[label]at the very end waits for a possible(.\!starts no image.[left open more than 512 bytes back is a stray bracket. This also keeps the bracket scans linear.These are documented as divergences from the reference in
MarkdownRepair.hpp. The single-tilde escape is off for streaming: it rewrites every~in the document, so a paragraph would change once it was finished.commonmarktoo.EnrichedMarkdownTexton both platforms now takes the streaming modes and prepares its text through the session whilestreamingAnimationis on. TogglingstreamingAnimationor a mode now re-renders it.Builds.
repair/is compiled whereverparser/is: the RN podspec and Android CMake, the core podspec, the standalone Android parser and iOS SPM package (newcore/repairlink), the example app's display benchmark, and the core test script.Docs. The streaming guide and the API reference no longer say
streamingConfigis inert undercommonmark, and the guide gains a short section on half-written inline syntax.Testing
Core (
scripts/test-core-parser.sh,yarn core:test):streaming_session_test:Visible text that changes instead of growing. The documents stream at a fixed chunk size; every update is parsed with md4c, and the harness counts the updates whose visible text isn't an append of the previous one:
What remains: a full reference link before its definition arrives, and a lone
|that becomes a spoiler's||.Unit tests: iOS 70 of 70, Android 96 of 96, ktlint clean.
Devices: Release builds of the example app at #1140 and at this PR, on an iPhone 15 Pro (iOS 26.6.1) and a Galaxy A16 (Android 16, device farm).
streamingAnimationstream:<https,[label](https://…and$O(n \logwhile each arrives.Not covered: web has no streaming filter yet. It comes with the streaming API in the last PR of the stack.
Changes after review
<pre>,<script>,<style>,<textarea>,<?,<!) instead of stopping there for good. Tag names compare as md4c does: ignoring case and ending at a space,>or the line's end.$ends the streamed text shows.$$counts as math in the filter only withlatexMath.streamingConfigmode change only while streaming, and reads the mode strings with one helper. Android looks up theMd4cFlagsfield ids once.Podfile.lockchecksums follow the podspec changes.Checked on the whole stack after these changes: core suite and fuzzing, Jest (1,838), Android unit tests (134), iOS unit tests (118), ktlint; streams on an iPhone 15 Pro and a Galaxy A16 call
onStreamingEndonce and end at the static height, with CPU level or lower; the 106 Maestro flows give the same results and screenshots as before.PR Checklist
🤖 Generated with Claude Code