fix(mobile): workspace row long-press opens only the menu; delete shows progress and tolerates teardown failures
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:
- expo-router's
Link.Menuputs aUIContextMenuInteractionon the trigger, but RN'sRCTSurfaceTouchHandlercan't be prevented by a descendant's gesture, so lifting the finger after the menu presents still deliversonPress. Upstream fixed it in 56.2.19 (cancelReactNativeTouches) — released 2026-08-17, blocked by bun's 3-dayminimumReleaseAgeand paired with a new expo-modules-core (bumping one module alone risks a launch crash, seepatches/README.md). - The teardown-failed retry sent
forcebut notskipTeardown, so the script ran and failed again — an endless "Force delete" prompt. - 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 nearestRCTSurfaceTouchHandlerand toggleisEnabledso the press is delivered astouchesCancelled. 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, underWorkspaceRow/hooks/) owns rename/delete/copy/share +isDeleting;WorkspaceRowMenuis now purely the nativeLink.Menu(Delete markeddestructive).- Delete: dirty worktree → "Delete anyway" (force, teardown still runs); teardown failed → retried once with
skipTeardownautomatically, 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). useHostWorkspacescache ops gainrefetchHostfor 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, afterbuild:types)bun run lintbun testinapps/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
patchedDependenciesentry.
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.
useHostWorkspacesaddsrefetchHostto confirm commits after relay timeouts.
Rollout notes
- Rebuild mobile dev clients to pick up the native
expo-routerpatch. - When bumping to
expo-router≥ 56.2.19, removepatches/expo-router@56.2.18.patchand itspatchedDependenciesentry; the guard test continues to pass.
Reviewer notes
- Adds the Swift patch and a guard test; updates
package.json/bun.lock. WorkspaceRowMenuis now a thinLink.Menu; actions moved intouseWorkspaceRowActionswithisDeletingwired intoWorkspaceRow.