Skip to content

Commit 14caab6

Browse files
committed
fix: cache appearance values from runtime setters
1 parent 58e5522 commit 14caab6

4 files changed

Lines changed: 100 additions & 8 deletions

File tree

HANDOFF.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,38 @@ During this thread `agent-device` was version `0.18.0`; the npm package is
111111

112112
## Current Verified State
113113

114+
Update from 2026-06-29 14:34 EDT:
115+
116+
- Simulator-only rule remains active. Do not use physical iPhone/iPad devices.
117+
- GitHub Actions run `28391820808` on `58e5522d` recovered macOS:
118+
setup, dependency install, FFI boundary check, V8 download, libffi build,
119+
metadata generation, NativeScript build, CLI build, and macOS tests all
120+
passed.
121+
- iOS simulator still failed in `ApiTests.js Appearance`, but now logged a
122+
concrete Jasmine failure before timing out waiting for JUnit:
123+
`Expected undefined to be { }`.
124+
- The failure maps to `UILabel.appearance().textColor`: assigning through the
125+
safe own accessor did not populate the target-class-scoped appearance cache,
126+
so the next `UILabel.appearance().textColor` read returned `undefined`.
127+
- Current runtime fix:
128+
- UIAppearance own property accessors now install a setter for every
129+
non-readonly target-class property.
130+
- If generated metadata lacks an explicit setter selector, the setter uses
131+
the existing runtime Objective-C property setter discovery path
132+
(`runtimeWritablePropertySetter`) before caching the assigned value.
133+
- Source coverage now scopes this guard to the UIAppearance accessor block.
134+
- Local verification after this correction:
135+
- `node packages/react-native/test/runtime-objc-property-setter.test.js`
136+
passed.
137+
- Runtime RN JS tests passed:
138+
`for f in packages/react-native/test/*.test.js; do node "$f" || exit 1; done`.
139+
- `npm run check:ffi-boundaries` passed.
140+
- `git diff --check` passed.
141+
- `npm run build:macos-cli` passed.
142+
- Not done:
143+
- Commit/push the setter-fallback correction and watch fresh PR CI.
144+
- If CI turns green, resume the dedicated simulator-only RNS parity sweep.
145+
114146
Update from 2026-06-29 13:47 EDT:
115147

116148
- Simulator-only rule remains active. Do not use physical iPhone/iPad devices.

NativeScript/ffi/shared/bridge/HostObjects.mm

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1967,13 +1967,22 @@ Function makeAppearanceProxyPropertySetter(
19671967
throw JSError(runtime,
19681968
"UIAppearance property setter expects a value.");
19691969
}
1970-
NativeApiMember setterMember = member;
1971-
setterMember.selectorName = member.setterSelectorName;
1972-
setterMember.signatureOffset = member.setterSignatureOffset;
19731970
Value setterArgs[] = {Value(runtime, args[0])};
1974-
callObjCSelector(runtime, bridge, native, false,
1975-
setterMember.selectorName, &setterMember, setterArgs,
1976-
1);
1971+
if (!member.setterSelectorName.empty()) {
1972+
NativeApiMember setterMember = member;
1973+
setterMember.selectorName = member.setterSelectorName;
1974+
setterMember.signatureOffset = member.setterSignatureOffset;
1975+
callObjCSelector(runtime, bridge, native, false,
1976+
setterMember.selectorName, &setterMember,
1977+
setterArgs, 1);
1978+
} else if (auto setterSelectorName =
1979+
runtimeWritablePropertySetter(native, member.name)) {
1980+
callObjCSelector(runtime, bridge, native, false, *setterSelectorName,
1981+
nullptr, setterArgs, 1);
1982+
} else {
1983+
throw JSError(runtime,
1984+
"UIAppearance property setter is unavailable.");
1985+
}
19771986
cacheAppearanceProxyPropertyValue(runtime, bridge, native, member.name,
19781987
args[0]);
19791988
return Value::undefined();
@@ -2014,7 +2023,7 @@ void installAppearanceProxyPropertyAccessors(
20142023
runtime, "get",
20152024
makeAppearanceProxyPropertyGetter(runtime, bridge, native,
20162025
retainedNative, member.name));
2017-
if (!member.readonly && !member.setterSelectorName.empty()) {
2026+
if (!member.readonly) {
20182027
descriptor.setProperty(
20192028
runtime, "set",
20202029
makeAppearanceProxyPropertySetter(runtime, bridge, native,

PROGRESS.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,43 @@ TypeScript/UI worklets.
1717

1818
## Latest Update - 2026-06-29
1919

20+
### 2026-06-29 14:34 EDT - UIAppearance setter fallback for cache
21+
22+
- Goal:
23+
- Stay on PR #46's generic runtime primitive path for the RN module branch
24+
and remain simulator-only. No physical devices were used.
25+
- CI finding:
26+
- GitHub Actions run `28391820808` on `58e5522d` recovered macOS:
27+
setup, dependency install, FFI boundary check, V8 download, libffi build,
28+
metadata generation, NativeScript build, CLI build, and macOS tests all
29+
passed.
30+
- iOS simulator still failed in `ApiTests.js Appearance`, but now logged a
31+
concrete Jasmine failure before timing out waiting for JUnit:
32+
`Expected undefined to be { }`.
33+
- That maps to `UILabel.appearance().textColor`: assignment returned without
34+
populating the target-class-scoped appearance cache, so the next
35+
`UILabel.appearance().textColor` read was still `undefined`.
36+
- Changes:
37+
- UIAppearance own property accessors now install a setter for every
38+
non-readonly target-class property.
39+
- When generated metadata does not carry an explicit setter selector, the
40+
setter uses the existing runtime Objective-C property setter discovery
41+
path (`runtimeWritablePropertySetter`) before caching the assigned value.
42+
- Source coverage now guards this fallback and keeps the accessor cache path
43+
specific to the UIAppearance block.
44+
- Verification:
45+
- `node packages/react-native/test/runtime-objc-property-setter.test.js`
46+
passed.
47+
- Runtime RN JS tests passed:
48+
`for f in packages/react-native/test/*.test.js; do node "$f" || exit 1; done`.
49+
- `npm run check:ffi-boundaries` passed.
50+
- `git diff --check` passed.
51+
- `npm run build:macos-cli` passed and compiled/linked the edited V8 bridge.
52+
- Still next:
53+
- Commit/push the setter-fallback correction and watch fresh PR CI for the
54+
authoritative iOS simulator result.
55+
- If CI turns green, resume the dedicated simulator-only RNS parity sweep.
56+
2057
### 2026-06-29 13:47 EDT - limit UIAppearance tagging to UIKit proxies
2158

2259
- Goal:

packages/react-native/test/runtime-objc-property-setter.test.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@ const runtimeHostObjects = fs.readFileSync(
4040
path.join(repoRoot, "NativeScript/ffi/shared/bridge/HostObjects.mm"),
4141
"utf8",
4242
);
43+
const appearanceAccessorStart = runtimeHostObjects.indexOf(
44+
"Function makeAppearanceProxyPropertySetter(",
45+
);
46+
const appearanceAccessorEnd = runtimeHostObjects.indexOf(
47+
"\nValue tagStaticAppearanceSelectorResult(",
48+
appearanceAccessorStart,
49+
);
50+
const appearanceAccessorSource = runtimeHostObjects.slice(
51+
appearanceAccessorStart,
52+
appearanceAccessorEnd,
53+
);
4354

4455
assert(
4556
runtimeHostObjects.includes("Class tagStaticAppearanceNativeResult(") &&
@@ -56,7 +67,10 @@ assert(
5667
runtimeHostObjects.includes("installAppearanceProxyPropertyAccessors") &&
5768
runtimeHostObjects.includes("makeAppearanceProxyPropertySetter") &&
5869
runtimeHostObjects.includes("makeAppearanceProxyPropertyGetter") &&
59-
runtimeHostObjects.includes("cacheAppearanceProxyPropertyValue("),
70+
runtimeHostObjects.includes("cacheAppearanceProxyPropertyValue(") &&
71+
appearanceAccessorSource.includes("runtimeWritablePropertySetter(native, member.name)") &&
72+
appearanceAccessorSource.includes("if (!member.readonly)") &&
73+
!appearanceAccessorSource.includes("!member.readonly && !member.setterSelectorName.empty()"),
6074
"runtime UIAppearance proxies should install safe property accessors backed by the appearance cache",
6175
);
6276
assert(

0 commit comments

Comments
 (0)