Repository navigation
feat(session-ui): replace marked + remend streaming re-lexing with @opencode/patchmark - #57016
Merged
Merged
Conversation
…pencode/patchmark
…l-corpus parity, and refdef cache invalidation
kitlangton
force-pushed
the
patchmark-streaming
branch
from
October 19, 2026 21:38
15b5120 to
150a772
Compare
…r-step allocations
kitlangton
marked this pull request as ready for review
October 20, 2026 05:32
This was referenced Oct 26, 2026
1 task
3 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.
In short
What it changes: while an assistant reply streams in, the desktop app re-parses the whole message as Markdown on every update. Long replies get slower and slower to render. This PR parses only the new text at the end, leaves finished blocks alone, inlines the full
@opencode/patchmarktest and fuzz suite intopackages/patchmark, and removes the unusedremenddependency.What a user sees: the exact same rendered reply as
dev(345/345real assistant messages + torture stream identical in Chromium), with34×less hot-loop block-splitting CPU (6 µs/flushvs163 µs/flush),8.7×less main-thread CPU on long replies with reference links and nested code fences (0.68 ms/flushvs5.91 ms/flushontorture),9.9×fewer mid-stream tail rewrites (300vs2,975), and 100% CommonMark 0.31.2 (652/652) + GFM (24/24) compliance.Why it matters: streaming Markdown runs on every assistant turn, so this is on the hot path for every user.
Summary
Replaces full-prefix
marked.lexer(text)+remend(raw)re-lexing inpackages/session-uiwith@opencode/patchmark(packages/patchmark), an incremental streaming CommonMark 0.31.2 + GFM + Math block and inline parser, and removesremend.What changed
packages/patchmark(@opencode/patchmark): Zero-runtime-dependency workspace package implementing the CommonMark 0.31.2 line-by-line block and delimiter-stack inline reference algorithm (src/blocks.ts,src/inline.ts,src/entities.ts,src/html.ts,src/stream.ts,src/heal.ts):100kagainstcommonmark0.31.2 +30kagainstmicromark-extension-gfm) with0unexplained diffs.20k), long*/_and distinct unclosed backtick runs, depth-200 nested blockquotes and lists, 1,000-row streaming GFM tables, and a 1 MB reply streamed 1 character at a time (1,048,576steps).groupHtmlBlocks: true, e.g.<details>...</details>) intact so nested fenced code blocks, continuation paragraphs, and loose/tight list semantics match whole-messagemarkedwithout a post-stream re-render.[id]: url "title"definitions emit{ op: "replace", index }only for blocks referencing that label, andmarkdown-cache.tsx/markdown.tsxkey settled blocks onblock.srcso backfilled definitions re-render cleanly and survivefinalize.src/heal.ts): Strips empty trailing markers (**,*,_,~~,`) before content arrives (preventing**→****→<hr>flashes) and holds incomplete setext underline candidates (-/=) until newline.packages/session-ui&packages/app: Routesstream()andproject()throughPatchmarkStream, preserves settledRenderedBlockreferences across streaming updates, and removesremend.End-to-End Streaming Render Profile in Chromium
Measured across the 345-message corpus plus the long-reply
torturefixture in Chromium (5 µs timer resolution, pre-warmed Shiki grammars):25,777flushes)dev→ PR94,306flushes)dev→ PRtorture(950flushes)dev→ PRproject)426.9 → 107.9 µs(17.6% → 5.8%)404.7 → 109.5 µs(16.0% → 5.0%)789.0 → 90.4 µs(8.7× faster)markedHTML render177.5 → 185.4 µs(7.3% → 10.0%)175.2 → 201.0 µs(6.9% → 9.2%)824.8 → 132.9 µs(6.2× faster)DOMPurify)210.9 → 178.3 µs(8.7% → 9.6%)199.4 → 189.7 µs(7.9% → 8.7%)895.0 → 131.3 µs(6.8× faster)39.5 → 9.4 µs(4.2× faster)35.5 → 28.5 µs(1.4% → 1.3%)983.2 → 164.9 µs(6.0× faster)1,473.0 → 1,269.0 µs(60.8% → 68.5%)1,627.9 → 1,548.6 µs(64.2% → 71.1%)4,981.6 → 510.8 µs(9.8× faster)postMessageCPU94.9 → 103.1 µs(3.9% → 5.6%)92.6 → 99.5 µs(3.7% → 4.6%)118.9 → 94.5 µs(1.26× faster)1,706.2 → 1,468.9 µs(>16 ms:65 → 8)1,850.4 → 1,760.6 µs(>16 ms:267 → 209)5,910.1 → 678.1 µs(8.7×,>16 ms:61 → 0)716.5 → 384.3 µs(1.86× faster)684.9 → 416.2 µs(1.65× faster)2,682.5 → 446.8 µs(6.0× faster)2,422.6 → 1,853.2 µs(1.31× faster)2,535.3 → 2,176.8 µs(1.16× faster)8,592.5 → 1,124.9 µs(7.6× faster)Recommendation on
marked& Top 3 Next Winsmarkedin the worker for HTML rendering.markedis only 9.2–10.0% of total CPU (~185–201 µs/flushon the worker) and 0.15% of main-thread time (~2 µs/flush). Replacing it withpatchmark's HTML renderer would duplicate KaTeX, async nested-Shiki, and custom link/table rendering for almost no main-thread gain, while 80.9% of total CPU is on the main thread.68.5–71.1%of total CPU, est.~600–850 µs/flushsaved): ScopedecorateCodeBlocks/images.updateto touched blocks instead of runningcontainer.querySelectorAllacross all settled blocks 3 times per flush; keep the rendered"live"DOM incanReusePendingBlockwhile"full"settles instead of flashingfallback()HTML; and cap word-level Solid store splitting (markdown-solid.tsx) on large container blocks.project+ live-tailmarked.parseinto one worker round-trip and share cache acrosslive→full(22.5–25.2%of total CPU, est.~145 µs/flushCPU +~600 µs/flushIPC latency saved): Eliminates the second worker RPC per flush, posts onlypatchesinstead of all settled blocks, and skips re-runningmarked+DOMPurifywhen a block'ssrcis unchanged on settle.d_shikiNested:79%of Shiki time) and coalesce flushes torequestAnimationFrame(est.~85%of nested Shiki saved +2–4×fewer flushes under fast bursts): CachecodeToHtmlfor completed code blocks nested inside open lists/blockquotes/<details>, and batch incoming streaming updates to display frames.@opentui/coreparseMarkdownIncremental): Across166,667token flushes on the 346 streams,parseMarkdownIncremental(marked.lexerwithtrailingUnstable = 2on the TUI main thread) takes47.3 µs/flush(7,891 mstotal, p95173 µs, p99368 µs), whereas@opencode/patchmarktakes7.5 µs/flush(1,244 mstotal, p9517 µs, p9939 µs) — a 6.3× overall and 9.3–10.2× tail latency reduction on the TUI main thread.Before / After Media
Before (
dev) streamingbefore-dev-stream.mp4
After (
@opencode/patchmark) streamingafter-patchmark-stream.mp4
Settled output. Before and after are byte-for-byte identical (
11,976 BHTML /262,426 BPNG), so one screenshot shows both:Verification
bun --cwd packages/patchmark test:22/22pass (65,292assertions across 652 CommonMark + 24 GFM spec examples, 1-char/random/token streaming invariance, seeded differential fuzzing, and slow-input linear time-budget tests)bun --cwd packages/patchmark fuzz:100,000core CommonMark +30,000GFM differential fuzz cases pass with0unexpected diffsbun --cwd packages/session-ui test:254/254targeted markdown tests passbun run typecheck+bun run lint:changed: pass (0errors,0warnings)