Hand the photographer's place between devices

A place recorded on the tablet should be where the desktop opens.

Exchanged through `.darkroom-derived/place.json`, beside the thumbnail
shards and the catalog snapshot. Newest timestamp wins outright: unlike
the catalog this is replaced rather than merged, because two devices
cannot both be where the photographer is and so there is nothing of
theirs inside ours to preserve.

It still refuses to upload over a copy it could not read, for a smaller
version of the reason `sync_catalog` does: a record we have not compared
against may be the newer one, and overwriting it would move the other
device's photographer without ever having seen where they were.

Last in the pass, and its failures are logged rather than reported.
Everything else in that folder is *derived* -- a faster way to learn what
the device could work out for itself -- so losing it costs time. A place
is a fact only the other device knew, and losing it costs a scroll. A
sync that ran out of connectivity should spend what it had on the shards.

The full pass runs after a thumbnail sweep or when Sync is pressed,
neither of which happens on an ordinary launch -- so a handover would
arrive one launch late, which is one too many for a feature whose whole
claim is picking up where you stopped. `spawn_place_fetch` is the small
half: one GET of a few hundred bytes, started beside the scan.

And it can still be refused. A handover is welcome on the way in and
unwelcome once the photographer has started: a grid that jumped
elsewhere mid-scroll because a round trip finally landed would have lost
their place to the feature meant to keep it. Any scroll, scrub, scope
change, filter or opened photograph closes the latch, and a record
arriving after that is written to disk and takes effect next launch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-30 20:40:22 +02:00
co-authored by Claude Opus 5
parent d44bffa4a8
commit 353382c07f
5 changed files with 566 additions and 33 deletions
+27 -5
View File
@@ -484,11 +484,33 @@ The peak is the working set, not the library, and the release is selective.
### 6.4 Derived state is dehydrated too
Shards and the catalog snapshot live in `.darkroom-derived/` **inside the
library folder**, so a sync client dehydrates them exactly as it dehydrates a
photograph. Unlike a photograph, none of them can be skipped: a shard that will
not open is a peer's thumbnails never merging, and a catalog snapshot that will
not open is their collections.
Shards, the catalog snapshot and the place record live in `.darkroom-derived/`
**inside the library folder**, so a sync client dehydrates them exactly as it
dehydrates a photograph. Unlike a photograph, none of them can be skipped: a
shard that will not open is a peer's thumbnails never merging, and a catalog
snapshot that will not open is their collections.
The folder holds three kinds of thing:
| File | What it is | How two devices reconcile it |
| --- | --- | --- |
| `shard-<client>-NNNN.sqlite` | Thumbnail and face shards | Sealed and immutable; a name match is a content match, so existence is the whole protocol |
| `catalog.sqlite` | The catalog snapshot, for its collections | Read-modify-write: **merge theirs, then push the union** |
| `place.json` | Where the photographer was (FR-UI-8) | Replace: the newer timestamp wins outright |
`place.json` is the odd one out, and deliberately. Everything else there is
*derived* — a faster way to learn something the device could have worked out for
itself from the originals and the sidecars — so losing it costs time. A place is
a fact only the other device knew, and losing it costs a scroll. That is why it
is exchanged last in the pass, why its failures are logged rather than reported,
and why it is the one file here that is replaced rather than merged: two devices
cannot both be where the photographer is, so there is nothing of theirs inside
ours to preserve.
It still refuses to upload over a copy it could not read, for a smaller version
of the reason below: a record we have not compared against might be the newer
one, and overwriting it would move the other device's photographer without ever
having seen where they were.
`derived_sync::read_derived` fetches on demand rather than giving up. More
important is what happens when it *cannot*: