Skip to content

[quality] static/manifest.json is never parsed by any test or CI gate; the PWA install contract is unguarded #333

Closed

Description

@hivecommons-hive

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/, plus name, short_name,
    start_url, display, background_color and theme_color. All 3 icon
    paths resolve today and the two PNG dimensions match their declared sizes
    (192x192, 512x512 read from the IHDR chunk) — but nothing pins any of that.

Why existing gates miss it

  • Docusaurus copies static/ into build/ 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:spelling runs npx cspell ... analyses docs *.md — static/ is not
    in its scope. check:markdown and check:format do not model cross-file
    path resolution into a JSON asset list.
  • The five validator scripts cover data/metrics.json,
    data/architectures/, data/awards.json and src/css/custom.css. None
    touches static/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

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.json parses as a JSON object, reported as a named test
    failure rather than a module-load crash.
  • name, short_name, start_url, display are non-empty strings;
    display is one of the four appmanifest spec values; start_url is
    root-absolute.
  • icons is a non-empty array — so a regression that empties it cannot
    make the remaining assertions vacuously pass.
  • Every icons[].src is root-absolute, does not escape static/ via ..,
    and carries a recognised type and a sizes string.
  • Every icon resolves under static/ case-exactly, compared per path
    segment via readdirSync, 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
    declared sizes; vector icons declare sizes: "any" rather than being
    pinned to one bucket.
  • At least one icon >=192px and one >=512px survive, which is Chromium's
    threshold for firing beforeinstallprompt.
  • background_color and theme_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 main today

Filed by quality agent (hold-gated mode)

— hive: agent=quality backend=copilot model=claude-opus-5

Activity

  1. added
    qualityApproved by a Hive merger/owner for auto-merge on green CI
    testingApproved by a Hive merger/owner for auto-merge on green CI
    agent/qualityApproved by a Hive merger/owner for auto-merge on green CI
    on Sep 20, 2026
  2. added a commit that references this issue on Sep 21, 2026
    f383ccf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent/qualityApproved by a Hive merger/owner for auto-merge on green CIqualityApproved by a Hive merger/owner for auto-merge on green CItestingApproved by a Hive merger/owner for auto-merge on green CI

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions