diff options
| author | Adam Soltys <[email protected]> | 2026-08-17 07:03:28 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-08-17 07:03:28 -0700 |
| commit | 49ae68290099bc37bbbf6d7ad4dee9f906623a38 (patch) | |
| tree | 01a84328f75caeaa5cd1801e4f49bb7839d1d0f9 | |
| parent | ede4516e2255bb65d22dd1a74d0e8332b0417194 (diff) | |
| download | coinosv3-49ae68290099bc37bbbf6d7ad4dee9f906623a38.tar.gz coinosv3-49ae68290099bc37bbbf6d7ad4dee9f906623a38.zip | |
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
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JtaatFfAbx91Nh3xqKVqKz
| -rw-r--r-- | src/app.js | 12 | ||||
| -rw-r--r-- | src/features/messages.js | 12 | ||||
| -rw-r--r-- | src/i18n.js | 1 | ||||
| -rw-r--r-- | tools/lock-wallet-test.js | 13 |
4 files changed, 36 insertions, 2 deletions
@@ -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: '<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>' }), 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); |
