Add src/library/content.ts: a ContentCache keyed by fileID under
cacheDirectory (flat originals/ and thumbnails/, 0700/0600). It fetches
through the request pools (#45) and the streaming decrypt / atomic writer
(#40), so present-means-complete; it reaps orphan temp files at open and
records the record of what is cached. Photo.original/thumbnail return
{path,bytes} (one skipped event when present, in cache or downloadDirectory),
and lib.thumbnails.ensure drives the thumbnail pool with priority, dedup, and
abort. Cached paths are projected onto PhotoRecord.
Integrity: enforced by the reused streaming decrypt (every chunk
authenticated, renamed in only on TAG_FINAL) plus a non-empty check. The
metadata.hash / declared-fileSize comparison is deferred — the only in-repo
hash fixture is a placeholder, so the construction cannot be confirmed against
fixtures (the issue's own precondition), and FileBlob.size is the encrypted
object size, not the decrypted length. Raised as a question on the PR.
Judgement call: the three thumbnail priorities map onto the pool's two tiers
(visible -> on-demand; ahead and background -> background).
Model: opus-4-8
Adds the in-process read surface, served from RAM with args-object signatures: albums (list/byName/byID), photos (byID/records -> plain PhotoRecord[]), thin Album/Photo wrappers, and timeline.groups (day/week/month) with a PhotoFilter (albumID/text/fileTypes/hasLocation/includeArchived; hidden excluded). Week keys use ISO YYYY-Www; each file appears once per group, newest first. Built on the #43 snapshot projection; no network.
Model: opus-4-8
Adds three independent bounded request pools — metadata (10 in flight), content (5), thumbnails (25), each overridable — with on-demand-before-background priority and in-flight dedup (a shared key runs once); an idle pool never lends slots, and retries run inside a slot. Self-contained module; the content cache wires it into the library later.
Model: opus-4-8
Adds a synchronous, key-free snapshot() returning LibrarySnapshot (one PhotoRecord per fileID, deduped, newest first) with edited-name/time precedence (pubMagicMetadata over basic metadata) in milliseconds, plus AlbumRecord (favorites identified by type). subscribe({onChange}) delivers LibraryChange (changed/removed albums and files, refreshedAt) only when a refresh changes something; unsubscribe stops delivery. Built on the existing refresh loop; served from RAM, safe to send over IPC.
Model: opus-4-8
Library.open loads metadata.json and serves reads from RAM: an empty cache awaits the first refresh, an existing cache returns at once and refreshes in the background so an unreachable server never stalls open(). A background timer refreshes every refreshIntervalSeconds (default 3), diffing only changed albums via the resumable cursor+tombstone enumerators and rewriting metadata.json only when something changed. A refresh failure is invisible to reads and surfaced via status()/onProgress; a failed save keeps status().lastError set and retries until one lands, so a stale disk is never masked. No sync()/refresh()/serverReachable surface; status() and close() included.
Model: opus-4-8
Adds the metadata.json store: loads whole into RAM with id-lookup Maps, rewrites whole through the exported fsync atomic writer (temp, fsync, rename, dir fsync); a missing, unparseable, or wrong-schema file loads as empty (it is a cache); directory 0700, file 0600. No lock file, no public sync().
Model: opus-4-8