Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

Animation: CJK paragraphs only animate their first batch, and queued words pop in when isAnimating turns false

Abierto
#669 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

@sleitor ya está trabajando en esto.

Desde el 16/10/2026.

  • #678 de @sleitor — abierto

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
35/100
Tipo de issue
Error
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
react, typescript
Área
frontend

Línea de trabajo

Start with the word-splitting in the animate plugin behind createAnimatePlugin and its sep option, which splits text nodes on /\s/. Run the repro script from the issue with sep: "word" and sep: "char" to see the unanimated span output. The second problem is in how Streamdown drops the animate plugin when isAnimating turns false, so read the per-block plugin list there. Done means CJK paragraphs animate fully and the trailing stagger completes before the spans are removed; the issue's open design questions need a maintainer decision first.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Two problems with animated streaming in [email protected], both visible in Chinese/Japanese chat replies.

1. sep: "word" treats a whole CJK paragraph as one word, so most of it never animates

sep: "word" splits text nodes at whitespace (/\s/). CJK text has no spaces between words, so a paragraph is a single "word". A word that started before prevContentLength is treated as already rendered (duration 0ms), so every character appended to it after the first commit appears with no animation.

Repro with the exported createAnimatePlugin, simulating four streaming commits of one paragraph:

import { createAnimatePlugin } from "streamdown";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";

const full = "量子電腦利用疊加與糾纏來平行處理資訊,因此在特定問題上遠快於傳統電腦。不過目前的硬體仍然很容易受到雜訊干擾。";
for (const sep of ["word", "char"]) {
  const plugin = createAnimatePlugin({ sep });
  let prev = 0;
  for (const n of [12, 25, 40, full.length]) {
    plugin.setPrevContentLength(prev);
    const md = full.slice(0, n);
    const tree = await unified().use(remarkParse).use(remarkRehype).use(plugin.rehypePlugin)
      .run(unified().use(remarkParse).parse(md));
    const spans = tree.children[0].children.filter((c) => c.tagName === "span");
    const animated = spans.filter((s) => !s.properties.style.includes("--sd-duration:0ms"));
    console.log(sep, n, animated.map((s) => s.children[0].value).join("|"));
    prev = n;
  }
}
word 12 量子電腦利用疊加與糾纏來
word 25                          <- nothing animates
word 40
word 54
char 12 量|子|電|腦|利|用|疊|加|與|糾|纏|來
char 25 平|行|處|理|資|訊|,|因|此|在|特|定|問
...

So with the default sep, a CJK reply fades in its first few characters and then pops in for the rest of each paragraph. sep: "char" works, but turns Latin text into a per-letter typewriter, so mixed-language replies have no good setting.

Suggestion: split words with Intl.Segmenter(undefined, { granularity: "word" }) (falling back to whitespace where unavailable). It segments CJK into words and leaves Latin text as today. The @streamdown/cjk plugin doesn't touch the animate plugin, so it doesn't help here.

2. Characters still waiting for their stagger appear all at once when isAnimating turns false

With stagger (default 40ms) the shared timeline schedules new words up to maxBacklogMs (default 320ms) ahead of wall-clock. When the stream ends and isAnimating becomes false, Streamdown stops adding the animate plugin to each block's rehype plugins immediately, so every block re-renders without the data-sd-animate spans. Words still waiting on --sd-delay, plus those mid-fade, become fully visible in that frame. The end of every reply visibly "pops", more so with sep: "char" or a fast model, where the backlog is longest.

Expected: after isAnimating goes false, keep the animate plugin until the timeline's last scheduled delay plus duration has elapsed, then drop the spans. We currently work around it by holding isAnimating true for maxBacklogMs + duration after the stream ends.

Also

stagger and maxBacklogMs are real AnimateOptions (in the types and source) but aren't on https://streamdown.ai/docs/animation. We had set stagger: 0 without knowing it disables the per-batch spread, which made streaming look chunky.

Environment: streamdown 2.7.0, React 19.3. Issue 1 reproduced with the script above; issue 2 is from reading dist/chunk-*.js (the per-block plugin list only includes the animate plugin while isAnimating).

Lenguaje dominante
TypeScript
Estrellas
5.7k
Forks
307
Merge medio
1 d 19 h
PR fusionados (30 d)
24

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de vercel/streamdown

Todos los issues de vercel/streamdown

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.