aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/app.js119
-rw-r--r--src/i18n.js11
-rw-r--r--src/style.css9
-rw-r--r--tools/avatar-thumb-test.js2
-rw-r--r--tools/boot-balance-test.js2
-rw-r--r--tools/boot-signin-flash-test.js6
-rw-r--r--tools/chat-layout-test.js2
-rw-r--r--tools/feed-follow-test.js2
-rw-r--r--tools/lock-wallet-test.js8
-rw-r--r--tools/minimal-smoke.js2
-rw-r--r--tools/onboard-backup-test.js121
-rw-r--r--tools/padlock-ask-test.js2
-rw-r--r--tools/padlock-flow-test.js4
-rw-r--r--tools/post-actions-test.js2
-rw-r--r--tools/profile-batch-test.js2
-rw-r--r--tools/profile-draft-test.js2
-rw-r--r--tools/profile-feed-test.js2
-rw-r--r--tools/profile-open-test.js2
-rw-r--r--tools/push-unavailable-test.js2
-rw-r--r--tools/quote-card-test.js2
-rw-r--r--tools/ui-morph-test.js4
-rw-r--r--tools/unifiedpush-test.js2
-rw-r--r--tools/youtube-embed-test.js2
-rw-r--r--tools/zap-tally-cache-test.js2
24 files changed, 269 insertions, 45 deletions
diff --git a/src/app.js b/src/app.js
index 4914528..69fcf1a 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1280,6 +1280,12 @@ async function enterWallet(mnemonic, passphrase, opts = {}) {
mnemonic: (mnemonic || '').trim().replace(/\s+/g, ' '),
passphrase: passphrase || '',
});
+ // Has this person ever had the recovery phrase in front of them? Creating
+ // a wallet shows it; importing one means they typed it. A sign-in DERIVES
+ // it — Google, a passkey and a nostr key all end up at a seed the user has
+ // never seen, and which nobody can reissue for them. That is the one case
+ // worth asking about, so it's recorded once and never guessed at again.
+ if (acc.seedSeen === undefined) acc.seedSeen = !opts.nostrPubkey;
await activateAccount(acc, { ...opts, fresh: true });
}
@@ -2888,7 +2894,12 @@ function recoveryCard(a) {
a.passphrase ? copyBtn(a.passphrase, t('copyPassphrase')) : null,
h('button', { class: 'btn-sm grow', onClick: () => { ui.revealShown = false; render(); } }, t('hide')))
: h('div', { class: 'row gap6' },
- h('button', { class: 'btn-primary grow', onClick: () => { ui.revealShown = 'words'; render(); } }, t('revealWords')),
+ h('button', { class: 'btn-primary grow', onClick: () => {
+ ui.revealShown = 'words';
+ // seeing it here counts: no need to be asked again elsewhere
+ if (!a.seedSeen) { a.seedSeen = true; persistAccounts(); if (a.persisted) writeVault(); }
+ render();
+ } }, t('revealWords')),
h('button', { class: 'btn-sm', onClick: () => { ui.revealShown = false; render(); } }, t('hide')))
);
}
@@ -3080,14 +3091,23 @@ function onboardScreen() {
// the login finishes.
if ((o.step === 'welcome' || o.step === 'seed' || o.step === 'signin')
&& ui.screen === 'wallet' && activeAccount() && !ui.nostrLoginBusy) {
- ui.onb = null;
- try { localStorage.removeItem(ONB_STEP_KEY); } catch {}
- // On-chain first: the wizard's promise is "your keys, your coins", and
- // Spending stays out of sight until the user sets it up.
- ui.account = 'savings';
- try { localStorage.setItem(ACCOUNT_KEY, 'savings'); } catch {}
- ui.tab = 'history';
- return null; // the caller falls through to the wallet itself
+ // A wallet that arrived by SIGN-IN has a recovery phrase its owner has
+ // never seen. Show it once, here, while it still reads as part of
+ // setting up — rather than leaving it buried in settings until the day
+ // they lose the Google account and find out it was never a backup.
+ // Handled further down, where page()/title() exist.
+ const arrived = activeAccount();
+ if (arrived && arrived.mnemonic && !arrived.seedSeen) o.step = 'backup';
+ else {
+ ui.onb = null;
+ try { localStorage.removeItem(ONB_STEP_KEY); } catch {}
+ // On-chain first: the wizard's promise is "your keys, your coins", and
+ // Spending stays out of sight until the user sets it up.
+ ui.account = 'savings';
+ try { localStorage.setItem(ACCOUNT_KEY, 'savings'); } catch {}
+ ui.tab = 'history';
+ return null; // the caller falls through to the wallet itself
+ }
}
if (o.step === 'spend') {
const addr = featureHook('namesAddress');
@@ -3164,14 +3184,24 @@ function onboardScreen() {
return page([
h('div', { class: 'onb-hero' }, brandHeader(false)),
title(t('onbWelcomeTitle')),
- // Starting is the primary path; bringing a nostr account is the
- // alternative, offered here rather than as a question of its own.
- h('button', { class: 'btn-primary btn-block', style: 'font-size:17px;padding:14px', onClick: () => {
- ui.unlockTab = 'create';
- ui.unlockError = '';
- o.step = 'seed';
- render();
- } }, t('onbStart')),
+ // What this wallet IS, before the buttons. Four equally weighted
+ // buttons with only the sign-in ones saying what they do sent people
+ // straight past the seed phrase — which is the wallet — into a login
+ // that is only a convenience wrapped around it.
+ h('p', { class: 'muted', style: 'margin:0' }, t('onbWelcomeBody')),
+ // Making your own keys is the path; a sign-in is the alternative,
+ // below the line.
+ h('button', {
+ // a stable hook for tests, so this button survives its own copy
+ id: 'onb-create',
+ class: 'btn-primary btn-block', style: 'font-size:17px;padding:14px', onClick: () => {
+ ui.unlockTab = 'create';
+ ui.unlockError = '';
+ o.step = 'seed';
+ render();
+ } }, t('onbStart')),
+ h('p', { class: 'small muted', style: 'margin:-6px 0 0;text-align:center' }, t('onbStartHint')),
+ h('div', { class: 'onb-or' }, h('span', {}, t('onbOrSignIn'))),
// All the sign-in doors right on the welcome screen (Google, passkey,
// Nostr) — the feature supplies them; the single-button fallback keeps
// builds without it working.
@@ -3186,6 +3216,42 @@ function onboardScreen() {
ui.onbError ? h('div', { class: 'notice err' }, ui.onbError) : null,
]);
}
+ if (o.step === 'backup') {
+ const acc = activeAccount();
+ // the wallet went away (switched, logged out) — nothing to back up
+ if (!acc || !acc.mnemonic) { ui.onb = null; try { localStorage.removeItem(ONB_STEP_KEY); } catch {} return null; }
+ const words = acc.mnemonic.split(' ');
+ const done = () => {
+ acc.seedSeen = true;
+ persistAccounts();
+ if (acc.persisted) writeVault();
+ ui.onb = null;
+ try { localStorage.removeItem(ONB_STEP_KEY); } catch {}
+ ui.account = 'savings';
+ try { localStorage.setItem(ACCOUNT_KEY, 'savings'); } catch {}
+ ui.tab = 'history';
+ render();
+ };
+ return page([
+ title(t('onbBackupTitle')),
+ h('p', { class: 'muted', style: 'margin:0' }, t('onbBackupBody')),
+ h('div', { class: 'warn-box' }, t('writeDownWarn')),
+ h('div', { class: 'words' }, words.map((w, i) =>
+ h('div', { class: 'w' }, h('span', { class: 'n' }, i + 1), h('span', { class: 't' }, w)))),
+ h('div', { class: 'row gap6' }, copyBtn(acc.mnemonic, t('copyPhrase'))),
+ h('button', { class: 'btn-primary btn-block', style: 'padding:14px', onClick: done }, t('onbBackupDone')),
+ // Later means later, not never: seedSeen stays false, so the wallet
+ // keeps a quiet reminder until the phrase has actually been seen.
+ h('button', { class: 'btn-ghost btn-block', onClick: () => {
+ ui.onb = null;
+ try { localStorage.removeItem(ONB_STEP_KEY); } catch {}
+ ui.account = 'savings';
+ try { localStorage.setItem(ACCOUNT_KEY, 'savings'); } catch {}
+ ui.tab = 'history';
+ render();
+ } }, t('onbBackupLater')),
+ ]);
+ }
if (o.step === 'seed') {
// The classic create/import card, inside the wizard: getting started
// means seeing (or bringing) your seed phrase, not a wallet minted
@@ -3439,12 +3505,31 @@ function walletScreen() {
brandHeader(true),
h('div', { class: 'mt16' }, balanceCard()),
ui.offlineFallback && wallet.offline ? offlineBanner() : null,
+ backupNotice(),
...featureAll('walletNotices'),
tabsBar(),
pane
);
}
+// A wallet whose recovery phrase has never been on screen keeps one quiet
+// line about it. "Later" is a real answer, so this is a row and not a modal —
+// but it doesn't dismiss, because the risk doesn't go away on its own. It
+// disappears the moment the phrase is actually seen (here or in settings).
+function backupNotice() {
+ const a = activeAccount();
+ if (!a || !a.mnemonic || a.seedSeen || wallet.watchOnly) return null;
+ return h('div', { class: 'small muted', style: 'margin:10px 0 0;text-align:center' },
+ t('backupReminder'),
+ ' ',
+ h('button', { class: 'linklike small', onClick: () => {
+ ui.tab = 'settings';
+ ui.settingsPage = 'wallet';
+ ui.revealShown = 'masked';
+ render();
+ } }, t('backupReminderGo')));
+}
+
function offlineBanner() {
return h(
'div',
diff --git a/src/i18n.js b/src/i18n.js
index 3faa843..6a46823 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -716,7 +716,16 @@ const STR = {
or: 'or',
nlSignIn: 'Sign in',
onbWelcomeTitle: 'Welcome to coinos',
- onbStart: 'Get started',
+ onbWelcomeBody: 'Bitcoin you actually hold. Your wallet is twelve words — nobody else ever has them, and they work in any wallet, anywhere.',
+ onbStart: 'Create a new wallet',
+ onbStartHint: "Takes a minute: write down twelve words and it's yours",
+ onbOrSignIn: 'or, if you already have an account',
+ onbBackupTitle: 'Write down your recovery phrase',
+ onbBackupBody: "Signing in was the easy part — but the sign-in isn't your wallet. These twelve words are. They're the only way back in if you lose access to that account, and nobody can reissue them for you.",
+ onbBackupDone: "I've written it down",
+ onbBackupLater: 'Later',
+ backupReminder: 'Your recovery phrase is still unwritten.',
+ backupReminderGo: 'Write it down',
onbSeedTitle: 'Set up your wallet',
onbSigninTitle: 'Sign in',
onbLegacyTitle: 'Move your coinos.io account',
diff --git a/src/style.css b/src/style.css
index a16101c..c9b4368 100644
--- a/src/style.css
+++ b/src/style.css
@@ -865,6 +865,15 @@ body::after {
column to the 420px cap even on narrower phones, overflowing the page. */
.onb { width: 100%; max-width: 420px; margin: 8vh auto 0; padding: 0 8px; }
.onb-title { margin: 0; font-size: 26px; letter-spacing: -0.02em; }
+/* The line between making your own keys and signing in with someone else's
+ account. Without it the four buttons read as four equal choices. */
+.onb-or {
+ display: flex; align-items: center; gap: 12px; margin: 4px 0 -4px;
+ color: var(--faint); font-size: 13px;
+}
+.onb-or::before, .onb-or::after {
+ content: ''; flex: 1; height: 1px; background: var(--line);
+}
.onb-hero { margin-bottom: 8px; }
.onb-punks { display: flex; gap: 10px; overflow-x: auto; padding: 6px 2px; }
.onb-punk { width: 56px; height: 56px; border-radius: 50%; flex-shrink: 0; cursor: pointer; border: 3px solid transparent; transition: transform .15s, border-color .15s; }
diff --git a/tools/avatar-thumb-test.js b/tools/avatar-thumb-test.js
index 727c49a..c776179 100644
--- a/tools/avatar-thumb-test.js
+++ b/tools/avatar-thumb-test.js
@@ -73,7 +73,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('get started'); await sleep(500);
+ await click('create a new wallet'); await sleep(500);
await click('import existing'); await sleep(400);
await page.waitForSelector('textarea');
const mnemonic = generateMnemonic(wordlist);
diff --git a/tools/boot-balance-test.js b/tools/boot-balance-test.js
index 96f47a9..c34712b 100644
--- a/tools/boot-balance-test.js
+++ b/tools/boot-balance-test.js
@@ -28,7 +28,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('get started'); await sleep(500);
+ await click('create a new wallet'); await sleep(500);
await click('import existing'); await sleep(400);
await page.waitForSelector('textarea');
await page.type('textarea', generateMnemonic(wordlist));
diff --git a/tools/boot-signin-flash-test.js b/tools/boot-signin-flash-test.js
index e46ee72..b29bc63 100644
--- a/tools/boot-signin-flash-test.js
+++ b/tools/boot-signin-flash-test.js
@@ -1,7 +1,7 @@
// A signed-in wallet must never flash the signed-out welcome screen. Anything
// that renders before boot has restored the session (a deferred feature chunk
// landing, an emitter tick) is rendering without knowing whether anyone is
-// signed in, and "Get started" over a wallet that's about to open is the one
+// signed in, and the start page over a wallet that's about to open is the one
// wrong guess that's unmistakable.
//
// The race is widened here (the locale fetch is delayed, so the deferred
@@ -27,7 +27,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('get started'); await sleep(500);
+ await click('create a new wallet'); await sleep(500);
await click('import existing'); await sleep(400);
await page.waitForSelector('textarea');
await page.type('textarea', generateMnemonic(wordlist));
@@ -48,7 +48,7 @@ try {
window.__sawSignin = null;
const tick = () => {
const txt = (document.body && document.body.innerText) || '';
- if (!window.__sawSignin && /get started|sign in with/i.test(txt)) window.__sawSignin = Math.round(performance.now());
+ if (!window.__sawSignin && /create a new wallet|sign in with/i.test(txt)) window.__sawSignin = Math.round(performance.now());
if (!window.__walletAt && document.querySelector('.balance')) window.__walletAt = Math.round(performance.now());
if (!window.__walletAt) requestAnimationFrame(tick);
};
diff --git a/tools/chat-layout-test.js b/tools/chat-layout-test.js
index 853aafa..772e38f 100644
--- a/tools/chat-layout-test.js
+++ b/tools/chat-layout-test.js
@@ -43,7 +43,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('button', 'Get started');
+ await click('button', 'Create a new wallet');
await sleep(300);
await click('button', 'Import existing');
await sleep(300);
diff --git a/tools/feed-follow-test.js b/tools/feed-follow-test.js
index 4c1fed3..ae486a6 100644
--- a/tools/feed-follow-test.js
+++ b/tools/feed-follow-test.js
@@ -31,7 +31,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('get started'); await sleep(500);
+ await click('create a new wallet'); await sleep(500);
await click('import existing'); await sleep(400);
await page.waitForSelector('textarea');
const mnemonic = generateMnemonic(wordlist);
diff --git a/tools/lock-wallet-test.js b/tools/lock-wallet-test.js
index 8db392a..494195e 100644
--- a/tools/lock-wallet-test.js
+++ b/tools/lock-wallet-test.js
@@ -44,7 +44,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('button', 'Get started');
+ await click('button', 'Create a new wallet');
await sleep(500);
await click('button', 'Import existing');
await sleep(400);
@@ -76,7 +76,7 @@ try {
await logout();
txt = await body();
check('it does not ask a second time', !/protect this device/i.test(txt), txt.slice(0, 120).replace(/\n+/g, ' | '));
- check('logout means the front door', /create new|get started|sign in/i.test(txt), txt.slice(0, 120).replace(/\n+/g, ' | '));
+ check('logout means the front door', /create a new wallet|sign in/i.test(txt), txt.slice(0, 120).replace(/\n+/g, ' | '));
await page.reload({ waitUntil: 'domcontentloaded' });
check('and a saved passwordless wallet is one reload away', await waitText('receive'));
await sleep(1000);
@@ -109,7 +109,7 @@ try {
check('back in the wallet', await waitText('receive'));
await logout();
txt = await body();
- check('logout is still the front door', /create new|get started|sign in/i.test(txt), txt.slice(0, 120).replace(/\n+/g, ' | '));
+ check('logout is still the front door', /create a new wallet|sign in/i.test(txt), txt.slice(0, 120).replace(/\n+/g, ' | '));
check('the wallet is still saved', (await vaultSize()) > 0);
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(800);
@@ -148,7 +148,7 @@ try {
await sleep(1200);
txt = await body();
check('everything is gone', (await vaultSize()) === 0);
- check('back at the start page', /get started/i.test(txt), txt.slice(0, 120).replace(/\n+/g, ' | '));
+ check('back at the start page', /create a new wallet/i.test(txt), txt.slice(0, 120).replace(/\n+/g, ' | '));
} catch (e) {
check('run completed', false, e.message);
} finally {
diff --git a/tools/minimal-smoke.js b/tools/minimal-smoke.js
index f3f896c..2bdc4a6 100644
--- a/tools/minimal-smoke.js
+++ b/tools/minimal-smoke.js
@@ -65,7 +65,7 @@ try {
await sleep(400);
console.log('\n[1] import + core wallet');
- await clickText('button', 'Get started'); // the front door gained a step
+ await clickText('button', 'Create a new wallet'); // the front door gained a step
await sleep(300);
await clickText('button', 'Import existing');
await sleep(200);
diff --git a/tools/onboard-backup-test.js b/tools/onboard-backup-test.js
new file mode 100644
index 0000000..3b242dd
--- /dev/null
+++ b/tools/onboard-backup-test.js
@@ -0,0 +1,121 @@
+// Two things about the front door.
+//
+// One: making your own keys has to look like the way in. Four equally
+// weighted buttons, where only the sign-in ones said what they did, sent
+// people straight past the seed phrase — which IS the wallet — into a login
+// that is only a convenience wrapped around one.
+//
+// Two: a wallet that arrived by SIGN-IN has a recovery phrase its owner has
+// never seen, and nobody can reissue it. So it gets shown once, during
+// setup, and if they put it off the wallet keeps one quiet line about it.
+//
+// Run: bun tools/onboard-backup-test.js
+import puppeteer from 'puppeteer-core';
+import { bech32 } from '@scure/base';
+import { generateSecretKey } from 'nostr-tools/pure';
+import { seedFromNostrKey } from '../src/nostr-login.js';
+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: 5291, 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 click = (page, t, sel = 'button') => page.evaluate((s, x) => {
+ const e = [...document.querySelectorAll(s)].find((n) => n.textContent.trim().toLowerCase().includes(x));
+ if (e) { e.click(); return true; } return false;
+}, sel, t.toLowerCase());
+const text = (page) => page.evaluate(() => document.body.innerText);
+const waitText = async (page, x, ms = 30000) => {
+ for (let i = 0; i < ms / 250; i++) { if ((await text(page)).toLowerCase().includes(x.toLowerCase())) return true; await sleep(250); }
+ return false;
+};
+
+// Sign in with a pasted nsec and stop at whatever the wizard shows next.
+// Its own browser context, so each run meets a genuinely fresh front door
+// rather than the wallet the previous one left in localStorage.
+async function signIn() {
+ const ctx = await browser.createBrowserContext();
+ const page = await ctx.newPage();
+ await page.setViewport({ width: 390, height: 844 });
+ await page.goto('http://localhost:5291/', { waitUntil: 'domcontentloaded' });
+ await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
+ await page.reload({ waitUntil: 'domcontentloaded' });
+ // wait for the front door to actually be painted before poking it
+ await waitText(page, 'sign in with nostr');
+ const sk = generateSecretKey();
+ const nsec = bech32.encode('nsec', bech32.toWords(sk), 1000);
+ await click(page, 'sign in with nostr');
+ await waitText(page, 'log in with nostr');
+ await page.waitForSelector('input[placeholder*="nsec" i], textarea[placeholder*="nsec" i]', { timeout: 15000 });
+ await page.type('input[placeholder*="nsec" i], textarea[placeholder*="nsec" i]', nsec);
+ await click(page, 'log in');
+ return { page, ctx, seed: seedFromNostrKey(sk) };
+}
+
+try {
+ console.log('[the start page]');
+ {
+ const page = await browser.newPage();
+ await page.setViewport({ width: 390, height: 844 });
+ await page.goto('http://localhost:5291/', { waitUntil: 'domcontentloaded' });
+ await sleep(1500);
+ const front = await page.evaluate(() => {
+ const btns = [...document.querySelectorAll('button')].filter((b) => b.offsetParent);
+ const primary = document.getElementById('onb-create');
+ const idx = btns.indexOf(primary);
+ const signins = btns.filter((b) => /sign in with/i.test(b.textContent));
+ return {
+ label: primary ? primary.textContent.trim() : null,
+ first: idx === 0 || (idx >= 0 && idx < btns.findIndex((b) => /sign in with/i.test(b.textContent))),
+ primaryTop: primary ? primary.getBoundingClientRect().top : -1,
+ signinTop: signins.length ? signins[0].getBoundingClientRect().top : -1,
+ divider: !!document.querySelector('.onb-or'),
+ body: (document.querySelector('.onb p') || {}).textContent || '',
+ };
+ });
+ check('the way in says what it makes', /wallet/i.test(front.label || ''), front.label || 'missing');
+ check('...and comes before the sign-in buttons', front.first && front.primaryTop < front.signinTop,
+ `create at ${Math.round(front.primaryTop)}, first sign-in at ${Math.round(front.signinTop)}`);
+ check('...with a line between them, so they read as the alternative', front.divider);
+ check('the page says what the wallet actually is', /twelve words|seed|phrase/i.test(front.body),
+ front.body.slice(0, 60) + '…');
+ await page.close();
+ }
+
+ console.log('\n[signing in, then writing it down]');
+ {
+ const { page, ctx, seed } = await signIn();
+ check('a sign-in is asked to write the phrase down', await waitText(page, 'write down your recovery phrase'));
+ const shown = await page.evaluate(() => [...document.querySelectorAll('.words .w .t')].map((e) => e.textContent.trim()));
+ check('...and the real twelve words are on screen', shown.length === 12 && shown.join(' ') === seed,
+ shown.length + ' words' + (shown.join(' ') === seed ? ', matching the derived seed' : ', WRONG'));
+ await click(page, "i've written it down");
+ check('confirming lands in the wallet', await waitText(page, 'receive'));
+ const nag = await text(page);
+ check('...and there is no reminder afterwards', !/still unwritten/i.test(nag));
+ await page.reload({ waitUntil: 'domcontentloaded' });
+ check('...not after a reload either', await waitText(page, 'receive') && !/still unwritten/i.test(await text(page)));
+ await ctx.close();
+ }
+
+ console.log('\n[signing in, then putting it off]');
+ {
+ const { page, ctx } = await signIn();
+ check('the ask appears', await waitText(page, 'write down your recovery phrase'));
+ await click(page, 'later');
+ check('later lands in the wallet too', await waitText(page, 'receive'));
+ check('...but the wallet keeps a quiet reminder', /still unwritten/i.test(await text(page)));
+ await page.reload({ waitUntil: 'domcontentloaded' });
+ await waitText(page, 'receive');
+ check('...which survives a reload', /still unwritten/i.test(await text(page)));
+ // and it is a way IN, not just a scold
+ await click(page, 'write it down');
+ check('tapping it opens the recovery phrase', await waitText(page, 'recovery phrase'));
+ await ctx.close();
+ }
+} finally { await browser.close(); server.stop(true); }
+console.log(ok ? '\n✅ the front door points at your own keys' : '\n❌ failed');
+process.exit(ok ? 0 : 1);
diff --git a/tools/padlock-ask-test.js b/tools/padlock-ask-test.js
index 8007aad..eb35047 100644
--- a/tools/padlock-ask-test.js
+++ b/tools/padlock-ask-test.js
@@ -26,7 +26,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('button', 'get started');
+ await click('button', 'create a new wallet');
await sleep(500);
await click('button', 'Import existing');
await sleep(400);
diff --git a/tools/padlock-flow-test.js b/tools/padlock-flow-test.js
index 4b659aa..be4f5ec 100644
--- a/tools/padlock-flow-test.js
+++ b/tools/padlock-flow-test.js
@@ -29,7 +29,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('button', 'Get started');
+ await click('button', 'Create a new wallet');
await sleep(500);
await click('button', 'Import existing');
await sleep(400);
@@ -117,7 +117,7 @@ try {
await p2.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await p2.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click2('Get started');
+ await click2('Create a new wallet');
await sleep(500);
check('wallet A open', await importWallet());
await sleep(2000);
diff --git a/tools/post-actions-test.js b/tools/post-actions-test.js
index 8cc525c..06d4739 100644
--- a/tools/post-actions-test.js
+++ b/tools/post-actions-test.js
@@ -38,7 +38,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('get started'); await sleep(500);
+ await click('create a new wallet'); await sleep(500);
await click('import existing'); await sleep(400);
await page.waitForSelector('textarea');
const mn = generateMnemonic(wordlist);
diff --git a/tools/profile-batch-test.js b/tools/profile-batch-test.js
index 335bfbc..bf8dcf4 100644
--- a/tools/profile-batch-test.js
+++ b/tools/profile-batch-test.js
@@ -68,7 +68,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('get started'); await sleep(500);
+ await click('create a new wallet'); await sleep(500);
await click('import existing'); await sleep(400);
await page.waitForSelector('textarea');
const mn = generateMnemonic(wordlist);
diff --git a/tools/profile-draft-test.js b/tools/profile-draft-test.js
index 243f8ec..cb016da 100644
--- a/tools/profile-draft-test.js
+++ b/tools/profile-draft-test.js
@@ -41,7 +41,7 @@ try {
await page.goto(origin);
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
- await click('Get started');
+ await click('Create a new wallet');
await click('Import existing');
await page.waitForSelector('textarea');
const mnemonic = generateMnemonic(wordlist);
diff --git a/tools/profile-feed-test.js b/tools/profile-feed-test.js
index 1e7283b..d285c44 100644
--- a/tools/profile-feed-test.js
+++ b/tools/profile-feed-test.js
@@ -25,7 +25,7 @@ const waitText = async (x, ms = 20000) => { for (let i = 0; i < ms / 250; i++) {
try {
await page.goto('http://localhost:5234/', { waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('button', 'Get started');
+ await click('button', 'Create a new wallet');
await sleep(300);
await click('button', 'Import existing');
await sleep(300);
diff --git a/tools/profile-open-test.js b/tools/profile-open-test.js
index 7c88dee..da90af5 100644
--- a/tools/profile-open-test.js
+++ b/tools/profile-open-test.js
@@ -62,7 +62,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('get started'); await sleep(500);
+ await click('create a new wallet'); await sleep(500);
await click('import existing'); await sleep(400);
await page.waitForSelector('textarea');
const mn = generateMnemonic(wordlist);
diff --git a/tools/push-unavailable-test.js b/tools/push-unavailable-test.js
index b285aee..4808501 100644
--- a/tools/push-unavailable-test.js
+++ b/tools/push-unavailable-test.js
@@ -38,7 +38,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('get started'); await sleep(500);
+ await click('create a new wallet'); await sleep(500);
await click('import existing'); await sleep(400);
await page.waitForSelector('textarea');
const mn = generateMnemonic(wordlist);
diff --git a/tools/quote-card-test.js b/tools/quote-card-test.js
index 5225f4a..695ee93 100644
--- a/tools/quote-card-test.js
+++ b/tools/quote-card-test.js
@@ -38,7 +38,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('get started'); await sleep(500);
+ await click('create a new wallet'); await sleep(500);
await click('import existing'); await sleep(400);
await page.waitForSelector('textarea');
const mn = generateMnemonic(wordlist);
diff --git a/tools/ui-morph-test.js b/tools/ui-morph-test.js
index 858a5a7..343ccd2 100644
--- a/tools/ui-morph-test.js
+++ b/tools/ui-morph-test.js
@@ -29,12 +29,12 @@ check('app booted to a screen with buttons', hasButton);
// walk into a wallet if the entry button exists
const clicked = await page.evaluate(() => {
- const btn = [...document.querySelectorAll('button')].find((b) => /get started/i.test(b.textContent));
+ const btn = [...document.querySelectorAll('button')].find((b) => /create a new wallet/i.test(b.textContent));
if (btn) { btn.click(); return true; }
return false;
});
await sleep(4000);
-check('entered next screen', await page.evaluate(() => document.body.textContent.length > 100), clicked ? '' : '(no Get started button — already in wallet)');
+check('entered next screen', await page.evaluate(() => document.body.textContent.length > 100), clicked ? '' : '(no start button — already in wallet)');
// node identity across background renders: tag a button, force renders, compare
const identity = await page.evaluate(async () => {
diff --git a/tools/unifiedpush-test.js b/tools/unifiedpush-test.js
index 022ead6..6a3819c 100644
--- a/tools/unifiedpush-test.js
+++ b/tools/unifiedpush-test.js
@@ -55,7 +55,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('get started'); await sleep(500);
+ await click('create a new wallet'); await sleep(500);
await click('import existing'); await sleep(400);
await page.waitForSelector('textarea');
const mn = generateMnemonic(wordlist);
diff --git a/tools/youtube-embed-test.js b/tools/youtube-embed-test.js
index f9bbf0d..a70272c 100644
--- a/tools/youtube-embed-test.js
+++ b/tools/youtube-embed-test.js
@@ -22,7 +22,7 @@ await page.goto('http://localhost:5267/', { waitUntil: 'domcontentloaded' });
await page.evaluate(() => { localStorage.setItem('btc-wallet-network', 'regtest'); localStorage.setItem('btc-wallet-theme', 'dark'); });
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
-await click('get started'); await sleep(500);
+await click('create a new wallet'); await sleep(500);
await click('import existing'); await sleep(400);
await page.waitForSelector('textarea');
const mn = generateMnemonic(wordlist);
diff --git a/tools/zap-tally-cache-test.js b/tools/zap-tally-cache-test.js
index 98cfe7e..4e91260 100644
--- a/tools/zap-tally-cache-test.js
+++ b/tools/zap-tally-cache-test.js
@@ -24,7 +24,7 @@ try {
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(400);
- await click('get started'); await sleep(500);
+ await click('create a new wallet'); await sleep(500);
await click('import existing'); await sleep(400);
await page.waitForSelector('textarea');
const mnemonic = generateMnemonic(wordlist);