fix(renderer): macOS double-tap via native smartMagnify event (supersedes broken minPointers approach)#34
Open
LeslieOA wants to merge 1 commit into
Open
fix(renderer): macOS double-tap via native smartMagnify event (supersedes broken minPointers approach)#34LeslieOA wants to merge 1 commit into
LeslieOA wants to merge 1 commit into
Conversation
…33) Supersedes the minPointers(2) approach from #23 / commit 9cd620b. That fix doesn't work — Workspace's diagnostic pass (their PR #183) confirmed RNGH-macos receives every trackpad tap as a single-pointer event, so minPointers(2) fails on first touch and the gesture never starts. The actual mechanism: macOS routes trackpad two-finger taps through NSResponder.smartMagnifyWithEvent: rather than touch events. Consumers wire a Swift module that listens for that and emits an `onSmartMagnify` event via the existing ScrollWheelBridge native module; the library subscribes to that event and converts to world coordinates the same way the iOS single-finger RNGH tap path does. Library-side changes (this commit) src/renderer/NativeScrollWheelView.tsx - Add SmartMagnifyEvent type. Top-left-origin {x, y} matches the shape of RNGH's TapGesture event so the consumer can reuse the world-coordinate conversion path. src/renderer/CanvasView.tsx - Remove the minPointers(2) macOS branch from the tap gesture — it does nothing useful and was misleading. - Add a useEffect listener for `onSmartMagnify` events that converts to world coords and calls handleDoubleTap. Lives next to the existing onScrollWheel listener for bridging consistency. - Add Platform.OS === 'macos' early-return in the tap gesture's onEnd. The RNGH tap still races with pan (so single-finger taps don't accidentally trigger pan logic), but the click itself is a no-op on macOS — Smart Zoom is the only path that fires handleDoubleTap. Consumer-side (NOT in this commit) Consumers need to provide a Swift module that handles smartMagnifyWithEvent: and emits onSmartMagnify through ScrollWheelBridge. Workspace's apps/desktop/macos/.../NativeModules/ CanvasScrollInterceptor.swift is the reference implementation — linked from the JSDoc on SmartMagnifyEvent. The macos-app harness (added in #21 / PR #30) will need its README updated post-merge to point at this consumer requirement. Filed as a follow-up rather than reaching into PR #30's branch. Verification - npm test: 38/38 - npm run typecheck: clean - npm run lint: exit 0 - Grep confirms no minPointers usage remains in the renderer source. Manual verification (you, on a Mac with a real Workspace-style smart magnify bridge wired up, or after #30 merges and someone wires the Swift module): - macOS: two-finger trackpad double-tap on a node zooms to that node - macOS: single-finger double-tap / mouse double-click is a no-op - iOS: single-finger double-tap on a node still zooms (unchanged) Refs: #33 Supersedes: #23 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LeslieOA
added a commit
that referenced
this pull request
May 25, 2026
…macos-harness
Bypasses react-native-macos-init entirely. The upstream tool has three
known incompatibilities (workspaces ENOWORKSPACES, peer-dep ERESOLVE
between react 19.1.0 and ^19.1.4, util.styleText brittleness on
Node < 20.12) that I previously documented as a manual setup procedure
in the README. That was an honest answer but a poor user experience —
the user has to bootstrap a temp project, install with --legacy-peer-deps,
run init, copy macos/ back, find-and-replace project name references.
The workspace-sh org already has a working solved-this-problem template
in `enriched-markdown-macos-harness` — a macOS harness for the
enriched-markdown library, structured identically to what we want.
Lifted its macos/ wholesale, customised only:
- example/macos-app/macos/workspace-macOS/Info.plist
CFBundleName "Enriched Markdown Harness" → "JSON Canvas Playground"
- example/macos-app/macos/workspace.xcodeproj/project.pbxproj
PRODUCT_BUNDLE_IDENTIFIER from
"org.reactjs.native.$(PRODUCT_NAME:rfc1034identifier)" to
"sh.workspace.jsoncanvas.macos-playground" (all 4 occurrences across
Debug / Release × app / .dSYM build configs)
- example/macos-app/app.json
name "macos-app" → "workspace" (must match AppDelegate.mm's
self.moduleName = @"workspace" which we inherit from the template)
- example/macos-app/macos/.gitignore
Added .xcode.env.local + xcuserdata + build / DerivedData entries
that the template's .gitignore missed
Everything else inherited as-is, including:
- The Podfile's Xcode 26+ fmt-as-C++17 workaround in post_install
(fmt 11.0.2's FMT_STRING uses consteval which newer Clang rejects;
the harness folks already solved this — we get it for free)
- AppDelegate / main.m / Info.plist / .entitlements / Main.storyboard
(the standard react-native-macos shell)
- workspace.xcodeproj/project.pbxproj (with our bundle id substituted)
- workspace.xcworkspace
- PrivacyInfo.xcprivacy
Root + library changes
- package.json: dropped the desktop:init script entirely (no longer
applicable — the native scaffold ships in-repo)
- README.md: rewrote the "Playground app (macOS)" section to match
the new flow (install → pods → macos, no init)
- example/macos-app/README.md: rewrote setup section explaining
template provenance and current state
Verification
- Library checks pass on this branch: npm test 38/38, typecheck clean,
lint exit 0
- example/macos-app/app.json's name "workspace" matches AppDelegate.mm's
registered moduleName "workspace" (grep confirmed)
- example/macos-app/macos/ is 140K, 18 source files — no Pods/, no
build/, no machine-local files
What you still need to do once
On your Mac, the smoke flow becomes:
npm run desktop:install # workspace deps with --legacy-peer-deps
npm run desktop:pods # pod install in example/macos-app/macos/
npm run desktop:macos # build + launch
If you also want Safari-style two-finger double-tap zoom (the work in
PR #34), you'll need to add a CanvasScrollInterceptor.swift to the
macOS target — reference impl in Workspace's apps/desktop. Documented
in example/macos-app/README.md.
Refs: #21
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LeslieOA
added a commit
that referenced
this pull request
May 25, 2026
…dule Real fix for two-finger trackpad zoom on macOS. The library now ships its own native event bridge — consumers don't write Swift, they just `npm install` and pod install picks it up via autolinking. Why a library-shipped native module The previous JS-only fix attempt (`minPointers(2)` in RNGH's Tap) silently never fires on RNGH-macos — trackpad taps arrive as single-pointer events, the predicate fails on first touch, the gesture never starts. Workspace diagnostics in workspace-sh/workspace#183 confirmed this. The actual mechanism: macOS routes trackpad two-finger taps through `NSResponder.smartMagnifyWithEvent:` (Safari's "Smart Zoom"). RNGH doesn't bridge it. Workspace's apps/desktop solves this by writing a Swift `CanvasScrollInterceptor` + `ScrollWheelBridge` in their own app. That's fine for an app, wrong for a library — every consumer would have to write the same Swift boilerplate. This commit moves that Swift code into the library, behind autolinking. Files ios/WorkspaceJsonCanvasGesture.swift RCTEventEmitter subclass. Installs an `NSEvent.addLocalMonitorForEvents` hook for `.smartMagnify` events, forwards them to JS as `onSmartMagnify` with top-left-origin window coordinates (matching the iOS RNGH tap event shape). Whole class wrapped in `#if os(macOS)` so the file is safe on iOS/Android builds. KNOWN LIMITATION (v1): the monitor is window-global, so coordinates are window-relative not canvas-view-relative. Fine for a full-window canvas (the playground harness). An app with chrome (sidebar, toolbar inset) needs to subtract its own offsets. Documented on the `SmartMagnifyEvent` type's JSDoc. A future view-component variant will give per-instance view-local coords. ios/WorkspaceJsonCanvasGestureBridge.m Objective-C `RCT_EXTERN_MODULE` shim that registers the Swift class with React Native's bridge. Gated on `TARGET_OS_OSX` so the linker doesn't choke on iOS. react-native-jsoncanvas.podspec Pod definition at repo root. `s.platforms = { :osx => '11.0' }` — macOS-only for now. `s.source_files = 'ios/**/*'` picks up everything in the ios/ directory; the per-file platform guards handle the rest. react-native.config.js Autolinker config. `dependency.platforms = { ios: null, android: null }` tells the autolinker not to integrate on those platforms (we have no iOS/Android native code today). The macOS Pod is picked up by react- native-macos's autolinking via the podspec at the repo root. JS changes src/renderer/NativeScrollWheelView.tsx - Added `SmartMagnifyEvent` type with full JSDoc on the v1 window-coord limitation - Added `jsonCanvasGestureEvents` — NativeEventEmitter wrapping our own `WorkspaceJsonCanvasGesture` module, separate from the consumer-provided `ScrollWheelBridge` (which stays for the existing `onScrollWheel` scroll-pan integration; that's an app-level concern so it's a peer dep, not something we ship) src/renderer/CanvasView.tsx - Removed the broken `minPointers(2)` macOS branch on the tap gesture. It was a wrong fix that never fired on RNGH-macos. - Added a new `useEffect` listener on `jsonCanvasGestureEvents` for `onSmartMagnify` that calls `handleDoubleTap(wx, wy)` with world-coord-converted tap location. Sits next to the existing `onScrollWheel` listener for bridging consistency. - Added a `Platform.OS === 'macos'` early-return in the RNGH tap's `onEnd` so single-finger double-clicks don't trigger handleDoubleTap twice (RNGH tap still races with pan to keep pan gestures clean, but the click itself is a no-op on macOS). Consumer flow after this lands cd example/macos-app npm install # autolinks the new Pod npm run desktop:macos:pods # pod install picks it up npm run desktop:macos:dev # build + run Two-finger trackpad double-tap on a node should now zoom to that node. Single-finger tap / mouse double-click: no-op (intentional). iOS / Android: unchanged. Supersedes #33 / #34 (which were JS-only attempts at the same fix that this work obsoletes). Refs: #21, #23, #33 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LeslieOA
added a commit
that referenced
this pull request
May 26, 2026
…-testing (#30) * feat: scaffold bare-RN + react-native-macos harness (closes #21) Sibling to example/expo-app — same App.tsx shape and same hesprs-demo fixture, but in a vanilla macOS window rather than Expo. Completes the harness story (Expo for iOS / Android / web; this for macOS). What this PR ships JavaScript / TypeScript / Metro / Babel scaffolding only. Native macos/ scaffold (Xcode project, Podfile, AppDelegate, NativeModules) is NOT in this PR — it's generated by react-native-macos's own CLI on the maintainer's Mac via `npm run desktop:init`. Files example/macos-app/ package.json Pinned to Workspace apps/desktop's stack: react@19.1.4 (exact), react-native@^0.81.5, react-native-macos@^0.81.4, gesture-handler / reanimated / worklets / skia matching the Expo playground. app.json macos-app / "JSON Canvas Playground (macOS)". index.js AppRegistry.registerComponent — bare RN entry. App.tsx Mirrors example/expo-app/App.tsx — Fit / Recenter buttons, getLastAction status pill. fixtures.ts Same hesprs-demo fixture as the Expo playground. metro.config.js extraNodeModules + blockList pinning the local react@19.1.4 so the Expo playground's root-hoisted react@19.2.0 can't leak in. Mirrors Workspace apps/desktop/metro.config.js. babel.config.js @react-native/babel-preset + reanimated/plugin + @babel/plugin-transform-export-namespace-from (the syntax-tree ecosystem dependencies in the library use export-namespace-from syntax that RN's preset doesn't transform by default). tsconfig.json Extends @react-native/typescript-config. .gitignore Standard bare-RN ignores plus macos/Pods, macos/build, DerivedData. README.md Setup instructions for the one-time native init and the run / clean / pods / start scripts. Root scripts (mirror Workspace's mobile:* / desktop:* convention) desktop:install npm install for the macOS app's workspace desktop:init Run react-native-macos init + pod install desktop:pods Re-run pod install (after native dep change) desktop:clean rm macos/build + macos/Pods desktop:start Metro on port 8083 (avoids Expo's 8082) desktop:clear watchman watch-del-all + metro --reset-cache desktop:macos react-native run-macos (build + launch) desktop:dev concurrently: clear + macos Deliberately NOT in this PR - No NSSplitView, no custom title bar, no native bridges. The example/macos-app/README.md "What this harness is NOT" section enumerates the don't-list explicitly so future-me doesn't drift scope toward recreating Workspace's apps/desktop. - No package.json devDeps for react-native-macos-init or react-native-cli — desktop:init uses npx to avoid persisting a tool we only run once. - No CI for this harness — RN-macOS builds require macos-15 runners, Xcode, and a full pod install; not worth the runner minutes until there's a real consumer. Verification - Library checks still pass: npm test 38/38, typecheck clean, lint exit 0. The macos-app sits in isolation under example/, the library's tsconfig / eslint / jest configs all scope to src/. - Every generated file syntactically valid (JSON parsed, JS Function constructor smoke). Manual verification needed by maintainer - Run npm run desktop:install + desktop:init to generate macos/ - npm run desktop:macos — confirm window opens, hesprs-demo renders, Fit / Recenter work, two-finger trackpad double-tap zooms a node Refs: #21 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(macos-app): document react-native-macos-init compatibility issues Attempting to verify the desktop:init script end-to-end (in the spirit of "make it actually testable") surfaced three upstream blockers: 1. ENOWORKSPACES: react-native-macos-init's internal npm install fails with "This command does not support workspaces." --no-workspaces isn't propagated into the tool's subprocess. 2. ERESOLVE: the RN community template installs react@19.1.0; react-native-macos@0.81.7 peers react@^19.1.4. The init's own npm install has no --legacy-peer-deps escape valve. 3. Node version sensitivity: the init's template generator calls util.styleText (added in Node 20.12). If npx resolves an older Node, generation completes without errors but produces empty output directories. None of these are fixable from a consumer (us). The Microsoft react-native-macos team is aware. Until upstream lands fixes, the init step is documented as a manual procedure (bootstrap outside the workspace, run with --legacy-peer-deps on Node ≥ 20.12, copy the resulting macos/ back). Changes - package.json: desktop:init script now prints an error pointing at the manual steps. desktop:install gains --legacy-peer-deps to match the bootstrap conditions. - example/macos-app/README.md: rewrites the "First-time setup" section with the three blockers documented and the manual procedure step-by-step. Library checks still pass on this branch (38/38, typecheck clean, lint exit 0). No source changes. The Expo playground (example/expo-app/) remains the path of least friction for testing the renderer — fully working today on iOS / Android (and web with the caveats in #17). Refs: #21 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(macos-app): ship working macos/ template from enriched-markdown-macos-harness Bypasses react-native-macos-init entirely. The upstream tool has three known incompatibilities (workspaces ENOWORKSPACES, peer-dep ERESOLVE between react 19.1.0 and ^19.1.4, util.styleText brittleness on Node < 20.12) that I previously documented as a manual setup procedure in the README. That was an honest answer but a poor user experience — the user has to bootstrap a temp project, install with --legacy-peer-deps, run init, copy macos/ back, find-and-replace project name references. The workspace-sh org already has a working solved-this-problem template in `enriched-markdown-macos-harness` — a macOS harness for the enriched-markdown library, structured identically to what we want. Lifted its macos/ wholesale, customised only: - example/macos-app/macos/workspace-macOS/Info.plist CFBundleName "Enriched Markdown Harness" → "JSON Canvas Playground" - example/macos-app/macos/workspace.xcodeproj/project.pbxproj PRODUCT_BUNDLE_IDENTIFIER from "org.reactjs.native.$(PRODUCT_NAME:rfc1034identifier)" to "sh.workspace.jsoncanvas.macos-playground" (all 4 occurrences across Debug / Release × app / .dSYM build configs) - example/macos-app/app.json name "macos-app" → "workspace" (must match AppDelegate.mm's self.moduleName = @"workspace" which we inherit from the template) - example/macos-app/macos/.gitignore Added .xcode.env.local + xcuserdata + build / DerivedData entries that the template's .gitignore missed Everything else inherited as-is, including: - The Podfile's Xcode 26+ fmt-as-C++17 workaround in post_install (fmt 11.0.2's FMT_STRING uses consteval which newer Clang rejects; the harness folks already solved this — we get it for free) - AppDelegate / main.m / Info.plist / .entitlements / Main.storyboard (the standard react-native-macos shell) - workspace.xcodeproj/project.pbxproj (with our bundle id substituted) - workspace.xcworkspace - PrivacyInfo.xcprivacy Root + library changes - package.json: dropped the desktop:init script entirely (no longer applicable — the native scaffold ships in-repo) - README.md: rewrote the "Playground app (macOS)" section to match the new flow (install → pods → macos, no init) - example/macos-app/README.md: rewrote setup section explaining template provenance and current state Verification - Library checks pass on this branch: npm test 38/38, typecheck clean, lint exit 0 - example/macos-app/app.json's name "workspace" matches AppDelegate.mm's registered moduleName "workspace" (grep confirmed) - example/macos-app/macos/ is 140K, 18 source files — no Pods/, no build/, no machine-local files What you still need to do once On your Mac, the smoke flow becomes: npm run desktop:install # workspace deps with --legacy-peer-deps npm run desktop:pods # pod install in example/macos-app/macos/ npm run desktop:macos # build + launch If you also want Safari-style two-finger double-tap zoom (the work in PR #34), you'll need to add a CanvasScrollInterceptor.swift to the macOS target — reference impl in Workspace's apps/desktop. Documented in example/macos-app/README.md. Refs: #21 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(macos-app): fix pod install + xcodebuild for npm-workspaces layout Unblocks the macOS harness end-to-end. Three real fixes layered on top of the template lift, plus a script naming reorganisation. 1. Podfile :path — use_react_native! needs a RELATIVE path The harness template's `:path => '../node_modules/react-native-macos'` assumed bare-RN layout where react-native-macos installs into the app's own node_modules. Under our npm-workspaces setup it hoists to the monorepo root. First attempt (absolute via ws_dir walk-up): use_react_native! does string-level './' prepending on the path arg, so an absolute path ended up as './/Users/leslieoa/...' and broke. Final fix: hardcode the relative walk-up '../../../node_modules/ react-native-macos'. Matches what the autolinker already resolves for gesture-handler / reanimated / worklets / skia per the link_native_modules log lines. 2. Postinstall react-native symlink — xcodebuild Hermes script fix The Hermes Pod's "Replace Hermes for the right configuration, if needed" build script hardcodes $PODS_ROOT/../../node_modules/react-native/scripts/xcode/with-environment.sh relative to macos/Pods. With react-native hoisted to the monorepo root (npm dedupe finds a single version that satisfies both example apps' pins), that path doesn't resolve and xcodebuild fails before any source compiles. Workspace's apps/desktop avoids this because apps/mobile pins react-native 0.83 exact and apps/desktop wants ^0.81 — npm can't hoist either, so both get local installs. Our example apps' pins happen to coexist at root, so we don't get the same accidental local install. Fix: a postinstall script at example/macos-app/scripts/link-hoisted-rn.js that walks up node_modules ancestors, finds whichever copy of react-native the monorepo resolved, and symlinks it into the app's local node_modules. Idempotent — leaves a real local install alone, recreates a stale symlink on every npm install. 3. .gitignore — .spm.pods/ pod install now generates a Swift Package Manager cache under macos/.spm.pods/. Ignored. 4. Script naming reorganisation: <form-factor>:<platform>:<mode> The old ios:* / android:* / desktop:* surface duplicated Metro and install scripts across iOS and Android (they actually do identical work for the shared expo-app). Restructured to: mobile:install npm install for example/expo-app mobile:start Metro (platform-agnostic — JS bundler) mobile:clear watchman + Metro --reset-cache mobile:ios:* iOS-specific run / prebuild / clean / dev mobile:android:* Android-specific run / prebuild / clean / dev desktop:install npm install for example/macos-app desktop:start Metro on port 8083 desktop:clear watchman + Metro --reset-cache desktop:macos:* macOS-specific run / pods / clean / dev Form-factor level collapses Metro + install (one shared per app). Platform level adds the build/run specifics. Pattern scales to future windows / linux desktop variants and tablet-specific mobile variants without renaming anything. README's "Example apps" section rewritten to match. 5. project.pbxproj — pod install regen pod install added the workspace integration markers to the project file. Standard pod-install side effect, not a hand-edit. Verification (your machine) After pulling this branch: npm run desktop:install # triggers postinstall symlink npm run desktop:macos:pods # pod install (re-run not strictly # required if Pods exist, # but safe to confirm) npm run desktop:macos:dev # Metro on 8083 + xcodebuild + launch Library checks still pass: tsc clean, 38/38 tests, lint exit 0. Refs: #21 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(examples): respect system color scheme in App.tsx wrappers The macOS playground rendered dark-scheme node colours (the renderer calls useColorScheme() internally) on top of a hardcoded white root background — node text was the right colour for dark mode but the canvas-empty area looked completely wrong. Same shape in the Expo playground. Both App.tsx wrappers now read useColorScheme() and pick #000 / #fff for the GestureHandlerRootView background to match what the renderer is doing internally. Hot reload picks it up immediately on theme switches. Refs: #21 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(macos-app): symlink react-native-macos too (not just react-native) The "Bundle React Native code and images" build phase script on the workspace-macOS target calls `../node_modules/react-native-macos/ scripts/react-native-xcode.sh` — same shape as the hermes-engine "Replace Hermes" script that already needed the workaround for react-native. Generalised link-hoisted-rn.js: PACKAGES array lists every hoisted React Native package that has at least one build script with a relative path hardcoded into pbxproj. Each entry gets the same symlink treatment. Add to the list as new "package not found" xcodebuild errors surface from other Pod scripts (most autolinked native modules use absolute paths in pbxproj so they're not affected, but a few — hermes, react-native-macos — bake in relative paths to their own scripts). The script comment also documents the diagnostic pattern so the next person who hits this knows what to do. Refs: #21 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(macos): ship native smartMagnify bridge as autolinked library module Real fix for two-finger trackpad zoom on macOS. The library now ships its own native event bridge — consumers don't write Swift, they just `npm install` and pod install picks it up via autolinking. Why a library-shipped native module The previous JS-only fix attempt (`minPointers(2)` in RNGH's Tap) silently never fires on RNGH-macos — trackpad taps arrive as single-pointer events, the predicate fails on first touch, the gesture never starts. Workspace diagnostics in workspace-sh/workspace#183 confirmed this. The actual mechanism: macOS routes trackpad two-finger taps through `NSResponder.smartMagnifyWithEvent:` (Safari's "Smart Zoom"). RNGH doesn't bridge it. Workspace's apps/desktop solves this by writing a Swift `CanvasScrollInterceptor` + `ScrollWheelBridge` in their own app. That's fine for an app, wrong for a library — every consumer would have to write the same Swift boilerplate. This commit moves that Swift code into the library, behind autolinking. Files ios/WorkspaceJsonCanvasGesture.swift RCTEventEmitter subclass. Installs an `NSEvent.addLocalMonitorForEvents` hook for `.smartMagnify` events, forwards them to JS as `onSmartMagnify` with top-left-origin window coordinates (matching the iOS RNGH tap event shape). Whole class wrapped in `#if os(macOS)` so the file is safe on iOS/Android builds. KNOWN LIMITATION (v1): the monitor is window-global, so coordinates are window-relative not canvas-view-relative. Fine for a full-window canvas (the playground harness). An app with chrome (sidebar, toolbar inset) needs to subtract its own offsets. Documented on the `SmartMagnifyEvent` type's JSDoc. A future view-component variant will give per-instance view-local coords. ios/WorkspaceJsonCanvasGestureBridge.m Objective-C `RCT_EXTERN_MODULE` shim that registers the Swift class with React Native's bridge. Gated on `TARGET_OS_OSX` so the linker doesn't choke on iOS. react-native-jsoncanvas.podspec Pod definition at repo root. `s.platforms = { :osx => '11.0' }` — macOS-only for now. `s.source_files = 'ios/**/*'` picks up everything in the ios/ directory; the per-file platform guards handle the rest. react-native.config.js Autolinker config. `dependency.platforms = { ios: null, android: null }` tells the autolinker not to integrate on those platforms (we have no iOS/Android native code today). The macOS Pod is picked up by react- native-macos's autolinking via the podspec at the repo root. JS changes src/renderer/NativeScrollWheelView.tsx - Added `SmartMagnifyEvent` type with full JSDoc on the v1 window-coord limitation - Added `jsonCanvasGestureEvents` — NativeEventEmitter wrapping our own `WorkspaceJsonCanvasGesture` module, separate from the consumer-provided `ScrollWheelBridge` (which stays for the existing `onScrollWheel` scroll-pan integration; that's an app-level concern so it's a peer dep, not something we ship) src/renderer/CanvasView.tsx - Removed the broken `minPointers(2)` macOS branch on the tap gesture. It was a wrong fix that never fired on RNGH-macos. - Added a new `useEffect` listener on `jsonCanvasGestureEvents` for `onSmartMagnify` that calls `handleDoubleTap(wx, wy)` with world-coord-converted tap location. Sits next to the existing `onScrollWheel` listener for bridging consistency. - Added a `Platform.OS === 'macos'` early-return in the RNGH tap's `onEnd` so single-finger double-clicks don't trigger handleDoubleTap twice (RNGH tap still races with pan to keep pan gestures clean, but the click itself is a no-op on macOS). Consumer flow after this lands cd example/macos-app npm install # autolinks the new Pod npm run desktop:macos:pods # pod install picks it up npm run desktop:macos:dev # build + run Two-finger trackpad double-tap on a node should now zoom to that node. Single-finger tap / mouse double-click: no-op (intentional). iOS / Android: unchanged. Supersedes #33 / #34 (which were JS-only attempts at the same fix that this work obsoletes). Refs: #21, #23, #33 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(macos): unblock autolinker discovery of the library's own Pod Two related fixes that together let `pod install` on macOS actually pick up the WorkspaceJsonCanvasGesture native module shipped in 241d5a0. 1. react-native.config.js: drop the platforms exclusion Earlier version declared dependency: { platforms: { ios: null, android: null } } intending to signal "we have no iOS/Android native code". The macOS autolinker reads the same config and interprets the `ios: null` exclusion as "exclude from all platforms", silently dropping us from `pod install` on macOS too — exactly the opposite of what we wanted. Confirmed empirically: with that block in place, `npx @react-native-community/cli config` returned 4 dependencies (skia / gesture-handler / reanimated / worklets) and omitted us entirely. Removing it (or making the file `module.exports = {}`) adds `@workspace.sh/react-native-jsoncanvas` to the list. Comment in the new minimal config explains why we don't restore the previous shape — the podspec's own s.platforms = { :osx => '11.0' } handles iOS exclusion at the CocoaPods level, no autolinker config needed. 2. link-hoisted-rn.js: also symlink our own library The autolinker resolves dependencies via the consumer's local node_modules. Our `file:../..` self-reference symlinks to the monorepo root, but npm hoists that symlink to root/node_modules, not example/macos-app/node_modules. The autolinker doesn't walk up. Added @workspace.sh/react-native-jsoncanvas to the symlink PACKAGES list alongside react-native + react-native-macos so the postinstall hook creates a local symlink at: example/macos-app/node_modules/@workspace.sh/react-native-jsoncanvas pointing at the actual library. Together these get the autolinker to see us, which gets pod install to integrate the WorkspaceJsonCanvasGesture pod, which gets the Swift code into the .app, which gets `onSmartMagnify` events firing. Verification on the user's machine after pulling: npm install # postinstall: symlinks npm run desktop:macos:pods # pod install picks up our Pod npm run desktop:macos:dev # rebuild + launch Then two-finger trackpad double-tap should fire handleDoubleTap. Refs: #21 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
What changed
The macOS two-finger double-tap fix landed in commit 9cd620b (via #23) used
Gesture.Tap().minPointers(2)on macOS. That doesn't work. Workspace ran diagnostics on this approach (their PR #183) and found:This PR ports their correct fix, library-side.
The actual mechanism
macOS routes trackpad two-finger taps through
NSResponder.smartMagnifyWithEvent:rather than touch events. Bridge it natively:Files in this PR
src/renderer/NativeScrollWheelView.tsxSmartMagnifyEventtype with explanatory docstring (the docstring is the spec for consumers wiring the Swift side)src/renderer/CanvasView.tsxSmartMagnifyEvent, (2)useEffectlistener foronSmartMagnifynext to the existingonScrollWheellistener, (3) dropminPointers(2)and addPlatform.OS === 'macos'early-return in the tap gesture'sonEnd51 additions, 7 deletions across 2 files. No other source touched.
Consumer-side responsibility (NOT in this PR)
Consumers need to provide a Swift module that handles
smartMagnifyWithEvent:and emitsonSmartMagnifythrough theirScrollWheelBridge. Workspace'sapps/desktop/macos/.../NativeModules/CanvasScrollInterceptor.swiftis the reference implementation; the JSDoc onSmartMagnifyEventpoints there.For our own
example/macos-app(on PR #30): a follow-up will update its README post-merge to point at this consumer-side requirement. Not reaching into #30's branch from here.Verification
npm test: 38/38npm run typecheck: cleannpm run lint: exit 0grep minPointers src/renderer/returns only the comment explaining why we don't use itManual verification (requires a Mac + Workspace-style smart magnify bridge wired up, or post-#30-merge + Swift module):
handleDoubleTappathCloses / supersedes
workspace-sh/workspace#183(the diagnostic + canonical fix that this ports)Refs: #33