diff --git a/CHANGELOG.md b/CHANGELOG.md index 0076ef0..fa6c306 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- A new tab or home-screen icon shows up as soon as it is deployed, instead of a day later. - Opened from the home screen on an iPhone, the top bar, the reader's back button and the top of the feed list keep clear of the Dynamic Island and the status bar instead of sitting under them. diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index 707b55e..be974c4 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -1179,7 +1179,7 @@ test('a file not yet downloaded has its icon in line with the rest of its row', }); test('the favicon is the logo, square, from both pages', async ({ page }) => { - await expect(page.locator('link[rel="icon"]')).toHaveAttribute('href', '/favicon.png'); + await expect(page.locator('link[rel="icon"]')).toHaveAttribute('href', /^\/favicon\.png\?v=[0-9a-f]{12}$/); // A browser asks for /favicon.ico on its own, signed in or not. for (const path of ['/favicon.ico', '/favicon.png', '/apple-touch-icon.png']) { const r = await page.request.get(path, { headers: { cookie: '' } }); diff --git a/web/build.mjs b/web/build.mjs index 957f11d..6542693 100644 --- a/web/build.mjs +++ b/web/build.mjs @@ -72,6 +72,11 @@ export function buildPage(name, { minify = true } = {}) { let out = page.replace(marker, ``); out = out.replace(//, () => ``); + // The icons are named by their contents too. They are kept a day under a fixed name, and a new + // logo went unseen for that day, in browsers and at Cloudflare's edge. The server ignores the + // query; /favicon.ico, which a browser asks for on its own, cannot carry one. + out = out.replace(/(href|src)="\/(favicon\.png|apple-touch-icon\.png|logo\.svg)"/g, + (_, attr, file) => `${attr}="/${file}?v=${hash(fs.readFileSync(path.join(here, file)))}"`); if (!minify) return { html: out, js, script }; const r = html.minifySync(out, { minifyJs: false, minifyCss: true, removeComments: true }); const bad = (r.errors || []).filter(e => e.level === 'Error');