Skip to content

v1.4.0 — conflict-notification controls, relay reliability, contributions#11

Merged
psimaker merged 16 commits into
mainfrom
fix/issue-10-conflict-notifications
May 30, 2026
Merged

v1.4.0 — conflict-notification controls, relay reliability, contributions#11
psimaker merged 16 commits into
mainfrom
fix/issue-10-conflict-notifications

Conversation

@psimaker

@psimaker psimaker commented May 30, 2026

Copy link
Copy Markdown
Owner

v1.4.0

Closes #10, plus background-sync reliability hardening, a region-correct Cloud Relay price display, and an optional tip jar.

✨ Added

  • Conflict-notifications toggle (Settings → Notifications) — mutes only the conflict banner; Cloud Relay wake-ups and background sync are unaffected. Default on, absent-key-safe so existing installs are never silently muted.
  • Support VaultSync — two one-time, repeatable contributions (StoreKit consumables) that unlock nothing. New product IDs to create in App Store Connect (Consumable):
    • eu.vaultsync.app.contribution.small (base US $2.99)
    • eu.vaultsync.app.contribution.big (base US $9.99)

🔧 Changed

  • Region-correct Cloud Relay price — the subscribe button and the App-Store-Review price line now use a single StoreKit-derived relayPriceText instead of mixing the localized displayPrice with a hard-coded "$0.99/month" (non-US storefronts previously showed two currencies).

🐛 Fixed (#10 + reliability)

  • Conflict-banner spam — stable notification identifier (replaces in place instead of stacking) + persisted last-notified count: alert only when the count grows, quiet refresh when it shrinks, clear at zero.
  • False "Cloud Relay broken" — silent push doesn't need alert authorization, so disabling notifications no longer flags APNs/relay as failed. Relay health now derives from subscription + APNs token + provisioning + recent silent-push trigger; alert permission is shown as a separate informational row.
  • Background sync — single-flight guard (two background wake-ups can't tear down each other's sync), deadline budgeted from sync start (no iOS overrun → no wake throttling), and error-state folders no longer spin the full deadline or raise a misleading "Background Sync Timed Out".
  • Localized the conflict notification body (de/zh-Hans).

✅ Verification

xcodebuild test on iPhone 16 sim: BUILD SUCCEEDED, all tests pass (23 XCTest + 46 Swift Testing across 10 suites, including the new suppression and folder-settlement suites).

🧪 Notes

  • Adds VaultSync.storekit (test-only, wired via project.yml) so IAP is testable in local Debug builds — the project had none.
  • The relay server needs no change: it already sends a correct silent push (apns-push-type: background, content-available:1, no alert), which iOS delivers regardless of alert authorization.
  • Follow-ups (separate issues): .stignore-aware conflict count + dedupe by original, event-based silent-push wake evidence, BGContinuedProcessing cross-mechanism race.

v1.4.0 release: Conflict notification controls, relay reliability, and contribution support

User-visible sync behavior

  • Conflict notifications redesigned: use a stable notification identifier so the banner is replaced in place instead of reposted; alerts are shown only when the unresolved conflict count increases, quiet-updates occur when the count falls, and the banner clears at zero.
  • Per-app toggle: Settings → Notifications → Conflict Notifications mutes only the conflict banner (default ON / absence-key-safe) while preserving Cloud Relay and silent-push background wake-ups.
  • In-app contributions: "Support VaultSync" tip jar with two consumable one-time StoreKit products (small/large tiers) for optional donations.
  • Cloud Relay price text unified and region-correct: UI uses StoreKit-derived relayPriceText for subscribe button and review-price line to avoid mixed-currency display.
  • Localization: conflict notification bodies and UI copy added/updated (English, German, Simplified Chinese) and full Spanish localization added.

Background execution and relay reliability

  • Single-flight guard prevents concurrent background wake-ups from interfering with each other when driving the Syncthing bridge.
  • Deadline budgeting: background sync measures elapsed time from sync start to avoid iOS overrun; the idle loop uses an absolute deadline and stops early when folders settle.
  • Error-state folders: folders in an error state no longer consume the full deadline or surface misleading timeout failures; a new settled-with-folder-error result is used to reflect this outcome without treating it as a relay failure.
  • Relay health diagnosis decoupled from alert authorization: relay composite health now derives from subscription, APNs token/provisioning, and recent silent-push evidence; iOS alert permission is presented as an informational tri-state rather than gating relay health.

Privacy and security

  • No changes to data-collection, permissions, or security model. Silent push behavior clarified: content-available pushes still deliver regardless of alert-permission state.

Test coverage and verification

  • Added unit tests: BackgroundSyncServiceTests with ConflictNotificationActionTests (decision logic scenarios) and FolderSettlementTests (settlement classification).
  • StoreKit test configuration (ios/VaultSync.storekit) added for local IAP testing.
  • Local test run reported via CI/Xcode: xcodebuild on iPhone 16 simulator succeeded (23 XCTest + 46 Swift tests across 10 suites).

Miscellaneous

  • UI polish, theme color constants, Refactors (conflict list/diff views), small API removals/renames, and project config updates (version bump to 1.4.0 / build 25, scheme StoreKit config).

Review Change Stack

psimaker added 7 commits May 30, 2026 07:08
)

The conflict banner was posted with a fresh UUID identifier on every silent
push that reached idle, so iOS never coalesced them — each was a new banner
that lit the screen even when the count was identical ("28 conflicts" over
and over), draining battery.

- Stable "sync-conflict" identifier: a re-post replaces the existing banner
  instead of stacking a new one.
- Persist the last-notified count; only alert (with sound) when the count
  rises, suppress entirely when unchanged, refresh quietly when it falls,
  clear when it hits zero.
- Re-baseline suppression from the foreground poll so a banner the user has
  already seen is not re-alerted, and a brand-new conflict after a full
  resolve still alerts instead of being read as a decrease.
- Localize the previously hard-coded English notification body (en/de/zh-Hans).

Decision logic extracted to a pure conflictNotificationAction(...) with tests.
Adds a dedicated Settings → Notifications toggle that gates only the conflict
banner. Cloud Relay silent-push wake-ups are untouched, so users can silence
conflict spam without disabling iOS notifications (which the app otherwise
misreads as the relay being broken — addressed separately).

- Gate read at the top of notifyConflictsIfAny(), before the per-folder disk
  scan, so turning banners off also skips that recurring I/O.
- Default ON via `object(forKey:) as? Bool ?? true` so existing installs (key
  absent after upgrade) are NOT silently muted — never `bool(forKey:)`.
- Toggle lives in its own Notifications section, independent of the Cloud
  Relay IAP block; @AppStorage-backed, default registered at launch.
- Strings localized for en/de/zh-Hans.
Turning off iOS notifications to silence the conflict banner made the app
report Cloud Relay as broken: refreshNotificationAuthorizationState() forced
APNs status to .failed purely because alert authorization was .denied. But
silent (content-available) pushes — the relay's wake mechanism — are delivered
regardless of alert authorization, so this was a false negative that scared
users off the exact workaround they needed, and cascaded a red "relay broken"
narrative across diagnostics, provisioning hints, and the APNs retry button.

- AppDelegate: stop marking APNs/relay failed on alert-denied entirely. Genuine
  APNs problems still surface via didFailToRegisterForRemoteNotifications.
- SubscriptionManager: expose alertAuthorizationDenied (informational) and a
  composite relayDeliveryLikelyWorking signal built from subscription + token +
  provisioned + (recent silent-push trigger OR healthy endpoint) — independent
  of alert authorization.
- RelayDiagnostics: show alert-banner permission as an informational row with a
  clarifying caption, a positive "delivering wake-ups" line, and reworded hints
  that no longer imply silent push needs notification banners.
- Strings localized for en/de/zh-Hans.
…ror-idle

Three independent reliability fixes surfaced while auditing #10:

- Single-flight guard around performBackgroundSync: two concurrent background
  wake-ups (silent push + BGAppRefresh, or two pushes) could have one task's
  expiration/cleanup stop the bridge mid-sync of the other, silently aborting a
  transfer. The second caller now just nudges a rescan and returns.
- Absolute deadline from sync start instead of "now": the silent-push setup
  waits (folder availability, wake-evidence, optional forced restart) already
  consume part of iOS's ~30s content-available budget. Budgeting from start
  keeps total wall-clock under budget so overruns don't throttle future wakes.
- Error-state folders no longer spin the full deadline: a folder stuck in
  "error" can never reach idle, so the wait loop used to burn the whole budget
  and raise a misleading "Background Sync Timed Out". allFoldersSettledOrErrored
  breaks early and the outcome is classified as settled, not timed out.

Idle/settlement logic extracted to a pure folderSettlement(...) with tests.
Note: cross-mechanism races with BGContinuedProcessing are out of scope here
(tracked for a follow-up); this covers the performBackgroundSync re-entry path.
Adversarial review of the branch surfaced three real bugs and three worth-fixing
nits, all corrected here:

- L1 (medium): the foreground 2s poll keeps running during the ~30s post-
  background grace window and was silently re-baselining the suppression count,
  so a conflict arriving in that window would be read as "unchanged" by the next
  silent push and never alert. Added a scene-active flag; the poll only
  re-baselines while the scene is genuinely active, freezing the baseline once
  backgrounded so such conflicts stay a genuine rise.
- L2 (medium): classifying an error-settled run as .synced made the widget show
  green "idle" while a folder was actually errored. Added a dedicated
  SyncResult.settledWithFolderError (isSuccessful=false so the widget shows
  error, shouldSurfaceIssue=false so it still does NOT raise a misleading
  "Timed Out" issue).
- L5 (low): the fast already-idle path skipped notifyConflictsIfAny entirely;
  now it notifies before cleanup (a quick conflict could otherwise slip through).
- R1: reconcile now skips the write/IPC when the count is unchanged (was hitting
  usernotificationsd every 2s tick in the zero-conflict steady state).
- R2: removed the now-dead `import UserNotifications` from AppDelegate.
- R3: split the relay signal into relayDeliveryConfirmed (recent trigger proves
  end-to-end delivery → "delivering wake-ups") vs relayDeliveryLikelyWorking
  (endpoint reachable only → "looks reachable").

Also gates notifyConflictsIfAny on !sceneActive so a silent push arriving while
the app is open doesn't post a banner over the in-app conflict UI (also removes
the foreground-reconcile vs background-notify race, L4).

Build/test: fixed a ShapeStyle ternary type error in the new alert-banner row
(Color.secondary/Color.green) caught by xcodebuild. App + widget compile clean
under Swift 6 strict concurrency; all unit tests pass (23 XCTest + 46 Swift
Testing across 10 suites, including the new suppression and folder-settlement
suites).
Adds a "Support VaultSync" section to Settings with two one-time, repeatable
contributions (StoreKit consumables) that unlock nothing — they only let users
support development, and can be given as often as they like.

- TipJarManager: loads the two consumables, sorts cheapest→most expensive,
  purchases and finishes the transaction (consumable = nothing to unlock, so
  finishing is the fulfillment). SubscriptionManager's existing updates loop
  finishes any contribution that arrives out-of-band (e.g. approved Ask to Buy).
- SettingsView: own section, per-row localized displayPrice, spinner while
  purchasing, a thank-you alert, and graceful unavailable/loading states.

Also fixes the inconsistent Cloud Relay price display: the subscribe button and
the App-Store-Review price line previously mixed StoreKit's localized
displayPrice with a hard-coded "$0.99/month", so non-US storefronts showed two
different currencies. Both now use a single StoreKit-derived
`relayPriceText` ("0,99 € / month", "A$1.99 / month", …) — never a hard-coded
amount.

New product IDs to create in App Store Connect (Consumable):
  eu.vaultsync.app.contribution.small  (base US $2.99)
  eu.vaultsync.app.contribution.big    (base US $9.99)

Adds VaultSync.storekit (test-only config, wired via project.yml) so IAP is
actually testable in local Debug builds — the project had none. Strings
localized for en/de/zh-Hans.

App + widget compile under Swift 6; all tests pass (23 XCTest + 46 Swift Testing).
Bump app + widget marketing version to 1.4.0 and build to 25. Add the 1.4.0
CHANGELOG entry (conflict-notification controls + reliability, region-correct
Cloud Relay pricing, one-time contributions) and refresh the README "What's New"
highlight.
@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

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 41 minutes and 5 seconds. Learn how PR review limits work.

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

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

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

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0fb71ef3-0266-4f3f-b80e-0f670b5c43e5

📥 Commits

Reviewing files that changed from the base of the PR and between acdf9a5 and 90eeb8d.

📒 Files selected for processing (6)
  • docs/setup.md
  • ios/.gitignore
  • ios/Signing.local.xcconfig.example
  • ios/Signing.xcconfig
  • ios/VaultSync/Views/ConflictListView.swift
  • ios/project.yml
📝 Walkthrough

Walkthrough

Adds user-controlled conflict-banner toggle and stable conflict-notification logic; serializes background sync wake-ups with a single-flight guard; classifies folder settlement and distinguishes a settled-with-folder-error result; adds StoreKit tip-jar products and a TipJarManager plus localized Cloud Relay pricing; updates UI wiring, multiple localizations (de/en/zh/es), tests, and release metadata.

Changes

Conflict notifications, relay diagnostics, and contribution system

Layer / File(s) Summary
All changes (single review checkpoint)
ios/VaultSync/Services/BackgroundSyncService.swift, ios/VaultSync/Services/SyncthingManager.swift, ios/VaultSync/Services/SubscriptionManager.swift, ios/VaultSync/Services/TipJarManager.swift, ios/VaultSync.storekit, ios/VaultSync/App/VaultSyncApp.swift, ios/VaultSync/App/AppDelegate.swift, ios/VaultSync/Views/*, ios/VaultSync/{de,en,zh-Hans,es}.lproj/*, ios/VaultSyncTests/BackgroundSyncServiceTests.swift, ios/project.yml, ...`
Monolithic checkpoint: conflict-notification decision model and persisted baseline; stable notification identifier posting/replace/clear logic and user toggle; scene-active gating and UserDefaults default; single-flight syncInFlightLock to serialize background wake-ups; new FolderSettlement model and SyncResult.settledWithFolderError; refactored deadline-wait loop and fast-idle conflict posting; SubscriptionManager relay delivery/price text additions; StoreKit VaultSync.storekit and TipJarManager for contributions; SettingsView support section and conflict banner toggle; Relay diagnostics UI and APNs guidance updates; multiple UI/text/theme tweaks and widget color change; expanded/added localizations (de/en/zh-Hans/es); tests for conflict action and folder settlement; project/scheme and version bumps.
  • Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

"Conflict banners now bow to user will,
tip jars clink gently on the sill,
sync wakes alone, the lock stands tall,
relay shines bright for one and all." 🎵💜

🚥 Pre-merge checks | ✅ 6 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive Title uses descriptive summary format rather than conventional-commit prefix; however, it clearly and concisely captures the three main features delivered in v1.4.0. Consider using 'feat: v1.4.0 — conflict-notification controls, relay reliability, contributions' or 'release: v1.4.0...' to adopt conventional-commit style while retaining clarity.
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed PR fully addresses Issue #10's core requirement: adds Settings toggle to mute only conflict banners (default enabled), prevents repetitive identical notifications via stable identifiers and persisted last-notified counts, allows background wake-ups and Cloud Relay to function independently, and surfaces conflicts only when count increases.
Out of Scope Changes check ✅ Passed All changes align with v1.4.0 scope: conflict-notification controls, Cloud Relay price accuracy, relay health diagnostics, background-sync reliability, tip jar, Spanish localization, and theme color refactoring are documented in PR objectives and changelog.
No Private Note Leakage ✅ Passed No private data leaked: logging is limited to product IDs, device ID prefix(8), and conflict counts. No secrets, receipt details, vault paths, or new diagnostics endpoints found.
Bounded Ios Background Work ✅ Passed 25s deadline budgeted from sync start, Task.isCancelled checks, expiration handlers stop Syncthing, deferred cleanup, bookmarks released, errors logged safely, tasks completed.
Bridge Contract Compatibility ✅ Passed Removed unused Swift wrappers; underlying gomobile Bridge functions intact. FolderStatusPayload structure and empty-string success convention unchanged. All 31 Bridge calls remain compatible.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-10-conflict-notifications

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

The .xcodeproj is gitignored and regenerated by xcodegen, so signing set in the
Xcode UI is wiped on every `xcodegen generate`. Pin the team (QWTAK63B7C) and
automatic signing in project.yml so it persists across regenerations for the
app, widget, and test targets.

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
ios/VaultSync/App/VaultSyncApp.swift (1)

57-107: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

.inactive still leaves sceneActive latched to the previous value.

The new notification baseline logic assumes “foreground-active” means .active only, but this switch only clears sceneActive on .background. During .inactive, background banner posting stays gated off and the foreground poll can still advance the suppression baseline, which creates a small timing window where a new conflict is treated as already surfaced.

Small fix
         .onChange(of: scenePhase) { _, newPhase in
             switch newPhase {
             case .active:
                 BackgroundSyncService.setSceneActive(true)
                 BackgroundSyncService.endBackgroundAssertion()
                 BackgroundSyncService.cancelContinuedProcessing()
                 if !SyncBridgeService.isRunning() {
                     // Syncthing may have been stopped by a BGTask expiration handler.
                     // Reset Swift-side state so start() works.
                     if syncthingManager.isRunning {
                         syncthingManager.resetForRestart()
                     }
                     vaultManager.restoreAccess()
                     syncthingManager.start()
                 } else if BackgroundSyncService.shouldRescanOnForeground(
                     now: Date(),
                     lastBackgroundedAt: lastBackgroundedAt,
                     threshold: Self.foregroundRescanThreshold
                 ) {
                     // Bridge is still alive but the app spent enough time in
                     // the background that the user likely edited the vault
                     // from another app (e.g. Obsidian). The iOS FSWatcher
                     // doesn't reliably see cross-sandbox writes, so trigger
                     // a fresh scan to pick them up immediately.
                     syncthingManager.triggerForegroundSync()
                 }
                 lastBackgroundedAt = nil
+            case .inactive:
+                BackgroundSyncService.setSceneActive(false)
             case .background:
                 lastBackgroundedAt = Date()
                 BackgroundSyncService.setSceneActive(false)
🤖 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/App/VaultSyncApp.swift` around lines 57 - 107, The switch on
scenePhase leaves sceneActive true during .inactive; update the onChange
handling so .inactive clears the foreground latch by calling
BackgroundSyncService.setSceneActive(false) (and any minimal teardown needed to
match .background behavior, e.g., releaseForegroundLifecycleLock() if
appropriate) — add a .inactive case in the same switch (near the
.active/.background cases) that clears sceneActive so the notification baseline
logic and suppression polling behave correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ios/VaultSync/Services/BackgroundSyncService.swift`:
- Around line 951-966: currentConflictCount() can return 0 on decode failures
which causes notifyConflictsIfAny() to take the .clear branch and remove
delivered notifications incorrectly; change the flow so decode/parse failures
are treated as "unreadable" rather than zero: make currentConflictCount() return
a Result/optional or throw on decode errors (or add a separate
currentConflictCountIsValid flag), update
conflictNotificationAction(currentCount:lastNotifiedCount:) to accept/recognize
an invalid/unreadable state and map that state to .suppress (or a new .unknown
state that does not clear notifications), and update notifyConflictsIfAny() to
avoid calling UNUserNotificationCenter.removeDeliveredNotifications or resetting
lastNotifiedConflictCountKey when the count is invalid/unreadable (use
identifiers: currentConflictCount(), conflictNotificationAction(),
notifyConflictsIfAny(), lastNotifiedConflictCountKey,
conflictNotificationIdentifier to find the places to change).

In `@ios/VaultSync/Views/RelayDiagnosticsView.swift`:
- Around line 129-133: The diagnostics row currently uses
subscriptionManager.alertAuthorizationDenied (a Bool) which treats any
non-denied state as "Allowed"; update RelayDiagnosticsView to drive the UI from
real UNNotificationSettings instead: add a computed property on
SubscriptionManager (e.g., notificationAlertStatus or a tri-state enum like
.allowed/.denied/.unknown) that calls
UNUserNotificationCenter.current().getNotificationSettings and evaluates
settings.authorizationStatus == .authorized AND settings.alertSetting ==
.enabled to return .allowed, return .denied for .denied or alertSetting ==
.disabled, otherwise .unknown; then replace the Text and foregroundStyle usage
in RelayDiagnosticsView to switch on that new property and show
"Allowed"/"Denied"/"Unknown" with appropriate colors (Color.green,
Color.secondary, Color.yellow) so the row reflects true alert banner capability
instead of a single Bool.

In `@ios/VaultSync/zh-Hans.lproj/Localizable.strings`:
- Line 524: Update the translation for the string "Show a banner when sync
conflicts are detected. Turning this off does not affect Cloud Relay or
background sync — your vault keeps syncing." so it uses the product's "Vault"
terminology instead of the generic "你的库", e.g. change "你的库会继续同步" to "你的 Vault
会继续同步" or "你的库(Vault)会继续同步" in the Localizable.strings entry to match other
Vault wording.

---

Outside diff comments:
In `@ios/VaultSync/App/VaultSyncApp.swift`:
- Around line 57-107: The switch on scenePhase leaves sceneActive true during
.inactive; update the onChange handling so .inactive clears the foreground latch
by calling BackgroundSyncService.setSceneActive(false) (and any minimal teardown
needed to match .background behavior, e.g., releaseForegroundLifecycleLock() if
appropriate) — add a .inactive case in the same switch (near the
.active/.background cases) that clears sceneActive so the notification baseline
logic and suppression polling behave correctly.
🪄 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: 623edf3f-be8d-4145-9906-1a2bbb1ba968

📥 Commits

Reviewing files that changed from the base of the PR and between 9c36fbd and 426107a.

📒 Files selected for processing (16)
  • CHANGELOG.md
  • README.md
  • ios/VaultSync.storekit
  • ios/VaultSync/App/AppDelegate.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/Services/TipJarManager.swift
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/Views/SettingsView.swift
  • ios/VaultSync/de.lproj/Localizable.strings
  • ios/VaultSync/en.lproj/Localizable.strings
  • ios/VaultSync/zh-Hans.lproj/Localizable.strings
  • ios/VaultSyncTests/BackgroundSyncServiceTests.swift
  • ios/project.yml
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*

⚙️ 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/project.yml
  • ios/VaultSync.storekit
  • ios/VaultSync/Services/SubscriptionManager.swift
  • CHANGELOG.md
  • README.md
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/de.lproj/Localizable.strings
  • ios/VaultSync/Views/SettingsView.swift
  • ios/VaultSync/zh-Hans.lproj/Localizable.strings
  • ios/VaultSync/en.lproj/Localizable.strings
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/Services/TipJarManager.swift
  • ios/VaultSyncTests/BackgroundSyncServiceTests.swift
  • ios/VaultSync/App/AppDelegate.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
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
**/*.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.

Follow Swift API Design Guidelines for API naming, parameter conventions, and code organization in Swift files

Use Swift strict concurrency where applicable to ensure thread-safe code in Swift

Files:

  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Views/SettingsView.swift
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/Services/TipJarManager.swift
  • ios/VaultSyncTests/BackgroundSyncServiceTests.swift
  • ios/VaultSync/App/AppDelegate.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
ios/**/*.swift

📄 CodeRabbit inference engine (README.md)

Use SwiftUI for UI development in the iOS app

Implement BGAppRefreshTask and BGContinuedProcessingTask for iOS background execution in the iOS app

Use APNs silent notifications via Cloud Relay for push wake-ups in the iOS app

Files:

  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Views/SettingsView.swift
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/Services/TipJarManager.swift
  • ios/VaultSyncTests/BackgroundSyncServiceTests.swift
  • ios/VaultSync/App/AppDelegate.swift
  • ios/VaultSync/Services/BackgroundSyncService.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/Services/SubscriptionManager.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/App/VaultSyncApp.swift
  • ios/VaultSync/Views/SettingsView.swift
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/Services/TipJarManager.swift
  • ios/VaultSyncTests/BackgroundSyncServiceTests.swift
  • ios/VaultSync/App/AppDelegate.swift
  • ios/VaultSync/Services/BackgroundSyncService.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:

  • CHANGELOG.md
  • README.md
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-05-30T06:20:57.301Z
Learning: Use Conventional Commits format for all commit messages
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-05-30T06:20:57.301Z
Learning: Provide clear PR descriptions in all pull requests
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-05-30T06:20:57.301Z
Learning: Sync Obsidian vaults directly into Obsidian's iOS sandbox folder
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-05-30T06:20:57.301Z
Learning: Use Syncthing v2.x via Go/gomobile .xcframework as the sync engine
🔇 Additional comments (11)
ios/VaultSyncTests/BackgroundSyncServiceTests.swift (1)

5-68: LGTM!

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

517-562: LGTM!

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

518-563: LGTM!

CHANGELOG.md (1)

7-24: LGTM!

README.md (1)

102-104: LGTM!

ios/project.yml (1)

33-35: LGTM!

Also applies to: 54-55, 104-105

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

28-88: LGTM!

Also applies to: 255-255

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

31-39: LGTM!

Also applies to: 317-321

ios/VaultSync.storekit (1)

1-74: LGTM!

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

8-97: LGTM!

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

13-15: LGTM!

Also applies to: 22-23, 99-109, 196-197, 234-336

Comment thread ios/VaultSync/Services/BackgroundSyncService.swift Outdated
Comment thread ios/VaultSync/Views/RelayDiagnosticsView.swift Outdated
Comment thread ios/VaultSync/zh-Hans.lproj/Localizable.strings Outdated
…h wording)

- Unreadable conflict snapshot no longer mistaken for "no conflicts":
  currentConflictCount() returns Int? and a folder-list or per-folder decode
  failure now suppresses instead of collapsing to 0. The old behaviour removed
  the delivered banner and reset the baseline on a transient read failure, then
  re-alerted the still-present conflicts as new on the next successful read.
- "Alert Banners" diagnostics row reflects real capability via a tri-state
  (allowed/denied/unknown) read from UNNotificationSettings — authorized alone
  isn't enough; a user can keep authorization but turn banners off
  (alertSetting == .disabled). Replaces the previous denied/allowed Bool.
- zh-Hans: use "Vault" instead of the generic "库" in the conflict-notifications
  toggle footer, matching the product terminology used elsewhere.

Skipped the .inactive scene-phase suggestion: .inactive is a transient
foreground sub-state (app switcher, Control Center, incoming call) and
.background reliably clears the scene-active latch, so clearing it on .inactive
would instead post conflict banners over the on-screen app.
@psimaker

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

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.

psimaker added 6 commits May 30, 2026 09:56
Dead code (all verified unreferenced across app, widget and tests):
- drop unused bridge wrappers getConfigJSON / setDiscoveryEnabled
- drop unused isPresetActive, isFolderSyncing and two folderLabel props
- drop never-read APNsRegistrationStore.Snapshot.status/failureReason

UI / visual polish (clear-improvement only, no v1.4.0 logic change):
- shared Color.vaultTeal/.vaultSlate palette (app + widget); unify the
  syncing-state colour across dashboard, vault rows and widget
- ConflictListView reads live conflictFiles so a resolved conflict vanishes,
  with an "all resolved" empty state; shared locale-aware conflict-date
  formatter so the diff header no longer shows the raw Syncthing timestamp
- line-diff legend (+/- symbols, colourblind/VoiceOver-safe) and
  equal-width row backgrounds; ASCII "..." -> "…"
- Subscribe spinner, Copy Device ID haptic + confirmation, Restore Purchases
  progress, pending-contribution notice
- consistent APNs buttons (>=44pt), neutral "Unknown" alert-banner colour,
  drop the misdirecting conflict "Learn how to fix" link
- device status "Offline" -> "Disconnected" to match the detail screen
- localize the background continued-processing title/subtitle and both
  conflict-diff pane titles
- add 46 keys that were silently falling back to English in de/zh
  (conflict resolution, device removal, background-sync diagnostics,
  relay errors, VoiceOver hints and the new UI feedback strings)
- remove 76 orphaned keys left behind by the onboarding/checklist/relay
  rewrites, incl. the stale hard-coded relay price and the unused QR Code key
- de-duplicate three keys whose de/zh copies had silently diverged,
  keeping the currently-shipping wording (last-wins)
- fix German in-progress tense (Synchronisiert -> Wird synchronisiert) and
  the zh notification-banner term for consistency
- result: en/de/zh at 510 keys each, no duplicates, no orphans,
  matching format specifiers
The bare Text(lastSync, style: .relative) renders only the magnitude
("2 hr", no "ago") and the appended L10n.tr("ago") leaked an untranslated
English "ago" into de/zh. Replace it with a fully localized relative phrase
from a cached RelativeDateTimeFormatter (unitsStyle .full) — "2 hours ago" /
"vor 2 Stunden" / "2 小时前" — rendered through one "Last sync: %@" key.

- new key reuses the existing "Last sync:" translations; the old key is dropped
- scope is this line only; the six other bare .relative usages are left as-is
- trade-off: the phrase is static (no live tick), fine for a last-sync label
- en/de/zh stay at 510 keys each, no orphans/dups, plutil OK; tests green
Add complete Spanish localization for the app (510 keys) and the
home-screen widget (16 keys), mirroring the English source exactly,
plus per-locale InfoPlist.strings for both targets.

- es.lproj/Localizable.strings + InfoPlist.strings for app and widget
- Informal tú register; brand terms (Vault, VaultSync, Cloud Relay,
  Syncthing, Obsidian, APNs) kept untranslated; every format specifier
  preserved and order-checked against en
- Wire es into knownRegions via the project.yml postGenCommand
  (Base, de, en, es, "zh-Hans")
- CHANGELOG 1.4.0 + README note that Spanish is now supported

Verified: plutil -lint clean; es-vs-en parity 510/510 app, 16/16 widget
(0 missing/extra/duplicate; format specifiers match); xcodebuild test
green (23 XCTest + 46 Swift Testing).
Part B — marketing copy (en + de/es/zh; English-key renames + Swift literals):
- Support footer: reframe around VaultSync being an independent, open-source
  (MPL-2.0), ad-free project; keep the "unlocks nothing / fully functional /
  give as often as you like" honesty verbatim.
- Cloud Relay footer: drop the over-stated "instant sync" claim for the honest
  silent-push framing — changes wake the app the moment they happen so incoming
  sync feels instant, and the relay only sends a wake-up signal, never sees your
  notes.
- Onboarding relay line: same concrete, honest framing.
- Untouched by design: the StoreKit price line "Cloud Relay — %@", the
  auto-renew/cancel terms line, and the Subscribe button.

Part A — consistency/quality (translated values only; English keys unchanged):
- de: "ein Banner" gender fix; unify "rate-limitiert"; standardize prose on
  "Hintergrundsynchronisation"; "Das Relay" (neuter).
- es: "Vault syncing" -> "Vault sincronizándose" (state, matches the checklist
  siblings; disambiguates it from the "Syncing Vault" action).
- zh: unify 限流 and 支持 terminology; full-width parens; parallel "%@ 中…"
  activity-log phrasing.

4-language parity intact (510 app keys each, 0 dups/orphans, format specifiers
matched), plutil -lint OK on all 8 strings files, xcodebuild test green (46 tests).
The zh-Hans translation of "%d additional files synced in %@" reordered the
folder before the count without positional specifiers, so String(format:) bound
the Int count to %@ (treated as an object pointer → crash) and the folder String
to %d. Switched to "%2$@ … %1$d" so the args map to the correct positions; en/de/es
already follow en's order. Reachable on Chinese devices when one poll syncs more
than 6 files in a single folder (rate-limited activity summary).

@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: 1

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/ConflictListView.swift (1)

73-83: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Respect locale + 12/24-hour preferences in formattedConflictDate
conflictDateDisplay hard-codes DateFormatter’s dateFormat = "yyyy-MM-dd HH:mm" (no localized styles/locale), so the output won’t follow the user’s locale formatting or 12/24-hour setting despite the “locale-aware” doc comment.

Suggested fix
     private static let conflictDateDisplay: DateFormatter = {
         let f = DateFormatter()
-        f.dateFormat = "yyyy-MM-dd HH:mm"
+        f.locale = .autoupdatingCurrent
+        f.dateStyle = .medium
+        f.timeStyle = .short
         return f
     }()
🤖 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/ConflictListView.swift` around lines 73 - 83, The
DateFormatter in conflictDateDisplay currently hardcodes dateFormat which
ignores user locale and 12/24-hour preferences; update conflictDateDisplay (used
by formattedConflictDate) to use localized styles instead of a fixed dateFormat
(e.g., set dateStyle and timeStyle such as .medium/.short) and ensure the
formatter's locale is Locale.current so output respects the user's locale and
12/24-hour settings; keep the static property and reuse it in
formattedConflictDate as before.
🤖 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/project.yml`:
- Around line 27-31: The shared ios/project.yml currently hard-codes
DEVELOPMENT_TEAM: QWTAK63B7C in the base settings which forces regenerated
projects to that team; remove this hard-coded DEVELOPMENT_TEAM from
settings.base and instead document or wire signing to a local override or
CI-only mechanism (e.g., per-developer xcconfig or an environment-variable
injected value in CI) so contributors can regenerate without local patches;
update any docs or README to show how to set DEVELOPMENT_TEAM locally and ensure
CODE_SIGN_STYLE remains if needed.

---

Outside diff comments:
In `@ios/VaultSync/Views/ConflictListView.swift`:
- Around line 73-83: The DateFormatter in conflictDateDisplay currently
hardcodes dateFormat which ignores user locale and 12/24-hour preferences;
update conflictDateDisplay (used by formattedConflictDate) to use localized
styles instead of a fixed dateFormat (e.g., set dateStyle and timeStyle such as
.medium/.short) and ensure the formatter's locale is Locale.current so output
respects the user's locale and 12/24-hour settings; keep the static property and
reuse it in formattedConflictDate as before.
🪄 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: eb2f7ffe-3cc9-4b7f-95b8-f9405ebb2f0f

📥 Commits

Reviewing files that changed from the base of the PR and between 426107a and acdf9a5.

📒 Files selected for processing (30)
  • CHANGELOG.md
  • README.md
  • ios/VaultSync/App/AppDelegate.swift
  • ios/VaultSync/Models/RelayProvisionStatus.swift
  • ios/VaultSync/Resources/Theme.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Services/SyncBridgeService.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/Services/TipJarManager.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Views/ConflictListView.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/Views/IgnorePatternsView.swift
  • ios/VaultSync/Views/LineDiffView.swift
  • ios/VaultSync/Views/OnboardingView.swift
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/Views/SettingsView.swift
  • ios/VaultSync/Views/SyncFilterRecommendationSheet.swift
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/de.lproj/Localizable.strings
  • ios/VaultSync/en.lproj/Localizable.strings
  • ios/VaultSync/es.lproj/InfoPlist.strings
  • ios/VaultSync/es.lproj/Localizable.strings
  • ios/VaultSync/zh-Hans.lproj/Localizable.strings
  • ios/VaultSyncWidget/VaultSyncWidget.swift
  • ios/VaultSyncWidget/de.lproj/Localizable.strings
  • ios/VaultSyncWidget/es.lproj/InfoPlist.strings
  • ios/VaultSyncWidget/es.lproj/Localizable.strings
  • ios/project.yml
💤 Files with no reviewable changes (5)
  • ios/VaultSync/Views/SyncFilterRecommendationSheet.swift
  • ios/VaultSync/Views/IgnorePatternsView.swift
  • ios/VaultSync/Services/SyncthingManager.swift
  • ios/VaultSync/Services/SyncBridgeService.swift
  • ios/VaultSync/Models/RelayProvisionStatus.swift
✅ Files skipped from review due to trivial changes (6)
  • ios/VaultSyncWidget/de.lproj/Localizable.strings
  • ios/VaultSyncWidget/es.lproj/InfoPlist.strings
  • ios/VaultSync/es.lproj/InfoPlist.strings
  • ios/VaultSyncWidget/es.lproj/Localizable.strings
  • ios/VaultSync/Resources/Theme.swift
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • README.md
  • ios/VaultSync/Views/RelayDiagnosticsView.swift
  • ios/VaultSync/App/AppDelegate.swift
  • ios/VaultSync/Views/SettingsView.swift
📜 Review details
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build & Test
🧰 Additional context used
📓 Path-based instructions (4)
**/*.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.

Follow Swift API Design Guidelines for naming, parameter conventions, and overall API structure

Use Swift strict concurrency where applicable to ensure thread-safe code and leverage Swift 6's concurrency features

Files:

  • ios/VaultSync/Views/OnboardingView.swift
  • ios/VaultSyncWidget/VaultSyncWidget.swift
  • ios/VaultSync/Views/LineDiffView.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Views/ConflictListView.swift
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/Services/TipJarManager.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
ios/**/*.swift

📄 CodeRabbit inference engine (README.md)

Follow SwiftUI conventions for view composition, state management, and layout in iOS UI code

Files:

  • ios/VaultSync/Views/OnboardingView.swift
  • ios/VaultSyncWidget/VaultSyncWidget.swift
  • ios/VaultSync/Views/LineDiffView.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Views/ConflictListView.swift
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/Services/TipJarManager.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/Services/BackgroundSyncService.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/OnboardingView.swift
  • ios/VaultSyncWidget/VaultSyncWidget.swift
  • ios/VaultSync/Views/LineDiffView.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Views/ConflictListView.swift
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/Services/TipJarManager.swift
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
**/*

⚙️ CodeRabbit configuration file

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

Files:

  • ios/VaultSync/Views/OnboardingView.swift
  • ios/VaultSyncWidget/VaultSyncWidget.swift
  • ios/VaultSync/Views/LineDiffView.swift
  • ios/VaultSync/Views/ConflictDiffView.swift
  • ios/VaultSync/Services/SubscriptionManager.swift
  • ios/VaultSync/Views/ConflictListView.swift
  • ios/VaultSync/Views/SyncIssuesView.swift
  • ios/VaultSync/Services/TipJarManager.swift
  • ios/VaultSync/es.lproj/Localizable.strings
  • ios/project.yml
  • ios/VaultSync/Views/ContentView.swift
  • ios/VaultSync/Services/BackgroundSyncService.swift
  • ios/VaultSync/de.lproj/Localizable.strings
  • ios/VaultSync/zh-Hans.lproj/Localizable.strings
  • ios/VaultSync/en.lproj/Localizable.strings
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
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-05-30T09:19:16.348Z
Learning: Use Conventional Commits for commit messages to maintain clear project history
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-05-30T09:19:16.348Z
Learning: Provide clear PR descriptions when contributing to explain the purpose, scope, and impact of changes
🔇 Additional comments (7)
ios/VaultSync/Services/TipJarManager.swift (1)

31-33: LGTM!

Also applies to: 64-64, 85-86

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

41-41: LGTM!

Also applies to: 56-56, 70-70, 190-232

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

18-29: LGTM!

Also applies to: 172-172, 576-576, 776-776

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

1-533: LGTM!

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

36-36: LGTM!

Also applies to: 119-119, 248-249, 356-356, 397-397, 446-446, 481-481, 486-532

ios/VaultSyncWidget/VaultSyncWidget.swift (1)

67-67: LGTM!

ios/project.yml (1)

16-16: LGTM!

Also applies to: 40-40, 59-60, 106-108, 112-113

Comment thread ios/project.yml Outdated
…erride)

- ConflictListView.conflictDateDisplay hard-coded "yyyy-MM-dd HH:mm" despite its
  "locale-aware" doc comment, ignoring the user's locale and 12/24-hour setting.
  Now uses localized dateStyle/timeStyle with an .autoupdatingCurrent locale.
- Moved DEVELOPMENT_TEAM out of the committed project.yml so a public-repo
  contributor's `xcodegen generate` is no longer forced onto the maintainer's
  team. Signing flows from Signing.xcconfig (committed, no team) which optionally
  includes a gitignored Signing.local.xcconfig (your DEVELOPMENT_TEAM) via
  `#include?`. That include is skipped when the file is absent, so fresh clones /
  CI generate and build for the Simulator unchanged. Copy
  Signing.local.xcconfig.example to set yours; documented in docs/setup.md.

Verified: xcodegen generate succeeds with AND without the local override; no team
in the generated pbxproj; build + tests green (23 XCTest + 46 Swift Testing).
@psimaker psimaker merged commit 0867083 into main May 30, 2026
6 checks passed
@psimaker psimaker deleted the fix/issue-10-conflict-notifications branch May 30, 2026 09:56
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.

Option to turn off conflict notifications

1 participant