Record why thumbnails are not queued

catalog.md §6 still described the design of 2026-08-09, where the grid
enqueued Thumbnail jobs at Interactive and a runner drained them. It was
never built that way: the grid asks a worker directly and the sweep's
work list is what the thumbnail store lacks. The one enqueue that did
exist fed a queue nobody claimed (#73).

§6.1 now states the decision and its evidence: the store is shared
between devices and is the only record that knows a thumbnail exists,
metadata is owed through metadata_state the same way, the retired rows
are dropped at open rather than by a migration so no older device loses
the synced catalog, and every_queued_kind_has_a_consumer holds the rule.
§6.3 notes that the priority ordering is had without the queue.

outstanding.md's FR-PLAT-AND-4 paragraph said the scan's thumbnail jobs
were the one reachable enqueue; it now says nothing enqueues, and that
feeding the runner means a handler and its enqueue in the same change.

Refs #73
This commit is contained in:
2026-09-26 13:15:18 -04:00
parent b3dbf4a039
commit 441f6f1404
2 changed files with 32 additions and 6 deletions
+25 -1
View File
@@ -420,6 +420,29 @@ pub enum JobKind {
}
```
**Thumbnails are not queued (decided 2026-09-26, #73).** `Thumbnail` is kept only so its number
stays taken, and is listed in `JobKind::RETIRED`. Up to 0.16.0 every remote scan enqueued one per
photograph, and nothing claimed the kind — no `JobHandler` was ever registered for it, on desktop or
Android (the same `dr-ui`), and the catalog snapshot carries `jobs` but the merge never reads them
(§8.2). The reference catalog held 23,582 such rows, about 1 MB with its indexes. Thumbnails are
made another way, and by the right source of truth:
- the grid asks a worker for the cells it is drawing, which serves them from the thumbnail store or
range-fetches the embedded preview (§7.2);
- the thumbnail sweep's work list is *what the store does not hold* (`thumbnails_outstanding`).
The store is shared between devices (§7.3), so it is the only thing that knows another device already
made a thumbnail; a per-device queue row cannot. A queue row would be a second, staler record of the
same debt. Metadata is owed the same way — `metadata_state < 2` is the sweep's work list — so the local
walk no longer enqueues `ExtractMetadata` either.
The rows already queued are dropped by `jobs::drop_retired`, from `runner::recover` at every catalog
open, rather than by a migration: a schema bump would make a device still on an older build refuse the
synced snapshot, and "every open" rather than "once" because an older build sharing the catalog
queues them again on its next scan. With the rows gone it is one probe of the `(kind, subject_id)`
index. `every_queued_kind_has_a_consumer` (dr-catalog) holds the rule: it reads the shipping sources
and fails if any kind is enqueued that no handler or claim names.
### 6.2 Coalescing is the point
`UNIQUE(kind, subject_id)` on `jobs` means enqueueing is idempotent: an image touched five times
@@ -443,7 +466,8 @@ priority governs the whole app:
Visible-cell work is enqueued by the grid as it scrolls, at `Interactive`. The effect is that a
freshly scanned library fills in *where the user is looking* first, and grinds through the rest
behind them.
behind them. (As built, thumbnails and metadata get this ordering without the queue — the grid
requests its visible cells directly and the sweeps take what is left; see §6.1.)
### 6.4 Durability and failure
+7 -5
View File
@@ -258,11 +258,13 @@ the platform half — a foreground `Service`, `FOREGROUND_SERVICE` and `POST_NOT
manifest, and a stated Doze behaviour. The build step that blocked it is no longer a blocker: the
APK now compiles its own Java.
Note also that **no handler is registered**, deliberately. The only enqueue site reachable in the
shipping app produces remote thumbnail jobs already served by the async grid worker, and
`walk::scan_root` — which holds the other two enqueue sites — has no caller outside an example.
Wiring the sweep to claim from the queue is the honest next step and is an async rewrite of
`library.rs`.
Note also that **no handler is registered**, deliberately — and, since #73, nothing enqueues
either. The remote scan's per-photograph `Thumbnail` jobs, and `walk::scan_root`'s `Thumbnail` and
`ExtractMetadata` jobs, duplicated debts the thumbnail store and `metadata_state` already record,
and were never claimed; they were removed and `Thumbnail` retired ([catalog.md §6.1](catalog.md)).
Feeding FR-PLAT-AND-4's platform half is therefore a matter of choosing a kind whose work has no
better source of truth, registering its handler, and enqueuing it in the same change —
`every_queued_kind_has_a_consumer` refuses the enqueue without the handler.
**FR-PLAT-AND-5 — built.** A tiered eviction registry drives GPU caches, then proxies, then
thumbnails, from `MainEvent::LowMemory` and `MainEvent::Stop`.