Fix: SW Network-First für Navigation + versionierte CSS/JS-URLs (v=32)

- sw.js: index.html nie aus Cache (navigation → network-first)
- index.html: ?v=32 an layout.css, components.css, api/ui/app.js
- sw.js STATIC_ASSETS: versionierte URLs gecacht
- app.js: Sidebar-Background per getComputedStyle statt CSS-Variable-String
- SW by-v31 → by-v32
This commit is contained in:
rene 2026-04-14 17:26:02 +02:00
parent b6fae96334
commit 619ff559e6
3 changed files with 33 additions and 24 deletions

View file

@ -20,10 +20,10 @@
<title>Ban Yaro</title> <title>Ban Yaro</title>
<!-- CSS: Reihenfolge ist wichtig --> <!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
<link rel="stylesheet" href="/css/design-system.css"> <link rel="stylesheet" href="/css/design-system.css">
<link rel="stylesheet" href="/css/layout.css"> <link rel="stylesheet" href="/css/layout.css?v=32">
<link rel="stylesheet" href="/css/components.css"> <link rel="stylesheet" href="/css/components.css?v=32">
</head> </head>
<body> <body>
@ -215,9 +215,9 @@
<div id="modal-container"></div> <div id="modal-container"></div>
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features --> <!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
<script src="/js/api.js"></script> <script src="/js/api.js?v=32"></script>
<script src="/js/ui.js"></script> <script src="/js/ui.js?v=32"></script>
<script src="/js/app.js"></script> <script src="/js/app.js?v=32"></script>
<!-- Feature-Seiten werden lazy geladen --> <!-- Feature-Seiten werden lazy geladen -->

View file

@ -196,17 +196,20 @@ const App = (() => {
const sidebar = document.getElementById('sidebar'); const sidebar = document.getElementById('sidebar');
if (!sidebar) return; if (!sidebar) return;
// Inline-Styles: unabhängig vom CSS-Cache-Stand // Inline-Styles: unabhängig vom CSS-Cache-Stand
// Inline-Styles: unabhängig vom CSS-Cache; Farben als Fallback hardcoded
const bg = getComputedStyle(document.documentElement)
.getPropertyValue('--c-surface').trim() || '#ffffff';
sidebar.style.cssText = [ sidebar.style.cssText = [
'display:flex', 'display:flex',
'position:fixed', 'position:fixed',
'top:0', 'left:0', 'bottom:0', 'top:0', 'left:0', 'bottom:0',
'width:240px', 'width:240px',
'z-index:2000', 'z-index:2000',
'background:var(--c-surface,#fff)', `background:${bg}`,
'flex-direction:column', 'flex-direction:column',
'overflow:hidden', 'overflow:hidden',
'box-shadow:4px 0 24px rgba(0,0,0,0.22)', 'box-shadow:4px 0 24px rgba(0,0,0,0.22)',
'border-right:1px solid var(--c-border-light,#eee)', 'border-right:1px solid #e5e7eb',
].join(';'); ].join(';');
document.getElementById('sidebar-backdrop')?.classList.add('visible'); document.getElementById('sidebar-backdrop')?.classList.add('visible');
} }

View file

@ -3,18 +3,17 @@
Offline-Cache + Push Notifications Offline-Cache + Push Notifications
============================================================ */ ============================================================ */
const CACHE_VERSION = 'by-v31'; const CACHE_VERSION = 'by-v32';
const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_STATIC = `${CACHE_VERSION}-static`;
// Diese Dateien werden beim Install gecacht (App Shell) // index.html wird NICHT pre-gecacht (immer Network-First)
const STATIC_ASSETS = [ const STATIC_ASSETS = [
'/',
'/css/design-system.css', '/css/design-system.css',
'/css/layout.css', '/css/layout.css?v=32',
'/css/components.css', '/css/components.css?v=32',
'/js/api.js', '/js/api.js?v=32',
'/js/ui.js', '/js/ui.js?v=32',
'/js/app.js', '/js/app.js?v=32',
'/manifest.json', '/manifest.json',
'/icons/icon-192.png', '/icons/icon-192.png',
]; ];
@ -40,14 +39,11 @@ self.addEventListener('activate', event => {
keys.filter(k => k !== CACHE_STATIC).map(k => caches.delete(k)) keys.filter(k => k !== CACHE_STATIC).map(k => caches.delete(k))
)) ))
.then(() => self.clients.claim()) .then(() => self.clients.claim())
// Alle offenen Fenster neu laden, damit neues CSS/JS aktiv wird
.then(() => self.clients.matchAll({ type: 'window' }))
.then(clients => clients.forEach(c => c.navigate(c.url)))
); );
}); });
// ---------------------------------------------------------- // ----------------------------------------------------------
// FETCH — Cache-First für statische Assets, Network-First für API // FETCH — Network-First für HTML, Cache-First für Assets, Network für API
// ---------------------------------------------------------- // ----------------------------------------------------------
self.addEventListener('fetch', event => { self.addEventListener('fetch', event => {
const url = new URL(event.request.url); const url = new URL(event.request.url);
@ -63,12 +59,25 @@ self.addEventListener('fetch', event => {
return; return;
} }
// Navigation (index.html): immer Network-First
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request)
.then(response => {
const clone = response.clone();
caches.open(CACHE_STATIC).then(c => c.put(event.request, clone));
return response;
})
.catch(() => caches.match('/'))
);
return;
}
// Statische Assets: Cache-First // Statische Assets: Cache-First
event.respondWith( event.respondWith(
caches.match(event.request) caches.match(event.request)
.then(cached => cached || fetch(event.request) .then(cached => cached || fetch(event.request)
.then(response => { .then(response => {
// Erfolgreiche Responses für statische Assets cachen
if (response.ok && event.request.method === 'GET') { if (response.ok && event.request.method === 'GET') {
const clone = response.clone(); const clone = response.clone();
caches.open(CACHE_STATIC).then(c => c.put(event.request, clone)); caches.open(CACHE_STATIC).then(c => c.put(event.request, clone));
@ -77,7 +86,6 @@ self.addEventListener('fetch', event => {
}) })
) )
.catch(() => { .catch(() => {
// Offline-Fallback: App Shell zurückgeben
if (event.request.mode === 'navigate') { if (event.request.mode === 'navigate') {
return caches.match('/'); return caches.match('/');
} }
@ -139,7 +147,6 @@ self.addEventListener('notificationclick', event => {
event.waitUntil( event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true }) clients.matchAll({ type: 'window', includeUncontrolled: true })
.then(windowClients => { .then(windowClients => {
// Offenes Fenster fokussieren
for (const client of windowClients) { for (const client of windowClients) {
if (client.url.includes(self.location.origin)) { if (client.url.includes(self.location.origin)) {
client.focus(); client.focus();
@ -147,7 +154,6 @@ self.addEventListener('notificationclick', event => {
return; return;
} }
} }
// Neues Fenster öffnen
return clients.openWindow(url); return clients.openWindow(url);
}) })
); );