src/popup/views/settings.js validates the dust-threshold input strictly and, on rejection, resyncs the field to the stored value without saying anything. The user types a value, the box silently changes to a different number, and nothing explains why.
It is the only validated input in that file that rejects without a message. The correct idiom is already in the same file — showFlash("Please enter an RPC URL.") at lines 198 and 243 — and the README Language & Labeling rule requires error messages to be full sentences.
Sharper now than before: the stricter parse landed in #179 newly rejects two inputs that previously worked. parseInt used to accept 1.5 as 1 and 100 gwei as 100; both are now rejected. So the inputs most likely to trigger the silent snap-back are exactly the ones a user had been getting away with.
Found by the independent review of #228, which flagged it rather than blocking since no documented rule is outright broken.
Also worth covering while in here, noted by the same review: Number(raw) now accepts notation the old parseInt(raw, 10) did not — 0x10 becomes 16 and 1e3 becomes 1000. Harmless, but wider than intended and undocumented.
Definition of done
Rejected input shows a full-sentence message explaining what is required, alongside the existing resync.
The message names the constraint (a whole number of gwei, zero or greater) rather than only reporting failure.
Hex and exponent notation are either accepted deliberately and documented, or rejected with the same message.
No layout shift when the message appears, per the README policy.
TODO.md updated in the same commit.
make check passes.
`src/popup/views/settings.js` validates the dust-threshold input strictly and, on rejection, resyncs the field to the stored value without saying anything. The user types a value, the box silently changes to a different number, and nothing explains why.
It is the only validated input in that file that rejects without a message. The correct idiom is already in the same file — `showFlash("Please enter an RPC URL.")` at lines 198 and 243 — and the README Language & Labeling rule requires error messages to be full sentences.
Sharper now than before: the stricter parse landed in https://git.eeqj.de/sneak/AutistMask/issues/179 newly rejects two inputs that previously worked. `parseInt` used to accept `1.5` as 1 and `100 gwei` as 100; both are now rejected. So the inputs most likely to trigger the silent snap-back are exactly the ones a user had been getting away with.
Found by the independent review of https://git.eeqj.de/sneak/AutistMask/pulls/228, which flagged it rather than blocking since no documented rule is outright broken.
Also worth covering while in here, noted by the same review: `Number(raw)` now accepts notation the old `parseInt(raw, 10)` did not — `0x10` becomes 16 and `1e3` becomes 1000. Harmless, but wider than intended and undocumented.
## Definition of done
- [ ] Rejected input shows a full-sentence message explaining what is required, alongside the existing resync.
- [ ] The message names the constraint (a whole number of gwei, zero or greater) rather than only reporting failure.
- [ ] Hex and exponent notation are either accepted deliberately and documented, or rejected with the same message.
- [ ] No layout shift when the message appears, per the README policy.
- [ ] `TODO.md` updated in the same commit.
- [ ] `make check` passes.
clawbot
added this to the 1.0.0 milestone 2026-08-11 15:16:57 +02:00
Move the parse into a pure popup module (src/popup/dustThreshold.js) alongside restorableViews.js, exporting the parse and the single rejection message, so both are unit-testable without a DOM.
Reject hex and exponent notation rather than accept it: the field is inputmode="numeric" and the message promises "a whole number of gwei", so accepting 0x10 as 16 or 1e3 as 1000 would be a second silent transformation of what the user typed — the same surprise this issue is about. Accepted input is plain decimal digits only; everything else ("", -1, 1.5, 100 gwei, 0x10, 1e3) is rejected with one message.
On rejection, showFlash("Please enter the dust threshold as a whole number of gwei, zero or greater.") plus the existing resync. No layout shift: #flash-msg is an always-present line with min-h-[1.25rem].
README: document the accepted format in the Settings screen element list.
Plan:
- Move the parse into a pure popup module (`src/popup/dustThreshold.js`) alongside `restorableViews.js`, exporting the parse and the single rejection message, so both are unit-testable without a DOM.
- Reject hex and exponent notation rather than accept it: the field is `inputmode="numeric"` and the message promises "a whole number of gwei", so accepting `0x10` as 16 or `1e3` as 1000 would be a second silent transformation of what the user typed — the same surprise this issue is about. Accepted input is plain decimal digits only; everything else (`""`, `-1`, `1.5`, `100 gwei`, `0x10`, `1e3`) is rejected with one message.
- On rejection, `showFlash("Please enter the dust threshold as a whole number of gwei, zero or greater.")` plus the existing resync. No layout shift: `#flash-msg` is an always-present line with `min-h-[1.25rem]`.
- README: document the accepted format in the Settings screen element list.
Rejection now flashes Please enter the dust threshold as a whole number of gwei, zero or greater. and the resync stays. Hex and exponent notation are rejected with that same message rather than accepted: they would store a number the user never typed, which is the silent substitution this issue is about. Accepted input is plain decimal digits, zero or greater. Documented in the Settings element list in README.md.
Verified: make check green on the host and uncached in the container (script/cibuild, docker build --no-cache on this image only) — 15 suites, 379 tests, prettier clean. tests/dustThreshold.test.js adds 18 tests; reverting only the showFlash call fails exactly the two message tests, so they are not vacuous.
Built in https://git.eeqj.de/sneak/AutistMask/pulls/243.
Rejection now flashes `Please enter the dust threshold as a whole number of gwei, zero or greater.` and the resync stays. Hex and exponent notation are rejected with that same message rather than accepted: they would store a number the user never typed, which is the silent substitution this issue is about. Accepted input is plain decimal digits, zero or greater. Documented in the Settings element list in `README.md`.
Verified: `make check` green on the host and uncached in the container (`script/cibuild`, `docker build --no-cache` on this image only) — 15 suites, 379 tests, prettier clean. `tests/dustThreshold.test.js` adds 18 tests; reverting only the `showFlash` call fails exactly the two message tests, so they are not vacuous.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
src/popup/views/settings.jsvalidates the dust-threshold input strictly and, on rejection, resyncs the field to the stored value without saying anything. The user types a value, the box silently changes to a different number, and nothing explains why.It is the only validated input in that file that rejects without a message. The correct idiom is already in the same file —
showFlash("Please enter an RPC URL.")at lines 198 and 243 — and the README Language & Labeling rule requires error messages to be full sentences.Sharper now than before: the stricter parse landed in #179 newly rejects two inputs that previously worked.
parseIntused to accept1.5as 1 and100 gweias 100; both are now rejected. So the inputs most likely to trigger the silent snap-back are exactly the ones a user had been getting away with.Found by the independent review of #228, which flagged it rather than blocking since no documented rule is outright broken.
Also worth covering while in here, noted by the same review:
Number(raw)now accepts notation the oldparseInt(raw, 10)did not —0x10becomes 16 and1e3becomes 1000. Harmless, but wider than intended and undocumented.Definition of done
TODO.mdupdated in the same commit.make checkpasses.Plan:
src/popup/dustThreshold.js) alongsiderestorableViews.js, exporting the parse and the single rejection message, so both are unit-testable without a DOM.inputmode="numeric"and the message promises "a whole number of gwei", so accepting0x10as 16 or1e3as 1000 would be a second silent transformation of what the user typed — the same surprise this issue is about. Accepted input is plain decimal digits only; everything else ("",-1,1.5,100 gwei,0x10,1e3) is rejected with one message.showFlash("Please enter the dust threshold as a whole number of gwei, zero or greater.")plus the existing resync. No layout shift:#flash-msgis an always-present line withmin-h-[1.25rem].Built in #243.
Rejection now flashes
Please enter the dust threshold as a whole number of gwei, zero or greater.and the resync stays. Hex and exponent notation are rejected with that same message rather than accepted: they would store a number the user never typed, which is the silent substitution this issue is about. Accepted input is plain decimal digits, zero or greater. Documented in the Settings element list inREADME.md.Verified:
make checkgreen on the host and uncached in the container (script/cibuild,docker build --no-cacheon this image only) — 15 suites, 379 tests, prettier clean.tests/dustThreshold.test.jsadds 18 tests; reverting only theshowFlashcall fails exactly the two message tests, so they are not vacuous.