Skip to content

Fix pre-existing bugs: stale closures, double-fire, zoom edge cases#165

Merged
thomasttvo merged 60 commits into
masterfrom
thomas/pre-existing-fixes-v2
Apr 29, 2026
Merged

Fix pre-existing bugs: stale closures, double-fire, zoom edge cases#165
thomasttvo merged 60 commits into
masterfrom
thomas/pre-existing-fixes-v2

Conversation

@thomasttvo
Copy link
Copy Markdown
Collaborator

@thomasttvo thomasttvo commented Apr 16, 2026

Summary

Stacks correctness and safety fixes on top of PR #149's feature strip. Targets bugs discovered during review of PR #149 that also exist in the current library codebase: stale pin-press closures, a listener leak and post-unmount onZoomAfter fire in zoomTo(), broken double-tap zoom cycle-back in two configurations, incorrect zoomCenter semantics (both in doubleTapZoomToCenter behavior and JSDoc), double-fire of onStaticPinPositionChange at pan-end, and an accidental onTransform double-fire on every pan frame. Supersedes the prematurely-merged PR #164 and incorporates five rounds of Claude Code Review feedback.

Test Plan

  • Mount a ReactNativeZoomableView with a StaticPin and a pan-responder gesture; double-tap at increasing levels and confirm zoom cycles back to initialZoom when it hits maxZoom.
  • Repeat with maxZoom={null} (unlimited) — confirm zoom still cycles back after ~3 steps rather than growing indefinitely.
  • Repeat with zoomStep={null} + a configured maxZoom — confirm cycle-back still works on double-tap at max.
  • Set doubleTapZoomToCenter={true} and double-tap on an off-center area; confirm the zoom centers on the viewport center (originalWidth/2, originalHeight/2), not the top-left corner.
  • Pass an onZoomAfter callback and call zoomTo(); confirm the callback fires exactly once, only when the animation finishes naturally, and reads the final post-animation zoomLevel.
  • Unmount the component mid-animation (with an external zoomAnimatedValue supplied) and confirm onZoomAfter does not fire after unmount.
  • Call zoomTo() twice in rapid succession and confirm the first animation's completion callback does not remove the second animation's pan-sync listener.
  • Wire up onStaticPinPositionChange and perform a single-tap pan; confirm the callback fires once with the final position (not twice ~100 ms apart).
  • Swap onPress/onLongPress handlers on a StaticPin after mount (e.g., via state change); tap and confirm the new handler runs, not the stale one from first render.
  • Regression: confirm zoomTo() still rejects values outside minZoom/maxZoom, and that minZoom={0} is now respected (previously silently skipped).

Risk

  • zoomTo() gesture/animation lifecycle: capturedListenerId rename plus the added this.mounted guard and finished check in .start() change both listener cleanup and onZoomAfter firing semantics — consumers relying on the previous (synchronous / always-fires) behavior may see a behavior change.
  • Double-tap zoom state machine in _getNextZoomStep: the guard reorder and new effectiveMax computation change the cycle-back trigger for several zoomStep / maxZoom combinations. Most likely regression surface.
  • onStaticPinPositionChange debounce flush replaces a direct call in two places (_handlePanResponderEnd and single-tap pan animation end); consumers that expected the pre-debounced immediate fire will now see delivery deferred to the flush tick.
  • StaticPin refs for onPress/onLongPress mean handler identity changes are now observed on every render — intentional, but worth spot-checking that no downstream consumer was relying on the old frozen-at-mount behavior.
  • zoomCenter JSDoc correction and the {0,0} → {W/2, H/2} change in doubleTapZoomToCenter flip the semantics external callers may have been coding against.

Change details

Listener lifecycle in zoomTo()

  • Renamed the captured outer listener id to capturedListenerId so the completion callback cannot accidentally remove a listener registered by a later zoomTo() call (no-shadow + correctness).
  • Added if (!this.mounted) return at the top of the .start() callback to prevent post-unmount onZoomAfter fires when consumers supply an external zoomAnimatedValue (componentWillUnmount intentionally skips stopAnimation() in that case).
  • Added finished check from the animation callback so onZoomAfter only fires when the animation completes naturally, not when interrupted or cancelled.
  • After natural completion, flush() the debounced onStaticPinPositionChange before calling onZoomAfter so consumers reading pin position in onZoomAfter get the final post-animation value.

Double-tap zoom cycle-back (_getNextZoomStep)

  • Reordered guards so the configured-maxZoom equality check runs before the zoomStep == null guard — previously zoomStep={null} + configured maxZoom silently lost cycle-back on double-tap at max.
  • Added an effectiveMax fallback of initialZoom * (1 + zoomStep)^3 when maxZoom == null, so unlimited-zoom consumers also cycle back instead of growing indefinitely.
  • Inline comment clarifies that the second equality guard is only live when maxZoom == null.

doubleTapZoomToCenter + zoomTo JSDoc

  • zoomCenter coordinates are viewport-relative (top-left origin), not center-relative. Updated the JSDoc to match reality.
  • Changed doubleTapZoomToCenter to use { originalWidth/2, originalHeight/2 } as the center, matching the corrected semantics.

zoomTo() boundary checks

  • Swapped truthy checks for != null so minZoom={0} is honored (previously silently skipped, allowing negative zoom / flipped content).

onStaticPinPositionChange firing

  • Replaced the direct _updateStaticPin() call at the end of _handlePanResponderEnd with debouncedOnStaticPinPositionChange.flush() to avoid the immediate + ~100 ms debounce double-fire.
  • Same replacement for the single-tap pan animation's completion callback.
  • Deleted the now-unreachable private _updateStaticPin method.

_setNewOffsetPosition

  • Removed the redundant zoomAnim.setValue(this.zoomLevel) call that caused onTransform (and therefore onStaticPinPositionMove) to fire twice per pan frame.

StaticPin stale-closure fix

  • Added onPressRef / onLongPressRef refs updated on every render, and called through the refs inside the PanResponder callbacks so handler identity changes after mount are honored. Previously the PanResponder was created once inside useRef and froze the handlers at mount.

thomasvo and others added 30 commits December 15, 2025 11:08
…StaticPin gesture

- Use stopAnimation callback to sync offsetX/offsetY/zoomLevel when
  interrupting animations, preventing stale offset jumps
- Store and remove animation listener IDs in componentWillUnmount to
  prevent memory leaks with external animated values
- Fix README movementSensibility default from 1.9 to 1 (matching code)
- Fix StaticPin onPanResponderMove to use || instead of && so single-axis
  pans on the pin are forwarded to parent
Promote the local listenerId in zoomTo() to a class field so
componentWillUnmount can clean it up if the component unmounts
mid-animation. Also add stopAnimation() calls to prevent
post-unmount callbacks from _resolveAndHandleTap's pan animation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts:
#	lib/commonjs/ReactNativeZoomableView.js
#	lib/commonjs/ReactNativeZoomableView.js.map
#	lib/commonjs/animations/index.js
#	lib/commonjs/animations/index.js.map
#	lib/commonjs/components/AnimatedTouchFeedback.js
#	lib/commonjs/components/AnimatedTouchFeedback.js.map
#	lib/commonjs/components/StaticPin.js
#	lib/commonjs/components/StaticPin.js.map
#	lib/commonjs/components/index.js
#	lib/commonjs/components/index.js.map
#	lib/commonjs/debugHelper/index.js
#	lib/commonjs/debugHelper/index.js.map
#	lib/commonjs/helper/calcNewScaledOffsetForZoomCentering.js
#	lib/commonjs/helper/calcNewScaledOffsetForZoomCentering.js.map
#	lib/commonjs/helper/coordinateConversion.js
#	lib/commonjs/helper/coordinateConversion.js.map
#	lib/commonjs/helper/index.js
#	lib/commonjs/helper/index.js.map
#	lib/commonjs/index.js
#	lib/commonjs/index.js.map
#	lib/commonjs/typings/index.js
#	lib/commonjs/typings/index.js.map
#	lib/module/ReactNativeZoomableView.js
#	lib/module/ReactNativeZoomableView.js.map
#	lib/module/animations/index.js
#	lib/module/animations/index.js.map
#	lib/module/components/AnimatedTouchFeedback.js
#	lib/module/components/AnimatedTouchFeedback.js.map
#	lib/module/components/StaticPin.js
#	lib/module/components/StaticPin.js.map
#	lib/module/components/index.js
#	lib/module/components/index.js.map
#	lib/module/debugHelper/index.js
#	lib/module/debugHelper/index.js.map
#	lib/module/helper/calcNewScaledOffsetForZoomCentering.js
#	lib/module/helper/calcNewScaledOffsetForZoomCentering.js.map
#	lib/module/helper/coordinateConversion.js
#	lib/module/helper/coordinateConversion.js.map
#	lib/module/helper/index.js
#	lib/module/helper/index.js.map
#	lib/module/index.js
#	lib/module/index.js.map
#	lib/module/typings/index.js
#	lib/module/typings/index.js.map
#	lib/typescript/ReactNativeZoomableView.d.ts
#	lib/typescript/animations/index.d.ts
#	lib/typescript/components/AnimatedTouchFeedback.d.ts
#	lib/typescript/components/StaticPin.d.ts
#	lib/typescript/components/index.d.ts
#	lib/typescript/debugHelper/index.d.ts
#	lib/typescript/helper/calcNewScaledOffsetForZoomCentering.d.ts
#	lib/typescript/helper/coordinateConversion.d.ts
#	lib/typescript/helper/index.d.ts
#	lib/typescript/index.d.ts
#	lib/typescript/typings/index.d.ts
PR #158 removed checked-in lib/ and added it to .gitignore.
These remaining tracked files are now redundant.
Add missing cleanup for singleTapTimeoutId, longPressTimeout,
and debouncedOnStaticPinPositionChange to prevent post-unmount
callbacks from firing.
The actual defaultProps value is 1, not 3. This documentation
mismatch was pre-existing but missed during the props table rewrite.
When zoomTo() is called twice in rapid succession (e.g. two fast
double-taps), the second call overwrites this.zoomToListenerId
without removing the first listener from zoomAnim. This leaks the
listener permanently, accumulating stale callbacks that fire
panAnim.setValue() with stale values.

Fix: remove any existing zoomTo listener before adding a new one.
The instance field is preserved for componentWillUnmount cleanup.
- StaticPin: clear parentNotified when parent handles move internally
  (3-touch branch), preventing phantom onSingleTap/onDoubleTap
- StaticPin: use pressDurationRef so longPressDuration prop changes
  are picked up without remounting
- measureZoomSubject: add mounted guards in async chain to prevent
  post-unmount setState

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Documents the full API surface, gesture system, static pin behavior,
animation listener lifecycle, coordinate system, and breaking changes
from the feature-stripping PR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nt, update README intro

- Add this.mounted check inside ref.measure() callback — the third async
  level that was missed when guards were added to RAF and setTimeout
- Clear singleTapTimeoutId in _handlePanResponderGrant to prevent a
  pending single-tap from firing mid-pan (causing competing animations)
- Remove "pan momentum" and "boundaries-crossed" from README intro bullet
  since those features were stripped in this PR

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Seven pre-existing bugs identified during code review of #149:

1. _setNewOffsetPosition redundantly called zoomAnim.setValue during pan,
   causing onTransform/onStaticPinPositionMove to fire twice per frame

2. zoomTo() used truthy checks for minZoom/maxZoom, so minZoom=0 was
   silently skipped allowing negative zoom (flipped content)

3. _getNextZoomStep returned undefined when maxZoom=null, silently
   disabling double-tap zoom when consumers set unlimited zooming

4. doubleTapZoomToCenter used {0,0} as zoom center but the coordinate
   system is viewport-relative (0,0 = top-left, not center)

5. zoomTo() fired onZoomAfter synchronously before the animation ran,
   so consumers got stale zoomLevel in the callback

6. onStaticPinPositionChange had two firing paths — immediate via
   _updateStaticPin on gesture end (stale coords during double-tap)
   and debounced via listener (correct). Replaced immediate call with
   debounce flush

7. StaticPin froze onPress/onLongPress callbacks at mount time via
   useRef(PanResponder.create(...)). Added refs that update on render

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PR #165 is in a terminal-clean state at HEAD 07dff2c: APPROVED,
mergeStateStatus CLEAN, 0 unresolved review threads, CI green.
Cycle 1 produced no code changes, so the notes are restructured to
record the clean status, fold cycle 0's fix details into a History
section, and capture reusable validation snippets (CI/review query,
unresolved-threads GraphQL, typescript+lint, PR scope check) for the
next iteration.
@thomasttvo
Copy link
Copy Markdown
Collaborator Author

@claude review

Comment thread src/ReactNativeZoomableView.tsx
Restores two double-tap state cleanups (originally cf2d4d7 + cda405e on the
class component, reverted in 5bed041 and never carried over to the functional
conversion in PR #150):

- `_handlePanResponderGrant` long-press timer body now clears
  `doubleTapFirstTapReleaseTimestamp.current` and `doubleTapFirstTap.current`
  after firing `_fireOnLongPress`. Prevents a tap → long-press → release
  sequence (when `longPressDuration < doubleTapDelay`) from spuriously
  matching the prior tap's timestamp and firing `onDoubleTap`.
- `_handlePanResponderMove` clears the same two refs on transition into
  `pinch` and on transition into `shift` (gated by `gestureType.current !==
  'shift'` to avoid clearing on every move frame). Without these, a
  tap-pinch-tap or tap-pan-tap within `doubleTapDelay` could spuriously
  match as a double-tap.

Also updates the SPECS.md "Timeout cleanup" bullet to enumerate the new
clearing points (pinch/shift transitions and long-press fire) so the spec
matches the restored behavior, and refreshes SHARED_TASK_NOTES.md with the
cycle 2 status.
@thomasttvo
Copy link
Copy Markdown
Collaborator Author

@claude review

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latest commit cleanly addresses my prior nit on the missed cf2d4d7 + cda405e double-tap state cleanups, with the right Move-transition + long-press-body strategy (not the reverted Grant-based one).

Extended reasoning...

Overview

This commit is a small, targeted fix: three short delete blocks in src/ReactNativeZoomableView.tsx (long-press timer body in _handlePanResponderGrant, pinch transition and shift transition in _handlePanResponderMove) plus a corresponding bullet update in SPECS.md and a SHARED_TASK_NOTES.md history entry. It restores two double-tap state cleanups that the class component had explicitly added (commits cf2d4d7 + cda405e) and that the PR #150 functional conversion dropped — exactly what my prior nit-severity comment flagged.

Security risks

None. The change manipulates internal gesture-disambiguation refs (doubleTapFirstTapReleaseTimestamp / doubleTapFirstTap) within an already-trusted local component. No I/O, no user input parsing, no auth-adjacent code paths.

Level of scrutiny

Low for this commit, despite being part of a larger PR. The fix is mechanical (delete two refs in three specific code paths), the strategy is precisely the one documented in the class-component git history, and the PR author explicitly avoids the previously-reverted a716099 Grant-based approach with a written rationale in SHARED_TASK_NOTES.md. Verified all three insertion points match the strategy in my prior review and the cited class commits.

Other factors

  • The bug-hunting system found no new issues on this commit.
  • All my previous findings on this PR have been addressed across the iteration cycle (mounted-guard in zoomTo(), JSDoc viewport-coordinate correction, double-tap cycle-back for both zoomStep=null+configured maxZoom and maxZoom=null cases, debounce flush at zoomTo() and single-tap completion, dead _updateStaticPin removal, capturedListenerId rename, redundant zoomAnim.setValue removal, pinch-during-zoomTo race via _cancelInFlightZoomToAnimation).
  • CI is green; yarn typescript && yarn lint pass per the task notes.
  • The PR has an existing human approval (elliottkember LGTM) and the only outstanding concern in the timeline was the one this commit resolves.

PR is approved with CI green (Checks SUCCESS, Claude Code Review NEUTRAL
as informational) and zero unresolved review threads. Latest Claude
review explicitly confirms the cycle 2 cleanup fix was correct, so this
cycle is a no-op.
@thomasttvo
Copy link
Copy Markdown
Collaborator Author

@claude review

Comment thread SPECS.md Outdated
The Timeout Cleanup bullet at SPECS.md:286 claimed `singleTapTimeoutId`
is "not cleared on new gesture start", but `_handlePanResponderGrant`
unconditionally clears it before starting any long-press timer. A tap
followed by an immediate pan/long-press within `doubleTapDelay`
therefore suppresses the pending `onSingleTap` rather than firing it
mid-gesture. Replaced the parenthetical with the correct enumeration
(double-tap detection, new gesture start, single-tap timeout fire,
`componentWillUnmount`).

Also updates SHARED_TASK_NOTES.md to record cycle 4 status.
@thomasttvo
Copy link
Copy Markdown
Collaborator Author

@claude review

Comment thread SPECS.md
The Classification Rules paragraph at SPECS.md:172 split classified-gesture
interruption by 3rd finger into two outcomes — C1 (no prior tap timestamp:
spurious onSingleTap) and C2 (prior tap within doubleTapDelay: spurious
double-tap zoom). C2 is unreachable: cycle 2's _handlePanResponderMove
cleanup (lines 669-670 / 697-698) clears doubleTapFirstTapReleaseTimestamp
on transition into 'pinch' or 'shift' before gestureType is set, so by the
time a 3rd finger interrupts a classified gesture, the prior-tap timestamp
is already gone. The second _handlePanResponderEnd call therefore always
lands in the else branch (spurious onSingleTap).

Collapsed C1/C2 into a single sentence describing the only reachable
outcome and cross-referenced the Tap Handling § Timeout Cleanup section
that documents the clearing.

Also dismissed the paired completeness finding asking the Mounted Guards
bullet at SPECS.md:317-321 to enumerate a 4th call site, per CLAUDE.md
SPECS scope rule (enumeration is not a correctness gap).

Refreshes SHARED_TASK_NOTES.md with cycle 5 status.
@thomasttvo
Copy link
Copy Markdown
Collaborator Author

@claude review

Comment thread src/ReactNativeZoomableView.tsx
The .start() completion lambda in publicZoomTo was reading
props.staticPinPosition and props.onZoomAfter at schedule time, so a
parent re-render during the ~500ms zoom animation would be ignored.
Extracted the body into a _onPublicZoomToAnimationComplete
useLatestCallback wrapper, mirroring _fireSingleTapTimerBody and
StaticPin's onPress/onLongPress refs. capturedListenerId stays a local
variable passed as an argument to preserve the rapid-zoomTo race
protection.

Also updates SHARED_TASK_NOTES.md with cycle 6 status.
@thomasttvo
Copy link
Copy Markdown
Collaborator Author

@claude review

PR is APPROVED, CI green, all 16 review threads resolved, and the latest
Claude Code Review on HEAD is NEUTRAL with no findings. `yarn typescript`
and `yarn lint` both pass (repo has no `yarn ci` script).

Mergeable status is CONFLICTING because master PR #167 deleted
SHARED_TASK_NOTES.md, REVIEW.md, and other Claude-Code artifacts. That is
a merge-time concern for the maintainer at land, not an in-scope fix for
this PR round.
@thomasttvo
Copy link
Copy Markdown
Collaborator Author

@claude review

thomasvo added 2 commits April 29, 2026 10:24
PR #165 is APPROVED, MERGEABLE, CI green, and all review threads remain
resolved with zero actionable findings on HEAD `3dc7e53`. This cycle is
a no-op — nothing in `src/` or SPECS.md changed since the last reviewed
HEAD `afae34a`.

The merge commit `3dc7e53` pulled in master PR #167 (Claude Code artifact
cleanup), which deleted SHARED_TASK_NOTES.md, .claude/settings.local.json,
and _session-state.md while adding REVIEW.md. The previous CONFLICTING
mergeable state from cycle 7 is now resolved — the branch took master's
deletions cleanly. `yarn typescript` and `yarn lint` both pass.

Note for maintainer: continuous-claude recreates this file each iteration
for coordination. Master removed it deliberately via PR #167, so it
should be dropped at merge time — it's only useful during the iteration
loop, not in master.
@thomasttvo
Copy link
Copy Markdown
Collaborator Author

@claude review

The continuous-pr-iterate agent kept recreating this iteration-coordination
file inside the repo despite the wrapper's instructions to use an out-of-repo
notes file. Master deleted the file in PR #167; this branch should match.
The wrapper was patched in v0.4.2 to soft-reset agent-created note commits
so this won't recur.
Comment thread src/ReactNativeZoomableView.tsx
The publicMoveTo JSDoc was stranded above the new
_cancelInFlightZoomToAnimation helper. Move it back above publicMoveTo
and add a fresh JSDoc describing the helper's actual behavior.
@thomasttvo thomasttvo merged commit 70d36a8 into master Apr 29, 2026
2 checks passed
@thomasttvo thomasttvo deleted the thomas/pre-existing-fixes-v2 branch April 29, 2026 19:50
thomasttvo pushed a commit that referenced this pull request Apr 29, 2026
Conflict resolution summary:
- src/ReactNativeZoomableView.tsx (manual): two regions plus surgical
  ports of master's modifications onto HEAD's relocated function bodies.
  - Mount-effect comment (line ~337): took theirs (master); the
    9-line comment was silently dropped during a prior bad merge,
    same regression pattern as 66ff2c1.
  - Helpers stranded at old location (line ~1114): took ours
    (empty); the PR moved these helpers earlier in the file for
    `no-use-before-define`. Master's body-level fixes were ported
    verbatim to the new locations:
      * _setNewOffsetPosition: dropped redundant
        zoomAnim.setValue(zoomLevel.current).
      * publicMoveTo / publicMoveBy: route through the new
        _cancelInFlightZoomToAnimation() helper.
      * publicZoomTo: != null guards on max/min; defensive listener
        removal comment; completion routed through new
        _onPublicZoomToAnimationComplete; direct onZoomAfter call
        moved into the completion callback.
      * _handleDoubleTap: doubleTapZoomToCenter coordinates corrected
        from (0,0) to (originalWidth/2, originalHeight/2) per
        publicZoomTo's viewport-relative semantics.
      * _fireSingleTapTimerBody: replaced direct _updateStaticPin()
        with debouncedOnStaticPinPositionChange.flush() inside the
        finished+isMounted branch.
      * Added _cancelInFlightZoomToAnimation and
        _onPublicZoomToAnimationComplete (new in master).
      * Deleted _updateStaticPin (now unreachable after the
        debouncedFlush replacement above).
- src/helper/getNextZoomStep.ts (PR's external module): logic ported to
  match master's _getNextZoomStep rewrite — cycle-back checked before
  the zoomStep guard, effectiveMax computed for the null-maxZoom case.
  Module shape preserved (PR's design choice for no-use-before-define).
- SPECS.md (auto-merged): bytewise matches origin/master; PR did not
  touch it.
- src/components/StaticPin.tsx (auto-merged): PR's two
  no-use-before-define eslint-disable comments and master's
  onPressRef / onLongPressRef stale-closure fix both preserved.
- src/helper/getNextZoomStep.ts file existence: master removed this
  file (inlined the helper); we kept it because the PR's whole purpose
  is to extract it — the file's logic was synced to master instead.

Auto-merged (verified preserved):
- _handlePanResponderGrant: master's _cancelInFlightZoomToAnimation()
  call, expanded stop-animation comment, and double-tap cleanup inside
  the long-press setTimeout body.
- _handlePanResponderEnd: master's debouncedOnStaticPinPositionChange
  .flush() replacement.
- _handlePanResponderMove: master's pinch-start and shift-start
  double-tap state cleanups.

Verification:
- Build: yarn install + yarn typescript + yarn lint + yarn build all clean.
- Function-body equality with origin/master verified for every
  master-modified helper after the manual ports.
- Runtime: NOT yet device-verified. This is a library; on-device
  verification requires Diana (or another consumer) to integrate this
  branch. Master's underlying behavioral fixes (#165) were already
  device-verified before merge to master; this commit only reconciles
  the no-use-before-define refactor with those fixes — no new behavior
  is introduced by the merge itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
thomasttvo pushed a commit that referenced this pull request Apr 30, 2026
The base branch (thomas/no-use-before-define) was squash-merged to
master as #152, which made a direct master merge re-conflict every file
the squash touched. Fix: merge origin/master into the local pre-squash
thomas/no-use-before-define (clean — squash content already there),
then merge that into thomas/reanimated.

Conflict resolution summary:
- src/ReactNativeZoomableView.tsx: kept ours. The reanimated rewrite
  uses RNGH worklets; master's PanResponder-era diffs (no-use-before-define
  cleanup, stale-closure fixes from #165) don't apply.
- src/components/StaticPin.tsx: kept ours. Reanimated branch moved
  pin gesture handling out; master's PanResponder/longPress refs
  belong to the old architecture.
- src/hooks/useDebugPoints.ts: kept ours. Uses RNGH GestureTouchEvent
  (e.allTouches), not the PanResponder GestureResponderEvent.
- src/index.tsx: kept ours. Preserves named import + extra exports
  (FixedSize, ReactNativeZoomableViewContext, useZoomableViewContext,
  Ref). Reanimated's ReactNativeZoomableView still exports a forwardRef
  named binding, so master's default-import + type-reimport workaround
  isn't needed.

Auto-merged from master:
- .gitignore, REVIEW.md, SPECS.md (docs / ignore rules)
- src/helper/getNextZoomStep.ts (zoom edge case fix from #165)
- src/hooks/useZoomSubject.ts (isMounted guards + measurement dedup —
  adapted dedup to compare SharedValue.value, dropped originalPageX/Y
  which the reanimated branch doesn't store)

Verification:
- yarn install: clean
- yarn lint: clean
- yarn typescript: clean
- yarn build (bob): clean
- No runtime/device verification (library-only repo, no example app run).
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.

3 participants