Library.open with a background refresh loop and staleness window #42

Closed
opened 2026-09-22 09:11:02 +02:00 by clawbot · 2 comments
Collaborator

Cache/API implementation (#36), phase 2.
Depends on the metadata-store unit and the enumeration unit.

Goal

Library.open() and the transparent refresh loop. No sync() anywhere in the
surface (owner ruling): open() loads metadata.json, does a first refresh,
then a background timer refreshes on an interval; every read is answered from
RAM with no network in front of it (owner's TTL/staleness ruling).

Scope

  • Library.open(args: { client; cacheDirectory?; downloadDirectory?; refreshIntervalSeconds?; onProgress? }):
    • cacheDirectory default from env-paths cache dir + /<userID>
      (bin/quak.ts already calls envPaths("quak", { suffix: "" })).
    • loads the store, runs one refresh, then starts the interval.
  • Refresh: one /collections/v2?sinceTime=cursor (empty when nothing changed),
    then a per-album diff only for albums whose updationTime advanced, applying
    tombstones (drop membership; mark collection deleted). Persist cursors + data
    in one rename only when something changed. Uses the enumeration variants.
  • refreshIntervalSeconds default 3 (owner: 3–5s recent images, ~10s album
    list/membership all met by one small poll per 3s). First run with no local
    copy resolves only when the first fetch completes; later runs resolve as soon
    as the local copy is loaded and the first refresh has started.
  • A failed refresh is not visible to reads: reads keep answering from RAM; the
    failure is reported once via onProgress (operation: "refresh",
    status: "failed") and shown in status().
  • status() and close() (stops the loop; in-flight work finishes). No
    serverReachable flag, no refresh: "beforeEachRead" mode, no lib.refresh()
    (all removed by owner rulings in the final design comment).
  • All metadata requests go through the metadata pool once that unit lands; until
    then, direct Client calls are acceptable and swapped in later.

Definition of done

  • open() loads + first-refreshes; interval refresh applies diffs and
    tombstones; metadata.json rewritten only on change.
  • Reads (added in later units) never trigger a network call.
  • Non-advancing / failing refresh handled per above; status() reflects it.
  • Tested against a mock Client; make check green.

Grounding

src/library/ (the Library class). Depends on metadata-store + enumeration.

Dispatch notes: TDD; no scripted edits; no interactive questions; plain language.
Squash subject ends (closes #<this issue>). End every message with
Model: opus-4-8.

Model: opus-4-8

Cache/API implementation (https://git.eeqj.de/sneak/quak/issues/36), phase 2. Depends on the metadata-store unit and the enumeration unit. ## Goal `Library.open()` and the transparent refresh loop. No `sync()` anywhere in the surface (owner ruling): `open()` loads `metadata.json`, does a first refresh, then a background timer refreshes on an interval; every read is answered from RAM with no network in front of it (owner's TTL/staleness ruling). ## Scope - `Library.open(args: { client; cacheDirectory?; downloadDirectory?; refreshIntervalSeconds?; onProgress? })`: - `cacheDirectory` default from `env-paths` cache dir + `/<userID>` (`bin/quak.ts` already calls `envPaths("quak", { suffix: "" })`). - loads the store, runs one refresh, then starts the interval. - Refresh: one `/collections/v2?sinceTime=cursor` (empty when nothing changed), then a per-album diff only for albums whose `updationTime` advanced, applying tombstones (drop membership; mark collection deleted). Persist cursors + data in one rename only when something changed. Uses the enumeration variants. - `refreshIntervalSeconds` default 3 (owner: 3–5s recent images, ~10s album list/membership all met by one small poll per 3s). First run with no local copy resolves only when the first fetch completes; later runs resolve as soon as the local copy is loaded and the first refresh has started. - A failed refresh is not visible to reads: reads keep answering from RAM; the failure is reported once via `onProgress` (`operation: "refresh"`, `status: "failed"`) and shown in `status()`. - `status()` and `close()` (stops the loop; in-flight work finishes). No `serverReachable` flag, no `refresh: "beforeEachRead"` mode, no `lib.refresh()` (all removed by owner rulings in the final design comment). - All metadata requests go through the metadata pool once that unit lands; until then, direct `Client` calls are acceptable and swapped in later. ## Definition of done - `open()` loads + first-refreshes; interval refresh applies diffs and tombstones; `metadata.json` rewritten only on change. - Reads (added in later units) never trigger a network call. - Non-advancing / failing refresh handled per above; `status()` reflects it. - Tested against a mock `Client`; `make check` green. ## Grounding `src/library/` (the `Library` class). Depends on metadata-store + enumeration. Dispatch notes: TDD; no scripted edits; no interactive questions; plain language. Squash subject ends ` (closes #<this issue>)`. End every message with `Model: opus-4-8`. Model: opus-4-8
Author
Collaborator

Implemented in #60.

Library.open() loads the metadata store, does one refresh, then refreshes on a background timer every refreshIntervalSeconds (default 3). Reads are answered from RAM with no network in front of them. A refresh commits to the store only after all its fetches succeed, so a failed refresh is invisible to reads and surfaces only via onProgress ("failed") and status(). No sync(), no refresh(), no serverReachable. status() and close() are provided; close() stops the timer and is idempotent.

A collection's files are re-enumerated only when its updationTime advances, reusing the metadata store and the collectionsSince/filesSince enumerators unchanged.

make check passes.

Model: opus-4-8

Implemented in https://git.eeqj.de/sneak/quak/pulls/60. `Library.open()` loads the metadata store, does one refresh, then refreshes on a background timer every `refreshIntervalSeconds` (default 3). Reads are answered from RAM with no network in front of them. A refresh commits to the store only after all its fetches succeed, so a failed refresh is invisible to reads and surfaces only via `onProgress` ("failed") and `status()`. No `sync()`, no `refresh()`, no `serverReachable`. `status()` and `close()` are provided; `close()` stops the timer and is idempotent. A collection's files are re-enumerated only when its `updationTime` advances, reusing the metadata store and the `collectionsSince`/`filesSince` enumerators unchanged. `make check` passes. Model: opus-4-8
Author
Collaborator

Landed on next via #60 (squash). Closing manually — the merge-commit keyword did not auto-close.

Model: opus-4-8

Landed on next via https://git.eeqj.de/sneak/quak/pulls/60 (squash). Closing manually — the merge-commit keyword did not auto-close. Model: opus-4-8
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/quak#42