Skip to content

Run 54 React Native and Expo libraries on macOS, and add real pointer input to the e2e driver - #531

Merged
jmeistrich merged 86 commits into
mainfrom
feat/desktop-libs-macos-land-v2
Nov 1, 2026
Merged

jmeistrich merged 86 commits into
mainfrom
feat/desktop-libs-macos-land-v2

Conversation

@jmeistrich

Copy link
Copy Markdown
Member

Replaces #530 (rebased onto main after #529 merged).

This branch (e065b890) has the same tree as #530 (99c74c64) except main's three test-only commits from #529 (package-distribution, runner-package-manager and windows-nitro-native tests). The device evidence below comes from #530's SHAs. On e065b890, typecheck, 13 scoped Vitest files (99/99), e2e validate and e2e lint all pass.

This makes 54 libraries that the Kitchen Sink adds for macOS build and work there. 35 have version-pinned Spark patches, plus Quick Base64 as a support library; the rest work without a patch, and that is now verified. Each library gets a row in docs/external-libraries.md, a Kitchen Sink screen and a macOS e2e flow. The PR also adds opt-in real mouse input to the e2e driver. Windows support is in a separate branch.

Behavior changes

  • Pinned libraries: before: no Spark patch · now: patched at native preparation; any other version logs a warning and builds unpatched · when: the app declares one of the 36 pinned libraries · why: optional pins must not break apps on other versions.
  • macOS codegen: before: classes registered for iOS-only codegen libraries, which were never built · now: excluded on macOS only · when: every macOS build.
  • MMKV storage location: before: ~/Documents/mmkv · now: ~/Library/Application Support/<namespace>/mmkv, with no migration · when: macOS, react-native-mmkv 4.3.2.
  • Library behavior on macOS: expo-haptics plays haptics instead of rejecting. Keyboard Controller no longer throws at import, and Screen Corner Radius reports 0. react-native-graph builds paths with Skia 2. Release Profiler traces go to the app's namespaced Caches folder. Full list: docs/external-libraries.md.
  • e2e driver: before: windows stayed hidden and the app never activated · now, with --pointer only: the target window is shown and the app activated, then restored at flow end or disconnect. Requires an Accessibility grant. The release gate rejects --pointer, and default runs are unchanged.

Changes

  • Native preparation: patches/dependencies.json adds optional pins. A pin on another installed version stays unpatched and also passes the "every active pin was prepared" check. Patching handles tab-indented manifests and exports maps that hide package.json.
  • macOS build: codegen libraries with iOS-only podspecs are kept out of macOS autolinking. The macOS Podfile passes --source-dir __dir__ to use_native_modules!, and patched pods declare macOS 14.
  • Kitchen Sink: screens load lazily inside an error boundary, so one failing library breaks only its own screen. Unistyles and Worklets Core Babel plugins are scoped to their demos. The driver's bundle server serves exported assets.
  • Pointer input: bun run e2e:run <flow> --backend driver --pointer sends real Quartz mouse events for tapOn. Each tap waits for mounted UI to settle within the step timeout. See docs/test-driver.md#real-pointer-input-macos.
  • Flows: the new macOS library checks are blocking in the release gate (49 flows). gesture-handler and pressable-pointer need real clicks (--pointer), so they live in e2e/pointer/ outside the gate and run with bun run gate:pointer in the pre-release checklist.
  • Windows: the 43 new native libraries are on the Kitchen Sink windows.autolinking.exclude list until the Windows work lands separately.

Verification

Run on a2531141:

  • bun run typecheck: passes.
  • Vitest, the 24 affected files: 429/431 pass. One failure is pre-existing on the base (e2e-coverage, release-gate dialog flows). The other, kitchen-sink's Windows rich-text test, comes from a stale local node_modules patch; a fresh install has not been tried.
  • bun run e2e:validate and bun run e2e:lint: 0 problems (79 flows).
  • bun.lock agrees with every workspace manifest (static check).

macOS, on a2531141 plus the gate commits (99c74c64):

  • Kitchen Sink macOS Debug build succeeds. e2e:run e2e/flows --backend driver: 72 pass, 23 fail, 4 skip; every failure uses a step the driver backend does not support yet, or was a stale dock golden (now updated, 4/4 pass).
  • bun run gate:pointer (gesture-handler, pressable-pointer with --pointer): 2/2 pass.
  • kitchen-sink, e2e-coverage and e2e-gate tests after a fresh install: 44/44 pass.

Not run: Windows, Intel macOS, the full test suite locally (CI runs it).

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-31T13:34:51.023806Z
We couldn’t complete this review. Comment @codex review to try again.
e065b89 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

MMKV builds on macOS with Spark's Nitro patches, but its default store was
~/Documents/mmkv, the user's TCC-protected Documents folder shared by every
app. A patch moves it to Application Support under SparkNamespace(), like
Spark's other app data.

Optional pins in patches/dependencies.json apply a patch when the app itself
installs the pinned version; other versions stay unpatched.

Adds a Kitchen Sink MMKV screen and a driver flow covering typed values,
listeners, persistence across launches and clearState.
Unistyles 3.6.0 ships only a UIKit native runtime. Its optional patch adds
a macOS podspec platform and an AppKit runtime: the main window's size and
backing scale, appearance changes for adaptive themes, and RTL. Insets,
bars and the keyboard report zero on desktop.

Adds a Kitchen Sink Unistyles screen, with the Babel plugin scoped to it,
and a driver flow that flips the app appearance and sees the theme follow.
Quick Crypto already targets macOS and its OpenSSL has a macOS slice, but
fastpbkdf2 includes <endian.h>, which macOS lacks; the patch uses
<machine/endian.h> as Mac Catalyst does. Its peer Quick Base64 declares
only iOS, so macOS skipped linking it and the first import failed; its
patch adds the macOS platform.

Adds a Kitchen Sink security screen and a driver flow checking hashes,
HMAC, PBKDF2, AES-GCM, random values and WebCrypto digest vectors.
Nitro Fetch's URLSession client compiles for AppKit, but its podspec
declares only iOS and tvOS. The patch adds macOS, runs launch prefetches
on NSApplicationDidFinishLaunching, and gives its URLCache an absolute
directory under the app's namespaced Caches folder: the relative diskPath
put Cache.db in the process's working directory on macOS.

Adds a Kitchen Sink networking screen that asserts the native client is
present (the JS otherwise falls back to global fetch) and a driver flow.
Blur is UIKit only, and without ios.componentProvider in its codegen
config RN 0.83 never registers its Fabric components. The patch declares
both components and adds AppKit BlurView and VibrancyView on
NSVisualEffectView materials, with light/dark styles mapped to appearances
and the reduced-transparency fallback. blurAmount has no AppKit
equivalent.

Adds a Kitchen Sink blur screen and a driver flow checking registration,
layout and vibrancy children.
Worklets Core's native code is platform-neutral, but its podspec declares
only iOS; the patch adds macOS. The Kitchen Sink adds the worklet Babel
plugin and the three Babel plugins it requires, which Expo SDK 55 apps no
longer carry.

Adds a Kitchen Sink screen and a driver flow running a worklet context,
a shared value and a call back to the main runtime.
CocoaPods skips a pod whose podspec lists platforms without macOS, but
codegen still registered the library's Fabric components and TurboModules.
The registry then mapped them to classes that were never built, and the app
crashed at launch inserting nil into the component dictionary
(react-native-fast-image). Spark's macOS native selection now excludes
those codegen libraries from the macOS Podfile only; iOS and Android builds
reading the shared react-native.config.js and Expo autolinking settings
keep them.

The podspec check reads the platforms assignment itself, singular
s.platform or plural s.platforms, in either :osx => or osx: syntax, so
react-native-gesture-handler (platforms { ios: ..., osx: ... }) stays in.
FastImage's native view is UIKit only. Its optional patch adds a macOS
entry that renders FastImage and FastImageBackground with React Native's
Image: sources, objectFit, tint, placeholders while loading, load events,
reload and preload. Cache APIs resolve not ok. onLoad reports the size from
Image.getSize, since React Native macOS scales a 1x image's load size by
the screen's.

Adds a Kitchen Sink screen and an offline driver flow.
The profiler's Hermes module has no UIKit code, but its podspec lists only
iOS. The patch adds macOS and writes traces to the app's namespaced Caches
folder: on a Mac, the root of ~/Library/Caches is shared by every app.

Adds a Kitchen Sink devtools screen and a driver flow that profiles busy
JavaScript and reads the trace back.
prepareNativePatches returned an optional pin it declined to patch (another
version installed), so non-desktop autolinking still forced that copy.
Record a package only once its version matches the pin. Also call the MMKV
check's file a file.
ATTrackingManager exists on macOS 11, but react-native-tracking-transparency
lists only iOS, checks UIApplication's state before prompting, and its
JavaScript returns unavailable off iOS. The patch adds macOS with AppKit's
activation check and lets macOS reach the native module.

Adds a Kitchen Sink security screen and a driver flow reading the status.
Skia 2.14 supports macOS upstream and needs no Spark patch. Adds a Kitchen
Sink screen and a driver flow that read back an on-screen Canvas and an
offscreen raster surface by pixel.

The Worklets Core Babel plugin, applied app-wide, rewrote Skia's worklets
and read its optional Reanimated proxy at load, so the app never rendered.
The Kitchen Sink now applies that plugin only to its Worklets Core demo.
Writing the patch receipt reformats a dependency's package.json as
two-space JSON, so a package.json hunk that matched a tab-indented upstream
file failed on every later preparation. Normalize package.json before
applying hunks, so patches target one form.
Blurhash is UIKit only and its codegen config names no component provider,
so RN 0.83 never registered its view. The patch declares the provider and
adds AppKit support: an NSView showing the decoded image as layer contents,
NSImage decoding and encoding, and resize modes as contents gravity.

Adds a Kitchen Sink screen and a driver flow that decode a Blurhash in the
native view and encode a local image.
Its iOS-only native module is absent on macOS, and the JavaScript read a
property of it at import, so importing the library threw. The patch reads
it optionally, so desktop reports a 0 radius.

Adds a Kitchen Sink screen and a driver flow.
Nitro SQLite supports macOS upstream and links beside Spark's OP-SQLite,
since its SQLite symbols are prefixed. Adds a Kitchen Sink storage screen
and a driver flow covering parameterized, batched and async queries.
OSLogStore's current-process scope exists on macOS 12, but the library's
podspec lists only iOS and only its iOS bindings call native. The patch
adds macOS 12 to the podspec and a macOS entry using the iOS bindings.

Adds a Kitchen Sink devtools screen and a driver flow that read back a
console marker from the unified log.
Nitro ZXing is C++ on top of VisionCamera 5 and Nitro Image, which Spark
already builds for macOS; its podspec only lacked the platform.

Adds a Kitchen Sink media screen and a driver flow that scan a QR code
from a still image.
Safe Area Context 5.6.2 supports macOS upstream; no patch is needed.
Adds a Kitchen Sink windows screen and a driver flow checking the
provider's frame and insets and SafeAreaView layout.
expo-keep-awake builds on macOS but did nothing there: activation compiled
to a no-op and isActivated returned false. The patch holds a ProcessInfo
activity that disables idle display and system sleep while activated.

Adds a Kitchen Sink screen and a driver flow that find the power assertion
in pmset while active and not after deactivating.
Screens' native containers are UIKit only, but callers that enable them
(such as React Navigation's native stack) rendered the missing native
components on macOS. The patch adds macOS entries that re-export each
component's web implementation, which uses plain views.

Adds a Kitchen Sink screen and a driver flow checking active, inactive and
stack screens.
Reanimated 4.2.1 and Worklets 0.7.4 support macOS upstream; no patch is
needed. Adds a Kitchen Sink screen and a driver flow checking a timing
animation's finished callback and UI-thread frame callbacks.
Gesture Handler 2.30.0 supports macOS upstream; no patch is needed. Adds a
Kitchen Sink screen and a driver flow checking that the native module and
button are registered and a GestureDetector mounts. Pointer gestures are
checked by hand.
native-stack's native view relies on UIKit header configs, which draw
nothing on macOS, so stack screens had no header or back button. The patch
gives macOS the JavaScript stack view web uses, which draws headers with
@react-navigation/elements. Core and bottom tabs work unchanged.

Adds a Kitchen Sink screen and a driver flow navigating a stack forward and
back and switching tabs.
Quick Crypto's BLAKE3 guards drop the NEON source and x86 SIMD symbols
only for the x86_64 iOS simulator, so an x86_64 macOS build failed to
compile and link. The patch adds the same settings for macosx x86_64. The
Kitchen Sink's PBKDF2 vector now runs 4096 iterations.
Nitro Fetch keeps its launch-prefetch queue in the nitro_fetch_storage
defaults suite and its encryption key under a fixed Keychain service. On a
Mac both are shared by every app using the library, and the queue can hold
request headers. The patch suffixes both with the app's namespace, as it
already does for the URL cache.
App Tracking Transparency does nothing on the Mac: the status is always
not-determined and requests never prompt. Unpatched, the library imports
and reports unavailable, which is the accurate answer, so the native build
added nothing.
…ings

The library's native module is iOS and Android only, so importing it threw on
macOS. A Mac has no software keyboard, so its macOS entries now use the
package's own no-op web bindings. Kitchen Sink renders the provider, a
keyboard-aware scroll view and the toolbar, with a driver flow.
Sentry's native module builds for macOS, but its replay mask views compile
only with UIKit while codegen still registers them, so the app aborted at
launch on a nil component class. On macOS they are now plain containers.
Kitchen Sink captures an error and checks its stack trace and native macOS
context without sending it, with a driver flow.
expo-router runs on macOS without a patch through React Navigation and the
react-native-screens patch. Kitchen Sink mounts ExpoRoot on a small route
folder whose home route pushes a dynamic route with a search param, with a
driver flow.
react-native-graph 1.4.0 builds its line paths with Skia 1's mutable SkPath
methods, which Skia 2 removed, so its graphs threw on render. The patch builds
them with Skia.PathBuilder instead. Kitchen Sink renders a static and an
animated graph with a gradient fill, with a driver flow.
Keep-awake always releases its tag, polls pmset instead of sleeping, and
tolerates the assertion development builds already hold. The drawer counts
onClose only after it opens, since it reports a close on mount. The bottom
sheet retries its snap until the sheet has measured itself. The pressable demo
gets the GestureHandlerRootView its native button requires.
The catalog golden still showed 6 populated areas from before the library
screens were added; it now matches the current catalog's counts and screens.
A blurhash, thumbhash, SF Symbol or ImageRef source rendered nothing and
fired no load events, so a screen waiting on onLoadEnd or onError waited
forever. Such a source now reports onLoadStart, onError and onLoadEnd once.
Sources also keep their width, height and scale, so a require()d @2x asset
draws at its point size rather than at 1x.
The macOS stand-in rebuilt object sources from uri and headers alone, so a
source's scale was lost and a @2x image drew at 1x, and a placeholder given
as { uri, headers } fell through to defaultSource and never showed. Sources
now keep width, height and scale, and uri placeholders load.
The date was applied before minimumDate and maximumDate, and NSDatePicker
clamps its date whenever the limits move, so one update changing the value
and both limits left the picker on a clamped date that JavaScript never
heard about. The limits now apply first, and the date is set after any
change to either.
…sing

Without NSLocationWhenInUseUsageDescription, expo-location calls RCTFatal,
which Debug builds swallow and Release builds throw, so a Release app
terminated on its first permission check. On macOS it now logs an error and
reports denied, as Debug did. The docs named NSLocationUsageDescription,
which CoreLocation and expo-location never read; they now name the
WhenInUse key.
The animated LineGraph wraps itself in a GestureDetector, which throws
without a GestureHandlerRootView in development builds, so the screen
showed the error in a dev Kitchen Sink. The e2e driver bundles with
__DEV__ off, which hid it.
Fourteen patched podspecs declared macOS 11 or 12, below Spark's macOS 14
floor. Masked View in particular uses macOS 14's CADisplayLink and only
built because the Podfile raised every pod's deployment target. They now
declare 14.0.
Driver windows are never shown, so an external CGWindow lookup cannot find
one for a real click. Add an opt-in --pointer backend using the existing
testID lookup and an external CGEventPostToPid helper.

Encode the events in the window-owning process so AppKit preserves the
window-local hit point. Show the window without presentation animation and
let the gesture demo accept its first background click on macOS. Missing
input-posting permission fails immediately without a prompt.

**Test plan**

Focused native protocol, pointer backend and existing driver backend checks
passed. Scoped strict TypeScript checking passed.

✅ Debug arm64 Kitchen Sink build via heavy, -jobs 6, no signing.

✅ Real native mouse-view probe observed one down and one up callback.

✅ Gesture-handler and drawer-layout flows via --backend driver --pointer:
GestureDetector's successful tap callback and drawer open/close were
observed, including a screenshot showing tap. The console remained locked.
An unknown window or a non-finite point now fails at once with E_NO_WINDOW or E_INVALID_ARGUMENT, as capture does, instead of waiting and reporting E_NOT_SETTLED.
…ettles

tapOn passed no timeout, so the driver's fixed 5 s settle wait applied and E_NOT_SETTLED failed the step. It now waits up to the step's timeout in slices under the driver's reply limit, re-resolving the target between them, and taps once the timeout is spent, as it did before the settle wait. pointerEvents with timeoutMs 0 skips the wait.
…ation

Main now requires every active pin to resolve to a prepared package. An optional
pin that finds another installed version stays unpatched on purpose, so it no
longer fails that check.
…ws outside the gate

The 50 library checks this branch adds pass on the driver backend, so the
release gate now blocks on them. The gesture-handler and pressable-pointer
flows need --pointer, which the gate does not accept, so they move to
e2e/pointer/flows/ and run with bun run gate:pointer before a release.
@react-native-community/blur and the other native libraries this branch adds
have no Windows implementation yet, so the Windows build excludes them like
the rich text libraries. Windows support comes on a separate branch.
The sidebar catalog counts changed as library screens were added, the title
bar tint changed slightly, and in Arabic the Dock screen title is now
translated and the sidebar uses the Latin digits the shell pins.
@jmeistrich
jmeistrich force-pushed the feat/desktop-libs-macos-land-v2 branch from e065b89 to 4633699 Compare November 1, 2026 11:08
@jmeistrich
jmeistrich merged commit d0fb595 into main Nov 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant