Skip to content

Commit 1b12a71

Browse files
committed
fix: cache appearance values from host sets
1 parent 14caab6 commit 1b12a71

4 files changed

Lines changed: 99 additions & 7 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 15:13 EDT:
115+
116+
- Simulator-only rule remains active. Do not use physical iPhone/iPad devices.
117+
- GitHub Actions run `28394366898` on `14caab6a` again kept setup,
118+
dependency install, FFI boundary check, V8 download, libffi build, metadata
119+
generation, NativeScript build, CLI build, and macOS tests green.
120+
- iOS simulator still failed in `ApiTests.js Appearance` with the same
121+
`Expected undefined to be { }` `UILabel.appearance().textColor` readback.
122+
- Root cause refinement:
123+
- The previous own-accessor setter fallback did not cover the assignment
124+
route used by V8 host objects.
125+
- Assignment can be handled by `NativeApiObjectHostObject::set` before the
126+
own accessor setter runs, and its tagged-UIAppearance branch still rejected
127+
metadata properties without `setterSelectorName`.
128+
- Current runtime fix:
129+
- The tagged-UIAppearance host-object assignment path now uses the same
130+
runtime Objective-C property setter fallback as the own accessor path.
131+
- It caches the assigned value after either metadata-setter or runtime-setter
132+
invocation.
133+
- Source coverage now guards both UIAppearance assignment routes.
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 this host-set 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 14:34 EDT:
115147

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

NativeScript/ffi/shared/bridge/HostObjects.mm

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1815,17 +1815,25 @@ throw JSError(
18151815
const auto& members = bridge_->membersForClass(*symbol);
18161816
if (const NativeApiMember* propertyMember =
18171817
selectWritablePropertyMember(members, property, false)) {
1818-
if (propertyMember->readonly ||
1819-
propertyMember->setterSelectorName.empty()) {
1818+
if (propertyMember->readonly) {
18201819
throw JSError(
18211820
runtime, "Attempted to assign to readonly property.");
18221821
}
1823-
NativeApiMember setterMember = *propertyMember;
1824-
setterMember.selectorName = propertyMember->setterSelectorName;
1825-
setterMember.signatureOffset = propertyMember->setterSignatureOffset;
18261822
Value args[] = {Value(runtime, value)};
1827-
callObjCSelector(runtime, bridge_, object_, false,
1828-
setterMember.selectorName, &setterMember, args, 1);
1823+
if (!propertyMember->setterSelectorName.empty()) {
1824+
NativeApiMember setterMember = *propertyMember;
1825+
setterMember.selectorName = propertyMember->setterSelectorName;
1826+
setterMember.signatureOffset = propertyMember->setterSignatureOffset;
1827+
callObjCSelector(runtime, bridge_, object_, false,
1828+
setterMember.selectorName, &setterMember, args, 1);
1829+
} else if (auto setterSelectorName =
1830+
runtimeWritablePropertySetter(object_, property)) {
1831+
callObjCSelector(runtime, bridge_, object_, false,
1832+
*setterSelectorName, nullptr, args, 1);
1833+
} else {
1834+
throw JSError(
1835+
runtime, "UIAppearance property setter is unavailable.");
1836+
}
18291837
cacheAppearanceProxyPropertyValue(runtime, bridge_, object_,
18301838
property, value);
18311839
NATIVE_API_SET_RETURN(true);

PROGRESS.md

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

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

20+
### 2026-06-29 15:13 EDT - UIAppearance host-set fallback
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 `28394366898` on `14caab6a` again kept setup,
27+
dependency install, FFI boundary check, V8 download, libffi build,
28+
metadata generation, NativeScript build, CLI build, and macOS tests green.
29+
- iOS simulator still failed in `ApiTests.js Appearance` with the same
30+
`Expected undefined to be { }` `UILabel.appearance().textColor` readback.
31+
- That means the previous own-accessor setter fallback did not cover the
32+
assignment route used by V8 host objects; assignment can be handled by
33+
`NativeApiObjectHostObject::set` before the own accessor setter runs.
34+
- Changes:
35+
- The tagged-UIAppearance branch in `NativeApiObjectHostObject::set` now uses
36+
the same runtime Objective-C property setter fallback when target-class
37+
metadata lacks `setterSelectorName`.
38+
- The branch now caches the assigned value after either metadata-setter or
39+
runtime-setter invocation, so subsequent `UILabel.appearance().textColor`
40+
reads should resolve through the target-class-scoped appearance cache.
41+
- Source coverage now guards both the own-accessor setter and the host-object
42+
assignment path.
43+
- Verification:
44+
- `node packages/react-native/test/runtime-objc-property-setter.test.js`
45+
passed.
46+
- Runtime RN JS tests passed:
47+
`for f in packages/react-native/test/*.test.js; do node "$f" || exit 1; done`.
48+
- `npm run check:ffi-boundaries` passed.
49+
- `git diff --check` passed.
50+
- `npm run build:macos-cli` passed and compiled/linked the edited V8 bridge.
51+
- Still next:
52+
- Commit/push this host-set fallback correction and watch fresh PR CI for the
53+
authoritative iOS simulator result.
54+
- If CI turns green, resume the dedicated simulator-only RNS parity sweep.
55+
2056
### 2026-06-29 14:34 EDT - UIAppearance setter fallback for cache
2157

2258
- Goal:

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ const appearanceAccessorSource = runtimeHostObjects.slice(
5151
appearanceAccessorStart,
5252
appearanceAccessorEnd,
5353
);
54+
const appearanceHostSetStart = runtimeHostObjects.indexOf(
55+
"if (Class appearanceClass =",
56+
);
57+
const appearanceHostSetEnd = runtimeHostObjects.indexOf(
58+
"\n if (auto setterSelectorName =",
59+
appearanceHostSetStart,
60+
);
61+
const appearanceHostSetSource = runtimeHostObjects.slice(
62+
appearanceHostSetStart,
63+
appearanceHostSetEnd,
64+
);
5465

5566
assert(
5667
runtimeHostObjects.includes("Class tagStaticAppearanceNativeResult(") &&
@@ -73,6 +84,11 @@ assert(
7384
!appearanceAccessorSource.includes("!member.readonly && !member.setterSelectorName.empty()"),
7485
"runtime UIAppearance proxies should install safe property accessors backed by the appearance cache",
7586
);
87+
assert(
88+
appearanceHostSetSource.includes("runtimeWritablePropertySetter(object_, property)") &&
89+
!appearanceHostSetSource.includes("propertyMember->readonly ||\n propertyMember->setterSelectorName.empty()"),
90+
"runtime UIAppearance host-object assignment should use the same runtime setter fallback before caching",
91+
);
7692
assert(
7793
!runtimeHostObjects.includes("SetNativeApiObjectPrototype(runtime, resultObject"),
7894
"runtime UIAppearance proxies should not replace their JS prototype with the target class prototype",

0 commit comments

Comments
 (0)