Skip to content

fix(mobile): workspace row long-press opens only the menu; delete shows progress and tolerates teardown failures - #6598

Merged
saddlepaddle merged 2 commits into
mainfrom
fix-workspace-long-press
Aug 18, 2026
Merged

saddlepaddle merged 2 commits into
mainfrom
fix-workspace-long-press

Conversation

@saddlepaddle

@saddlepaddle saddlepaddle commented Aug 18, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Long-pressing a home workspace row on iOS opened the native context menu and navigated into the workspace. Root cause is in expo-router ≤ 56.2.18 (it never cancels react-native's in-flight touch when the menu opens); upstream's 56.2.19 Swift fix is carried here as a bun patch of our pinned version.
  • The delete flow gets a visible in-flight state (dimmed row, spinner, disabled tap/"+"), a real error message on failure, and stops letting a failing teardown script block or loop the delete. Same teardown fix applied to the workspace-screen sheet.

Why / Context

Reported: "pressing and holding does the normal click (navigates)" and "the delete flow has no signal and then it fails". Three distinct defects were under that:

  1. expo-router's Link.Menu puts a UIContextMenuInteraction on the trigger, but RN's RCTSurfaceTouchHandler can't be prevented by a descendant's gesture, so lifting the finger after the menu presents still delivers onPress. Upstream fixed it in 56.2.19 (cancelReactNativeTouches) — released 2026-08-17, blocked by bun's 3-day minimumReleaseAge and paired with a new expo-modules-core (bumping one module alone risks a launch crash, see patches/README.md).
  2. The teardown-failed retry sent force but not skipTeardown, so the script ran and failed again — an endless "Force delete" prompt.
  3. Relay caps a request at 30s while a teardown may run 60s; slow deletes surfaced as "Delete failed" while the host was still deleting.

How It Works

  • patches/expo-router@56.2.18.patch: verbatim upstream Swift — when UIKit asks for the menu configuration, find the nearest RCTSurfaceTouchHandler and toggle isEnabled so the press is delivered as touchesCancelled. Native change: dev clients need a rebuild to pick it up. Guard test reads the installed Swift file, so it keeps passing after the eventual bump; drop the patch then.
  • useWorkspaceRowActions (new, under WorkspaceRow/hooks/) owns rename/delete/copy/share + isDeleting; WorkspaceRowMenu is now purely the native Link.Menu (Delete marked destructive).
  • Delete: dirty worktree → "Delete anyway" (force, teardown still runs); teardown failed → retried once with skipTeardown automatically, no prompt (per Satya: the toast was annoying, and the script already ran); other errors alert with the host's message; on any other failure the host list is refetched and, because the host archives the row before slow work, a missing row means the delete committed (covers the relay timeout).
  • useHostWorkspaces cache ops gain refetchHost for that.

Manual QA Checklist

Verified on the iPhone 16e simulator with a native build of this branch, against a live host over the relay (standalone host-service, scratch project with two worktrees, one with a 6s teardown, one dirty with a teardown that exits 1). Screenshots + notes: https://claude.ai/code/artifact/eb182eda-203d-4622-8520-506ac8f09208

  • Long-press a row → native menu (Rename / Delete in red / Copy ID / Share); dismiss → still on home, no navigation
  • Delete a clean workspace → confirm alert → row dims with spinner, "+" disabled, for the whole teardown → row gone
  • Delete a dirty workspace with a failing teardown → "Worktree has uncommitted changes → Delete anyway" → deleted (filmed before the teardown prompt was removed; the retry now runs without asking — JS-only change, typechecked)
  • Not exercised: a >30s teardown (relay timeout path); code path is refetch-and-decide

Testing

  • bun run typecheck (mobile, after build:types)
  • bun run lint
  • bun test in apps/mobile (incl. the new patch guard)
  • Native build with the patch compiles (xcodebuild, Debug simulator)

Known Limitations

  • Old dev clients keep the old long-press behaviour until rebuilt (JS reload isn't enough).
  • The "before" claim rests on the mechanism + upstream's changelog; I did not re-film an unpatched binary side by side.

Follow-ups

  • Next Expo SDK 56 batch bump (expo-router ≥ 56.2.19 with its same-day expo-modules-core): delete the patch and its patchedDependencies entry.

Summary by cubic

Fixes iOS long-press on a workspace row so it opens only the native context menu (no navigation), and makes delete show progress while tolerating teardown failures and relay timeouts. Previously, long-press also fired the row’s onPress; delete had no in-flight UI and could loop or misreport failures.

Carries upstream Swift fix as a bun patch to expo-router@56.2.18 so the menu cancels the in-flight touch. While delete runs the row dims, a spinner replaces the icon, and tap and the "+" button are disabled. A teardown failure is retried once with skipTeardown; dirty worktrees prompt “Delete anyway”; other errors show the host’s message; after a relay timeout the host list is refetched and a missing row counts as committed.

  • Workspace header actions adopt the same delete retry/teardown and timeout handling.
  • useHostWorkspaces adds refetchHost to confirm commits after relay timeouts.

Rollout notes

  • Rebuild mobile dev clients to pick up the native expo-router patch.
  • When bumping to expo-router ≥ 56.2.19, remove patches/expo-router@56.2.18.patch and its patchedDependencies entry; the guard test continues to pass.

Reviewer notes

  • Adds the Swift patch and a guard test; updates package.json/bun.lock.
  • WorkspaceRowMenu is now a thin Link.Menu; actions moved into useWorkspaceRowActions with isDeleting wired into WorkspaceRow.

Written for commit daad5c8. Summary will update on new commits.

Review in cubic

@coderabbitai

coderabbitai Bot commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@saddlepaddle, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 seconds

Limit details: You’ve used all 10 included reviews currently available under your plan.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0a2c12a0-595d-405b-9283-8d8a20ed1b45

📥 Commits

Reviewing files that changed from the base of the PR and between 0113ea2 and daad5c8.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • apps/mobile/expo-router-context-menu-patch.test.ts
  • apps/mobile/hooks/useHostWorkspaces/useHostWorkspaces.ts
  • apps/mobile/screens/(authenticated)/(home)/home/components/WorkspaceRow/WorkspaceRow.tsx
  • apps/mobile/screens/(authenticated)/(home)/home/components/WorkspaceRow/components/WorkspaceRowMenu/WorkspaceRowMenu.tsx
  • apps/mobile/screens/(authenticated)/(home)/home/components/WorkspaceRow/hooks/useWorkspaceRowActions/index.ts
  • apps/mobile/screens/(authenticated)/(home)/home/components/WorkspaceRow/hooks/useWorkspaceRowActions/useWorkspaceRowActions.ts
  • apps/mobile/screens/(authenticated)/workspace/[id]/hooks/useWorkspaceHeaderActions/useWorkspaceHeaderActions.ts
  • package.json
  • patches/README.md
  • patches/expo-router@56.2.18.patch

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.

@saddlepaddle
saddlepaddle force-pushed the fix-workspace-long-press branch from 5a44f71 to 207c1ec Compare August 18, 2026 02:44
expo-router <= 56.2.18 attaches the iOS context-menu interaction to a
Link.Trigger but never cancels react-native's in-flight touch, so lifting
the finger after the menu presents still fires the row's onPress — a
long-press on a home workspace row opened the menu and navigated.

Upstream fixed it natively in 56.2.19 (released today; blocked by bun's
minimumReleaseAge and paired with a new expo-modules-core), so carry that
change as a bun patch of the pinned 56.2.18: when UIKit asks for the menu
configuration, toggle the nearest RCTSurfaceTouchHandler so the press is
delivered as touchesCancelled. Documented in patches/README.md with the
guard test the convention requires; drop it on the next SDK 56 batch bump.
The workspace row's context menu now hands its actions to
useWorkspaceRowActions, which owns rename/delete/copy/share and an
isDeleting flag: while workspaceCleanup.destroy runs the row dims, a
spinner takes the icon slot, and tap and "+" are disabled. Delete is a
destructive menu item.

Delete-flow fixes, mirrored on the workspace-screen sheet:
- a failing teardown script no longer prompts (or loops — the old
  "Force delete" retry never passed skipTeardown): the script already
  ran, so the delete retries once without it and finishes
- dirty worktree still prompts, now "Delete anyway" as on desktop
- other failures show the host's message; after a relay timeout the list
  is refetched and a missing row counts as committed (the host archives
  before any slow work), instead of "Delete failed" for a delete that is
  still running
@github-actions

github-actions Bot commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

@saddlepaddle
saddlepaddle merged commit b6017c7 into main Aug 18, 2026
28 of 29 checks passed
saddlepaddle added a commit that referenced this pull request Aug 18, 2026

This branch was previously deployed

1 inactive deployment
preview — daad5c8f Deployed Aug 18, 2026 by saddlepaddle via Deploy Docs #7976
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