fix(quick-save): Drain-Trailing-Re-Run gegen verworfene onChanged waehrend laufendem Drain (Latenz, kein Verlust)

This commit is contained in:
2026-06-14 14:29:47 +02:00
parent 43403bc755
commit 767c7c80aa
+25 -21
View File
@@ -233,39 +233,43 @@ function bindGlobalEvents() {
}); });
} }
// ---- LIVE-SYNC (Quick-Save aus dem Background) ---- // ---- QUICK-SAVE PENDING-QUEUE ----
// Ein Quick-Save schreibt boards im Background. Ein offener Tab muss das sehen, // Der Background-Worker haengt Quick-Saves an den eigenen Store-Key 'quicksave_pending' an (er
// sonst ueberschreibt er den Eintrag beim naechsten eigenen Save (QS-03). // schreibt NIE boards). Diese Seite ist die einzige boards-Schreiberin und drained die Queue in die
// Drained die Quick-Save-Queue in die Inbox. Die SEITE ist die einzige Schreiberin von 'boards'; // Inbox. Getrennte Schreib-Domaenen -> Worker und Seite koennen sich nicht im boards-Array
// der Background-Worker haengt nur an 'quicksave_pending' an. Dadurch koennen sich Worker und Seite // gegenseitig ueberschreiben (Datensicherheit, Phase-4-Review-Blocker 2b).
// nicht im boards-Array gegenseitig ueberschreiben (Datensicherheit, Phase-4-Review-Blocker 2b).
let _drainBusy = false; let _drainBusy = false;
let _drainQueued = false; // ein waehrend eines laufenden Drains angefragter Drain wird nachgeholt
async function drainQuickSavePending() { async function drainQuickSavePending() {
if (_drainBusy) return; // Re-Entry-Schutz (init + onChanged koennten ueberlappen) if (_drainBusy) { _drainQueued = true; return; }
_drainBusy = true; _drainBusy = true;
try { try {
const pending = await Store.get('quicksave_pending'); const pending = await Store.get('quicksave_pending');
if (!Array.isArray(pending) || pending.length === 0) return; if (Array.isArray(pending) && pending.length > 0) {
const drained = pending.slice(); const drained = pending.slice();
const drainedIds = new Set(drained.map(e => e && e.id).filter(Boolean)); const drainedIds = new Set(drained.map(e => e && e.id).filter(Boolean));
const inbox = await ensureInboxBoard(); // legt die Inbox an, falls noetig; gibt das Board zurueck const inbox = await ensureInboxBoard(); // legt die Inbox an, falls noetig; gibt das Board zurueck
for (const e of drained) { for (const e of drained) {
if (e && typeof e.url === 'string' && e.url) { if (e && typeof e.url === 'string' && e.url) {
inbox.bookmarks.push(normalizeBookmark({ title: e.title, url: e.url })); inbox.bookmarks.push(normalizeBookmark({ title: e.title, url: e.url }));
}
} }
await saveBoards();
// NUR die verarbeiteten Eintraege entfernen — ein gleichzeitiger Worker-Append bleibt erhalten.
const still = await Store.get('quicksave_pending');
const remaining = Array.isArray(still) ? still.filter(e => e && !drainedIds.has(e.id)) : [];
await Store.set('quicksave_pending', remaining);
// Render nur, wenn gerade KEIN Drag laeuft (renderBoards->replaceChildren wuerde ihn abreissen).
if (!document.querySelector('.board.dragging, .bm-item.dragging-source')) renderBoards();
} }
await saveBoards();
// NUR die verarbeiteten Eintraege entfernen — ein gleichzeitiger Worker-Append bleibt erhalten.
const still = await Store.get('quicksave_pending');
const remaining = Array.isArray(still) ? still.filter(e => e && !drainedIds.has(e.id)) : [];
await Store.set('quicksave_pending', remaining);
// Render nur, wenn gerade KEIN Drag laeuft (renderBoards->replaceChildren wuerde ihn abreissen).
if (!document.querySelector('.board.dragging, .bm-item.dragging-source')) renderBoards();
} catch (e) { } catch (e) {
console.error('Quick-Save-Drain fehlgeschlagen:', e && e.message); console.error('Quick-Save-Drain fehlgeschlagen:', e && e.message);
} finally { } finally {
_drainBusy = false; _drainBusy = false;
} }
// Kam waehrend des Drains ein weiterer Quick-Save an (onChanged wurde durch _drainBusy verworfen),
// jetzt nachholen. Der Eintrag war sicher in der Queue, nur noch nicht eingelesen.
if (_drainQueued) { _drainQueued = false; drainQuickSavePending(); }
} }
// Live-Sync (QS-03): ein offener NewTab drained die Queue, sobald der Worker etwas anhaengt. // Live-Sync (QS-03): ein offener NewTab drained die Queue, sobald der Worker etwas anhaengt.