diff options
| author | Adam Soltys <[email protected]> | 2026-09-15 21:37:47 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-15 21:37:47 -0700 |
| commit | 88636e5c158c9e390eec25c4471207780857abeb (patch) | |
| tree | 2b3d8e7cb430d1e24d17c599dc51cf72ce9e18b9 /src | |
| parent | 14aff677c6e091c5dd9e5f58ae7e62c855de03ae (diff) | |
| download | coinosv3-88636e5c158c9e390eec25c4471207780857abeb.tar.gz coinosv3-88636e5c158c9e390eec25c4471207780857abeb.zip | |
Offline, the chat shows what it read: messages cache on arrival.
The room cache was written only on our own sends, deletes and
attachments, so a member who only reads had nothing on an offline boot —
"No messages yet" over a room read the day before, and no faces or names
with it. Messages now persist as they arrive (debounced past the backfill
burst), so the last fifty per channel are there without a network. Our
own face never waits its turn for a thumbnail and never gives it up to
the budget, so the header avatar paints offline too.
tools/offline-cache-test.js boots the PWA build, caches a room, reloads
fully offline and checks messages, names and thumbnails are all there.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JuwQqNWtDJcxPWtEm9134z
Diffstat (limited to 'src')
| -rw-r--r-- | src/features/messages.js | 18 |
1 files changed, 16 insertions, 2 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index e812520..cb63e04 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -545,7 +545,9 @@ export function messagesFeature(ctx) { // least recently seen faces give theirs up first. The row itself stays — // that face just paints the way it used to. if (JSON.stringify(s).length > THUMB_BUDGET) { - const oldestFirst = Object.keys(s).filter((k) => s[k].thumb).sort((a, b) => (s[a].t || 0) - (s[b].t || 0)); + // never our own: the header face is the one that must paint offline + const mine = new Set(myPubkeys()); + const oldestFirst = Object.keys(s).filter((k) => s[k].thumb && !mine.has(k)).sort((a, b) => (s[a].t || 0) - (s[b].t || 0)); for (const k of oldestFirst) { delete s[k].thumb; delete s[k].thumbFor; if (JSON.stringify(s).length <= THUMB_BUDGET) break; @@ -648,7 +650,7 @@ export function messagesFeature(ctx) { // won't have us is asked about twice a month rather than every boot. if (p.thumbFailVersion === THUMB_VERSION && p.thumbFail === p.picture && Date.now() - (p.thumbFailAt || 0) < Math.min(THUMB_RETRY * 2 ** ((p.thumbFails || 1) - 1), THUMB_RETRY_MAX)) return; - if (thumbing.has(pk) || thumbing.size >= 3) return; // a few at a time + if (thumbing.has(pk) || (thumbing.size >= 3 && !isMe(pk))) return; // a few at a time — our own face never waits // Making the thumbnail costs one more fetch of the original today to // save every fetch after it — but not on a connection someone is // nursing. Data Saver keeps today's behaviour. @@ -1128,11 +1130,13 @@ export function messagesFeature(ctx) { msgs.set(rumor.id, { rumor, author }); bumpMsgRev(); room.typing.delete(author); // the message itself ends the "typing…" + schedulePersist(room); } else if (rumor.kind === 5) { for (const e of rumor.tags.filter((x) => x[0] === 'e')) { const m = room.byChannel.get(channelId)?.get(e[1]); if (!m || m.author === author) room.deletes.add(e[1]); } + schedulePersist(room); } else if (rumor.kind === 3302) { const target = tag('e')?.[1]; if (target) { @@ -1162,6 +1166,16 @@ export function messagesFeature(ctx) { return room.jm.channels || []; }; + // Messages that ARRIVE get cached too, not only ones we send: the cache + // used to be written on our own actions alone, so a member who only reads + // had nothing on an offline boot — the room said "No messages yet" with a + // hundred messages read the day before. A backfill lands in a burst, so + // the write waits for it to settle. + const persistTimers = new Map(); // room -> timer + function schedulePersist(room) { + clearTimeout(persistTimers.get(room)); + persistTimers.set(room, setTimeout(() => { persistTimers.delete(room); try { persistCache(room); } catch {} }, 2500)); + } function persistCache(room) { const s = st(); for (const [chId, msgs] of room.byChannel) { |
