Skip to content

fix: read AnimationConfigCtx lazily in HydrationControlCtx#23

Merged
jonlaing merged 2 commits into
mainfrom
fix/hydration-animation-config-lazy-read
Jun 25, 2026
Merged

fix: read AnimationConfigCtx lazily in HydrationControlCtx#23
jonlaing merged 2 commits into
mainfrom
fix/hydration-animation-config-lazy-read

Conversation

@jonlaing

Copy link
Copy Markdown
Owner

Summary

Fixes list animations being silently dropped on hydrated pages (SSG + SSR).

each's animate config is supplied locally via Effect.provideService(AnimationConfigCtx, ...) wrapping the each body. ClientControlCtx already reads this config lazily inside addSlot/removeSlot via Effect.serviceOption(AnimationConfigCtx) — so when an each provides config one frame up, the client context sees it. ✅

HydrationControlCtx was reading it once at Layer construction time, at the hydration root — before any each had a chance to run. The captured value was always undefined, every if (animate && ...) guard short-circuited, and animations were silently skipped. ❌

This change mirrors ClientControlCtx's pattern: every addSlot/removeSlot reads AnimationConfigCtx via Effect.serviceOption at the moment the effect runs. Drops the captured animationConfig parameter from createClientLikeControlCtx and createHydrationControlCtx entirely; the Layer no longer pre-reads the service.

Diagnosis credit

The diagnosis came from an investigation by another agent looking at why list animations silently failed on a hydrated SSG portfolio site. The specific evidence trail:

  • Control/index.ts:172each provides AnimationConfigCtx locally
  • ClientControlCtx.ts:111-112 — client context reads it lazily inside addSlot (works)
  • HydrationControlCtx.ts:418-435 (before this fix) — hydration context read it at Layer construction (broken)
  • The closed-over undefined value was threaded through fork → createClientLikeControlCtx, so even forked client-like contexts inherited the stale undefined

Test plan

  • pnpm exec tsc --noEmit in packages/dom — clean
  • Existing Control / hydrate / Animation tests still pass (50/50)
  • Manually verify list animations fire on a hydrated SSG page (the original repro)
  • Optional follow-up: add a regression test in hydrate.test.ts that exercises each with animate during hydration. Skipped here to keep the fix focused — the change is purely "do the same thing ClientControlCtx already does."

Closes #22

🤖 Generated with Claude Code

The hydration control contexts were reading AnimationConfigCtx once at
Layer construction (at the hydration root) and closing over the result.
Since each's animate config is provided locally via Effect.provideService
around the each, the captured value was always undefined — the layer
runs before any each has a chance to provide its config. Net effect:
list animations were silently dropped on every hydrated page (SSG/SSR).

Mirrors the ClientControlCtx pattern: read AnimationConfigCtx via
Effect.serviceOption inside addSlot/removeSlot, so each call sees the
config its parent provided. Drops the captured animationConfig parameter
from createClientLikeControlCtx and createHydrationControlCtx entirely;
the Layer no longer pre-reads the service.

Confirmed all existing Control/hydrate/Animation tests still pass (50
tests, 3 files).

Closes #22

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 22, 2026

Copy link
Copy Markdown

Deploying effex with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2e2670e
Status: ✅  Deploy successful!
Preview URL: https://f71ab932.effex.pages.dev
Branch Preview URL: https://fix-hydration-animation-conf.effex.pages.dev

View logs

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 22, 2026

Copy link
Copy Markdown

Deploying effex-api with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2e2670e
Status: ✅  Deploy successful!
Preview URL: https://8ee54fe8.effex-api.pages.dev
Branch Preview URL: https://fix-hydration-animation-conf.effex-api.pages.dev

View logs

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@jonlaing jonlaing merged commit 4ec2da9 into main Jun 25, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

List animations silently dropped during hydration (SSG)

1 participant