aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-29 09:03:13 -0700
committerAdam Soltys <[email protected]>2026-09-29 09:03:13 -0700
commitd2b3c05995b0d54cfe586128fd2e958a563ac861 (patch)
tree96efd61a7a790d398c33c3f378bc069ac235a4c6 /tools
parent4164d22809e49f1a5c55571f4f37eed8c2a70ba7 (diff)
downloadcoinosv3-d2b3c05995b0d54cfe586128fd2e958a563ac861.tar.gz
coinosv3-d2b3c05995b0d54cfe586128fd2e958a563ac861.zip
Someone with no profile picture paints as their punk from the first frame after a reload, not a blank circle that turns into one.
Faceless answers were never persisted (they'd evict real faces from the 150-row profile blob), so every reload re-asked and showed the loading circle until the relays said "nothing". A small pubkey->time list now remembers them: no kind 0 on two asks running (a cold relay isn't enough), or a kind 0 naming nothing; finding a face takes them off it. room-faces-test --refresh: reload inside the coinos room, faces keyed by author — 2 changed (wait→punk) before, 0 after. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
Diffstat (limited to 'tools')
-rw-r--r--tools/room-faces-test.js30
1 files changed, 30 insertions, 0 deletions
diff --git a/tools/room-faces-test.js b/tools/room-faces-test.js
index 8c39e59..706691b 100644
--- a/tools/room-faces-test.js
+++ b/tools/room-faces-test.js
@@ -31,6 +31,10 @@ try {
await sleep(process.argv.includes('--no-hover') ? 1500 : 6000);
const face = () => [...document.querySelectorAll('.chat-log .chat-row > .chat-avatar:not(.spacer), .chat-log .chat-row > .hat-wrap > .chat-avatar')]
.map((a) => a.classList.contains('ava-img') ? 'pic' : a.querySelector('img.punk') ? 'punk' : a.classList.contains('loading') ? 'wait' : 'other');
+ // name -> face state, per author (rows stream in, so positions shift)
+ const keyed = () => { const o = {}; for (const r of document.querySelectorAll('.chat-log .chat-row')) {
+ const a = r.querySelector(':scope > .chat-avatar:not(.spacer), :scope > .hat-wrap > .chat-avatar'); const n = r.querySelector('.chat-meta')?.firstElementChild?.textContent;
+ if (a && n) o[n] = a.classList.contains('ava-img') ? 'pic' : a.querySelector('img.punk') ? 'punk' : a.classList.contains('loading') ? 'wait' : 'other'; } return o; };
await page.evaluate((fs) => {
const face = new Function('return ' + fs)();
const mo = new MutationObserver(() => { if (!window.__first && document.querySelector('.chat-log .chat-row')) { window.__first = face(); window.__firstAt = performance.now(); } });
@@ -49,6 +53,32 @@ try {
check('the room opens with its messages', (first || []).length > 0);
check('no face paints as a punk and then turns into a picture', flips === 0, flips + ' flipped');
check('the tap waits no more than a moment', at - t0 < 2500, Math.round(at - t0) + 'ms');
+
+ if (process.argv.includes('--refresh')) {
+ // a reload inside the room: every face as it will stay, from the first
+ // frame — a known-faceless member is their punk at once, not a blank
+ // circle that turns into one. (Wait out the second ask that confirms
+ // who has no profile.)
+ await sleep(25000);
+ await page.evaluateOnNewDocument((fs, ks) => {
+ const face = new Function('return ' + fs)();
+ const keyed = new Function('return ' + ks)();
+ const mo = new MutationObserver(() => { if (!window.__first && document.querySelector('.chat-log .chat-row')) { window.__first = face(); window.__firstBy = keyed(); } });
+ addEventListener('DOMContentLoaded', () => mo.observe(document.body, { childList: true, subtree: true }));
+ }, face.toString(), keyed.toString());
+ await page.reload({ waitUntil: 'domcontentloaded' });
+ for (let i = 0; i < 100 && !(await page.evaluate(() => !!window.__first)); i++) await sleep(100);
+ await sleep(6000);
+ const f2 = await page.evaluate(() => window.__first);
+ const l2 = await page.evaluate(new Function('return (' + face.toString() + ')()'));
+ const byName = await page.evaluate(new Function('return (' + keyed.toString() + ')()'));
+ const firstBy = await page.evaluate(() => window.__firstBy);
+ const changedWho = Object.keys(firstBy || {}).filter((k) => byName[k] && firstBy[k] !== byName[k] && !(firstBy[k] === 'punk' && byName[k] === 'other'));
+ if (changedWho.length) console.log(' changed:', changedWho.map((k) => k + ' ' + firstBy[k] + '→' + byName[k]).join(', '));
+ const changed = changedWho.length;
+ console.log(' after reload, first paint:', JSON.stringify(tally(f2)), ' settled:', JSON.stringify(tally(l2)));
+ check('after a reload in the room, no face changes once painted', !!f2 && changed === 0, changed + ' changed');
+ }
} finally { await browser.close(); server.stop(true); }
function tally(a) { const o = {}; for (const x of a || []) o[x] = (o[x] || 0) + 1; return o; }
console.log(ok ? '\n✅ a room opens with its faces' : '\n❌ failed');