Prevent blob eviction: store as ArrayBuffer + request persistence

iOS Safari evicts Blob file-backed data from IndexedDB under memory
pressure, causing upload POSTs to throw 'Load failed' without ever
reaching the server. Two-pronged fix:

1. Store photos as ArrayBuffer (inline bytes) instead of Blob (file
   reference) in IndexedDB — ArrayBuffers are not subject to eviction
2. Request navigator.storage.persist() to signal the browser not to
   evict our storage under pressure

Also adds Storage.getBlob() helper for converting stored ArrayBuffer
back to Blob at upload/display time, with backward compat for any
existing Blob-stored photos.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 16:01:07 -06:00
parent 1da3eea7b8
commit 5105b42c46
5 changed files with 48 additions and 57 deletions

View File

@@ -631,7 +631,9 @@ async function loadRecentPhotos() {
container.innerHTML = '';
for (const photo of photos) {
const url = URL.createObjectURL(photo.blob);
const blob = Storage.getBlob(photo);
if (!blob) continue;
const url = URL.createObjectURL(blob);
const statusClass = photo.status === 'verified' ? 'verified' :
photo.status === 'uploading' ? 'uploading' : 'pending';

View File

@@ -452,9 +452,10 @@ function createQueueItem(photo, isCompleted) {
// Create thumbnail (use placeholder if blob was stripped)
const thumbnail = document.createElement('div');
thumbnail.className = 'queue-item-thumbnail';
if (photo.blob && photo.blob.size > 0) {
const photoBlob = Storage.getBlob(photo);
if (photoBlob && photoBlob.size > 0) {
const img = document.createElement('img');
img.src = URL.createObjectURL(photo.blob);
img.src = URL.createObjectURL(photoBlob);
thumbnail.appendChild(img);
} else {
thumbnail.innerHTML = '<span class="thumbnail-placeholder">' +
@@ -472,8 +473,8 @@ function createQueueItem(photo, isCompleted) {
const meta = document.createElement('div');
meta.className = 'queue-item-meta';
const date = new Date(photo.completedAt || photo.timestamp).toLocaleString();
if (photo.blob && photo.blob.size > 0) {
const size = (photo.blob.size / 1024 / 1024).toFixed(2);
if (photoBlob && photoBlob.size > 0) {
const size = (photoBlob.size / 1024 / 1024).toFixed(2);
meta.textContent = size + ' MB \u2022 ' + date;
} else {
meta.textContent = date;
@@ -552,8 +553,9 @@ async function updateStats() {
const pendingCount = photos.filter(p => p.status === 'pending').length;
const uploadingCount = photos.filter(p => p.status === 'uploading').length;
const totalSize = photos
.filter(p => p.blob && p.blob.size)
.reduce((sum, p) => sum + p.blob.size, 0) / 1024 / 1024;
.map(p => Storage.getBlob(p))
.filter(b => b && b.size)
.reduce((sum, b) => sum + b.size, 0) / 1024 / 1024;
document.getElementById('pending-stat').textContent = pendingCount;
document.getElementById('uploading-stat').textContent = uploadingCount;