Handy-App reparieren: Zugangsseite war nicht installierbar + Knopf ohne Rueckmeldung

Nutzer-Report 19.08.2026: "komm auf dem Handy nicht rein, hab es installiert und
klappt nicht" / "er nimmt meinen Code nicht an".

Hauptursache war ein veralteter Zugangscode (nur Doku, kein Code) -- beim Nachpruefen
kamen aber drei echte Fehler auf der Zugangsseite zutage, alle nur auf dem Handy spuerbar:

1. gate.html war NICHT installierbar. Ohne gueltige Sitzung leitet gate.js jeden Aufruf
   hierher um -- es ist also der Bildschirm, von dem aus man die App installiert. Genau
   dort fehlten Manifest-Verweis und Service-Worker-Registrierung, die Chrome fuer eine
   echte App verlangt (index.html hatte beides laengst, gate.html laedt main.js bewusst
   nicht). Wer vor dem ersten Login installierte, bekam nur eine leere Verknuepfung.
   Exakt derselbe Fehler wie am 07.08.2026 in VanVans Shop, hier nie aufgefallen.

2. Der "Oeffnen"-Knopf gab keinerlei Rueckmeldung und die Anfrage hatte kein Zeitlimit.
   Bleibt im Mobilfunknetz eine Antwort aus, haengt fetch() unbegrenzt -- es sieht aus,
   als sei der Tap nicht angekommen. Jetzt: Knopf sperrt sich sofort und zeigt "Wird
   geprueft...", Abbruch nach 10s mit klarer Meldung (AbortController), Doppel-Tap
   ignoriert. Letzteres ist hier besonders wichtig, weil server/gate.js nach 5
   Fehlversuchen die IP fuer 15 Minuten sperrt -- Doppel-Taps zaehlten bisher mit.

3. apple-touch-icon fehlte im GESAMTEN Projekt. iOS ignoriert das Manifest fuer das
   Startbildschirm-Symbol und liest nur dieses Tag -- auf dem iPhone gab es deshalb einen
   unscharfen Seiten-Screenshot statt des Logos. Neu erzeugt (180x180, vollflaechig ohne
   Alpha: iOS faerbt Transparenz schwarz, und icon-512.png hat nachgemessen transparente
   Ecken). Zentral ueber main.js in alle 34 Seiten eingehaengt statt 34x kopiert;
   gate.html hat es statisch, da ohne main.js.

Zusaetzlich maskable-Icons ergaenzt: Android beschneidet das Startsymbol auf einen Kreis.
Per Simulation der Sicherheitszone nachgemessen -- das bisherige Icon haette 19,87% des
Logos verloren (Husky-Ohren + Schriftzug). Die neuen Varianten (60%/59% Fuellgrad) liegen
bei exakt 0 abgeschnittenen Pixeln, Fuellgrad dafuer schrittweise eingemessen statt geraten.

Markenfarbe #8FD9EA aus dem vorhandenen Icon ausgelesen, keine neue Farbe erfunden.
Validiert: node --check, Manifest-JSON, Tag-Balance, alle 8 Pflicht-Zutaten vorhanden.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-19 20:13:10 +02:00
co-authored by Claude Opus 5
parent fed6a1f7c7
commit 80dbfc9367
6 changed files with 116 additions and 5 deletions
+79 -5
View File
@@ -6,6 +6,26 @@
<title>Zugang — DOGFATHER UNIVERSE</title>
<meta name="robots" content="noindex, nofollow" />
<link rel="icon" type="image/png" href="assets/img/favicon.png" />
<!-- BUGFIX (19.08.2026, Nutzer-Report vom Handy: "hab es installiert und klappt nicht"):
Diese Seite ist der ERSTE Bildschirm, den man ueberhaupt zu sehen bekommt (ohne gueltige
Sitzung leitet gate.js JEDEN Aufruf hierher um) -- also auch der Bildschirm, von dem aus
man die App installiert. Sie hatte aber weder Manifest-Verweis noch Service-Worker-
Registrierung, und beides verlangt Chrome, damit ueberhaupt eine echte, installierbare
App entsteht. Ergebnis: Wer vor dem ersten Login installiert hat, bekam bestenfalls eine
nackte Verknuepfung ohne Icon/Standalone-Modus. Exakt derselbe Fehler wie am 07.08.2026
in VanVans Shop (dort gefixt) -- hier war er nur nie aufgefallen, weil die Seite hinter
der Schranke (index.html) das alles laengst hatte. -->
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#0b0d10" />
<!-- iOS ignoriert das Manifest fuer das Startbildschirm-Symbol komplett und nimmt NUR
apple-touch-icon. Fehlt es, zeigt iPhone/iPad einen unscharfen Screenshot der Seite
statt des Logos. Bewusst eine eigene Datei OHNE Transparenz: iOS ersetzt Alpha durch
Schwarz, und icon-512.png hat transparente Ecken (nachgemessen) -> haette schwarze
Ecken gegeben. -->
<link rel="apple-touch-icon" href="/assets/img/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="DogFather" />
<link rel="stylesheet" href="assets/css/main.css" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css" />
<style>
@@ -281,35 +301,89 @@
// Beide Kacheln (Dogi/VanVan) nutzen dieselbe Logik und denselben
// /gate-auth-Endpunkt — nur das jeweils eigene Formular/Eingabefeld/
// Fehlerfeld unterscheidet sich.
/* BUGFIX (19.08.2026, Nutzer-Report vom Handy): Der Knopf gab beim Antippen KEINERLEI
Rueckmeldung und die Anfrage hatte kein Zeitlimit. Bleibt im Mobilfunknetz eine Antwort
aus, haengt fetch() unbegrenzt -- fuer die Person sieht es exakt so aus, als sei der Tap
gar nicht angekommen ("er nimmt meinen Code nicht an"), obwohl in Wahrheit nur die
Antwort fehlt. Zusaetzlich liess sich mehrfach hintereinander absenden, was bei der
5-Versuche-Sperre in server/gate.js besonders unguenstig ist: Doppel-Taps zaehlen als
mehrere Fehlversuche und koennen die eigene IP fuer 15 Minuten aussperren.
Derselbe Fix wie am 06.08.2026 in VanVans gate.html/verwaltung-code.html. */
const ZEITLIMIT_MS = 10000;
function bindeGateKachel(formId, inputId, fehlerId) {
const form = document.getElementById(formId);
const fehlerEl = document.getElementById(fehlerId);
const btn = form.querySelector("button[type=submit]");
const btnText = btn ? btn.querySelector(".btn-holo-text") : null;
const originalText = btnText ? btnText.textContent : "";
let laeuft = false;
function sperren(an) {
laeuft = an;
if (btn) btn.disabled = an;
if (btnText) btnText.textContent = an ? "Wird geprüft…" : originalText;
form.setAttribute("aria-busy", an ? "true" : "false");
}
form.addEventListener("submit", async function (e) {
e.preventDefault();
if (laeuft) return; // Doppel-Tap ignorieren
fehlerEl.classList.remove("zeige");
const code = document.getElementById(inputId).value.trim();
if (!code) return;
if (!code) {
fehlerEl.textContent = "Bitte zuerst den Zugangscode eingeben.";
fehlerEl.classList.add("zeige");
return;
}
sperren(true);
/* AbortController statt endlosem Warten. AbortSignal.timeout() waere kuerzer, kennen
aeltere Handy-Browser aber nicht -- deshalb bewusst der breiter unterstuetzte Weg. */
const abbruch = new AbortController();
const timer = setTimeout(() => abbruch.abort(), ZEITLIMIT_MS);
try {
const res = await fetch("/gate-auth", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code, next }),
signal: abbruch.signal,
});
const data = await res.json();
if (!res.ok || !data.ok) {
const data = await res.json().catch(() => null);
if (!res.ok || !data || !data.ok) {
fehlerEl.textContent = (data && data.error) || "Falscher Zugangscode.";
fehlerEl.classList.add("zeige");
sperren(false);
return;
}
/* Erfolg: Knopf bewusst gesperrt lassen — die Seite wird gleich gewechselt, ein
erneuter Tap in der Zwischenzeit waere nur verwirrend. */
if (btnText) btnText.textContent = "Willkommen 💙";
location.href = data.next || "/index.html";
} catch {
fehlerEl.textContent = "Verbindung fehlgeschlagen. Bitte nochmal versuchen.";
} catch (err) {
fehlerEl.textContent =
err && err.name === "AbortError"
? "Zeitüberschreitung — Verbindung zu langsam. Bitte nochmal versuchen."
: "Verbindung fehlgeschlagen. Bitte nochmal versuchen.";
fehlerEl.classList.add("zeige");
sperren(false);
} finally {
clearTimeout(timer);
}
});
}
bindeGateKachel("gate-form-dogi", "gate-code-dogi", "gate-fehler-dogi");
bindeGateKachel("gate-form-vanvan", "gate-code-vanvan", "gate-fehler-vanvan");
/* Service-Worker-Registrierung — auf allen anderen Seiten macht das main.js, diese Seite
ist aber bewusst eigenstaendig und laedt main.js nicht. Ohne registrierten Service
Worker bietet Chrome hier gar kein "App installieren" an (siehe Kommentar im Kopfbereich).
Bewusst identisch defensiv wie in main.js: nur ueber http(s), Fehler still schlucken. */
if ("serviceWorker" in navigator && /^https?:$/.test(location.protocol)) {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/sw.js").catch(() => {});
});
}
})();
</script>
</body>