···134134135135---
136136137137-## Your Taskclick-and-hold on any window to drag/move it
137137+## Your Task: pull-to-refresh triggers sync in mobile app
138138139139Enter plan mode (use EnterPlanMode tool) and create a detailed implementation plan.
140140Wait for my review before executing. Do NOT auto-execute.
+59-1
backend/tauri-mobile/src/App.tsx
···159159 const [lastSync, setLastSync] = useState<string | null>(null);
160160 const [syncStatus, setSyncStatus] = useState<SyncStatus | null>(null);
161161162162+ // Pull-to-refresh state
163163+ const pullStartY = useRef<number | null>(null);
164164+ const PULL_THRESHOLD = 80;
165165+162166 // Detect system dark mode via native iOS API
163167 useEffect(() => {
164168 const checkDarkMode = async () => {
···978982 const scrollToTop = () => {
979983 mainRef.current?.scrollTo({ top: 0, behavior: "smooth" });
980984 };
985985+986986+ // Pull-to-refresh touch handlers
987987+ const handleTouchStart = (e: React.TouchEvent) => {
988988+ // Ignore if editing, add input expanded, or already syncing
989989+ const anyEditing = editingUrlId || editingTextId || editingTagsetId || editingImageId;
990990+ if (anyEditing || addInputExpanded || isSyncing) return;
991991+992992+ const main = mainRef.current;
993993+ if (!main) return;
994994+995995+ // Only track if at scroll top
996996+ if (main.scrollTop <= 0) {
997997+ pullStartY.current = e.touches[0].clientY;
998998+ }
999999+ };
10001000+10011001+ const handleTouchMove = (e: TouchEvent) => {
10021002+ if (pullStartY.current === null) return;
10031003+10041004+ const pullDistance = e.touches[0].clientY - pullStartY.current;
10051005+10061006+ // If pulling down past threshold, prevent default scroll
10071007+ if (pullDistance > PULL_THRESHOLD) {
10081008+ e.preventDefault();
10091009+ }
10101010+ };
10111011+10121012+ const handleTouchEnd = (e: React.TouchEvent) => {
10131013+ if (pullStartY.current === null) return;
10141014+10151015+ const pullDistance = e.changedTouches[0].clientY - pullStartY.current;
10161016+ pullStartY.current = null;
10171017+10181018+ // Trigger sync if pulled past threshold
10191019+ if (pullDistance > PULL_THRESHOLD) {
10201020+ syncAll();
10211021+ }
10221022+ };
10231023+10241024+ // Attach touchmove with passive: false to allow preventDefault
10251025+ useEffect(() => {
10261026+ const main = mainRef.current;
10271027+ if (!main) return;
10281028+10291029+ main.addEventListener("touchmove", handleTouchMove, { passive: false });
10301030+ return () => {
10311031+ main.removeEventListener("touchmove", handleTouchMove);
10321032+ };
10331033+ }, [editingUrlId, editingTextId, editingTagsetId, editingImageId, addInputExpanded, isSyncing]);
98110349821035 // Reset to show all types (home view) and scroll to top
9831036 const showAll = () => {
···19892042 </button>
19902043 </header>
1991204419921992- <main className="saved-view" ref={mainRef}>
20452045+ <main
20462046+ className="saved-view"
20472047+ ref={mainRef}
20482048+ onTouchStart={handleTouchStart}
20492049+ onTouchEnd={handleTouchEnd}
20502050+ >
19932051 {/* Quick add - expandable in place */}
19942052 <div className={`expandable-card ${addInputExpanded ? 'expanded' : ''}`}>
19952053 {!addInputExpanded ? (