Skip to content

Consensus series: all 16 step-through SVG animations have been broken since the Fumadocs migration #73

Description

@vatsalpatel

Every figure in the Generalized Consensus series that was authored as an animation — 15 stepped manually via a Next button, 1 autoplayed with a replay button — now renders as a single static image showing all animation states superimposed at once. The prose still refers to "the animation above" / "the following animated example", so the affected sections are not just un-animated, they're unreadable — several figures on a page are byte-identical images.

Affected pages

16 animations across 5 posts, each duplicated in the blog and docs copies (10 routes total):

Route Animations
/blog/generalized-consensus-part4 · /docs/consensus/part-04 1
/blog/generalized-consensus-part6 · /docs/consensus/part-06 5
/blog/generalized-consensus-part7 · /docs/consensus/part-07 6
/blog/generalized-consensus-part8 · /docs/consensus/part-08 3
/blog/generalized-consensus-part10 · /docs/consensus/part-10 1

Clearest example: https://multigres.com/blog/generalized-consensus-part7. Figures 3, 4, 5, 6, 7 and 8 are six <img> tags pointing at the same part07-fig3.svg:

<img alt="Figure 3: Raft timeline propagation" src="/assets/part07-fig3-DEk2p0Pr.svg" >
<img alt="Figure 4: Initial state"             src="/assets/part07-fig3-DEk2p0Pr.svg" >
<img alt="Figure 5: Scenario 2"                src="/assets/part07-fig3-DEk2p0Pr.svg" >
<img alt="Figure 6: Scenario 3"                src="/assets/part07-fig3-DEk2p0Pr.svg" >
<img alt="Figure 7: Scenario 4"                src="/assets/part07-fig3-DEk2p0Pr.svg" >
<img alt="Figure 8: Scenario 5"                src="/assets/part07-fig3-DEk2p0Pr.svg" >

Why the images look wrong, not merely static

These SVGs are not pictures of one state — they are the whole sequence stacked in one file, with every state given an id. part07-fig3.svg carries 70 ids: per-node term/value slots (n1t3n6v5), nine message arrows (n1n6, n3n5, n4n1, n4n2, n4n3, n4n5, n4n6, n5n3, n5n4) and five step captions (desc1desc5).

A GSAP timeline fetched the SVG, inlined it, hid everything not yet reached, and revealed one step per click. Reusing one file across six figures was deliberate: same artwork, different timeline. With the timeline gone, the browser paints all 70 elements simultaneously — every term, every arrow and all five step captions on top of each other.

Root cause

Commit ac5d0a5"Migrate to Fumadocs (#56)" (2026-06-02, 227 files changed, 11,411 insertions, 42,459 deletions). The animation layer was 17 of the 48 files that commit deleted:

  • src/components/AnimatedSVG.tsx — React wrapper (fetch + inline + Next/Reset controls)
  • src/lib/svg-animator.ts — the SVGAnimator GSAP engine
  • src/lib/svg-animator.README.md
  • 14 per-figure timeline scripts: part06Fig1, part06Fig2Scenario1-3, part06Fig3, part07Fig3Raft, part07Fig3Scenario2-5, part08Fig3a/b/c, requestProcessing
  • gsap@^3.13.0 was also dropped from package.json

The lossy conversion happened in scripts/migrate-from-docusaurus.mjs:93-100:

out = out.replace(
  /<AnimatedSVG[\s\S]*?src=\{useBaseUrl\(['"]([^'"]+)['"]\)\}[\s\S]*?alt="([^"]*)"[\s\S]*?\/>/g,
  (_, src, alt) => `\n![${alt}](${src})\n`,
);

The regex captures only src and alt. onAnimate={part07Fig3Raft} — the entire animation — is absorbed by the [\s\S]*? and discarded. Line 88 (out.replace(/^import\s+.+$/gm, '')) strips the accompanying imports. So

<AnimatedSVG src={useBaseUrl('/img/consensus/part07-fig3.svg')} onAnimate={part07Fig3Scenario5}  />

became

![Figure 8: Scenario 5](/img/consensus/part07-fig3.svg)

Verification

  • The animations were correctly wired immediately before the migration: at ac5d0a5^, all 16 onAnimate={…} references in content (14 distinct animations — part07Fig3Raft and part06Fig3 are each used twice) resolve to an exported animation script, with zero dangling references.
  • Only one commit ever broke this. Full history of the animation infrastructure: c8d144e (added, 2025-10-21), 9a8e8ba, 183475e (part 4 added), 4422d47 (ToC overlap fix, 2025-11-03), then ac5d0a5 (deleted). Nothing after ac5d0a5 touches it.
  • animated-svg-container appears 0 times in the current production HTML.
  • Internet Archive brackets the regression: the 2026-05-18 capture has 6 animation containers and 0 static figure images; the 2026-07-08 capture has 0 containers and 6 static images.

Not lost

Every SVG asset survived the migration intact — they were moved from static/img/consensus/ to public/img/consensus/. That's why the pages render something instead of broken images, and it means no artwork needs to be recreated. Everything deleted is recoverable verbatim from ac5d0a5^.

Note that the 16 animations are driven by only 6 distinct SVG filespart06-fig1, part06-fig2, part06-fig3, part07-fig3, part08-fig3 and requestProcessing. Reuse was deliberate: one artwork, several timelines over it. That is exactly why six figures on part 7 now render as the same image.

Also worth knowing: the animated SVGs were only ever requested via a runtime fetch(), so the Wayback crawler never discovered their URLs. multigres.com/img/consensus/part07-fig3.svg has zero captures (part07-fig1/2/6, which were ordinary markdown images, were captured). There is therefore no archived copy of the working animations to compare against — the only faithful reference is a local build of ac5d0a5^.

Minor issues in the same area

Not part of the regression, but visible on part 7:

  • The final figure is labelled Figure 6: Timeline Priority (content/blog/generalized-consensus-part7.md:273) though it is the 9th figure.
  • Parts 6, 7 and 10 still contain orphaned <div style={{textAlign: 'center', …}}> wrappers left over from the removed <AnimatedSVG> blocks (e.g. content/blog/generalized-consensus-part7.md:248-252). In a .md file these are inert raw HTML — the style={{…}} attribute is not valid HTML and does nothing.

Suggested direction

A full revert of ac5d0a5 is not viable — it would restore the entire Docusaurus site. A targeted restore works instead: bring back the 17 deleted animation files and the gsap dependency, then reconnect them. The original wiring cannot be reverted as-is because it depended on @site/* (no longer in tsconfig.json) and @docusaurus/useBaseUrl (Docusaurus is fully removed), and because the migration rewrote the content files into plain markdown images.

The reconnection can be done entirely in the renderer with no content changes: the migration discarded onAnimate but preserved alt, and all 16 (alt, svg) pairs are unique, so the caption is sufficient to recover which timeline belongs to which figure. Overriding img in src/components/mdx.tsx to swap those 16 figures for AnimatedSVG fixes all 5 posts and both the blog and docs copies at once.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions