Skip to content

feat: relay reliability + UI consistency pass (1.6.0) - #38

Merged
psimaker merged 13 commits into
mainfrom
redesign/ui-consistency-pass
Jun 10, 2026
Merged

feat: relay reliability + UI consistency pass (1.6.0)#38
psimaker merged 13 commits into
mainfrom
redesign/ui-consistency-pass

Conversation

@psimaker

@psimaker psimaker commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Relay reliability

  • notify sidecar: stale-peer sweep re-sends wake-ups while a peer still needs data (STALE_RETRIGGER_SECONDS, default 6h) — recovers missed/expired pushes
  • iOS: overnight BGProcessingTask catch-up (charging + network, multi-minute budget)
  • Relay Diagnostics: 7-day wake-up counter (device-local), live Low Power Mode warning, force-quit education
  • docs: instant-upload.md (Shortcuts automation for iPhone→server), honest self-hosted relay constraint in relay-spec

UI consistency pass

  • design tokens (muted fills, hairline, mono font), StatusTag component
  • dashboard rebuilt on the design system; actionable checklist; dismissable relay upsell
  • touch targets, a11y polish, l10n terminology unification (en/de/es/zh-Hans)

Bumps version to 1.6.0 (build 28).

VaultSync 1.6.0: Relay reliability and UI consistency pass

User-visible sync improvements

Relay wake-up reliability:

  • Sidecar now implements a stale-peer sweep that re-triggers wake-ups every 6 hours (configurable via STALE_RETRIGGER_SECONDS) for devices still reporting outstanding sync need, recovering missed or expired pushes without waiting for local changes.
  • iOS adds an overnight catch-up sync via BGProcessingTask (≈180 second budget) that runs when the device is charging with network connectivity, providing a longer execution window alongside existing background primitives.

Relay diagnostics visibility:

  • New 7-day wake-up received counter in Relay Diagnostics (local rolling history capped at 200 entries stored in UserDefaults).
  • Low Power Mode detection and warning in diagnostics UI with educational hints about deferred wake-ups.
  • Force-quit education hints to explain wake-up delivery interruptions.

Workflow automation:

  • New instant-upload.md documentation and in-app guidance for iOS Shortcuts personal automation to trigger VaultSync uploads when leaving Obsidian (uses vaultsync://sync URL scheme).

UI consistency and design system

  • New design tokens introduced: muted fill colors (vaultAccentFill, vaultSlateFill variants), hairline stroke color, monospaced font helper (vaultMono), and spacing constant xxs.
  • New StatusTag reusable component for state/count badges with consistent styling.
  • Dashboard rebuilt with actionable setup checklist, dismissable relay upsell card (shown after first sync), StatusRow/ActionCard usage for vault and device listings, and ContentUnavailableView empty states.
  • Touch target and accessibility improvements: buttons use .controlSize(.regular), icon/glyph tint control in StatusRow, widget accessibility labels, conditional button visibility over disabled states.
  • Terminology unification: "Trigger" → "Wake-up" across UI and localization keys.

Localization and documentation

  • Localization updates for en/de/es/zh-Hans with Unicode punctuation ( arrows, proper ellipsis).
  • Updated relay-spec.md to document stale-peer sweep behavior and STALE_RETRIGGER_SECONDS configuration.
  • Removed "Self-hosted Relay (roadmap)" claims and clarified that self-hosted setups require App Store APNs push keys.
  • Updated troubleshooting guidance to reference the Shortcuts automation for improved iPhone→server sync.

Background execution changes

  • BGProcessingTask registration requires network connectivity and external power, with ~1 hour earliest begin date; expiration handler only stops Syncthing if not in foreground.
  • Sidecar stale-peer sweep is skipped while debouncing change-driven triggers to avoid thundering-herd behavior.

Test coverage

  • Added RelayTriggerStoreTests validating 7-day window counting and history capping (200 entry limit).
  • Added configuration and behavior tests in notify/main_test.go for stale-retrigger defaults, parsing, and sweep triggering.
  • Test setup cleanup updated to clear relay trigger history from UserDefaults.

Version and metadata

  • Version bumped to 1.6.0 (build 28) in ios/project.yml for both app and widget targets.
  • CHANGELOG updated with release notes on relay behavior, diagnostics, automation, and UI polish.

psimaker added 11 commits June 10, 2026 18:19
APNs silent pushes expire after ~1h and the change-driven trigger path
dedups by marker, so a phone that was offline for the only change of the
day stayed stale until the next change or a manual app open.

The sidecar now sweeps /rest/db/completion on a slow cadence (default
6h, STALE_RETRIGGER_SECONDS, 0 disables) and re-sends a wake-up while
any unpaused peer still reports outstanding need{Items,Bytes,Deletes}.
Sweeps skip when a change-driven trigger is already debouncing, and a
fully-synced fleet never causes a push.
Adds the third standard background primitive next to BGAppRefreshTask
and BGContinuedProcessingTask: a BGProcessingTask scheduled on every
backgrounding (charging + network required) that runs the shared
background sync cycle with a 180s budget instead of ~25s. Large
catch-ups that time out in the refresh/silent-push window now complete
overnight on the charger.

The 'processing' UIBackgroundModes entry was already declared; only the
task identifier, registration, scheduling, and handler were missing.
…ow Power Mode

Silent pushes are the part of Cloud Relay iOS is allowed to drop: a
force-quit blocks them entirely until the next manual launch, and Low
Power Mode defers them. Users had no way to see how much was actually
arriving, and no hint why it stopped.

- RelayTriggerStore now keeps a rolling local history of wake-up
  arrivals (capped at 200, never leaves the device); Relay Diagnostics
  shows a 7-day received counter.
- Live Low Power Mode warning in the Trigger Delivery section and as a
  troubleshooting hint.
- Force-quit education: permanent footnote, plus a targeted 'wake-ups
  went quiet' hint when delivery was confirmed before but nothing
  arrived within the freshness window.
- Localized in en/de/es/zh-Hans following existing terminology
  (Weck-Signal / señal de activación / 唤醒信号).
The vaultsync://sync deep link has existed for a while and the widgets
are already tap-to-sync, but nothing told users they can automate the
upload direction: a one-time Shortcuts personal automation (Obsidian
'Is Closed' -> Open URL vaultsync://sync) syncs every editing session
the moment they leave Obsidian.

New docs/instant-upload.md with setup steps and honest caveats, linked
from the README doc table, the architecture asymmetry table, and the
'Background Sync Not Working' troubleshooting section.
…PNs constraint

The spec promised a free self-hosted relay tier as roadmap. It cannot
be built: APNs only accepts pushes for eu.vaultsync.app signed with
VaultSync's own p8 key, which can never be distributed (security +
Apple ToS). Advertising it anchored a free-tier expectation against the
product's only revenue stream while being technically unfulfillable.

Replaced the section with the real constraint, what remains open
(RELAY_URL for development, full-stack self-build from source under
MPL-2.0), dropped the pricing-table row and the answered open
questions.
Changelog for the relay-reliability wave: sidecar stale-peer sweep,
overnight BGProcessingTask catch-up, Cloud Relay delivery visibility
(7-day counter, Low Power / force-quit education), the instant-upload
Shortcuts guide, and the honest self-hosted relay documentation.
Cross-language:
- Unify relay diagnostics on wake-up terminology (was: trigger vs wake-up
  on the same screen); renames keys 'Trigger Delivery' -> 'Wake-up Delivery'
  and 'Last Trigger Received' -> 'Last Wake-up Received'
- Replace ASCII '->' with '→' and '...' with '…' in user-facing strings
- Drop literal backticks and stray 'homeserver container' wording from the
  no-wake-up hint; align it with the helper naming used everywhere else
- Tighten the longest walls of text (relay setup steps 1+2, 'not active
  yet' explainer, contribution blurb)

German:
- Standardize on 'Sync-…' compounds (was a mix of Hintergrundsynchronisation/
  Hintergrundsync/Hintergrund-Sync, Sync-Problem vs Synchronisationsprobleme,
  Synchronisations-Engine vs Sync-Engine, Synchronisierung vs Synchronisation)
- Use iOS-standard 'Mitteilungen' instead of 'Benachrichtigungen'
- Fix 'QR-Code scannen', restore full meaning of 'Add or Reconnect Device',
  fix word order in the server-helper hint, passive 'Verbindet neu…'
- Unify dashes to '–' and quotes to „…“ in values; widget: 'Wartet',
  'Jetzt synchronisieren'
- Disambiguate 'Learn how to fix' ('Lösung anzeigen') from 'How to fix: %@'

Spanish:
- 'compartición' instead of stray 'recurso compartido'; 'Volver a conectar'
  instead of mixed 'Reconectar'; 'Tiempo agotado' for Timed out; 'Preparado'
  for Ready (was colliding with Done = 'Listo'); '«…»' quotes; 'La mejor
  opción' for Best value

Chinese (Simplified):
- Translate the remaining 'homeserver' mention; restore 'First' in accept-
  first-share; '在 %@ 中' for folder context (was duration-like '在 %@ 内');
  natural per-period price format ('每个月 ¥…'); celebratory '太好了' for Great

All four files keep full key parity and format-specifier parity; xcodebuild
build + test pass.
…component

- Theme: alpha-capable vaultColor so muted fills (vaultAccentFill/Subtle,
  vaultSlateFill/Subtle) retire the per-view 'colorScheme == .dark ? a : b'
  opacity math; vaultHairline for card strokes; VaultSpacing.xxs (2pt);
  Font.vaultMono() replacing scattered .system(design: .monospaced) literals.
- DesignSystem: StatusTag capsule badge unifying the three hand-rolled badge
  looks (conflict counts, 'Save N%', Ready/Needs Attention); filled variant
  uses systemBackground text so it stays readable on lifted dark tints.
- StatusRow: optional glyphTint for the rare non-SyncStatus glyph color.
…checklist; dismissable relay upsell

ContentView:
- The four hand-built relay rows become one relayNavRow (StatusRow-based);
  the active state reuses the same StatusBadge as the Relay tab.
- Sync and folder errors render as ActionCard instead of red caption stacks;
  the Obsidian connect block becomes an ActionCard with a real primary button.
- Vault rows get the StatusRow treatment (full-size glyph, headline title,
  StatusTag conflict count) instead of plain text with a caption2 icon.
- Empty states (vaults, devices) use ContentUnavailableView; Add Device moves
  from the section header to the Devices tab toolbar.
- The relay upsell no longer hijacks tab selection at the aha moment: it is a
  dismissable dashboard card (View Cloud Relay / Not now).

Setup checklist:
- Items with an in-app entry point (connect Obsidian, add device, open Relay
  tab) carry a ChecklistAction; the view renders a real button below the
  remediation prose, routed through Settings back to ContentView, which
  presents the right sheet/tab after the dismissal transition.

l10n: five new keys in en/de/es/zh-Hans (wake-up terminology per glossary).
… a11y polish

- Onboarding: the seven hand-rolled dark-mode opacity sites move to the new
  muted fill / hairline tokens; paddings and the benefit-chip radius join the
  VaultSpacing/VaultRadius scale.
- SyncIssuesView: .small action buttons become .regular (44pt minimum); a
  button that could do nothing is hidden instead of disabled unexplained.
- PendingShares/SubscribePlanPicker/SetupChecklist: hand-rolled capsule
  badges replaced with StatusTag; spacing literals onto the scale.
- DeviceDetailView: renaming now shows a transient saved checkmark
  (mirrors MonoField copy feedback) instead of committing invisibly.
- Conflict/LineDiff/IgnorePatterns/RelayServerSetup: Font.vaultMono and
  VaultRadius.control replace ad-hoc monospace fonts and radius 8.
- Widget: the medium widget's bare clock time gets a 'Last sync'
  accessibility label; QR scanner and relay views join the spacing scale.
@coderabbitai

coderabbitai Bot commented Jun 10, 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 2 minutes and 16 seconds. Learn how PR review limits work.

To continue reviewing without waiting, purchase usage credits 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: 6ee32745-4d61-4052-a856-262ff066fd32

📥 Commits

Reviewing files that changed from the base of the PR and between 6028af6 and 6fa62e3.

📒 Files selected for processing (3)
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/Views/DesignSystem.swift
  • notify/main_test.go
📝 Walkthrough

Walkthrough

This 1.6.0 release adds backend and iOS features to improve push reliability and background sync, modernizes the iOS app UI with design tokens, introduces setup checklist action shortcuts, and applies comprehensive multilingual localization updates across four languages.

Changes

Backend: Stale-Peer Sweep & Wake-Up Tracking

Layer / File(s) Summary
Stale-peer sweep mechanism
notify/main.go, notify/syncthing.go, notify/main_test.go, notify/README.md
Notify service gains a configurable periodic sweep (STALE_RETRIGGER_SECONDS, default ~6h, disabled via 0) that re-triggers wake-ups when any unpaused remote peer still needs items/bytes/deletes. New ListDevices, Completion, and getJSON API helpers query Syncthing device state, and two decision helpers (completionNeedsWakeup, anyPeerNeedsData) detect stalled peers; main run loop adds a conditional ticker channel to trigger relay on each sweep tick.
Relay wake-up history tracking
ios/VaultSync/Models/RelayProvisionStatus.swift, ios/VaultSync/Services/SubscriptionManager.swift, ios/VaultSyncTests/RelayTriggerStoreTests.swift, ios/VaultSyncTests/TestSupport.swift
RelayTriggerStore persists a bounded rolling history (capped at 200) of received wake-up timestamps in UserDefaults, exposes receivedHistory() and receivedCount(within:) helpers for local diagnostics, and SubscriptionManager updates relayWakeupsLast7Days on refresh. Tests validate rolling-window counting and history capping behavior.
Relay documentation & architecture updates
docs/relay-spec.md, docs/architecture.md, docs/instant-upload.md, docs/troubleshooting.md, CHANGELOG.md, README.md
Removes self-hosted relay roadmap claims and free-tier assertions; documents stale-peer sweep mechanism and STALE_RETRIGGER_SECONDS config; updates background task strategy to include BGProcessingTask for overnight catch-up; adds new guide for iOS Shortcuts automation to trigger immediate sync when leaving Obsidian.

iOS App: Background Sync, UI Modernization, Localization

Layer / File(s) Summary
Background processing task (overnight catch-up)
ios/VaultSync/Services/BackgroundSyncService.swift, ios/VaultSync/App/VaultSyncApp.swift
Adds BGProcessingTask handler and scheduleProcessing() method that submits a request requiring network connectivity and external power with ~1h earliest begin date; schedules 180s sync budget on execution; scene lifecycle wires processing schedule after app-refresh schedule.
Design system foundation: colors, spacing, typography
ios/VaultSync/Resources/Theme.swift
vaultColor now accepts lightAlpha/darkAlpha parameters for trait-aware fill opacity; Color palette gains new vaultAccentFill*, vaultSlateFill*, vaultHairline variants; Font.vaultMono() centralizes monospaced font construction; VaultSpacing adds xxs constant. These tokens unify design across all views.
StatusTag component & StatusRow enhancements
ios/VaultSync/Views/DesignSystem.swift
Introduces reusable StatusTag capsule badge for counts/state with tint control; adds glyphTint parameter to StatusRow for glyph color override; updates DetailRow and MonoField to use vaultMono.
ContentView dashboard refactor: upsell card, ActionCard, empty states
ios/VaultSync/Views/ContentView.swift
Dashboard introduces dismissible relay upsell card at first-sync aha moment; consolidates sync/error UI with ActionCard for consistent error presentation; adds ContentUnavailableView empty states for vaults and devices; refactors relay navigation via reusable relayNavRow() builder; moves "Add Device" entry point to Devices tab toolbar.
Relay diagnostics UI: Low Power Mode, wake-up count
ios/VaultSync/Views/RelayDiagnosticsView.swift
Tracks iOS Low Power Mode via @State updated on NSProcessInfoPowerStateDidChange; displays "Wake-ups (Last 7 Days)" counter; conditionally shows Low Power Mode warning and background-retention guidance; updates troubleshooting hints.
Setup checklist with in-app action shortcuts
ios/VaultSync/ViewModels/SetupChecklistViewModel.swift, ios/VaultSync/Views/SetupChecklistView.swift, ios/VaultSync/Views/SettingsView.swift
Adds ChecklistAction enum (connectObsidian, addDevice, openRelayTab); extends ChecklistItem with optional action property; renders prominent action buttons for incomplete items; wires actions through settings sheet and routes to ContentView for in-app navigation (with brief delay for visual sequencing).
Systematic spacing & typography polish
ios/VaultSync/Views/*.swift, ios/VaultSync/Resources/Theme.swift
Replaces hardcoded spacing (pixels) with VaultSpacing constants throughout all view files; converts system monospaced fonts to vaultMono helpers for consistency. Affected views: ConflictDiffView, ConflictListView, IgnorePatternsView, LineDiffView, OnboardingView, PendingSharesView, QRScannerView, SubscribePlanPicker, SyncActivityView, and supporting layouts.
Device rename feedback & widget accessibility
ios/VaultSync/Views/DeviceDetailView.swift, ios/VaultSyncWidget/VaultSyncWidget.swift
DeviceDetailView adds transient "Name saved" confirmation with 1.5s auto-dismiss after successful rename; widget adds accessibility labels/values so VoiceOver announces "last sync" time clearly.
Error & sync issue UI improvements
ios/VaultSync/Models/SyncUserError.swift, ios/VaultSync/Services/VaultManager.swift, ios/VaultSync/Views/SyncIssuesView.swift
Updates remediation strings to use Unicode arrows () instead of ASCII (->); refactors SyncIssuesView action buttons to use .controlSize(.regular) for touch targets and conditional rendering (hidden instead of disabled when no action available).
Multilingual localization: English, German, Spanish, Chinese
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, ios/VaultSyncWidget/de.lproj/Localizable.strings
Updates terminology across all languages: "trigger" → "wake-up", ASCII arrows → Unicode arrows, ellipsis normalization; adds new strings for Shortcuts automation, Low Power Mode education, 7-day history tracking, and device rename feedback; refines German, Spanish, and Chinese phrasing for clarity.
Version & build configuration
ios/project.yml
Bumps app and widget versions from 1.5.1 (build 27) to 1.6.0 (build 28); adds eu.vaultsync.app.sync-processing BGTask identifier to permitted list.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


Possibly related PRs

  • psimaker/vaultsync#24: Both touch relay trigger logic in notify/main.go; main PR adds stale-peer sweep cadence while #24 refactors trigger outcome handling.
  • psimaker/vaultsync#7: Both refactor error/remediation UI surfaces in ContentView, sync issues, and diagnostics views to handle external documentation links consistently.
  • psimaker/vaultsync#33: Both extend relay wake-up tracking (history, 7-day counts in RelayTriggerStore/SubscriptionManager) and touch diagnostics UI; #33 focuses on recording via deep-link while main PR adds local persistence & counting.

🌙 A thousand wake-ups, all in a sweep,
Low power fades, yet syncing runs deep.
Dashboards glow with colors anew,
Checklists tap, and actions ensue—
Design tokens reign, from London to Shenzhen fine. ✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch redesign/ui-consistency-pass

@psimaker psimaker changed the title VaultSync 1.6.0: relay reliability + UI consistency pass feat: relay reliability + UI consistency pass (1.6.0) Jun 10, 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: 2

🧹 Nitpick comments (2)
ios/VaultSync/Views/ContentView.swift (1)

233-245: ⚡ Quick win

Avoid fixed-delay sheet routing for checklist actions.

Using Task.sleep(.milliseconds(450)) as a dismissal synchronization gate is timing-sensitive and can still race on slower/interactive transitions; route the deferred action from a dismissal completion signal (e.g., queued pending action processed after showSettings becomes false) instead of a hardcoded delay.

🤖 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/ContentView.swift` around lines 233 - 245, The current
handleChecklistAction uses a fixed Task.sleep delay which can race; remove the
sleep and instead enqueue the action into a pendingChecklistAction property
(e.g., Optional<SetupChecklistViewModel.ChecklistAction>) inside
handleChecklistAction, then trigger dismissal (toggle showSettings as before)
and perform the actual routing when the sheet has fully dismissed by observing
the dismissal state (e.g., an onChange of showSettings or a didSet on
showSettings) — when showSettings becomes false, read and clear
pendingChecklistAction and perform the routing logic (set showObsidianPicker,
showAddDevice, or selectedTab = .relay). Ensure all UI updates happen on the
main actor.
notify/main_test.go (1)

1049-1055: ⚡ Quick win

Tighten stale-sweep regression guard for local-device queries

The stub fails on paused-peer completion requests, but it still allows completion lookups against the local device ID. That can let a self-device filtering regression slip through while tests stay green.

Suggested test-hardening patch
 	case "/rest/db/completion":
-		if r.URL.Query().Get("device") == "PAUSED-PEER" {
-			t.Error("stale sweep queried completion for a paused device")
-		}
+		switch r.URL.Query().Get("device") {
+		case "PAUSED-PEER":
+			t.Error("stale sweep queried completion for a paused device")
+		case "DEVICE-STALE":
+			t.Error("stale sweep queried completion for the local device")
+		}
 		w.Header().Set("Content-Type", "application/json")
 		_, _ = w.Write([]byte(`{"completion":99,"needBytes":` + strconv.FormatInt(needBytes.Load(), 10) + `,"needItems":0,"needDeletes":0}`))
🤖 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 `@notify/main_test.go` around lines 1049 - 1055, The stub handling the
"/rest/db/completion" path only rejects requests when
r.URL.Query().Get("device") == "PAUSED-PEER" but still allows queries for the
local device, weakening the stale-sweep guard; change the conditional in that
handler to also reject when the "device" query equals the test's local device
identifier (the same value the test uses for the current/self device) so both
"PAUSED-PEER" and the local device ID are treated as invalid in the completion
stub; update the check around r.URL.Query().Get("device") and keep the same
response behavior otherwise (leave the Content-Type header and the JSON body
using needBytes.Load()).
🤖 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/Views/ContentView.swift`:
- Around line 599-605: The Text and DisclosureGroup instances in ContentView are
using hardcoded English strings (e.g., the Text("In the picker, choose \"On My
iPhone\" → \"Obsidian\", then tap Open."), DisclosureGroup("Can't find the
Obsidian folder?") and its child Text) so localizations are missing; replace
these literal strings with localized keys (either use
LocalizedStringKey("your.key.here") or NSLocalizedString("your.key.here",
comment: "…") / Text(LocalizedStringKey("your.key.here"))), update the
DisclosureGroup and inner Text to use those keys (refer to the ContentView type
and the specific Text/DisclosureGroup declarations to locate them), and add
corresponding entries for each key into Localizable.strings for en, de, es, and
zh-Hans with appropriate translator comments.

In `@ios/VaultSync/Views/DesignSystem.swift`:
- Around line 59-63: The filled StatusTag currently uses
Color(.systemBackground) for its foreground which can produce low-contrast text
on bright tint colors; update the StatusTag rendering logic (the view that uses
the filled Bool and the modifiers .foregroundStyle and .background in
DesignSystem.swift) to choose a contrast-safe foreground dynamically—either use
the dynamic label color (Color(uiColor: .label) / Color.primary) for filled tags
or implement a luminance check on the tint color and pick a light or dark
foreground (e.g., .white or .black / .label) based on that result—so that filled
tags always maintain accessible contrast in both light and dark modes and with
VoiceOver/Dynamic Type.

---

Nitpick comments:
In `@ios/VaultSync/Views/ContentView.swift`:
- Around line 233-245: The current handleChecklistAction uses a fixed Task.sleep
delay which can race; remove the sleep and instead enqueue the action into a
pendingChecklistAction property (e.g.,
Optional<SetupChecklistViewModel.ChecklistAction>) inside handleChecklistAction,
then trigger dismissal (toggle showSettings as before) and perform the actual
routing when the sheet has fully dismissed by observing the dismissal state
(e.g., an onChange of showSettings or a didSet on showSettings) — when
showSettings becomes false, read and clear pendingChecklistAction and perform
the routing logic (set showObsidianPicker, showAddDevice, or selectedTab =
.relay). Ensure all UI updates happen on the main actor.

In `@notify/main_test.go`:
- Around line 1049-1055: The stub handling the "/rest/db/completion" path only
rejects requests when r.URL.Query().Get("device") == "PAUSED-PEER" but still
allows queries for the local device, weakening the stale-sweep guard; change the
conditional in that handler to also reject when the "device" query equals the
test's local device identifier (the same value the test uses for the
current/self device) so both "PAUSED-PEER" and the local device ID are treated
as invalid in the completion stub; update the check around
r.URL.Query().Get("device") and keep the same response behavior otherwise (leave
the Content-Type header and the JSON body using needBytes.Load()).
🪄 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: 00c1d8d7-07a2-45ef-a7bb-6c0285f401e2

📥 Commits

Reviewing files that changed from the base of the PR and between f9e8681 and 6028af6.

📒 Files selected for processing (45)
  • CHANGELOG.md
  • README.md
  • docs/architecture.md
  • docs/instant-upload.md
  • docs/relay-spec.md
  • docs/troubleshooting.md
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Models/RelayProvisionStatus.swift
  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Resources/Theme.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Services/VaultManager.swift
  • ios/VaultSync/ViewModels/SetupChecklistViewModel.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Views/ConflictListView.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/Views/DesignSystem.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • ios/VaultSync/Views/IgnorePatternsView.swift
  • ios/VaultSync/Views/LineDiffView.swift
  • ios/VaultSync/Views/OnboardingView.swift
  • ios/VaultSync/Views/PendingSharesView.swift
  • ios/VaultSync/Views/QRScannerView.swift
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/Views/RelayHomeView.swift
  • ios/VaultSync/Views/RelayServerSetupView.swift
  • ios/VaultSync/Views/SettingsView.swift
  • ios/VaultSync/Views/SetupChecklistView.swift
  • ios/VaultSync/Views/SubscribePlanPicker.swift
  • ios/VaultSync/Views/SyncActivityView.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/RelayTriggerStoreTests.swift
  • ios/VaultSyncTests/TestSupport.swift
  • ios/VaultSyncWidget/VaultSyncWidget.swift
  • ios/VaultSyncWidget/de.lproj/Localizable.strings
  • ios/project.yml
  • notify/README.md
  • notify/main.go
  • notify/main_test.go
  • notify/syncthing.go
📜 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: Build & Test
🧰 Additional context used
📓 Path-based instructions (12)
ios/**/*.lproj/Localizable.strings

📄 CodeRabbit inference engine (README.md)

App must be localized in English, German, Spanish, and Simplified Chinese

Files:

  • ios/VaultSyncWidget/de.lproj/Localizable.strings
  • ios/VaultSync/es.lproj/Localizable.strings
  • ios/VaultSync/de.lproj/Localizable.strings
  • ios/VaultSync/en.lproj/Localizable.strings
  • ios/VaultSync/zh-Hans.lproj/Localizable.strings
**/*

⚙️ 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/VaultSyncWidget/de.lproj/Localizable.strings
  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictListView.swift
  • ios/VaultSync/Views/RelayServerSetupView.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Views/SyncActivityView.swift
  • ios/VaultSyncWidget/VaultSyncWidget.swift
  • docs/troubleshooting.md
  • ios/project.yml
  • README.md
  • docs/instant-upload.md
  • ios/VaultSync/Models/RelayProvisionStatus.swift
  • notify/README.md
  • ios/VaultSync/Views/QRScannerView.swift
  • CHANGELOG.md
  • ios/VaultSync/Views/RelayHomeView.swift
  • docs/architecture.md
  • ios/VaultSyncTests/TestSupport.swift
  • notify/syncthing.go
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Views/LineDiffView.swift
  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/Views/SubscribePlanPicker.swift
  • ios/VaultSync/Services/VaultManager.swift
  • ios/VaultSync/Views/IgnorePatternsView.swift
  • ios/VaultSync/Views/SettingsView.swift
  • docs/relay-spec.md
  • ios/VaultSync/Views/SetupChecklistView.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • notify/main.go
  • ios/VaultSyncTests/RelayTriggerStoreTests.swift
  • ios/VaultSync/Views/DesignSystem.swift
  • ios/VaultSync/Resources/Theme.swift
  • notify/main_test.go
  • ios/VaultSync/ViewModels/SetupChecklistViewModel.swift
  • ios/VaultSync/es.lproj/Localizable.strings
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/de.lproj/Localizable.strings
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/en.lproj/Localizable.strings
  • ios/VaultSync/zh-Hans.lproj/Localizable.strings
  • ios/VaultSync/Views/PendingSharesView.swift
  • ios/VaultSync/Views/OnboardingView.swift
**

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

  • ios/VaultSyncWidget/de.lproj/Localizable.strings
  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictListView.swift
  • ios/VaultSync/Views/RelayServerSetupView.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Views/SyncActivityView.swift
  • ios/VaultSyncWidget/VaultSyncWidget.swift
  • docs/troubleshooting.md
  • ios/project.yml
  • README.md
  • docs/instant-upload.md
  • ios/VaultSync/Models/RelayProvisionStatus.swift
  • notify/README.md
  • ios/VaultSync/Views/QRScannerView.swift
  • CHANGELOG.md
  • ios/VaultSync/Views/RelayHomeView.swift
  • docs/architecture.md
  • ios/VaultSyncTests/TestSupport.swift
  • notify/syncthing.go
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Views/LineDiffView.swift
  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/Views/SubscribePlanPicker.swift
  • ios/VaultSync/Services/VaultManager.swift
  • ios/VaultSync/Views/IgnorePatternsView.swift
  • ios/VaultSync/Views/SettingsView.swift
  • docs/relay-spec.md
  • ios/VaultSync/Views/SetupChecklistView.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • notify/main.go
  • ios/VaultSyncTests/RelayTriggerStoreTests.swift
  • ios/VaultSync/Views/DesignSystem.swift
  • ios/VaultSync/Resources/Theme.swift
  • notify/main_test.go
  • ios/VaultSync/ViewModels/SetupChecklistViewModel.swift
  • ios/VaultSync/es.lproj/Localizable.strings
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/de.lproj/Localizable.strings
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/en.lproj/Localizable.strings
  • ios/VaultSync/zh-Hans.lproj/Localizable.strings
  • ios/VaultSync/Views/PendingSharesView.swift
  • ios/VaultSync/Views/OnboardingView.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/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictListView.swift
  • ios/VaultSync/Views/RelayServerSetupView.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Views/SyncActivityView.swift
  • ios/VaultSyncWidget/VaultSyncWidget.swift
  • ios/VaultSync/Models/RelayProvisionStatus.swift
  • ios/VaultSync/Views/QRScannerView.swift
  • ios/VaultSync/Views/RelayHomeView.swift
  • ios/VaultSyncTests/TestSupport.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Views/LineDiffView.swift
  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/Views/SubscribePlanPicker.swift
  • ios/VaultSync/Services/VaultManager.swift
  • ios/VaultSync/Views/IgnorePatternsView.swift
  • ios/VaultSync/Views/SettingsView.swift
  • ios/VaultSync/Views/SetupChecklistView.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • ios/VaultSyncTests/RelayTriggerStoreTests.swift
  • ios/VaultSync/Views/DesignSystem.swift
  • ios/VaultSync/Resources/Theme.swift
  • ios/VaultSync/ViewModels/SetupChecklistViewModel.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/Views/PendingSharesView.swift
  • ios/VaultSync/Views/OnboardingView.swift
**/*.{swift,go,sh,kt,java,js,ts,tsx,jsx,py,c,h,cpp,cc}

📄 CodeRabbit inference engine (README.md)

All code must be licensed under MPL-2.0 (Mozilla Public License 2.0)

Files:

  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictListView.swift
  • ios/VaultSync/Views/RelayServerSetupView.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Views/SyncActivityView.swift
  • ios/VaultSyncWidget/VaultSyncWidget.swift
  • ios/VaultSync/Models/RelayProvisionStatus.swift
  • ios/VaultSync/Views/QRScannerView.swift
  • ios/VaultSync/Views/RelayHomeView.swift
  • ios/VaultSyncTests/TestSupport.swift
  • notify/syncthing.go
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Views/LineDiffView.swift
  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/Views/SubscribePlanPicker.swift
  • ios/VaultSync/Services/VaultManager.swift
  • ios/VaultSync/Views/IgnorePatternsView.swift
  • ios/VaultSync/Views/SettingsView.swift
  • ios/VaultSync/Views/SetupChecklistView.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • notify/main.go
  • ios/VaultSyncTests/RelayTriggerStoreTests.swift
  • ios/VaultSync/Views/DesignSystem.swift
  • ios/VaultSync/Resources/Theme.swift
  • notify/main_test.go
  • ios/VaultSync/ViewModels/SetupChecklistViewModel.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/Views/PendingSharesView.swift
  • ios/VaultSync/Views/OnboardingView.swift
ios/**/*.{swift,xcconfig,pbxproj}

📄 CodeRabbit inference engine (README.md)

iOS app must target iOS 18 or later

Files:

  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictListView.swift
  • ios/VaultSync/Views/RelayServerSetupView.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Views/SyncActivityView.swift
  • ios/VaultSyncWidget/VaultSyncWidget.swift
  • ios/VaultSync/Models/RelayProvisionStatus.swift
  • ios/VaultSync/Views/QRScannerView.swift
  • ios/VaultSync/Views/RelayHomeView.swift
  • ios/VaultSyncTests/TestSupport.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Views/LineDiffView.swift
  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/Views/SubscribePlanPicker.swift
  • ios/VaultSync/Services/VaultManager.swift
  • ios/VaultSync/Views/IgnorePatternsView.swift
  • ios/VaultSync/Views/SettingsView.swift
  • ios/VaultSync/Views/SetupChecklistView.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • ios/VaultSyncTests/RelayTriggerStoreTests.swift
  • ios/VaultSync/Views/DesignSystem.swift
  • ios/VaultSync/Resources/Theme.swift
  • ios/VaultSync/ViewModels/SetupChecklistViewModel.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/Views/PendingSharesView.swift
  • ios/VaultSync/Views/OnboardingView.swift
ios/**/*.swift

📄 CodeRabbit inference engine (README.md)

ios/**/*.swift: Swift 6 with SwiftUI must be used for iOS development
Background sync must use BGAppRefreshTask and BGContinuedProcessingTask (iOS 26+ when available)
Push notifications must use APNs silent push via Cloud Relay
Pairing must support both QR code scanning and manual Device ID paste input for Syncthing connection
App must provide an activity timeline and diagnostics showing exactly what synced
VoiceOver accessibility must be supported throughout the entire iOS app
Dynamic Type text scaling must be supported throughout the entire iOS app

Files:

  • ios/VaultSync/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictListView.swift
  • ios/VaultSync/Views/RelayServerSetupView.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Views/SyncActivityView.swift
  • ios/VaultSyncWidget/VaultSyncWidget.swift
  • ios/VaultSync/Models/RelayProvisionStatus.swift
  • ios/VaultSync/Views/QRScannerView.swift
  • ios/VaultSync/Views/RelayHomeView.swift
  • ios/VaultSyncTests/TestSupport.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Views/LineDiffView.swift
  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/Views/SubscribePlanPicker.swift
  • ios/VaultSync/Services/VaultManager.swift
  • ios/VaultSync/Views/IgnorePatternsView.swift
  • ios/VaultSync/Views/SettingsView.swift
  • ios/VaultSync/Views/SetupChecklistView.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • ios/VaultSyncTests/RelayTriggerStoreTests.swift
  • ios/VaultSync/Views/DesignSystem.swift
  • ios/VaultSync/Resources/Theme.swift
  • ios/VaultSync/ViewModels/SetupChecklistViewModel.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/Views/PendingSharesView.swift
  • ios/VaultSync/Views/OnboardingView.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/Models/SyncUserError.swift
  • ios/VaultSync/Views/ConflictListView.swift
  • ios/VaultSync/Views/RelayServerSetupView.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Views/SyncActivityView.swift
  • ios/VaultSyncWidget/VaultSyncWidget.swift
  • ios/VaultSync/Models/RelayProvisionStatus.swift
  • ios/VaultSync/Views/QRScannerView.swift
  • ios/VaultSync/Views/RelayHomeView.swift
  • ios/VaultSyncTests/TestSupport.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Views/LineDiffView.swift
  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/Views/SubscribePlanPicker.swift
  • ios/VaultSync/Services/VaultManager.swift
  • ios/VaultSync/Views/IgnorePatternsView.swift
  • ios/VaultSync/Views/SettingsView.swift
  • ios/VaultSync/Views/SetupChecklistView.swift
  • ios/VaultSync/Views/DeviceDetailView.swift
  • ios/VaultSyncTests/RelayTriggerStoreTests.swift
  • ios/VaultSync/Views/DesignSystem.swift
  • ios/VaultSync/Resources/Theme.swift
  • ios/VaultSync/ViewModels/SetupChecklistViewModel.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/Views/PendingSharesView.swift
  • ios/VaultSync/Views/OnboardingView.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/troubleshooting.md
  • README.md
  • docs/instant-upload.md
  • notify/README.md
  • CHANGELOG.md
  • docs/architecture.md
  • docs/relay-spec.md
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) + 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.

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

🌉 Go bridge (go/bridge/)

Minimal API exported via gomobile. Only primitives + string + []byte cross the bridge; complex data is JSON-serialized (read accessors are named Get…JSON). QR scanning and conflict diffs are produced on the iOS side, not via the ...

Files:

  • docs/troubleshooting.md
  • docs/instant-upload.md
  • docs/architecture.md
  • docs/relay-spec.md
ios/project.yml

📄 CodeRabbit inference engine (README.md)

Use XcodeGen for iOS project generation instead of manual Xcode project configuration

Files:

  • ios/project.yml

⚙️ CodeRabbit configuration file

ios/project.yml: This generates the Xcode project and Info.plist. Review changes for bundle ID,
entitlements, background modes, URL schemes, signing settings, and accidental secret exposure.

Files:

  • ios/project.yml
notify/**/*.{go,dockerfile,sh}

📄 CodeRabbit inference engine (README.md)

Docker sidecar (vaultsync-notify) must never expose notes, file or folder names, or vault structure to the relay

Files:

  • notify/syncthing.go
  • notify/main.go
  • notify/main_test.go
notify/**/*.go

⚙️ CodeRabbit configuration file

notify/**/*.go: Review goroutine lifecycle, context cancellation, HTTP timeouts, signal handling, debounce behavior,
Syncthing REST API polling, relay API calls, error classification, and API-key handling.
Flag leaked request bodies, note metadata, Syncthing API keys, relay keys, or APNs-related secrets.

Files:

  • notify/syncthing.go
  • notify/main.go
  • notify/main_test.go
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:40.163Z
Learning: Sync engine must use Syncthing 2.x via Go/gomobile xcframework
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:40.163Z
Learning: Cloud Relay must only ever access Device ID and push token needed to route wake-ups, never notes or vault structure
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:40.163Z
Learning: VaultSync must implement peer-to-peer syncing directly between iOS app and Syncthing server with no intermediary cloud storage
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:40.163Z
Learning: Files must sync into Obsidian's iOS sandbox without requiring external configuration or manual folder setup
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:40.163Z
Learning: Conflict resolution must support side-by-side diffs for Markdown files
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:50.824Z
Learning: Use gomobile to embed Syncthing's Go reference implementation as an iOS library with guaranteed wire compatibility; do not reimplement the Syncthing protocol in Swift
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:50.824Z
Learning: Organize iOS app code into the following groups: App/, Models/, ViewModels/, Views/, Services/, and Resources/
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:50.824Z
Learning: Implement foreground sync by running Syncthing unrestricted for immediate, continuous synchronization
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:50.824Z
Learning: Design sync as asymmetric: server-to-iPhone via Cloud Relay silent push acceleration, iPhone-to-server via foreground Syncthing with Shortcuts automation for on-demand uploads
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:58.284Z
Learning: Implement Shortcuts personal automation to trigger VaultSync uploads when Obsidian is closed, using the URL scheme `vaultsync://sync`
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:58.284Z
Learning: Support URL scheme `vaultsync://sync` to trigger immediate scan and sync operations
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:58.284Z
Learning: Support optional folder parameter in URL scheme: `vaultsync://sync?folder=<folder-id>` for vault-specific syncing
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:58.284Z
Learning: Ensure VaultSync can be opened and trigger sync operations within the iOS ~30 second grace window after app backgrounding
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:58.284Z
Learning: Implement home screen and lock screen widgets with tap-to-sync functionality that triggers the `vaultsync://sync` URL scheme action
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:44:58.284Z
Learning: Implement pull-to-refresh gesture inside VaultSync to allow on-demand rescan and sync operations
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:25.062Z
Learning: All endpoints must require TLS (HSTS, minimum TLS 1.2)
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:25.062Z
Learning: No file content, folder names, file names, file sizes, or metadata must ever be transmitted from the homeserver to the central relay or iOS app
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:25.062Z
Learning: APNs payload must be a silent push with no visible alert or body content
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:25.062Z
Learning: Identity is based on Syncthing Device ID; no user accounts or API keys required
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:25.062Z
Learning: The central relay is a minimal, horizontally-scalable infrastructure with stateless request handling and a shared database for device tokens
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:25.062Z
Learning: Device tokens are personal data; ensure GDPR data processing agreements and privacy policy disclosures are in place
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:40.282Z
Learning: When Syncthing is not running (Device ID empty, sync engine offline), force-close VaultSync and reopen it, keep it in foreground for 20-30 seconds, confirm Device ID appears, tap Rescan Vault, and reboot device if still failing
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:40.282Z
Learning: The vaultsync-notify service must run as the correct user (matching the uid/gid that owns config.xml) to read Syncthing API key from config.xml; set PUID/PGID in Docker Compose or use -u <uid>:<gid> with docker run
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:40.282Z
Learning: Set SYNCTHING_CONFIG environment variable to point to the actual config.xml path when it is not auto-detected (required for Synology/QNAP), and remove SYNCTHING_API_KEY override if set to allow fallback to config.xml auto-detection
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:40.282Z
Learning: The relay health check must be verified to be green (relay reachable) and Last Trigger Received timestamp must update to confirm wake-ups are actually delivered, not just that the relay is up
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:40.282Z
Learning: On desktop Syncthing, the iOS Device ID must be listed in the folder Sharing settings for pending shares to appear in VaultSync
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:40.282Z
Learning: Silent push wake-ups require a valid APNs token and provisioned device; use Retry APNs Registration and Retry Provisioning in Cloud Relay tab when wake-ups do not arrive despite being subscribed
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:40.282Z
Learning: The Obsidian folder picker must be directed to On My iPhone → Obsidian (or the vault root containing .obsidian directory) to properly connect the Obsidian folder in VaultSync
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:40.282Z
Learning: When vault access fails with bookmark expiration, reconnect to the Obsidian folder by re-selecting it in the Files picker to renew the security-scoped bookmark
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:40.282Z
Learning: For background sync issues, ensure VaultSync is opened in foreground to sync iPhone → server reliably; use Cloud Relay for near-realtime server → iPhone sync; consider Shortcuts automation for instant upload if needed
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:40.282Z
Learning: When a required device is disconnected, verify desktop Syncthing is running and online, both devices can reach each other, and the device still exists in VaultSync; remove and re-add the device if its ID changed
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-06-10T17:45:40.282Z
Learning: When filing issues, include VaultSync version, iOS version, screenshots of Sync Issues and Relay health & diagnostics, vaultsync-notify --doctor output, and relevant log lines
🪛 LanguageTool
docs/instant-upload.md

[style] ~24-~24: Consider an alternative for the overused word “exactly”.
Context: ...ot only when you force-quit it. That is exactly what you want: every editing session en...

(EXACTLY_PRECISELY)

CHANGELOG.md

[uncategorized] ~13-~13: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...ver reported anywhere), warns live when Low Power Mode is deferring silent pushes, and ex...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

🪛 markdownlint-cli2 (0.22.1)
docs/instant-upload.md

[warning] 14-14: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (51)
ios/VaultSync/Services/BackgroundSyncService.swift (1)

27-27: LGTM!

Also applies to: 65-65, 111-125, 145-145, 229-255, 266-266, 793-818

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

108-108: LGTM!

ios/project.yml (1)

62-63: LGTM!

Also applies to: 75-75, 126-127

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

8-8: LGTM!

Also applies to: 33-40, 96-102

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

16-16: LGTM!

Also applies to: 39-39, 42-42, 54-54, 58-58, 64-64, 71-73, 77-84, 106-106

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

69-69: LGTM!

Also applies to: 73-73, 93-93, 95-95

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

81-81: LGTM!

Also applies to: 98-98, 103-103

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

16-16: LGTM!

Also applies to: 37-37

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

51-51: LGTM!

Also applies to: 149-149

ios/VaultSyncWidget/VaultSyncWidget.swift (1)

176-180: LGTM!

notify/main.go (7)

34-42: LGTM!


143-151: LGTM!


220-228: LGTM!


411-421: LGTM!


513-543: LGTM!


563-567: LGTM!


569-621: LGTM!

notify/syncthing.go (4)

178-191: LGTM!


193-200: LGTM!


202-215: LGTM!


217-244: LGTM!

docs/relay-spec.md (4)

59-59: LGTM!


197-197: LGTM!


261-271: LGTM!


274-279: LGTM!

docs/architecture.md (2)

23-23: LGTM!


31-31: LGTM!

docs/instant-upload.md (3)

1-20: LGTM!


22-27: LGTM!


29-31: LGTM!

notify/README.md (1)

64-64: LGTM!

docs/troubleshooting.md (1)

142-142: LGTM!

README.md (1)

131-131: LGTM!

CHANGELOG.md (1)

7-24: LGTM!

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

136-176: LGTM!


33-35: LGTM!

Also applies to: 590-590

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

10-10: LGTM!

Also applies to: 27-29


270-290: LGTM!


396-411: LGTM!


99-99: LGTM!

Also applies to: 111-111, 131-131, 191-191, 221-223, 249-249

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

69-126: LGTM!

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

73-73: LGTM!

Also applies to: 200-200

ios/VaultSync/Services/VaultManager.swift (1)

251-251: LGTM!

Also applies to: 266-266, 279-279

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

70-70: LGTM!

Also applies to: 110-110, 123-123, 135-135, 139-139, 151-151, 158-158, 170-170, 231-231, 276-276, 292-292, 299-299, 318-318, 356-356, 463-463, 598-612

ios/VaultSyncTests/TestSupport.swift (1)

22-39: LGTM!

ios/VaultSyncTests/RelayTriggerStoreTests.swift (1)

1-42: LGTM!

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

10-10: LGTM!

Also applies to: 16-23, 54-54, 70-70, 81-81, 97-97, 110-110, 114-114, 116-116, 123-123, 135-135, 139-139, 151-151, 154-154, 158-158, 170-170, 177-177, 231-231, 241-241, 246-246, 260-260, 266-267, 268-268, 271-276, 280-280, 287-289, 293-293, 299-300, 314-315, 318-318, 339-339, 356-356, 393-393, 402-402, 411-413, 426-429, 435-435, 440-440, 463-463, 465-465, 484-484, 494-497, 499-499, 501-501, 518-522, 524-524, 526-527, 539-539, 583-585, 593-593, 598-600, 603-606, 609-612

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

70-70: LGTM!

Also applies to: 97-97, 110-110, 123-123, 135-135, 139-139, 151-151, 158-158, 170-170, 176-176, 178-178, 231-231, 276-276, 288-289, 293-293, 297-297, 299-299, 318-318, 356-356, 386-386, 463-463, 484-484, 544-544, 588-588, 598-598, 604-604, 606-606, 609-612

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

2-2: LGTM!

Also applies to: 70-70, 110-110, 123-123, 135-135, 139-139, 151-151, 158-158, 170-170, 231-231, 276-276, 293-293, 299-299, 318-318, 356-356, 452-452, 463-463, 489-490, 595-595, 598-598, 604-604, 606-606, 609-612

ios/VaultSyncWidget/de.lproj/Localizable.strings (1)

15-15: LGTM!

Also applies to: 21-21

ios/VaultSync/Services/SubscriptionManager.swift (1)

33-35: LGTM!

Also applies to: 590-590

Comment thread ios/VaultSync/Views/ContentView.swift
Comment thread ios/VaultSync/Views/DesignSystem.swift Outdated
psimaker added 2 commits June 10, 2026 20:18
…outing

Filled StatusTag picked its text color by flipping with the color
scheme (systemBackground), which made light mode white-on-amber at
~2.5:1 — a WCAG failure — while dark mode's lifted tints were fine.
The text now picks black or white by the resolved fill's relative
luminance (break-even 0.179), so both modes get the higher-contrast
choice for any tint.

Checklist remediation routing replaces the 450ms sleep gate with the
settings sheet's onDismiss callback: the pending action is stored on
tap and runs only after the dismissal transition has actually
completed, so it can no longer race slow or interactive transitions.

Both from CodeRabbit review on #38 (the suggested Color.primary fix
was not used — it would have inverted the problem into dark mode).
The completion stub only rejected paused-peer lookups; a regression in
self-device filtering would have stayed green. It now also fails on
queries for the local device ID.
@psimaker

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

1 participant