Skip to content

fix(dom): use real microtask + force layout for enter connection wait - #73

Merged
jonlaing merged 1 commit into
mainfrom
fix/enter-anim-connection-real-microtask
Aug 3, 2026
Merged

fix(dom): use real microtask + force layout for enter connection wait#73
jonlaing merged 1 commit into
mainfrom
fix/enter-anim-connection-real-microtask

Conversation

@jonlaing

@jonlaing jonlaing commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up to #71. That fix used Effect.yieldNow() to wait for connection — but yieldNow only reschedules inside Effect's own queue. If no other work is queued, the fiber runs again immediately, without the browser flushing microtasks or committing DOM. In practice the connection check still saw isConnected === false on real re-mounts, we hit the 3-attempt bound, and proceeded against a disconnected element. User confirmed post-merge that the exact same failure was still happening.

Fix

Two changes:

1. Real microtask via queueMicrotask. Replace Effect.yieldNow() with Effect.async<void>(resume => queueMicrotask(() => resume(Effect.void))). queueMicrotask is a real browser primitive that guarantees the callback runs after the current task's synchronous work and other pending microtasks — i.e. after the outer render flow finishes appending the wrapper's ancestor chain to the document. Loop bounded at 32 attempts; each microtask is submicrosecond, so a full spin is well under 1ms even for callers whose element never gets inserted (e.g. tests).

2. Force style computation via offsetHeight. After the element is connected, read element.offsetHeight to force the browser to compute layout + styles. Some engines defer style computation for freshly-inserted nodes until it's needed; without this, the forceReflow inside runEnterAnimation could sample an unstyled snapshot as the transition's "before" reference, leaving nothing for the browser to interpolate from.

Diagnostic that led here

User pasted their onBeforeEnter snapshot on nav-back:

{ className: "opacity-0 -rotate-45 -translate-y-[100px]",
  opacity: "", transform: "", transitionProperty: "", transitionDuration: "",
  parent: "DIV" }

className correct (enterFrom applied), but every computed-style field is empty — the tell for a disconnected node. Confirmed #71's Effect.yieldNow wasn't actually flushing microtasks.

Tests

Existing regression test from #71 (onBeforeEnter fires against an attached element (nested remount)) still passes with the new implementation. 788 tests pass across the workspace.

  • tsc --noEmit clean.
  • Full suite green.

Changeset: @effex/dom patch.

🤖 Generated with Claude Code

Follow-up to the previous release's connection wait. That version used
Effect.yieldNow() to defer the enter fiber past the outer synchronous
render flow, but yieldNow only reschedules inside Effect's own queue —
if no other work is queued, the fiber can re-run immediately without
the browser flushing microtasks or committing DOM. In practice this
meant real client re-mounts still saw element.isConnected === false at
the 3-attempt bound, the animation ran against a disconnected element,
and the transition never fired.

Switch to queueMicrotask via Effect.async — a real browser microtask
guarantees the fiber won't resume until the current task's synchronous
work and other queued microtasks have run. That's when the outer flow
finishes inserting the wrapper's ancestor chain, so element becomes
connected. Bounded at 32 attempts; each is submicrosecond in modern
engines, so a full spin is well under 1ms even for callers whose
element never gets inserted.

Also force a style/layout computation via `element.offsetHeight` after
connection. Some engines defer styling for freshly-inserted nodes
until needed, and without this forceReflow in runEnterAnimation could
sample an unstyled snapshot as the transition's "before" reference,
leaving nothing for the browser to interpolate from.

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

Copy link
Copy Markdown

Deploying effex with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8e9426f
Status: ✅  Deploy successful!
Preview URL: https://f6ed929a.effex.pages.dev
Branch Preview URL: https://fix-enter-anim-connection-re.effex.pages.dev

View logs

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying effex-api with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8e9426f
Status: ✅  Deploy successful!
Preview URL: https://81f07480.effex-api.pages.dev
Branch Preview URL: https://fix-enter-anim-connection-re.effex-api.pages.dev

View logs

@jonlaing
jonlaing merged commit dfad996 into main Aug 3, 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.

1 participant