Skip to content

fix: rebase folder paths on launch + guided vault removal (#25) - #32

Merged
psimaker merged 4 commits into
mainfrom
fix/issue-25-folder-path-resilience
Jun 1, 2026
Merged

fix: rebase folder paths on launch + guided vault removal (#25)#32
psimaker merged 4 commits into
mainfrom
fix/issue-25-folder-path-resilience

Conversation

@psimaker

@psimaker psimaker commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Why

iOS does not keep an app's data-container path stable across reinstall, restore, or device migration, but Syncthing
persists absolute folder paths in its config. A stale path left a vault permanently stuck on "VaultSync cannot access
this folder" with no in-app way to recover (#25).

What

  • Launch-time path reconcile (FolderPathReconciler): on every engine start (foreground + both background start
    paths) each folder's path is re-derived from the bookmark-resolved Obsidian root + a per-folder relative-path sidecar.
    A change to the app's own storage is repaired automatically; the per-folder mapping is recorded at accept time and
    backfilled for healthy folders on first launch.
  • SetFolderPath (Go bridge): rewrites a folder's path in place, preserving devices, label, .stignore, and the
    index DB — a Path change is restart-only in lib/model and never drops the DB, so there is no re-hash and no
    re-download. It refuses to rebase unless the target holds this folder's own marker fingerprint
    (.stfolder/syncthing-folder-<hash(folderID)>.txt), so a folder can never be pointed at an empty or foreign directory
    (which a send-receive folder would treat as "all files deleted" and propagate to peers).
  • Guided recovery UI: folders that can't be reached surface a "Needs Attention" card and a per-vault Remove
    Vault
    action (plus Reconnect to Obsidian where the folder maps to it) instead of an inert error.
  • EnsureDefaultIgnores (Go bridge): moves the default-ignore read-merge-write into Go and aborts on a read error
    instead of overwriting a populated .stignore with just the defaults.
  • New user-facing strings localized in English, German, Spanish, and Simplified Chinese.

Honest scope

A change to the app's own storage heals with zero user action. After a device restore or migration the Obsidian
security-scoped bookmark often has to be re-granted once; that single reconnect then rebases every vault onto its
existing data (index-preserving). This is a platform limit — the cross-app bookmark is a non-programmatic single point
of failure — and it still goes further than other iOS Syncthing clients, which require a full re-sync.

Testing

  • New Go bridge tests: SetFolderPath (in-place rebase preserves devices; empty-dir, foreign-marker, and non-existent
    targets refused; no-op when unchanged) and EnsureDefaultIgnores (adds only missing patterns, preserves custom
    order, idempotent, aborts on read error). Full Go bridge suite green.
  • 11 FolderPathReconciler unit tests (rebase / backfill / no-op / unmappable).
  • Full iOS suite green (68 tests / 13 suites).

Fixes #25.

Overview

This PR fixes issue #25 by making Syncthing folder paths resilient to iOS container path changes that occur during app reinstall, restore, or device migration. Previously, when iOS relocated an app's sandboxed storage, vaults would become stuck with a "VaultSync cannot access this folder" error with no in-app recovery path.

User-Visible Changes

Path Reconciliation & Recovery

  • Folder paths are automatically re-derived from the current Obsidian bookmark root plus a stored relative-path sidecar on every engine start (foreground return, background sync, post-onboarding)
  • Unreachable folders now display a "Needs Attention" card in the Sync tab with per-vault actions to either "Reconnect to Obsidian" (if mapped) or "Remove This Vault"
  • Users can now remove inaccessible vaults from the app without manual workarounds

Sync Filter Handling

  • Default .stignore patterns are now read, merged, and written atomically; read errors prevent accidental overwrites of existing filters

Localization

  • New UI text added in English, German, Spanish, and Simplified Chinese for vault removal and recovery flows

Sync Behavior Impact

  • Path reconciliation runs transparently before sync operations; no user action required for recovery in most cases
  • Folder device pairings, labels, ignore rules, and block indices are preserved during path rebase; no re-hashing or re-download needed
  • Path updates require the target directory to contain a Syncthing folder marker (.stfolder/syncthing-folder-<hash(folderID)>.txt) to prevent rebasing onto wrong/empty directories

Background Execution

  • Path reconciliation occurs during background sync, ensuring stale paths are corrected before sync proceeds
  • Forced Syncthing restarts during background operations trigger path healing before local rescans

Test Coverage

  • 11 new unit tests for FolderPathReconciler covering rebase, backfill, and sidecar persistence scenarios
  • Go bridge tests for SetFolderPath (marker verification, device preservation, edge cases) and EnsureDefaultIgnores (merge logic, permission errors)
  • Full test suite passes: 68 iOS tests across 13 suites, full Go bridge suite green

Technical Implementation

  • FolderPathReconciler: Pure-logic reconciliation (testable without I/O) plus live bridge-driven variant for app startup paths
  • Go SetFolderPath: Validates folder existence, verifies target marker, updates config in-place
  • Go EnsureDefaultIgnores: Reads existing filters, merges defaults preserving order, aborts on read error
  • SyncBridgeService & SyncthingManager: Expose bridge functions and manage reconciliation lifecycle across app foreground/background states
  • VaultManager: Records per-folder relative paths at vault accept time; backfilled for healthy folders on first launch

psimaker added 2 commits June 1, 2026 08:51
iOS does not keep an app's data-container path stable across reinstall,
restore, or migration, but Syncthing persists absolute folder paths.
A stale path left a vault stuck on "VaultSync cannot access this folder"
with no way to recover (issue #25).

Go bridge:
- SetFolderPath rewrites a folder's path in place, preserving its
  devices, label, .stignore, and index DB (the DB is keyed by folder ID,
  so the folder restarts at the new path without re-hashing or
  re-downloading). Refuses a non-existent target so a folder is never
  pointed at an empty directory.
- EnsureDefaultIgnores moves the default-ignore read-merge-write into Go
  and aborts on a read error instead of overwriting a populated
  .stignore with just the defaults.

iOS:
- FolderPathReconciler re-derives every folder's path from the
  bookmark-resolved Obsidian root on each engine start (foreground and
  the two background start paths), silently rebasing a moved container.
  A per-folder relative-path sidecar is recorded at accept time and
  backfilled for already-healthy folders on first launch.
- Folders that genuinely can't be reached surface a calm "Needs
  Attention" card and a per-vault "Remove Vault" action (with
  "Reconnect to Obsidian" where applicable) instead of an inert error.
- New strings localized in English, German, Spanish, and Simplified
  Chinese.

Tests: new Go bridge tests (SetFolderPath, EnsureDefaultIgnores incl.
the read-error/no-overwrite guard) and 11 FolderPathReconciler tests;
full iOS suite (68 tests) and the Go bridge suite are green.
Defense in depth from the #25 architecture review. A rebase now verifies
the target directory holds THIS folder's Syncthing marker
(.stfolder/syncthing-folder-<hash(folderID)>.txt) instead of only
checking that a directory exists. The fingerprint file proves the target
was this very folder's root, so a folder can never be pointed at an empty
or foreign directory — which a send-receive folder would otherwise treat
as "all files deleted" and propagate to peers.

Also makes the CHANGELOG narrative honest: a change to the app's own
storage heals automatically, but a device restore/migration needs a
one-time Obsidian-folder reconnect (index-preserving, no re-download).

Go bridge tests extended (empty dir and foreign-marker dir refused;
valid rebase requires the marker). Full Go bridge suite and the iOS
suite (68 tests) are green.
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@psimaker, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 23 minutes and 32 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

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.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 84d28ea8-359b-45ed-91d2-76b8a5374462

📥 Commits

Reviewing files that changed from the base of the PR and between c4fa251 and f73f88c.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • README.md
  • ios/VaultSync/Services/FolderPathReconciler.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSyncTests/FolderPathReconcilerTests.swift
  • ios/project.yml
📝 Walkthrough

Walkthrough

This PR enables automatic folder-path recovery when iOS sandbox containers relocate (after reinstall, backup restore, or migration). A Go bridge layer (SetFolderPath, EnsureDefaultIgnores) provides safe rebasing with marker verification and atomic .stignore merging. Swift reconciler persists per-folder relative paths and rebases them on container changes. The iOS app triggers reconciliation at foreground/launch/background-sync entry points, surfaces unreachable folders in UI with recovery or removal options, and delegates ignore handling to the bridge.

Changes

Folder Path Resilience Recovery

Layer / File(s) Summary
Go bridge: safe path updates and ignore merging
CHANGELOG.md, go/bridge/folders.go, go/bridge/folderstatus.go, go/bridge/folders_test.go
Adds SetFolderPath with SHA-256 marker fingerprinting to prevent destructive rebases onto wrong directories. Adds EnsureDefaultIgnores to merge default patterns into .stignore atomically, aborting if read fails to avoid overwriting. Tests validate marker verification, folder validation, merge idempotence, and non-overwrite behavior on permission errors.
Swift path reconciliation core
ios/VaultSync/Services/FolderPathReconciler.swift, ios/VaultSyncTests/FolderPathReconcilerTests.swift
Implements relative-path sidecar persistence in UserDefaults, canonic path normalization, and pure reconcile(folders:env:) logic that rebases mapped folders to resolved Obsidian root locations, backfills unmapped folders under the root, and leaves unmapped external folders untouched. Live reconcileLive mode fetches folder list, constructs FileManager-aware environment, and applies bridge path updates. Comprehensive test suite validates rebasing, backfilling, no-ops, and persistence round-trips.
SyncBridgeService wrappers
ios/VaultSync/Services/SyncBridgeService.swift
Exposes setFolderPath and ensureDefaultIgnores as Swift-friendly optionals wrapping Go bridge calls.
SyncthingManager reconciliation and unreachable detection
ios/VaultSync/Services/SyncthingManager.swift
Adds reconcileFolderPaths(obsidianRoot:) to trigger live reconciliation with folder-list refresh. Introduces UnreachableFolder struct and unreachableFolders property that filter errored folders to path-related failure reasons and expose error paths and recovery hints. Updates unresolvedIssues to exclude unreachable folders when reporting generic folder errors. Delegates applyDefaultIgnoresIfNeeded to bridge API. Removes stored relative mappings when folders are deleted.
iOS app reconciliation entry points
ios/VaultSync/App/VaultSyncApp.swift, ios/VaultSync/Services/BackgroundSyncService.swift, ios/VaultSync/Services/VaultManager.swift
Calls reconcileFolderPaths after Syncthing restart on app foreground and after onboarding completion. Reconciles paths in background sync before and after forced restarts. Records relative-path sidecars when accepting pending shares to enable future rebases.
ContentView unreachable vaults and removal UI
ios/VaultSync/Views/ContentView.swift
Adds "Unreachable Vaults" section in Sync tab showing folders with path errors; provides context-sensitive actions (reconnect or remove). Implements vault-removal confirmation dialog with error reporting. Replaces symlink-based path canonicalization with FolderPathReconciler.canonical(). Reconciles folder paths when Obsidian container access is granted. Hides remove action from expanded directory rows. Skips recommendation nudge for unreachable folders.
Localization strings: removal and path resilience
ios/VaultSync/en.lproj/Localizable.strings, ios/VaultSync/de.lproj/Localizable.strings, ios/VaultSync/es.lproj/Localizable.strings, ios/VaultSync/zh-Hans.lproj/Localizable.strings
Adds user-facing text for vault removal flow and messaging clarifying that removing stops sync only on the current iPhone without affecting notes on other devices. Provided in four languages.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • psimaker/vaultsync#2: Adds guarded .stignore read/merge/write to prevent overwriting when reads fail, complementing this PR's bridge-based safer default ignore merge via EnsureDefaultIgnores.
  • psimaker/vaultsync#5: Modifies app foreground and sync-trigger flows in VaultSyncApp, SyncthingManager, BackgroundSyncService, and ContentView with rescan debounce/pull-to-refresh, overlapping entry points where this PR adds reconciliation calls.

Poem

📍 When containers drift and paths grow stale,
A sidecar saves the day—mark, map, and sail.
SHA-256 fingerprints ensure you land just right,
While .stignore merges happen safe and tight.
Unreachable vaults now show a gentle way,
Reconnect or clear—your choice, your say. 🔄

🚥 Pre-merge checks | ✅ 5 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
No Private Note Leakage ⚠️ Warning FolderPathReconciler logs folder.id as .public (lines 129,133,135) and VaultManager logs folder.id without privacy markers (lines 198,224), exposing vault IDs in device logs. Mark folder.id as private in FolderPathReconciler (lines 129,133,135) and VaultManager (lines 198,224) logging statements per privacy guidelines.
Bounded Ios Background Work ⚠️ Warning Folder IDs logged as public (lines 129-135) leak vault context. UserDefaults mutations unsynchronized across foreground/background paths risking data loss. Use privacy: .private for folder IDs. Protect sidecar read-modify-write with OSAllocatedUnfairLock or atomic API.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed PR comprehensively addresses #25 objectives: implements SetFolderPath bridge function with marker verification (#25), FolderPathReconciler for automatic path reconciliation across iOS container changes (#25), EnsureDefaultIgnores to safely merge defaults without data loss (#25), and guided removal UI with localization (#25).
Out of Scope Changes check ✅ Passed All changes are scoped to #25: bridge functions for path management and ignore merging, iOS path reconciliation service, UI for unreachable vaults and removal flows, and supporting tests. No unrelated refactoring or feature creep detected.
Bridge Contract Compatibility ✅ Passed SetFolderPath and EnsureDefaultIgnores use gomobile-compatible types, maintain empty-string success convention, encode JSON consistently, and have comprehensive test coverage.
Title check ✅ Passed The title uses conventional-commit style (fix:) and clearly summarizes the two main changes: folder-path rebasing on launch and guided vault removal, with the issue reference.

✏️ 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 fix/issue-25-folder-path-resilience

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.

@psimaker psimaker changed the title Fix #25: resilient folder paths (launch-time rebase) + guided vault removal fix: rebase folder paths on launch + guided vault removal (#25) Jun 1, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
ios/VaultSync/Services/SyncthingManager.swift (1)

342-358: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Filtering the issue card is not enough if the remediation still rescans unreachable folders.

This hides unreachable folders from the generic issue count, but the Sync Issues action still calls rescanFailedVaults(), which uses folderIDsWithErrors and therefore includes the same unreachable IDs. When reachable and unreachable errors coexist, the “rescan failed vaults” path still sends users through the no-op recovery this change is trying to remove.

As per coding guidelines, **/*: VaultSync syncs private Obsidian notes through Syncthing. Treat data loss, privacy leaks, security regressions, and broken sync behavior as high priority.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ios/VaultSync/Services/SyncthingManager.swift` around lines 342 - 358, The
issue: unreachable folder IDs are filtered out of the SyncIssueItem list but the
remediation still calls rescanFailedVaults() which uses folderIDsWithErrors and
will include unreachable IDs; update the remediation path so
rescanFailedVaults() is invoked only with the filtered erroredFolderIDs (or
ensure rescanFailedVaults ignores unreachableFolders). Concretely, when creating
the SyncIssueItem for .folderErrors (the SyncIssueItem construction that
references folderIDsWithErrors and folderID: erroredFolderIDs.first), pass
erroredFolderIDs into the rescan action handler (or alter rescanFailedVaults()
to accept an explicit [FolderID] parameter and skip unreachableIDs) so the
rescan excludes unreachableFolders and the remediation becomes a no-op only for
truly unreachable entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ios/VaultSync/Services/FolderPathReconciler.swift`:
- Around line 129-135: The log statements in FolderPathReconciler (around the
loop calling env.setPath) currently mark folder.id as .public; remove any
exposure of the folder identifier from logs by omitting folder.id from the
interpolated messages or marking it private (e.g., do not use ", privacy:
.public"); update the logger.warning and logger.info calls in the block that
handles "Skip rebase", "Rebase failed", and the successful rebase to either
redact the identifier or log a generic message referencing only non-sensitive
context (keep references to env.setPath, folder.id, and the surrounding rebase
logic to locate the spots to change).
- Around line 34-56: The UserDefaults read-modify-write in loadRel/saveRel and
the mutating APIs setRel, removeRel, and reconcile are not synchronized and can
race between foreground/background threads; wrap all access to the rel sidecar
behind a single-serialized execution context (e.g., a dedicated actor or a
serial DispatchQueue) or expose an atomic update API that performs
load-modify-save as one synchronous step. Concretely, create a
FolderPathReconcilerStorage actor or serial queue responsible for loadRel(),
saveRel(_:), setRel(_:forFolder:), removeRel(forFolder:), and reconcile(...) and
move the UserDefaults reads/writes into that serialized context so callers
always await/dispatch into it, ensuring no concurrent access to relStoreKey or
the dictionary map.

In `@ios/VaultSync/Services/SyncthingManager.swift`:
- Around line 497-506: reconcileFolderPaths is currently using Task { } so
waitForFoldersForSyncRequest and refreshFolders run on the `@MainActor`; change
the task to run off the main actor (use Task.detached) so polling/bridge work
(waitForFoldersForSyncRequest and the FolderPathReconciler call) happens off the
UI thread, and only invoke refreshFolders back on the main actor via
MainActor.run when UI/actor-safety is required; locate reconcileFolderPaths and
update the Task usage, keeping FolderPathReconciler.reconcileLive and
waitForFoldersForSyncRequest logic intact and wrapping any refreshFolders call
in MainActor.run.
- Around line 718-724: applyDefaultIgnoresIfNeeded is running
SyncBridgeService.ensureDefaultIgnores on the MainActor which can block the UI;
change call sites so the bridge work runs off-main (invoke
SyncBridgeService.ensureDefaultIgnores from Task.detached or an async
non-@MainActor context), make ensureDefaultIgnores/BridgeEnsureDefaultIgnores
cancellation-aware (check Task.isCancelled or use withTaskCancellationHandler)
and only hop back to `@MainActor` for any state updates or logging that must run
on the UI actor (e.g., update UI state or call logger.warning on the main
actor).

---

Outside diff comments:
In `@ios/VaultSync/Services/SyncthingManager.swift`:
- Around line 342-358: The issue: unreachable folder IDs are filtered out of the
SyncIssueItem list but the remediation still calls rescanFailedVaults() which
uses folderIDsWithErrors and will include unreachable IDs; update the
remediation path so rescanFailedVaults() is invoked only with the filtered
erroredFolderIDs (or ensure rescanFailedVaults ignores unreachableFolders).
Concretely, when creating the SyncIssueItem for .folderErrors (the SyncIssueItem
construction that references folderIDsWithErrors and folderID:
erroredFolderIDs.first), pass erroredFolderIDs into the rescan action handler
(or alter rescanFailedVaults() to accept an explicit [FolderID] parameter and
skip unreachableIDs) so the rescan excludes unreachableFolders and the
remediation becomes a no-op only for truly unreachable entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: aad6c6b1-a22c-4f06-b9fe-aa52cfb107ea

📥 Commits

Reviewing files that changed from the base of the PR and between feb1694 and c4fa251.

📒 Files selected for processing (16)
  • CHANGELOG.md
  • go/bridge/folders.go
  • go/bridge/folders_test.go
  • go/bridge/folderstatus.go
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/Services/FolderPathReconciler.swift
  • ios/VaultSync/Services/SyncBridgeService.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/Services/VaultManager.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/de.lproj/Localizable.strings
  • ios/VaultSync/en.lproj/Localizable.strings
  • ios/VaultSync/es.lproj/Localizable.strings
  • ios/VaultSync/zh-Hans.lproj/Localizable.strings
  • ios/VaultSyncTests/FolderPathReconcilerTests.swift
📜 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
🧰 Additional context used
📓 Path-based instructions (7)
**/*.swift

📄 CodeRabbit inference engine (Custom checks)

For Swift background execution changes, pass if work is bounded, cancellation-aware, handles expiration callbacks, and records errors without leaking private vault data. Fail only when background work can continue unbounded, miss cleanup, or violate iOS background execution constraints.

Files:

  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Services/VaultManager.swift
  • ios/VaultSync/Services/SyncBridgeService.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/Services/FolderPathReconciler.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSyncTests/FolderPathReconcilerTests.swift
  • ios/VaultSync/Views/ContentView.swift
ios/**/*.swift

📄 CodeRabbit inference engine (README.md)

ios/**/*.swift: Follow Swift API Design Guidelines for Swift code
Use Swift strict concurrency where applicable
Use SwiftUI for iOS user interface development
Use BGAppRefreshTask and BGContinuedProcessingTask for background execution on iOS 18+
Implement APNs silent notifications via Cloud Relay for push wake-ups

Files:

  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Services/VaultManager.swift
  • ios/VaultSync/Services/SyncBridgeService.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/Services/FolderPathReconciler.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSyncTests/FolderPathReconcilerTests.swift
  • ios/VaultSync/Views/ContentView.swift

⚙️ CodeRabbit configuration file

ios/**/*.swift: Focus on Swift 6 strict concurrency, Sendable/MainActor correctness, Task cancellation,
retain cycles, memory pressure, SwiftUI observation state, StoreKit/APNs flows, and iOS background execution limits.
Pay special attention to BGAppRefreshTask and BGContinuedProcessingTask behavior, expiration handling,
bounded work, and cleanup when the app is suspended or terminated.

Files:

  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Services/VaultManager.swift
  • ios/VaultSync/Services/SyncBridgeService.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/Services/FolderPathReconciler.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSyncTests/FolderPathReconcilerTests.swift
  • ios/VaultSync/Views/ContentView.swift
**/*

⚙️ CodeRabbit configuration file

**/*: VaultSync syncs private Obsidian notes through Syncthing. Treat data loss,
privacy leaks, security regressions, and broken sync behavior as high priority.
Do not nitpick formatting unless it affects maintainability, correctness, or public API clarity.
Flag any accidental logging, telemetry, crash reporting, or network transfer of note contents,
vault paths, filenames with private context, API keys, APNs tokens, relay keys, or security-scoped bookmark data.

Files:

  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/es.lproj/Localizable.strings
  • ios/VaultSync/de.lproj/Localizable.strings
  • CHANGELOG.md
  • go/bridge/folderstatus.go
  • ios/VaultSync/Services/VaultManager.swift
  • ios/VaultSync/Services/SyncBridgeService.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/zh-Hans.lproj/Localizable.strings
  • ios/VaultSync/Services/FolderPathReconciler.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • go/bridge/folders.go
  • ios/VaultSyncTests/FolderPathReconcilerTests.swift
  • ios/VaultSync/en.lproj/Localizable.strings
  • ios/VaultSync/Views/ContentView.swift
  • go/bridge/folders_test.go
**/*.md

⚙️ CodeRabbit configuration file

**/*.md: Review public documentation for technical accuracy, privacy/security claims, App Store-facing wording,
setup correctness, and consistency with the free app plus optional Cloud Relay subscription model.

Files:

  • CHANGELOG.md
{go,notify}/**/*.go

📄 CodeRabbit inference engine (README.md)

Follow standard Go conventions for Go code

Files:

  • go/bridge/folderstatus.go
  • go/bridge/folders.go
  • go/bridge/folders_test.go
go/**/*.go

📄 CodeRabbit inference engine (README.md)

Embed Syncthing 2.x engine via Go/gomobile as .xcframework in iOS builds

Files:

  • go/bridge/folderstatus.go
  • go/bridge/folders.go
  • go/bridge/folders_test.go
go/bridge/**/*.go

⚙️ CodeRabbit configuration file

go/bridge/**/*.go: This code crosses the gomobile Swift-Go boundary. Verify exported signatures use only gomobile-safe primitive types,
preserve the JSON string contract, keep empty-string success conventions intact, and avoid breaking Swift decoding tests.
Review Syncthing lifecycle, locking, error strings, and noassets build assumptions carefully.

Files:

  • go/bridge/folderstatus.go
  • go/bridge/folders.go
  • go/bridge/folders_test.go
🔇 Additional comments (5)
ios/VaultSync/de.lproj/Localizable.strings (1)

576-585: LGTM!

ios/VaultSync/en.lproj/Localizable.strings (1)

576-585: LGTM!

ios/VaultSync/es.lproj/Localizable.strings (1)

576-585: LGTM!

ios/VaultSync/zh-Hans.lproj/Localizable.strings (1)

576-585: LGTM!

go/bridge/folders.go (1)

194-218: ⚡ Quick win

Fix review: marker fingerprint check matches Syncthing’s default .stfolder contract — Syncthing’s default .stfolder includes a deterministic syncthing-folder-<hash>.txt fingerprint derived from sha256(folderID) truncated to the first 3 bytes, and for custom Marker Name it only requires the marker entry to exist (it isn’t created automatically). The current verifyFolderMarker logic (including the h[:3] filename) and the unit tests’ writeFolderMarker helper align with that behavior.

Comment thread ios/VaultSync/Services/FolderPathReconciler.swift Outdated
Comment thread ios/VaultSync/Services/FolderPathReconciler.swift Outdated
Comment thread ios/VaultSync/Services/SyncthingManager.swift
Comment thread ios/VaultSync/Services/SyncthingManager.swift Outdated
psimaker added 2 commits June 1, 2026 10:04
- FolderPathReconciler: serialize the rel sidecar behind a serial queue
  and have reconcile write per-folder atomically (recordRel) from a read
  snapshot instead of a bulk overwrite, so concurrent foreground and
  background reconciles can't drop a mapping. Stop logging folder IDs as
  public.
- SyncthingManager: run reconcileFolderPaths' folder-wait + reconcile off
  the main actor (Task.detached), hopping back only for refreshFolders;
  make applyDefaultIgnoresIfNeeded nonisolated and dispatch its three call
  sites off-main so the .stignore read-merge-write never blocks the UI.
- ContentView: "rescan failed vaults" now skips unreachable folders (a
  rescan cannot fix a stale path).

Full iOS suite (68 tests) green.
Mark the #25 folder-path resilience and Sync Filters hardening as the
1.5.1 release in the changelog, lead the README "What's New" with the
1.5.1 fix, and bump CFBundleShortVersionString/CFBundleVersion in
project.yml for the app and widget targets.
@psimaker
psimaker merged commit 8cb5fb8 into main Jun 1, 2026
12 checks passed
@psimaker
psimaker deleted the fix/issue-25-folder-path-resilience branch June 1, 2026 08:36
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.

VaultSync cannot access this folder persistent Sync Error

1 participant