From 8e2c33b4ddaff5865b4cc6fe0f493ad98ef61006 Mon Sep 17 00:00:00 2001 From: Gregor MacLennan Date: Wed, 4 Nov 2026 23:35:06 +0000 Subject: [PATCH 1/2] test(sync): pin data progress across multiple syncing devices --- test/lib/sync.test.ts | 67 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 test/lib/sync.test.ts diff --git a/test/lib/sync.test.ts b/test/lib/sync.test.ts new file mode 100644 index 0000000..0d1293f --- /dev/null +++ b/test/lib/sync.test.ts @@ -0,0 +1,67 @@ +/** + * @vitest-environment node + */ +import type { ComapeoProjectClientApi } from '@comapeo/ipc' +import { describe, expect, it } from 'vitest' + +import { SyncStore, type SyncState } from '../../src/lib/sync.js' + +type DeviceCounts = Record + +function createSyncState(devices: DeviceCounts): SyncState { + const remoteDeviceSyncState: SyncState['remoteDeviceSyncState'] = {} + for (const [deviceId, counts] of Object.entries(devices)) { + remoteDeviceSyncState[deviceId] = { + initial: { isSyncEnabled: true, want: 0, wanted: 0 }, + data: { isSyncEnabled: true, ...counts }, + } + } + return { + initial: { isSyncEnabled: true }, + data: { isSyncEnabled: true }, + remoteDeviceSyncState, + } +} + +function createFakeProject(initialState: SyncState) { + let listener: ((state: SyncState) => void) | undefined + const project = { + $sync: { + getState: async () => initialState, + on: (_event: 'sync-state', fn: (state: SyncState) => void) => { + listener = fn + }, + off: () => { + listener = undefined + }, + }, + } + return { + project: project as unknown as ComapeoProjectClientApi, + emitState: (state: SyncState) => listener?.(state), + } +} + +describe('SyncStore data progress', () => { + it('is not complete while any syncing device still has blocks to exchange', async () => { + const { project, emitState } = createFakeProject( + createSyncState({ + cloud: { want: 5, wanted: 0 }, + phone: { want: 5, wanted: 0 }, + }), + ) + const store = new SyncStore(project) + const unsubscribe = store.subscribe(() => {}) + await expect.poll(() => store.getStateSnapshot()).not.toBeNull() + + emitState( + createSyncState({ + cloud: { want: 5, wanted: 0 }, + phone: { want: 0, wanted: 0 }, + }), + ) + + expect(store.getDataProgressSnapshot()).toBe(0.5) + unsubscribe() + }) +}) From eb7458233649be15f0f39b87e311ffa37f4628c9 Mon Sep 17 00:00:00 2001 From: Gregor MacLennan Date: Wed, 4 Nov 2026 23:35:19 +0000 Subject: [PATCH 2/2] fix(sync): sum remaining blocks across devices in data sync progress --- src/lib/sync.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/sync.ts b/src/lib/sync.ts index 9d30362..9ad0ec3 100644 --- a/src/lib/sync.ts +++ b/src/lib/sync.ts @@ -61,7 +61,7 @@ export class SyncStore { const existingMaxCount = this.#perDeviceMaxSyncCount.get(deviceId) if (typeof existingMaxCount === 'number' && existingMaxCount > 0) { - currentSyncCount = getDataSyncCountForDevice(deviceSyncState) + currentSyncCount += getDataSyncCountForDevice(deviceSyncState) totalMaxSyncCount += existingMaxCount } }