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"]); + }); +});