fix(renderer): make code entrance animation type line-by-line#724
Merged
Conversation
…IC#531) On first render animStates was an empty Map, populated only via a setTimeout(0) effect. Every CodeLineRow therefore mounted with animState undefined, latched mounted=true from its initial props, and took the non-animated branch. When the states arrived one tick later, every already-mounted row fired setTyping(true) in the same tick, so all lines typed simultaneously - the delayed-mount stagger path was unreachable on first paint. Compute the first-render typing states synchronously in the useState initializer so rows see their animState and typingDelay on the very first render: line 0 types immediately, each later row stays unmounted until its cumulative delay. The effect now only handles subsequent line edits (inserted/replaced), with prevLinesRef seeded to the initial lines. Behavior note: previously, toggling animate from false to true marked every line "inserted" (prevLinesRef started empty); now the ref is seeded so a toggle does not replay the entrance - arguably the less surprising behavior. Fixes THU-MAIC#531 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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 #531 — the code element's entrance animation typed all lines simultaneously instead of line-by-line.
Root cause
Exactly the ordering issue the report hypothesized:
animStatesis an empty Map (it was only populated via asetTimeout(..., 0)inside an effect).CodeLineRowtherefore mounts withanimState === undefined→isNewLine === false→useState(!isNewLine || typingDelay === 0)latchesmounted = true. ThetypingDelaysmap is also empty at this point, so every row getstypingDelay = 0.isNewLineflips to true for every row — butmountedis already latched true, so the delayed-mount path (setTimeout(() => setMounted(true), typingDelay)) is a no-op, and themounted && isNewLineeffect firessetTyping(true)for all rows in the same tick.Fix
Compute the first-render
'typing'states synchronously in theuseStateinitializer, so rows see theiranimStateand cumulativetypingDelayon the very first render: line 0 types immediately, each later row rendersnulluntil its stagger delay elapses. The effect now only handles subsequent line edits (inserted/replaced), withprevLinesRefseeded to the initial lines and the first effect run skipped.Behavior note: previously, toggling
animatefalse→true marked every lineinserted(becauseprevLinesRefstarted empty). With the ref seeded, a toggle no longer replays the entrance — flagging in case that old behavior was intentional.Scope note:
packages/@maic/renderer/src/elements/code/BaseCodeElement.tsxhas the identical first-render pattern, but #706 is currently rewriting that exact effect (prev-line map). To avoid conflicting with it, this PR only fixes the app copy; happy to port the same fix to the package copy once #706 lands.Verification
tsc --noEmit,eslint,prettier --checkall clean.vitest.config.tsonly includestests/**/*.test.tsin a node environment (no jsdom/RTL infrastructure for.tsxbehavior tests). If you'd like one, I can propose a minimal jsdom setup in a separate PR.🤖 Generated with Claude Code