Skip to content

feat: improve sync responsiveness (v1.3.0) - #5

Merged
psimaker merged 2 commits into
mainfrom
feat/sync-responsiveness
May 11, 2026
Merged

feat: improve sync responsiveness (v1.3.0)#5
psimaker merged 2 commits into
mainfrom
feat/sync-responsiveness

Conversation

@psimaker

@psimaker psimaker commented May 11, 2026

Copy link
Copy Markdown
Owner

Closes #4.

Summary

  • Lower the per-folder RescanIntervalS from 3600 s to 60 s (Syncthing's own default). Existing folders carrying the legacy 3600 are migrated on bridge start; user-customised values are preserved.
  • Auto-trigger a foreground rescan when the scene returns to .active after the app was backgrounded for ≥5 s, so edits made in Obsidian on iOS show up without the close-and-reopen workaround.
  • Add pull-to-refresh on the main vault list as a deliberate, low-noise manual sync hook (no dedicated button on the home screen).

Addresses the issue surfaced on Reddit by u/wet_tank. Cloud Relay does not cover the iPhone → desktop direction (notify lives on the desktop), so the fix lives entirely on the iOS / bridge side.

Test plan

  • Go bridge unit tests (rescan_migration_test.go): default = 60, legacy 3600 → migrated to 60, custom 120 preserved.
  • Swift unit tests (ForegroundRescanDebounceTests): predicate fires above threshold, skips below, handles nil, fires at exact boundary.
  • Full Go bridge suite green.
  • Full iOS test suite (36 tests) green.
  • Manual: pull-to-refresh gesture on main vault list triggers a sync.
  • Manual: background app >5 s → return → auto-rescan fires. Background <5 s → return → no rescan.
  • Manual: existing on-device install with at least one vault → after this build, that vault's RescanIntervalS is 60.

This PR improves iPhone → desktop sync responsiveness by reducing fallback rescan latency, adding a foreground rescan when the app returns from background, and exposing a manual pull-to-refresh.

User-visible sync behavior

  • New folders now use a 60 s rescan interval (Syncthing default) instead of 3600 s.
  • Existing folders with the legacy 3600 s value are migrated to 60 s at bridge startup; any user-customized rescan intervals are preserved.
  • When the app returns to .active after being backgrounded for ≥5 s, the app triggers an immediate foreground rescan so edits made in Obsidian on iOS surface on desktop without restarting VaultSync.
  • Main vault list gains pull-to-refresh as a manual sync trigger.

Implementation highlights

  • Go bridge: introduced defaultRescanIntervalS = 60, updated AddFolder and AcceptPendingFolder to use it, and added a startup migration in StartSyncthing that replaces legacy 3600 s values with 60 s while preserving custom values.
  • iOS app: track lastBackgroundedAt in VaultSyncApp; added BackgroundSyncService.shouldRescanOnForeground(now:lastBackgroundedAt:threshold:), an async SyncthingManager.performForegroundSync(folderID:) wrapper, and a .refreshable pull-to-refresh on the main vault list.
  • Documentation: bump to v1.3.0 and list these changes in README/CHANGELOG.

Privacy / security impact

  • No new network endpoints or background upload behavior introduced. The changes only alter rescan timing and foreground-triggered syncs; user data handling and Syncthing security model are unchanged.

Background execution impact

  • Foreground rescan is triggered only when returning to .active after a threshold and is gated by existing bridge-running/sync-in-progress checks. Existing background assertion and continued-processing behavior remain unchanged.

Test coverage and outstanding verification

  • Added Go tests (3) covering default rescan assignment, legacy-3600→60 migration, and preservation of custom intervals.
  • Added Swift tests (4) for the foreground-rescan debounce predicate (nil, below/above/equal-threshold cases).
  • CI: unit test suites for Go and Swift are green.
  • Manual verification still pending for: pull-to-refresh UX, background-duration-triggered rescan in real-world usage, and migration behavior on existing user installs.

Closes #4.

Review Change Stack

- Lower RescanIntervalS default from 3600 to 60 (matches Syncthing's own
  default) and migrate existing folders that still carry the legacy value.
- Auto-trigger a foreground rescan when the app returns to .active after
  being backgrounded ≥5 seconds — covers cases where the iOS sandbox
  prevents FSWatcher from seeing Obsidian edits.
- Add pull-to-refresh on the main vault list for an explicit user-initiated
  sync without adding a dedicated button to the home screen.

Closes #4
@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: aa042769-4d49-4697-9b2e-520696ab0b51

📥 Commits

Reviewing files that changed from the base of the PR and between ec6eadf and 33de8e5.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • README.md
  • ios/project.yml
✅ Files skipped from review due to trivial changes (3)
  • CHANGELOG.md
  • ios/project.yml
  • README.md
📜 Recent review details
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Go Tests

📝 Walkthrough

Walkthrough

This PR addresses unresponsive sync when editing in third-party apps (e.g., Obsidian) on iOS by reducing the fallback rescan interval from 60 minutes to 60 seconds and triggering responsive foreground rescans. The Go bridge normalizes folder configurations and adds a startup migration, while iOS detects background transitions and optionally rescans with a 5-second debounce threshold.

Changes

Go Bridge Rescan Interval Normalization

Layer / File(s) Summary
Rescan Interval Constant
go/bridge/folders.go
Introduces defaultRescanIntervalS = 60 as the fallback interval for new folder configurations.
Folder Configuration Updates
go/bridge/folders.go, go/bridge/pendingfolders.go
AddFolder and AcceptPendingFolder use the default constant instead of hardcoded 3600 when creating new Syncthing folders.
Startup Migration Logic
go/bridge/syncthing.go
StartSyncthing iterates configured folders and migrates legacy 3600 intervals to defaultRescanIntervalS, preserving user-customized values.
Go Bridge Tests
go/bridge/rescan_migration_test.go
Three tests verify default interval on new folder creation, legacy interval migration, and preservation of custom intervals across restart.

iOS Foreground Sync Trigger

Layer / File(s) Summary
Foreground Rescan Debounce Predicate
ios/VaultSync/Services/BackgroundSyncService.swift
Pure static helper shouldRescanOnForeground evaluates whether elapsed background time meets the rescan threshold.
App State and Scene Lifecycle
ios/VaultSync/App/VaultSyncApp.swift
Tracks lastBackgroundedAt on background entry; on foreground, checks debounce predicate and conditionally triggers triggerForegroundSync() with a 5-second threshold.
Async Foreground Sync API
ios/VaultSync/Services/SyncthingManager.swift
Adds performForegroundSync() async method for awaitable callers; existing triggerForegroundSync() remains fire-and-forget.
Pull-to-Refresh UI
ios/VaultSync/Views/ContentView.swift
Main list now has .refreshable modifier to allow user-initiated foreground sync.
iOS Foreground Debounce Tests
ios/VaultSyncTests/ForegroundRescanDebounceTests.swift
Four tests verify debounce predicate behavior: nil handling, above/below threshold, and exact boundary conditions.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

📱 From background dreams to foreground screens,
A gentle nudge when sync convenes—
Five seconds rest, then files align,
No more the 60-minute climb.
Fast folders, fast hearts, a quicker shine. ✨

🚥 Pre-merge checks | ✅ 7
✅ Passed checks (7 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title uses conventional-commit style (feat:) and clearly summarizes the main objective of improving sync responsiveness for v1.3.0.
Linked Issues check ✅ Passed All coding requirements from issue #4 are met: Go bridge rescan interval changes (default 60s, migration, preservation), iOS app background tracking, foreground rescan trigger, async wrapper, and pull-to-refresh are all implemented with corresponding tests.
Out of Scope Changes check ✅ Passed All changes directly address the stated objectives (rescan interval, foreground sync, pull-to-refresh). Version bumps and changelog/readme updates are expected release activities, not out-of-scope.
No Private Note Leakage ✅ Passed Code does not leak sensitive data. No vault paths, note contents, or secrets logged. FolderID marked private. No network calls or crash reporting added.
Bounded Ios Background Work ✅ Passed iOS background work is properly bounded (max 3s), expiration-aware with registered handlers, respects privacy (no vault data in logs), and manages assertions correctly.
Bridge Contract Compatibility ✅ Passed Gomobile types, JSON shapes, and empty-string conventions preserved. No exported function signatures changed. Internal constant added. Tests added verify behavior without breaking contract.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sync-responsiveness

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

@psimaker psimaker changed the title feat: improve iPhone → desktop sync responsiveness feat: improve sync responsiveness (v1.3.0) May 11, 2026
@psimaker
psimaker merged commit 841f639 into main May 11, 2026
6 checks passed
@psimaker
psimaker deleted the feat/sync-responsiveness branch May 11, 2026 11:18
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.

Improve sync responsiveness when editing on iPhone

1 participant