Picking the newer of two default versions stopped the wrong edit being shown,
but it did not close the split: the losing version stayed in the file, and a
device holding disjoint work — a crop made here, an exposure change made there
— still contributed only one of the two.
Worse, the next write made it larger. `amend` looks its version up by uuid,
neither of the two was ours, so the miss minted a *third* `default = 1` block
and the file grew one rival per device per photograph.
`Sidecar::fuse_default_versions` folds them down. The version with the highest
`(revision, modified)` is the accumulator and every other default is merged
into it as the remote, which is what makes the fold order-independent —
`Version::merge` raises its own revision to `max + 1` as it goes, so merging a
chain in ascending order stops being ascending after the first step and a third
device would be dropped. Contested values resolve to the winner, disjoint keys
survive from both sides because the merge is key-wise, and ratings come across
under `merge_judgement`, so a device that never judged the frame cannot erase
one that did.
The result is a function of the file's bytes alone, so two devices that fuse
independently reach the same document and converge instead of overwriting each
other.
Called wherever a sidecar is parsed:
- `amend`, with the write's own uuid, so the fold lands on the identity this
device is about to use and the lookup below it hits instead of missing.
- `spawn_sidecar_fetch`, so opening a photograph shows everything done to it
rather than whichever half won.
- `drain_one`, because `merge_into` reconciles by uuid and would otherwise
publish the split rather than resolve it.
- `presets::load_local` and `save_local` — a local sidecar's folder may be
synced by something else entirely, and gets the same split.
A file with one default under the expected uuid comes back byte-identical, so
this costs nothing on the ordinary write and no sidecar is uploaded merely for
having been read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>