Skip to content

fix(ios): consent dialogs with visible Cancel on iOS 26; localized marker-missing folder error (#64, #65)#72

Merged
psimaker merged 3 commits into
mainfrom
fix/ui-audit-release-blockers
Jul 7, 2026
Merged

fix(ios): consent dialogs with visible Cancel on iOS 26; localized marker-missing folder error (#64, #65)#72
psimaker merged 3 commits into
mainfrom
fix/ui-audit-release-blockers

Conversation

@psimaker

@psimaker psimaker commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Fixes #64, fixes #65 — the two release-blocking findings from the 1.8.0 pre-release UI audit; ships in the pending 1.8.0 build.

Verification: full Xcode test plan green (211 passed / 0 failed, iPhone 17 Pro, iOS 26.5), design-token lint green, Localizable key counts 4x621, no
real duplicate keys. No go/ changes — no xcframework rebuild required by this PR.

Follow-up (deliberately not here): a UI-test target for automated dialog-rendering regression tests; remaining audit polish stays in #71.

This change fixes two iOS release blockers.

It switches the merge, vault-removal, device-removal, and conflict-resolution consent flows from .confirmationDialog to .alert, so Cancel and destructive actions remain visible on iOS 18/26. It also adds DEBUG-only UI audit fixtures to preseed those screens for screenshot verification without affecting release builds.

For sync errors, raw Syncthing “folder marker missing” failures are now mapped to a localized “Vault Folder Was Moved or Deleted” message in English, German, Spanish, and Simplified Chinese. The app keeps the raw engine text in technical details, removes the rescan CTA when rescan can’t help, and links users to manual recovery steps.

User-visible sync behavior and recovery guidance were updated accordingly. Privacy/security impact is limited to clearer handling of potentially unsafe folder state; background execution behavior is unchanged outside DEBUG-only guards. Coverage was added with new tests for the error mapping and rescan-CTA availability, alongside the existing Xcode test plan verification.

psimaker added 3 commits July 7, 2026 20:10
…uidance (#65)

When a vault's folder was moved, renamed, replaced, or deleted outside
the app, Syncthing's raw English "folder marker missing (this indicates
potential data loss, …)" text surfaced verbatim as the user-facing
message in every locale: the bridge classifies it as "unknown_error",
so SyncUserError.fromFolderStatus fell through to the default branch,
which passes the engine message straight through. The paired guidance
("…then retry" and the "Rescan Failed Vaults" button) could not help:
Syncthing refuses to scan a folder without its marker, by design.

What could go wrong and why this is safe:
- The new mapping is match-only presentation: no path is touched,
  nothing is re-pointed, re-accepted, or rescanned automatically
  (doctrine 002; safety rule 3 untouched).
- Matched on the message ("marker missing") before the reason switch in
  fromFolderStatus and before the generic classifiers in
  from(rawMessage:), so both surfaces (folder status row, failed-rescan
  error) map identically; every other error keeps its existing mapping
  (regression-tested).
- The rescan CTA hides only when every errored folder is a
  marker-missing case — any other folder error keeps the button.
- The raw engine text stays available in technicalDetails.

Strings added to all four .lproj files; troubleshooting.md gained the
linked "Vault Folder Was Moved or Deleted" section; regression tests
in SyncUserErrorTests reference #65.

Fixes #65.
…ble Cancel (#64)

On iOS 26, .confirmationDialog renders as a centered popover whose
cancel-role button is not rendered at all — dismissal only by tapping
outside. The merge-consent dialog (the #54 consent gate) and the
vault-removal dialog therefore showed the destructive action as their
only visible button, funneling users toward exactly the action those
gates exist to slow down. iOS 18 rendered the same code as an action
sheet with a visible Cancel. Recorded as decision 011.

What could go wrong and why this is safe:
- Pure presentation change: titles, consequence texts, button labels,
  roles, and the confirm handlers are unchanged; no accept logic and
  no localization keys touched.
- .alert renders title, full message, destructive action, and Cancel
  on every supported OS version — verified empirically with
  before/after screenshots on iOS 26.5 and iOS 18.0 simulators.
- The DEBUG-only UIAuditFixture launch-argument harness added here
  seeds the dialog/error states for that evidence; it is compiled out
  of release builds and skips all engine management while active (same
  reasoning as TestHost), so it can never affect shipping behaviour.

The two remaining .confirmationDialog instances (device removal,
conflict resolve) are converted in the follow-up commit per decision
011.

Fixes #64.
…olve consent (#64)

Completes the class-wide cleanup behind decision 011: on iOS 26 a
.confirmationDialog renders as a centered popover without its
cancel-role button. The device-removal dialog additionally declared no
Cancel button at all, so on iOS 26 it offered destructive "Remove" as
its only visible choice; the conflict-resolve dialog discards a version
of a note on two of its three actions.

What could go wrong and why this is safe:
- Presentation-only: titles, messages, button labels, roles, and
  handlers are unchanged — except that device removal gains the
  explicit Cancel it always should have had (key exists in all four
  locales). keep-both keeps its non-destructive role.
- Verified empirically via the UIAuditFixture harness: after-screenshots
  on iOS 26.5 show destructive action and Cancel rendered for both
  dialogs.

Refs #64 / decision 011. The remaining low-priority UI-audit findings
stay bundled in #71.
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Consent-related SwiftUI dialogs in ContentView, DeviceDetailView, and ConflictDiffView are converted from confirmationDialog to alert to ensure Cancel is always visible (iOS 26 fix). A new folderMarkerMissing error category adds localized recovery guidance and gates the Rescan CTA. Debug-only UI audit fixtures seed these states for verification, plus supporting docs and tests.

Changes

Consent alert migration and folder marker missing handling

Layer / File(s) Summary
Decision record for alert-over-confirmationDialog policy
docs/decisions/011-consent-dialogs-alert-not-confirmation-dialog.md
Documents the mandate to use .alert instead of .confirmationDialog for consent/safety UI and lists converted call sites.
ContentView vault removal and merge consent alerts
ios/VaultSync/Views/ContentView.swift
Vault-removal and merge-confirmation flows switch from confirmationDialog to alert, preserving destructive actions and cancel behavior.
DeviceDetailView and ConflictDiffView consent alerts
ios/VaultSync/Views/DeviceDetailView.swift, ios/VaultSync/Views/ConflictDiffView.swift
Device-removal and conflict-resolution prompts switch from confirmationDialog to alert with explicit destructive and Cancel buttons.
Debug-only UI audit fixture infrastructure
ios/VaultSync/App/UIAuditFixture.swift, ios/VaultSync/App/VaultSyncApp.swift, ios/VaultSync/Views/ContentView.swift, ios/VaultSync/Views/ConflictDiffView.swift, ios/VaultSync/Views/DeviceDetailView.swift
Adds a fixture enum backed by UserDefaults, DEBUG-only lifecycle guards, and onAppear hooks that pre-seed consent alerts and a fixture detail sheet for simulator UI verification.
folderMarkerMissing error category and routing
ios/VaultSync/Models/SyncUserError.swift
Adds a new error category detected from raw messages and folder-status details, with localized title/message/remediation and a dedicated troubleshooting URL anchor.
Conditional rescan CTA availability
ios/VaultSync/Services/SyncthingManager.swift, ios/VaultSync/Views/SyncIssuesView.swift
Adds hasRescanableFolderErrors to exclude marker-missing failures from rescan eligibility, adjusts remediation text, and hides the Rescan button when it can't help.
Localized strings
ios/VaultSync/de.lproj/..., ios/VaultSync/en.lproj/..., ios/VaultSync/es.lproj/..., ios/VaultSync/zh-Hans.lproj/...
Adds four localized strings per locale describing the vault-folder-moved-or-deleted recovery scenario.
Troubleshooting guide and changelog
docs/troubleshooting.md, CHANGELOG.md
Adds a troubleshooting section/index entry and changelog fixes for the alert-cancel and folder-marker-missing issues.
Tests
ios/VaultSyncTests/SyncUserErrorTests.swift
Adds tests validating folderMarkerMissing mapping/localization/routing and rescan CTA availability logic.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Poem

A dialog once hid its Cancel from view,
On iOS 26, only "Merge" broke through.
Now .alert steps in, both buttons in sight,
And missing folder markers get words that are right.
🐰 Hop, patch, ship — the vault sleeps tight.

🚥 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 title uses a conventional fix prefix and accurately summarizes the consent-dialog and marker-missing error fixes.
Linked Issues check ✅ Passed The PR satisfies #64 and #65 by switching consent dialogs to alerts and localizing the marker-missing error with updated remediation.
Out of Scope Changes check ✅ Passed No clear out-of-scope changes are evident; the added docs, fixtures, tests, and localization support the linked UI and error-handling fixes.
No Private Note Leakage ✅ Passed Touched additions are alerts/UI fixtures and local error mapping; no new logging, analytics, crash reporting, or network paths include note contents, paths, or tokens.
Bounded Ios Background Work ✅ Passed PR only changes consent/error UI; no new BGTask/beginBackgroundTask/expirationHandler code was added, and BackgroundSyncService is unchanged.
Bridge Contract Compatibility ✅ Passed PR only changes UI alert presentation; no Go bridge or bridge-service files, gomobile types, JSON shapes, or bridge tests were touched.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ui-audit-release-blockers

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.

@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.

Caution

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

⚠️ Outside diff range comments (1)
ios/VaultSync/Views/SyncIssuesView.swift (1)

161-185: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Route marker-missing folder errors to the moved/deleted guide

troubleshootingURL(for:) still hard-codes #bookmark-access-expired for .folderErrors, so a card that only contains folder-marker loss points to the wrong recovery doc. Use hasRescanableFolderErrors here (or reuse SyncUserError.troubleshootingURL) so marker-missing-only cases land on #vault-folder-was-moved-or-deleted.

🤖 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/Views/SyncIssuesView.swift` around lines 161 - 185, The
troubleshooting link selection in troubleshootingURL(for:) is routing
.folderErrors to the bookmark-access-expired anchor, which is the wrong recovery
doc for marker-missing-only cases. Update the .folderErrors handling to use
hasRescanableFolderErrors or reuse SyncUserError.troubleshootingURL so those
cases point to the moved/deleted guide anchor instead. Keep the existing nil
behavior for the other kinds in SyncIssuesView.
🤖 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.

Outside diff comments:
In `@ios/VaultSync/Views/SyncIssuesView.swift`:
- Around line 161-185: The troubleshooting link selection in
troubleshootingURL(for:) is routing .folderErrors to the bookmark-access-expired
anchor, which is the wrong recovery doc for marker-missing-only cases. Update
the .folderErrors handling to use hasRescanableFolderErrors or reuse
SyncUserError.troubleshootingURL so those cases point to the moved/deleted guide
anchor instead. Keep the existing nil behavior for the other kinds in
SyncIssuesView.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 222774a0-ee61-4194-b92d-e26aaa77a8aa

📥 Commits

Reviewing files that changed from the base of the PR and between e7e02d5 and bc2d0ea.

📒 Files selected for processing (16)
  • CHANGELOG.md
  • docs/decisions/011-consent-dialogs-alert-not-confirmation-dialog.md
  • docs/troubleshooting.md
  • ios/VaultSync/App/UIAuditFixture.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • ios/VaultSync/Views/SyncIssuesView.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/SyncUserErrorTests.swift
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Go Tests
⚠️ CI failures not shown inline (2)

GitHub Actions: Security / Go Lint: fix(ios): consent dialogs with visible Cancel on iOS 26; localized marker-missing folder error (#64, #65)

Conclusion: failure

View job details

##[group]Run unformatted="$(gofmt -l go/bridge notify)"
 �[36;1munformatted="$(gofmt -l go/bridge notify)"�[0m
 �[36;1mif [ -n "$unformatted" ]; then�[0m
 �[36;1m  echo "::error::These files need gofmt:"�[0m

GitHub Actions: Security / 1_Go Lint.txt: fix(ios): consent dialogs with visible Cancel on iOS 26; localized marker-missing folder error (#64, #65)

Conclusion: failure

View job details

##[group]Run unformatted="$(gofmt -l go/bridge notify)"
 �[36;1munformatted="$(gofmt -l go/bridge notify)"�[0m
 �[36;1mif [ -n "$unformatted" ]; then�[0m
 �[36;1m  echo "::error::These files need gofmt:"�[0m
🧰 Additional context used
📓 Path-based instructions (9)
**

⚙️ CodeRabbit configuration file

**:

VaultSync

VaultSync

Self-hosted Obsidian vault sync for iPhone and iPad.

Your notes sync peer-to-peer over Syncthing, straight into Obsidian's iOS sandbox — no note cloud, no account, no tracking.

Download on the App Store



Stars
License: MPL-2.0
iOS 18+
CI

VaultSync welcome screen VaultSync home screen

🔭 Why VaultSync

  • Peer-to-peer & private — syncs directly between your own devices over Syncthing. No note cloud, no account, no tracking.
  • Lands in Obsidian — files sync into Obsidian's iOS sandbox, where the app already looks for them.
  • Pair by QR, resolve conflicts — connect your server in seconds; settle Markdown conflicts with side-by-side diffs.
  • Server changes wake your iPhone — optional Cloud Relay nudges the app the moment your server updates, so incoming notes land eve...

Files:

  • docs/decisions/011-consent-dialogs-alert-not-confirmation-dialog.md
  • ios/VaultSync/zh-Hans.lproj/Localizable.strings
  • ios/VaultSync/en.lproj/Localizable.strings
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/es.lproj/Localizable.strings
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/de.lproj/Localizable.strings
  • docs/troubleshooting.md
  • ios/VaultSync/App/UIAuditFixture.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • CHANGELOG.md
  • ios/VaultSyncTests/SyncUserErrorTests.swift
  • ios/VaultSync/Views/ContentView.swift
docs/**

⚙️ CodeRabbit configuration file

docs/**: # Architecture

VaultSync embeds Syncthing's Go reference implementation as an iOS library via gomobile — no reimplementation of the protocol in Swift, and guaranteed wire compatibility.

┌─────────────────────────────────┐
│         SwiftUI Frontend        │   iOS-native UI, Swift 6
├─────────────────────────────────┤
│       Swift ↔ Go Bridge         │   thin API via gomobile
│                                 │   → exported as .xcframework
├─────────────────────────────────┤
│        syncthing/lib (Go)       │   protocol, discovery, sync
└─────────────────────────────────┘
              ↕ filesystem
┌─────────────────────────────────┐
│    Obsidian Vault (direct)      │   Obsidian's iOS sandbox
└─────────────────────────────────┘

🔄 Sync strategy

  • Foreground — Syncthing runs unrestricted: immediate, continuous sync.
  • BackgroundBGAppRefreshTask (requested ~15 min out; iOS decides the actual timing) + BGProcessingTask (overnight catch-up: multi-minute budget while charging with network) + BGContinuedProcessingTask (iOS 26+, longer runtime for user-initiated tasks). A ~30s grace window after backgrounding lets in-flight work finish.
  • Push (Cloud Relay) — optional. Near-realtime server → iPhone wake-ups via APNs silent push. See relay-spec.md.

VaultSync is intentionally asymmetric:

Direction Path
Server → iPhone vaultsync-notify spots outgoing changes → Cloud Relay silent push → VaultSync wakes and pulls.
iPhone → Server iOS doesn't guarantee timely background execution for local edits. The reliable path is to open VaultSync and let embedded Syncthing run in the foreground — a Shortcuts automation can do that automatically whenever you leave Obsidian.

Cloud Relay is a server → iPhone acceleration path, not a guarantee of symmetric real-time background sync.

Connection paths & iOS network privacy

How peers are reached, fastest f...

Files:

  • docs/decisions/011-consent-dialogs-alert-not-confirmation-dialog.md
  • docs/troubleshooting.md
docs/decisions/**

⚙️ CodeRabbit configuration file

docs/decisions/**: # 001 — Two Syncthing folders never overlap on disk (three enforcement layers)

Context: A server sharing more than one vault could hand the second share the same local folder as the first (1.6.0–1.7.0) or a subfolder inside an existing vault (1.7.1). Overlapping folders sync each other's content as their own — deleting the stray copy on any peer would have deleted the inner vault everywhere (#45).

Decision: The no-overlap invariant (equal, nested, or containing paths) is enforced in three independent layers, each with its own tests: the Go hard floor (AcceptPendingFolder/AddFolder reject with folderPathOverlapError), the Swift mapping (VaultManager.resolveSharePath returns nil rather than an overlapping path), and the launch shield (PathCollisionGuard pauses already-overlapping folders exactly once).

Why: The single-layer version failed twice — both #45 bugs lived in the Swift mapping. The Go floor backstops future mapping bugs, the Swift layer turns a hard engine error into user guidance, and only the shield catches damage that predates the fix.

Rejected alternative: Enforcing only in the Swift mapping — the proven failure mode; a bug there would silently re-open the hole with no backstop.

Links: #45, PR #47 (same-folder merge, 1.7.1), PR #51 (nesting, 1.7.2).

docs/decisions/**: # 002 — Recovery from data damage is never automatic

Context: When VaultSync detects existing damage (two vaults merged into one folder, or one nested inside another, #45), an automatic repair would have to move, rename, delete, or re-accept user data whose true state only the user knows — after a merge, the app cannot tell which files belong to which vault.

Decision: Recovery is always: pause the affected folders (exactly once — a folder the user deliberately resumes is never re-paused), explain the problem in a critical issue with concrete recovery steps, and let the user act.

Why: Sync propagates every local act...

Files:

  • docs/decisions/011-consent-dialogs-alert-not-confirmation-dialog.md
**/*

⚙️ 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:

  • docs/decisions/011-consent-dialogs-alert-not-confirmation-dialog.md
  • ios/VaultSync/zh-Hans.lproj/Localizable.strings
  • ios/VaultSync/en.lproj/Localizable.strings
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/es.lproj/Localizable.strings
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/de.lproj/Localizable.strings
  • docs/troubleshooting.md
  • ios/VaultSync/App/UIAuditFixture.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • CHANGELOG.md
  • ios/VaultSyncTests/SyncUserErrorTests.swift
  • ios/VaultSync/Views/ContentView.swift
**/*.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:

  • docs/decisions/011-consent-dialogs-alert-not-confirmation-dialog.md
  • docs/troubleshooting.md
  • CHANGELOG.md
ios/**/*.{swift,strings,stringsdict}

📄 CodeRabbit inference engine (README.md)

Support localization in English, German, Spanish, and Simplified Chinese

Files:

  • ios/VaultSync/zh-Hans.lproj/Localizable.strings
  • ios/VaultSync/en.lproj/Localizable.strings
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/es.lproj/Localizable.strings
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/de.lproj/Localizable.strings
  • ios/VaultSync/App/UIAuditFixture.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSyncTests/SyncUserErrorTests.swift
  • ios/VaultSync/Views/ContentView.swift
**/*.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/Views/SyncIssuesView.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/App/UIAuditFixture.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSyncTests/SyncUserErrorTests.swift
  • ios/VaultSync/Views/ContentView.swift
ios/**/*.swift

📄 CodeRabbit inference engine (README.md)

ios/**/*.swift: Use Swift 6 and SwiftUI for iOS app development
Implement VoiceOver and Dynamic Type accessibility support throughout the app
Use BGAppRefreshTask and BGContinuedProcessingTask (iOS 26+ when available) for background sync operations
Use APNs silent push notifications via Cloud Relay for server-to-iPhone wake-ups
Implement side-by-side diff resolution for Markdown file conflicts
Provide an activity timeline and diagnostics interface showing exactly what synced and when
Implement QR code pairing for Syncthing Device ID connection setup
Detect and list available Obsidian vaults automatically upon connection

Files:

  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/App/UIAuditFixture.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSyncTests/SyncUserErrorTests.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/Views/SyncIssuesView.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/App/UIAuditFixture.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSyncTests/SyncUserErrorTests.swift
  • ios/VaultSync/Views/ContentView.swift
ios/**/*.{swift,pbxproj}

📄 CodeRabbit inference engine (README.md)

Target iOS / iPadOS 18 or later as the minimum deployment target

Files:

  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/App/UIAuditFixture.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSyncTests/SyncUserErrorTests.swift
  • ios/VaultSync/Views/ContentView.swift
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-07T18:31:55.577Z
Learning: If VaultSync shows "Sync Engine Not Running" and the Device ID is empty, force-close the app, reopen it, keep it in the foreground for 20–30 seconds, confirm the Device ID appears, rescan the vault, and reboot the device if needed.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-07T18:31:55.577Z
Learning: For `vaultsync-notify` 401/403 or permission-denied errors, run the helper as the user that owns `config.xml`, point `SYNCTHING_CONFIG` at the real Syncthing config if needed, and remove any incorrect `SYNCTHING_API_KEY` override before recreating the container and rerunning `--doctor`.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-07T18:31:55.577Z
Learning: If VaultSync reports "Relay Unreachable", verify internet access, check `RELAY_URL`, review firewall/VPN/proxy egress rules, test the relay health endpoint from the homeserver, and confirm the app's relay status and last trigger received update.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-07T18:31:55.577Z
Learning: If onboarding is stuck on "Confirm your first share" and no pending shares appear, verify the desktop Syncthing folder is shared to the iOS Device ID, confirm the Device ID matches, ensure Syncthing is online and the folder is not paused, then refresh or re-share the folder.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-07T18:31:55.577Z
Learning: If APNs wake-ups are not arriving, use the app's relay diagnostics to retry APNs registration, retry provisioning to rebind the token to the device IDs, and then verify a file change updates "Last Trigger Received".
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-07T18:31:55.577Z
Learning: If VaultSync says the Obsidian folder is not connected, make sure Obsidian has been opened at least once, reconnect the folder in the Files picker, choose the correct Obsidian folder or vault root containing `.obsidian`, and confirm vaults or pending shares appear.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-07T18:31:55.577Z
Learning: If a vault shows "Vault Folder Was Moved or Deleted", move the folder back if it was renamed or relocated; otherwise remove the vault in VaultSync and re-accept its share to create a fresh folder.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-07T18:31:55.577Z
Learning: If VaultSync cannot access a folder because a bookmark expired, reconnect to the Obsidian folder in the Files picker, keep the app in the foreground while rescanning, and remove the vault only if its storage is truly gone.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-07T18:31:55.577Z
Learning: If background sync works in the foreground but not when the app is closed, rescan manually, clear sync issues, reconnect the Obsidian folder if needed, verify relay health for relay users, and check the last sync timestamp after the next background window.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-07T18:31:55.577Z
Learning: If a required device is disconnected, ensure desktop Syncthing is running, confirm device connectivity over LAN/VPN/relay, verify the device still exists in VaultSync, and remove/re-add it if its ID changed.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-07T18:31:55.577Z
Learning: When filing an issue, include the VaultSync version, iOS version, screenshots of Sync Issues and Relay health & diagnostics, `vaultsync-notify --doctor` output if using the relay, and relevant `vaultsync-notify` log lines.
📚 Learning: 2026-06-10T18:47:10.724Z
Learnt from: psimaker
Repo: psimaker/vaultsync PR: 38
File: ios/VaultSync/Views/ContentView.swift:605-611
Timestamp: 2026-06-10T18:47:10.724Z
Learning: In the SwiftUI codebase under ios/VaultSync, do not flag missing localization for SwiftUI string literals used as Text("…") or DisclosureGroup("…") titles/labels. In SwiftUI, these string literals are treated as LocalizedStringKey and resolve via the app’s Localizable.strings automatically—so they only need attention if the corresponding key is actually missing. Only require an explicit localization helper (e.g., L10n.tr(…)) when the string is not being passed through SwiftUI’s LocalizedStringKey path (e.g., plain String values provided to non-SwiftUI APIs).

Applied to files:

  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/App/UIAuditFixture.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Views/ContentView.swift
🪛 LanguageTool
CHANGELOG.md

[style] ~21-~21: This expression tends to be overused. Try opting for a more concise alternative.
Context: ...during the window asks you to try again in a moment. Nothing is ever moved automatically. L...

(IN_A_MOMENT)

🔇 Additional comments (23)
docs/decisions/011-consent-dialogs-alert-not-confirmation-dialog.md (1)

1-8: LGTM!

ios/VaultSync/Views/ContentView.swift (4)

158-190: Alert conversions look correct.

Destructive/cancel roles and dismissal semantics (vaultPendingRemoval = nil / pendingMergeConfirmation = nil) match the pre-existing .confirmationDialog behavior, and the presenting: binding wiring is the correct overload for this use case.


91-100: 🎯 Functional Correctness | ⚡ Quick win

Four .alert modifiers now chained on one view — worth a quick manual/screenshot check.

ContentView.body now attaches four separate .alert(...) modifiers directly on the TabView (pre-existing "Error"/"Note" alerts plus the two new ones). SwiftUI's older Alert-struct API had a well-known "only the last alert in the chain fires" bug; this was fixed for the .alert(_:isPresented:...) family starting iOS 15, and the pre-existing two-alert chain presumably already works in production, so risk here is low — but since the UI-audit harness exists specifically to screenshot-verify dialog rendering, it would be worth confirming all four alerts (not just the new pair) still present correctly when triggered back-to-back on iOS 18/26, rather than relying on the general API guarantee alone.

Also applies to: 158-190


24-26: LGTM!

Also applies to: 219-238, 363-444


390-418: 🔒 Security & Privacy

No issue: _testSetFolders and _testSetFolderStatuses are #if DEBUG-gated in SyncthingManager.

			> Likely an incorrect or invalid review comment.
ios/VaultSync/Views/DeviceDetailView.swift (1)

94-119: LGTM!

Good catch adding the previously-missing Cancel button (per decision 011, this dialog "declared no Cancel at all"). Fixture gating and alert wiring both look correct.

ios/VaultSync/Views/ConflictDiffView.swift (1)

78-87: LGTM!

Destructive-role assignment correctly reflects actual data risk (keepBoth is non-destructive; keepThis/keepOther are), and the alert conversion preserves the existing confirm/cancel handlers.

Also applies to: 113-128

ios/VaultSync/App/UIAuditFixture.swift (1)

1-27: LGTM!

ios/VaultSync/App/VaultSyncApp.swift (1)

64-72: LGTM!

Also applies to: 122-126

ios/VaultSync/Models/SyncUserError.swift (2)

49-55: Clean mapping, matches tests and doctrine-002 stance.

The marker-missing detection order (checked before other classifiers, in both from(rawMessage:) and fromFolderStatus) and the dedicated folderMarkerMissing helper correctly avoid surfacing the raw "potential data loss" engine string and steer users to manual recovery instead of rescan/retry, matching the linked tests.

Also applies to: 138-144, 181-199


261-262: LGTM!

ios/VaultSync/Services/SyncthingManager.swift (3)

1919-1929: LGTM!


529-540: LGTM!


2282-2285: 🩺 Stability & Availability

No change needed for _testSetFolderStatuses. It’s already inside the #if DEBUG test-hooks block, so it won’t ship in release builds.

			> Likely an incorrect or invalid review comment.
ios/VaultSync/de.lproj/Localizable.strings (1)

657-660: LGTM!

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

657-660: LGTM!

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

657-660: 📐 Maintainability & Code Quality

No issue here; zh-Hans includes the same four keys.

			> Likely an incorrect or invalid review comment.
ios/VaultSync/Views/SyncIssuesView.swift (1)

84-94: LGTM!

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

657-660: LGTM!

docs/troubleshooting.md (1)

13-13: LGTM!

New section and index entry are consistent with the SyncUserError.troubleshootingURL anchor (#vault-folder-was-moved-or-deleted) and align with the manual-recovery doctrine (decision 002).

Also applies to: 117-129

CHANGELOG.md (1)

20-23: LGTM!

ios/VaultSyncTests/SyncUserErrorTests.swift (2)

64-134: LGTM!

FolderMarkerMissingMappingTests correctly exercises both the folder-status and raw-message paths, the anti-misdirection remediation check, and the dedicated troubleshooting anchor, matching the fromFolderStatus/from/troubleshootingURL implementations shown in context.


136-179: LGTM!

RescanCTAAvailabilityTests correctly validates that marker-only errors hide the rescan path while a mix with a non-marker error keeps it available, matching hasRescanableFolderErrors.

@psimaker psimaker merged commit 363337a into main Jul 7, 2026
14 checks passed
@psimaker psimaker deleted the fix/ui-audit-release-blockers branch July 7, 2026 18:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant