Serve PWA icons from fresh /icons/v2/ URLs with every iOS size
Publish Container / publish (push) Successful in 2m43s

iOS showed a letter tile ('N') when adding to the Home Screen. The live files were fine, but a
reverse proxy in front of the app serves static files with a long cache lifetime (live icon.svg
and sw.js were stale) and iOS remembers a failed icon per URL.

- New versioned URLs no proxy/phone has cached; old paths kept for existing installs
- apple-touch-icon declared at 180/167/152/120 (opaque 8-bit RGB PNGs) for app and dashboard
- 180px icon also listed in both manifests; notification icons and root fallbacks use v2
- Service worker cache bumped

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Nate EmmertandClaude Sonnet 5.5 committed 2026-10-15 14:22:05 -04:00
1 parent 19479daece
commit f21c91527c
29 files changed
+90 -35

No files matched your search

+3 -3
View File
@@ -2083,10 +2083,10 @@ function renderAlerts(data) {
if (typeof Notification !== 'undefined' && Notification.permission === 'granted') {
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
navigator.serviceWorker.ready.then(reg =>
reg.showNotification(title, { body, icon: '/icons/icon-192.png', badge: '/icons/icon-192.png', tag: id, data: { url: '/?tab=alerts' } })
).catch(() => new Notification(title, { body, icon: '/icons/icon-192.png' }));
reg.showNotification(title, { body, icon: '/icons/v2/icon-192.png', badge: '/icons/v2/icon-192.png', tag: id, data: { url: '/?tab=alerts' } })
).catch(() => new Notification(title, { body, icon: '/icons/v2/icon-192.png' }));
} else {
new Notification(title, { body, icon: '/icons/icon-192.png', badge: '/icons/icon-192.png' });
new Notification(title, { body, icon: '/icons/v2/icon-192.png', badge: '/icons/v2/icon-192.png' });
}
}
if (state.ntfyTopic) {
+6 -3
View File
@@ -5,10 +5,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#070c14">
<link rel="manifest" href="/dashboard.webmanifest">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/dashboard-icon-192.png">
<link rel="apple-touch-icon" href="/icons/dashboard-apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/v2/dashboard-icon-192.png">
<link rel="apple-touch-icon" sizes="180x180" href="/icons/v2/dashboard-apple-touch-icon-180.png">
<link rel="apple-touch-icon" sizes="167x167" href="/icons/v2/dashboard-apple-touch-icon-167.png">
<link rel="apple-touch-icon" sizes="152x152" href="/icons/v2/dashboard-apple-touch-icon-152.png">
<link rel="apple-touch-icon" sizes="120x120" href="/icons/v2/dashboard-apple-touch-icon-120.png">
<meta name="apple-mobile-web-app-title" content="Station">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/dashboard-icon-192.png">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/v2/dashboard-icon-192.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="stylesheet" href="/pwa-safe.css">
+5 -2
View File
@@ -8,8 +8,11 @@
<meta name="apple-mobile-web-app-title" content="Station">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="manifest" href="/dashboard.webmanifest">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/dashboard-icon-192.png">
<link rel="apple-touch-icon" href="/icons/dashboard-apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/v2/dashboard-icon-192.png">
<link rel="apple-touch-icon" sizes="180x180" href="/icons/v2/dashboard-apple-touch-icon-180.png">
<link rel="apple-touch-icon" sizes="167x167" href="/icons/v2/dashboard-apple-touch-icon-167.png">
<link rel="apple-touch-icon" sizes="152x152" href="/icons/v2/dashboard-apple-touch-icon-152.png">
<link rel="apple-touch-icon" sizes="120x120" href="/icons/v2/dashboard-apple-touch-icon-120.png">
<link rel="stylesheet" href="/pwa-safe.css">
<title>Station Dashboard — Sign In</title>
<style>
+5 -2
View File
@@ -6,8 +6,11 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="manifest" href="/dashboard.webmanifest">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/dashboard-icon-192.png">
<link rel="apple-touch-icon" href="/icons/dashboard-apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/v2/dashboard-icon-192.png">
<link rel="apple-touch-icon" sizes="180x180" href="/icons/v2/dashboard-apple-touch-icon-180.png">
<link rel="apple-touch-icon" sizes="167x167" href="/icons/v2/dashboard-apple-touch-icon-167.png">
<link rel="apple-touch-icon" sizes="152x152" href="/icons/v2/dashboard-apple-touch-icon-152.png">
<link rel="apple-touch-icon" sizes="120x120" href="/icons/v2/dashboard-apple-touch-icon-120.png">
<meta name="apple-mobile-web-app-title" content="Station">
<link rel="stylesheet" href="/pwa-safe.css">
<title>Station Dashboard — Recover Access</title>
+5 -2
View File
@@ -6,8 +6,11 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="manifest" href="/dashboard.webmanifest">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/dashboard-icon-192.png">
<link rel="apple-touch-icon" href="/icons/dashboard-apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/v2/dashboard-icon-192.png">
<link rel="apple-touch-icon" sizes="180x180" href="/icons/v2/dashboard-apple-touch-icon-180.png">
<link rel="apple-touch-icon" sizes="167x167" href="/icons/v2/dashboard-apple-touch-icon-167.png">
<link rel="apple-touch-icon" sizes="152x152" href="/icons/v2/dashboard-apple-touch-icon-152.png">
<link rel="apple-touch-icon" sizes="120x120" href="/icons/v2/dashboard-apple-touch-icon-120.png">
<meta name="apple-mobile-web-app-title" content="Station">
<link rel="stylesheet" href="/pwa-safe.css">
<title>Station Settings</title>
+5 -2
View File
@@ -6,8 +6,11 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="manifest" href="/dashboard.webmanifest">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/dashboard-icon-192.png">
<link rel="apple-touch-icon" href="/icons/dashboard-apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/v2/dashboard-icon-192.png">
<link rel="apple-touch-icon" sizes="180x180" href="/icons/v2/dashboard-apple-touch-icon-180.png">
<link rel="apple-touch-icon" sizes="167x167" href="/icons/v2/dashboard-apple-touch-icon-167.png">
<link rel="apple-touch-icon" sizes="152x152" href="/icons/v2/dashboard-apple-touch-icon-152.png">
<link rel="apple-touch-icon" sizes="120x120" href="/icons/v2/dashboard-apple-touch-icon-120.png">
<meta name="apple-mobile-web-app-title" content="Station">
<link rel="stylesheet" href="/pwa-safe.css">
<title>Station Dashboard — Create Account</title>
+5 -2
View File
@@ -10,8 +10,11 @@
<meta name="apple-mobile-web-app-title" content="Station">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="manifest" href="/dashboard.webmanifest">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/dashboard-icon-192.png">
<link rel="apple-touch-icon" href="/icons/dashboard-apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/v2/dashboard-icon-192.png">
<link rel="apple-touch-icon" sizes="180x180" href="/icons/v2/dashboard-apple-touch-icon-180.png">
<link rel="apple-touch-icon" sizes="167x167" href="/icons/v2/dashboard-apple-touch-icon-167.png">
<link rel="apple-touch-icon" sizes="152x152" href="/icons/v2/dashboard-apple-touch-icon-152.png">
<link rel="apple-touch-icon" sizes="120x120" href="/icons/v2/dashboard-apple-touch-icon-120.png">
<link rel="stylesheet" href="/pwa-safe.css">
<title>Station Dashboard</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
+9 -3
View File
@@ -14,19 +14,25 @@
],
"icons": [
{
"src": "/icons/dashboard-icon-192.png",
"src": "/icons/v2/dashboard-icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/dashboard-icon-512.png",
"src": "/icons/v2/dashboard-apple-touch-icon-180.png",
"sizes": "180x180",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/v2/dashboard-icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/dashboard-icon-maskable-512.png",
"src": "/icons/v2/dashboard-icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

+22
View File
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#3b82f6"/><stop offset="1" stop-color="#0a1a40"/></linearGradient>
<radialGradient id="glow" cx="0.18" cy="0.1" r="0.7"><stop offset="0" stop-color="#fff" stop-opacity="0.28"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></radialGradient>
<radialGradient id="sun" cx="0.4" cy="0.4" r="0.7"><stop offset="0" stop-color="#ffe9a8"/><stop offset="1" stop-color="#f97316"/></radialGradient>
<radialGradient id="sunglow"><stop offset="0" stop-color="#fbbf24" stop-opacity="0.5"/><stop offset="1" stop-color="#fbbf24" stop-opacity="0"/></radialGradient>
<linearGradient id="cloud" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#c9dcf5"/></linearGradient>
<filter id="soft" x="-20%" y="-20%" width="140%" height="160%"><feGaussianBlur stdDeviation="22"/></filter>
</defs>
<rect width="1024" height="1024" fill="url(#bg)"/>
<rect width="1024" height="1024" fill="url(#glow)"/>
<circle cx="400" cy="430" r="230" fill="url(#sunglow)"/>
<circle cx="400" cy="430" r="132" fill="url(#sun)"/>
<g filter="url(#soft)" opacity="0.35" transform="translate(0 26)" fill="#06122e">
<circle cx="392" cy="572" r="96"/><circle cx="522" cy="522" r="126"/><circle cx="648" cy="582" r="86"/>
<rect x="300" y="560" width="432" height="132" rx="66"/>
</g>
<g fill="url(#cloud)">
<circle cx="392" cy="572" r="96"/><circle cx="522" cy="522" r="126"/><circle cx="648" cy="582" r="86"/>
<rect x="300" y="560" width="432" height="132" rx="66"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+6 -3
View File
@@ -6,9 +6,12 @@
<link rel="stylesheet" href="/pwa-safe.css">
<title>NeCloud Weather</title>
<link rel="manifest" href="/manifest.json">
<link rel="icon" type="image/svg+xml" href="/icons/icon.svg">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png">
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png">
<link rel="icon" type="image/svg+xml" href="/icons/v2/icon.svg">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/v2/icon-192.png">
<link rel="apple-touch-icon" sizes="180x180" href="/icons/v2/apple-touch-icon-180.png">
<link rel="apple-touch-icon" sizes="167x167" href="/icons/v2/apple-touch-icon-167.png">
<link rel="apple-touch-icon" sizes="152x152" href="/icons/v2/apple-touch-icon-152.png">
<link rel="apple-touch-icon" sizes="120x120" href="/icons/v2/apple-touch-icon-120.png">
<meta name="theme-color" content="#edf2f7" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#070c14" media="(prefers-color-scheme: dark)">
<meta name="apple-mobile-web-app-capable" content="yes">
+14 -8
View File
@@ -18,25 +18,31 @@
],
"icons": [
{
"src": "/icons/icon-192.png",
"src": "/icons/v2/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-512.png",
"src": "/icons/v2/apple-touch-icon-180.png",
"sizes": "180x180",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/v2/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-maskable-512.png",
"src": "/icons/v2/icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/icons/icon.svg",
"src": "/icons/v2/icon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any"
@@ -59,7 +65,7 @@
"url": "/?tab=conditions",
"icons": [
{
"src": "/icons/icon-192.png",
"src": "/icons/v2/icon-192.png",
"sizes": "192x192"
}
]
@@ -71,7 +77,7 @@
"url": "/?tab=station",
"icons": [
{
"src": "/icons/icon-192.png",
"src": "/icons/v2/icon-192.png",
"sizes": "192x192"
}
]
@@ -83,7 +89,7 @@
"url": "/?tab=unified-map",
"icons": [
{
"src": "/icons/icon-192.png",
"src": "/icons/v2/icon-192.png",
"sizes": "192x192"
}
]
@@ -95,7 +101,7 @@
"url": "/?tab=alerts",
"icons": [
{
"src": "/icons/icon-192.png",
"src": "/icons/v2/icon-192.png",
"sizes": "192x192"
}
]
+4 -4
View File
@@ -4,7 +4,7 @@ self.addEventListener('message', event => {
if (event.data === 'skipWaiting') self.skipWaiting();
});
const CACHE = 'weather-v13';
const CACHE = 'weather-v14';
// App shell cached on install for offline resilience.
const PRECACHE = [
@@ -18,7 +18,7 @@ const PRECACHE = [
'/alert-info.js',
'/app-ui.js',
'/manifest.json',
'/icons/icon.svg'
'/icons/v2/icon.svg'
];
self.addEventListener('install', event => {
@@ -44,8 +44,8 @@ self.addEventListener('push', event => {
event.waitUntil(
self.registration.showNotification(payload.title || 'NeCloud Weather', {
body: payload.body || '',
icon: '/icons/icon-192.png',
badge: '/icons/icon-192.png',
icon: '/icons/v2/icon-192.png',
badge: '/icons/v2/icon-192.png',
tag: payload.tag || 'weather-alert',
data: { url: payload.url || '/' },
})
+1 -1
View File
@@ -638,7 +638,7 @@ app.use(express.static(path.join(__dirname, 'public')));
// Prevent browser favicon requests from showing noisy 404s.
// Icons iOS and browsers request from the site root when a page doesn't declare one
const ICON_DIR = path.join(__dirname, 'public', 'icons');
const ICON_DIR = path.join(__dirname, 'public', 'icons', 'v2');
app.get(['/apple-touch-icon.png', '/apple-touch-icon-precomposed.png'], (_req, res) => res.sendFile(path.join(ICON_DIR, 'apple-touch-icon.png')));
app.get('/favicon.ico', (_req, res) => res.sendFile(path.join(ICON_DIR, 'icon-192.png')));