- Camera-photo latency: root cause was READ_MEDIA_IMAGES never
granted (granted=false on the phone), so the MediaStore fast-path
query silently returned nothing and photos only appeared via the
slow full FUSE walk. The app now requests the permission at
runtime (once, with a single retry on denial). The camera writes
through MediaStore at capture time, so the ContentObserver fires
immediately and the photo appears in ~1 s.
- Delete blank boxes: two JS flaws — (1) no img onerror handler, so a
cell that requested a thumb for an already-deleted file (404) sat
as an empty dark box forever; (2) recycled cells were matched by
slot index only, so after a deletion shifted the list a cell could
keep a stale id. Now: onerror drops stale ids (idSet membership) or
retries transient ones, and cells reassign on idx OR id change.
- Truncation guard (chicken-and-egg): the mass-delete suspicion
threshold was 20% but observed FUSE truncations lose 15-17%, which
sailed under it and poisoned the DB. Now 10%; a confirmed deletion
(two-strike passed) lowers the new walked_max watermark, a refused
one keeps it. Layer-2 re-walk compares against max(db, walked_max)
so a poisoned (shrunk) DB cannot silence truncation detection.
Emulator suite: healthy→truncated (all 3 layers, 0 loss),
poisoned-DB→truncated (layer 2 now fires), healthy walk self-heals.
- Verified on the phone: DB matches disk exactly (8,398 Camera +
27 screenshots + 11 messaging + 2 bluesky = 8,438); an earlier
10,166 count was a transient user-system state, not truncation.