diff --git a/js/app.js b/js/app.js index 08baa44..04836aa 100644 --- a/js/app.js +++ b/js/app.js @@ -1509,7 +1509,20 @@ async function withEncryptedUsername(obj) { // so other devices converge to the same value). async function migrateUsernamesAtRest() { if (!state.cryptoKey) return; - const todo = state.entries.filter(e => e && !e.username_enc && (e.username || '') !== ''); + let pool = (state.entries || []).slice(); + // Trashed rows live in state.trashed (loaded on demand), not state.entries, + // so the live-only sweep would leave a soft-deleted entry's username in + // cleartext until purge. Fetch + decrypt the trash so it's covered too — + // the PUT updates the row's fields without touching `deleted`, so it stays + // in the trash. Trashed rows aren't in the sync snapshot, so no churn. + try { + const trash = await api('/entries?deleted=1', { headers: authHeaders() }); + if (Array.isArray(trash)) { + await decryptEntryUsernames(trash); + pool = pool.concat(trash); + } + } catch (_) {} + const todo = pool.filter(e => e && !e.username_enc && (e.username || '') !== ''); if (todo.length === 0) return; let migrated = 0; for (const e of todo) {