Name the icons by their contents in the pages (#57)

/favicon.png, /apple-touch-icon.png and /logo.svg are kept a day under
fixed names, so after the new logo went out, curl through the tunnel and
browsers still got the old one. The pages now ask for them as
/favicon.png?v=<hash>, the way they already ask for app.js and app.css,
so a changed icon is a new URL for every cache on the way. /favicon.ico
cannot carry a query, as browsers ask for it on their own; it keeps the
day.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 14:11:03 +00:00
parent 40c92ad08d
commit ae900b82ca
3 changed files with 7 additions and 1 deletions

View File

@@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### 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 - 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 of the feed list keep clear of the Dynamic Island and the status bar instead of sitting under
them. them.

View File

@@ -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 }) => { 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. // 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']) { for (const path of ['/favicon.ico', '/favicon.png', '/apple-touch-icon.png']) {
const r = await page.request.get(path, { headers: { cookie: '' } }); const r = await page.request.get(path, { headers: { cookie: '' } });

View File

@@ -72,6 +72,11 @@ export function buildPage(name, { minify = true } = {}) {
let out = page.replace(marker, `<script src="/${script}?v=${hash(js)}"></script>`); let out = page.replace(marker, `<script src="/${script}?v=${hash(js)}"></script>`);
out = out.replace(/<link rel="stylesheet" data-src="[^"]*">/, out = out.replace(/<link rel="stylesheet" data-src="[^"]*">/,
() => `<link rel="stylesheet" href="/${STYLE}?v=${hash(buildStyle({ minify }))}">`); () => `<link rel="stylesheet" href="/${STYLE}?v=${hash(buildStyle({ minify }))}">`);
// 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 }; if (!minify) return { html: out, js, script };
const r = html.minifySync(out, { minifyJs: false, minifyCss: true, removeComments: true }); const r = html.minifySync(out, { minifyJs: false, minifyCss: true, removeComments: true });
const bad = (r.errors || []).filter(e => e.level === 'Error'); const bad = (r.errors || []).filter(e => e.level === 'Error');