From b83edfda74e6b3dc2a93621671906882f9ee5dec Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Thu, 12 Feb 2026 00:18:58 -0800 Subject: [PATCH] Investigate all fields on reconcile calls --- frontend/src/messageCache.ts | 13 ++++++--- frontend/src/test/messageCache.test.ts | 37 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/frontend/src/messageCache.ts b/frontend/src/messageCache.ts index d526ea4..75973d5 100644 --- a/frontend/src/messageCache.ts +++ b/frontend/src/messageCache.ts @@ -89,15 +89,20 @@ export function updateAck(messageId: number, ackCount: number, paths?: MessagePa * Preserves any older paginated messages not present in the fetched page. */ export function reconcile(current: Message[], fetched: Message[]): Message[] | null { - const currentById = new Map(); + const currentById = new Map(); for (const m of current) { - currentById.set(m.id, m.acked); + currentById.set(m.id, { acked: m.acked, pathsLen: m.paths?.length ?? 0, text: m.text }); } let needsUpdate = false; for (const m of fetched) { - const currentAck = currentById.get(m.id); - if (currentAck === undefined || currentAck !== m.acked) { + const cur = currentById.get(m.id); + if ( + !cur || + cur.acked !== m.acked || + cur.pathsLen !== (m.paths?.length ?? 0) || + cur.text !== m.text + ) { needsUpdate = true; break; } diff --git a/frontend/src/test/messageCache.test.ts b/frontend/src/test/messageCache.test.ts index 537a5e2..a33a8e7 100644 --- a/frontend/src/test/messageCache.test.ts +++ b/frontend/src/test/messageCache.test.ts @@ -363,6 +363,43 @@ describe('messageCache', () => { expect(merged).not.toBeNull(); expect(merged!).toHaveLength(1); }); + + it('detects stale paths', () => { + const current = [ + createMessage({ id: 1, acked: 1, paths: [{ path: '1A', received_at: 1700000000 }] }), + ]; + const fetched = [ + createMessage({ + id: 1, + acked: 1, + paths: [ + { path: '1A', received_at: 1700000000 }, + { path: '2B', received_at: 1700000001 }, + ], + }), + ]; + + const merged = messageCache.reconcile(current, fetched); + expect(merged).not.toBeNull(); + expect(merged![0].paths).toHaveLength(2); + }); + + it('detects stale text (e.g. post-decryption)', () => { + const current = [createMessage({ id: 1, text: '[encrypted]' })]; + const fetched = [createMessage({ id: 1, text: 'Hello world' })]; + + const merged = messageCache.reconcile(current, fetched); + expect(merged).not.toBeNull(); + expect(merged![0].text).toBe('Hello world'); + }); + + it('returns null when acked, paths length, and text all match', () => { + const paths = [{ path: '1A', received_at: 1700000000 }]; + const current = [createMessage({ id: 1, acked: 2, paths, text: 'Hello' })]; + const fetched = [createMessage({ id: 1, acked: 2, paths, text: 'Hello' })]; + + expect(messageCache.reconcile(current, fetched)).toBeNull(); + }); }); describe('clear', () => {