Skip to content

Conversation

@orionmiz
Copy link
Collaborator

This fix resolves a race condition where swiping back during a push transition caused the app to freeze. The issue was particularly severe when using historySyncPlugin, causing complete UI lockup.

The root cause was that the swipe gesture listener on the edge element did not check if the activity was in an active transition state before starting the swipe. This allowed users to initiate a swipe gesture while a new activity was sliding in (enter-active) or sliding out (exit-active), creating conflicting animations.

Changes:

  • Added transition state check in useStyleEffectSwipeBack's onTouchStart
  • Swipe gestures are now blocked when activity is in "enter-active" or "exit-active" transition states
  • Only allows swipe to start when activity is in stable state ("enter-done" or "exit-done")

Fixes #609

🤖 Generated with Claude Code

@changeset-bot
Copy link

changeset-bot bot commented Oct 22, 2025

🦋 Changeset detected

Latest commit: 8d38200

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@stackflow/react-ui-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Oct 22, 2025

Deploying stackflow-demo with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8d38200
Status: ✅  Deploy successful!
Preview URL: https://228cb229.stackflow-demo.pages.dev
Branch Preview URL: https://claude-issue-609-fix-011cumz.stackflow-demo.pages.dev

View logs

@coderabbitai
Copy link

coderabbitai bot commented Oct 22, 2025

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Fixed touch event handling during push and pop transitions to prevent interference with swipe-back gestures.

Walkthrough

Replaces previous non-capture touchstart/touchend listeners with capture-phase listeners for touchstart, touchmove, touchend, and touchcancel, and consolidates to a single handler that calls stopPropagation during transitions; no public API changes.

Changes

Cohort / File(s) Summary
Touch event interception
extensions/react-ui-core/src/usePreventTouchDuringTransition.ts
Replaced prior onTouchStart/onTouchEnd logic with a single preventTouch handler that calls e.stopPropagation(); added capture-phase listeners for touchstart, touchmove, touchend, and touchcancel; removed previous non-capture listeners; updated cleanup to remove capturing listeners.
Release metadata
.changeset/fix-swipe-back-transition.md
Added changeset documenting a patch for @stackflow/react-ui-core describing the swipe-back gesture fix via capture-phase touch listeners.

Sequence Diagram(s)

sequenceDiagram
    participant User as User Touch
    participant Doc as document (capture)
    participant Hook as usePreventTouchDuringTransition
    participant App as App / Children

    Note over User,Doc: Any touch event during transition (start/move/end/cancel)
    User->>Doc: touchstart / touchmove / touchend / touchcancel (capture)
    Doc->>Hook: preventTouch invoked
    alt globalTransitionState active
        Hook->>Hook: e.stopPropagation()
        Hook-->>Doc: interception (propagation stopped)
    else not active
        Hook-->>App: allow event to continue
    end
    Note over App: Children receive events only when not intercepted
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title "fix: prevent swipe back gesture during push/pop transitions" is concise, clear, and directly summarizes the main objective of the pull request. It accurately describes the core fix addressing the race condition where swipe-back gestures during transitions caused UI freezing. The title is specific enough for teammates reviewing history to understand the primary change without being overly verbose.
Linked Issues Check ✅ Passed The code changes address the core requirements of issue #609 by preventing swipe-back gestures during push/pop transitions. The modifications to usePreventTouchDuringTransition.ts add capture-phase event listeners for all touch events (touchstart, touchmove, touchend, touchcancel) to prevent touch events from reaching child elements during transitions. This approach effectively blocks swipe gesture initiation while an activity is mid-transition, which directly resolves the reported freeze and UI lockup issues. The changeset correctly documents this fix as preventing touch events during transitions to address the race condition.
Out of Scope Changes Check ✅ Passed All changes in the pull request are directly aligned with the scope of fixing the swipe-back gesture issue during transitions. The modifications to usePreventTouchDuringTransition.ts are precisely targeted at preventing touch events during transitions, and the new changeset entry appropriately documents the fix. No unrelated changes or modifications outside the stated objective of resolving issue #609 are present in the changeset.
Description Check ✅ Passed The pull request description is related to the changeset and provides meaningful context about the bug being fixed. It explains the race condition causing app freezing, identifies the root cause, and outlines the approach taken to resolve it. The description references the linked issue #609 and clearly communicates that the fix prevents swipe gesture initiation during transitions. While there appears to be a discrepancy between the mechanism described (transition state checks in onTouchStart) and what the raw summary shows (capture-phase event listeners), the high-level description remains aligned with addressing the core issue.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/issue-609-fix-011CUMZHgVW13WuPSeKTGiZ5

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 24fc4aa and 8d38200.

📒 Files selected for processing (1)
  • extensions/react-ui-core/src/usePreventTouchDuringTransition.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Write source in TypeScript with strict typing enabled across the codebase

Files:

  • extensions/react-ui-core/src/usePreventTouchDuringTransition.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: stackflow-docs
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (2)
extensions/react-ui-core/src/usePreventTouchDuringTransition.ts (2)

21-31: Excellent use of capture-phase listeners to block touch events during transitions.

The combination of preventDefault(), stopPropagation(), and capture-phase listeners ensures that all touch events are intercepted before reaching child elements (including the edge swipe area). This comprehensively prevents swipe-back gestures during push/pop transitions, directly addressing the race condition described in issue #609.


11-39: The ref stability concern is not applicable to the current implementation.

All three consumer components (Modal, BottomSheet, AppScreen) follow standard React patterns: refs are created via useRef() at the component level and attached to the same root container element throughout the component's lifecycle. Since useRef() returns the same object across re-renders and the container element never changes, the ref is guaranteed to remain stable. The implementation correctly captures ref.current into $ref for cleanup, ensuring listeners are properly removed from the same element they were attached to. Adding ref to the dependency array would be unnecessary and could cause unintended re-attachment on every ref identity change (which doesn't happen).

Likely an incorrect or invalid review comment.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 22, 2025

@stackflow/demo

yarn add https://pkg.pr.new/@stackflow/[email protected]

commit: 8d38200

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Oct 22, 2025

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
stackflow-docs 8d38200 Commit Preview URL Oct 22 2025, 06:39 AM

This fix resolves a race condition where swiping back during a push
transition caused the app to freeze. The issue was particularly severe
when using historySyncPlugin, causing complete UI lockup.

Root Cause:
The usePreventTouchDuringTransition hook was using bubble phase event
listeners on appScreenRef, which executed AFTER the edge element's
swipe gesture listeners. This meant the edge listener's touch handlers
ran before the parent could prevent them, allowing the swipe gesture
to start during transitions.

Solution:
Modified usePreventTouchDuringTransition to use capture phase listeners:
- Added { capture: true } option to addEventListener
- Added e.stopPropagation() to prevent events from reaching child elements
- This ensures the parent's prevention logic runs BEFORE edge's swipe listeners

Event Flow (Before):
1. User touches edge element
2. Edge's touchstart listener executes (swipe starts) ❌
3. Event bubbles to appScreenRef
4. Parent's preventDefault() executes (too late!)

Event Flow (After):
1. User touches edge element
2. Parent's capture phase listener executes first
3. stopPropagation() + preventDefault() blocks further propagation ✅
4. Edge's touchstart listener never executes

Changes:
- File: extensions/react-ui-core/src/usePreventTouchDuringTransition.ts
- Lines: 23, 28, 33-34, 37-38
- Added stopPropagation() calls
- Changed to capture phase with { capture: true } option

This fix ensures that when globalTransitionState is not "idle", ALL
touch events (including edge swipe gestures) are properly prevented
across the entire activity, preventing the race condition.

Fixes #609

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@orionmiz orionmiz force-pushed the claude/issue-609-fix-011CUMZHgVW13WuPSeKTGiZ5 branch from fa211a0 to e11c802 Compare October 22, 2025 05:56
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
extensions/react-ui-core/src/usePreventTouchDuringTransition.ts (1)

4-41: Clarify PR description inaccuracy: transition state check not added to useStyleEffectSwipeBack.

The PR description states "Added transition state check in useStyleEffectSwipeBack's onTouchStart," but useStyleEffectSwipeBack.ts contains no such check. The onTouchStart function (line ~231) only handles blur, state caching, and callback invocation—no transition state validation.

The actual changes are in usePreventTouchDuringTransition.ts:

  • Added stopPropagation() in touch event handlers
  • Switched to capture-phase listeners ({ capture: true })
  • Transition state check was already present (line 17: stack.globalTransitionState === "idle")

These are separate concerns: usePreventTouchDuringTransition blocks touches during transitions; useStyleEffectSwipeBack detects swipe gestures independently. Update the PR description to accurately reflect the changes made.

🧹 Nitpick comments (1)
extensions/react-ui-core/src/usePreventTouchDuringTransition.ts (1)

33-34: Consider adding touchmove to event handling for edge case coverage.

The implementation currently prevents touchstart and touchend, which effectively blocks most new gestures from initiating during transitions. However, a theoretical edge case exists: if a user begins a touch gesture while idle and a transition starts during that gesture, touchmove events would continue firing and potentially interfere with transition state.

This scenario has low probability since transitions typically originate from discrete user actions rather than during ongoing gestures. The current implementation is sound for the primary use case. However, for defensive robustness, consider adding a touchmove handler:

$ref.addEventListener("touchmove", onTouchMove, { capture: true });

with a corresponding handler that prevents default and stops propagation, similar to onTouchStart and onTouchEnd.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fa211a0 and e11c802.

📒 Files selected for processing (1)
  • extensions/react-ui-core/src/usePreventTouchDuringTransition.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Write source in TypeScript with strict typing enabled across the codebase

Files:

  • extensions/react-ui-core/src/usePreventTouchDuringTransition.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: stackflow-docs
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (3)
extensions/react-ui-core/src/usePreventTouchDuringTransition.ts (3)

21-29: LGTM: stopPropagation() addition strengthens event isolation.

Adding stopPropagation() to both touch handlers prevents events from bubbling to parent elements, complementing the capture-phase strategy to fully isolate touch events during transitions.


31-34: LGTM: Capture phase correctly prevents edge swipe conflicts.

The capture-phase listeners intercept touch events before they reach child elements (like the edge swipe area), directly addressing the race condition described in issue #609 where swipe-back gestures could start during transition animations.


36-39: LGTM: Cleanup correctly matches addEventListener options.

The removeEventListener calls properly include { capture: true } to match the addEventListener options, ensuring listeners are correctly removed and preventing memory leaks.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
.changeset/fix-swipe-back-transition.md (1)

5-5: Use hyphen for the compound modifier.

The description contains a minor grammar issue: "capture phase event listeners" should use a hyphen to join the compound modifier.

Apply this diff to fix the grammar:

-Fix swipe back gesture during push/pop transitions by using capture phase event listeners to prevent touch events from reaching child elements during transitions
+Fix swipe back gesture during push/pop transitions by using capture-phase event listeners to prevent touch events from reaching child elements during transitions
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e11c802 and 24fc4aa.

📒 Files selected for processing (1)
  • .changeset/fix-swipe-back-transition.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
.changeset/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Include a Changeset entry for any user-facing package change

Files:

  • .changeset/fix-swipe-back-transition.md
🪛 LanguageTool
.changeset/fix-swipe-back-transition.md

[grammar] ~5-~5: Use a hyphen to join words.
Context: ...ng push/pop transitions by using capture phase event listeners to prevent touch e...

(QB_NEW_EN_HYPHEN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: stackflow-docs
  • GitHub Check: Cloudflare Pages

@orionmiz orionmiz changed the title fix: prevent swipe back gesture during push/pop transitions (#609) fix: prevent swipe back gesture during push/pop transitions Oct 22, 2025
Add touchmove and touchcancel event prevention to ensure complete touch
gesture blocking during transitions. This prevents edge cases where:

1. Touch starts before transition (touchstart passes)
2. Transition begins mid-gesture
3. touchmove continues the unwanted gesture

Also refactored to use a single preventTouch handler for cleaner code.
@orionmiz orionmiz merged commit fe8b8fe into main Oct 22, 2025
9 checks passed
@orionmiz orionmiz deleted the claude/issue-609-fix-011CUMZHgVW13WuPSeKTGiZ5 branch October 22, 2025 09:03
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