aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-10 22:08:26 -0700
committerAdam Soltys <[email protected]>2026-09-10 22:08:26 -0700
commitf0a5e0de3e68eee184b25ced2393329aec169f6d (patch)
treeb0d0450d93715a728f56f8fea3f6e533fc61f5d0 /tools
parentdb5218ec83c1d84da5a2062e514b65132bcfe240 (diff)
downloadcoinosv3-f0a5e0de3e68eee184b25ced2393329aec169f6d.tar.gz
coinosv3-f0a5e0de3e68eee184b25ced2393329aec169f6d.zip
The padlock answers where you tap it, including on your profile page.
Feature screens (a profile, a chat thread, a search) are drawn ahead of the wallet screen — and the padlock's "Protect this device?" card was drawn BY the wallet screen. So tapping the padlock from a profile did nothing you could see: the card was waiting behind the page, and only appeared once you navigated back to the wallet. The ask now outranks feature screens (with onboarding still ahead of it), and declining leaves you exactly where you tapped, because nothing else moves. A lock that goes through does move: it drops the profile/chat/search the padlock was tapped from, so you land on the wallet wearing a closed padlock instead of reading a profile page with only a toast to say what happened. Logging out drops it too — a page belonging to the locked session had no business outliving it, or greeting whoever unlocks next. tools/padlock-ask-test.js covers the tap and the decline. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
Diffstat (limited to 'tools')
-rw-r--r--tools/padlock-ask-test.js63
1 files changed, 63 insertions, 0 deletions
diff --git a/tools/padlock-ask-test.js b/tools/padlock-ask-test.js
new file mode 100644
index 0000000..8007aad
--- /dev/null
+++ b/tools/padlock-ask-test.js
@@ -0,0 +1,63 @@
+// The padlock is answerable from wherever it's tapped. Feature screens (your
+// profile, a chat thread) are drawn ahead of the wallet screen, and the
+// "Protect this device?" card is drawn BY the wallet screen — so the tap used
+// to do nothing visible until you navigated home and the card finally
+// appeared there. The ask outranks feature screens now, and declining still
+// leaves you exactly where you tapped.
+//
+// Run: bun tools/padlock-ask-test.js
+import puppeteer from 'puppeteer-core';
+import { generateMnemonic } from '@scure/bip39';
+import { wordlist } from '@scure/bip39/wordlists/english';
+import { buildHtml } from '../build.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; };
+const html = await buildHtml({ minify: true, pwa: false });
+const server = Bun.serve({ port: 5231, fetch: () => new Response(html, { headers: { 'content-type': 'text/html' } }) });
+const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] });
+const page = await browser.newPage();
+const click = (sel, t) => page.evaluate((s, x) => { const e = [...document.querySelectorAll(s)].find((n) => n.textContent.trim().toLowerCase().includes(x.toLowerCase())); if (e) { e.click(); return true; } return false; }, sel, t);
+const bodyText = () => page.evaluate(() => document.body.innerText);
+const waitText = async (x, ms = 25000) => { for (let i = 0; i < ms / 250; i++) { if ((await bodyText()).toLowerCase().includes(x.toLowerCase())) return true; await sleep(250); } return false; };
+try {
+ await page.setViewport({ width: 390, height: 844 });
+ await page.goto('http://localhost:5231/', { waitUntil: 'domcontentloaded' });
+ await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
+ await page.reload({ waitUntil: 'domcontentloaded' });
+ await sleep(400);
+ await click('button', 'get started');
+ await sleep(500);
+ await click('button', 'Import existing');
+ await sleep(400);
+ await page.waitForSelector('textarea');
+ await page.type('textarea', generateMnemonic(wordlist));
+ await click('button', 'Open wallet');
+ await waitText('receive', 20000);
+
+ // open my own profile (the header avatar)
+ await page.evaluate(() => document.querySelector('.header-avatar')?.click());
+ await sleep(900);
+ const onProfile = await page.evaluate(() => !!document.querySelector('.profile-avatar, .chat-avatar.profile-avatar'));
+ check('profile page is open', onProfile, (await bodyText()).slice(0, 60).replace(/\n/g, ' | '));
+
+ // tap the padlock — the ask must appear without navigating anywhere
+ const tapped = await page.evaluate(() => {
+ const b = [...document.querySelectorAll('button')].find((e) => /lock/i.test(e.getAttribute('aria-label') || ''));
+ if (!b) return false; b.click(); return true;
+ });
+ check('padlock is reachable from the profile page', tapped);
+ await sleep(500);
+ const text = await bodyText();
+ check('the ask appears on the tap', /protect this device/i.test(text), text.split('\n').slice(0, 3).join(' | '));
+
+ // declining lands back on the profile, not somewhere else
+ await click('button', 'not now');
+ await sleep(600);
+ const back = await page.evaluate(() => !!document.querySelector('.profile-avatar'));
+ check('declining lands back on the profile page', back, (await bodyText()).slice(0, 60).replace(/\n/g, ' | '));
+} finally {
+ await browser.close(); server.stop(true);
+}
+console.log(ok ? '\n✅ padlock ask is immediate' : '\n❌ failed');
+process.exit(ok ? 0 : 1);