diff options
| author | Adam Soltys <[email protected]> | 2026-10-10 08:04:59 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-10 08:04:59 -0700 |
| commit | 89b16d6363920129744665ceeda5218f6786aaaa (patch) | |
| tree | fea7e76644233d51db10fe61d6052c15bd817a26 | |
| parent | 9abd654e8ef57de59a4f4bd5cbfbba560ed7e523 (diff) | |
| download | coinosv3-89b16d6363920129744665ceeda5218f6786aaaa.tar.gz coinosv3-89b16d6363920129744665ceeda5218f6786aaaa.zip | |
Feed: your own posts are in Following. The follows feed read only the people you follow, so your own post was there just after you sent it (the optimistic row) and gone after a reload — Adam didn't see his latest. Like every other client, a follows feed now includes you. Someone who follows nobody still gets the "find people" invitation, with their own posts under it. feed-live-test's catch-up queue now goes only to an ask naming the post's author (the feed also asks for yours); tools/feed-repost-test.js checks your own post shows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
| -rw-r--r-- | src/features/messages.js | 11 | ||||
| -rw-r--r-- | tools/feed-live-test.js | 6 | ||||
| -rw-r--r-- | tools/feed-repost-test.js | 9 |
3 files changed, 20 insertions, 6 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index 99b5c48..b271bc9 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -5605,6 +5605,9 @@ export function messagesFeature(ctx) { const feedAuthors = (def = feedDef()) => { if (!def) return []; const set = new Set(def.follows ? followsNow().set : []); + // your own posts belong in the feed of the people you follow, as in + // every other client — not only for the moment after posting + if (def.follows && mePk()) set.add(mePk()); for (const pk of def.authors || []) if (pk) set.add(pk); for (const pk of def.priv || []) if (pk) set.add(pk); // a list's private members for (const pk of packAuthors(def)) set.add(pk); @@ -11772,6 +11775,8 @@ export function messagesFeature(ctx) { }); const authors = feedAuthors(def); const hasQuery = feedHasQuery(def); + // the follows feed of someone who follows nobody: only their own posts + const noFollows = !!def.follows && !followsNow().set.size && !(def.authors || []).length && !(def.priv || []).length && !feedTopics(def).length; prepareFeedAhead(c); // Never a bare spinner over posts we already hold: while the opening // page's faces and pictures are still being decoded, the cached rows @@ -11851,11 +11856,13 @@ export function messagesFeature(ctx) { h('span', { class: 'spinner sm' }), h('span', { class: 'small muted' }, t('feedOfLoading'))) : def.of && !hasQuery ? h('div', { class: 'small muted', style: 'text-align:center;padding:12px 0' }, t('feedNoFollowsOf')) - : !hasQuery + : !hasQuery || noFollows ? def.builtin ? h('div', { class: 'col', style: 'gap:8px' }, h('div', { class: 'small muted' }, t('feedNoFollows')), - h('button', { class: 'btn-sm', onClick: () => { stopFeedWatch(); hook('openUserSearch'); render(); } }, t('feedFindPeople'))) + h('button', { class: 'btn-sm', onClick: () => { stopFeedWatch(); hook('openUserSearch'); render(); } }, t('feedFindPeople')), + // your own posts, under the invitation + noFollows && visible.length && mediaMode === 'all' ? h('div', { class: 'card col notes-feed', style: 'gap:0' }, ...rows) : null) : h('div', { class: 'col', style: 'gap:8px' }, h('div', { class: 'small muted' }, t('feedNoQuery')), h('button', { class: 'btn-sm', onClick: () => openFeedEditor(def) }, t('feedEdit'))) diff --git a/tools/feed-live-test.js b/tools/feed-live-test.js index b9879d0..2bee19f 100644 --- a/tools/feed-live-test.js +++ b/tools/feed-live-test.js @@ -58,8 +58,10 @@ await page.evaluateOnNewDocument(() => { subs.push([ws, m[1]]); // A catch-up query (no since, no until) is answered from the queue // the test filled, ahead of the real relay's end-of-stream. - if (!m[2].since && !m[2].until && (window.__catchup || []).length) { - const evs = window.__catchup; window.__catchup = []; + // (only to an ask that names their author: the feed also asks for your own posts) + const wants = (ev) => !m[2].authors || m[2].authors.includes(ev.pubkey); + if (!m[2].since && !m[2].until && (window.__catchup || []).some(wants)) { + const evs = window.__catchup.filter(wants); window.__catchup = window.__catchup.filter((ev) => !wants(ev)); setTimeout(() => { for (const ev of evs) ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EVENT', m[1], ev]) })); }, 0); } } diff --git a/tools/feed-repost-test.js b/tools/feed-repost-test.js index 7bc43aa..4936dd6 100644 --- a/tools/feed-repost-test.js +++ b/tools/feed-repost-test.js @@ -7,6 +7,7 @@ import puppeteer from './puppeteer.js'; import { cacheKeyFor } from '../src/wallet.js'; import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure'; +import { privateKeyFromSeedWords } from 'nostr-tools/nip06'; import { buildHtml } from '../build.js'; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); let ok = true; @@ -33,7 +34,7 @@ const page = await browser.newPage(); await page.evaluateOnNewDocument((fx) => { const Real = window.WebSocket; const fits = (ev, f) => (!f.kinds || f.kinds.includes(ev.kind)) && (!f.authors || f.authors.includes(ev.pubkey)) && (!f.ids || f.ids.includes(ev.id)); - window.WebSocket = function (...a) { const ws = new Real(...a); const send = ws.send.bind(ws); ws.send = (d) => { try { const m = JSON.parse(d); if (m[0] === 'REQ') { const fs = m.slice(2); setTimeout(() => { if (window.__live) for (const ev of fx.filter((e) => fs.some((f) => fits(e, f)))) ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EVENT', m[1], ev]) })); ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EOSE', m[1]]) })); }, 0); return; } if (m[0] === 'EVENT') return; } catch {} return send(d); }; return ws; }; + window.WebSocket = function (...a) { const ws = new Real(...a); const send = ws.send.bind(ws); ws.send = (d) => { try { const m = JSON.parse(d); if (m[0] === 'REQ') { const fs = m.slice(2); setTimeout(() => { if (window.__live) for (const ev of fx.concat(window.__extra || []).filter((e) => fs.some((f) => fits(e, f)))) ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EVENT', m[1], ev]) })); ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EOSE', m[1]]) })); }, 0); return; } if (m[0] === 'EVENT') return; } catch {} return send(d); }; return ws; }; window.WebSocket.prototype = Real.prototype; Object.assign(window.WebSocket, Real); }, POSTS); const errs = []; @@ -57,7 +58,9 @@ try { localStorage.setItem(k + ':profiles', JSON.stringify({ [z]: { name: 'Zapstore', t: Date.now() }, [l]: { name: 'LWB', t: Date.now() }, [o]: { name: 'Other', t: Date.now() } })); localStorage.setItem('btc-wallet-feed:' + k, 'following'); }, [base, ZAPSTORE.pk, LWB.pk, OTHER.pk]); - await page.evaluateOnNewDocument(() => { window.__live = true; }); + // and a post of my own, which I don't follow myself + const MINE = finalizeEvent({ kind: 1, created_at: now - 50, tags: [], content: 'my own latest post' }, privateKeyFromSeedWords(mn)); + await page.evaluateOnNewDocument((e) => { window.__live = true; window.__extra = [e]; }, MINE); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]')?.click()); @@ -70,6 +73,8 @@ try { check('a note already in the feed isn\'t shown again for its repost', (t.match(/zapstore says hello itself/g) || []).length === 1); await page.evaluate(() => [...document.querySelectorAll('[data-zap-post]')].find((n) => n.textContent.includes('Tankful')).querySelector('.note-text, .note-content').click()); check('a tap opens the reposted note\'s thread', await page.evaluate(async () => { for (let i = 0; i < 20; i++) { if (location.pathname.includes('nevent') || document.querySelector('.thread-reply-send, .thread-view')) return true; await new Promise((r) => setTimeout(r, 200)); } return false; })); + await page.evaluate(() => history.back()); await sleep(800); + check('my own post is in my Following feed', await waitText('my own latest post', 10000)); check('no page errors', errs.length === 0, errs.slice(0, 2).join(' | ')); } finally { await browser.close(); server.stop(true); } console.log(ok ? '\n✅ reposts show in the feed' : '\n❌ failed'); |
