diff --git a/TODO.md b/TODO.md index 8094a90..b19330a 100644 --- a/TODO.md +++ b/TODO.md @@ -44,6 +44,14 @@ undefined identifiers, which is how # Completed Steps +- 2026-08-12: One wording for a rejected password on every screen that asks for + one — the send confirmation and the delete-wallet confirmation no longer say + "Wrong password." (a fragment, which `RULES.md` Language & Labeling forbids) + and the two reveal screens no longer say "not correct", so all five + `decryptWithPassword` call sites now show the sentence the dApp approval paths + introduced. Strings only, no behaviour change, and each error container + measured at a 360px viewport in the pinned Playwright container + ([#172](https://git.eeqj.de/sneak/AutistMask/issues/172)). - 2026-08-12: The dust threshold field now explains a rejection instead of snapping back in silence, with the parse in a pure, unit-tested module that accepts plain decimal digits only — hex and exponent notation are refused diff --git a/src/popup/views/confirmTx.js b/src/popup/views/confirmTx.js index 03678f4..5d59734 100644 --- a/src/popup/views/confirmTx.js +++ b/src/popup/views/confirmTx.js @@ -422,7 +422,10 @@ function init(ctx) { password, ); } catch (e) { - showError("confirm-tx-password-error", "Wrong password."); + showError( + "confirm-tx-password-error", + "That password is incorrect. Please try again.", + ); return; } diff --git a/src/popup/views/deleteWallet.js b/src/popup/views/deleteWallet.js index 78e440c..685f937 100644 --- a/src/popup/views/deleteWallet.js +++ b/src/popup/views/deleteWallet.js @@ -74,7 +74,8 @@ function init(_ctx) { try { await decryptWithPassword(wallet.encryptedSecret, pw); } catch (_e) { - $("delete-wallet-flash").textContent = "Wrong password."; + $("delete-wallet-flash").textContent = + "That password is incorrect. Please try again."; $("delete-wallet-flash").style.visibility = "visible"; btn.disabled = false; btn.classList.remove("text-muted"); diff --git a/src/popup/views/exportPrivkey.js b/src/popup/views/exportPrivkey.js index 6860240..dd60f51 100644 --- a/src/popup/views/exportPrivkey.js +++ b/src/popup/views/exportPrivkey.js @@ -144,7 +144,7 @@ async function reveal() { $("export-privkey-flash").style.visibility = "hidden"; } catch { if (!isCurrentReveal(generation)) return; - fail("That password is not correct. Please try again."); + fail("That password is incorrect. Please try again."); } finally { btn.disabled = false; btn.classList.remove("text-muted"); diff --git a/src/popup/views/showPhrase.js b/src/popup/views/showPhrase.js index 80ad327..d8962c2 100644 --- a/src/popup/views/showPhrase.js +++ b/src/popup/views/showPhrase.js @@ -126,7 +126,7 @@ async function reveal() { if (!isCurrentReveal(generation)) return; // Deliberately not the caught error: the message is fixed so that // nothing derived from the ciphertext or the attempt can surface. - fail("That password is not correct. Please try again."); + fail("That password is incorrect. Please try again."); } finally { btn.disabled = false; btn.classList.remove("text-muted"); diff --git a/tests/exportPrivkey.test.js b/tests/exportPrivkey.test.js index 468acca..516026b 100644 --- a/tests/exportPrivkey.test.js +++ b/tests/exportPrivkey.test.js @@ -247,7 +247,7 @@ describe("a reveal that is not interrupted", () => { expect(node("export-privkey-value").textContent).toBe(""); expect(node("export-privkey-flash").textContent).toBe( - "That password is not correct. Please try again.", + "That password is incorrect. Please try again.", ); }); }); diff --git a/tests/passwordMessages.test.js b/tests/passwordMessages.test.js new file mode 100644 index 0000000..463e748 --- /dev/null +++ b/tests/passwordMessages.test.js @@ -0,0 +1,79 @@ +// One wording for one condition (issue #172). +// +// Every screen that asks for the password decrypts the vault itself, and +// each one used to write its own sentence for the same failure: the send +// confirmation and the delete-wallet confirmation said "Wrong password." +// (a fragment, which RULES.md Language & Labeling forbids), the reveal +// screens said "That password is not correct.", and the two dApp approval +// paths said "That password is incorrect." A user hitting two of those +// minutes apart had no way to tell whether the wallet meant the same +// thing. +// +// This scans the source rather than driving six views, because the +// invariant is about the set of call sites and not about any one of them: +// a seventh screen that decrypts the vault has to join the set, and a +// DOM test per view cannot notice one that was never written. + +const fs = require("fs"); +const path = require("path"); + +const SRC = path.join(__dirname, "..", "src"); + +const CANONICAL = "That password is incorrect. Please try again."; + +// Wordings this repo has actually shipped for the same condition. A +// literal match, so a new variant is caught by the "only the canonical +// sentence appears" assertion below rather than by guessing at phrasing. +const SUPERSEDED = [ + "Wrong password.", + "That password is not correct. Please try again.", +]; + +function jsFilesUnder(dir) { + return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) return jsFilesUnder(full); + return entry.name.endsWith(".js") ? [full] : []; + }); +} + +// The call sites are found, not listed: the file layout moves (the private +// key export was in addressDetail.js when #172 was filed and is its own +// view now), and a hardcoded list would quietly stop covering a screen it +// no longer names. +function callSites() { + return jsFilesUnder(SRC).filter((file) => { + if (file === path.join(SRC, "shared", "vault.js")) return false; + return fs.readFileSync(file, "utf8").includes("decryptWithPassword("); + }); +} + +describe("password failure messages", () => { + const files = callSites(); + + test("the call sites are found where they are expected", () => { + const found = files + .map((f) => path.relative(SRC, f).split(path.sep).join("/")) + .sort(); + expect(found).toEqual([ + "popup/views/approval.js", + "popup/views/confirmTx.js", + "popup/views/deleteWallet.js", + "popup/views/exportPrivkey.js", + "popup/views/showPhrase.js", + ]); + }); + + test("the canonical message is a full sentence", () => { + expect(CANONICAL).toMatch(/^[A-Z][^]*\.$/); + }); + + test.each(files)("%s tells the user the canonical sentence", (f) => { + expect(fs.readFileSync(f, "utf8")).toContain(CANONICAL); + }); + + test.each(files)("%s carries no superseded wording", (f) => { + const source = fs.readFileSync(f, "utf8"); + for (const old of SUPERSEDED) expect(source).not.toContain(old); + }); +});