aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-11-07 09:47:20 -0700
committerAdam Soltys <[email protected]>2026-11-07 09:47:20 -0700
commite905e5be5b2a0385e89de5ab3c0d95a917307314 (patch)
tree5ef7ba6a6f7a45ace8368de767ca101896ebed8f
parentb3a5ea14585a25e6fc7c9e35f94fcd3e1025a543 (diff)
downloadcoinosv3-e905e5be5b2a0385e89de5ab3c0d95a917307314.tar.gz
coinosv3-e905e5be5b2a0385e89de5ab3c0d95a917307314.zip
A flick through the feed no longer drags background work through the middle of it. "When I flick to scroll, it's not getting up to speed very quickly, and clicking around in the app feels a bit sluggish at times" (Adam, 2026-11-07). Measured on his Pixel 9 in the installed PWA: idle is quiet (0.16 s of script in 10 s), but three real flicks through Following spent 1.7 s of 9 on the main thread, frames up to 200 ms, and the work was the avatar thumbnailer finishing faces as they scrolled in: a synchronous canvas.toDataURL WebP encode per face (229 ms), the profile cache JSON.stringified twice per half-second flush (once just to measure it, up to ~2 MB with thumbnails), and a whole-page repaint per finished face that changes nothing on screen (feed rows keep the face they were admitted with). Now: the crop, resize and encode happen off the main thread (createImageBitmap with resizeWidth/Height, OffscreenCanvas.convertToBlob, FileReader), with the old canvas path as the fallback; a finished face is recorded once the page settles; scheduleRepaint holds while a finger is down or the page scrolled in the last 200 ms (at most 1.5 s), and the profile flush likewise (at most 5 s, forced on an account switch) and sizes the blob by summing rows instead of stringifying it. Tests: the create flow's heads-up / passphrase / check steps from earlier today broke every test that made a wallet by waiting for the words and clicking "Skip verification"; tools/puppeteer.js exports seedWords() and skipSeedCheck() and the 96 tests use them. room-faces and avatar-profile fail the same way on 8858591 (row.hover, scrollIntoView), before today's changesHEADmaster
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6Ts5Tktega8VbLg31q
-rw-r--r--src/features/messages.js89
-rw-r--r--tools/account-switch-leak-test.js6
-rw-r--r--tools/amber-dm-upgrade-test.js6
-rw-r--r--tools/avatar-profile-test.js6
-rw-r--r--tools/call-test.js6
-rw-r--r--tools/call-wake-test.js6
-rw-r--r--tools/comments-test.js6
-rw-r--r--tools/composer-media-test.js6
-rw-r--r--tools/dm-nip42-test.js6
-rw-r--r--tools/dm-row-resort-test.js6
-rw-r--r--tools/feed-appetite-test.js6
-rw-r--r--tools/feed-conversations-test.js6
-rw-r--r--tools/feed-custom-test.js6
-rw-r--r--tools/feed-list-test.js6
-rw-r--r--tools/feed-media-cache-test.js6
-rw-r--r--tools/feed-media-views-test.js6
-rw-r--r--tools/feed-mention-tap-test.js6
-rw-r--r--tools/feed-pager-ahead-test.js6
-rw-r--r--tools/feed-picture-pager-test.js6
-rw-r--r--tools/feed-reboost-test.js6
-rw-r--r--tools/feed-repost-test.js6
-rw-r--r--tools/feed-switch-seed-test.js6
-rw-r--r--tools/feed-video-aspect-test.js6
-rw-r--r--tools/feed-video-pager-test.js6
-rw-r--r--tools/feed-window-dom-test.js6
-rw-r--r--tools/fullscreen-notice-test.js6
-rw-r--r--tools/gif-picker-test.js6
-rw-r--r--tools/go-live-test.js6
-rw-r--r--tools/grid-early-player-test.js6
-rw-r--r--tools/history-export-test.js6
-rw-r--r--tools/image-tap-test.js6
-rw-r--r--tools/lightbox-scroll-test.js6
-rw-r--r--tools/link-preview-test.js6
-rw-r--r--tools/live-stream-test.js6
-rw-r--r--tools/media-grid-dups-test.js6
-rw-r--r--tools/media-grid-order-test.js6
-rw-r--r--tools/media-moments-feed-test.js6
-rw-r--r--tools/mention-ac-test.js6
-rw-r--r--tools/moderation-test.js6
-rw-r--r--tools/negentropy-sync-test.js6
-rw-r--r--tools/notif-categories-test.js4
-rw-r--r--tools/notif-custom-emoji-test.js6
-rw-r--r--tools/notif-faces-test.js6
-rw-r--r--tools/notif-note-cache-test.js6
-rw-r--r--tools/notif-report-fold-test.js6
-rw-r--r--tools/nwc-settings-ui-test.js6
-rw-r--r--tools/onboard-cover-test.js4
-rw-r--r--tools/own-post-feeds-test.js6
-rw-r--r--tools/pager-caption-test.js6
-rw-r--r--tools/pager-close-test.js6
-rw-r--r--tools/pager-controls-test.js6
-rw-r--r--tools/pager-eager-test.js6
-rw-r--r--tools/pager-first-frame-test.js6
-rw-r--r--tools/pager-gone-picture-test.js6
-rw-r--r--tools/pager-no-poster-test.js6
-rw-r--r--tools/pager-prime-test.js6
-rw-r--r--tools/pager-ready-gate-test.js6
-rw-r--r--tools/pager-resize-test.js6
-rw-r--r--tools/pager-rotate-test.js6
-rw-r--r--tools/pager-seek-hide-test.js6
-rw-r--r--tools/pager-sharpen-test.js6
-rw-r--r--tools/pager-swipe-fly-test.js6
-rw-r--r--tools/pager-tail-test.js6
-rw-r--r--tools/pager-tap-slop-test.js6
-rw-r--r--tools/pager-wheel-test.js6
-rw-r--r--tools/pic-proxy-test.js6
-rw-r--r--tools/picture-pager-ready-test.js6
-rw-r--r--tools/poll-test.js6
-rw-r--r--tools/post-edit-test.js6
-rw-r--r--tools/profile-pager-test.js6
-rw-r--r--tools/profile-repost-test.js6
-rw-r--r--tools/puppeteer.js17
-rw-r--r--tools/quote-pager-test.js6
-rw-r--r--tools/quoted-clip-fullscreen-test.js6
-rw-r--r--tools/relay-budget-test.js6
-rw-r--r--tools/relays-settings-test.js6
-rw-r--r--tools/resume-wake-test.js6
-rw-r--r--tools/room-faces-test.js6
-rw-r--r--tools/scheduled-post-test.js6
-rw-r--r--tools/sensitive-feed-test.js6
-rw-r--r--tools/signer-resume-test.js6
-rw-r--r--tools/slow-image-test.js6
-rw-r--r--tools/standalone-file-test.js4
-rw-r--r--tools/thread-instant-test.js6
-rw-r--r--tools/translate-phone-lang-test.js6
-rw-r--r--tools/translate-test.js6
-rw-r--r--tools/video-autoplay-test.js6
-rw-r--r--tools/video-compress-test.js6
-rw-r--r--tools/video-events-test.js6
-rw-r--r--tools/video-fallback-test.js6
-rw-r--r--tools/video-gone-test.js6
-rw-r--r--tools/video-hls-play-test.js6
-rw-r--r--tools/video-tap-test.js6
-rw-r--r--tools/video-tile-poster-test.js6
-rw-r--r--tools/video-touch-tap-test.js6
-rw-r--r--tools/web-comment-test.js6
-rw-r--r--tools/wn-only-device-test.js6
-rw-r--r--tools/youtube-tap-test.js6
98 files changed, 373 insertions, 303 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index 90bfaaf..6c4f5f8 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -760,14 +760,38 @@ export function messagesFeature(ctx) {
let allUnsubs = [];
let dmUnsubs = []; // the DM wrap subscriptions alone, so they can be rebuilt
+ // Is the page under the reader's finger, or still coasting from a flick?
+ // Background work (a repaint for a face that arrived, writing the profile
+ // cache) waits for it to settle: on Adam's Pixel a flick through the feed
+ // spent 1.7 s of 9 on the main thread doing exactly that, and taps queued
+ // behind it (2026-11-07, "flick to scroll … not getting up to speed …
+ // clicking around feels sluggish"). Never longer than `most` ms, though:
+ // a slow, steady read must still see what arrived.
+ let pageMovedAt = 0, fingerDown = false;
+ if (typeof window !== 'undefined') {
+ window.addEventListener('scroll', () => { pageMovedAt = Date.now(); }, { passive: true, capture: true });
+ window.addEventListener('touchstart', () => { fingerDown = true; }, { passive: true, capture: true });
+ for (const n of ['touchend', 'touchcancel']) window.addEventListener(n, (e) => { if (!e.touches || !e.touches.length) fingerDown = false; }, { passive: true, capture: true });
+ }
+ const pageMoving = () => fingerDown || Date.now() - pageMovedAt < 200;
+ const whenSettled = (fn, most = 1500, step = 150) => {
+ const until = Date.now() + most;
+ const go = () => { if (pageMoving() && Date.now() < until) { setTimeout(go, step); return; } fn(); };
+ go();
+ };
+
let repaintTimer = null;
const scheduleRepaint = () => {
if (repaintTimer) return;
+ const since = Date.now();
const run = () => {
// not under a pager in motion: the page beneath is out of sight, and
// its repaint (40–55 ms on Adam's phone) dropped frames from flights
// through the pictures (2026-10-17) — it waits for the landing
if (pagerMoving()) { repaintTimer = setTimeout(run, 120); return; }
+ // nor mid-flick on the page itself (up to 1.5 s): a whole-page morph
+ // in the middle of a fling is a dropped frame and a late tap
+ if (pageMoving() && Date.now() - since < 1500) { repaintTimer = setTimeout(run, 150); return; }
repaintTimer = null;
// a visitor's public page (a profile, a thread, a feed) repaints too
if (ui.screen === 'wallet' || ui.pubProf) renderThreadStable();
@@ -826,11 +850,14 @@ export function messagesFeature(ctx) {
// memory and is written back once per burst: a batch of thirty profiles
// landing during a scroll used to parse and re-serialize the whole blob
// thirty times over, on the main thread, mid-scroll.
- let profBlob = null, profFlush = null;
+ let profBlob = null, profFlush = null, profFlushSince = 0;
const PROFILE_ROWS = 1500; // a row without a thumbnail is ~430 chars; IndexedDB has room
const profilesDisk = () => (profBlob ||= wallet.loadFeatureState('profiles', {}));
- function flushProfiles() {
- profFlush = null;
+ function flushProfiles(force) {
+ // up to 5 s for the page to settle: stringifying this blob (thumbnails
+ // and all, up to ~2 MB) is a long main-thread task
+ if (!force && pageMoving() && Date.now() - (profFlushSince || 0) < 5000) { profFlush = setTimeout(flushProfiles, 300); return; }
+ profFlush = null; profFlushSince = 0;
const s = profBlob;
if (!s) return;
// Who stays when space runs out: your own faces, the people you follow,
@@ -848,7 +875,10 @@ export function messagesFeature(ctx) {
// Thumbnails are the bulk of this blob, so they live on a budget: the
// least recently seen strangers give theirs up first. The row itself
// stays — that face just paints the way it used to.
- let size = JSON.stringify(s).length;
+ // (an estimate, not a JSON.stringify of the lot just to measure it:
+ // a row is ~430 chars plus its thumbnail)
+ let size = 0;
+ for (const k in s) size += 430 + ((s[k] && s[k].thumb) || '').length;
if (size > THUMB_BUDGET) {
// never our own: the header face is the one that must paint offline
for (const k of evictFirst(Object.keys(s).filter((k) => s[k].thumb && !mine.has(k)))) {
@@ -875,7 +905,7 @@ export function messagesFeature(ctx) {
? { thumb: p.thumb, thumbFor: p.thumbFor, thumbPx: p.thumbPx || 0 } : {}),
...(p.thumbFail ? { thumbFail: p.thumbFail, thumbFailAt: p.thumbFailAt || 0, thumbFails: p.thumbFails || 1,
thumbFailVersion: p.thumbFailVersion || 0 } : {}) };
- if (!profFlush) profFlush = setTimeout(flushProfiles, 500);
+ if (!profFlush) { profFlushSince = Date.now(); profFlush = setTimeout(flushProfiles, 500); }
}
// A punk picture is OUR OWN art. Every coinos user who keeps the default
// publishes https://v3.coinos.io/punks/N.webp as their nostr picture, and
@@ -1081,6 +1111,39 @@ export function messagesFeature(ctx) {
blobProbing.set(url, task);
return task;
}
+ // Centre-crop to a square (the way background-size:cover paints it),
+ // shrink, and encode — off the main thread where the browser allows: the
+ // crop and resize through createImageBitmap, the encode through an
+ // OffscreenCanvas's convertToBlob. The synchronous canvas.toDataURL it
+ // replaces cost ~30 ms a face on a phone, mid-scroll (2026-11-07).
+ async function encodeThumb(bmp) {
+ const side = Math.min(bmp.width, bmp.height);
+ if (!side) throw new Error('empty');
+ const sx = (bmp.width - side) / 2, sy = (bmp.height - side) / 2;
+ if (typeof OffscreenCanvas !== 'undefined' && OffscreenCanvas.prototype.convertToBlob) {
+ let small = null;
+ try {
+ small = await createImageBitmap(bmp, sx, sy, side, side, { resizeWidth: THUMB_PX, resizeHeight: THUMB_PX, resizeQuality: 'high' });
+ const oc = new OffscreenCanvas(THUMB_PX, THUMB_PX);
+ oc.getContext('2d').drawImage(small, 0, 0);
+ const asUrl = (b) => new Promise((ok, bad) => { const r = new FileReader(); r.onload = () => ok(r.result); r.onerror = () => bad(r.error); r.readAsDataURL(b); });
+ let blob = await oc.convertToBlob({ type: 'image/webp', quality: 0.75 });
+ if (blob.type !== 'image/webp') blob = await oc.convertToBlob({ type: 'image/jpeg', quality: 0.7 });
+ if (blob.size * 4 / 3 > THUMB_MAX) blob = await oc.convertToBlob({ type: 'image/webp', quality: 0.5 }); // a busy picture, leaned on harder
+ const data = await asUrl(blob);
+ if (data.length > THUMB_MAX) throw new Error('too big');
+ return data;
+ } finally { try { small && small.close(); } catch {} }
+ }
+ const c = document.createElement('canvas');
+ c.width = c.height = THUMB_PX;
+ c.getContext('2d').drawImage(bmp, sx, sy, side, side, 0, 0, THUMB_PX, THUMB_PX);
+ let data = c.toDataURL('image/webp', 0.75);
+ if (!data.startsWith('data:image/webp')) data = c.toDataURL('image/jpeg', 0.7);
+ if (data.length > THUMB_MAX) data = c.toDataURL('image/webp', 0.5);
+ if (data.length > THUMB_MAX) throw new Error('too big');
+ return data;
+ }
function makeThumb(pk, p) {
if (!p || !p.picture || typeof document === 'undefined') return;
if (noCorsHost(p.picture)) return; // a host that won't hand over bytes; the <img> shows it anyway
@@ -1152,18 +1215,8 @@ export function messagesFeature(ctx) {
scheduleRepaint(); // the big face, where one is up, can paint from the copy now
}
bmp = await createImageBitmap(await res.blob());
- // centre-crop to a square, the way background-size:cover paints it
- const side = Math.min(bmp.width, bmp.height);
- if (!side) throw new Error('empty');
- const c = document.createElement('canvas');
- c.width = c.height = THUMB_PX;
- c.getContext('2d').drawImage(bmp, (bmp.width - side) / 2, (bmp.height - side) / 2,
- side, side, 0, 0, THUMB_PX, THUMB_PX);
- let data = c.toDataURL('image/webp', 0.75);
- if (!data.startsWith('data:image/webp')) data = c.toDataURL('image/jpeg', 0.7);
- if (data.length > THUMB_MAX) data = c.toDataURL('image/webp', 0.5); // a busy picture, leaned on harder
- if (data.length > THUMB_MAX) throw new Error('too big');
- done({ thumb: data, thumbFor: url, thumbPx: THUMB_PX });
+ const data = await encodeThumb(bmp);
+ whenSettled(() => done({ thumb: data, thumbFor: url, thumbPx: THUMB_PX }));
} catch (e) {
// a quick TypeError is the CORS refusal (a dead host takes longer)
// only a refusal corsFetch confirmed (a body cut off mid-download
@@ -19062,7 +19115,7 @@ export function messagesFeature(ctx) {
follows = null; followsAt = 0; feed = null; relayLists = null;
mutes = null; mutesAt = 0;
stCache = null; stAt = 0; // the next account's state, not this one's
- if (profFlush) { clearTimeout(profFlush); flushProfiles(); } // this account's faces, written before the blob is dropped
+ if (profFlush) { clearTimeout(profFlush); flushProfiles(true); } // this account's faces, written before the blob is dropped
profBlob = null;
timedActivity.clear(); diskHeld.clear(); reacts.clear(); boosts.clear(); seenNoteEv.clear(); myReactEv.clear(); quoted.clear();
noteCountsReady.clear();
diff --git a/tools/account-switch-leak-test.js b/tools/account-switch-leak-test.js
index ea3b4ee..aa06579 100644
--- a/tools/account-switch-leak-test.js
+++ b/tools/account-switch-leak-test.js
@@ -3,7 +3,7 @@
// under its key, so a throwaway account showed adam's zaps and reactions for
// good. Also: a DM cache already polluted that way is swept on load.
// Run: bun tools/account-switch-leak-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
@@ -61,9 +61,9 @@ const nav = (label) => page.evaluate((l) => document.querySelector(`.app-nav-but
async function newWallet() {
await click('create a new wallet'); await sleep(600);
if (!(await page.$('.words .w .t'))) { await click('generate'); await sleep(600); }
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
return mn;
}
diff --git a/tools/amber-dm-upgrade-test.js b/tools/amber-dm-upgrade-test.js
index 250e8ef..3a1917a 100644
--- a/tools/amber-dm-upgrade-test.js
+++ b/tools/amber-dm-upgrade-test.js
@@ -7,7 +7,7 @@
// relays hold is opened again — including ones the old code wrote off.
// The remote signer is played by this script over a shimmed relay.
// Run: bun tools/amber-dm-upgrade-test.js (and ALL_LOGGED=1 bun tools/amber-dm-upgrade-test.js)
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import * as nip44 from 'nostr-tools/nip44';
@@ -102,9 +102,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
// an Amber login, and a wrap log in which the old code had already
diff --git a/tools/avatar-profile-test.js b/tools/avatar-profile-test.js
index 5c5293d..6d6c944 100644
--- a/tools/avatar-profile-test.js
+++ b/tools/avatar-profile-test.js
@@ -3,7 +3,7 @@
// wherever the reader was in the feed. Back returns to the same place.
//
// Run: bun tools/avatar-profile-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { generateMnemonic } from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english';
import { cacheKeyFor } from '../src/wallet.js';
@@ -25,9 +25,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
const A = 'a'.repeat(63) + '9', B = 'b'.repeat(63) + '7';
diff --git a/tools/call-test.js b/tools/call-test.js
index d5c3909..44f9b46 100644
--- a/tools/call-test.js
+++ b/tools/call-test.js
@@ -5,7 +5,7 @@
// both. A call can be declined, and a stranger's (neither followed nor
// written to) doesn't ring, but is in the conversation as a missed call. Everything on the wire is an ephemeral gift wrap.
// Run: bun tools/call-test.js (NO_BUILD=1 to reuse dist/)
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
import { getPublicKey } from 'nostr-tools/pure';
@@ -74,9 +74,9 @@ async function wallet(name) {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive');
return { page, mn, pk: getPublicKey(privateKeyFromSeedWords(mn)), base: cacheKeyFor(mn + '\n'), click, waitText, name };
}
diff --git a/tools/call-wake-test.js b/tools/call-wake-test.js
index b0f64cc..3f81db1 100644
--- a/tools/call-wake-test.js
+++ b/tools/call-wake-test.js
@@ -6,7 +6,7 @@
// they talk. A push for a stranger's call, or for something that isn't an
// offer, rings nothing. Plus the worker's check on its own (classifyCall).
// Run: bun tools/call-wake-test.js (NO_BUILD=1 to reuse dist/)
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
import { getPublicKey, finalizeEvent, generateSecretKey } from 'nostr-tools/pure';
@@ -91,9 +91,9 @@ async function wallet(name) {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive');
const sk = privateKeyFromSeedWords(mn);
return { context, page, mn, sk, pk: getPublicKey(sk), base: cacheKeyFor(mn + '\n'), click, waitText, name };
diff --git a/tools/comments-test.js b/tools/comments-test.js
index 5a73e6f..21d028d 100644
--- a/tools/comments-test.js
+++ b/tools/comments-test.js
@@ -5,7 +5,7 @@
// are caught at the socket; nothing is published.
//
// Run: bun tools/comments-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { SimplePool } from 'nostr-tools/pool';
import { buildHtml } from '../build.js';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -86,8 +86,8 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
- await click('skip verification');
+ await seedWords(page);
+ await skipSeedCheck(page);
await waitText('receive', 20000);
await page.goto('http://localhost:5283/' + NEVENT, { waitUntil: 'domcontentloaded' });
const first = comments.sort((a, b) => a.created_at - b.created_at)[0];
diff --git a/tools/composer-media-test.js b/tools/composer-media-test.js
index 94b343a..db1648a 100644
--- a/tools/composer-media-test.js
+++ b/tools/composer-media-test.js
@@ -5,7 +5,7 @@
// paperclip, its URL landing in the draft.
//
// Run: bun tools/composer-media-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { buildHtml } from '../build.js';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -42,9 +42,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
await page.evaluate(() => document.querySelector('button[aria-label="Settings"]').click());
await sleep(300);
diff --git a/tools/dm-nip42-test.js b/tools/dm-nip42-test.js
index 51fa3a9..debe910 100644
--- a/tools/dm-nip42-test.js
+++ b/tools/dm-nip42-test.js
@@ -6,7 +6,7 @@
// copy of what we send in our own inbox so other clients see it too.
// Both inboxes are played by this script's WebSocket shim.
// Run: bun tools/dm-nip42-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent, verifyEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
@@ -87,9 +87,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const ME_SK = privateKeyFromSeedWords(mn); const ME = getPublicKey(ME_SK);
const base = cacheKeyFor(mn + '\n');
diff --git a/tools/dm-row-resort-test.js b/tools/dm-row-resort-test.js
index 9e400bc..0ecdd44 100644
--- a/tools/dm-row-resort-test.js
+++ b/tools/dm-row-resort-test.js
@@ -3,7 +3,7 @@
// it was first drawn for — a tap on Bill's face opened someone else's
// profile. After the re-sort, each row's avatar and row open their own.
// Run: bun tools/dm-row-resort-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
@@ -67,9 +67,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('Receive', 20000);
const ME = getPublicKey(privateKeyFromSeedWords(mn));
const base = cacheKeyFor(mn + '\n');
diff --git a/tools/feed-appetite-test.js b/tools/feed-appetite-test.js
index b79a2a9..9f469cb 100644
--- a/tools/feed-appetite-test.js
+++ b/tools/feed-appetite-test.js
@@ -6,7 +6,7 @@
// still a while brings the ordinary page back. A page held still under an
// insert above is not travel.
// Run: bun tools/feed-appetite-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -79,9 +79,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/feed-conversations-test.js b/tools/feed-conversations-test.js
index f16fc35..7394986 100644
--- a/tools/feed-conversations-test.js
+++ b/tools/feed-conversations-test.js
@@ -4,7 +4,7 @@
// profile's Replies tab shows the same context; the view persists across
// feeds and reloads like the other views.
// Run: bun tools/feed-conversations-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -48,9 +48,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, alice, bob, roots, replies]) => {
diff --git a/tools/feed-custom-test.js b/tools/feed-custom-test.js
index 122cb0a..c5ed41f 100644
--- a/tools/feed-custom-test.js
+++ b/tools/feed-custom-test.js
@@ -4,7 +4,7 @@
// feed, and deleting a feed. No relays are contacted for real — the
// WebSocket shim answers the subscriptions.
// Run: bun tools/feed-custom-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { generateMnemonic } from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english';
import { cacheKeyFor } from '../src/wallet.js';
@@ -90,9 +90,9 @@ try {
await sleep(400);
// through the real first run: the wizard's own words, read off the screen
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
// the starter follows land once the current list has been asked for
diff --git a/tools/feed-list-test.js b/tools/feed-list-test.js
index 6d60240..00942b8 100644
--- a/tools/feed-list-test.js
+++ b/tools/feed-list-test.js
@@ -5,7 +5,7 @@
// unticking "Keep as a nostr list" sends the NIP-09 deletion. No relay is
// written for real — the WebSocket shim swallows EVENT and answers OK.
// Run: bun tools/feed-list-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
@@ -83,9 +83,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
const ME_SK = privateKeyFromSeedWords(mn);
diff --git a/tools/feed-media-cache-test.js b/tools/feed-media-cache-test.js
index 5a26ca2..8ca8185 100644
--- a/tools/feed-media-cache-test.js
+++ b/tools/feed-media-cache-test.js
@@ -3,7 +3,7 @@
// slow picture), a reload paints the grid from that cache at once and
// resumes the crawl where it stopped, and tile frames are kept as pictures.
// Run: bun tools/feed-media-cache-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -115,9 +115,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const key = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk]) => {
diff --git a/tools/feed-media-views-test.js b/tools/feed-media-views-test.js
index dac9804..74de24a 100644
--- a/tools/feed-media-views-test.js
+++ b/tools/feed-media-views-test.js
@@ -5,7 +5,7 @@
// until tapped: then the author, the words as a caption, likes and sats.
// Back and Escape close it.
// Run: bun tools/feed-media-views-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -112,9 +112,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const key = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/feed-mention-tap-test.js b/tools/feed-mention-tap-test.js
index e4d3f7a..d381288 100644
--- a/tools/feed-mention-tap-test.js
+++ b/tools/feed-mention-tap-test.js
@@ -2,7 +2,7 @@
// thread on top of them (the mention's tap reached the row, which opened
// the thread over the profile it had just shown). Links in a post likewise.
// Run: bun tools/feed-mention-tap-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { npubEncode } from 'nostr-tools/nip19';
@@ -33,9 +33,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('Receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, a, f, ns]) => {
diff --git a/tools/feed-pager-ahead-test.js b/tools/feed-pager-ahead-test.js
index 057ac3e..0906034 100644
--- a/tools/feed-pager-ahead-test.js
+++ b/tools/feed-pager-ahead-test.js
@@ -3,7 +3,7 @@
// feed pages on, and the pager grows — past a run of posts with nothing to
// show, too — so the reader never meets the end while there is more.
// Run: bun tools/feed-pager-ahead-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -96,9 +96,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/feed-picture-pager-test.js b/tools/feed-picture-pager-test.js
index 4cbe68c..f74c981 100644
--- a/tools/feed-picture-pager-test.js
+++ b/tools/feed-picture-pager-test.js
@@ -4,7 +4,7 @@
// closes it, as before; once the reader has moved on, a tap shows the post's
// details and the next tap (or Escape) closes the pager onto that post.
// Run: bun tools/feed-picture-pager-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -67,9 +67,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/feed-reboost-test.js b/tools/feed-reboost-test.js
index e22df91..f43e7b5 100644
--- a/tools/feed-reboost-test.js
+++ b/tools/feed-reboost-test.js
@@ -4,7 +4,7 @@
// reload — stays out. A repost of a post you haven't read still lands, and
// the row you read keeps its place.
// Run: bun tools/feed-reboost-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -95,9 +95,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pks]) => {
diff --git a/tools/feed-repost-test.js b/tools/feed-repost-test.js
index 4936dd6..1bf3d7a 100644
--- a/tools/feed-repost-test.js
+++ b/tools/feed-repost-test.js
@@ -4,7 +4,7 @@
// copy doesn't check out is left out, and a note already in the feed isn't
// shown twice.
// Run: bun tools/feed-repost-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
@@ -48,9 +48,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, z, l, o]) => {
diff --git a/tools/feed-switch-seed-test.js b/tools/feed-switch-seed-test.js
index bf2b830..6d9d4f0 100644
--- a/tools/feed-switch-seed-test.js
+++ b/tools/feed-switch-seed-test.js
@@ -3,7 +3,7 @@
// other open feeds already hold, and once the feed on screen settles the
// other feeds' first posts are fetched ahead into the disk cache.
// Run: bun tools/feed-switch-seed-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -70,9 +70,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, a, b, ns]) => {
diff --git a/tools/feed-video-aspect-test.js b/tools/feed-video-aspect-test.js
index 84c0568..4196fe1 100644
--- a/tools/feed-video-aspect-test.js
+++ b/tools/feed-video-aspect-test.js
@@ -4,7 +4,7 @@
// shape still gets the 16:9 box.
// Photos too: a big one paints from a column-sized copy (see makeMediaThumb).
// Run: bun tools/feed-video-aspect-test.js (CHROME=<path> to pick a browser)
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -96,9 +96,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const key = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/feed-video-pager-test.js b/tools/feed-video-pager-test.js
index 61119a0..5bba7d8 100644
--- a/tools/feed-video-pager-test.js
+++ b/tools/feed-video-pager-test.js
@@ -4,7 +4,7 @@
// Each clip has a seek bar to drag; a tap pauses (and shows the details),
// another plays on. Leaving full screen leaves the pager.
// Run: bun tools/feed-video-pager-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -82,9 +82,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/feed-window-dom-test.js b/tools/feed-window-dom-test.js
index c86e98e..1f6ff93 100644
--- a/tools/feed-window-dom-test.js
+++ b/tools/feed-window-dom-test.js
@@ -3,7 +3,7 @@
// page keeps its length, the row under your eyes never moves when the window
// shifts, and scrolling back up brings the top posts back.
// Run: bun tools/feed-window-dom-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -78,9 +78,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/fullscreen-notice-test.js b/tools/fullscreen-notice-test.js
index d3b2785..a7c041a 100644
--- a/tools/fullscreen-notice-test.js
+++ b/tools/fullscreen-notice-test.js
@@ -5,7 +5,7 @@
// (feed-video-pager-test). A feed clip's own bar has no full-screen button:
// a tap on the clip is the way, into the pager.
// Run: bun tools/fullscreen-notice-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -79,9 +79,9 @@ async function run(lockOk) {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/gif-picker-test.js b/tools/gif-picker-test.js
index f72cde3..9a48de9 100644
--- a/tools/gif-picker-test.js
+++ b/tools/gif-picker-test.js
@@ -4,7 +4,7 @@
// In a DM the picked GIF is sent at once. Uses the live API — localhost is
// admitted as nostr.build's shared development client.
// Run: bun tools/gif-picker-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -35,9 +35,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk]) => {
diff --git a/tools/go-live-test.js b/tools/go-live-test.js
index c3fc8e3..3990fa6 100644
--- a/tools/go-live-test.js
+++ b/tools/go-live-test.js
@@ -6,7 +6,7 @@
// recording, and the recording is complete.
// Run: bun tools/go-live-test.js (LIVE_SERVER=path to coinosv3-server/live/server.js; NO_BUILD=1 to reuse dist/;
// LIVE_CONFIG=a json with {sfuAppId, sfuSecret} to watch over WebRTC — the real Cloudflare SFU)
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { naddrEncode } from 'nostr-tools/nip19';
import { mkdtempSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
@@ -129,9 +129,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive');
const base = cacheKeyFor(mn + '\n');
await page.evaluate((k) => {
diff --git a/tools/grid-early-player-test.js b/tools/grid-early-player-test.js
index 0ab1c1e..04e44fb 100644
--- a/tools/grid-early-player-test.js
+++ b/tools/grid-early-player-test.js
@@ -14,7 +14,7 @@
// that); on a link slower than the clip, more — to the 4 MB cap. With Data
// Saver on, the pager is careful again: such a clip waits for a tap and
// nothing loads ahead.
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -100,9 +100,9 @@ async function run() {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/history-export-test.js b/tools/history-export-test.js
index f7fff82..877b95e 100644
--- a/tools/history-export-test.js
+++ b/tools/history-export-test.js
@@ -2,7 +2,7 @@
// movements) as a line of a CSV file, oldest first, with what was paid, the
// fee beside it and the change to the balance; a failed payment is left out.
// Run: bun tools/history-export-test.js (NO_BUILD=1 to reuse dist/)
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let ok = true;
@@ -43,9 +43,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive');
// Spending's history, as the Ark manager stores it (moved into IndexedDB at the next boot)
const key = cacheKeyFor(mn + '\n') + ':ark:' + ns('http://localhost:3535');
diff --git a/tools/image-tap-test.js b/tools/image-tap-test.js
index d3bcb7e..ce40d53 100644
--- a/tools/image-tap-test.js
+++ b/tools/image-tap-test.js
@@ -3,7 +3,7 @@
// the picture opens the picture.
//
// Run: bun tools/image-tap-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { buildHtml } from '../build.js';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -27,9 +27,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
const A = 'a'.repeat(63) + '9';
diff --git a/tools/lightbox-scroll-test.js b/tools/lightbox-scroll-test.js
index be03e9b..f507557 100644
--- a/tools/lightbox-scroll-test.js
+++ b/tools/lightbox-scroll-test.js
@@ -6,7 +6,7 @@
// thread had been opened from, or to the top. Back out of a thread still
// lands on the row that opened it.
// Run: bun tools/lightbox-scroll-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -56,9 +56,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/link-preview-test.js b/tools/link-preview-test.js
index 52ddca1..190b220 100644
--- a/tools/link-preview-test.js
+++ b/tools/link-preview-test.js
@@ -4,7 +4,7 @@
// nothing to say stays a bare link; pictures and YouTube keep their own
// boxes.
// Run: bun tools/link-preview-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
@@ -80,9 +80,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
// follow the author, so the feed asks for their posts
diff --git a/tools/live-stream-test.js b/tools/live-stream-test.js
index 2be6051..56dcb71 100644
--- a/tools/live-stream-test.js
+++ b/tools/live-stream-test.js
@@ -6,7 +6,7 @@
// profile says they're on, Back stops the player, the browser without HLS
// of its own plays it through hls.js, and a zap carries the stream's `a`.
// Run: bun tools/live-stream-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { naddrEncode } from 'nostr-tools/nip19';
@@ -150,9 +150,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive');
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/media-grid-dups-test.js b/tools/media-grid-dups-test.js
index e7b67da..1a00222 100644
--- a/tools/media-grid-dups-test.js
+++ b/tools/media-grid-dups-test.js
@@ -5,7 +5,7 @@
// that won't load leaves the Images grid instead of a broken-picture glyph. And on a big screen with a mouse, a
// small clip opened in the pager is drawn at most 1.5× its own pixels, not
// blown up across the monitor. Run: bun tools/media-grid-dups-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -92,9 +92,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/media-grid-order-test.js b/tools/media-grid-order-test.js
index 9274d92..0e333f0 100644
--- a/tools/media-grid-order-test.js
+++ b/tools/media-grid-order-test.js
@@ -3,7 +3,7 @@
// jumped around): tiles found later go under the ones already showing, in
// their own date order, even when they are dated between them; only a post
// newer than the top tile goes above it. Run: bun tools/media-grid-order-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -97,9 +97,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const key = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk]) => {
diff --git a/tools/media-moments-feed-test.js b/tools/media-moments-feed-test.js
index 7b429c9..5b4c8fb 100644
--- a/tools/media-moments-feed-test.js
+++ b/tools/media-moments-feed-test.js
@@ -1,6 +1,6 @@
// Full feed integration with local media and relay acknowledgements; no events leave the test.
// Run: bun tools/media-moments-feed-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -111,9 +111,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const key = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/mention-ac-test.js b/tools/mention-ac-test.js
index 52ba1ef..e476e61 100644
--- a/tools/mention-ac-test.js
+++ b/tools/mention-ac-test.js
@@ -2,7 +2,7 @@
// people — the ones you follow first, then the search — and picking one puts
// nostr:npub… in the text; the post goes out with their p tag.
// Run: bun tools/mention-ac-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';
import { npubEncode } from 'nostr-tools/nip19';
@@ -59,9 +59,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
await sleep(3000); // a new wallet's starter follows land first; ours go over them
const base = cacheKeyFor(mn + '\n');
diff --git a/tools/moderation-test.js b/tools/moderation-test.js
index 426115c..8856864 100644
--- a/tools/moderation-test.js
+++ b/tools/moderation-test.js
@@ -5,7 +5,7 @@
// are hidden; Settings → Nostr lays the mute list out and edits it.
// No relay is written for real — the WebSocket shim swallows EVENT.
// Run: bun tools/moderation-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
@@ -114,9 +114,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
const ME_SK = privateKeyFromSeedWords(mn);
diff --git a/tools/negentropy-sync-test.js b/tools/negentropy-sync-test.js
index e0257ca..c7adb84 100644
--- a/tools/negentropy-sync-test.js
+++ b/tools/negentropy-sync-test.js
@@ -8,7 +8,7 @@
// backlog, and is remembered as such for a day: the next ask there goes
// straight out as a REQ.
// Run: bun tools/negentropy-sync-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
import { buildHtml } from '../build.js';
@@ -48,9 +48,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const ME = getPublicKey(privateKeyFromSeedWords(mn));
const MINE = finalizeEvent({ kind: 1, created_at: now - 600, tags: [], content: 'my post, replied to' }, FRIEND.sk);
diff --git a/tools/notif-categories-test.js b/tools/notif-categories-test.js
index f38dd01..8bd0da1 100644
--- a/tools/notif-categories-test.js
+++ b/tools/notif-categories-test.js
@@ -1,7 +1,7 @@
// Settings → Notifications lists zaps and reactions as their own categories,
// each with its own switch. Run: bun tools/notif-categories-test.js
import assert from 'node:assert/strict';
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { buildHtml } from '../build.js';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const html = await buildHtml({ minify: true, pwa: false });
@@ -19,7 +19,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload(); await sleep(500);
await click('create a new wallet'); await sleep(600);
- await click('skip verification'); await sleep(300);
+ await skipSeedCheck(page); await sleep(300);
await page.waitForFunction(() => document.body.innerText.toLowerCase().includes('receive'), { timeout: 20000 });
await page.evaluate(() => [...document.querySelectorAll('button')].find((e) => e.getAttribute('aria-label') === 'Settings').click()); await sleep(600);
await page.evaluate(() => [...document.querySelectorAll('.settings-tile')].find((e) => /notification/i.test(e.textContent)).click()); await sleep(600);
diff --git a/tools/notif-custom-emoji-test.js b/tools/notif-custom-emoji-test.js
index 5847a0c..b1ff564 100644
--- a/tools/notif-custom-emoji-test.js
+++ b/tools/notif-custom-emoji-test.js
@@ -3,7 +3,7 @@
// :peepoClap: text, because the row kept the content and not the tag.
// A row saved before that is healed when the relay hands the reaction again.
// Run: bun tools/notif-custom-emoji-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
@@ -34,9 +34,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
const ME = getPublicKey(privateKeyFromSeedWords(mn));
diff --git a/tools/notif-faces-test.js b/tools/notif-faces-test.js
index a1c1c8f..0aa7a92 100644
--- a/tools/notif-faces-test.js
+++ b/tools/notif-faces-test.js
@@ -3,7 +3,7 @@
// opens with faces, not circles filling in. The stub relay answers the kind-0
// batch for the actors with pictures served by this test.
// Run: bun tools/notif-faces-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
@@ -65,9 +65,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
const ME = getPublicKey(privateKeyFromSeedWords(mn));
diff --git a/tools/notif-note-cache-test.js b/tools/notif-note-cache-test.js
index e24c2e2..2b8be67 100644
--- a/tools/notif-note-cache-test.js
+++ b/tools/notif-note-cache-test.js
@@ -1,7 +1,7 @@
// Referenced notification notes are fetched before the page opens, persisted,
// and available on the first paint after a reload.
// Run: bun tools/notif-note-cache-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
@@ -84,9 +84,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await clickText('create a new wallet');
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mnemonic = (await page.$$eval('.words .w .t', (nodes) => nodes.map((node) => node.textContent.trim()))).join(' ');
- await clickText('skip verification');
+ await skipSeedCheck(page);
await waitText('receive');
const base = cacheKeyFor(mnemonic + '\n');
const me = getPublicKey(privateKeyFromSeedWords(mnemonic));
diff --git a/tools/notif-report-fold-test.js b/tools/notif-report-fold-test.js
index 678aefd..6a906ec 100644
--- a/tools/notif-report-fold-test.js
+++ b/tools/notif-report-fold-test.js
@@ -4,7 +4,7 @@
// (Adam's phone, 2026-10-14: a reply Amethyst showed was missing here). A
// person one follow flagged, or a stranger nobody did, shows as ever.
// Run: bun tools/notif-report-fold-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
@@ -36,9 +36,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
const ME = getPublicKey(privateKeyFromSeedWords(mn));
diff --git a/tools/nwc-settings-ui-test.js b/tools/nwc-settings-ui-test.js
index b88f9f7..ea52a6c 100644
--- a/tools/nwc-settings-ui-test.js
+++ b/tools/nwc-settings-ui-test.js
@@ -2,7 +2,7 @@
// budget that resets every day, week or month; the card says so, and
// Revoke keeps it (greyed out of the list) answering for a while.
// Run: bun tools/nwc-settings-ui-test.js (needs the regtest ark stack on :3535 for the card to show)
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { buildHtml } from '../build.js';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let ok = true;
@@ -47,8 +47,8 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
- await click('skip verification');
+ await seedWords(page);
+ await skipSeedCheck(page);
await waitText('receive', 20000);
await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((e) => e.getAttribute('aria-label') === 'Settings'); b.click(); }); await sleep(600);
await page.evaluate(() => { const b = [...document.querySelectorAll('.settings-tile')].find((e) => [...e.querySelectorAll('span')].some((x) => x.textContent.trim() === 'Nostr')); if (b) b.click(); }); await sleep(800);
diff --git a/tools/onboard-cover-test.js b/tools/onboard-cover-test.js
index 4262bc1..733a515 100644
--- a/tools/onboard-cover-test.js
+++ b/tools/onboard-cover-test.js
@@ -3,7 +3,7 @@
// twelve nature covers with a counter; the preview copies are what load.
// Run: bun run build && bun tools/onboard-cover-test.js [screenshot.png]
import assert from 'node:assert/strict';
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const server = Bun.serve({ port: 0, async fetch(req) {
const p = new URL(req.url).pathname;
@@ -21,7 +21,7 @@ try {
await page.reload(); await sleep(1200);
const click = (x) => page.evaluate((x) => { const e = [...document.querySelectorAll('button')].find((n) => n.textContent.trim().toLowerCase().includes(x)); e?.click(); return !!e; }, x);
await click('create a new wallet'); await sleep(1500);
- await click('skip verification'); await sleep(1500);
+ await skipSeedCheck(page); await sleep(1500);
await page.evaluate(() => localStorage.setItem('btc-wallet-onb-step', 'avatar'));
await page.reload();
await page.waitForSelector('.onb-cover', { timeout: 10000 });
diff --git a/tools/own-post-feeds-test.js b/tools/own-post-feeds-test.js
index a3e8fc3..a877749 100644
--- a/tools/own-post-feeds-test.js
+++ b/tools/own-post-feeds-test.js
@@ -6,7 +6,7 @@
// whose tag it wears, which is now a t tag on the event) — not to a topic
// feed that happens to be open and doesn't match.
// Run: bun tools/own-post-feeds-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
import { generateSecretKey, getPublicKey } from 'nostr-tools/pure';
@@ -63,9 +63,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
const ME = getPublicKey(privateKeyFromSeedWords(mn));
diff --git a/tools/pager-caption-test.js b/tools/pager-caption-test.js
index 50ffe4f..a3d7800 100644
--- a/tools/pager-caption-test.js
+++ b/tools/pager-caption-test.js
@@ -4,7 +4,7 @@
// caption leaves the pager and opens the post, where the whole caption
// reads. A link to the web opens in its own tab and the pager stays.
// Run: bun tools/pager-caption-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { npubEncode } from 'nostr-tools/nip19';
@@ -87,9 +87,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, valley, ns]) => {
diff --git a/tools/pager-close-test.js b/tools/pager-close-test.js
index a7c6c12..2f0bece 100644
--- a/tools/pager-close-test.js
+++ b/tools/pager-close-test.js
@@ -6,7 +6,7 @@
// the reader on that page, and that entry becomes it. And the ordinary case
// still steps Back, so the phone's Back doesn't reopen the pager.
// Run: bun tools/pager-close-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -96,9 +96,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-controls-test.js b/tools/pager-controls-test.js
index a02591d..79e9bca 100644
--- a/tools/pager-controls-test.js
+++ b/tools/pager-controls-test.js
@@ -4,7 +4,7 @@
// the reaction button offers the emoji sheet, over the pager, as the feed's
// does; the emoji picked shows on the button, and a tap again takes it back.
// Run: bun tools/pager-controls-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -54,9 +54,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-eager-test.js b/tools/pager-eager-test.js
index 3d980fb..f1cd150 100644
--- a/tools/pager-eager-test.js
+++ b/tools/pager-eager-test.js
@@ -8,7 +8,7 @@
// Saver on, the pager is careful again: such a clip waits for a tap and
// nothing loads ahead.
// Run: bun tools/pager-eager-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -93,9 +93,9 @@ async function run(saveData, slow = false) {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-first-frame-test.js b/tools/pager-first-frame-test.js
index 0d477b7..63de5ac 100644
--- a/tools/pager-first-frame-test.js
+++ b/tools/pager-first-frame-test.js
@@ -5,7 +5,7 @@
// opening, and a clip beyond gets no picture (and stays out of the
// carousel) until a first frame comes — never the post's still first. The slide left behind keeps its frame for the swipe
// back. Run: bun tools/pager-first-frame-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -81,9 +81,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-gone-picture-test.js b/tools/pager-gone-picture-test.js
index 34c89b6..b7e3c8f 100644
--- a/tools/pager-gone-picture-test.js
+++ b/tools/pager-gone-picture-test.js
@@ -2,7 +2,7 @@
// nor the original can load) never joins the pager's carousel: a picture
// joins once decoded (2026-10-17), and a step passes over it to the next.
// (Its slide is marked gone, with the host's name, as before — out of sight.) Run: bun tools/pager-gone-picture-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -43,9 +43,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-no-poster-test.js b/tools/pager-no-poster-test.js
index 332eb53..c40f73e 100644
--- a/tools/pager-no-poster-test.js
+++ b/tools/pager-no-poster-test.js
@@ -7,7 +7,7 @@
// connection to be careful with (Data Saver), where a clip may never load,
// the thumbnail poster stays.
// Run: bun tools/pager-no-poster-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -77,9 +77,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-prime-test.js b/tools/pager-prime-test.js
index 4989790..df20cc7 100644
--- a/tools/pager-prime-test.js
+++ b/tools/pager-prime-test.js
@@ -15,7 +15,7 @@
// that); on a link slower than the clip, more — to the 4 MB cap. With Data
// Saver on, the pager is careful again: such a clip waits for a tap and
// nothing loads ahead.
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -97,9 +97,9 @@ async function run() {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-ready-gate-test.js b/tools/pager-ready-gate-test.js
index dd6982d..20cb337 100644
--- a/tools/pager-ready-gate-test.js
+++ b/tools/pager-ready-gate-test.js
@@ -5,7 +5,7 @@
// grid — shows the clips the screen's way round (tall ones on a phone held
// upright), a square either way, until the toggle asks for all shapes.
// Run: bun tools/pager-ready-gate-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -81,9 +81,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-resize-test.js b/tools/pager-resize-test.js
index 07fcbba..d18745b 100644
--- a/tools/pager-resize-test.js
+++ b/tools/pager-resize-test.js
@@ -5,7 +5,7 @@
// button on a clip 23 posts down showed him another post's picture. The
// resize can come before the observer has even named the first slide.
// Run: bun tools/pager-resize-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -68,9 +68,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-rotate-test.js b/tools/pager-rotate-test.js
index 9dcc7e3..4d453ad 100644
--- a/tools/pager-rotate-test.js
+++ b/tools/pager-rotate-test.js
@@ -4,7 +4,7 @@
// autoplay used to start it again, from the start, with sound if it had been
// unmuted there), and not the slide a turn's half-way layout passes over.
// Run: bun tools/pager-rotate-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -68,9 +68,9 @@ async function run(unmuteFirst) {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-seek-hide-test.js b/tools/pager-seek-hide-test.js
index 50863ba..59a0353 100644
--- a/tools/pager-seek-hide-test.js
+++ b/tools/pager-seek-hide-test.js
@@ -8,7 +8,7 @@
// position counter is the other way
// round: it shows while the pager moves and fades out once it has settled.
// Run: bun tools/pager-seek-hide-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -70,9 +70,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-sharpen-test.js b/tools/pager-sharpen-test.js
index ea5c878..e61b51d 100644
--- a/tools/pager-sharpen-test.js
+++ b/tools/pager-sharpen-test.js
@@ -3,7 +3,7 @@
// comes to rest; only the slide at rest carries the zoom layer. (Originals
// decoded and uploaded as they passed made a flight stutter — Adam,
// 2026-10-16.) Run: bun tools/pager-sharpen-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -42,9 +42,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-swipe-fly-test.js b/tools/pager-swipe-fly-test.js
index 657ea75..1184e7b 100644
--- a/tools/pager-swipe-fly-test.js
+++ b/tools/pager-swipe-fly-test.js
@@ -5,7 +5,7 @@
// mid-flight stops it on the post most on screen, and a swipe back from
// there goes to the one before.
// Run: bun tools/pager-swipe-fly-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -85,9 +85,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-tail-test.js b/tools/pager-tail-test.js
index 9d537e1..96227f8 100644
--- a/tools/pager-tail-test.js
+++ b/tools/pager-tail-test.js
@@ -4,7 +4,7 @@
// moved onto and plays by itself. With nothing older to be had, the end says
// that's everything. Escape from there lands on the last clip's post.
// Run: bun tools/pager-tail-test.js (SHOT=path.png saves the loading screen)
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -84,9 +84,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-tap-slop-test.js b/tools/pager-tap-slop-test.js
index 4de87df..c184e3b 100644
--- a/tools/pager-tap-slop-test.js
+++ b/tools/pager-tap-slop-test.js
@@ -4,7 +4,7 @@
// 30px pauses (or plays) the clip and leaves the pager where it was; only a
// longer one pages.
// Run: bun tools/pager-tap-slop-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -66,9 +66,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pager-wheel-test.js b/tools/pager-wheel-test.js
index 45a5115..7f59c34 100644
--- a/tools/pager-wheel-test.js
+++ b/tools/pager-wheel-test.js
@@ -5,7 +5,7 @@
// the wheel pans it while zoomed and pages again at 1×; and arrows at the
// edges for the pointer, the sideways pair only where the post has more.
// Run: bun tools/pager-wheel-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -68,9 +68,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/pic-proxy-test.js b/tools/pic-proxy-test.js
index 881867c..69a09c1 100644
--- a/tools/pic-proxy-test.js
+++ b/tools/pic-proxy-test.js
@@ -5,7 +5,7 @@
// paints the original as before. The server is stood in for: its answers are
// canned.
// Run: bun tools/pic-proxy-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -76,9 +76,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/picture-pager-ready-test.js b/tools/picture-pager-ready-test.js
index 7cfaa45..08a4e21 100644
--- a/tools/picture-pager-ready-test.js
+++ b/tools/picture-pager-ready-test.js
@@ -5,7 +5,7 @@
// the screen stay out, with the "Tall only" / "All shapes" toggle in the
// top bar; and with fewer than ten ready and more still warming, no
// counter shows and a flick goes one post. Run: bun tools/picture-pager-ready-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -50,9 +50,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/poll-test.js b/tools/poll-test.js
index edcb061..41aa02e 100644
--- a/tools/poll-test.js
+++ b/tools/poll-test.js
@@ -4,7 +4,7 @@
// caught at the socket so the test never votes on someone's real poll.
//
// Run: bun tools/poll-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { SimplePool } from 'nostr-tools/pool';
import { cacheKeyFor } from '../src/wallet.js';
import { neventOf } from '../src/nostr.js';
@@ -41,9 +41,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
const Q = 'a'.repeat(63) + '9';
diff --git a/tools/post-edit-test.js b/tools/post-edit-test.js
index 2ed1260..08d1190 100644
--- a/tools/post-edit-test.js
+++ b/tools/post-edit-test.js
@@ -5,7 +5,7 @@
// deletion is remembered across a reload.
// No relay is written for real — the WebSocket shim swallows EVENT.
// Run: bun tools/post-edit-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
@@ -95,9 +95,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
const ME_SK = privateKeyFromSeedWords(mn);
diff --git a/tools/profile-pager-test.js b/tools/profile-pager-test.js
index 2a78598..5593192 100644
--- a/tools/profile-pager-test.js
+++ b/tools/profile-pager-test.js
@@ -3,7 +3,7 @@
// else's — and closing it after moving on lands the profile on that post.
// (It opened alone, in the lightbox, before: no pager on a profile.)
// Run: bun tools/profile-pager-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -67,9 +67,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns, other]) => {
diff --git a/tools/profile-repost-test.js b/tools/profile-repost-test.js
index 7825efe..5ee97e7 100644
--- a/tools/profile-repost-test.js
+++ b/tools/profile-repost-test.js
@@ -4,7 +4,7 @@
// boost, a video event still has its clip — they were all saved as kind 1
// notes, so a boost came back as "reply · {"content":…}".
// Run: bun tools/profile-repost-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { npubEncode } from 'nostr-tools/nip19';
import { buildHtml } from '../build.js';
@@ -57,8 +57,8 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
- await click('skip verification');
+ await seedWords(page);
+ await skipSeedCheck(page);
await waitText('receive', 20000);
await page.goto(server.url.href + npubEncode(ME.pk), { waitUntil: 'domcontentloaded' });
await look('from the relays');
diff --git a/tools/puppeteer.js b/tools/puppeteer.js
index 2609981..8cefd6e 100644
--- a/tools/puppeteer.js
+++ b/tools/puppeteer.js
@@ -126,3 +126,20 @@ export default new Proxy(real, {
return typeof v === 'function' ? v.bind(t) : v;
},
});
+
+// The create flow (2026-11-07): a heads-up, then the words, then the
+// passphrase page, then a one-word check. seedWords() gets a test from
+// "Create a new wallet" to the words on screen; skipSeedCheck() from there
+// (or from the heads-up) into the wallet, no passphrase, check skipped.
+export async function seedWords(page) {
+ await page.waitForSelector('#seed-intro-next, .words .w .t', { timeout: 30000 });
+ if (await page.$('#seed-intro-next')) await page.evaluate(() => document.getElementById('seed-intro-next').click());
+ await page.waitForSelector('.words .w .t');
+}
+export async function skipSeedCheck(page) {
+ if (await page.$('#seed-intro-next')) await seedWords(page);
+ for (const id of ['seed-next', 'pass-next', 'seed-skip']) {
+ await page.waitForSelector('#' + id, { timeout: 10000 });
+ await page.evaluate((i) => document.getElementById(i).click(), id);
+ }
+}
diff --git a/tools/quote-pager-test.js b/tools/quote-pager-test.js
index 7f8b76c..d125edd 100644
--- a/tools/quote-pager-test.js
+++ b/tools/quote-pager-test.js
@@ -6,7 +6,7 @@
// quoted picture's details name its author, and closing lands on the post
// that quoted it.
// Run: bun tools/quote-pager-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { noteEncode } from 'nostr-tools/nip19';
@@ -80,9 +80,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pa, pq]) => {
diff --git a/tools/quoted-clip-fullscreen-test.js b/tools/quoted-clip-fullscreen-test.js
index 41984a2..ce235f6 100644
--- a/tools/quoted-clip-fullscreen-test.js
+++ b/tools/quoted-clip-fullscreen-test.js
@@ -5,7 +5,7 @@
// handler took the clip back out of full screen for a pager that then
// wouldn't open.
// Run: bun tools/quoted-clip-fullscreen-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -47,9 +47,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, alice, bob, roots, replies]) => {
diff --git a/tools/relay-budget-test.js b/tools/relay-budget-test.js
index bb1dd11..d9798a3 100644
--- a/tools/relay-budget-test.js
+++ b/tools/relay-budget-test.js
@@ -9,7 +9,7 @@
// unchanged live subscriptions stay up, the reports sync honours the saved
// clock, each relay's REQ closes on its own EOSE, and no relay carries more
// than ten one-shot REQs at once. Run: bun tools/relay-budget-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { neventEncode } from 'nostr-tools/nip19';
@@ -101,9 +101,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/relays-settings-test.js b/tools/relays-settings-test.js
index 9e18f59..771e3cd 100644
--- a/tools/relays-settings-test.js
+++ b/tools/relays-settings-test.js
@@ -3,7 +3,7 @@
// both only after a fresh lookup, newer than the copies it replaces.
// No relay is written for real — the WebSocket shim swallows EVENT.
// Run: bun tools/relays-settings-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
import { buildHtml } from '../build.js';
@@ -71,9 +71,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const ME_SK = privateKeyFromSeedWords(mn);
const ME = getPublicKey(ME_SK);
diff --git a/tools/resume-wake-test.js b/tools/resume-wake-test.js
index 3b992fb..c142dfb 100644
--- a/tools/resume-wake-test.js
+++ b/tools/resume-wake-test.js
@@ -7,7 +7,7 @@
// 2026-11-01). On resume every open socket is now asked one tiny REQ; a
// silent one is closed and redialed at once, its subscriptions refired, and
// the feed refresh runs on the live sockets. Run: bun tools/resume-wake-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -71,9 +71,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/room-faces-test.js b/tools/room-faces-test.js
index cc4c6fe..ecbcc14 100644
--- a/tools/room-faces-test.js
+++ b/tools/room-faces-test.js
@@ -4,7 +4,7 @@
// profiles cached — the worst case — against the real coinos community.
//
// Run: bun tools/room-faces-test.js [--no-hover]
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { buildHtml } from '../build.js';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let ok = true;
@@ -22,8 +22,8 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
- await click('skip verification');
+ await seedWords(page);
+ await skipSeedCheck(page);
await waitText('receive', 20000);
await page.evaluate(() => [...document.querySelectorAll('.app-bottom-nav .app-nav-button')][1].click());
await waitText('members', 20000);
diff --git a/tools/scheduled-post-test.js b/tools/scheduled-post-test.js
index 546537c..e4a61f1 100644
--- a/tools/scheduled-post-test.js
+++ b/tools/scheduled-post-test.js
@@ -4,7 +4,7 @@
// post waits in a "Scheduled" list, where Cancel sends a kind 5 for it to
// /schedule/cancel. The notifier is stubbed.
// Run: bun tools/scheduled-post-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { verifyEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -47,8 +47,8 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
- await click('skip verification');
+ await seedWords(page);
+ await skipSeedCheck(page);
await waitText('receive', 20000);
await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]')?.click());
diff --git a/tools/sensitive-feed-test.js b/tools/sensitive-feed-test.js
index a050269..626da2c 100644
--- a/tools/sensitive-feed-test.js
+++ b/tools/sensitive-feed-test.js
@@ -4,7 +4,7 @@
// accounts whose own profile says they post adult work. Elsewhere a marked
// post is folded behind its warning with a Show.
// Run: bun tools/sensitive-feed-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { finalizeEvent, generateSecretKey, getPublicKey } from 'nostr-tools/pure';
import { buildHtml, buildVerifyWorker } from '../build.js';
@@ -74,9 +74,9 @@ try {
await sleep(400);
const click = (t) => page.evaluate((x) => { const e = [...document.querySelectorAll('button')].find((n) => n.textContent.trim().toLowerCase().includes(x)); if (e) { e.click(); return true; } return false; }, t);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await page.waitForFunction(() => /receive/i.test(document.body.innerText), { timeout: 20000 });
await sleep(3000); // the starter follows land first
const base = cacheKeyFor(mn + '\n');
diff --git a/tools/signer-resume-test.js b/tools/signer-resume-test.js
index c35b655..9ca3950 100644
--- a/tools/signer-resume-test.js
+++ b/tools/signer-resume-test.js
@@ -6,7 +6,7 @@
// keeps trying the stored session and closes itself once the signer answers.
// The remote signer is played by this script over a shimmed relay.
// Run: bun tools/signer-resume-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import * as nip44 from 'nostr-tools/nip44';
@@ -100,9 +100,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
const local = Array.from(CLIENT_SK, (b) => b.toString(16).padStart(2, '0')).join('');
diff --git a/tools/slow-image-test.js b/tools/slow-image-test.js
index 103f6da..43eff97 100644
--- a/tools/slow-image-test.js
+++ b/tools/slow-image-test.js
@@ -4,7 +4,7 @@
// inside a fixed box (nothing moves when it lands); a picture that refused to
// load is still a link.
// Run: bun tools/slow-image-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -46,9 +46,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/standalone-file-test.js b/tools/standalone-file-test.js
index 743bc7d..13445f6 100644
--- a/tools/standalone-file-test.js
+++ b/tools/standalone-file-test.js
@@ -3,7 +3,7 @@
// Run: bun run build && bun tools/standalone-file-test.js
import assert from 'node:assert/strict';
import { resolve } from 'node:path';
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: true, args: ['--no-sandbox'] });
try {
@@ -25,7 +25,7 @@ try {
await page.evaluate(() => { const s = [...document.querySelectorAll('select')].find((x) => [...x.options].some((o) => o.value === 'en')); s.value = 'en'; s.dispatchEvent(new Event('change', { bubbles: true })); });
await sleep(500);
await click('create a new wallet'); await sleep(1500);
- await click('skip verification'); await sleep(1500);
+ await skipSeedCheck(page); await sleep(1500);
// the avatar step comes after a name claim; resume the wizard there
await page.evaluate(() => localStorage.setItem('btc-wallet-onb-step', 'avatar'));
await page.reload(); await sleep(2500);
diff --git a/tools/thread-instant-test.js b/tools/thread-instant-test.js
index bd80408..e9a9138 100644
--- a/tools/thread-instant-test.js
+++ b/tools/thread-instant-test.js
@@ -3,7 +3,7 @@
// is seeded from them — the relays only add to it. A thread with nothing in
// hand paints its replies as they arrive, not after the slowest relay.
// Run: bun tools/thread-instant-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -95,9 +95,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, alice, bob, carol, notes]) => {
diff --git a/tools/translate-phone-lang-test.js b/tools/translate-phone-lang-test.js
index 9e1eefc..28676eb 100644
--- a/tools/translate-phone-lang-test.js
+++ b/tools/translate-phone-lang-test.js
@@ -6,7 +6,7 @@
// the target and takes a DeepL key, which then rides with each request.
// The registrar's /translate is stubbed (window.fetch shim).
// Run: bun tools/translate-phone-lang-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -57,9 +57,9 @@ try {
await page.evaluate(() => { localStorage.setItem('btc-wallet-network', 'regtest'); localStorage.setItem('btc-wallet-lang', 'en'); });
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/translate-test.js b/tools/translate-test.js
index d3fe095..f00ba22 100644
--- a/tools/translate-test.js
+++ b/tools/translate-test.js
@@ -3,7 +3,7 @@
// with the switch off they offer Translate instead; ⋯ → Translate asks for
// any post. The registrar's /translate is stubbed here (window.fetch shim).
// Run: bun tools/translate-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -70,9 +70,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/video-autoplay-test.js b/tools/video-autoplay-test.js
index 6b943f2..6d245b3 100644
--- a/tools/video-autoplay-test.js
+++ b/tools/video-autoplay-test.js
@@ -4,7 +4,7 @@
// loaded for a box that never comes into view. Set CLIP to a webm to serve;
// the default is made with ffmpeg on the fly.
// Run: bun tools/video-autoplay-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -53,9 +53,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/video-compress-test.js b/tools/video-compress-test.js
index 19735c8..2b83edc 100644
--- a/tools/video-compress-test.js
+++ b/tools/video-compress-test.js
@@ -4,7 +4,7 @@
// upload is caught and its bytes checked with ffprobe.
//
// Run: bun tools/video-compress-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let ok = true;
const check = (n, c, d = '') => { console.log(` ${c ? '✓' : '✗'} ${n}${d ? ' — ' + d : ''}`); if (!c) ok = false; };
@@ -73,8 +73,8 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(600);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
- await click('skip verification');
+ await seedWords(page);
+ await skipSeedCheck(page);
await waitText('receive', 20000);
check('the browser can encode H.264 (WebCodecs)', await page.evaluate(async () => (await VideoEncoder.isConfigSupported({ codec: 'avc1.42001f', width: 1280, height: 720, bitrate: 2.5e6 })).supported));
// the post composer: the pencil on the feed
diff --git a/tools/video-events-test.js b/tools/video-events-test.js
index c024949..56ea486 100644
--- a/tools/video-events-test.js
+++ b/tools/video-events-test.js
@@ -6,7 +6,7 @@
// version. A note linking such a file, typed video/* in its imeta, plays it
// too. And the clips are in the pager.
// Run: bun tools/video-events-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -90,9 +90,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk]) => {
diff --git a/tools/video-fallback-test.js b/tools/video-fallback-test.js
index 93726e1..ff45846 100644
--- a/tools/video-fallback-test.js
+++ b/tools/video-fallback-test.js
@@ -4,7 +4,7 @@
// post gave no poster wears the server's still. The server (vmedia) is
// stood in for here: its answers are canned.
// Run: bun tools/video-fallback-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -81,9 +81,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/video-gone-test.js b/tools/video-gone-test.js
index 95826b5..48f2956 100644
--- a/tools/video-gone-test.js
+++ b/tools/video-gone-test.js
@@ -6,7 +6,7 @@
// file's link, its controls and cover gone; the next paint of the post says
// it at once, with no request. A clip on a live host plays as before.
// Run: bun tools/video-gone-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -73,9 +73,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/video-hls-play-test.js b/tools/video-hls-play-test.js
index f1aea1c..4a18e76 100644
--- a/tools/video-hls-play-test.js
+++ b/tools/video-hls-play-test.js
@@ -4,7 +4,7 @@
// and a post linking an .m3u8 stream plays that. vmedia runs here for real,
// on a local copy of the clip.
// Run: bun tools/video-hls-play-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -90,9 +90,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/video-tap-test.js b/tools/video-tap-test.js
index 50e5a69..f03aef6 100644
--- a/tools/video-tap-test.js
+++ b/tools/video-tap-test.js
@@ -3,7 +3,7 @@
// rest). With a mouse the middle of the clip is a big play/pause button that
// never goes full screen.
// Run: bun tools/video-tap-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -49,9 +49,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/video-tile-poster-test.js b/tools/video-tile-poster-test.js
index 48385fe..ed672c2 100644
--- a/tools/video-tile-poster-test.js
+++ b/tools/video-tile-poster-test.js
@@ -4,7 +4,7 @@
// picture for falls back to the frame grab, and one the browser can't load
// either (deleted, its 404 without CORS headers) leaves the grid.
// Run: bun tools/video-tile-poster-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -75,9 +75,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/video-touch-tap-test.js b/tools/video-touch-tap-test.js
index 71ffe9c..ff25095 100644
--- a/tools/video-touch-tap-test.js
+++ b/tools/video-touch-tap-test.js
@@ -5,7 +5,7 @@
// on the control bar or the paused clip's play button stays the player's, a
// drag is a scroll, and where the click does come it isn't taken twice.
// Run: bun tools/video-touch-tap-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -51,9 +51,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {
diff --git a/tools/web-comment-test.js b/tools/web-comment-test.js
index 7a6258f..513217b 100644
--- a/tools/web-comment-test.js
+++ b/tools/web-comment-test.js
@@ -9,7 +9,7 @@
// tag beside it, counted as a report of the PERSON (es stayed 0 for an
// 'other' e tag) — two such reports from follows folded the author's every
// post. Run: bun tools/web-comment-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -70,9 +70,9 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pks, ns]) => {
diff --git a/tools/wn-only-device-test.js b/tools/wn-only-device-test.js
index a91262b..7b19876 100644
--- a/tools/wn-only-device-test.js
+++ b/tools/wn-only-device-test.js
@@ -6,7 +6,7 @@
// a fresh one of ours. A device whose key was retired by another sees the
// deletion and stops putting a key back; the card says so there.
// Run: bun tools/wn-only-device-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { privateKeyFromSeedWords } from 'nostr-tools/nip06';
@@ -76,9 +76,9 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
const SK = privateKeyFromSeedWords(mn), ME = getPublicKey(SK);
diff --git a/tools/youtube-tap-test.js b/tools/youtube-tap-test.js
index 271277f..e08058b 100644
--- a/tools/youtube-tap-test.js
+++ b/tools/youtube-tap-test.js
@@ -4,7 +4,7 @@
// over the picture takes them; YouTube's control bar along the bottom is
// left to YouTube, and full screen the layer steps aside.
// Run: bun tools/youtube-tap-test.js
-import puppeteer from './puppeteer.js';
+import puppeteer, { seedWords, skipSeedCheck } from './puppeteer.js';
import { cacheKeyFor } from '../src/wallet.js';
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { buildHtml } from '../build.js';
@@ -60,9 +60,9 @@ async function run(saveData) {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400);
await click('create a new wallet'); await sleep(600);
- await page.waitForSelector('.words .w .t');
+ await seedWords(page);
const mn = (await page.$$eval('.words .w .t', (l) => l.map((e) => e.textContent.trim()))).join(' ');
- await click('skip verification');
+ await skipSeedCheck(page);
await waitText('receive', 20000);
const base = cacheKeyFor(mn + '\n');
await page.evaluate(([k, pk, ns]) => {