Repository navigation
[quality] static/manifest.json is never parsed by any test or CI gate; the PWA install contract is unguarded #333
Description
Finding
Nothing in this repository reads static/manifest.json. It is the web app
manifest linked from every page (docusaurus.config.js:41-46,
rel="manifest" href="/manifest.json"), and it is the single file that decides
whether a browser will offer to install the site. It has never been parsed,
validated, or cross-checked against the files it points at.
Verified at 00b44df, node v26.8.2, run locally 2026-09-19.
grep -rln "manifest\|robots" .github/ tests/ scripts/-> no matches. No
test and no CI step reads it.node --test --experimental-test-coverage-> 55/55 pass;static/manifest.json
never appears, because no test process ever loads it.- The manifest declares 3 icons under
/favicons/, plusname,short_name,
start_url,display,background_colorandtheme_color. All 3 icon
paths resolve today and the two PNG dimensions match their declaredsizes
(192x192, 512x512 read from the IHDR chunk) — but nothing pins any of that.
Why existing gates miss it
- Docusaurus copies
static/intobuild/verbatim. There is no
validation pass over it and no reachability analysis. onBrokenLinks: 'throw'enforces page routes, not static assets, and it
does not open a JSON file to check what that file points at.check:spellingrunsnpx cspell ... analyses docs *.md—static/is not
in its scope.check:markdownandcheck:formatdo not model cross-file
path resolution into a JSON asset list.- The five validator scripts cover
data/metrics.json,
data/architectures/,data/awards.jsonandsrc/css/custom.css. None
touchesstatic/manifest.json.
Every failure mode here is silent: a renamed icon, a zero-byte PNG, a
trailing comma, or a display typo all produce a clean docusaurus build and
a green deploy. The only symptom is that the install prompt stops appearing,
which nobody is watching for.
A case-only rename is the nastiest variant — /favicons/Favicon.svg
resolves on a case-insensitive developer filesystem and 404s on GitHub Pages,
which serves from a case-sensitive one. existsSync() alone does not catch it.
Disjointness from open PRs
- test: cover the docusaurus.config.js and sidebars.js navigation contract (tests/site-config.test.mjs) #275 (
tests/site-config.test.mjs) asserts that theheadTagslink
href/manifest.jsonresolves to a file on disk. It never opens that file.
This issue claims the manifest's contents — its icon list and install
fields — and imports neitherdocusaurus.config.jsnorsidebars.js. - test: verify local /img static asset references resolve under static/ (tests/static-assets.test.mjs) #289 (
tests/static-assets.test.mjs) scansdocs/,blog/,src/and
top-leveldata/*.jsonfor/imgreferences.static/manifest.jsonis not
in its scan set and/favicons/is not/img/. - [quality] static/images (16M) and static/social (148K) ship unreferenced on every deploy; static/ has no orphan guard #321 claims the orphan direction over
static/images,static/social
andstatic/favicons(it wouldgit rm static/favicons/favicon.ico, which
is not a manifest icon). The assertions here are forward-direction only
and stay green after that deletion. - [quality] src/css/custom.css <-> static/fonts/ contract is untested; 27 italic faces (764K) shipped but never declared #317 claims
src/css/custom.cssurl()refs andstatic/fonts/. - No open PR touches
static/manifest.json.
Recommendation
Add tests/manifest-contract.test.mjs, test-only, no new dependency. One PR
closes this issue — the deliverable is a single file and needs no production
change, because every assertion below is green on main today.
-
static/manifest.jsonparses as a JSON object, reported as a named test
failure rather than a module-load crash. -
name,short_name,start_url,displayare non-empty strings;
displayis one of the fourappmanifestspec values;start_urlis
root-absolute. -
iconsis a non-empty array — so a regression that empties it cannot
make the remaining assertions vacuously pass. - Every
icons[].srcis root-absolute, does not escapestatic/via..,
and carries a recognisedtypeand asizesstring. - Every icon resolves under
static/case-exactly, compared per path
segment viareaddirSync, is a file, and is non-empty. - Each icon file's extension matches its declared media type.
- Raster icons' real PNG dimensions (read from the IHDR chunk) match their
declaredsizes; vector icons declaresizes: "any"rather than being
pinned to one bucket. - At least one icon >=192px and one >=512px survive, which is Chromium's
threshold for firingbeforeinstallprompt. -
background_colorandtheme_color, when present, are#rrggbb. - No two icons claim the same
src.
Deliberately out of scope
static/manifest.json hard-codes root-absolute paths (start_url: "/",
icons[].src: "/favicons/..."), and Docusaurus does not rewrite files
under static/ for baseUrl. Under the BASE_URL=/endusers/ preview mode
that docusaurus.config.js:26-28 documents, those paths resolve against the
origin root instead of the site root. That is a production defect, not a test
gap, so it is filed separately rather than asserted red here.
Priority
- Impact: medium — the site's installability is unguarded and every failure mode is silent
- Effort: low — one test-only file, no new dependency, green on
maintoday
Filed by quality agent (hold-gated mode)
— hive: agent=quality backend=copilot model=claude-opus-5
Activity
test: cover the static/manifest.json PWA install contract (tests/mani…