fix(native): seed a concrete scheme-aware Android __rn-css-color#392
Draft
YevheniiKotyrlo wants to merge 1 commit into
Draft
fix(native): seed a concrete scheme-aware Android __rn-css-color#392YevheniiKotyrlo wants to merge 1 commit into
YevheniiKotyrlo wants to merge 1 commit into
Conversation
On Android the root `__rn-css-color` fallback was
PlatformColor('?attr/textColorPrimary'), which resolves to a ColorStateList;
RN's ColorPropConverter returns the resource *reference* rather than an ARGB
int, so it silently never paints. Every color resolving through the root
fallback -- default ring-* / inset-ring-* (Tailwind's default ring color is
currentcolor), text-current, bg-current -- was invisible on Android. iOS's
PlatformColor('label') is fine.
Seed a concrete color on Android instead, made scheme-aware through the root
observable's existing prefers-color-scheme evaluation (no extra Appearance
listener). The root variable is only the ultimate fallback -- any
ancestor-published or themed --__rn-css-color overrides it -- so a binary
black/white default is spec-faithful.
Splitting the platforms also drops the file's `as any` and two eslint
suppressions: the Android media-query seed types cleanly, and the iOS
PlatformColor uses an isolated `as unknown as StyleDescriptor` bridge.
Adds android + ios tests (currentcolor, live scheme reactivity, a default ring,
ancestor override) that resolve the ColorStateList reference before the fix and
concrete colors after.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On Android, every color that resolves through the root
__rn-css-colorfallback renders nothing — defaultring-*/inset-ring-*(Tailwind v4's default ring color iscurrentcolor),text-current, andbg-currentare all invisible. iOS is fine.The root seed in
src/native-internal/root.ts:On Android
PlatformColor('?attr/textColorPrimary')resolves through React Native'sColorPropConverter.resolveThemeAttribute, which returnsoutValue.datawithout inspectingoutValue.type:?attr/textColorPrimaryis a ColorStateList on AppCompat / Material themes, soTheme.resolveAttributefillsoutValuewith a resource reference (TYPE_REFERENCE), not a packed color — andoutValue.datais then that reference id, used as if it were an ARGB int. The paint is a garbage, effectively-invisible color. The fallback'SystemBaseHighColor'has no@/?prefix, so it isn't a valid resource path and is silently skipped. Net effect on device: no exception, no paint — verified on an Android emulator, where re-seeding__rn-css-colorwith#FF00FFmade the rings andtext-currentrender magenta, proving the resolution machinery is correct and only the seed value was wrong.Fix
Seed a concrete color on Android, made scheme-aware through the root observable's existing
prefers-color-schemeevaluation — no newAppearancelistener, and it drops theas any+ two eslint suppressions:The root variable is only the ultimate fallback (any ancestor-published or themed
--__rn-css-coloroverrides it), so a binary black/white default is spec-faithful — CSS's initialcoloris effectivelycanvastext. iOS keepsPlatformColor('label'), a first-class dynamic color that already tracks the system appearance.Why the media-query form: the root observable already evaluates each seeded value's media query against the reactive
colorSchemeobservable (native/conditions/media-query.ts), so[["#FFFFFF", darkMQ], ["#000000"]]is scheme-reactive for free — the same mechanismlight-dark()andprefers-color-schemerules use — rather than a second imperativeAppearancesubscription.Test plan
src/__tests__/native/root-color-seed-android.test.tsx(Platform.OS mocked to android — the seed is a module-load side effect and jest-expo defaults to ios):currentcolorwith no ancestor →#000000(light) /#FFFFFF(dark).act(() => colorScheme.set("dark"))re-resolves the same element.box-shadowcurrentcolor) paint with the seed color — the reported symptom.null) falls back to the light default.src/__tests__/native/root-color-seed-ios.test.tsx:PlatformColor('label'); ancestor override still works.src/__tests__/vendor/tailwind/ring-color-seed.test.tsx(real Tailwind → the runtime, android):ring-2/ring— whose default color iscurrentcolor— paint the concrete seed (#000000light,#FFFFFFdark); an explicitring-red-500still wins. This exercises the reported symptom through actual Tailwind output, not a hand-writtenbox-shadow.Verified red→green: pre-fix, the Android tests resolve to
{ semantic: ["?attr/textColorPrimary", "SystemBaseHighColor"] }(the ColorStateList reference); post-fix, concrete colors.yarn test,yarn typecheck,yarn eslint, andprettier --checkall pass. (Twobabel/import-plugin suites fail only on Windows — a pre-existing path-separator bug in the plugin, unrelated to this change; they are green on CI.)Follow-up (separate, facebook/react-native)
The deeper RN-layer bug —
ColorPropConverter.resolveThemeAttributereturningoutValue.dataunchecked for ColorStateList attributes (a reference, not a color) — affects anyPlatformColor('?attr/<ColorStateList>')usage and is worth a separate issue in react-native itself.References
ColorPropConverter.resolveThemeAttributereturnsoutValue.datawithout checkingoutValue.type(Kotlin source, commit-pinned) ·PlatformColor?attr/textColorPrimaryis a themed ColorStateList in AppCompat / Material components, which is why the returnedoutValue.datais a reference, not a color.colorSchemeobservable (native/conditions/media-query.ts—prefers-color-scheme→get(colorScheme)), so a media-query-conditioned seed is scheme-reactive with no extra listener.