baustelle-pwa/index.php
Eduard Wisch 7c9563ec90
All checks were successful
Deploy baustelle-pwa / deploy (push) Successful in 1s
feat: PWA Cache-Busting via filemtime (claude-db #201) — kein manuelles Hochzählen mehr
Das bisherige 'CACHE = baustelle-vN' Pattern erforderte, dass ich bei
jeder Änderung drei Stellen synchron halte (sw.js, index.html ?v=,
manifest). Eddy hat klargestellt dass das nicht akzeptabel ist.

Fix nach KB #201 Pattern (referenz: dolibarr.stundenzettel v2.2.0):

1. index.html → index.php
   PHP berechnet bei jedem Request filemtime() von app.css, app.js,
   lib/*.js, manifest.webmanifest. Die mtimes kommen automatisch
   beim Deploy (rsync preserviert sie default) und werden als ?v=
   an alle Asset-URLs gehängt.

2. sw.js liest Version aus eigener URL-Query:
   const SW_VERSION = (new URL(self.location.href)).searchParams.get('v')
   const CACHE = 'baustelle-' + SW_VERSION
   activate() löscht alle caches die mit 'baustelle-' anfangen aber
   nicht der aktuelle sind.

3. Client-Registration mit Auto-Update:
   - setInterval 60s reg.update()
   - visibilitychange-Listener für Tab-Fokus
   - updatefound → SKIP_WAITING postMessage
   - controllerchange → einmaliger location.reload

4. SHELL pre-cache enthält nur statische Dateien (index.php, share.html,
   icons). CSS/JS werden beim ersten fetch dynamisch gecached — so
   gibt es keinen Mix zwischen alten und neuen ?v= Versionen.

5. manifest.webmanifest start_url auf /custom/baustelle/index.php

Ergebnis: Ich deploye → mtime ändert sich → neue URLs → Browser holt
frische Files → SW aktiviert automatisch beim nächsten Tab-Fokus.
Nie wieder manuelles Hochzählen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 01:26:26 +02:00

97 lines
3.9 KiB
PHP

<?php
/* Baustelle PWA — Einstiegsseite.
* Cache-Versionierung via filemtime() nach Pattern claude-db #201.
* Bei jedem Deploy ändern sich die mtimes → neue URLs → Browser holt frische Files.
*/
$cssVersion = @filemtime(__DIR__.'/app.css') ?: time();
$jsVersion = max(
@filemtime(__DIR__.'/app.js') ?: time(),
@filemtime(__DIR__.'/lib/idb.js') ?: time(),
@filemtime(__DIR__.'/lib/api.js') ?: time(),
@filemtime(__DIR__.'/lib/offline.js')?: time(),
@filemtime(__DIR__.'/lib/router.js') ?: time()
);
$manifestVersion = @filemtime(__DIR__.'/manifest.webmanifest') ?: time();
$swVersion = max($cssVersion, $jsVersion, $manifestVersion);
// Einstiegsseite NIE cachen
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="theme-color" content="#1a1a1f">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>Baustelle</title>
<link rel="manifest" href="manifest.webmanifest?v=<?php echo $manifestVersion; ?>">
<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="icon" type="image/png" sizes="512x512" href="icons/icon-512.png">
<link rel="apple-touch-icon" href="icons/icon-192.png">
<link rel="stylesheet" href="app.css?v=<?php echo $cssVersion; ?>">
</head>
<body>
<div id="app">
<header id="topbar">
<button id="back-btn" class="icon-btn" style="display:none">←</button>
<h1 id="page-title">Baustelle</h1>
<button id="help-btn" class="icon-btn" title="Hilfe">❓</button>
<span id="status-badge">🟢</span>
</header>
<main id="main"></main>
<nav id="bottom-nav" style="display:none">
<button data-route="orders" class="active">📋 Aufträge</button>
<button data-route="reports">📑 Berichte</button>
<button data-route="settings">⚙️</button>
</nav>
</div>
<div id="toast-container"></div>
<script src="lib/idb.js?v=<?php echo $jsVersion; ?>"></script>
<script src="lib/api.js?v=<?php echo $jsVersion; ?>"></script>
<script src="lib/offline.js?v=<?php echo $jsVersion; ?>"></script>
<script src="lib/router.js?v=<?php echo $jsVersion; ?>"></script>
<script src="app.js?v=<?php echo $jsVersion; ?>"></script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('sw.js?v=<?php echo $swVersion; ?>').then(function (reg) {
// Periodisch updates prüfen
setInterval(function () { reg.update(); }, 60000);
// Bei Tab-Wechsel zurück: sofort prüfen
document.addEventListener('visibilitychange', function () {
if (document.visibilityState === 'visible') reg.update();
});
// Neuer SW installed → sofort aktivieren
reg.addEventListener('updatefound', function () {
var nw = reg.installing;
if (!nw) return;
nw.addEventListener('statechange', function () {
if (nw.state === 'installed' && navigator.serviceWorker.controller) {
nw.postMessage({ type: 'SKIP_WAITING' });
}
});
});
}).catch(function (e) { console.warn('SW reg failed', e); });
// Controller-Change → einmal neu laden
var reloaded = false;
navigator.serviceWorker.addEventListener('controllerchange', function () {
if (reloaded) return;
reloaded = true;
window.location.reload();
});
});
}
</script>
</body>
</html>