Link one tab icon so the chosen one is not outvoted by the wax seal (#74)

Every page linked the per-account SVG tab icon and, after it, a static 192px wax-seal PNG. Firefox stores an SVG icon at width 65535 and a PNG at its declared size, and picks the smallest icon that is big enough for a tab, so the PNG won and every account saw the wax seal whatever it picked in Settings → Appearance → Logo.

The PNG tab-icon link is gone, so the SVG is the only tab icon. The apple-touch-icon and the manifest keep their PNGs. A browser with no SVG tab-icon support gets no tab icon instead of the wrong one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Walid ManiandClaude Opus 5.5 authored and GitHub committed 2026-10-12 13:49:49 +02:00
1 parent 2a765d8b32
commit 5debbdbc01
3 files changed
+41 -5

No files matched your search

+6
View File
@@ -8,6 +8,12 @@ The published image tags: `latest` follows the most recent release below,
## Unreleased
**The tab icon is the one you picked, in Firefox too.** Settings → Appearance → Logo changed the
icon in the app but not in the tab, which kept showing the wax seal. Every page also linked a
fixed 192px wax-seal PNG as a second tab icon, and Firefox prefers that to the SVG for a tab. The
PNG is gone from the tab icons; the home-screen and install icons are unchanged. No migration.
[#74](https://github.com/karatektus/pl_mail/pull/74), by @Flouss.
**A message with a stray non-UTF-8 byte in the wrong place was never stored.** Over IMAP, a mail
whose text was labelled `us-ascii` but written in Latin-1 — "Grüße" from a mailer nobody configured
— was refused by the database ("invalid byte sequence for encoding UTF8: 0xfc"), asked for again
+12 -5
View File
@@ -12,10 +12,18 @@
operating system happens to draw. A tab strip is where people find plMail
among thirty other tabs, and the thing they look for is the icon.
Now the real mark, in the same two forms every other icon here takes:
an SVG that stays sharp at 16px and any pixel ratio, and the 192px PNG for
anything that will not take an SVG favicon. Browsers pick the first `rel`
they understand, so the SVG is listed first.
Now the real mark, as an SVG that stays sharp at 16px and any pixel ratio.
It is the ONLY tab icon linked, and that is deliberate. This used to be
followed by `<link rel="icon" type="image/png" sizes="192x192"
href="/icons/icon-192.png">` as a fallback, and the fallback won: Firefox
stores an SVG icon at width 65535 and a PNG at its declared size, and picks
the smallest icon that is big enough for the tab, which is the 192px PNG.
A static file can only wear the product default, so every account saw the
wax seal in its tabs whatever icon it had chosen. A second candidate can
only be wrong for someone. The PNGs still serve the places that need a
raster and have no per-account route: the apple-touch-icon in app.html.twig
and the manifest's install icons.
The SVG comes from a route rather than a file, because the logo is a
per-user setting now (Settings → Appearance → Logo: an icon, then its
@@ -30,4 +38,3 @@
it live on every pick (see repaintLogo) — before the save has landed, which
is why the drawing cannot come from the session. See BrandingController. #}
<link rel="icon" type="image/svg+xml" href="{{ logo_favicon_url(appearance().effectiveLogoMotif, appearance().effectiveLogoPaint) }}">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png">
+23
View File
@@ -58,6 +58,29 @@ final class BrandingFaviconTest extends WebTestCase
}
}
/**
* A page links ONE tab icon, and it is the route's.
*
* Firefox files an SVG tab icon under width 65535 and a PNG under its
* declared size, then answers "which icon for a 16px tab" with the smallest
* one that is big enough, so a 192px PNG always beat the SVG. The PNG is a
* static file and wears the product default, so every account saw the wax
* seal in its tabs whatever it had chosen. The home-screen and install
* icons keep their PNGs (apple-touch-icon, the manifest); only the tab
* icon must not have a second candidate.
*/
public function testNoRasterTabIconCompetesWithTheChosenOne(): void
{
$client = static::createClient();
$icons = $client->request('GET', '/login')->filter('link[rel~="icon"]');
// The presence companion: with no tab icon at all the check below would
// pass on an empty head.
self::assertCount(1, $icons, 'exactly one tab icon is linked');
self::assertSame('image/svg+xml', $icons->attr('type'), 'and it is the per-account SVG route');
}
public function testTheTabIconWearsTheUsersChoice(): void
{
[$client, $user] = $this->signedIn();