Skip to content

className swap after a frozen-time theme flip applies stale ink (React Compiler required ingredient, Pro 1.6.0) #655

Description

@invivek26

What happened?

After a theme flip that lands while a screen is frozen (freezeOnBlur under react-native-screens), the first className swap on a node of that screen — a cn/tv conditional flipping between two class strings — applies stale ink: the label keeps the previous class's color instead of the new class's token. In the minimal repro the selected pill's text-on-brand label paints #2d2d2d (the light --color-fg it carried while unselected); in our production app the same defect paints RN's default black. The target token (--color-on-brand: #ffffff) is identical in every theme block, so this is not a wrong-theme value — the swap's resolution never lands. The broken node heals after another swap cycle. Flips performed while the screen is visible do not reproduce.

Two findings that should narrow it fast:

  • React Compiler (experiments.reactCompiler) is a required ingredient — the identical app without it renders correctly. The repro repo's git history contains five progressively-closer variants that do NOT reproduce (plain react-freeze; + CircleTopRight transition; + freeze/thaw cycles; + GlassView containment; + native-stack freezeOnBlur): only adding the compiler makes it fire, deterministically.
  • Pixel-verified: 48 glyph pixels at rgb(45,45,45) inside the brand-blue selected pill.

Source-level suspects from reading 1.6.0, offered with appropriate humility: the frozen-family nativeProps_DEPRECATED stash seeded in HybridShadowRegistry::updateShadowTree is cleared only in linkShadowNodeWithUniwindStyle (ref-callback timing — the clearing comment already mentions "timing conflicts"), and with the compiler memoizing elements, the re-link/re-resolution that would normally accompany the swap may be skipped, leaving the stale payload standing.

Follow-up to #649 in the native className lane (#650 does not cover it). Re-filed from #653 with the runnable repro the automation asked for — uniwind-pro installs from the public npm registry, so the repo runs as-is.

Steps to Reproduce

In the linked repo (bun install && npx expo run:ios):

  1. App boots in dark, pill Alpha selected (blue fill, white label)
  2. Tap "Open Settings" (Home freezes under freezeOnBlur)
  3. Tap "Light"
  4. Tap "Back" — Home comes back correctly re-themed
  5. Tap "Beta": the blue bg-brand fill is correct, but the label paints #2d2d2d (the previous class's ink) instead of text-on-brand #ffffff
  6. Tap Alpha, then Beta again — heals

Snack or Repository Link (Optional)

https://github.com/invivek26/uniwind-frozen-swap-repro

Uniwind version

uniwind-pro 1.6.0

React Native Version

0.85.3

Platforms

iOS

Expo

Yes

Additional information 〰

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