diff --git a/apple-touch-icon.png b/apple-touch-icon.png new file mode 100644 index 0000000..72650b3 Binary files /dev/null and b/apple-touch-icon.png differ diff --git a/catcrafts-head.js b/catcrafts-head.js index 5709296..54e2fa6 100644 --- a/catcrafts-head.js +++ b/catcrafts-head.js @@ -51,6 +51,15 @@ ensure('link[rel="icon"]', () => { return el; }); +// Keeps Safari from probing /apple-touch-icon{,-precomposed}.png at the root. +// Both files exist anyway, for the clients that probe regardless of markup. +ensure('link[rel="apple-touch-icon"]', () => { + const el = document.createElement("link"); + el.rel = "apple-touch-icon"; + el.href = "/apple-touch-icon.png"; + return el; +}); + ensure('meta[name="viewport"]', () => { const el = document.createElement("meta"); el.name = "viewport"; diff --git a/deploy/Caddyfile.example b/deploy/Caddyfile.example index 1ef3c25..7e63b71 100644 --- a/deploy/Caddyfile.example +++ b/deploy/Caddyfile.example @@ -80,8 +80,18 @@ catcrafts.net { Cross-Origin-Resource-Policy "same-origin" } # Subresources an isolated document pulls in must carry CORP themselves. + # The icons need it for the same reason the stylesheet does: "/" boots the + # wasm, so its icon fetches happen inside a require-corp document. + # + # -precomposed is listed here even though the rewrite below folds it onto + # apple-touch-icon.png: Caddy's directive order runs `header` BEFORE + # `rewrite`, so this matcher sees the URI as it arrived. (`handle` runs after, + # which is why @static does NOT need to list it.) Drop the path here and the + # probe still 200s, just bare. header /styles.css Cross-Origin-Resource-Policy "same-origin" - header /favicon.svg Cross-Origin-Resource-Policy "same-origin" + @icons path /favicon.svg /favicon.ico /apple-touch-icon.png \ + /apple-touch-icon-precomposed.png + header @icons Cross-Origin-Resource-Policy "same-origin" # ── analytics, two tiers ────────────────────────────────────────────── # @@ -120,9 +130,23 @@ catcrafts.net { # better than the backend would. `precompressed` serves the .zst / .gz # siblings the CI build produces, so the ~800 KB module is never recompressed # per request. Cache-busted by the ?v= in index.html. + # + # CAVEAT for the icons in here: nothing cache-busts them. Clients ask for + # /favicon.svg, /favicon.ico and /apple-touch-icon.png by bare path, so an + # immutable year is exactly how long a redrawn icon takes to reach a repeat + # visitor. Long-standing for favicon.svg; the rasters inherit it deliberately + # rather than diverge. Shorten all three together if that ever bites. + # + # The rewrite is Safari's legacy Home Screen probe, from before it honoured + # . What it asks for is byte-identical to + # apple-touch-icon.png, so it is folded onto that rather than committed twice + # — a second copy would also ride in the wasm bundle's VFS and be fetched at + # every boot for nothing. @static below matches the rewritten path. + rewrite /apple-touch-icon-precomposed.png /apple-touch-icon.png @static path /catcrafts*.wasm /runtime.js /dom-env.js /dom-webgpu.js \ /catcrafts-head.js /files.json /variants.json /styles.css \ - /favicon.svg /robots.txt /*.wgsl /*.jpg /posts.json /rates.json + /favicon.svg /favicon.ico /apple-touch-icon.png \ + /robots.txt /*.wgsl /*.jpg /posts.json /rates.json handle @static { header Cache-Control "public, max-age=31536000, immutable" file_server { diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..8296b67 Binary files /dev/null and b/favicon.ico differ diff --git a/project.cpp b/project.cpp index 5207c56..23753db 100644 --- a/project.cpp +++ b/project.cpp @@ -333,6 +333,18 @@ extern "C" Configuration CrafterBuildProject(std::span a cfg.files.emplace_back(fs::path("sitemap.xml")); cfg.files.emplace_back(fs::path("feed.xml")); cfg.files.emplace_back(fs::path("favicon.svg")); + // Icon paths clients ask for without ever being told to. /favicon.ico is + // the root-path fallback a client uses when it cannot render the SVG above + // (Safari, historically) or never runs the JS that declares it (feed + // readers, the chat-app unfurlers, crawlers hitting the static shell); the + // apple-touch-icon.png is what Safari wants for its Home Screen and + // Favorites tile — its legacy -precomposed path is a Caddy rewrite onto + // this same file rather than a second entry here. Both were top entries in + // the 404 report until they shipped. Rasterised from favicon.svg by + // tools/make-icons.sh and committed — re-run it whenever that SVG is + // redrawn. + cfg.files.emplace_back(fs::path("favicon.ico")); + cfg.files.emplace_back(fs::path("apple-touch-icon.png")); // WGSL for the ray-traced WebGPU demo embedded in the blog (see // interfaces/Catcrafts-Demo.cppm). Fetched at runtime by WebGPUShader. cfg.files.emplace_back(fs::path("shaders/raygen.wgsl")); diff --git a/shared/interfaces/Catcrafts.Shared-Views.cppm b/shared/interfaces/Catcrafts.Shared-Views.cppm index b5d0a99..2af75dd 100644 --- a/shared/interfaces/Catcrafts.Shared-Views.cppm +++ b/shared/interfaces/Catcrafts.Shared-Views.cppm @@ -2260,6 +2260,13 @@ export std::string RenderDocument(const RenderedPage& page, "{}{}{}{}{}" "\n" "\n" + // Declaring this is what stops Safari probing the root for + // /apple-touch-icon-precomposed.png and /apple-touch-icon.png, which is + // its fallback when a page names no Home Screen icon. /favicon.ico + // deliberately gets NO : it exists purely so the by-path request + // resolves for clients that ignore the SVG, and declaring it would give + // SVG-capable browsers a reason to fetch the raster as well. + "\n" "\n" "{}" "\n\n" diff --git a/tests/ShouldStayScriptFree/main.cpp b/tests/ShouldStayScriptFree/main.cpp index 3639353..73108e7 100644 --- a/tests/ShouldStayScriptFree/main.cpp +++ b/tests/ShouldStayScriptFree/main.cpp @@ -115,6 +115,7 @@ int main(int argc, char** argv) { { const std::string body = srv.Body("/demos/raytracer"); for (std::string_view probe : { "rel=\"stylesheet\"", "rel=\"icon\"", + "rel=\"apple-touch-icon\"", "name=\"viewport\"" }) { const std::size_t n = CountOccurrences(body, probe); Check(n == 1, std::format("demo page has exactly one {}", probe), diff --git a/tools/dev.sh b/tools/dev.sh index 34ca882..6d7e059 100755 --- a/tools/dev.sh +++ b/tools/dev.sh @@ -113,7 +113,8 @@ cat > "$WORK/Caddyfile" <