diff --git a/frontend/src/app.js b/frontend/src/app.js
index bf6b0ba..eedd8da 100644
--- a/frontend/src/app.js
+++ b/frontend/src/app.js
@@ -1192,3 +1192,149 @@ mountBuildBadge();
updateButtonVisibility(); /* Initial call */
}
})();
+
+/* ---------- Service Worker für Update-Detection ---------- */
+if('serviceWorker' in navigator){
+ /* Service Worker als Blob registrieren (Single-File-App) */
+ const swCode = `
+ const CACHE_NAME = 'werkbaum-v1';
+ self.addEventListener('install', (e) => self.skipWaiting());
+ self.addEventListener('activate', (e) => e.waitUntil(clients.claim()));
+
+ self.addEventListener('message', async (e) => {
+ if(e.data?.type === 'CHECK_UPDATE') {
+ try {
+ const response = await fetch(self.location.href, { cache: 'no-store' });
+ const newContent = await response.text();
+ const stored = await getStored();
+ if(stored && stored !== newContent) {
+ notifyClients({ type: 'UPDATE_AVAILABLE' });
+ }
+ await storeContent(newContent);
+ } catch(err) { console.error('Update-Check:', err); }
+ }
+ });
+
+ function notifyClients(msg) {
+ self.clients.matchAll().then(clients =>
+ clients.forEach(c => c.postMessage(msg))
+ );
+ }
+
+ async function getStored() {
+ try {
+ const db = await new Promise((r, x) => {
+ const req = indexedDB.open('werkbaum-updates', 1);
+ req.onerror = () => x(req.error);
+ req.onsuccess = () => r(req.result);
+ req.onupgradeneeded = (e) => {
+ if(!e.target.result.objectStoreNames.contains('content')) {
+ e.target.result.createObjectStore('content', { keyPath: 'id' });
+ }
+ };
+ });
+ return new Promise((r, x) => {
+ const t = db.transaction('content', 'readonly');
+ const req = t.objectStore('content').get('index');
+ req.onerror = () => x(req.error);
+ req.onsuccess = () => r(req.result?.data);
+ });
+ } catch(e) { return null; }
+ }
+
+ async function storeContent(content) {
+ try {
+ const db = await new Promise((r, x) => {
+ const req = indexedDB.open('werkbaum-updates', 1);
+ req.onerror = () => x(req.error);
+ req.onsuccess = () => r(req.result);
+ });
+ return new Promise((r, x) => {
+ const t = db.transaction('content', 'readwrite');
+ const req = t.objectStore('content').put({ id: 'index', data: content });
+ req.onerror = () => x(req.error);
+ req.onsuccess = () => r();
+ });
+ } catch(e) {}
+ }
+ `;
+
+ const blob = new Blob([swCode], { type: 'application/javascript' });
+ const swUrl = URL.createObjectURL(blob);
+
+ navigator.serviceWorker.register(swUrl).then((registration) => {
+ /* Periodisch auf Updates prüfen */
+ setInterval(() => {
+ if(registration.active){
+ registration.active.postMessage({ type: 'CHECK_UPDATE' });
+ }
+ }, 60000); /* Jede Minute */
+
+ /* Starte erste Prüfung nach 5 Sekunden */
+ setTimeout(() => {
+ if(registration.active){
+ registration.active.postMessage({ type: 'CHECK_UPDATE' });
+ }
+ }, 5000);
+ }).catch((error) => {
+ console.error('Service Worker Registration fehlgeschlagen:', error);
+ });
+
+ /* Höre auf Update-Benachrichtigungen vom Service Worker */
+ navigator.serviceWorker.addEventListener('message', (event) => {
+ if(event.data && event.data.type === 'UPDATE_AVAILABLE'){
+ showUpdateNotification();
+ }
+ });
+}
+
+function showUpdateNotification(){
+ /* Entferne alte Benachrichtigung, falls vorhanden */
+ const existingNotif = document.getElementById('updateNotification');
+ if(existingNotif) existingNotif.remove();
+
+ /* Erstelle Benachrichtigungselement */
+ const notif = document.createElement('div');
+ notif.id = 'updateNotification';
+ notif.style.cssText = `
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ background: var(--or, #0F766E);
+ color: white;
+ padding: 12px 16px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ z-index: 1000;
+ font-size: 14px;
+ font-family: 'IBM Plex Sans', system-ui, sans-serif;
+ `;
+
+ notif.innerHTML = `
+ 📦 Neue Version verfügbar
+
+ `;
+
+ document.body.insertBefore(notif, document.body.firstChild);
+
+ document.getElementById('updateBtn').addEventListener('click', () => {
+ window.location.reload();
+ });
+
+ /* Auto-dismiss nach 30 Sekunden wenn nicht geklickt */
+ setTimeout(() => {
+ if(notif.parentNode) notif.remove();
+ }, 30000);
+}