forked from mirror/crumbs-original
fix: resolve lost updates in multi-user collaboration sync
Concurrent edits to shared notes (e.g., one user checks a shopping list item while another adds an item) were lost because the sync system used whole-document Last-Write-Wins. This replaces it with a 3-way line-level content merge using node-diff3, so changes to different lines are preserved from both users. Key changes: - Add node-diff3 for 3-way line-level content merging - Remove the timestamp gate in processSyncPush that rejected entire offline sync pushes when another user had updated the note - Add content merge to both the sync push path and the PATCH endpoint, using version snapshots as the merge base - Send baseVersion from the client so the server can find the correct common ancestor for 3-way merge - Protect pending local fields during client pull merge by reading the sync queue and preserving queued-but-unpushed changes - Add comprehensive tests for content merge, server sync, and CRDT merge https://claude.ai/code/session_01FNpVBBjM6vKzfR8y9NM9Ci
This commit is contained in:
@@ -124,6 +124,37 @@ test.describe('Note Sharing', () => {
|
||||
expect(syncedNote.content).toBe('Owner sync update');
|
||||
});
|
||||
|
||||
test('Scenario: Both users\' edits to different fields are preserved after concurrent PATCH', async ({
|
||||
authenticatedPage: page,
|
||||
collabPage
|
||||
}) => {
|
||||
// Given a shared checklist note exists
|
||||
const note = await createSharedNote(
|
||||
page,
|
||||
collabPage,
|
||||
'Shopping List',
|
||||
'- [ ] Milk\n- [ ] Bread\n- [ ] Eggs'
|
||||
);
|
||||
|
||||
// When the owner checks Milk
|
||||
await page.request.patch(`/api/notes/${note.id}`, {
|
||||
data: { content: '- [x] Milk\n- [ ] Bread\n- [ ] Eggs', baseVersion: note.version }
|
||||
});
|
||||
|
||||
// And the collaborator adds Butter (based on the same original version)
|
||||
await collabPage.request.patch(`/api/notes/${note.id}`, {
|
||||
data: { content: '- [ ] Milk\n- [ ] Bread\n- [ ] Eggs\n- [ ] Butter', baseVersion: note.version }
|
||||
});
|
||||
|
||||
// Then both changes are preserved via content merge
|
||||
const res = await page.request.get(`/api/notes/${note.id}`);
|
||||
const merged = await res.json();
|
||||
expect(merged.content).toContain('- [x] Milk');
|
||||
expect(merged.content).toContain('- [ ] Butter');
|
||||
expect(merged.content).toContain('- [ ] Bread');
|
||||
expect(merged.content).toContain('- [ ] Eggs');
|
||||
});
|
||||
|
||||
test('Scenario: Collaborator sees leave action and can leave a shared note', async ({
|
||||
authenticatedPage: page,
|
||||
collabPage
|
||||
|
||||
Reference in New Issue
Block a user