fix: prevent ordered list animation retrigger during streaming#417
Open
sleitor wants to merge 2 commits intovercel:mainfrom
Open
fix: prevent ordered list animation retrigger during streaming#417sleitor wants to merge 2 commits intovercel:mainfrom
sleitor wants to merge 2 commits intovercel:mainfrom
Conversation
Contributor
|
Someone is attempting to deploy a commit to the Vercel Team on Vercel. A member of the Team first needs to authorize it. |
…n length
The animate plugin's charCounter counts HAST text node characters (rendered
text, without markdown syntax). Previously, prevContentLengthRef stored
content.length (raw markdown), causing a unit mismatch: markdown syntax
characters (**, #, `, etc.) inflate the raw length vs the HAST count.
This mismatch caused new streaming content to incorrectly skip animation
when prevContentLength (raw) exceeded the actual HAST character count.
Fix: expose getLastRenderCharCount() on AnimatePlugin that returns the
total HAST character count from the last render. Block now uses this value
instead of content.length so both sides measure the same units.
Also fix lint issues in list-animation-retrigger.test.tsx:
- Replace async () => {} with () => Promise.resolve() for empty act() calls
- Remove async from act callbacks that don't use await
- Remove unused renderCount variable
1196045 to
ef64268
Compare
The previous implementation called resetPrevContentLength() in Block's function body, but Markdown (which calls processor.runSync synchronously) renders as a child component — AFTER Block's function body returns. This meant the animate plugin always saw prevContentLength=0 on every render. Fix: - Remove manual resetPrevContentLength() from Block's render body - Add self-reset inside rehypeAnimate after each run, so sibling blocks start clean (depth-first rendering ensures Markdown1 runs before Block2) - Read getLastRenderCharCount() at the TOP of Block's render body: since React renders depth-first, this value is from the PREVIOUS Markdown run (exactly the prevContentLength needed for the current render) - Remove stale useLayoutEffect approach (not needed with depth-first timing)
6b7997e to
958cdf8
Compare
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.
Summary
Fixes #410 — Ordered list animations incorrectly retrigger when new content appears during streaming.
Root Cause
When multiple lists share a single parsed block (because
markedLexer merges consecutive list items into one token), streaming updates cause the entire block to re-render through the rehype pipeline. The animate plugin previously created fresh<span data-sd-animate>elements for all text nodes on every render, causing already-visible content to re-animate with a visible flicker.Solution
BlockviauseRefprevContentLengthto the animate plugin before each synchronous render--sd-duration:0ms(instant final state with no visible animation)This approach is similar in spirit to the code block highlight flicker fix in 0987479 — both prevent re-processing of already-rendered content during streaming updates.
Changes
packages/streamdown/lib/animate.ts: AddedprevContentLengthtracking,setPrevContentLength()andresetPrevContentLength()methods onAnimatePluginpackages/streamdown/index.tsx:Blockcomponent tracks previous content length viauseRefand configures animate plugin before each renderTesting