Use the real icon whenever possible #35

Closed
opened 2026-07-27 14:13:42 +00:00 by Grandiras · 2 comments
Owner

Currently, we have the simplified icon for tab headers. But it's e.g. also used as a web app icon. Whenever we have enough space, we should use the original icon.

Currently, we have the simplified icon for tab headers. But it's e.g. also used as a web app icon. Whenever we have enough space, we should use the original icon.
Collaborator

🤖 On it — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 On it — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/120); the reply will link the full session transcript. 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-35/run-120.md) — updating every ~5 minutes while the task runs.
Collaborator

Pushed to claude/issue-35 (compare).

What the problem actually was

favicon.svg was the only icon the app declared — there was no web app manifest and no apple-touch-icon. So when a browser installs the site (home screen, launcher, desktop shortcut) it had nothing but the favicon to install, and shipped the deliberately-reduced 16 px cut at 192 px.

What I did

Three new SVGs in wwwroot — one family, three separate decisions, not copies:

  • icon.svg — the full mark as a file. A Chromium render of it and of BrandMark.razor is pixel-for-pixel identical (verified, 0 px differ).
  • apple-touch-icon.svg — same artwork, full bleed. iOS composites a home-screen icon on black and applies its own squircle mask, so icon.svg's transparent surround would come back as a black ring and its 25% corners would be rounded twice.
  • icon-maskable.svg — full bleed, artwork scaled 0.78 about the centre. An Android launcher may crop to a circle of 80% diameter; at full size the sun's halo reaches 31.4 of our 64 and loses its top-right edge. 0.78 brings the furthest point to 24.5, inside the 25.6 safe circle, while the gradient still runs edge to edge.

favicon.svg is untouched and stays the only rel="icon". I deliberately did not add a second rel="icon" sizes="any" pointing at the full mark — that lets the browser choose it for a 16 px tab, which is exactly the mush the reduced cut exists to prevent. The full mark reaches the large surfaces via channels that are large by definition (manifest, apple-touch-icon).

manifest.json, not site.webmanifest: an extension the static-file content-type map doesn't know is served as a 404, and .json always is. It declares 192/512 any plus a 512 maskable.

tools/render-icons.sh — the PNGs are committed build output and must be re-rendered when a source SVG changes, or iOS and Android keep showing the old mark. Needs librsvg; ImageMagick's built-in SVG renderer is not a substitute (it drops the gradient fills and paints the squircle black — I hit that first).

tests/e2e/icons.spec.js and a CLAUDE.md section covering all of the above.

Verified

  • dotnet build Ponente.sln — clean, 0 warnings.
  • dotnet test — 46/46 pass.
  • Ran the app for real (installed the .NET 10 SDK + Postgres in the container) and probed it: /manifest.json → 200 application/json, all four PNGs → 200 image/png, all served anonymously; the three <link> tags render in /login's head.
  • Chromium Page.getAppManifest over the running app: manifest fetched, zero parse errors, 3 icons, console clean.
  • New icons.spec.js4/4 pass against the live app.
  • auth-entrance.spec.js9/9 pass (the existing spec that leans hardest on App.razor's head).
  • Visually checked every rendered PNG, and confirmed the alpha split by PNG header: icon-*.png colour type 6 (keeps the squircle's transparency), apple-touch-icon.png / icon-maskable-512.png colour type 2 (opaque, full bleed).
  • rsvg's renders match Blink's structurally — with 2% fuzz, 2570 of 262144 px differ, i.e. antialiased edges only.

One thing I couldn't run here: the full compose stack (no podman/docker in the CI container), so board.spec.js and friends weren't exercised — nothing in this change touches them, but worth a normal --force-recreate run before merging.


⏱ 13m 46s · 68 turns · tokens: 82.1k in / 42.3k out (+4125.4k cache-read)
📜 Full session transcript
→ Opened PR: #40

Pushed to **`claude/issue-35`** ([compare](https://git.grandiras.net/Grandiras/Ponente/compare/main...claude/issue-35)). ## What the problem actually was `favicon.svg` was the *only* icon the app declared — there was no web app manifest and no `apple-touch-icon`. So when a browser installs the site (home screen, launcher, desktop shortcut) it had nothing but the favicon to install, and shipped the deliberately-reduced 16 px cut at 192 px. ## What I did **Three new SVGs in `wwwroot`** — one family, three separate decisions, not copies: - **`icon.svg`** — the full mark as a file. A Chromium render of it and of `BrandMark.razor` is **pixel-for-pixel identical** (verified, 0 px differ). - **`apple-touch-icon.svg`** — same artwork, full bleed. iOS composites a home-screen icon on black and applies its own squircle mask, so `icon.svg`'s transparent surround would come back as a black ring and its 25% corners would be rounded twice. - **`icon-maskable.svg`** — full bleed, artwork scaled 0.78 about the centre. An Android launcher may crop to a circle of 80% diameter; at full size the sun's halo reaches 31.4 of our 64 and loses its top-right edge. 0.78 brings the furthest point to 24.5, inside the 25.6 safe circle, while the gradient still runs edge to edge. **`favicon.svg` is untouched and stays the only `rel="icon"`.** I deliberately did *not* add a second `rel="icon" sizes="any"` pointing at the full mark — that lets the browser choose it for a 16 px tab, which is exactly the mush the reduced cut exists to prevent. The full mark reaches the large surfaces via channels that are large by definition (manifest, apple-touch-icon). **`manifest.json`**, not `site.webmanifest`: an extension the static-file content-type map doesn't know is served as a 404, and `.json` always is. It declares 192/512 `any` plus a 512 `maskable`. **`tools/render-icons.sh`** — the PNGs are committed build output and must be re-rendered when a source SVG changes, or iOS and Android keep showing the old mark. Needs librsvg; ImageMagick's built-in SVG renderer is *not* a substitute (it drops the gradient fills and paints the squircle black — I hit that first). **`tests/e2e/icons.spec.js`** and a `CLAUDE.md` section covering all of the above. ## Verified - `dotnet build Ponente.sln` — clean, 0 warnings. - `dotnet test` — 46/46 pass. - Ran the app for real (installed the .NET 10 SDK + Postgres in the container) and probed it: `/manifest.json` → 200 `application/json`, all four PNGs → 200 `image/png`, all served **anonymously**; the three `<link>` tags render in `/login`'s head. - Chromium `Page.getAppManifest` over the running app: manifest fetched, **zero parse errors**, 3 icons, console clean. - New `icons.spec.js` — **4/4 pass** against the live app. - `auth-entrance.spec.js` — **9/9 pass** (the existing spec that leans hardest on `App.razor`'s head). - Visually checked every rendered PNG, and confirmed the alpha split by PNG header: `icon-*.png` colour type 6 (keeps the squircle's transparency), `apple-touch-icon.png` / `icon-maskable-512.png` colour type 2 (opaque, full bleed). - rsvg's renders match Blink's structurally — with 2% fuzz, 2570 of 262144 px differ, i.e. antialiased edges only. One thing I couldn't run here: the full compose stack (no podman/docker in the CI container), so `board.spec.js` and friends weren't exercised — nothing in this change touches them, but worth a normal `--force-recreate` run before merging. --- ⏱ 13m 46s · 68 turns · tokens: 82.1k in / 42.3k out (+4125.4k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-35/run-120.md) → Opened PR: https://git.grandiras.net/Grandiras/Ponente/pulls/40
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/Ponente#35
No description provided.