fix(mana/web/sync): drain leftover pending changes on startup

startAll() registered channels but never kicked a push for changes
that survived across page reloads. schedulePush only fires from the
Dexie hook on fresh writes, so any pending row from a previous session
sat in _pendingChanges until the user happened to mutate the same
table again — observed live as 27 pending across mana/memoro/places
that never reached the server despite a healthy sync route.

Add drainLeftoverPending() called once at the end of startAll(): scan
_pendingChanges for distinct appIds and schedulePush each registered
channel. Fire-and-forget; errors swallowed because the push retry
path already handles failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-09 13:24:51 +02:00
parent 0450c86527
commit be8c0482b7

View file

@ -551,6 +551,13 @@ export function createUnifiedSync(serverUrl: string, getToken: () => Promise<str
// Lazy apps: no pull until ensureAppSynced() is called
}
// Drain leftover pending changes from previous sessions. Without this,
// pending writes survive across reloads but never push because
// schedulePush() only fires on fresh Dexie writes — channels start
// cold and the queue stays stuck until the user happens to mutate
// the same table again.
drainLeftoverPending();
// Listen for online/offline
if (typeof window !== 'undefined') {
window.addEventListener('online', handleOnline);
@ -558,6 +565,25 @@ export function createUnifiedSync(serverUrl: string, getToken: () => Promise<str
}
}
function drainLeftoverPending(): void {
// Fire-and-forget: startAll() is sync, but the drain needs an async
// Dexie query. Errors are swallowed because the existing push retry
// path handles failures — we just need to kick the channels once.
(async () => {
try {
const leftoverAppIds = new Set<string>();
await db.table('_pendingChanges').each((change: { appId?: string }) => {
if (change.appId) leftoverAppIds.add(change.appId);
});
for (const appId of leftoverAppIds) {
if (channels.has(appId)) schedulePush(appId);
}
} catch {
// DB not ready or query failed — next user write will trigger push anyway.
}
})();
}
function stopAll(): void {
for (const [, channel] of channels) {
if (channel.pushTimer) clearTimeout(channel.pushTimer);