frontend: Update-Erkennung über vollen Content-Hash statt 300-Zeichen-Fenster

Die Erkennung hashte nur die ersten + letzten 300 Zeichen der Seite. Da der
Editor eine self-contained Datei ist (D19), liegen der gesamte App-Code UND die
Footer-Version als inline-Bundle in der Mitte — außerhalb des abgetasteten
Fensters. Anfang/Ende sind Build-übergreifend identisch, daher wurde eine neue
Version nie erkannt (nur die ETag änderte sich -> "Metadaten geändert, aber
Inhalt gleich").

- hashContent() (cyrb53) über den GESAMTEN Text als alleinige Wahrheit.
- ETag/Last-Modified-Vergleich entfernt: GitHub Pages liefert je Cache-Knoten
  wechselnde ETags für identischen Inhalt und löste damit die irreführende
  Metadaten-Meldung aus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-07-23 10:52:16 +02:00
co-authored by Claude Opus 4.8
parent 29e144eddf
commit 17d354059e
+24 -25
View File
@@ -1229,50 +1229,49 @@ mountBuildBadge();
ALLEN Builds aktiv. */ ALLEN Builds aktiv. */
const isProdBuild = import.meta.env.VITE_BUILD_BADGE === 'none'; const isProdBuild = import.meta.env.VITE_BUILD_BADGE === 'none';
/* Kompakter, deterministischer Hash (cyrb53) über den GESAMTEN HTML-Text.
Wichtig: Der frühere Ansatz „erste 300 + letzte 300 Zeichen" verfehlte JEDE
Änderung — die Seite ist eine self-contained Datei (D19), der komplette
App-Code UND die Footer-Version liegen als inline-Bundle in der MITTE, also
außerhalb des abgetasteten Fensters. Anfang/Ende sind Build-übergreifend
identisch. Nur ein Hash über den vollen Text erkennt neue Deployments. */
function hashContent(str){
let h1 = 0xdeadbeef, h2 = 0x41c6ce57;
for(let i = 0; i < str.length; i++){
const ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1>>>16), 2246822507) ^ Math.imul(h2 ^ (h2>>>13), 3266489909);
h2 = Math.imul(h2 ^ (h2>>>16), 2246822507) ^ Math.imul(h1 ^ (h1>>>13), 3266489909);
return (4294967296 * (2097151 & h2) + (h1>>>0)).toString(36);
}
async function checkForUpdates(){ async function checkForUpdates(){
try { try {
/* Fetch HTML mit Cache-Busting via Timestamp */ /* Fetch HTML mit Cache-Busting via Timestamp */
const resp = await fetch(location.href + '?t=' + new Date().getTime(), { cache: 'no-store' }); const resp = await fetch(location.href + '?t=' + new Date().getTime(), { cache: 'no-store' });
if(!resp.ok) throw new Error(`HTTP ${resp.status}`); if(!resp.ok) throw new Error(`HTTP ${resp.status}`);
/* Voller Content-Hash ist die alleinige Wahrheit. ETag/Last-Modified werden
bewusst NICHT mehr herangezogen: GitHub Pages liefert je Cache-Knoten
unterschiedliche ETags für identischen Inhalt und löste damit die
irreführende Meldung „Metadaten geändert, aber Inhalt gleich" aus. */
const html = await resp.text(); const html = await resp.text();
const lastModified = resp.headers.get('last-modified'); const hash = hashContent(html);
const etag = resp.headers.get('etag');
/* Prüfe auf echte Änderung via Content-Hash */
const stored = localStorage.getItem('werkbaum-html-hash'); const stored = localStorage.getItem('werkbaum-html-hash');
const hash = html.substring(0, 300) + html.substring(html.length - 300);
/* Vergleich: ETag/Last-Modified ODER Content-Hash */
const storedETag = localStorage.getItem('werkbaum-etag');
const storedModified = localStorage.getItem('werkbaum-last-modified');
const headersSame = (etag && etag === storedETag) || (lastModified && lastModified === storedModified);
const contentSame = stored && stored === hash;
if(contentSame && headersSame){
if(!stored){ if(!stored){
logUpdate('✓ Erste Prüfung Hash gespeichert'); logUpdate('✓ Erste Prüfung Hash gespeichert');
} else { } else if(hash === stored){
logUpdate('✓ Alles aktuell'); logUpdate('✓ Alles aktuell');
}
} else if(contentSame && !headersSame){
/* Content gleich, aber Headers geändert wahrscheinlich nur Build-Metadaten */
if(!stored) {
logUpdate('✓ Erste Prüfung Hash gespeichert');
} else { } else {
logUpdate('⚙ Metadaten geändert, aber Inhalt gleich');
}
} else if(stored && !contentSame) {
localStorage.setItem('werkbaum-update-available', 'true'); localStorage.setItem('werkbaum-update-available', 'true');
logUpdate('✅ NEUE VERSION ERKANNT!'); logUpdate('✅ NEUE VERSION ERKANNT!');
if(!document.hidden) checkAndShowUpdateNotification(); if(!document.hidden) checkAndShowUpdateNotification();
} else if(!stored) {
logUpdate('✓ Erste Prüfung Hash gespeichert');
} }
localStorage.setItem('werkbaum-html-hash', hash); localStorage.setItem('werkbaum-html-hash', hash);
if(etag) localStorage.setItem('werkbaum-etag', etag);
if(lastModified) localStorage.setItem('werkbaum-last-modified', lastModified);
} catch(err) { } catch(err) {
const msg = err.message || err.toString(); const msg = err.message || err.toString();
if(msg.includes('Failed to fetch')) { if(msg.includes('Failed to fetch')) {