aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-15 21:37:47 -0700
committerAdam Soltys <[email protected]>2026-09-15 21:37:47 -0700
commit88636e5c158c9e390eec25c4471207780857abeb (patch)
tree2b3d8e7cb430d1e24d17c599dc51cf72ce9e18b9 /src
parent14aff677c6e091c5dd9e5f58ae7e62c855de03ae (diff)
downloadcoinosv3-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.js18
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) {