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):
- App boots in dark, pill Alpha selected (blue fill, white label)
- Tap "Open Settings" (Home freezes under
freezeOnBlur)
- Tap "Light"
- Tap "Back" — Home comes back correctly re-themed
- 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
- 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 〰
What happened?
After a theme flip that lands while a screen is frozen (
freezeOnBlurunder react-native-screens), the first className swap on a node of that screen — acn/tvconditional 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'stext-on-brandlabel paints#2d2d2d(the light--color-fgit 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:
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; +CircleTopRighttransition; + freeze/thaw cycles; +GlassViewcontainment; + native-stackfreezeOnBlur): only adding the compiler makes it fire, deterministically.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_DEPRECATEDstash seeded inHybridShadowRegistry::updateShadowTreeis cleared only inlinkShadowNodeWithUniwindStyle(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-proinstalls from the public npm registry, so the repo runs as-is.Steps to Reproduce
In the linked repo (
bun install && npx expo run:ios):freezeOnBlur)bg-brandfill is correct, but the label paints#2d2d2d(the previous class's ink) instead oftext-on-brand#ffffffSnack 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 〰