fix: preserve local-only notes during loadNotes merge

mergeNotesByVersion only iterated incoming notes, dropping any
local-only notes (e.g. just created but not yet in the server/IDB
response). This caused note cards to disappear briefly after creation
when loadNotes raced with createNote.

Also rename E2E test note title to avoid name collision with existing
"Groceries" test.

https://claude.ai/code/session_01TdPpATUkmdY7DhBkmqrvWs
This commit is contained in:
Claude
2026-03-21 20:27:38 +01:00
committed by Fred Cabassut
parent bd3a14d518
commit 6278f11184
3 changed files with 22 additions and 3 deletions
+11
View File
@@ -67,6 +67,17 @@ describe('mergeNotesByVersion', () => {
expect(result[0].id).toBe('n2');
});
it('preserves local-only notes not present in the incoming data', () => {
const localOnly = makeNote({ id: 'n1', version: 1, content: 'just created' });
const serverNote = makeNote({ id: 'n2', version: 1, content: 'from server' });
const result = mergeNotesByVersion([localOnly], [serverNote]);
expect(result).toHaveLength(2);
expect(result.find((n) => n.id === 'n1')?.content).toBe('just created');
expect(result.find((n) => n.id === 'n2')?.content).toBe('from server');
});
it('preserves multiple notes correctly during a race condition', () => {
const localNotes = [
makeNote({ id: 'n1', version: 5, content: '- [x] Just saved' }),
+8
View File
@@ -60,11 +60,19 @@ function isNetworkError(err: unknown): boolean {
*/
export function mergeNotesByVersion(current: Note[], incoming: Note[]): Note[] {
const currentMap = new Map(current.map((n) => [n.id, n]));
const incomingIds = new Set<string>();
const merged = incoming.map((incomingNote) => {
incomingIds.add(incomingNote.id);
const local = currentMap.get(incomingNote.id);
if (local && local.version > incomingNote.version) return local;
return incomingNote;
});
// Preserve local-only notes (e.g. just created, not yet in server/IDB response)
for (const local of current) {
if (!incomingIds.has(local.id)) {
merged.push(local);
}
}
return merged;
}
+3 -3
View File
@@ -62,10 +62,10 @@ test.describe('Checklist', () => {
test('Scenario: Multiple checked items persist after closing and reopening', async ({ authenticatedPage: page }) => {
// Given a checklist note with three items exists
await createChecklistNote(page, 'Groceries', ['Milk', 'Eggs', 'Bread']);
await createChecklistNote(page, 'Weekly Shop', ['Milk', 'Eggs', 'Bread']);
// When the user reopens and checks two items
await noteCard(page, 'Groceries').click();
await noteCard(page, 'Weekly Shop').click();
await page.getByTestId('checklist-checkbox').nth(0).click(); // Milk → done
await page.getByTestId('checklist-checkbox').nth(0).click(); // Eggs (now first) → done
@@ -74,7 +74,7 @@ test.describe('Checklist', () => {
await page.getByTestId('close-editor-btn').click();
// And the checked state persists after reopening
await noteCard(page, 'Groceries').click();
await noteCard(page, 'Weekly Shop').click();
await expect(page.getByTestId('checklist-toggle-done')).toContainText('2 done');
await expect(page.getByTestId('checklist-input')).toHaveCount(1); // Only Bread remains active
await expect(page.getByTestId('checklist-input').first()).toHaveValue('Bread');