Root cause: the worker job queue was FIFO (mpsc). Scrolling down filled it
with slow cold decodes of far cells; scrolling back up re-submitted the top
cells as fast disk-cache hits that then waited behind the whole backlog —
seconds of shimmer. Deterministic repro measured 6.3 s behind a 180-cold
backlog (emulator UI showed the same while 220 cold jobs waited 0.5-0.9 s).
Fix: job queue is now Mutex<VecDeque> + Condvar; Engine::submit prepends
each new batch so the most-recent intent (the viewport, or what you scrolled
back to) is decoded before stale far-away prefetch. Cold backlog keeps
warming the disk cache in the background (total work unchanged).
- regression: thumb::tests::scroll_back_cache_hit_not_starved (6.3 s -> 130 ms)
- dispatcher exits via a closed flag set in Engine::drop (replaces the
mpsc channel-close shutdown)
- HANDOFF: M3 fix note, prepend rationale, emulator realtest_extra dataset
- main.slint: ListView of GridRows (3 square CellViews each); Cell struct
{path, image, loaded}; shimmer placeholder via animation-tick()
expression (1.18 has no from:/to: animation objects); scroll-changed
callback feeds Rust.
- lib.rs: active-range model (visible rows ± 4-row margin); on_scroll is
cheap (re-anchor + submit prefetch only); a 10 Hz drain timer owns ALL
model mutation, capped at 16 row-replacements/tick (ANR guard);
apply_row_batch replaces each affected outer row once — the only update
path that reaches ListView rows recycled while off-screen; clearing a
cell releases its dedupe entry so re-entry re-submits (disk-cache hit).
- thumb.rs: LIST_TARGET(64) -> GRID_TARGET(500); cache key includes target.
- Verified on emulator (150 real JPEGs + 5k stubs): first paint 0.6 s
cold, warm relaunch from disk cache, 0 ANRs through 16 fast swipes,
backlog drains to 0. 13 unit tests + 5k engine test pass.
- HANDOFF: M3 status, Slint gotchas 11-13 (in-property binding, nested
model updates vs row recycling, ListView spacing), emulator test-data
notes.
- relaunch: DB list paints immediately ('N files (cached)'), background
walk refreshes ('Updating… N') — no more 5.5 s blank list on warm start
- first launch: walk_stream() feeds the list in 512-file batches while
scanning; UI never waits for the full DB build
- skip hidden dirs (.thumbnails/.trash/…): -559 entries on the phone
- db.rs: cfg-gate ANDROID_PACKAGE (dead-code warning on desktop)
Phone (8.5k photos): cold scan 4.2 s with live list; warm relaunch
paints instantly, rescan 4.2 s in background.
- scan.rs: recursive walk of Pictures/DCIM (GALLERY_SCAN_DIRS override),
image-ext detection, mtime/size capture, no symlink-dir following
- db.rs: rusqlite(bundled) store; incremental apply (insert/update/delete)
keyed on path with mtime/size change detection; derived columns reset on
change; Android DB path in Android/data/<pkg>/files (EXTERNAL_DATA_DIR is
unset in this NativeActivity setup)
- lib.rs: start_scan() background thread -> invoke_from_event_loop ->
VecModel + status line; called from desktop and Android entry points
- main.slint: ListView of paths (M1), status line with scan stats
- main.rs: fix desktop bin for 1.18.1 (no slint::Result; ComponentHandle)
- build.sh: export NDK clang CC_/CXX_/AR_ per target (bundled sqlite)
- tests: 5k-file scan < 2s, incremental apply, roundtrip
Verified on emulator: 5002 files in 153-226 ms; relaunch 0+ 0~ 0-;
touch -> 1~, delete -> 1-.