The merge window kept reporting "Order kept from this machine (most recently
edited)" for playlists edited on the other machine. Three defects, confirmed
against the real snapshots in .resolved/:
* "this machine" was inferred from which copy held the plain filename. That is
Syncthing's call, not a statement about authorship — it sets the local copy
aside as readily as a remote one. In the 9:34 PM `* a fresh master` merge the
copy labelled "the other machine" was this machine's own 3:15 PM merge output,
so the label was exactly backwards. The 7-char device ID in the conflict
filename — the only real evidence — was matched by a bare \w+ and deleted with
the file. New sync_identity.py decodes it against Syncthing's config.xml and
works out which device is us from cert.pem.
* The decision leaned local. date_modified was only consulted when *both* copies
had one, and an iTunes playlist never reordered here has none — so the honest
comparison was skipped exactly when one machine had edited and the other
hadn't. A stamped copy now beats an unstamped one; mtime is the fallback only
when neither side has ever been edited. And every merge used to rewrite the
file it kept whether or not anything changed, freshening its mtime while the
conflict file kept its origin's: a ratchet. No-op merges write nothing, and a
merge whose result is a union neither copy had stamps date_modified, so the
other machine adopts it instead of trading the same 19 tracks back and forth.
* Nothing was actionable. Re-inserted tracks are now named with their position
("Pola — Abeille -> position 24, after ..."), six in the window and all of
them in what-changed.txt at the top of the backup snapshot, alongside the real
conflict filename and its device. Tracks only this copy has are reported too
rather than resurrected in silence.
Also fixed while in here: a rename or folder move made elsewhere was discarded
by every merge (only track_ids and settings were adopted); _reconcile_playlist
asserted the local edit was newer and never checked, so a reorder synced in from
the other machine was undone and flushed back to disk, and the branch reaching
it was gated on a dirty flag that a column drag sets; and _merge_metadata
decided the music folder from whichever copy an mtime coin flip had kept.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y9ZEFi4qNJ39FMiBtiAxy2
"unplayed", "missed and never skipped" and "top 100 in past year" showed up in
the merge window constantly, and nobody had touched their rules. A live smart
playlist's membership is derived, but it was being persisted — and
recompute_smart_playlist rewrites it (and bumped date_modified) every time a
play count moves. Both machines did that against the same file after every
song, so Syncthing conflicted on a list the next load throws away and rebuilds
anyway. Membership now stays in memory: save_playlist writes track_ids: [] for
a live smart playlist, and the recompute passes touch=False so it marks nothing
dirty and moves no timestamp. live_update=False and unsupported criteria are
unchanged — their track_ids are a snapshot, which is real content. Since
_mark_playlist also dirties the metadata, library_metadata.json stops being
rewritten every song too.
The rest was presentation. Every summary read at the same weight, so someone
resizing a column on the other machine popped and raised the same window as a
21-track reconciliation, described as "Library columns/settings taken from the
most recently edited copy." Summaries now carry a level — WARNING for a merge
that couldn't resolve cleanly or discarded a side, CHANGE for real content,
INFO for cosmetic or derived — and the dialog has a Show: selector that filters
to one level and above. It opens at the highest level in the batch, so nothing
routine steals focus and a blank window can't happen; a later warning always
pulls the view back up to it. The wording says what happened instead of how the
merge works: a metadata merge names the keys that actually differed, and
adopting the other machine's music folder grades CHANGE rather than INFO,
because that one is a setting somebody chose.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y9ZEFi4qNJ39FMiBtiAxy2