# Workflow - `git pull` `next` and cut a branch from it — one branch per issue, named `issue--`. Never branch from `main`. - Do the work as one commit whose title ends with ` (closes #N)`, with the `TODO.md` update in that same commit. - Move Next Step to the top of Completed Steps; move the top item of Future Steps into Next Step. - Run `make fmt`, then `make check`. A feature branch may be red; `next` and `main` may not. - Rebase onto current `next` immediately before pushing — other branches land on `next` continuously — and re-run `make check` after resolving, because a clean textual merge can still break the build. - Push the branch and open one PR per issue with base `next`. Never base `main`. - An independent reviewer who did not write the change gates the merge. On a passed review the PR is squash-merged into `next`. - `next` is the branch for the next milestone. It is kept green and mergeable to `main` at any moment, without notice. - `main` receives exactly one PR per milestone, from `next`. Releases are tagged from `main`. # Status pre-1.0, working towards the 1.0.0 milestone. Tagged v0.1.0 on 2026-02-27. The milestone is in flight on `next`; its `next` -> `main` PR is [#190](https://git.eeqj.de/sneak/AutistMask/pulls/190). `make check` verified green on `next` at `e9fa8be` on 2026-08-10, and `make build` produces `dist/chrome/` and `dist/firefox/` with every bundle verified to have `DEBUG` compiled off. The backlog lives on the [Gitea tracker](https://git.eeqj.de/sneak/AutistMask/issues), which is authoritative; this file does not duplicate it. Full policy file set present. Real-browser end-to-end suites (`make test-e2e` for Chrome, `make test-e2e-firefox` for Firefox) sit alongside `make check`, which now does static analysis as well as formatting, and `.gitea/workflows/e2e.yml` runs both of them on every push. # Next Step Pre-1.0 security review of the extension (key handling, DEBUG mode policy, RPC input validation) before any 1.0rc tag. Individual filed issues are parts of it, but the review is broader than any of them. # Completed Steps - 2026-08-20: The dApp approval screen no longer shows a token transfer it cannot scale as `0.0000` ([#306](https://git.eeqj.de/sneak/AutistMask/issues/306)). `decodeCalldata` read decimals from the 512-entry bundled token list alone and fell back to 18, so every token outside it — most of them, including anything the user added by contract address — was displayed at the wrong scale: a `transfer` of 5,000 units of a 6-decimal token read as `0.0000`, and a user who reads zero confirms the drain. The new `src/shared/approvalAmount.js` resolves the scale from the bundled list, then `state.trackedTokens`, then the decimals the block explorer already reported in `addr.tokenBalances`, and refuses one the explorer's own entries disagree about. Where no source knows it, the amount line is not formatted at all: it shows the base-unit integer and states that the scale is unknown, for `approve` as well as `transfer`. An unbounded allowance still reads `Unlimited`, which needs no scale. - 2026-08-20: A web page can no longer switch the wallet's chain, and switching no longer destroys the user's endpoints ([#308](https://git.eeqj.de/sneak/AutistMask/issues/308)). `wallet_switchEthereumChain` was answered for any origin at all, with no connection check and no prompt: any page could clear the `[TESTNET]` banner under a user who believed they were on Sepolia. It now takes the same `allowedSites`/`connectedSites` gate the signing methods take, ahead of the same-chain and unsupported-chain answers, and refuses an unconnected origin with `4100`. The switch itself also overwrote `state.rpcUrl` and `state.blockscoutUrl` with the network defaults, so a user running their own node lost that url permanently and silently to a public endpoint that then sees every address they hold. Endpoints are now remembered per network in `state.networkEndpoints`, snapshotted from the network being left and restored for the network being entered; `state.rpcUrl` stays the live value for the active network, so no reader changed. A profile written before the map existed has its stored pair adopted for the network it was stored under, and loses nothing. The handler now loads state before it switches ([#316](https://git.eeqj.de/sneak/AutistMask/issues/316)): the service worker populates nothing at module scope, so a worker revived by the page's own message held `DEFAULT_STATE`, and the switch persisted every field of it — wiping every wallet, every site approval and every tracked token from storage along with the endpoint. - 2026-08-20: The wallet's own ERC-20 send signs the amount it displayed ([#305](https://git.eeqj.de/sneak/AutistMask/issues/305)). The confirmation screen renders from the block explorer's cached decimals; the transfer was encoded from `decimals()` read off the contract at signing time, and nothing compared the two, so a token whose on-chain scale disagreed — an upgradeable or proxy token, a stale explorer entry, a compromised Blockscout — signed an amount that was never on screen, off by a power of ten per decimal place of disagreement. The scale is now carried forward on the pending transaction from the same balance entry the screen's amount, balance and symbol come from, and the contract's answer is read at signing time only to be compared with it: a disagreement is a refusal naming both numbers, never a preference for either (`src/shared/transferAmount.js`, the `confirmTx` counterpart to `approvalVerify.js`). The gas estimate encodes from the same carried value and no longer reads `decimals()` at all. Nothing in the e2e suite had ever clicked `#btn-confirm-send`, which is how this shipped: the popup's own Send → ConfirmTx → Sign & Send → WaitTx path now runs end to end to a broadcast, with the `transfer()` amount decoded out of the raw signed bytes and asserted against what the screen displayed, and a companion case where the contract starts answering a different scale after the screen was built and nothing reaches the RPC. Reverting only the signing-side comparison turns that second case red and leaves the other 53 green. - 2026-08-17: The Settings screen is driven in a browser, and every element id the popup looks up is checked statically. Nothing exercised Settings in the e2e suite, and jest runs with no DOM, so the densest run of `$("...")` lookups in the codebase was unverified at runtime. Seven new cases in `tests/e2e/run.js` reach Settings, assert the About well and the wallet list were actually written, assert the four Token Spam Protection checkboxes are real checkboxes defaulted on, and assert the theme and network selectors offer the choices `src/shared/networks.js` and `index.html` define. The selectors are then driven to `dark` and `sepolia` — neither is the first `