From 56fc589e0bf60d8dccbe4fa91cd48407739e4bc9 Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Thu, 16 Apr 2026 11:44:22 -0700 Subject: [PATCH] Move to all PNGs in webmanifest. --- app/frontend_static.py | 39 ++++++++++++++++++++++++----------- tests/test_frontend_static.py | 11 ++++++++-- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/app/frontend_static.py b/app/frontend_static.py index 99591e1..9b975eb 100644 --- a/app/frontend_static.py +++ b/app/frontend_static.py @@ -135,7 +135,34 @@ def register_frontend_static_routes(app: FastAPI, frontend_dir: Path) -> bool: "display_override": ["window-controls-overlay", "standalone", "fullscreen"], "theme_color": "#111419", "background_color": "#111419", + # Icons are PNG-only on purpose. iOS Safari's manifest parser has + # historically been unreliable with SVG icons, and Android/Chrome + # PWA install flows prefer PNG for the install prompt. + # + # The "any" purpose entries are what iOS and desktop Chrome use + # for the home-screen / install icon. "maskable" entries are + # Android-only (adaptive icon with safe-zone crop); iOS does not + # apply the safe-zone mask, so a maskable-only icon set would + # render with excessive padding. "icons": [ + { + "src": f"{base}favicon-96x96.png", + "sizes": "96x96", + "type": "image/png", + "purpose": "any", + }, + { + "src": f"{base}apple-touch-icon.png", + "sizes": "180x180", + "type": "image/png", + "purpose": "any", + }, + { + "src": f"{base}favicon-256x256.png", + "sizes": "256x256", + "type": "image/png", + "purpose": "any", + }, { "src": f"{base}web-app-manifest-192x192.png", "sizes": "192x192", @@ -148,18 +175,6 @@ def register_frontend_static_routes(app: FastAPI, frontend_dir: Path) -> bool: "type": "image/png", "purpose": "maskable", }, - { - "src": f"{base}favicon.svg", - "sizes": "any", - "type": "image/svg+xml", - "purpose": "any", - }, - { - "src": f"{base}favicon-256x256.png", - "sizes": "256x256", - "type": "image/png", - "purpose": "any", - }, ], "screenshots": [ { diff --git a/tests/test_frontend_static.py b/tests/test_frontend_static.py index b569a2b..bcdaa3c 100644 --- a/tests/test_frontend_static.py +++ b/tests/test_frontend_static.py @@ -69,7 +69,12 @@ def test_valid_dist_serves_static_and_spa_fallback(tmp_path): assert manifest["scope"] == "http://testserver/" assert manifest["id"] == "http://testserver/" assert manifest["display"] == "standalone" - assert manifest["icons"][0]["src"] == "http://testserver/web-app-manifest-192x192.png" + icon_srcs = {icon["src"] for icon in manifest["icons"]} + assert "http://testserver/web-app-manifest-192x192.png" in icon_srcs + assert "http://testserver/web-app-manifest-512x512.png" in icon_srcs + # SVG icons cause inconsistent PWA icon rendering on iOS; the manifest + # must be PNG-only. + assert all(icon["type"] == "image/png" for icon in manifest["icons"]) file_response = client.get("/robots.txt") assert file_response.status_code == 200 @@ -152,7 +157,9 @@ def test_webmanifest_includes_forwarded_prefix(tmp_path): assert data["start_url"] == expected_base assert data["scope"] == expected_base assert data["id"] == expected_base - assert data["icons"][0]["src"] == f"{expected_base}web-app-manifest-192x192.png" + icon_srcs = {icon["src"] for icon in data["icons"]} + assert f"{expected_base}web-app-manifest-192x192.png" in icon_srcs + assert f"{expected_base}web-app-manifest-512x512.png" in icon_srcs def test_first_available_prefers_dist_over_prebuilt(tmp_path):