Skip to content

Commit ae76cf9

Browse files
committed
feat: refresh onboarding, setup status, and docs for v1.1.0
1 parent baf35e7 commit ae76cf9

19 files changed

Lines changed: 573 additions & 329 deletions

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ All notable changes to VaultSync are documented here.
44

55
---
66

7+
## [1.1.0] — 2026-04-22
8+
9+
### Added
10+
11+
- **Localized setup surfaces** — The new onboarding and setup-status copy now ships in English, German, and Simplified Chinese.
12+
13+
### Changed
14+
15+
- **Calmer first launch** — First-run onboarding is now a short, informational 2-screen introduction. Real setup stays on the VaultSync home screen, where pairing, pending shares, vault activity, and sync status already live.
16+
- **Setup Guide -> Setup Status** — Settings now open a live setup-status and troubleshooting view instead of the old onboarding-style guide. It focuses on essential sync readiness and points users back to the home screen for action.
17+
- **More honest vault-sync status** — A pending share no longer counts as “done”. Setup Status now keeps vault syncing marked as needing attention until at least one vault is actually active.
18+
- **Cleaner Settings** — Discovery controls were removed from Settings to reduce noise. Discovery remains enabled by default.
19+
- **iOS support messaging corrected** — Project docs and release metadata now consistently reflect VaultSync’s iOS/iPadOS 18+ support, while `BGContinuedProcessingTask` remains an iOS 26+ enhancement when available.
20+
721
## [1.0.2] — 2026-04-12
822

923
### Fixed

README.md

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Your notes, your devices, your server. No managed note-sync cloud required.
2121
[![CI](https://img.shields.io/github/actions/workflow/status/psimaker/vaultsync/ci.yml?style=flat-square&logo=github-actions&logoColor=white&label=Build)](https://github.com/psimaker/vaultsync/actions/workflows/ci.yml)
2222
[![Last Commit](https://img.shields.io/github/last-commit/psimaker/vaultsync?style=flat-square&label=Last+Commit)](https://github.com/psimaker/vaultsync/commits)
2323

24-
[![iOS 26+](https://img.shields.io/badge/iOS-26%2B-007AFF?style=flat-square&logo=apple&logoColor=white)](https://developer.apple.com/ios/)
24+
[![iOS 18+](https://img.shields.io/badge/iOS-18%2B-007AFF?style=flat-square&logo=apple&logoColor=white)](https://developer.apple.com/ios/)
2525
[![Swift 6](https://img.shields.io/badge/Swift-6-FA7343?style=flat-square&logo=swift&logoColor=white)](https://swift.org)
2626
[![Xcode 26+](https://img.shields.io/badge/Xcode-26%2B-147EFB?style=flat-square&logo=xcode&logoColor=white)](https://developer.apple.com/xcode/)
2727
[![Open Issues](https://img.shields.io/github/issues/psimaker/vaultsync?style=flat-square&label=Issues)](https://github.com/psimaker/vaultsync/issues)
@@ -99,21 +99,19 @@ VaultSync is also not a magic always-on Syncthing daemon for iOS. Apple’s back
9999

100100
---
101101

102-
## What’s New — v1.0.2
102+
## What’s New — v1.1.0
103103

104-
> **Reliable Silent-Push Sync**Background sync now correctly waits for pulls to finish instead of shutting down at the first idle flicker. Fixes the core reason why pushes sometimes arrived but files did not.
104+
> **Calmer First Launch**First-run onboarding is now a short 2-screen introduction. Setup itself happens on the VaultSync home screen, where the real device, vault, and sync controls already live.
105105
>
106-
> **Server Edits Wake iPhone Again**`vaultsync-notify` now reacts to real outgoing change markers on the homeserver, so direct edits on your server trigger an iPhone wake-up again without falling back to noisy state-transition pushes.
106+
> **Setup Status in Settings**The old setup guide is now a live status and troubleshooting view that highlights what is ready, what still needs attention, and where to fix it.
107107
>
108-
> **Recovery When iOS Resumes Cold**If a silent push wakes VaultSync but Syncthing does not show real peer activity quickly enough, VaultSync now force-restarts the embedded engine and retries within the same background run.
108+
> **Pending Shares Need Action**A waiting vault offer no longer looks finished. Vault syncing is only marked ready once at least one vault is actually active.
109109
>
110-
> **Longer iOS Grace Period** — The app now holds a background-task assertion on suspend, giving Syncthing more time to wrap up in-flight operations.
110+
> **Cleaner Settings** — The old discovery controls are gone, reducing noise in Settings while leaving discovery enabled by default.
111111
>
112-
> **Correct Vault Path**Accepting a pending share no longer creates a redundant nested subfolder when the selected Obsidian root already matches the share name.
112+
> **Localized Setup Flow**The refreshed onboarding and setup-status copy is now available in English, German, and Simplified Chinese.
113113
>
114-
> **Gentler Push Pressure**`vaultsync-notify` now deduplicates wake-ups per real change marker, reducing the chance of iOS throttling while still covering direct server-side edits.
115-
>
116-
> **Faster Recovery** — Relay re-provisioning interval reduced from 24h to 6h so a server-side token cleanup heals automatically.
114+
> **iOS 18+** — VaultSync now supports iOS/iPadOS 18 and later.
117115
118116
See [CHANGELOG.md](CHANGELOG.md) for full details.
119117

@@ -133,9 +131,9 @@ VaultSync uses Syncthing to sync your vault between your own devices over LAN or
133131

134132
VaultSync syncs directly into Obsidian’s iOS sandbox. Open Obsidian and your vault is where it should be.
135133

136-
### Guided setup
134+
### Calm onboarding, real setup
137135

138-
A 5-step checklist helps you pair your iPhone or iPad with an existing Syncthing device via QR code.
136+
A short first-run onboarding explains the flow, and the actual setup happens on the home screen where you can connect Obsidian, pair devices, accept shares, and monitor sync.
139137

140138
### Markdown conflict resolver
141139

@@ -307,7 +305,7 @@ See [notify/README.md](notify/README.md) for full configuration options.
307305
308306
| Requirement | Details |
309307
|---|---|
310-
| iPhone or iPad | iOS/iPadOS 26 or later |
308+
| iPhone or iPad | iOS/iPadOS 18 or later |
311309
| Obsidian | Installed on iOS/iPadOS |
312310
| Syncthing | Running on a Mac, Linux machine, NAS, or homeserver |
313311
| Cloud Relay | Optional, available via in-app purchase |
@@ -346,10 +344,10 @@ Until then, VaultSync works in two modes:
346344

347345
| | |
348346
|---|---|
349-
| Platform | iOS/iPadOS 26+ |
347+
| Platform | iOS/iPadOS 18+ |
350348
| Language | Swift 6, SwiftUI |
351349
| Sync engine | Syncthing v2.x via Go/gomobile `.xcframework` |
352-
| Background execution | `BGAppRefreshTask` + `BGContinuedProcessingTask` |
350+
| Background execution | `BGAppRefreshTask` + `BGContinuedProcessingTask` (iOS 26+ when available) |
353351
| Push wake-ups | APNs silent notifications via Cloud Relay |
354352
| Notify sidecar | Docker container for homeserver/NAS setups |
355353
| License | MPL-2.0 |

docs/architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Key exports:
3939
## Sync Strategy
4040

4141
- **Foreground:** Syncthing runs unrestricted. Immediate, continuous sync.
42-
- **Background:** `BGAppRefreshTask` (~30s) + `BGContinuedProcessingTask` (iOS 26, longer runtime for user-initiated tasks).
42+
- **Background:** `BGAppRefreshTask` (~30s) + `BGContinuedProcessingTask` (iOS 26+ when available, longer runtime for user-initiated tasks).
4343
- **Push sync (Cloud Relay):** Optional. Near-realtime `server -> iPhone` wake-ups via APNs silent push notifications. See [relay-spec.md](relay-spec.md).
4444

4545
## Directional Behavior

docs/images/screenshot-home.png

-834 Bytes
Loading

docs/images/screenshot-welcome.png

-51.2 KB
Loading

docs/setup.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ make patch # creates _syncthing_patched/ with applied fixes
3535
make xcframework
3636
```
3737

38-
This produces `go/build/SyncBridge.xcframework` (~30-50 MB) targeting iOS 26+ (arm64) and iOS Simulator (arm64).
38+
This produces `go/build/SyncBridge.xcframework` (~30-50 MB) targeting iOS 18+ (arm64) and iOS Simulator (arm64).
3939

4040
### 5. Generate the Xcode project
4141

@@ -50,7 +50,7 @@ xcodegen generate
5050
open VaultSync.xcodeproj
5151
```
5252

53-
Select the **VaultSync** scheme, choose a device or simulator (iOS 26+), and build (Cmd+B).
53+
Select the **VaultSync** scheme, choose a supported device or simulator (iOS 18+), and build (Cmd+B).
5454

5555
## Running Tests
5656

@@ -71,5 +71,5 @@ cd ios && xcodebuild test \
7171
- **gomobile not found:** Ensure `$(go env GOPATH)/bin` is in your `$PATH`.
7272
- **xcframework build fails:** Run `make clean` first, then retry `make xcframework`.
7373
- **Xcode project missing:** Run `xcodegen generate` in the `ios/` directory.
74-
- **Simulator not available:** Ensure you have the iOS 26+ simulator runtime installed in Xcode.
74+
- **Simulator not available:** Ensure you have a supported iOS 18+ simulator runtime installed in Xcode.
7575
- **Runtime or sync issues after build:** Use [docs/troubleshooting.md](troubleshooting.md).

ios/VaultSync/ViewModels/SetupChecklistViewModel.swift

Lines changed: 35 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ final class SetupChecklistViewModel {
4141

4242
var items: [ChecklistItem] {
4343
[
44-
syncthingItem,
45-
desktopDeviceItem,
4644
obsidianItem,
45+
desktopDeviceItem,
4746
firstShareItem,
47+
syncthingItem,
4848
relayItem
4949
]
5050
}
@@ -78,22 +78,19 @@ final class SetupChecklistViewModel {
7878
if syncthingManager.isRunning, !syncthingManager.deviceID.isEmpty {
7979
return ChecklistItem(
8080
requirement: .syncthingRunning,
81-
title: L10n.tr("Syncthing engine started"),
82-
description: L10n.tr("Device ID is available and Syncthing is running."),
81+
title: L10n.tr("Sync engine running"),
82+
description: L10n.tr("VaultSync’s sync engine is running."),
8383
remediation: "",
8484
isOptional: false,
8585
isComplete: true
8686
)
8787
}
8888

89-
let message = syncthingManager.userError?.message ?? L10n.tr("VaultSync is still starting Syncthing.")
90-
let remediation = syncthingManager.userError?.remediation
91-
?? L10n.tr("Keep the app open for a moment. If this persists, restart VaultSync.")
9289
return ChecklistItem(
9390
requirement: .syncthingRunning,
94-
title: L10n.tr("Syncthing engine started"),
95-
description: message,
96-
remediation: remediation,
91+
title: L10n.tr("Sync engine running"),
92+
description: L10n.tr("VaultSync’s sync engine is still starting or unavailable."),
93+
remediation: L10n.tr("If this stays unavailable, restart VaultSync and check the home screen for issues."),
9794
isOptional: false,
9895
isComplete: false
9996
)
@@ -104,8 +101,8 @@ final class SetupChecklistViewModel {
104101
if count > 0 {
105102
return ChecklistItem(
106103
requirement: .desktopDeviceAdded,
107-
title: L10n.tr("Desktop device paired"),
108-
description: countDescription(count, singular: L10n.tr("device configured"), plural: L10n.tr("devices configured")),
104+
title: L10n.tr("Computer or server added"),
105+
description: L10n.tr("Your iPhone is paired with at least one Syncthing device."),
109106
remediation: "",
110107
isOptional: false,
111108
isComplete: true
@@ -114,36 +111,31 @@ final class SetupChecklistViewModel {
114111

115112
return ChecklistItem(
116113
requirement: .desktopDeviceAdded,
117-
title: L10n.tr("Desktop device paired"),
118-
description: L10n.tr("No desktop or laptop Syncthing device configured yet."),
119-
remediation: L10n.tr("Add a device from the main screen using its Syncthing Device ID."),
114+
title: L10n.tr("Computer or server added"),
115+
description: L10n.tr("Your iPhone is not paired with a Syncthing device yet."),
116+
remediation: L10n.tr("Add your computer or server from the Devices section on the home screen."),
120117
isOptional: false,
121118
isComplete: false
122119
)
123120
}
124121

125122
private var obsidianItem: ChecklistItem {
126123
if vaultManager.isAccessible {
127-
let vaultCount = vaultManager.detectedVaults.count
128-
let detail = vaultCount > 0
129-
? L10n.fmt("Connected. %@", countDescription(vaultCount, singular: L10n.tr("vault detected"), plural: L10n.tr("vaults detected")))
130-
: L10n.tr("Connected. Waiting for vault folders to appear.")
131124
return ChecklistItem(
132125
requirement: .obsidianConnected,
133-
title: L10n.tr("Obsidian connected"),
134-
description: detail,
126+
title: L10n.tr("Obsidian folder connected"),
127+
description: L10n.tr("VaultSync can access your local Obsidian folder."),
135128
remediation: "",
136129
isOptional: false,
137130
isComplete: true
138131
)
139132
}
140133

141-
let issue = vaultManager.accessIssue
142134
return ChecklistItem(
143135
requirement: .obsidianConnected,
144-
title: L10n.tr("Obsidian connected"),
145-
description: issue?.message ?? L10n.tr("VaultSync does not have access to your Obsidian directory."),
146-
remediation: issue?.remediation ?? L10n.tr("Connect the Obsidian folder from the main screen."),
136+
title: L10n.tr("Obsidian folder connected"),
137+
description: L10n.tr("VaultSync cannot access your local Obsidian folder."),
138+
remediation: L10n.tr("Connect your Obsidian folder from the VaultSync home screen."),
147139
isOptional: false,
148140
isComplete: false
149141
)
@@ -153,41 +145,41 @@ final class SetupChecklistViewModel {
153145
if !syncthingManager.folders.isEmpty {
154146
return ChecklistItem(
155147
requirement: .firstShareDetectedOrAccepted,
156-
title: L10n.tr("First share detected"),
157-
description: countDescription(syncthingManager.folders.count, singular: L10n.tr("shared folder active"), plural: L10n.tr("shared folders active")),
148+
title: L10n.tr("Vault syncing"),
149+
description: L10n.tr("At least one Obsidian vault is active in VaultSync."),
158150
remediation: "",
159151
isOptional: false,
160152
isComplete: true
161153
)
162154
}
163155

164-
if !syncthingManager.pendingFolders.isEmpty {
156+
if !syncthingManager.actionablePendingFolders.isEmpty {
165157
return ChecklistItem(
166158
requirement: .firstShareDetectedOrAccepted,
167-
title: L10n.tr("First share detected"),
168-
description: countDescription(syncthingManager.pendingFolders.count, singular: L10n.tr("pending share found"), plural: L10n.tr("pending shares found")),
169-
remediation: L10n.tr("Open Pending Shares in VaultSync and accept one to start syncing."),
159+
title: L10n.tr("Vault syncing"),
160+
description: L10n.tr("A vault offer is waiting to be accepted."),
161+
remediation: L10n.tr("A vault offer is waiting. Accept it from Pending Shares on the home screen."),
170162
isOptional: false,
171-
isComplete: true
163+
isComplete: false
172164
)
173165
}
174166

175167
if syncthingManager.hasSeenPendingFolderOffer {
176168
return ChecklistItem(
177169
requirement: .firstShareDetectedOrAccepted,
178-
title: L10n.tr("First share detected"),
179-
description: L10n.tr("A share was detected earlier, but there is no active folder yet."),
180-
remediation: L10n.tr("If syncing has not started, reshare a vault from desktop Syncthing."),
170+
title: L10n.tr("Vault syncing"),
171+
description: L10n.tr("A vault offer was seen earlier, but no vault is syncing right now."),
172+
remediation: L10n.tr("If syncing has not started, share your Obsidian vault again from Syncthing on your computer."),
181173
isOptional: false,
182174
isComplete: false
183175
)
184176
}
185177

186178
return ChecklistItem(
187179
requirement: .firstShareDetectedOrAccepted,
188-
title: L10n.tr("First share detected"),
189-
description: L10n.tr("No folder share from your desktop has been detected yet."),
190-
remediation: L10n.tr("From desktop Syncthing, share one vault to this iPhone Device ID."),
180+
title: L10n.tr("Vault syncing"),
181+
description: L10n.tr("No Obsidian vault is active in VaultSync yet."),
182+
remediation: L10n.tr("Share your Obsidian vault from Syncthing on your computer."),
191183
isOptional: false,
192184
isComplete: false
193185
)
@@ -197,8 +189,8 @@ final class SetupChecklistViewModel {
197189
if subscriptionManager.isRelaySubscribed {
198190
return ChecklistItem(
199191
requirement: .relayConfigured,
200-
title: L10n.tr("Cloud Relay configured (optional)"),
201-
description: L10n.tr("Instant sync via Cloud Relay is active."),
192+
title: L10n.tr("Cloud Relay ready"),
193+
description: L10n.tr("Cloud Relay is available for faster background updates."),
202194
remediation: "",
203195
isOptional: true,
204196
isComplete: true
@@ -207,15 +199,11 @@ final class SetupChecklistViewModel {
207199

208200
return ChecklistItem(
209201
requirement: .relayConfigured,
210-
title: L10n.tr("Cloud Relay configured (optional)"),
211-
description: L10n.tr("Cloud Relay is off."),
212-
remediation: L10n.tr("You can enable it later in Settings for instant push-based sync."),
202+
title: L10n.tr("Cloud Relay ready"),
203+
description: L10n.tr("Cloud Relay is not enabled."),
204+
remediation: L10n.tr("Enable Cloud Relay later in Settings if you want faster background updates."),
213205
isOptional: true,
214206
isComplete: false
215207
)
216208
}
217-
218-
private func countDescription(_ count: Int, singular: String, plural: String) -> String {
219-
L10n.fmt("%d %@.", count, count == 1 ? singular : plural)
220-
}
221209
}

0 commit comments

Comments
 (0)