From 1247c24c4d619ff714aa3e7d25396b91316cfba7 Mon Sep 17 00:00:00 2001
From: clawbot <35+clawbot@noreply.example.org>
Date: Sun, 4 Oct 2026 12:58:26 +0200
Subject: [PATCH] fix: keep the dApp approval error containers to their
reserved height (closes #297)
#approve-tx-error and #approve-sign-error reserved 20px, but their border
and padding took 10px of it, so a one-line error grew them to 26px and
pushed the Confirm/Sign buttons down 6px. They now reserve 30px, the same
4px to spare over one line that the other password error containers have.
A new end-to-end test shows each of the six password error containers on
its own screen at the popup viewport, empty and then with an error, and
fails if one changes height or the element below it moves.
Model: opus-5-5
---
TODO.md | 10 ++++++
src/popup/index.html | 4 +--
tests/e2e/run.js | 75 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 87 insertions(+), 2 deletions(-)
diff --git a/TODO.md b/TODO.md
index eddcc3a..4bf1256 100644
--- a/TODO.md
+++ b/TODO.md
@@ -45,6 +45,16 @@ but the review is broader than any of them.
# Completed Steps
+- 2026-10-04: The error container on each dApp approval screen keeps its height
+ when an error appears
+ ([#297](https://git.eeqj.de/sneak/AutistMask/issues/297)). `#approve-tx-error`
+ and `#approve-sign-error` reserved 20px, but their border and padding took
+ 10px of it, so a one-line error grew them to 26px and pushed the buttons below
+ down 6px. They now reserve 30px. A new test in `tests/e2e/run.js` shows each
+ of the six password error containers on its own screen, empty and then with an
+ error, and fails if one changes height or the element below it moves. Some of
+ the longer messages these two containers can show still take two lines.
+
- 2026-10-04: A transaction with no `to` says "This transaction creates a new
contract. It has no recipient." on its recipient line and in its transaction
history row ([#250](https://git.eeqj.de/sneak/AutistMask/issues/250)). The
diff --git a/src/popup/index.html b/src/popup/index.html
index 741bc1f..554f6be 100644
--- a/src/popup/index.html
+++ b/src/popup/index.html
@@ -1633,7 +1633,7 @@
@@ -1710,7 +1710,7 @@
diff --git a/tests/e2e/run.js b/tests/e2e/run.js
index c3b61de..1a256c1 100644
--- a/tests/e2e/run.js
+++ b/tests/e2e/run.js
@@ -1449,6 +1449,81 @@ test("an over-long flash message keeps to one line (#252)", async (env) => {
}
});
+// --------------------------------------- password error containers (#297)
+
+// Every screen that asks for a password reserves room for one line of error.
+// The two on the dApp approval screens also have a border and padding, which
+// that reserved height has to cover too.
+const PASSWORD_ERROR_CONTAINERS = [
+ "approve-tx-error",
+ "approve-sign-error",
+ "export-privkey-flash",
+ "show-phrase-flash",
+ "delete-wallet-flash",
+ "confirm-tx-password-error",
+];
+
+// Shows only the screen holding the container, then measures the container
+// and the element below it empty and again filled the way showError() in
+// src/popup/views/helpers.js fills it. Runs in the page.
+function measurePasswordError(id) {
+ const container = document.getElementById(id);
+ const screen = container.closest(".view");
+ for (const view of document.querySelectorAll(".view")) {
+ view.classList.toggle("hidden", view !== screen);
+ }
+ const below = container.nextElementSibling;
+ const measure = () => ({
+ height: container.getBoundingClientRect().height,
+ belowTop: below.getBoundingClientRect().top + window.scrollY,
+ belowHeight: below.getBoundingClientRect().height,
+ });
+ const empty = measure();
+ container.textContent = "Please enter your password.";
+ container.style.visibility = "visible";
+ const filled = measure();
+ container.textContent = "";
+ container.style.visibility = "hidden";
+ return { empty, filled };
+}
+
+test("a password error moves nothing on any screen (#297)", async (env) => {
+ const page = await openPopup(env.ctx, env.popupUrl);
+ try {
+ await page.setViewportSize(POPUP_VIEWPORT);
+ for (const id of PASSWORD_ERROR_CONTAINERS) {
+ const { empty, filled } = await page.evaluate(
+ measurePasswordError,
+ id,
+ );
+ assert(
+ empty.belowHeight > 0,
+ "nothing is shown below #" + id + ", so nothing was measured",
+ );
+ assert(
+ filled.height === empty.height,
+ "#" +
+ id +
+ " is " +
+ empty.height +
+ "px empty and " +
+ filled.height +
+ "px with an error",
+ );
+ assert(
+ filled.belowTop === empty.belowTop,
+ "the element below #" +
+ id +
+ " moved " +
+ (filled.belowTop - empty.belowTop) +
+ "px when the error appeared",
+ );
+ }
+ } finally {
+ await page.close();
+ }
+});
+
// --------------------------------------------- confirmation screen (#238)
//
// The screen that decides what gets signed. The arithmetic underneath it