diff --git a/.gitea/workflows/e2e.yml b/.gitea/workflows/e2e.yml index 30fc7d3..bec0582 100644 --- a/.gitea/workflows/e2e.yml +++ b/.gitea/workflows/e2e.yml @@ -19,13 +19,10 @@ on: [push] # by the host daemon and mounts an empty directory, and the runner image's # node is too old to install this repo's dependencies. # -# These jobs REPORT, they do not gate. Whether a check blocks a merge is -# 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 -# 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 -# README.md names them. A gate that fails at random teaches people to merge -# past red. +# These jobs REPORT, they do not gate. No branch requires these two jobs, so +# a failure is a red mark a reviewer has to account for. The Chrome suite has +# failed under load before, and no such report is open. A gate that fails at +# random teaches people to merge past red. # # 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 @@ -36,8 +33,7 @@ jobs: e2e-chrome: runs-on: ubuntu-latest # Bounds the image build, a cold cache included, and the three Chrome - # programs, so a hung browser frees the shared runner. README.md - # "In CI" has the measured times. + # programs, so a hung browser frees the shared runner. timeout-minutes: 40 steps: # actions/checkout v4.2.2, 2026-02-22 @@ -47,8 +43,7 @@ jobs: e2e-firefox: runs-on: ubuntu-latest # Bounds the image build, a cold cache included, and both Firefox - # programs, so a hung browser frees the shared runner. README.md - # "In CI" has the measured times. + # programs, so a hung browser frees the shared runner. timeout-minutes: 15 steps: # actions/checkout v4.2.2, 2026-02-22 diff --git a/script/lib/package.js b/script/lib/package.js index 3db9793..f745968 100644 --- a/script/lib/package.js +++ b/script/lib/package.js @@ -1,7 +1,7 @@ // Turn a verified dist/ into the two distributable archives. // // 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 // write into dist/: it reads the emitted tree and writes release/. // diff --git a/script/test-e2e-firefox b/script/test-e2e-firefox index 884a710..7dd655c 100755 --- a/script/test-e2e-firefox +++ b/script/test-e2e-firefox @@ -60,12 +60,12 @@ main() { echo "Running the Firefox e2e suite..." # --shm-size=1g: Firefox needs more than the default 64MB /dev/shm. - # --network none: the suite stubs nothing, so this is what keeps the - # run offline and deterministic. The extension swallows its own - # fetch failures, so the popup flows work unchanged; see the - # network note in README.md. Weaker than the Chrome suite's - # fixture interception, and honestly so — it proves no request - # escaped, but it cannot report which ones were attempted. + # --network none: the container has no network, and the suite reaches + # only the test page and stub node on loopback (README.md, Browser + # tests). The extension swallows its own fetch failures, so the popup + # flows work unchanged. Weaker than the Chrome suite's fixture + # interception, and honestly so — it proves no request escaped, but it + # cannot report which ones were attempted. # HOME=/tmp: the image's root home is not a reliable place for the # browser profile. # diff --git a/src/background/state.js b/src/background/state.js index 3710d37..2288201 100644 --- a/src/background/state.js +++ b/src/background/state.js @@ -18,11 +18,11 @@ // updateState(fn) read fresh, apply fn to that fresh record, write it // back — all holding the lock every write of the stored // record takes, so no other write lands in between, by -// the background or by any page (README, "Stored state -// and its version"). The read is one storage round trip -// ahead of the write rather than a page lifetime ahead of -// it (which is what made the popup's saveState() need a -// per-field merge against a baseline at all). +// the background or by any page (README, "Stored data"). +// The read is one storage round trip ahead of the write +// rather than a page lifetime ahead of it (which is what +// made the popup's saveState() need a per-field merge +// against a baseline at all). // // A handler that must both read and write therefore does its network work // 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 // 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 -// 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 // anything it awaits: the inner turn waits for the lock the outer one holds, diff --git a/src/popup/views/approval.js b/src/popup/views/approval.js index 7423b82..0907895 100644 --- a/src/popup/views/approval.js +++ b/src/popup/views/approval.js @@ -319,12 +319,12 @@ function showTxApproval(details) { ); } // 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 - // screens say the amount could not be decoded, with no currency after - // it, rather than show the transaction's ETH value as the amount. A - // swap paid in native ETH keeps that value: it is the ETH the swap - // pays (for an exact-out swap, the most it pays), not a figure that - // had to be read from the calldata. + // action could not be read or a V4 exact-out swap. The wait, success + // and error screens say the amount could not be decoded, with no + // currency after it, rather than show the transaction's ETH value as + // the amount. A swap paid in native ETH keeps that value: it is the + // ETH the swap pays (for an exact-out swap, the most it pays), not a + // figure that had to be read from the calldata. const tokenIn = decoded.details.find((d) => d.label === "Token In"); if ( tokenIn && diff --git a/src/popup/views/deleteAddress.js b/src/popup/views/deleteAddress.js index 2fba841..5614957 100644 --- a/src/popup/views/deleteAddress.js +++ b/src/popup/views/deleteAddress.js @@ -83,10 +83,11 @@ function recoveryPathText(wallet) { // 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 // 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 -// when every holding here is one with no price, since "$0.00" directly under -// "This address holds a balance." is a contradiction. A token holding below -// 0.000001 does not count, as the lines below leave it out. +// total line at all on testnet, before the first price fetch or while no +// refresh has read the ETH balance, and no figure when every holding here is +// one with no price, since "$0.00" directly under "This address holds a +// 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 // sentence says "may hold". diff --git a/src/popup/views/helpers.js b/src/popup/views/helpers.js index 61801fb..ceb40db 100644 --- a/src/popup/views/helpers.js +++ b/src/popup/views/helpers.js @@ -272,7 +272,7 @@ function clearFlash() { } // 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 // message is also put in the line's title. Write messages to fit, at most 50 // 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 "…". // Safety: refuses to truncate more than 10 characters, which is the maximum -// that still prevents address spoofing attacks (see Display Consistency in -// README). Callers that need to display less should use a different UI -// approach rather than silently making addresses insecure. +// that still prevents address spoofing attacks (see Addresses in README). +// Callers that need to display less should use a different UI approach rather +// than silently making addresses insecure. function truncateMiddle(str, maxLen) { if (str.length <= maxLen) return str; 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 }); } -// A transaction's time as every screen shows it (README, Display -// Consistency): the ISO datetime, in UTC when the UTC Timestamps setting is -// on, and the relative age. Views import these two; they keep no copies. +// A transaction's time as every screen shows it (README, Amounts): the ISO +// datetime, in UTC when the UTC Timestamps setting is on, and the relative age. +// Views import these two; they keep no copies. function isoDate(timestamp) { const d = new Date(timestamp * 1000); const pad = (n) => String(n).padStart(2, "0"); diff --git a/src/popup/views/settings.js b/src/popup/views/settings.js index 653e713..acfa9c4 100644 --- a/src/popup/views/settings.js +++ b/src/popup/views/settings.js @@ -265,7 +265,7 @@ async function renderSiteLists() { // 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 // 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 // a popup closed straight after diff --git a/src/shared/amountDisplay.js b/src/shared/amountDisplay.js index ee2c441..5232574 100644 --- a/src/shared/amountDisplay.js +++ b/src/shared/amountDisplay.js @@ -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 // copy of the truncation — the history and balance lists // (`src/shared/transactions.js`), the approval screen's ERC-20 amount line diff --git a/src/shared/browserApi.js b/src/shared/browserApi.js index 1fe990f..15b78b4 100644 --- a/src/shared/browserApi.js +++ b/src/shared/browserApi.js @@ -240,10 +240,9 @@ function onStorageChanged(listener) { /** * Run `fn` while holding the browser's lock called `name` * (`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 - * window (README, "Stored state and its version"), so while one of them holds - * `name` another asking for it waits, in the order they asked. The lock is let - * go when `fn` settles, whether it resolved or rejected. + * and its background share these locks, so while one of them holds `name` + * another asking for it waits, in the order they asked. The lock is let go when + * `fn` settles, whether it resolved or rejected. * * Every write of the stored record holds the lock named after its key, * "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 // until the browser closes. It survives the background worker being stopped -// and started again, which module-level variables do not (README, Background -// scheduling). The background keeps the connections allowed without +// and started again, which module-level variables do not (README, The +// background worker). The background keeps the connections allowed without // "Remember" there; see src/background/index.js. Firefox has it from version // 115, the minimum manifest/firefox.json declares. // diff --git a/tests/approvalDisplayFloor.test.js b/tests/approvalDisplayFloor.test.js index 044e9da..0fb67f2 100644 --- a/tests/approvalDisplayFloor.test.js +++ b/tests/approvalDisplayFloor.test.js @@ -1,11 +1,11 @@ // The floor of the approval screen's amount line. // // Amounts are truncated to four decimal places for scannability (README.md, -// Display Consistency). With the token's true scale resolved, that truncation -// can still take a real amount below the floor and print it as `0.0000`: one -// base unit of an 18-decimal token, or a few hundred of an 8-decimal one. On -// the one screen whose job is to state what is being authorized, a nonzero -// transfer or allowance then reads as nothing. +// Amounts). With the token's true scale resolved, that truncation can still +// take a real amount below the floor and print it as `0.0000`: one base unit of +// an 18-decimal token, or a few hundred of an 8-decimal one. On the one screen +// whose job is to state what is being authorized, a nonzero transfer or +// allowance then reads as nothing. // // The invariant asserted here is narrow: a nonzero amount never renders as // zero. The four-decimal rule itself is unchanged, and the string the diff --git a/tests/dustThreshold.test.js b/tests/dustThreshold.test.js index 0a6a0e6..60eb040 100644 --- a/tests/dustThreshold.test.js +++ b/tests/dustThreshold.test.js @@ -74,7 +74,7 @@ describe("parsing the dust threshold", () => { }); 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", () => { expect(DUST_THRESHOLD_MESSAGE).toMatch(/^[A-Z].*\.$/); 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 // the node environment here, with no layout engine, so every rendered // 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 - // length, including one that wrapped to two lines and pushed the - // settings view down 12px. + // reserved space rule in README, Appearance, with this regex, and it + // passed at any message length, including one that wrapped to two lines + // and pushed the settings view down 12px. // // The line cuts a message too long for it with an ellipsis (see // showFlash() in src/popup/views/helpers.js). The assertions that diff --git a/tests/e2e/run.js b/tests/e2e/run.js index f8f5fa6..eaab238 100644 --- a/tests/e2e/run.js +++ b/tests/e2e/run.js @@ -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"); - // Full Identifiers Policy: shown whole, and copyable. + // README, ShowRecoveryPhrase: shown whole, and copyable. const title = await env.page.getAttribute( "#show-phrase-value", "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 // 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 @@ -2603,7 +2603,7 @@ function toHexWei(wei) { } // 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: // asserting against an independent rendering is stronger than asserting a // 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, }); 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); assert( 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) // -// 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 // 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 @@ -5770,8 +5770,8 @@ async function main() { // There is no fourth: once the context is closed nothing can record, // because the route handler and the console listeners died with it. // Traffic that a test defers past the trailing drain is therefore - // never observed at all — a real limit of this design, stated in the - // README, and not one any post-teardown hook could close. + // never observed at all — a real limit of this design, and not one + // any post-teardown hook could close. // // Two green-but-vacuous runs on this harness were the same shape: a // record falling outside somebody's window and being dropped. First diff --git a/tests/manifest.test.js b/tests/manifest.test.js index f00f55d..dc526ed 100644 --- a/tests/manifest.test.js +++ b/tests/manifest.test.js @@ -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 -// save could land between the background's read and its write (README, -// "Stored state and its version"). +// save could land between the background's read and its write. describe("private windows", () => { test("a page in a firefox private window cannot read or write extension storage", () => { expect(readManifest("firefox").incognito).toBe("not_allowed"); diff --git a/tests/support/navigatorLocks.js b/tests/support/navigatorLocks.js index 4722749..5c7b503 100644 --- a/tests/support/navigatorLocks.js +++ b/tests/support/navigatorLocks.js @@ -5,7 +5,7 @@ // browser's own does for the one call the extension makes, // `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 -// (README, "Stored state and its version"). +// (README, "Stored data"). // // Run before each test file by `setupFiles` in package.json. diff --git a/tests/timestampDisplay.test.js b/tests/timestampDisplay.test.js index fc4a5b0..201134e 100644 --- a/tests/timestampDisplay.test.js +++ b/tests/timestampDisplay.test.js @@ -1,7 +1,7 @@ // A transaction's time is written by isoDate() and timeAgo() in -// src/popup/views/helpers.js on every screen that shows one (README, Display -// Consistency; https://git.eeqj.de/sneak/AutistMask/issues/168). AddressDetail -// and AddressToken used to define their own copies, so a fix to the shared pair +// src/popup/views/helpers.js on every screen that shows one (README, Amounts; +// https://git.eeqj.de/sneak/AutistMask/issues/168). AddressDetail and +// AddressToken used to define their own copies, so a fix to the shared pair // would not have reached them. // // The pair is replaced before the views are loaded, because a view takes it diff --git a/tests/transactions.test.js b/tests/transactions.test.js index 0ce34d4..87a2839 100644 --- a/tests/transactions.test.js +++ b/tests/transactions.test.js @@ -1,7 +1,7 @@ // Tests for the address-poisoning defense in src/shared/transactions.js. // -// README.md:730-814 describes four filters as a core security property of -// the wallet: known token symbol verification, the low-holder threshold, the +// README, Scam protection, describes four filters as a core security property +// of the wallet: known token symbol verification, the low-holder threshold, the // fraud contract blocklist, and the dust threshold. A regression in any of // them does not crash — it silently stops filtering, and poisoned look-alike // addresses reappear in the user's transaction history. These tests pin the @@ -46,11 +46,12 @@ const { debugFetch } = require("../src/shared/log"); const { state } = require("../src/shared/state"); // --------------------------------------------------------------------------- -// Addresses and hashes from the two attacks documented in README.md:730-814. +// Addresses and hashes from two real attacks. README, Scam protection, has the +// filters that stop them. // --------------------------------------------------------------------------- -// The fake "Ethereum" token with symbol "ETH" and zero holders -// (README.md:735-750). +// The fake "Ethereum" token with symbol "ETH" and zero holders. See "Known +// symbol spoofing" and "Low-holder tokens" in README, Scam protection. const FAKE_ETH_CONTRACT = "0xd05339f9ea5ab9d9f03b9d57f671d2abd1f55c82"; // The fraudulent Transfer event it emitted. const FAKE_ETH_TRANSFER_HASH = @@ -62,7 +63,8 @@ const LEGIT_RECIPIENT = "0xc3c693ae04bad5f13c45885c1e85a9557798f37e"; // The scam address the fake token transfer pointed at ("0xC3C0" vs "0xC3c6"). const TOKEN_SCAM_LOOKALIKE = "0xc3c0aea127c575b9ffd03bf11c6a878e8979c37f"; -// The second wave: a real 1 gwei native transfer (README.md:800-808). +// The second wave: a real 1 gwei native transfer. See "Dust transactions" in +// README, Scam protection. const DUST_TX_HASH = "0x2708ebddfb9b5fa3f7a89d3ea398ef9fd8771b83ed861ecb7c21cd55d18edc74"; const DUST_SENDER_LOOKALIKE = "0xc3c6b3b4402bd78a9582ab6b00e747769344f37e"; @@ -79,7 +81,7 @@ const NOVEL_SPAM_SYMBOL = "SPAMTKN"; // An ordinary counterparty for legitimate-transaction fixtures. const ORDINARY_PEER = "0x5aa0f9f1e0a1d0e0e5c1e7ce3b7dbbe9c19f0a11"; -// The documented default settings (README.md:810-814, state.js:24-27). +// The documented default settings (README, Settings; state.js:24-27). const DEFAULT_FILTERS = { hideSpoofedSymbols: true, hideLowHolderTokens: true, @@ -220,7 +222,7 @@ describe("token list assumptions the fixtures rely on", () => { }); }); -describe("the real attacks documented in README.md:730-814", () => { +describe("the real attacks", () => { test('the fake "Ethereum"/"ETH" token transfer is filtered by default', () => { const attack = fakeEthTokenTransfer(); const result = filterTransactions([attack], filters());