From a18637287775134fa5389bd49260e1d714ddd070 Mon Sep 17 00:00:00 2001 From: sneak Date: Tue, 6 Oct 2026 23:50:17 +0000 Subject: [PATCH] fix: Back from Settings no longer lands on a delete wallet screen left by the gear (closes #480) Leaving the delete wallet or lost-password screen drops its wallet selection, but the settings gear had just pushed the screen onto the Back stack, so Back from Settings showed a screen whose button could only answer "No wallet selected for deletion." Each screen's leave handler now also takes it off the top of the stack, as the private key export and recovery phrase screens do since https://git.eeqj.de/sneak/AutistMask/issues/461. Back from Settings then stays on Settings once, as it does for the recovery phrase screen. Jest tests drive the gear and then Back on both screens, and the delete screen's own Back. Model: opus-5-5 --- README.md | 6 ++++ TODO.md | 9 ++++++ src/popup/views/deleteWallet.js | 27 +++++++++++++---- tests/deleteWalletLostPassword.test.js | 41 ++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4ef284c..5a6ef3f 100644 --- a/README.md +++ b/README.md @@ -1838,6 +1838,9 @@ view would leave a wallet one click from deletion. try again." on the error line, nothing deleted - "I have lost my password" → **DeleteWalletLostPassword** - "Back" → previous screen (Settings) + - Settings gear → **Settings**, whose "Back" never lands back on this + screen: leaving drops the wallet the screen was showing, so it also takes + the screen off the Back stack #### DeleteWalletLostPassword (`delete-wallet-lost-password`) @@ -1876,6 +1879,9 @@ view would leave a wallet one click from deletion. selection comes back with it. The two delete screens are siblings rather than parent and child: nothing is pushed on the way here, so both have Settings as their Back target. + - Settings gear → **Settings**, whose "Back" never lands back on this + screen: leaving drops the wallet the screen was showing, so it also takes + the screen off the Back stack - **Deliberately not password-gated.** A password in front of _discarding_ a secret protects nobody: an attacker at the popup who wants the wallet gone can uninstall the extension, so the only person such a gate stops is the owner who diff --git a/TODO.md b/TODO.md index c60adca..f47bf3c 100644 --- a/TODO.md +++ b/TODO.md @@ -45,6 +45,15 @@ but the review is broader than any of them. # Completed Steps +- 2026-10-06: Back from Settings no longer lands on the delete wallet or + lost-password screen after either was left by the settings gear + ([#480](https://git.eeqj.de/sneak/AutistMask/issues/480)), the defect + [#461](https://git.eeqj.de/sneak/AutistMask/issues/461) fixed for the two + secret screens. Leaving drops the screen's wallet selection, so its leave + handler now also takes it off the Back stack, as a reopened popup already + does. `tests/deleteWalletLostPassword.test.js` drives the gear and then Back + on both screens. + - 2026-10-06: `script/bootstrap` no longer reports success while node cannot find a package listed in `dependencies` or `devDependencies` of `package.json` ([#263](https://git.eeqj.de/sneak/AutistMask/issues/263)). After the install diff --git a/src/popup/views/deleteWallet.js b/src/popup/views/deleteWallet.js index a1e284d..e7bbb89 100644 --- a/src/popup/views/deleteWallet.js +++ b/src/popup/views/deleteWallet.js @@ -49,9 +49,9 @@ function confirmKey(name) { } // Drop the password from the DOM and the wallet selection from the -// closure. Registered as the view-leave handler as well as run on entry, -// so the typed password does not sit in the hidden view after the user -// navigates away by any route, including the Settings gear. +// closure. Run by the view-leave handler as well as on entry, so the typed +// password does not sit in the hidden view after the user navigates away +// by any route, including the Settings gear. function clear() { deleteWalletIndex = null; $("delete-wallet-password").value = ""; @@ -147,8 +147,25 @@ async function finishDelete(walletIdx) { function init(_ctx) { ctx = _ctx; - onViewLeave("delete-wallet-confirm", clear); - onViewLeave("delete-wallet-lost-password", clearLostPassword); + // Leaving drops the wallet selection, so each screen also comes off the + // Back stack, where the settings gear has just put it: Back from + // Settings must not land on a screen whose button can only answer "No + // wallet selected for deletion." A reopened popup drops them from the + // stack the same way (https://git.eeqj.de/sneak/AutistMask/issues/480). + onViewLeave("delete-wallet-confirm", () => { + clear(); + const stack = state.viewStack; + if (stack[stack.length - 1] === "delete-wallet-confirm") { + stack.pop(); + } + }); + onViewLeave("delete-wallet-lost-password", () => { + clearLostPassword(); + const stack = state.viewStack; + if (stack[stack.length - 1] === "delete-wallet-lost-password") { + stack.pop(); + } + }); // No wipe here: goBack() routes through showView(), which runs the // leave hook. diff --git a/tests/deleteWalletLostPassword.test.js b/tests/deleteWalletLostPassword.test.js index b28c3fe..8e23c2f 100644 --- a/tests/deleteWalletLostPassword.test.js +++ b/tests/deleteWalletLostPassword.test.js @@ -615,3 +615,44 @@ describe("the password route's confirm button", () => { ]); }); }); + +// https://git.eeqj.de/sneak/AutistMask/issues/480: leaving either delete +// screen drops its wallet selection, so Back onto one showed a screen whose +// button could only answer "No wallet selected for deletion." +describe("Back from Settings after leaving by the settings gear", () => { + test("does not land on the delete screen", () => { + const { helpers, deleteWallet, state } = load(); + deleteWallet.show(1); + // The settings gear: push the current view, then show Settings. + helpers.pushCurrentView(); + helpers.showView("settings"); + + expect(state.viewStack).toEqual(["main", "settings"]); + helpers.goBack(); + expect(state.currentView).not.toBe("delete-wallet-confirm"); + }); + + test("does not land on the lost-password screen", async () => { + const { helpers, deleteWallet, state } = load(); + await openLostPassword(deleteWallet, 1); + // The settings gear: push the current view, then show Settings. + helpers.pushCurrentView(); + helpers.showView("settings"); + + expect(state.viewStack).toEqual(["main", "settings"]); + helpers.goBack(); + expect(state.currentView).not.toBe(VIEW); + }); + + // The lost-password screen's own Back is "Back returns to the delete + // screen with its wallet still chosen", above. + test("the delete screen's own Back leaves the rest of the stack alone", async () => { + const { deleteWallet, state } = load(); + deleteWallet.show(1); + + await click("btn-delete-wallet-back"); + + expect(state.currentView).toBe("settings"); + expect(state.viewStack).toEqual(["main"]); + }); +}); -- 2.54.0