Neither manifest declared an icons block, so both browsers drew the generic
puzzle piece. Both now declare 16/32/48/128 as icons/icon<size>.png, and the
four PNGs live at icons/ in the tree.
What changed
manifest/chrome.json, manifest/firefox.json: icons at 16/32/48/128.
icons/icon{16,32,48,128}.png: new artwork (below).
build.js: copies each browser's icons into its own dist directory, reading the sizes out of the manifest that ships next to them rather than
from a second list here. A size a manifest declares and icons/ does not hold
fails the build with the missing path; a value that is not a plain file under icons/ is refused before it is joined into any path. The icons are recorded
as emitted like everything else, so they are in the build receipt and script/verify-build covers them.
tests/manifest.test.js: asserts the declaration in both manifests, that both
declare the same set, and that each referenced file is a PNG whose IHDR states
the size the entry claims — a declaration on its own would still permit a
reference to a file that is absent or not an image.
tests/packaging.test.js: pins the case of a manifest naming an icon the
archive does not contain. script/lib/package.js needed no change — its MANIFEST_PATH_RE already matches .png — but that coverage was incidental,
and this is the file it protects.
Icons are inside the built artifacts, verified by unpacking them
make package, then unzip on the artifacts themselves (not the repo's own zip
reader):
A missing icon fails the build — demonstrated, then restored
Removing the file the manifest names:
$ mv icons/icon48.png /tmp/… && make build ; echo $?
Build failed: manifest/chrome.json declares icons/icon48.png, which is not in this tree
discard-dist-on-failure: the release build failed, so dist/ WAS REMOVED …
2
And the packager's own reference-resolver, reached by making build.js
temporarily skip one copy so dist/ was complete except for that icon:
$ make package ; echo $?
package: the chrome archive would not be self-contained: it is told to load icons/icon48.png, which is not in it
2
Both were reverted immediately; icons/icon48.png is back at 8d2b688321249b4100e9d472a52477593074213a222d6dab00d6229b6cc740dc, and the
temporary build.js line is not in the diff (grep TEMPORARY build.js finds
nothing).
The artwork
Original, and produced here rather than obtained: a throwaway Python program
(standard library only — zlib + struct, no PIL, no network) rasterised
geometry defined in that program into RGBA PNGs at the four sizes. The mark is a
flat dark-navy rounded square (#101A2E) carrying a teal (#35E0C2) triangular
"A": one outer triangle minus a triangular counter, drawn from six coordinates,
antialiased by 8x8 supersampling. Nothing was traced, copied, downloaded or
derived from any existing mark, project or trademark, and no font is involved.
One shape in two flat colours is also what a 16px toolbar slot can actually
carry.
The generator itself is deliberately not committed: it is a Python file in a
Node repo and outside this unit's scope. Say the word and it can be filed as a
follow-up so the artwork is regenerable in-tree.
Verification
All run through make / script/ only; lint ran in the pinned container.
make check: green — 56 suites, 1023 tests; test-verify-build 46 cases; check-censored 186 tracked files; eslint + prettier in the lint image.
make build: green — 23 emitted files verified against the receipt,
4 bundles autistmask-build-debug=off, check-censored 23 files under dist/.
make package: green — 11 members per archive.
make test-e2e (Chrome): green — 55/55 and 5/5.
make test-e2e-firefox: green — 8/8 and 7/7; the second suite installs the
packaged XPI, so the artifact carrying these icons was installed in real
Firefox.
Rebased onto next at 75a5fa9 and re-ran make check and make build after
the rebase. docker ps -a is empty of anything from this run; every e2e
container ran --rm and no image or cache was pruned.
Closes [#371](https://git.eeqj.de/sneak/AutistMask/issues/371).
Neither manifest declared an `icons` block, so both browsers drew the generic
puzzle piece. Both now declare 16/32/48/128 as `icons/icon<size>.png`, and the
four PNGs live at `icons/` in the tree.
## What changed
- `manifest/chrome.json`, `manifest/firefox.json`: `icons` at 16/32/48/128.
- `icons/icon{16,32,48,128}.png`: new artwork (below).
- `build.js`: copies each browser's icons into its own dist directory,
**reading the sizes out of the manifest that ships next to them** rather than
from a second list here. A size a manifest declares and `icons/` does not hold
fails the build with the missing path; a value that is not a plain file under
`icons/` is refused before it is joined into any path. The icons are recorded
as emitted like everything else, so they are in the build receipt and
`script/verify-build` covers them.
- `tests/manifest.test.js`: asserts the declaration in both manifests, that both
declare the same set, and that each referenced file is a PNG whose IHDR states
the size the entry claims — a declaration on its own would still permit a
reference to a file that is absent or not an image.
- `tests/packaging.test.js`: pins the case of a manifest naming an icon the
archive does not contain. `script/lib/package.js` needed no change — its
`MANIFEST_PATH_RE` already matches `.png` — but that coverage was incidental,
and this is the file it protects.
## Icons are inside the built artifacts, verified by unpacking them
`make package`, then `unzip` on the artifacts themselves (not the repo's own zip
reader):
```
$ unzip -l release/autistmask-chrome-0.1.0.zip
1875 icons/icon128.png
292 icons/icon16.png
534 icons/icon32.png
725 icons/icon48.png
1735 manifest.json
2297222 src/background/index.js
2829 src/content/index.js
2914 src/content/inpage.js
82069 src/popup/index.html
2091897 src/popup/index.js
15049 src/popup/styles.css
11 files
$ unzip -l release/autistmask-firefox-0.1.0.xpi
1875 icons/icon128.png
292 icons/icon16.png
534 icons/icon32.png
725 icons/icon48.png
1121 manifest.json
2297222 src/background/index.js
2829 src/content/index.js
2914 src/content/inpage.js
82069 src/popup/index.html
2091897 src/popup/index.js
15049 src/popup/styles.css
11 files
```
Unpacked and inspected, both targets:
```
unpack/chrome/icons/icon16.png: PNG image data, 16 x 16, 8-bit/color RGBA
unpack/chrome/icons/icon32.png: PNG image data, 32 x 32, 8-bit/color RGBA
unpack/chrome/icons/icon48.png: PNG image data, 48 x 48, 8-bit/color RGBA
unpack/chrome/icons/icon128.png: PNG image data, 128 x 128, 8-bit/color RGBA
unpack/firefox/icons/*.png: same four, same sizes
chrome manifest.json in the archive: {'16': 'icons/icon16.png', '32': ..., '48': ..., '128': ...}
firefox manifest.json in the archive: identical
```
Byte-identical to the tree (sha256 of the unpacked chrome icons vs `icons/`):
```
7cde2461656591e48c3d4167371fb84d3b6a18b16e5d7b9d63c74fb242c33aa2 icon128.png
8d7ad8e2b9fad9455a5783722caa70b1d87bb4aa36bc29888b46c925ae6c6eb5 icon16.png
7217fad521d816998423c5aa792e8e4cc519587022343cb8abfdfbd4a72f0de0 icon32.png
8d2b688321249b4100e9d472a52477593074213a222d6dab00d6229b6cc740dc icon48.png
```
## A missing icon fails the build — demonstrated, then restored
Removing the file the manifest names:
```
$ mv icons/icon48.png /tmp/… && make build ; echo $?
Build failed: manifest/chrome.json declares icons/icon48.png, which is not in this tree
discard-dist-on-failure: the release build failed, so dist/ WAS REMOVED …
2
```
And the packager's own reference-resolver, reached by making `build.js`
temporarily skip one copy so `dist/` was complete except for that icon:
```
$ make package ; echo $?
package: the chrome archive would not be self-contained: it is told to load icons/icon48.png, which is not in it
2
```
Both were reverted immediately; `icons/icon48.png` is back at
`8d2b688321249b4100e9d472a52477593074213a222d6dab00d6229b6cc740dc`, and the
temporary `build.js` line is not in the diff (`grep TEMPORARY build.js` finds
nothing).
## The artwork
Original, and produced here rather than obtained: a throwaway Python program
(standard library only — `zlib` + `struct`, no PIL, no network) rasterised
geometry defined in that program into RGBA PNGs at the four sizes. The mark is a
flat dark-navy rounded square (`#101A2E`) carrying a teal (`#35E0C2`) triangular
"A": one outer triangle minus a triangular counter, drawn from six coordinates,
antialiased by 8x8 supersampling. Nothing was traced, copied, downloaded or
derived from any existing mark, project or trademark, and no font is involved.
One shape in two flat colours is also what a 16px toolbar slot can actually
carry.
The generator itself is deliberately not committed: it is a Python file in a
Node repo and outside this unit's scope. Say the word and it can be filed as a
follow-up so the artwork is regenerable in-tree.
## Verification
All run through `make` / `script/` only; lint ran in the pinned container.
- `make check`: green — 56 suites, 1023 tests; `test-verify-build` 46 cases;
`check-censored` 186 tracked files; eslint + prettier in the lint image.
- `make build`: green — 23 emitted files verified against the receipt,
4 bundles `autistmask-build-debug=off`, `check-censored` 23 files under
`dist/`.
- `make package`: green — 11 members per archive.
- `make test-e2e` (Chrome): green — 55/55 and 5/5.
- `make test-e2e-firefox`: green — 8/8 and 7/7; the second suite installs the
packaged XPI, so the artifact carrying these icons was installed in real
Firefox.
Rebased onto `next` at `75a5fa9` and re-ran `make check` and `make build` after
the rebase. `docker ps -a` is empty of anything from this run; every e2e
container ran `--rm` and no image or cache was pruned.
Neither manifest declared an `icons` block, so Chrome and Firefox both drew the
generic puzzle piece for this extension. That is the first thing the owner sees
on every launch, and an unbranded placeholder is also how a user fails to tell a
real extension from a look-alike.
Both manifests now declare 16/32/48/128 as `icons/icon<size>.png`, and the four
PNGs live at `icons/` in the tree. build.js copies them into each browser
directory relative to it, so nothing points up and out the way `dist/styles.css`
does, and the receipt records them like every other emitted file.
The sizes copied come from the manifest that will ship next to them, not from a
second list in build.js: a size a manifest declares and `icons/` does not hold
fails the build with the path that is missing, rather than emitting a directory
whose manifest references nothing. script/lib/package.js already resolved `.png`
strings, so an icon that reached a manifest but not the archive fails
self-containment; tests/packaging.test.js now pins that case, since it was
covered only incidentally before.
tests/manifest.test.js asserts the declaration in both manifests, that both
declare the same set, and that each referenced file is a PNG whose IHDR states
the size the entry claims — a declaration alone would still permit a reference
to a file that is not there or is not an image.
The artwork is original, drawn from geometry rather than traced or downloaded: a
flat dark-navy rounded square (#101A2E) with a teal (#35E0C2) triangular "A" —
one outer triangle minus a triangular counter — rasterised with 8x8 supersampling
and encoded as RGBA PNG. One shape, two flat colours, which is what a 16px
toolbar slot can carry.
Verified: make check 56 suites / 1023 tests, make build and make package green,
both archives unpacked and the four icons confirmed inside each with bytes
identical to the tree, make test-e2e 55/55 and 5/5, make test-e2e-firefox 8/8 and
7/7 (the latter installs the packaged XPI). Removing icons/icon48.png fails
make build; making build.js skip one copy fails make package with "the chrome
archive would not be self-contained: it is told to load icons/icon48.png, which
is not in it".
clawbot
self-assigned this 2026-08-23 21:17:07 +02:00
PASS — DoD of #371 verified in an independent clone at d0e6758: both archives unpacked with unzip carry 11 members each including all four icons, byte-identical to the tree (12/12 sha256 match), each archive's own manifest.json declares 16/32/48/128; make check (56 suites / 1023 tests, lint executed in the pinned container at 6.5s, not CACHED), make build, make package, make test-e2e (55/55, 5/5) and make test-e2e-firefox (8/8, 7/7, installing the XPI I built) all green; CI green on the head commit; no conflict with current next; one commit, no attribution trailers.
Non-blocking observations, reported rather than filed as defects:
Malformed-icon gap (a case not in the PR body, probed here): replacing icons/icon48.png with a non-PNG of the same name leaves make build and make package at exit 0 and the junk bytes ship inside the archive. make check does catch it — the new IHDR assertion in tests/manifest.test.js fails exactly as intended, so that test is not vacuous — but the build/package path validates only existence, not PNG-ness. Acceptable layering given CI runs check on every push; noting it because make package alone is not sufficient to trust an artifact's icons.
ICON_REF_RE could not be bypassed. icons/../../../../../../etc/passwd.png and /etc/passwd.png were both refused at exit 2 with ...which is not a plain file under icons/, before any path join. The class excludes / and \, so no segment can be exactly .., the value must start with the literal icons/ so it can never go absolute, and JS $ without m admits no trailing-newline trick. Missing-file and traversal failures both discard dist/.
build.js now names the two manifest source paths twice — in MANIFEST_SOURCES and inline in the "copy manifests" block. Harmless today; if that location ever moves, copyIcons could read a different manifest than the one that ships, and the icons copied would not match the manifest shipped beside them.
The commit message and PR describe the mark as a triangular "A". Rendered, it is a hollow triangle with a triangular counter and no crossbar — it reads as a triangle/delta, not as the letter. Legible at 16px (checked against both white and mid-grey backgrounds), no transparent-on-transparent, no resemblance to another product's mark I can identify.
On the disclosed gap: shipping the four binaries without the committed generator is acceptable at this stage and should not block, but the follow-up to make the artwork regenerable in-tree is worth filing now rather than after the mark first needs changing.
PASS — DoD of [#371](https://git.eeqj.de/sneak/AutistMask/issues/371) verified in an independent clone at `d0e6758`: both archives unpacked with `unzip` carry 11 members each including all four icons, byte-identical to the tree (12/12 sha256 match), each archive's own `manifest.json` declares 16/32/48/128; `make check` (56 suites / 1023 tests, lint executed in the pinned container at 6.5s, not `CACHED`), `make build`, `make package`, `make test-e2e` (55/55, 5/5) and `make test-e2e-firefox` (8/8, 7/7, installing the XPI I built) all green; CI green on the head commit; no conflict with current `next`; one commit, no attribution trailers.
Non-blocking observations, reported rather than filed as defects:
- Malformed-icon gap (a case not in the PR body, probed here): replacing `icons/icon48.png` with a non-PNG of the same name leaves `make build` and `make package` at exit 0 and the junk bytes ship inside the archive. `make check` does catch it — the new IHDR assertion in `tests/manifest.test.js` fails exactly as intended, so that test is not vacuous — but the build/package path validates only existence, not PNG-ness. Acceptable layering given CI runs `check` on every push; noting it because `make package` alone is not sufficient to trust an artifact's icons.
- `ICON_REF_RE` could not be bypassed. `icons/../../../../../../etc/passwd.png` and `/etc/passwd.png` were both refused at exit 2 with `...which is not a plain file under icons/`, before any path join. The class excludes `/` and `\`, so no segment can be exactly `..`, the value must start with the literal `icons/` so it can never go absolute, and JS `$` without `m` admits no trailing-newline trick. Missing-file and traversal failures both discard `dist/`.
- `build.js` now names the two manifest source paths twice — in `MANIFEST_SOURCES` and inline in the "copy manifests" block. Harmless today; if that location ever moves, `copyIcons` could read a different manifest than the one that ships, and the icons copied would not match the manifest shipped beside them.
- The commit message and PR describe the mark as a triangular "A". Rendered, it is a hollow triangle with a triangular counter and no crossbar — it reads as a triangle/delta, not as the letter. Legible at 16px (checked against both white and mid-grey backgrounds), no transparent-on-transparent, no resemblance to another product's mark I can identify.
On the disclosed gap: shipping the four binaries without the committed generator is acceptable at this stage and should not block, but the follow-up to make the artwork regenerable in-tree is worth filing now rather than after the mark first needs changing.
clawbot
merged commit 45500e66cf into next2026-08-23 21:26:14 +02:00
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.
Closes #371.
Neither manifest declared an
iconsblock, so both browsers drew the genericpuzzle piece. Both now declare 16/32/48/128 as
icons/icon<size>.png, and thefour PNGs live at
icons/in the tree.What changed
manifest/chrome.json,manifest/firefox.json:iconsat 16/32/48/128.icons/icon{16,32,48,128}.png: new artwork (below).build.js: copies each browser's icons into its own dist directory,reading the sizes out of the manifest that ships next to them rather than
from a second list here. A size a manifest declares and
icons/does not holdfails the build with the missing path; a value that is not a plain file under
icons/is refused before it is joined into any path. The icons are recordedas emitted like everything else, so they are in the build receipt and
script/verify-buildcovers them.tests/manifest.test.js: asserts the declaration in both manifests, that bothdeclare the same set, and that each referenced file is a PNG whose IHDR states
the size the entry claims — a declaration on its own would still permit a
reference to a file that is absent or not an image.
tests/packaging.test.js: pins the case of a manifest naming an icon thearchive does not contain.
script/lib/package.jsneeded no change — itsMANIFEST_PATH_REalready matches.png— but that coverage was incidental,and this is the file it protects.
Icons are inside the built artifacts, verified by unpacking them
make package, thenunzipon the artifacts themselves (not the repo's own zipreader):
Unpacked and inspected, both targets:
Byte-identical to the tree (sha256 of the unpacked chrome icons vs
icons/):A missing icon fails the build — demonstrated, then restored
Removing the file the manifest names:
And the packager's own reference-resolver, reached by making
build.jstemporarily skip one copy so
dist/was complete except for that icon:Both were reverted immediately;
icons/icon48.pngis back at8d2b688321249b4100e9d472a52477593074213a222d6dab00d6229b6cc740dc, and thetemporary
build.jsline is not in the diff (grep TEMPORARY build.jsfindsnothing).
The artwork
Original, and produced here rather than obtained: a throwaway Python program
(standard library only —
zlib+struct, no PIL, no network) rasterisedgeometry defined in that program into RGBA PNGs at the four sizes. The mark is a
flat dark-navy rounded square (
#101A2E) carrying a teal (#35E0C2) triangular"A": one outer triangle minus a triangular counter, drawn from six coordinates,
antialiased by 8x8 supersampling. Nothing was traced, copied, downloaded or
derived from any existing mark, project or trademark, and no font is involved.
One shape in two flat colours is also what a 16px toolbar slot can actually
carry.
The generator itself is deliberately not committed: it is a Python file in a
Node repo and outside this unit's scope. Say the word and it can be filed as a
follow-up so the artwork is regenerable in-tree.
Verification
All run through
make/script/only; lint ran in the pinned container.make check: green — 56 suites, 1023 tests;test-verify-build46 cases;check-censored186 tracked files; eslint + prettier in the lint image.make build: green — 23 emitted files verified against the receipt,4 bundles
autistmask-build-debug=off,check-censored23 files underdist/.make package: green — 11 members per archive.make test-e2e(Chrome): green — 55/55 and 5/5.make test-e2e-firefox: green — 8/8 and 7/7; the second suite installs thepackaged XPI, so the artifact carrying these icons was installed in real
Firefox.
Rebased onto
nextat75a5fa9and re-ranmake checkandmake buildafterthe rebase.
docker ps -ais empty of anything from this run; every e2econtainer ran
--rmand no image or cache was pruned.PASS — DoD of #371 verified in an independent clone at
d0e6758: both archives unpacked withunzipcarry 11 members each including all four icons, byte-identical to the tree (12/12 sha256 match), each archive's ownmanifest.jsondeclares 16/32/48/128;make check(56 suites / 1023 tests, lint executed in the pinned container at 6.5s, notCACHED),make build,make package,make test-e2e(55/55, 5/5) andmake test-e2e-firefox(8/8, 7/7, installing the XPI I built) all green; CI green on the head commit; no conflict with currentnext; one commit, no attribution trailers.Non-blocking observations, reported rather than filed as defects:
icons/icon48.pngwith a non-PNG of the same name leavesmake buildandmake packageat exit 0 and the junk bytes ship inside the archive.make checkdoes catch it — the new IHDR assertion intests/manifest.test.jsfails exactly as intended, so that test is not vacuous — but the build/package path validates only existence, not PNG-ness. Acceptable layering given CI runscheckon every push; noting it becausemake packagealone is not sufficient to trust an artifact's icons.ICON_REF_REcould not be bypassed.icons/../../../../../../etc/passwd.pngand/etc/passwd.pngwere both refused at exit 2 with...which is not a plain file under icons/, before any path join. The class excludes/and\, so no segment can be exactly.., the value must start with the literalicons/so it can never go absolute, and JS$withoutmadmits no trailing-newline trick. Missing-file and traversal failures both discarddist/.build.jsnow names the two manifest source paths twice — inMANIFEST_SOURCESand inline in the "copy manifests" block. Harmless today; if that location ever moves,copyIconscould read a different manifest than the one that ships, and the icons copied would not match the manifest shipped beside them.On the disclosed gap: shipping the four binaries without the committed generator is acceptable at this stage and should not block, but the follow-up to make the artwork regenerable in-tree is worth filing now rather than after the mark first needs changing.