Files
dogfather-universe/webdesign/sw.js
T
DogFatherGitandClaude Opus 5 469a6c195f Portal: Aufgabenliste und Postfach fuer den Kunden
Schritt 4 von 4 -- damit sind beide Wuensche vom 22.08.2026 vollstaendig:
"wenn sie drauf druecken sollen sie auch sehen was ich schon gemacht habe
von dem was im plan war" und "die kunden sollen mir auch nachrichten
hinterlassen koennen".

AUFGABENLISTE IM PROJEKT

Bisher sah der Kunde nur die Phase ("Design . 3/7") -- eine Zahl ohne
Inhalt. Sie beantwortet die eigentliche Frage nicht: WAS ist denn fertig?

Die Liste steht GANZ OBEN, direkt nach dem Projektkopf, noch vor
Zahlungen und Dateien. Die sind wichtig, aber sie sind nicht der Grund
fuer den Klick.

Zwei Entscheidungen praegen die Ansicht:

1. Offene Kundenpunkte stehen in einem EIGENEN Kasten ganz oben ("Das
   brauche ich noch von dir"), nicht nur farblich markiert irgendwo
   mittendrin. Wer eine lange Liste sieht, liest sie als Bericht ueber
   fremde Arbeit und ueberliest seinen eigenen Teil -- genau daraus
   entstehen die meisten Verzoegerungen. Ist nichts offen, steht das
   auch da: "Von dir wird gerade nichts gebraucht." Eine gute Nachricht
   darf ausgesprochen werden.

2. Der Kunde hakt seine eigenen Punkte selbst ab. Nur diese haben einen
   Knopf; bei fremden Punkten ist das Zeichen reine Anzeige. Ein Knopf,
   der nichts tut, laesst die Seite kaputt wirken. Geprueft: 4 Knoepfe
   bei 2 offenen eigenen Punkten (sie erscheinen zweimal), 5 feste.

Grosse Zahl statt Prozent: "2 von 6 erledigt" ist greifbar, "33 %" ist
eine Rechnung, die niemand fuehlt. Der Balken traegt die vier
Prozessfarben -- dieselben wie auf der Ablaufseite und in der Verwaltung.

Der Ton ist bewusst gewaehlt. Der Kunde liest die Liste, wenn er unsicher
ist -- also im Zweifel schon angespannt. "Wir warten auf dich" waere
Druck, "Das brauche ich noch von dir" ist eine Bitte.

POSTFACH AUF DER UEBERSICHT

Bewusst auf der Uebersicht, nicht im Projekt: Wer kein Projekt hat -- oder
eine Frage, die zu keinem gehoert -- konnte vorher gar nicht schreiben und
musste zur E-Mail greifen. Damit war der Verlauf weg, sobald man ihn
brauchte.

Niedrigschwellig: kein Betreff, kein Pflichtfeld, der Projektbezug ist
freiwillig. Wer glaubt, eine Nachricht muesse eine "richtige" Anfrage
sein, schreibt gar nicht erst -- und genau die kurzen Fragen sollen hier
landen. Wird nachgeladen, damit die Uebersicht sofort dasteht.

Projekt- und Aufgabendaten werden gleichzeitig angefordert statt
nacheinander -- sonst waere die Wartezeit die Summe beider Anfragen. Die
Aufgabenliste darf dabei fehlschlagen, ohne die Seite mitzureissen: Wer
wegen einer leeren Liste seine Zahlungen nicht mehr saehe, waere
schlechter dran als vorher.

EIN SICHTBARER FEHLER GEFUNDEN

Auf dem Screenshot stand "Ideen & Aenderungswuensche". Ursache ist
eine Falle mit zwei Wegen: Texte ueber data-i18n landen als HTML in der
Seite, dort ist "&" richtig. Derselbe Text im JavaScript laeuft aber
durch die Absicherung und wird ein zweites Mal kodiert. Derselbe Baustein
ist also je nach Verwendungsort richtig oder falsch.

Das kann man nicht im Kopf behalten -- deshalb neu pruef-texte.mjs mit
ZWEI Pruefungen:

  Anzeige:   alle 11 Seiten x 5 Sprachen = 55 Kombinationen, sichtbarer
             Text darf keine Kodierungsreste enthalten.
  Quelltext: welcher Baustein mit "&" wird irgendwo abgesichert
             eingesetzt?

Beide sind noetig. Gegengeprueft: Die Anzeigepruefung allein haette den
Fehler NICHT gefunden, weil die Ideen-Karte erst nach einer Anmeldung
erscheint. Die Quelltextpruefung findet ihn ohne Anzeige.

Auch die Quelltextpruefung selbst wurde zweimal gegengeprueft: Zuerst
meldete sie zusaetzlich po_senden ("Senden", voellig harmlos) -- ihre
Blockerkennung lief bis zum naechsten Schluessel und schluckte dabei
einen Kommentar mit "&". Jetzt zaehlt sie geschweifte Klammern. Ein
Fehlalarm im Pruefwerkzeug ist fast so schaedlich wie ein uebersehener
Fehler: Man gewoehnt sich daran, ihn wegzusehen.

GEPRUEFT

32 Pruefungen im Portal (Computer und Handy), alle bestanden. Darunter:
114 Texte in allen fuenf Sprachen vollstaendig, Kundenpunkte stehen vor
der langen Liste, Aufgaben vor den Zahlungen, Abhaken meldet den
richtigen Punkt, alle Kaestchen mindestens 24 px, keine Fehler im
Protokoll. Dazu 55 Seiten-Sprach-Kombinationen ohne Kodierungsreste.

Ein Fehler im Test selbst gefunden und behoben: Die Adressabfrage prueft
jetzt den PFAD statt der ganzen Adresse. Der API-Server heisst
"postfach.dogfather-universe.com" -- ein includes("/postfach") traf den
HOSTNAMEN und damit jede Anfrage, auch die Uebersicht.

Versionsstempel und Cache-Name auf v5.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-23 10:46:09 +02:00

192 lines
7.9 KiB
JavaScript

/* =====================================================================
sw.js — Service Worker NUR für /webdesign/
Macht den Bereich auf Handy und Computer als App installierbar und
sorgt dafür, dass die Oberfläche auch bei wackeliger Verbindung
sofort da ist.
⚠️ SICHERHEITSENTSCHEIDUNG, DIE HIER DEN AUSSCHLAG GIBT
Der Webdesign-Bereich ist zugangsgeschützt (server/webdesign-gate.js).
Ein Service Worker, der HTML-Seiten zwischenspeichert, würde diesen
Schutz aushebeln: Nach dem Abmelden — oder nachdem jemand anders das
Gerät in die Hand nimmt — lieferte der Zwischenspeicher die geschützten
Seiten samt Preisen, Anfragen und Kundendaten weiterhin aus, ohne dass
der Server je gefragt wird. Genau das ist der klassische Fehler bei
PWAs hinter einer Anmeldung.
Deshalb gilt hier strikt:
- HTML-Seiten werden NIEMALS zwischengespeichert. Immer Netz.
Ist kein Netz da, kommt eine schlichte Offline-Meldung — nicht die
zuletzt gesehene, möglicherweise vertrauliche Seite.
- Zwischengespeichert werden ausschließlich die neutralen Bausteine
unter /assets/ (Stilvorlagen, Skripte, Logo, Icons). Die enthalten
keine Kunden- oder Projektdaten und liegen ohnehin außerhalb der
Zugangsschranke.
- Antworten der API (postfach.dogfather-universe.com) werden
ebenfalls nie zwischengespeichert.
Das kostet Offline-Komfort. Der ist hier aber weniger wert als die
Zusicherung, dass nach dem Schließen der Sitzung wirklich nichts mehr
einsehbar ist.
===================================================================== */
/* Version im Namen: eine Änderung dieser Zeichenkette wirft beim nächsten
Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den
Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten
Stand fest. */
const CACHE_NAME = "dogfather-webdesign-v5";
/* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE
HTML-Datei in dieser Liste. */
const GRUNDGERUEST = [
"/assets/css/main.css",
"/assets/css/webdesign.css",
"/assets/js/wd-core.js",
"/assets/img/logo-dogfather-transparent.png",
"/assets/img/wd-app-192.png",
"/assets/img/wd-app-512.png",
];
self.addEventListener("install", (ereignis) => {
ereignis.waitUntil(
caches.open(CACHE_NAME).then((speicher) =>
/* addAll bricht komplett ab, wenn EINE Datei fehlt — dann wäre der
Service Worker gar nicht erst installiert und die App nicht
installierbar. Deshalb jede Datei einzeln und Fehler schlucken. */
Promise.all(
GRUNDGERUEST.map((pfad) =>
speicher.add(new Request(pfad, { cache: "reload" })).catch(() => {})
)
)
)
);
/* Sofort übernehmen statt auf das Schließen aller Tabs zu warten —
sonst bleibt nach einem Deploy tagelang die alte Fassung aktiv. */
self.skipWaiting();
});
self.addEventListener("activate", (ereignis) => {
ereignis.waitUntil(
caches.keys()
.then((namen) => Promise.all(
namen.filter((n) => n !== CACHE_NAME).map((n) => caches.delete(n))
))
.then(() => self.clients.claim())
);
});
/* Schlichte Offline-Meldung. Bewusst als erzeugte Antwort statt als
Datei — so kann sie nie mit echtem Seiteninhalt verwechselt werden.
Fünf Sprachen wären hier Überinstrumentierung: der Text erscheint nur
ohne Internet und ist absichtlich sehr kurz gehalten. */
function offlineSeite() {
return new Response(
`<!DOCTYPE html><html lang="de"><head><meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Keine Verbindung</title>
<style>
body{margin:0;min-height:100svh;display:flex;align-items:center;justify-content:center;
background:#05070b;color:#eef4fb;font-family:system-ui,sans-serif;text-align:center;padding:2rem}
.k{max-width:34ch}
h1{font-size:1.4rem;margin:0 0 .6rem}
p{color:#b3c2d4;line-height:1.6;margin:0 0 1.4rem}
button{min-height:48px;padding:.8rem 1.6rem;border:none;border-radius:999px;
background:#7fd0e8;color:#04070c;font-weight:700;font-size:1rem;cursor:pointer}
</style></head><body>
<div class="k">
<h1>Keine Verbindung</h1>
<p>Dogfather Webdesign braucht eine Internetverbindung. Sobald du wieder online bist, geht es normal weiter.</p>
<button onclick="location.reload()">Erneut versuchen</button>
</div></body></html>`,
{ status: 503, headers: { "Content-Type": "text/html; charset=utf-8" } }
);
}
self.addEventListener("fetch", (ereignis) => {
const anfrage = ereignis.request;
// Nur einfache Abrufe. Absendungen (POST) gehen immer direkt ans Netz.
if (anfrage.method !== "GET") return;
const url = new URL(anfrage.url);
// Fremde Server (API, externe Bilder) nie anfassen.
if (url.origin !== self.location.origin) return;
/* --- HTML: immer Netz, niemals zwischenspeichern (siehe Kopf) --- */
const willHtml = anfrage.mode === "navigate" ||
(anfrage.headers.get("accept") || "").includes("text/html");
if (willHtml) {
ereignis.respondWith(
fetch(anfrage).catch(() => offlineSeite())
);
return;
}
/* --- Zugangs-Endpunkte niemals zwischenspeichern --- */
if (url.pathname.startsWith("/webdesign/zugang-")) return;
/* --- Neutrale Bausteine unter /assets/ ---
⚠️ HIER WAR EIN ECHTER FEHLER (gemeldet 22.08.2026: "immer noch so").
Vorher lief das als "stale-while-revalidate": die gespeicherte Fassung
ging sofort raus, im Hintergrund wurde eine frische geholt. Das ist
schnell — bedeutet aber, dass jede Korrektur an CSS oder JavaScript
beim ersten Aufruf UNSICHTBAR bleibt und erst beim zweiten wirkt.
Genau das ist passiert: die Korrektur am gestreckten Logo war auf dem
Server längst live, der Browser hat aber die alte Stilvorlage aus
meinem eigenen Zwischenspeicher angezeigt. Ein Fehler, der wie ein
nicht behobener Fehler aussieht — die unangenehmste Sorte.
Jetzt: erst Netz, Zwischenspeicher nur als Notfallnetz.
Für Stilvorlagen und Skripte ist Richtigkeit wichtiger als die
eingesparten Millisekunden — eine falsche Darstellung kostet mehr
als ein kurzer Ladevorgang. Bilder und Symbole bleiben beim schnellen
Weg: die ändern sich praktisch nie, und wenn doch, bekommen sie einen
neuen Dateinamen. */
if (url.pathname.startsWith("/assets/")) {
const istCodeOderStil = /\.(css|js|mjs)$/i.test(url.pathname);
if (istCodeOderStil) {
// Netz zuerst
ereignis.respondWith(
fetch(anfrage)
.then((antwort) => {
if (antwort && antwort.status === 200 && antwort.type === "basic") {
const kopie = antwort.clone();
caches.open(CACHE_NAME).then((speicher) => speicher.put(anfrage, kopie));
}
return antwort;
})
.catch(() => caches.match(anfrage).then((g) => g || Response.error()))
);
return;
}
// Bilder, Symbole, Schriften: Zwischenspeicher zuerst
ereignis.respondWith(
caches.open(CACHE_NAME).then(async (speicher) => {
const gespeichert = await speicher.match(anfrage);
const ausDemNetz = fetch(anfrage)
.then((antwort) => {
/* Nur vollständige, eigene Antworten ablegen. Teilantworten
(206, z.B. bei Video-Sprüngen) lassen sich nicht sinnvoll
zwischenspeichern und würden den Speicher beschädigen. */
if (antwort && antwort.status === 200 && antwort.type === "basic") {
speicher.put(anfrage, antwort.clone());
}
return antwort;
})
.catch(() => null);
return gespeichert || (await ausDemNetz) || Response.error();
})
);
return;
}
/* Alles Übrige: kein respondWith -> der Browser macht es ganz normal
selbst. Das ist die sicherste Voreinstellung. */
});