2 Commits
Author SHA1 Message Date
sneak de22b4edb0 docs: code comments name the headings README has now (closes #724)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s
The README rewrite renamed or merged several headings that comments still
cited. Each comment now names the current heading.

The e2e workflow's citations of "In CI" are dropped, because no section has what
they pointed at.

The comment on withLock() no longer states a Firefox private window exception,
because the manifest keeps the extension out of those windows.

Two comments also take corrections from reviews: the cases with no total in
DeleteAddress's warning, and the screens that say a swap's amount could not be
decoded.

Model: opus-5-5
2026-11-03 12:11:36 +00:00
clawbot a65e84c2dd docs: the sentences the rewrite reviews left as follow-up notes (closes #723)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s
Each sentence named on #723 now
matches the code on `next`:

- the Firefox load steps are numbered;
- the AddToken refusal lists name a token that fakes a known symbol;
- the worker's in-memory state is listed in full;
- `script/bootstrap` falls back to `npm install -g` for yarn;
- the Send line under a `null` ETH balance holds with ETH selected, and
  "Max" has a line of its own;
- the Home and Send lines there name `SepoliaETH` on Sepolia;
- the well-known symbols are given per network;
- a network switch empties the fraud contract list;
- the cases where an import leaves out a removed address are spelled out;
- a new token's line depends on "Show tracked tokens with zero balance";
- wallet drainer is defined at first use only.

Model: fable-5-1
2026-11-03 13:06:17 +01:00
18 changed files with 125 additions and 99 deletions

No files matched your search

+4 -7
View File
@@ -23,9 +23,8 @@ on: [push]
# Gitea branch protection, which this repo does not configure, so a failure # Gitea branch protection, which this repo does not configure, so a failure
# here is a red mark a reviewer has to account for rather than a hard # here is a red mark a reviewer has to account for rather than a hard
# block. Making e2e-chrome a required check is blocked while reports of the # block. Making e2e-chrome a required check is blocked while reports of the
# Chrome suite failing under load are still open; the "In CI" section of # Chrome suite failing under load are still open. A gate that fails at random
# README.md names them. A gate that fails at random teaches people to merge # teaches people to merge past red.
# past red.
# #
# Nothing here may pass vacuously. There is no continue-on-error and no # Nothing here may pass vacuously. There is no continue-on-error and no
# `|| true`. Both scripts exit non-zero when docker is missing, when the # `|| true`. Both scripts exit non-zero when docker is missing, when the
@@ -36,8 +35,7 @@ jobs:
e2e-chrome: e2e-chrome:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Bounds the image build, a cold cache included, and the three Chrome # Bounds the image build, a cold cache included, and the three Chrome
# programs, so a hung browser frees the shared runner. README.md # programs, so a hung browser frees the shared runner.
# "In CI" has the measured times.
timeout-minutes: 40 timeout-minutes: 40
steps: steps:
# actions/checkout v4.2.2, 2026-02-22 # actions/checkout v4.2.2, 2026-02-22
@@ -47,8 +45,7 @@ jobs:
e2e-firefox: e2e-firefox:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Bounds the image build, a cold cache included, and both Firefox # Bounds the image build, a cold cache included, and both Firefox
# programs, so a hung browser frees the shared runner. README.md # programs, so a hung browser frees the shared runner.
# "In CI" has the measured times.
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
# actions/checkout v4.2.2, 2026-02-22 # actions/checkout v4.2.2, 2026-02-22
+30 -13
View File
@@ -89,9 +89,13 @@ Load it:
2. Turn on "Developer mode". 2. Turn on "Developer mode".
3. Click "Load unpacked". 3. Click "Load unpacked".
4. Pick `dist/chrome/`. 4. Pick `dist/chrome/`.
- Firefox: open `about:debugging#/runtime/this-firefox`, click "Load Temporary - Firefox:
Add-on" and pick `dist/firefox/manifest.json`. A temporary add-on is unloaded 1. Open `about:debugging#/runtime/this-firefox`.
when Firefox exits. Read Installing on Firefox below before relying on it. 2. Click "Load Temporary Add-on".
3. Pick `dist/firefox/manifest.json`.
A temporary add-on is unloaded when Firefox exits. Read Installing on Firefox
below before relying on it.
The user guide, [`docs/README.md`](docs/README.md), explains how to use the The user guide, [`docs/README.md`](docs/README.md), explains how to use the
wallet. The release procedure is in [`docs/RELEASE.md`](docs/RELEASE.md). wallet. The release procedure is in [`docs/RELEASE.md`](docs/RELEASE.md).
@@ -375,12 +379,18 @@ Three rules follow from the worker being stopped:
worker stopping. Such a connection ends when the browser closes, when the worker stopping. Such a connection ends when the browser closes, when the
active address changes, or when the site is removed in Settings. active address changes, or when the site is removed in Settings.
Two things live only in the worker's memory and are lost when it stops: pending These things live only in the worker's memory and are lost when it stops:
approvals and the record of broadcast nonces. An approval, and the site's
request waiting on it, exist nowhere else. So while any approval is pending, the - pending approvals;
worker calls `runtime.getPlatformInfo()` every 20 seconds. Chrome counts that - the record of broadcast nonces;
call as activity and keeps the worker running. The timer stops itself once - the timer that keeps the worker running while an approval is pending;
nothing is pending. - the record of the one open transaction approval;
- the job that sets up the alarms at every start, while it runs.
An approval, and the site's request waiting on it, exist nowhere else. So while
any approval is pending, the worker calls `runtime.getPlatformInfo()` every 20
seconds. Chrome counts that call as activity and keeps the worker running. The
timer stops itself once nothing is pending.
Alarms the code no longer handles are listed in `OBSOLETE_ALARMS` and cleared on Alarms the code no longer handles are listed in `OBSOLETE_ALARMS` and cleared on
every start. The worker fetches nothing when it starts. every start. The worker fetches nothing when it starts.
@@ -903,7 +913,8 @@ not change. A flash message refuses:
- a token already tracked; - a token already tracked;
- an address on the scam list; - an address on the scam list;
- a contract that fails the lookup. - a contract that fails the lookup;
- a token that fakes a known symbol.
A network switch before the lookup answers adds nothing, and a flash message A network switch before the lookup answers adds nothing, and a flash message
says so. says so.
@@ -1224,8 +1235,12 @@ holds for a new address, and for every address after a network switch. While it
is `null`: is `null`:
- the balance lists show "quantity unknown"; - the balance lists show "quantity unknown";
- Home's ETH balance reads `unknown (ETH)`; - Home's ETH balance reads `unknown (ETH)` on mainnet and `unknown (SepoliaETH)`
- Send reads `Current balance: unknown (ETH)`, and "Max" fills in nothing; on Sepolia;
- with ETH selected, Send reads `Current balance: unknown (ETH)` on mainnet and
`Current balance: unknown (SepoliaETH)` on Sepolia;
- with ETH selected, "Max" fills in nothing and shows the flash message "This
token's balance is unknown.";
- "Send" is not refused for a zero balance; - "Send" is not refused for a zero balance;
- the address has no total; - the address has no total;
- DeleteAddress shows its warning. - DeleteAddress shows its warning.
@@ -1725,7 +1740,9 @@ The `Makefile` targets are thin shims over `script/`:
- `script/bootstrap`: install the dependencies. Node and yarn come from the - `script/bootstrap`: install the dependencies. Node and yarn come from the
system when present. Otherwise node comes from a pinned nvm install and yarn system when present. Otherwise node comes from a pinned nvm install and yarn
from corepack. Fails when node cannot find a package named in `package.json`. from corepack. When corepack and nvm are both missing, yarn comes from
`npm install -g`. Fails when node cannot find a package named in
`package.json`.
- `script/setup`: `script/bootstrap`, then the git pre-commit hook. - `script/setup`: `script/bootstrap`, then the git pre-commit hook.
- `script/test`: build the `test` stage of the `Dockerfile` with `--no-cache`. - `script/test`: build the `test` stage of the `Dockerfile` with `--no-cache`.
The stage runs the jest suite, stopped at 90 seconds and rerun verbose on The stage runs the jest suite, stopped at 90 seconds and rerun verbose on
+33 -21
View File
@@ -60,7 +60,8 @@ settings.
- "Check recipients on Etherscan". Etherscan is a block explorer website. With - "Check recipients on Etherscan". Etherscan is a block explorer website. With
the setting on, the wallet fetches the recipient's Etherscan page each time the setting on, the wallet fetches the recipient's Etherscan page each time
you review a send. The wallet warns when Etherscan labels the address as you review a send. The wallet warns when Etherscan labels the address as
phishing, scam, exploiter or wallet drainer. Only the recipient's address is phishing, scam, exploiter or wallet drainer. A wallet drainer is a contract
that empties a wallet once given permission. Only the recipient's address is
sent. The Etherscan server cannot be changed. sent. The Etherscan server cannot be changed.
Each time a site's request opens a window, the wallet checks the site against a Each time a site's request opens a window, the wallet checks the site against a
@@ -211,10 +212,18 @@ allowed or denied for it.
Getting the address back is not easy. "+" computes only addresses that this Getting the address back is not easy. "+" computes only addresses that this
wallet has never listed. The removed address was listed once, so "+" skips it. wallet has never listed. The removed address was listed once, so "+" skips it.
The way back is to delete the whole wallet and import its backup again. After The way back is to delete the whole wallet and import its backup again. The
the import, the scan lists the address again if it holds ETH or has sent a import lists the wallet's first address, then scans for used addresses. An
transaction. Otherwise "+" may reach it again, because the new wallet has never address is used when it holds ETH or has sent a transaction. A read that fails
listed it. during the scan counts the address as unused. The scan stops after five unused
addresses in a row. When the scan finds two or more used addresses, the wallet
lists those instead. The wallet does not list the removed address when:
- the scan finds two or more used addresses, and the address is not among them;
- the scan finds fewer than two, and the address is not the first.
When the wallet does not list the address, "+" may reach it again, because the
new wallet has never listed it.
### Deleting a wallet ### Deleting a wallet
@@ -245,9 +254,11 @@ else is treated as spam and hidden. Turning "Use Blockscout" off removes every
untracked token from the lists at once. untracked token from the lists at once.
The balance list and the send screen hide a token that carries a well-known The balance list and the send screen hide a token that carries a well-known
symbol, `ETH` included, but comes from another contract. A token that fakes a symbol but comes from another contract. `ETH` and `SepoliaETH` are well-known
known symbol cannot be tracked ("Not added: this token fakes a known symbol."), symbols on every network. The symbols of the built-in list are well-known on
and is never listed at zero. See Scam protection. Ethereum Mainnet only. A token that fakes a known symbol cannot be tracked ("Not
added: this token fakes a known symbol."), and is never listed at zero. See Scam
protection.
### Tracking a token ### Tracking a token
@@ -263,8 +274,9 @@ From Settings, under Tracked Tokens, click "+ Add token". That screen offers the
top 10 tokens as buttons, the top 100 in a dropdown, and a contract address top 10 tokens as buttons, the top 100 in a dropdown, and a contract address
field. The two lists are offered on Ethereum Mainnet only. field. The two lists are offered on Ethereum Mainnet only.
A line for the new token reads `quantity unknown` until a refresh has read its While "Show tracked tokens with zero balance" is on, a line for the new token
balance. reads `quantity unknown` until a refresh has read its balance. With that setting
off, the token has no line until a refresh has read its balance.
A token is tracked on the network that it was added on. Settings lists the A token is tracked on the network that it was added on. Settings lists the
tracked tokens of the active network, each with an `[x]` button that stops tracked tokens of the active network, each with an `[x]` button that stops
@@ -274,7 +286,8 @@ The wallet refuses to track:
- a token that is already tracked; - a token that is already tracked;
- an address on its scam list; - an address on its scam list;
- a contract that does not answer as an ERC-20 token. - a contract that does not answer as an ERC-20 token;
- a token that fakes a known symbol.
### The token screen ### The token screen
@@ -602,11 +615,11 @@ Blockscout reports no holder count for a token, the history and the send screen
still show it. While "Use Blockscout" is off, no holder count is known. still show it. While "Use Blockscout" is off, no holder count is known.
Fraud contracts. When the wallet detects a fake token transfer, it adds the Fraud contracts. When the wallet detects a fake token transfer, it adds the
token's contract to a list that it keeps. While "Hide transactions from detected token's contract to a list. A network switch empties the list. While "Hide
fraud contracts" is on, the history hides transfers from the contracts on that transactions from detected fraud contracts" is on, the history hides transfers
list. The send screen never offers those tokens. Turning "Hide fake tokens from the contracts on that list. The send screen never offers those tokens.
impersonating a known symbol" off also stops new contracts being added to the Turning "Hide fake tokens impersonating a known symbol" off also stops new
list. contracts being added to the list.
Dust transactions. Scammers send tiny amounts of ETH from an address that Dust transactions. Scammers send tiny amounts of ETH from an address that
resembles one you have dealt with, so that the similar address lands in your resembles one you have dealt with, so that the similar address lands in your
@@ -616,11 +629,10 @@ threshold starts at 100,000 gwei, which is 0.0001 ETH. A threshold of 0 hides
nothing. nothing.
Known scam addresses. The wallet ships a list of addresses known for fraud, Known scam addresses. The wallet ships a list of addresses known for fraud,
including phishing addresses and wallet drainers. A wallet drainer is a contract including phishing addresses and wallet drainers. Sending to a listed address
that empties a wallet once given permission. Sending to a listed address raises raises a warning on the confirmation screen, and a listed address cannot be
a warning on the confirmation screen, and a listed address cannot be tracked as tracked as a token. The list holds only addresses involved in fraud; it is not a
a token. The list holds only addresses involved in fraud; it is not a sanctions sanctions list.
list.
Addresses and hashes are shown in full, because address poisoning relies on Addresses and hashes are shown in full, because address poisoning relies on
shortened ones. The only shortened address is the label of a token with no known shortened ones. The only shortened address is the label of a token with no known
+1 -1
View File
@@ -1,7 +1,7 @@
// Turn a verified dist/ into the two distributable archives. // Turn a verified dist/ into the two distributable archives.
// //
// Invoked by script/package, which runs `make build` first so that dist/ has // Invoked by script/package, which runs `make build` first so that dist/ has
// already been checked against the build's own receipt (see the Build Receipts // already been checked against the build's own receipt (see the Builds
// section of README.md). This program does not build anything and does not // section of README.md). This program does not build anything and does not
// write into dist/: it reads the emitted tree and writes release/. // write into dist/: it reads the emitted tree and writes release/.
// //
+6 -6
View File
@@ -60,12 +60,12 @@ main() {
echo "Running the Firefox e2e suite..." echo "Running the Firefox e2e suite..."
# --shm-size=1g: Firefox needs more than the default 64MB /dev/shm. # --shm-size=1g: Firefox needs more than the default 64MB /dev/shm.
# --network none: the suite stubs nothing, so this is what keeps the # --network none: the container has no network, and the suite reaches
# run offline and deterministic. The extension swallows its own # only the test page and stub node on loopback (README.md, Browser
# fetch failures, so the popup flows work unchanged; see the # tests). The extension swallows its own fetch failures, so the popup
# network note in README.md. Weaker than the Chrome suite's # flows work unchanged. Weaker than the Chrome suite's fixture
# fixture interception, and honestly so — it proves no request # interception, and honestly so — it proves no request escaped, but it
# escaped, but it cannot report which ones were attempted. # cannot report which ones were attempted.
# HOME=/tmp: the image's root home is not a reliable place for the # HOME=/tmp: the image's root home is not a reliable place for the
# browser profile. # browser profile.
# #
+6 -6
View File
@@ -18,11 +18,11 @@
// updateState(fn) read fresh, apply fn to that fresh record, write it // updateState(fn) read fresh, apply fn to that fresh record, write it
// back — all holding the lock every write of the stored // back — all holding the lock every write of the stored
// record takes, so no other write lands in between, by // record takes, so no other write lands in between, by
// the background or by any page (README, "Stored state // the background or by any page (README, "Stored data").
// and its version"). The read is one storage round trip // The read is one storage round trip ahead of the write
// ahead of the write rather than a page lifetime ahead of // rather than a page lifetime ahead of it (which is what
// it (which is what made the popup's saveState() need a // made the popup's saveState() need a per-field merge
// per-field merge against a baseline at all). // against a baseline at all).
// //
// A handler that must both read and write therefore does its network work // A handler that must both read and write therefore does its network work
// against a snapshot it owns, and applies the RESULT inside updateState(). // against a snapshot it owns, and applies the RESULT inside updateState().
@@ -72,7 +72,7 @@ async function updateStateOnce(mutate) {
// src/shared/browserApi.js) is held from the read to the write: two // src/shared/browserApi.js) is held from the read to the write: two
// background turns, or a turn and a page's save, never interleave // background turns, or a turn and a page's save, never interleave
// (https://git.eeqj.de/sneak/AutistMask/issues/641). Every page's save waits // (https://git.eeqj.de/sneak/AutistMask/issues/641). Every page's save waits
// while it is held (README, "Stored state and its version"). // while it is held (README, "Stored data").
// //
// `mutate` must also not call updateState() itself, directly or through // `mutate` must also not call updateState() itself, directly or through
// anything it awaits: the inner turn waits for the lock the outer one holds, // anything it awaits: the inner turn waits for the lock the outer one holds,
+6 -6
View File
@@ -319,12 +319,12 @@ function showTxApproval(details) {
); );
} }
// A swap the decoder read no amount for, such as one whose V4 swap // A swap the decoder read no amount for, such as one whose V4 swap
// action could not be read or a V4 exact-out swap. The wait and error // action could not be read or a V4 exact-out swap. The wait, success
// screens say the amount could not be decoded, with no currency after // and error screens say the amount could not be decoded, with no
// it, rather than show the transaction's ETH value as the amount. A // currency after it, rather than show the transaction's ETH value as
// swap paid in native ETH keeps that value: it is the ETH the swap // the amount. A swap paid in native ETH keeps that value: it is the
// pays (for an exact-out swap, the most it pays), not a figure that // ETH the swap pays (for an exact-out swap, the most it pays), not a
// had to be read from the calldata. // figure that had to be read from the calldata.
const tokenIn = decoded.details.find((d) => d.label === "Token In"); const tokenIn = decoded.details.find((d) => d.label === "Token In");
if ( if (
tokenIn && tokenIn &&
+5 -4
View File
@@ -83,10 +83,11 @@ function recoveryPathText(wallet) {
// rounded number would report "0.0000 ETH" for an address holding real money. // rounded number would report "0.0000 ETH" for an address holding real money.
// The lines below it carry the amounts, in the same format as Home and // The lines below it carry the amounts, in the same format as Home and
// AddressDetail, followed by the USD total when there is one to give — no // AddressDetail, followed by the USD total when there is one to give — no
// total line at all on testnet or before the first price fetch, and no figure // total line at all on testnet, before the first price fetch or while no
// when every holding here is one with no price, since "$0.00" directly under // refresh has read the ETH balance, and no figure when every holding here is
// "This address holds a balance." is a contradiction. A token holding below // one with no price, since "$0.00" directly under "This address holds a
// 0.000001 does not count, as the lines below leave it out. // balance." is a contradiction. A token holding below 0.000001 does not count,
// as the lines below leave it out.
// //
// While no refresh has read the ETH balance the address may be empty, so the // While no refresh has read the ETH balance the address may be empty, so the
// sentence says "may hold". // sentence says "may hold".
+7 -7
View File
@@ -272,7 +272,7 @@ function clearFlash() {
} }
// The flash line reserves exactly one line, and a message that wrapped would // The flash line reserves exactly one line, and a message that wrapped would
// push the screen below it down (README, No Layout Shift). So #flash-msg never // push the screen below it down (README, Appearance). So #flash-msg never
// wraps: text too long for the line is cut with an ellipsis, and the whole // wraps: text too long for the line is cut with an ellipsis, and the whole
// message is also put in the line's title. Write messages to fit, at most 50 // message is also put in the line's title. Write messages to fit, at most 50
// characters, so none is cut. // characters, so none is cut.
@@ -528,9 +528,9 @@ const ADDRESS_MIN_DISPLAY_LEN = 32;
// Truncate the middle of a string, replacing removed characters with "…". // Truncate the middle of a string, replacing removed characters with "…".
// Safety: refuses to truncate more than 10 characters, which is the maximum // Safety: refuses to truncate more than 10 characters, which is the maximum
// that still prevents address spoofing attacks (see Display Consistency in // that still prevents address spoofing attacks (see Addresses in README).
// README). Callers that need to display less should use a different UI // Callers that need to display less should use a different UI approach rather
// approach rather than silently making addresses insecure. // than silently making addresses insecure.
function truncateMiddle(str, maxLen) { function truncateMiddle(str, maxLen) {
if (str.length <= maxLen) return str; if (str.length <= maxLen) return str;
const removed = str.length - maxLen + 1; // +1 for the ellipsis char const removed = str.length - maxLen + 1; // +1 for the ellipsis char
@@ -633,9 +633,9 @@ function formatAddressHtml(address, ensName, maxLen, title) {
return renderAddressHtml(address, { title, ensName, maxLen }); return renderAddressHtml(address, { title, ensName, maxLen });
} }
// A transaction's time as every screen shows it (README, Display // A transaction's time as every screen shows it (README, Amounts): the ISO
// Consistency): the ISO datetime, in UTC when the UTC Timestamps setting is // datetime, in UTC when the UTC Timestamps setting is on, and the relative age.
// on, and the relative age. Views import these two; they keep no copies. // Views import these two; they keep no copies.
function isoDate(timestamp) { function isoDate(timestamp) {
const d = new Date(timestamp * 1000); const d = new Date(timestamp * 1000);
const pad = (n) => String(n).padStart(2, "0"); const pad = (n) => String(n).padStart(2, "0");
+1 -1
View File
@@ -265,7 +265,7 @@ async function renderSiteLists() {
// A network switch, made by the user here or saved by the background once the // A network switch, made by the user here or saved by the background once the
// user approved a site's request (src/popup/index.js), leaves every screen on // user approved a site's request (src/popup/index.js), leaves every screen on
// the page showing the previous network. Two functions leave the page as // the page showing the previous network. Two functions leave the page as
// README's Screen Map describes: // README's Navigation section describes:
// //
// - cutViewStack(), before the switch is saved, so that save keeps the cut for // - cutViewStack(), before the switch is saved, so that save keeps the cut for
// a popup closed straight after // a popup closed straight after
+1 -1
View File
@@ -1,4 +1,4 @@
// The 4-decimal amount rule from README.md's Display Consistency section, and // The 4-decimal amount rule from README.md's Amounts section, and
// the one exception to it, in one place. Three call sites had grown their own // the one exception to it, in one place. Three call sites had grown their own
// copy of the truncation — the history and balance lists // copy of the truncation — the history and balance lists
// (`src/shared/transactions.js`), the approval screen's ERC-20 amount line // (`src/shared/transactions.js`), the approval screen's ERC-20 amount line
+5 -6
View File
@@ -240,10 +240,9 @@ function onStorageChanged(listener) {
/** /**
* Run `fn` while holding the browser's lock called `name` * Run `fn` while holding the browser's lock called `name`
* (`navigator.locks`), and settle as `fn` settles. Every page of the extension * (`navigator.locks`), and settle as `fn` settles. Every page of the extension
* and its background share these locks, except a page in a Firefox private * and its background share these locks, so while one of them holds `name`
* window (README, "Stored state and its version"), so while one of them holds * another asking for it waits, in the order they asked. The lock is let go when
* `name` another asking for it waits, in the order they asked. The lock is let * `fn` settles, whether it resolved or rejected.
* go when `fn` settles, whether it resolved or rejected.
* *
* Every write of the stored record holds the lock named after its key, * Every write of the stored record holds the lock named after its key,
* "autistmask", from its read to its write: saveState() in * "autistmask", from its read to its write: saveState() in
@@ -266,8 +265,8 @@ function withLock(name, fn) {
// `storage.session`, the browser's session storage: what is kept there lasts // `storage.session`, the browser's session storage: what is kept there lasts
// until the browser closes. It survives the background worker being stopped // until the browser closes. It survives the background worker being stopped
// and started again, which module-level variables do not (README, Background // and started again, which module-level variables do not (README, The
// scheduling). The background keeps the connections allowed without // background worker). The background keeps the connections allowed without
// "Remember" there; see src/background/index.js. Firefox has it from version // "Remember" there; see src/background/index.js. Firefox has it from version
// 115, the minimum manifest/firefox.json declares. // 115, the minimum manifest/firefox.json declares.
// //
+5 -5
View File
@@ -1,11 +1,11 @@
// The floor of the approval screen's amount line. // The floor of the approval screen's amount line.
// //
// Amounts are truncated to four decimal places for scannability (README.md, // Amounts are truncated to four decimal places for scannability (README.md,
// Display Consistency). With the token's true scale resolved, that truncation // Amounts). With the token's true scale resolved, that truncation can still
// can still take a real amount below the floor and print it as `0.0000`: one // take a real amount below the floor and print it as `0.0000`: one base unit of
// base unit of an 18-decimal token, or a few hundred of an 8-decimal one. On // an 18-decimal token, or a few hundred of an 8-decimal one. On the one screen
// the one screen whose job is to state what is being authorized, a nonzero // whose job is to state what is being authorized, a nonzero transfer or
// transfer or allowance then reads as nothing. // allowance then reads as nothing.
// //
// The invariant asserted here is narrow: a nonzero amount never renders as // The invariant asserted here is narrow: a nonzero amount never renders as
// zero. The four-decimal rule itself is unchanged, and the string the // zero. The four-decimal rule itself is unchanged, and the string the
+4 -4
View File
@@ -74,7 +74,7 @@ describe("parsing the dust threshold", () => {
}); });
describe("the rejection message", () => { describe("the rejection message", () => {
// README, Language & Labeling: error messages are full sentences. // README, Wording: error messages are full sentences.
test("is a full sentence naming the constraint", () => { test("is a full sentence naming the constraint", () => {
expect(DUST_THRESHOLD_MESSAGE).toMatch(/^[A-Z].*\.$/); expect(DUST_THRESHOLD_MESSAGE).toMatch(/^[A-Z].*\.$/);
expect(DUST_THRESHOLD_MESSAGE).toContain("whole number of gwei"); expect(DUST_THRESHOLD_MESSAGE).toContain("whole number of gwei");
@@ -95,9 +95,9 @@ describe("the flash line the message is shown in", () => {
// NOT and CANNOT assert that the message fits inside it: jest runs on // NOT and CANNOT assert that the message fits inside it: jest runs on
// the node environment here, with no layout engine, so every rendered // the node environment here, with no layout engine, so every rendered
// height is zero. An earlier version of this block claimed to pin the // height is zero. An earlier version of this block claimed to pin the
// No Layout Shift policy with this regex, and it passed at any message // reserved space rule in README, Appearance, with this regex, and it
// length, including one that wrapped to two lines and pushed the // passed at any message length, including one that wrapped to two lines
// settings view down 12px. // and pushed the settings view down 12px.
// //
// The line cuts a message too long for it with an ellipsis (see // The line cuts a message too long for it with an ellipsis (see
// showFlash() in src/popup/views/helpers.js). The assertions that // showFlash() in src/popup/views/helpers.js). The assertions that
+5 -5
View File
@@ -1280,7 +1280,7 @@ test("the correct password reveals the full phrase, and nothing logs it (#161)",
); );
assert(!promptShown, "the password prompt is still shown after unlock"); assert(!promptShown, "the password prompt is still shown after unlock");
// Full Identifiers Policy: shown whole, and copyable. // README, ShowRecoveryPhrase: shown whole, and copyable.
const title = await env.page.getAttribute( const title = await env.page.getAttribute(
"#show-phrase-value", "#show-phrase-value",
"title", "title",
@@ -2255,7 +2255,7 @@ async function waitForFilledFlashLine(page) {
} }
} }
// README, No Layout Shift: the rejection message goes into #flash-msg, // README, Appearance: the rejection message goes into #flash-msg,
// whose min-h-[1.25rem] reserves exactly ONE line at text-xs, and which // whose min-h-[1.25rem] reserves exactly ONE line at text-xs, and which
// cuts a message too long for that line with an ellipsis rather than wrap // cuts a message too long for that line with an ellipsis rather than wrap
// it. This shows the real message and measures that nothing moves; the // it. This shows the real message and measures that nothing moves; the
@@ -2603,7 +2603,7 @@ function toHexWei(wei) {
} }
// A fee in wei as the confirmation screen writes it: truncated to four decimal // A fee in wei as the confirmation screen writes it: truncated to four decimal
// places (README.md, Display Consistency). Deliberately a second // places (README.md, Amounts). Deliberately a second
// implementation rather than an import of src/shared/amountDisplay.js: // implementation rather than an import of src/shared/amountDisplay.js:
// asserting against an independent rendering is stronger than asserting a // asserting against an independent rendering is stronger than asserting a
// function equals itself. The fixture's fees are above 0.0001 ETH, so the // function equals itself. The fixture's fees are above 0.0001 ETH, so the
@@ -3298,7 +3298,7 @@ async function goToTokenConfirm(env) {
amount: TOKEN_AMOUNT, amount: TOKEN_AMOUNT,
}); });
await waitForEstimate(env.page); await waitForEstimate(env.page);
// TOKEN_AMOUNT to four decimal places (README.md, Display Consistency). // TOKEN_AMOUNT to four decimal places (README.md, Amounts).
const shown = await shownAmount(env.page); const shown = await shownAmount(env.page);
assert( assert(
shown === "0.2500 " + STUB_TOKEN.symbol, shown === "0.2500 " + STUB_TOKEN.symbol,
@@ -3453,7 +3453,7 @@ test("a token that lies about decimals() at signing time broadcasts nothing (#30
// ------------------------- a send Sign & Send refuses moves nothing (#583) // ------------------------- a send Sign & Send refuses moves nothing (#583)
// //
// README, No Layout Shift. Sign & Send refuses a send on the confirmation // README, Appearance. Sign & Send refuses a send on the confirmation
// screen when the active network changed since review and when its gas limit // screen when the active network changed since review and when its gas limit
// or fee is over its bound, and each refusal's sentence sits in a box that held // or fee is over its bound, and each refusal's sentence sits in a box that held
// its space from the first paint. This drives the total fee refusal, the longer // its space from the first paint. This drives the total fee refusal, the longer
+2 -2
View File
@@ -165,9 +165,9 @@ describe("declared icons", () => {
}); });
}); });
// README, Installing on Firefox: the extension does not run in private windows.
// Firefox gives a page in a private window locks of its own, so that page's // Firefox gives a page in a private window locks of its own, so that page's
// save could land between the background's read and its write (README, // save could land between the background's read and its write.
// "Stored state and its version").
describe("private windows", () => { describe("private windows", () => {
test("a page in a firefox private window cannot read or write extension storage", () => { test("a page in a firefox private window cannot read or write extension storage", () => {
expect(readManifest("firefox").incognito).toBe("not_allowed"); expect(readManifest("firefox").incognito).toBe("not_allowed");
+1 -1
View File
@@ -5,7 +5,7 @@
// browser's own does for the one call the extension makes, // browser's own does for the one call the extension makes,
// `navigator.locks.request(name, callback)`. Every module a test file loads // `navigator.locks.request(name, callback)`. Every module a test file loads
// shares it, as the extension's background and its pages share the browser's // shares it, as the extension's background and its pages share the browser's
// (README, "Stored state and its version"). // (README, "Stored data").
// //
// Run before each test file by `setupFiles` in package.json. // Run before each test file by `setupFiles` in package.json.
+3 -3
View File
@@ -1,7 +1,7 @@
// A transaction's time is written by isoDate() and timeAgo() in // A transaction's time is written by isoDate() and timeAgo() in
// src/popup/views/helpers.js on every screen that shows one (README, Display // src/popup/views/helpers.js on every screen that shows one (README, Amounts;
// Consistency; https://git.eeqj.de/sneak/AutistMask/issues/168). AddressDetail // https://git.eeqj.de/sneak/AutistMask/issues/168). AddressDetail and
// and AddressToken used to define their own copies, so a fix to the shared pair // AddressToken used to define their own copies, so a fix to the shared pair
// would not have reached them. // would not have reached them.
// //
// The pair is replaced before the views are loaded, because a view takes it // The pair is replaced before the views are loaded, because a view takes it