From 49ae68290099bc37bbbf6d7ad4dee9f906623a38 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 17 Aug 2026 07:03:28 -0700 Subject: The profile grows a second, honest exit: Log out leaves the room, Log out & forget this device burns it down — behind the Delete-all warning, which now also sweeps the cached balances and history it always implied MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz --- src/app.js | 12 ++++++++++++ src/features/messages.js | 12 ++++++++++-- src/i18n.js | 1 + tools/lock-wallet-test.js | 13 +++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/app.js b/src/app.js index 3a9a8d2..0e36cba 100644 --- a/src/app.js +++ b/src/app.js @@ -2497,6 +2497,10 @@ function accountSettingsScreen() { // Wipe every wallet from this device: session accounts, the encrypted vault, // and saved watch-only accounts. Unbacked-up seeds are unrecoverable after this. function clearAll() { + // Forget means forget: cached balances, history and feature state go with + // the seeds (best effort — a still-locked vault's mnemonic-keyed caches + // can't be found without its password; the xpub-keyed ones still match). + for (const a of [...accounts, ...loadWatchAccounts()]) wipeAccountCache(a); try { localStorage.removeItem(VAULT_KEY); } catch {} try { localStorage.removeItem(WATCH_KEY); } catch {} ui.confirmClear = false; @@ -4167,6 +4171,14 @@ const ctx = { // start page (Get started / sign in). Saved wallets stay one Unlock away. if (!ui.pw) { ui.screen = 'unlock'; ui.unlockTab = 'create'; ui.onb = { step: 'welcome' }; render(); } }, + // The drastic exit: leave AND remove everything saved on this device. + // Routes through the Delete-all warning — never a single tap. + logoutForget: () => { + ui.profilePk = null; ui.profEdit = null; ui.profEditFilled = false; ui.chatOpen = false; + ui.screen = 'accounts'; + ui.confirmClear = true; + render(); + }, // the identity the header avatar currently wears (login > seed key > // remembered) — profile "mine"-ness must match what the user clicked shownPubkey: () => (featureHook('nostrLoginIdentity') || {}).pubkey diff --git a/src/features/messages.js b/src/features/messages.js index db4ad93..f7fc30a 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -1487,6 +1487,13 @@ export function messagesFeature(ctx) { }, h('span', { style: 'display:flex', html: '' }), t('logout')); + // Two clearly-named exits: leave (wallets stay saved, one unlock away), + // or leave and take everything with you — the latter behind the + // Delete-all warning, never a single tap. + const forgetBtn = () => ctx.logoutForget ? h('button', { + class: 'btn-ghost btn-block small', style: 'color:var(--muted)', + onClick: () => ctx.logoutForget(), + }, t('logoutForget')) : null; const full = fullProfiles.get(pk); // Your own profile IS the editor — and it renders IMMEDIATELY, seeded // from the local name/picture cache, so the page never reflows when the @@ -1569,9 +1576,10 @@ export function messagesFeature(ctx) { h('button', { class: 'btn-primary btn-block', disabled: ui.profSaving, onClick: saveProfile }, ui.profSaving ? h('span', { class: 'spinner sm' }) : t('save')), hook('hatShopEntry'), - logoutBtn()) + logoutBtn(), + forgetBtn()) : mine - ? logoutBtn() // the editor renders above once the kind 0 loads + ? h('div', { class: 'col', style: 'gap:8px' }, logoutBtn(), forgetBtn()) // the editor renders above once the kind 0 loads : h('div', { class: 'row gap6 wrap' }, h('button', { class: 'btn-primary grow', onClick: () => { const peer = pk; diff --git a/src/i18n.js b/src/i18n.js index e2416af..1dabde4 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -39,6 +39,7 @@ const STR = { accounts: 'Wallets', addWallet: 'Add wallet', clearAll: 'Delete all', + logoutForget: 'Log out & forget this device', clearAllWarn: '⚠ This removes every wallet from this device. Any wallet whose recovery phrase or key you have not backed up will be lost for good.', removeWalletTitle: 'Remove wallet', removeWalletWarn: '⚠ Remove “{name}” from this device? It will be gone from this browser — make sure you have its recovery phrase. Other wallets aren’t affected.', diff --git a/tools/lock-wallet-test.js b/tools/lock-wallet-test.js index 30f5982..a798d09 100644 --- a/tools/lock-wallet-test.js +++ b/tools/lock-wallet-test.js @@ -119,6 +119,19 @@ try { check('the password opens it', await waitText('receive')); await sleep(1000); + console.log('\n[log out & forget is the other exit]'); + await page.evaluate(() => document.querySelector('.header-avatar')?.click()); + await sleep(800); + txt = await body(); + check('the profile offers the drastic exit too', /forget this device/i.test(txt), txt.slice(0, 160).replace(/\n+/g, ' | ')); + await click('button', 'forget this device'); + await sleep(600); + txt = await body(); + check('it routes through the Delete-all warning', /delete all/i.test(txt) && /lost for good/i.test(txt), txt.slice(0, 160).replace(/\n+/g, ' | ')); + await click('button', 'Back'); + await sleep(600); + check('backing out deletes nothing', (await vaultSize()) > 0); + console.log('\n[delete all is the destructive one]'); await click('button', 'Wallets'); await sleep(800); -- cgit v1.2.3