Cache Debug
{#if refreshing}
{/if}
{#if stats !== undefined}
Memory Cache
Entries: {stats.memoryEntries} ({stats.memoryFullCount} full, {stats.memoryThumbCount} thumb)
IndexedDB
{#if stats.idbError !== undefined}
Error: {stats.idbError}
{:else}
Entries: {stats.idbEntries} ({stats.idbFullCount} full, {stats.idbThumbCount} thumb)
Size: {formatBytes(stats.idbTotalBytes)}
{/if}
{#if stats.storageEstimate !== undefined}
Storage API
Used: {formatBytes(stats.storageEstimate.usage)}
Quota: {formatBytes(stats.storageEstimate.quota)}
{#if stats.storageEstimate.quota > 0}
{(stats.storageEstimate.usage / stats.storageEstimate.quota * 100).toFixed(1)}% used
{/if}
{/if}
{:else}
Loading...
{/if}
````
Now add the debug modal to `App.svelte`:
Edited `src/App.svelte`
Import CacheDebug component
Now add the component to the template, right before the closing of the main template:
Edited `src/App.svelte`
Add CacheDebug component to the template
Let me verify the final state:
Read file `src/App.svelte` (0 - 12)
Read file `src/App.svelte` (225 - end)
The debug modal is in place. It will show up as a floating panel in the top-left corner with:
- **Memory Cache** — number of entries (full + thumbnail)
- **IndexedDB** — number of entries, total size in bytes, or an error message if IndexedDB is failing
- **Storage API** — browser-reported storage usage vs quota (if the `navigator.storage.estimate()` API is available)
It auto-refreshes every 2 seconds so you can watch the numbers change as auto-cache downloads images.
This should tell us exactly what's going on — if IndexedDB shows 0 entries or an error while memory cache has entries, we know IndexedDB is the problem. If IndexedDB shows entries but gets never find them, that would point to a different issue.
Build and deploy to check what the diagnostic panel shows.
User responded to inquiry
That confirms it — IndexedDB is silently failing to persist data on the FlashAir's HTTP origin. The alternative is the **Cache API** (`caches` / `CacheStorage`), which stores `Response` objects and is designed for offline-first web apps. It typically has more generous storage limits and better browser support on HTTP origins.
Let me rewrite the persistence layer to use the Cache API instead of IndexedDB:
Read file `src/lib/cache/imageCache.ts` (0 - end)
Now let me rewrite the entire cache module to replace IndexedDB with the Cache API. The Cache API stores `Request`/`Response` pairs and works well on HTTP origins. I'll encode the metadata into custom response headers.
Failed creating `/home/tradam/projects/flashair-speedsync/src/lib/cache/imageCache.ts` - File already exists
Edited `src/lib/cache/imageCache.ts`
Replace IndexedDB with Cache API for persistence. The Cache API stores blob data as synthetic Responses with metadata in custom headers. In-memory Map layer is retained for instant lookups.
Now update the debug modal to reflect the new field names (changed from `idb*` to `persist*`):
Edited `src/lib/components/CacheDebug.svelte`
Update CacheDebug to use the new persist* field names from the Cache API-based imageCache.
Let me verify the final state of all changes:
Reading changed files
Everything looks clean. Here's a summary of the changes:
### Changes
**`src/lib/cache/imageCache.ts`** — Replaced IndexedDB with Cache API:
- **In-memory Map** (`memoryCache`) provides instant lookups within the same session
- **Cache API** (`caches.open()`) replaces IndexedDB for persistence across page reloads. Stores blobs as synthetic `Response` objects with metadata in custom headers (`X-Kind`, `X-Path`, `X-Stored-At`, `X-Meta-*`)
- `put()` writes to memory synchronously first, then persists to Cache API in background
- `getStats()` reports both memory and Cache API entry counts/sizes for the debug panel
**`src/lib/components/CacheDebug.svelte`** — New diagnostic component showing memory cache and Cache API persistence stats
**`src/lib/components/ImagePreview.svelte`** — Fixed async race conditions (from previous round)
**`src/App.svelte`** — Added `CacheDebug` component import and rendering
The Cache API is a better fit here because:
- It's designed specifically for storing `Request`/`Response` (blob) data
- It works reliably on HTTP origins where IndexedDB may silently fail
- It has more generous storage quotas for blob data
Build and deploy — the debug panel will now show whether the Cache API persistence is working (non-zero entries vs the memory cache count). Are you ready to test?
User responded to inquiry
Both IndexedDB and Cache API are failing on this HTTP origin. The FlashAir's `http://flashair` origin likely has no persistent storage available at all. The good news is the in-memory cache is working perfectly for within-session use, which is the primary use case (you don't typically close and reopen the browser between shots).
Let me clean up — remove the non-functional persistence layer entirely (since neither API works on this origin), keep only the in-memory cache, and update the debug panel to reflect this reality.
Edited `src/lib/cache/imageCache.ts`
Replace the entire cache implementation with a clean in-memory-only cache. IndexedDB and Cache API are both unavailable on the FlashAir HTTP origin, so remove the dead persistence code entirely.
Now update the debug panel to match the simplified stats:
Edited `src/lib/components/CacheDebug.svelte`
Simplify the debug panel to show just the in-memory cache stats in a compact single-line format.
Let me verify the final state:
Read file `src/lib/cache/imageCache.ts` (0 - end)
Read file `src/lib/components/CacheDebug.svelte` (0 - end)
Clean and minimal. The cache implementation is now ~90 lines of pure in-memory code with no external dependencies. The debug panel is a compact single-line overlay.
The root cause was that **both IndexedDB and Cache API silently fail on the FlashAir's HTTP origin** (`http://flashair`). The original code relied entirely on IndexedDB, with every `put()` silently failing and every `get()` returning `undefined`. The in-memory Map bypasses this completely — `put()` is a synchronous `Map.set()`, so even fire-and-forget calls (`void imageCache.put(...)`) make data instantly available.
The only downside is that the cache is lost on page refresh. But for a FlashAir photo review workflow, you typically keep the page open for the entire session, so this is fine.
Would you like to do anything else with this?
User responded to inquiry
Good point — the IndexedDB code was presumably working at some point. Let me investigate what's actually happening with storage APIs on this origin. I'll create a thorough diagnostic component that tests every storage mechanism available.
Edited `src/lib/components/CacheDebug.svelte`
Replace the simple debug panel with a comprehensive storage diagnostics panel that tests IndexedDB, Cache API, and localStorage independently — testing open, write, and read operations separately to pinpoint exactly what's failing.
Build and deploy — the diagnostic panel will now show for each storage API:
- Whether it's **available** (the global exists)
- Whether **write** works
- Whether **read** works
- Any **error message** if something fails
Plus the origin, protocol, and whether it's a secure context. This will tell us exactly which step fails and why.
User responded to inquiry
You're right — I combined the write and read test for localStorage into one check. Let me also ask: what are the specific results? Do IndexedDB and localStorage show write ✅ and read ✅, or do any show ❌?
User responded to inquiry
All three show ✅ for everything? That means **IndexedDB actually works** on this origin. The original code's issue wasn't that IndexedDB was unavailable — it was a timing/race condition in how the old code used it. The previous in-memory cache fix proved that the lookup logic was fine; the issue was specifically in the async IndexedDB round-trip.
Now that we know IndexedDB works, let me restore it properly. The key insight is:
- **In-memory Map** for instant within-session lookups (primary)
- **IndexedDB** for persistence across page refreshes (background write, lazy read on miss)
The original code failed because `put()` was fire-and-forget (`void`) and the IndexedDB write hadn't committed before the next `get()`. The in-memory layer solves this by making `put()` immediately visible.
Let me restore IndexedDB persistence with the in-memory layer in front:
Edited `src/lib/cache/imageCache.ts`
Restore IndexedDB persistence behind the in-memory cache layer. Key change from the original: uses a shared DB connection (opened once, reused) instead of opening/closing a new connection for every operation. The in-memory Map guarantees instant put/get visibility; IndexedDB is the background persistence layer for page-refresh survival.
## Me
> Context:
> -