diff options
| author | Adam Soltys <[email protected]> | 2026-08-15 17:42:10 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-08-15 17:42:10 -0700 |
| commit | 79d360e0f4221256675b274127e0abacaf54be28 (patch) | |
| tree | 95511bc1b89d8d11d73e195af5f98848940a062f /src | |
| parent | ceab8e8aecc29ac84e184886312a27c835cad8a9 (diff) | |
| download | coinosv3-79d360e0f4221256675b274127e0abacaf54be28.tar.gz coinosv3-79d360e0f4221256675b274127e0abacaf54be28.zip | |
The padlock refuses to strand a seed: a wallet the vault never met gets saved (password asked) before locking, and unlocking returns you to the wallet you left
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.js | 28 | ||||
| -rw-r--r-- | src/i18n.js | 1 |
2 files changed, 26 insertions, 3 deletions
@@ -1400,6 +1400,8 @@ function startForget(id) { function cancelPw() { // Declining the lock-time offer is an answer, not a postponement. if (ui.pw && ui.pw.purpose === 'lock') markLockPwAsked(); + // Backing out of save-before-lock means "don't lock" — go home, keys intact. + if (ui.pw && ui.pw.purpose === 'locksave') ui.screen = 'wallet'; ui.pw = null; render(); } @@ -1444,10 +1446,12 @@ function submitPw() { mergeVaultList(list); // bring existing persisted accounts into the session } const acc = accounts.find((a) => a.id === p.accId); - if (acc) acc.persisted = p.purpose === 'save'; + if (acc) acc.persisted = p.purpose === 'save' || p.purpose === 'locksave'; writeVault(); persistAccounts(); + const finishLock = p.purpose === 'locksave'; ui.pw = null; + if (finishLock) { softLock(); return; } render(); } @@ -1514,7 +1518,8 @@ function unlockVault() { else localStorage.removeItem(UNLOCK_UNTIL_KEY); } catch {} armUnlockDeadline(); - if (accounts.length) activateAccount(accounts[0], { fresh: true }); + const cur = accounts.find((a) => a.id === activeId) || accounts[0]; + if (cur) activateAccount(cur, { fresh: true }); else lock(); // everything got signed out by the timer } function skipVault() { @@ -1755,6 +1760,23 @@ function softLock() { // Nothing watchable, or a passwordless vault (locking would mean nothing): // fall back to the classic flow, which offers to set a password. if (!watch.length || !hasVault() || blankPw) { lock({ offerPassword: true }); ui.justLocked = true; return; } + // A wallet that isn't IN the vault can't come back from a lock — unlocking + // decrypts the vault, and this seed isn't there (created while the vault + // sat locked, so autoSave couldn't write it). Save it first: with the + // session password if we hold it, else ask for the vault password now. + const active = activeAccount(); + if (active && active.type === 'full' && !active.provisional && !active.persisted) { + if (vaultPassword != null) { + active.persisted = true; + writeVault(); + persistAccounts(); + } else { + ui.pw = { purpose: 'locksave', accId: active.id, mode: 'enter', v1: '', v2: '', error: '' }; + ui.screen = 'accounts'; // the pw prompt renders on the accounts screen + render(); + return; + } + } const keepId = activeId; for (const f of FEATURES) { try { f.stop && f.stop(); } catch {} } vaultPassword = null; @@ -2439,7 +2461,7 @@ function pwPromptCard() { return h('div', { class: 'card col' }, h('h3', {}, atLock ? t('lockPwTitle') : change ? t('changePassword') : p.mode === 'set' ? t('setPassword') : t('enterPassword')), h('p', { class: 'small muted', style: 'margin:0' }, - atLock ? t('lockPwDesc') : change ? t('changePasswordDesc') : p.mode === 'set' ? t('setPasswordDesc') : t('enterPasswordDesc')), + atLock ? t('lockPwDesc') : change ? t('changePasswordDesc') : p.purpose === 'locksave' ? t('lockSaveDesc') : p.mode === 'set' ? t('setPasswordDesc') : t('enterPasswordDesc')), change ? h('input', { type: 'password', placeholder: t('currentPassword'), value: p.v0, onInput: (e) => (p.v0 = e.target.value) }) : null, h('input', { type: 'password', placeholder: newish ? t('passwordOptional') : t('password'), value: p.v1, onInput: (e) => (p.v1 = e.target.value) }), newish ? h('input', { type: 'password', placeholder: t('confirmPassword'), value: p.v2, onInput: (e) => (p.v2 = e.target.value) }) : null, diff --git a/src/i18n.js b/src/i18n.js index 9e5d77f..4538894 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -77,6 +77,7 @@ const STR = { pwChanged: 'Password changed', enterPassword: 'Enter your password', enterPasswordDesc: 'Unlock your saved wallets to continue.', + lockSaveDesc: "This wallet isn't saved on this device yet. Enter your password to save it before locking — otherwise unlocking couldn't bring it back.", password: 'Password', confirmPassword: 'Confirm password', pwMismatch: 'Passwords do not match.', |
