You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* docs: design spec for conflict skip-family fix (#8)
Spec for the fix to issue #8: 'Always skip on this iPhone' currently
only ignores the original path, so future sync-conflict copies bypass
the filter and conflicts reappear. Design adds a paired ignore pattern
(original + sync-conflict-* glob), actively removes existing conflict
copies, and groups the pair as a single row in the Sync Filters list.
* docs: implementation plan for conflict skip-family fix (#8)
* feat(bridge): add RemoveConflictFilesForOriginal for skip cleanup
Removes every sync-conflict copy of a given file in a folder, leaving
the original on disk. Used by the 'Always skip on this iPhone' flow to
actively clear conflict-copy leftovers when the user opts out of a file.
* feat(ios): wrap RemoveConflictFilesForOriginal in SyncBridgeService
* feat(ios): add conflictGlob helper and SkipFamilyGrouping
Pure helpers shared between the conflict-resolver Skip flow and the
Sync Filters list rendering. Tests cover root files, nested paths,
extension-less filenames, mixed-extension stems, and pair detection.
* feat(ios): add skipFileAndCleanupConflicts to SyncthingManager
Single-call API for the conflict-resolver Skip flow. Writes the pair of
ignore patterns, deletes existing conflict copies on disk, rescans, and
refreshes the conflict cache. Returns the number of removed conflict
copies so the UI can surface it in the success alert.
* feat(ios): wire ConflictDiffView Skip to skipFileAndCleanupConflicts
Replace the single-pattern addIgnorePattern call with the full Skip
flow. Confirmation alert now mentions conflict copies and, when any
existed on disk, the number that were removed. Dismisses the diff view
on OK since the conflict no longer exists.
* feat(ios): group Skip-Family pairs as one row in Sync Filters
A paired '<X>' + '<X>.sync-conflict-*' renders as a single Custom
Patterns entry with a '+ conflict copies' caption. Swipe-to-delete
removes both lines from .stignore atomically. Orphan singletons keep
their existing single-line rendering.
* feat(ios): localize Skip-Family alert and Sync Filters caption
Adds the new 'conflict copies' alert variant plus singular/plural
variants of the removed-count sentence and the '+ conflict copies'
caption used by the Sync Filters list, for en / de / zh-Hans.
* chore: bump to 1.3.2 (build 24)
* docs: changelog entry for 1.3.2 (skip-family fix, #8)
* docs: update sync-filters UX spec for skip-family (#8)
* chore: stop tracking docs/superpowers (internal development notes)
* docs: update README What's New to v1.3.2
* feat(ios): track reconnect grace period for required devices
Add a per-device 'first observed disconnected' timestamp. Within a 30s
window the device counts as 'reconnecting' (calm dashboard state, no
warning); after the window it surfaces as a real disconnect via the
existing Sync-Issues path. Single entry point applyDeviceList(_:) keeps
the timestamp dictionary consistent with the devices array.
* feat(ios): show 'Reconnecting…' dashboard state during grace period
Insert a new branch in syncStatusText / syncStatusIcon / syncStatusColor
that triggers when reconnectingRequiredDeviceIDs is non-empty. Dashboard
icon swaps to a system ProgressView spinner for the iOS-native look
familiar from pull-to-refresh, and a 'Restoring connection to N
device(s)' caption appears under the status text.
* feat(ios): localize Reconnecting dashboard state (en/de/zh-Hans)
Adds the three new keys consumed by the dashboard reconnecting branch.
* docs: add reconnecting-grace-period note to 1.3.2 changelog
* docs: add language specifier to sync-filters-ux fenced code block
* fix(ios): gate reconnecting UI behind status precedence
If currentSyncError != nil OR !isRunning OR foldersWithErrors is non-empty,
the dashboard's syncStatusText already shows a higher-priority state
('Error', 'Starting…', 'Sync Issue'). The reconnecting spinner +
'Restoring connection to N device(s)' caption used to render anyway,
producing an incoherent row (e.g. red 'Error' text with a teal reconnect
spinner). shouldShowReconnectingUI mirrors the syncStatusText cascade so
the reconnecting visuals only appear when reconnecting is actually the
status being reported.
* fix(ios): guard Skip-Family pairing against wildcard / directory rules
A user-added .stignore entry like '*.tmp' or 'drafts/' would, via
SyncthingManager.conflictGlob, produce a derived glob ('*.sync-conflict-*',
'drafts.sync-conflict-*') that could coincidentally match another manual
entry and produce a spurious pairing. The result would be swipe-delete
removing both unrelated patterns at once.
Add isConcreteFilePathPattern guard that excludes lines containing
glob metacharacters (*, ?, [], {}, !) or a trailing slash. Apply in
both the pre-pass and the main emission loop. Three new regression
tests cover '*.tmp', 'drafts/', and 'note?.md'.
* fix(ios): propagate cleanup and rescan failures from skipFileAndCleanupConflicts
Previously errors from removeConflictFilesForOriginal and rescanFolder
were silently swallowed: the call returned success even when on-disk
conflict copies couldn't be removed or the rescan failed. The user saw
a 'Skipping enabled' alert while the conflict actually still surfaced
on the home screen, with no signal what went wrong.
Now both bridge errors are mapped to a SyncUserError and returned to
the caller, so the existing 'Could not add filter' alert path
surfaces a real message. The .stignore write still happens before the
cleanup, so on partial failure the file is at least correctly
ignored — the error message tells the user the leftover copies
weren't removed.
* fix(ios): dispatch skipThisFile via Task so the button handler returns immediately
Honest scope note: this only wraps the call in a Task and returns the
button handler synchronously. The underlying skipFileAndCleanupConflicts
is still @MainActor-isolated (it touches manager state, refreshConflicts,
etc.) so the file I/O continues to run on the main actor. A full move
to a background executor would require splitting the bridge cleanup,
rescan, and refresh paths into nonisolated entry points — out of scope
for this PR. The Task wrapper is a non-regressing first step and makes
that future refactor strictly easier.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+9Lines changed: 9 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,6 +4,15 @@ All notable changes to VaultSync are documented here.
4
4
5
5
---
6
6
7
+
## [1.3.2] — 2026-05-23
8
+
9
+
### Fixed
10
+
11
+
-**Skip on iPhone now actually skips returning conflicts** ([#8](https://github.com/psimaker/vaultsync/issues/8)) — Tapping "Always skip on this iPhone" in the conflict resolver previously added only the original file's path to `.stignore`, so a fresh `sync-conflict-…` copy with a new timestamp would arrive from the desktop and the conflict reappeared. The Skip flow now also writes a `<path>.sync-conflict-*` glob, deletes any conflict copies of the file already sitting in the vault, and rescans so the conflict disappears from the Sync Issues list immediately. The Sync Filters → Custom Patterns list groups the pair as a single row with a "+ conflict copies" caption.
12
+
-**"Reconnecting…" replaces the transient "device disconnected" warning** — When VaultSync resumes after the app has been away from the foreground for a while, the home screen no longer briefly flashes a "1 Required Device Is Disconnected" warning while the embedded Syncthing process is still rebuilding its connection. Instead, the sync status reads "Reconnecting…" with a calm system spinner for up to 30 seconds. If the peer is genuinely offline beyond that window, the existing warning surfaces normally.
Copy file name to clipboardExpand all lines: README.md
+2-6Lines changed: 2 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -99,13 +99,9 @@ VaultSync is also not a magic always-on Syncthing daemon for iOS. Apple’s back
99
99
100
100
---
101
101
102
-
## What’s New — v1.3.1
102
+
## What’s New — v1.3.2
103
103
104
-
> **Conflict button no longer also opens the browser** — Tapping "Resolve Conflicts" on the home screen now just opens the conflict list, instead of also launching Safari at the same time.
105
-
>
106
-
> **Cleaner home screen and settings** — The "VaultSync" page title is gone, devices show just their name, and Settings no longer surfaces per-device rows for relay peers that are already provisioned.
107
-
>
108
-
> **German and Simplified Chinese fixes** — The Sync Issues titles ("1 Konflikt muss gelöst werden", "1 个冲突待解决", …) are now translated instead of falling back to English.
104
+
> **Skip on iPhone now actually skips returning conflicts** — Tapping "Always skip on this iPhone" in the conflict resolver previously added only the original file's path to the ignore list, so a fresh sync-conflict copy with a new timestamp would arrive from the desktop and the conflict reappeared. Skip now also covers future conflict copies of the same file, removes any existing copies on disk, and groups the pair as a single row in Sync Filters.
109
105
110
106
See [CHANGELOG.md](CHANGELOG.md) for full details.
Copy file name to clipboardExpand all lines: docs/sync-filters-ux.md
+18-6Lines changed: 18 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
# Sync Filters — UX Spec
2
2
3
3
> Status: **implemented** (issue [#1](https://github.com/psimaker/vaultsync/issues/1), shipped in v1.2.0)
4
-
> Last updated: 2026-05-09
4
+
> Last updated: 2026-05-23
5
5
6
6
This document is the design reference for the Sync Filters feature — the UI for excluding files and folders from sync requested in issue #1 by @vitaly74. It captures the rationale behind the layout, preset catalog, migration path, and multi-vault behavior; refer to it when extending or modifying the feature.
7
7
@@ -116,18 +116,30 @@ The Recommended set is also auto-applied silently when a new folder is added (so
116
116
117
117
## 6. Conflict → Ignore
118
118
119
-
In `ConflictDiffView`, a new toolbar menu appears (top-right `⋯`):
119
+
In `ConflictDiffView`, a toolbar menu appears (top-right `⋯`):
120
120
121
-
```
121
+
```text
122
122
⋯ menu
123
123
└─ Always skip on this iPhone
124
124
```
125
125
126
-
Tapping it adds the conflict's *exact relative path* to the folder's ignore list. Then a confirmation alert:
126
+
Tapping it performs a **Skip Family** action (added in v1.3.2, see issue [#8](https://github.com/psimaker/vaultsync/issues/8)):
127
+
128
+
1. Writes a *pair* of patterns to `.stignore`: the file's exact relative path and a matching `<path>.sync-conflict-*` glob.
129
+
2. Deletes any sync-conflict copies of that file currently on disk.
130
+
3. Rescans the folder and refreshes the conflict cache so the conflict disappears from the home-screen Sync Issues list immediately.
131
+
132
+
Confirmation alert:
133
+
134
+
> "`'.obsidian/plugins/dataview/cache.db'` and its conflict copies will no longer sync to this iPhone. You can undo this in Sync Filters."
135
+
136
+
If existing conflict copies were removed, a second line is appended:
137
+
138
+
> "2 existing conflict copies were removed."
127
139
128
-
> "`'.obsidian/plugins/dataview/cache.db'` will no longer sync to this iPhone. You can undo this in Sync Filters."
140
+
Reasoning behind the family approach: the v1.2.0 design used an exact-path pattern for predictability, but that left a hole — a fresh `sync-conflict-…` copy with a new timestamp would arrive from the desktop and the conflict reappeared. Pairing the original path with the conflict-copy glob makes "skip" actually mean skip, without sacrificing predictability: the two `.stignore` lines are still plain, no smart-glob heuristics, no hidden state. In the Sync Filters list the pair is presented as a single row with a `+ conflict copies` caption.
129
141
130
-
Reasoning behind exact-path (not smart-glob): predictable. The user knows exactly what they ignored. If they later want to widen to `*.cache.db` or `.obsidian/plugins/dataview/*`, they can do that in the editor.
142
+
The original file itself is **not** deleted from disk — only the conflict-copy variants. Users who later want to revert can swipe-to-delete the row in Sync Filters; both lines are removed atomically.
0 commit comments