aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-01 06:45:07 -0700
committerAdam Soltys <[email protected]>2026-10-01 06:45:07 -0700
commit5ff909d37fe32ba04a841941593d95c7b3498ec3 (patch)
treec85847c8f4a06b0a6b7dab8abb2d2eb54fefa226 /src/features
parent44d9de56e75875919b7ee4e2325396daf9efdd7d (diff)
downloadcoinosv3-5ff909d37fe32ba04a841941593d95c7b3498ec3.tar.gz
coinosv3-5ff909d37fe32ba04a841941593d95c7b3498ec3.zip
A profile shows all of someone's posts, not just the two primal still holds.
The phone had written off relay.coinos.io, nos.lol and damus after a network blip, so only primal answered. Home relays are now skipped for a minute at most and forgiven on resume/online; failures while hidden or offline don't count. Profiles also ask history-keeping relays, merge answers instead of replacing the page, and re-ask on reopen. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VP5Ljqk7EYcm5QQAbzDwog
Diffstat (limited to 'src/features')
-rw-r--r--src/features/messages.js48
1 files changed, 34 insertions, 14 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index a378f1d..b3f40f2 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -3495,7 +3495,11 @@ export function messagesFeature(ctx) {
relayListCache.set(pk, p);
return p;
}
- const notesRelays = async (pk) => [...new Set([...NOTE_RELAYS, ...(await relaysOf(pk))])];
+ // Plus relays that keep history: the big ones prune within days, and an
+ // author without a NIP-65 list (Joe Nakamoto, 2026-10-01) otherwise shows
+ // only whatever primal still holds — 2 posts of 22.
+ const PROFILE_ARCHIVE_RELAYS = ['wss://relay.ditto.pub', 'wss://nostr21.com', 'wss://offchain.pub'];
+ const notesRelays = async (pk) => [...new Set([...NOTE_RELAYS, ...(await relaysOf(pk)), ...PROFILE_ARCHIVE_RELAYS])];
// Posts of theirs we already hold. Tapping a face in the feed is the
// common way onto a profile, and the feed we just came from is full of
@@ -3513,35 +3517,51 @@ export function messagesFeature(ctx) {
return [...out.values()].sort((a, b) => b.created_at - a.created_at);
}
+ // Asked again when the page is reopened after this long: one bad answer
+ // (a relay down, the phone just back from sleep) must not freeze a
+ // profile at two posts for the rest of the session.
+ const NOTES_REFRESH_MS = 60_000;
function notesFor(pk) {
let c = notesCache.get(pk);
- if (c) return c;
+ if (c) {
+ if (!c.fetching && Date.now() - (c.fetchedAt || 0) > NOTES_REFRESH_MS) fetchNotes(pk, c);
+ return c;
+ }
// stored posts paint the page instantly; the relay fetch below freshens
const stored = pageCache().notes[pk];
const seed = stored ? stored.v : notesInHand(pk);
// Seeded from memory the status stays 'loading': these are posts we
- // happen to have, not their page, so the real answer still replaces
- // them — but there is something to read while it comes.
+ // happen to have, not their page, so there is something to read while
+ // the real answer comes.
c = stored
? { status: 'ready', notes: stored.v }
: { status: 'loading', notes: seed };
notesCache.set(pk, c);
+ fetchNotes(pk, c);
+ return c;
+ }
+ function fetchNotes(pk, c) {
+ c.fetching = true;
(async () => {
const evs = await queryOn(await notesRelays(pk), { kinds: FEED_KINDS, authors: [pk], limit: 30 }, 4500);
- const seen = new Set();
- const fresh = (evs || [])
- .filter((e) => !seen.has(e.id) && seen.add(e.id))
- .sort((a, b) => b.created_at - a.created_at);
- if (fresh.length || !c.notes.length) {
- await notesReady(fresh.slice(0, FEED_PAGE)); // the first screen arrives whole
- c.notes = fresh;
- persistPage('notes', pk, fresh.slice(0, 20).map(slimNote)); // cache stays bounded
- }
+ // Merged with what's already on the page, never swapped for it: the
+ // relays answer unevenly (primal held 2 of Joe Nakamoto's posts, ours
+ // 18, the archives 30), and a thin answer used to erase a full page.
+ const byId = new Map(c.notes.map((e) => [e.id, e]));
+ let added = 0;
+ for (const e of evs || []) if (!byId.has(e.id)) { byId.set(e.id, e); added++; }
+ if (!added && c.notes.length) return;
+ const merged = [...byId.values()].sort((a, b) => b.created_at - a.created_at);
+ await notesReady(merged.slice(0, FEED_PAGE)); // the first screen arrives whole
+ c.notes = merged;
+ c.end = false;
+ persistPage('notes', pk, merged.slice(0, 20).map(slimNote)); // cache stays bounded
})().catch(() => {}).finally(() => {
c.status = 'ready';
+ c.fetching = false;
+ c.fetchedAt = Date.now();
if (ui.profilePk === pk) render();
});
- return c;
}
// Infinite scroll: pull the next page of OLDER notes (until = oldest seen)