Files
dogfather-universe/webdesign/diagnose.html
T
DogFatherGitandClaude Opus 5 1afb38258c Anfuehrungszeichen: drei Loecher in meiner eigenen Wache
Die zweite Sitzung hat in den Praesentationen des Vaults 83 Stellen
gefunden — und damit auf Loecher in MEINER Wache gezeigt. Ich habe
sie nachgemessen, alle drei waren echt.

LOCH 1 — SIE SAH NUR `server/workspace-*.js`

Also ausgerechnet nicht `workspace.js`, die groesste Datei des
Hauses. Der weitere Filter fand dort sofort drei Stellen:

    workspace.js:3404  Kanal „Chat-Moderation"     (Protokoll)
    workspace.js:3422  „${titel}" -> ${haus}       (Protokoll)
    workspace.js:3304  „${… || "ohne Titel"}"      (siehe Loch 3)

Das ist heute der DRITTE Fall von „die Wache kennt nur die halbe
Menge" — nach pruef-struktur (sah nur die Pruefdateien, nicht die
Anwendung) und pruef-fingermass (sah keine variable Breite).

LOCH 2 — IHR AUSDRUCK GILT JE ZEILE

Ein `„`, das erst in der naechsten Zeile mit einem geraden `"`
schliesst, fiel durch. Gemessen: drei echte Faelle, zwei davon
sichtbarer Seitentext.

    workspace/leistung.html       „nicht / zugeordnet"
    workspace/leistung.html       „Backstage-Tabelle / einfuegen"
    workspace/assets/js/ampel.js  „noch ' + 'verbessern"

Beim letzten laeuft das Zitat ueber eine Zeichenketten-Verkettung.

Neue Regel ohne Fehlalarme: erst alle RICHTIG geschlossenen Paare
entfernen (auch mehrzeilig), dann die einzeilig falschen (die meldet
die alte Regel). Was danach noch ein `„` hat und binnen drei Zeilen
ein gerades `"`, ist ein Fund. Ausgenommen bleibt das Zeichen ALS
WERT — in `content: "„";` und in den Sprachtabellen ist das `„` der
Inhalt und das `"` die Begrenzung.

LOCH 3 — EINE AUSNAHME, DIE EINEN FUND VERSCHLUCKT HAT

Gefunden durch die ZAHL, nicht durch den Blick: Der weitere Filter
liess die Ausnahmen von 12 auf 13 steigen. Die dreizehnte war

    `… „${zeile[titelSpalte] || "ohne Titel"}"`

Der Ausdruck blieb am `"` von `"ohne Titel"` haengen; als Inhalt kam
`${zeile[titelSpalte] || ` heraus, das endet auf ein Leerzeichen,
und damit galt „der Satz geht in der naechsten Zeile weiter". Das
echte Schlusszeichen stand hinter dem `}` und war gerade.

Dass die Ausnahmen gezaehlt UND genannt werden, hat ihn gefunden.
Genau dafuer steht die Zeile dort.

Behoben an der Wurzel: Anfuehrungszeichen innerhalb einer Einsetzung
`${…}` werden vorher durch X ersetzt, bei gleicher Laenge und
gleichen Zeilenumbruechen. Das nimmt einer ganzen Sorte
Fehlklassifizierung die Grundlage — die Ausnahmen fielen danach von
13 auf 11.

UND EIN VIERTES, BEIM MESSEN AUFGEFALLEN

`„Van-Van”` in assets/js/data-modis.js schliesst mit U+201D, dem
ENGLISCHEN Zeichen. Zweimal, beide Male in einer DEUTSCHEN Zeile
(`de:` und `"de-CH":`) — nicht einmal eine Uebersetzung, in der es
richtig waere. Eigene Regel dafuer, die keine Ausnahme braucht: Wer
mit `„` oeffnet, schliesst deutsch; in fremdsprachigen Zeilen steht
gar kein `„` am Anfang.

DIE AUSNAHMEZAHL IST JETZT STRENG. Sie stand auf „hoechstens 12" —
damit waere der Anstieg auf 13 nicht aufgefallen. Jetzt genau 11,
wie bei den Grundlinien in pruef-fingermass.

GEPRUEFT: pruef-struktur 68 -> 75 Pruefungen, 0 Fehler. Dazu
leistung-optik 59/0, ampel 47/0, treff 85/0, modi-verborgen 87/0 —
die Seiten, deren Text ich angefasst habe. Steuerzeichen: 830
Dateien, keines. Beide Stempel gesetzt.

Sechs echte Stellen berichtigt, vier neue Gegenproben.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-01 19:28:38 +02:00

368 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<link rel="icon" type="image/png" href="/assets/img/app-symbole/webdesign-32.png?v=202610011927" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/webdesign-180.png?v=202610011927" />
<meta charset="UTF-8" />
<!-- viewport-fit=cover ist die Voraussetzung dafuer, dass env(safe-area-inset-*)
ueberhaupt einen Wert liefert. Ohne diese Angabe gibt jedes env() null zurueck
-- die Sicherheitsabstaende im CSS waeren totes Recht. Zusammen mit
apple-mobile-web-app-status-bar-style=black-translucent (siehe unten) laeuft
der Inhalt sonst installiert unter Uhr und Akkuanzeige. -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>Diagnose — Dogfather Webdesign</title>
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#564e95" />
<style>
/* ---------------------------------------------------------------------
BEWUSST OHNE EXTERNE DATEIEN.
Diese Seite muss auch dann funktionieren, wenn genau das kaputt ist,
was sie untersuchen soll: veraltete CSS- und JS-Dateien aus dem
Zwischenspeicher. Würde sie webdesign.css oder wd-core.js laden,
könnte sie am selben Problem scheitern und wäre nutzlos.
Deshalb steht hier alles inline und es gibt keinen Service Worker.
--------------------------------------------------------------------- */
:root { color-scheme: dark; }
* { box-sizing: border-box; }
body {
margin: 0; padding: 1.5rem 1rem 3rem;
background: #05070b; color: #eef4fb;
font: 16px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
.hu { max-width: 720px; margin: 0 auto; }
h1 { font-size: 1.5rem; margin: 0 0 .3rem; }
p.lead { color: #8f9fb4; margin: 0 0 1.6rem; }
h2 { font-size: 1.05rem; margin: 1.8rem 0 .6rem; color: #7fd0e8; }
.zeile {
display: flex; gap: .8rem; align-items: flex-start;
padding: .8rem .9rem; margin-bottom: .5rem;
border: 1px solid rgba(127,208,232,.16); border-radius: 10px;
background: rgba(16,26,41,.5);
}
/* min-width:0 ist Pflicht, kein Feinschliff: Ohne das weigert sich ein
Flex-Kind, unter seine Inhaltsbreite zu schrumpfen, und langer Text
sprengt die Karte seitlich. */
.zeile .inhalt { min-width: 0; flex: 1 1 auto; }
.zeile b { display: block; margin-bottom: .1rem; }
/* Nur der Beschreibungstext, NICHT jeder span in der Zeile -- sonst
traefe die Regel auch den Statuskreis. */
.zeile .text { color: #8f9fb4; font-size: .9rem; overflow-wrap: anywhere; }
.mark {
flex: 0 0 26px; width: 26px; height: 26px; border-radius: 50%;
display: grid; place-items: center;
font-size: .95rem; line-height: 1;
/* Der Kreis enthaelt genau ein Zeichen. Sollte je mehr hineingeraten,
bricht es nicht die Seite auf, sondern wird abgeschnitten. */
overflow: hidden;
}
.gut { background: rgba(111,214,163,.15); color: #6fd6a3; }
.warn { background: rgba(232,200,119,.18); color: #e8c877; }
.bad { background: rgba(255,157,157,.15); color: #ff9d9d; }
.wart { background: rgba(143,159,180,.15); color: #8f9fb4; }
button {
font: inherit; font-weight: 700; cursor: pointer;
padding: .85rem 1.3rem; border-radius: 10px; border: 0;
background: linear-gradient(135deg, #a9e2f2, #7fd0e8); color: #04060a;
min-height: 48px;
}
button.zweit { background: transparent; color: #7fd0e8; border: 1px solid rgba(127,208,232,.34); }
.knoepfe { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1rem; }
.hinweis {
margin-top: 1.6rem; padding: 1rem 1.1rem; border-radius: 12px;
background: rgba(232,200,119,.08); border: 1px solid rgba(232,200,119,.32);
}
.hinweis b { color: #e8c877; }
code { background: rgba(127,208,232,.1); padding: .1rem .35rem; border-radius: 5px; font-size: .88em; }
</style>
</head>
<body>
<div class="hu">
<h1>Diagnose</h1>
<p class="lead">
Diese Seite prüft, warum der Verwaltungs- oder Portalbereich nicht lädt.
Sie lädt selbst keine externen Dateien — sie funktioniert also auch dann,
wenn genau die das Problem sind.
</p>
<h2>Prüfungen</h2>
<div id="ergebnis">
<div class="zeile"><span class="mark wart" aria-hidden="true">…</span><div class="inhalt"><b>Prüfung läuft</b><span class="text">Einen Moment.</span></div></div>
</div>
<div class="knoepfe">
<button type="button" id="reparieren">Zwischenspeicher leeren und neu laden</button>
<button type="button" class="zweit" id="nochmal">Nochmal prüfen</button>
</div>
<div class="hinweis">
<p style="margin:0 0 .4rem"><b>Was der grosse Knopf macht</b></p>
<p style="margin:0;font-size:.92rem">
Er meldet den Service Worker ab und löscht alle zwischengespeicherten
Dateien dieser Seite. Danach holt der Browser alles frisch vom Server.
<strong>Deine Anmeldung und deine Daten bleiben unberührt</strong> —
gelöscht werden nur Kopien von Dateien, die ohnehin jederzeit neu
geladen werden können.
</p>
</div>
</div>
<script>
/* Alles inline und ohne Abhängigkeiten — siehe Begründung im CSS-Kopf. */
(function () {
"use strict";
var API = "https://postfach.dogfather-universe.com";
var ziel = document.getElementById("ergebnis");
/* Die Zeile wird Element fuer Element gebaut, NICHT ueber innerHTML mit
anschliessendem querySelector.
Genau daran ist die erste Fassung gescheitert: Sie schrieb
d.querySelector("span").textContent = text -- und querySelector
liefert den ERSTEN passenden span. Das war der Statuskreis, nicht das
Textfeld darunter. Der ganze Beschreibungstext landete dadurch in
einem 26 Pixel breiten Kreis und lief als schmale Buchstabensaeule
ueber die halbe Seite.
Mit direkten Verweisen auf die erzeugten Elemente kann das nicht
passieren -- es gibt nichts zu verwechseln. */
function zeile(art, titel, text) {
var zeichen = { gut: "✓", warn: "!", bad: "✕", wart: "…" };
var d = document.createElement("div");
d.className = "zeile";
var kreis = document.createElement("span");
kreis.className = "mark " + art;
kreis.textContent = zeichen[art] || "?";
kreis.setAttribute("aria-hidden", "true");
var inhalt = document.createElement("div");
inhalt.className = "inhalt";
var ueberschrift = document.createElement("b");
ueberschrift.textContent = titel;
var beschreibung = document.createElement("span");
beschreibung.className = "text";
beschreibung.textContent = text;
inhalt.appendChild(ueberschrift);
inhalt.appendChild(beschreibung);
d.appendChild(kreis);
d.appendChild(inhalt);
ziel.appendChild(d);
}
async function pruefen() {
ziel.innerHTML = "";
/* Welche Fassung SOLL es sein? Steht im Service-Worker-Skript auf
dem Server. Ohne diesen Vergleich kann man nicht sagen, ob ein
gefuellter Zwischenspeicher gut oder schlecht ist -- die erste
Fassung meldete jeden Zwischenspeicher als "auffaellig", auch den
topaktuellen. Das ist Panikmache, kein Befund. */
var sollFassung = null;
try {
var swTxt = await (await fetch("/webdesign/sw.js?diagnose=" + Date.now(), { cache: "no-store" })).text();
var tref = swTxt.match(/CACHE_NAME\s*=\s*"([^"]+)"/);
if (tref) sollFassung = tref[1];
} catch (e) { /* nicht schlimm, dann eben ohne Vergleich */ }
/* 1. Service Worker.
Aktiv zu sein ist der NORMALFALL -- die Seite ist als App
installierbar, dafuer braucht es ihn. Auffaellig ist er nur, wenn
er eine veraltete Fassung vorhaelt. */
var swAktiv = 0;
if ("serviceWorker" in navigator) {
try {
var regs = await navigator.serviceWorker.getRegistrations();
swAktiv = regs.filter(function (r) {
return (r.scope || "").indexOf("/webdesign") >= 0;
}).length;
} catch (e) { swAktiv = -1; }
}
/* 2. Zwischenspeicher -- und der Vergleich, auf den es ankommt. */
var gefunden = [];
if ("caches" in window) {
try {
var namen = await caches.keys();
gefunden = namen.filter(function (n) { return n.indexOf("webdesign") >= 0; });
} catch (e) { gefunden = []; }
}
var aktuell = !sollFassung || !gefunden.length || gefunden.indexOf(sollFassung) >= 0;
var veraltet = gefunden.filter(function (n) { return n !== sollFassung; });
if (swAktiv < 0) {
zeile("warn", "Service Worker", "Konnte nicht geprüft werden.");
} else if (!swAktiv) {
zeile("gut", "Service Worker", "Keiner aktiv — alles kommt direkt vom Server.");
} else if (aktuell && !veraltet.length) {
zeile("gut", "Service Worker",
"Aktiv und aktuell. Das ist so gewollt — dadurch lässt sich die Seite als App installieren.");
} else {
zeile("warn", "Service Worker",
"Aktiv, hält aber eine veraltete Fassung vor. Der Knopf unten setzt ihn zurück.");
}
if (!("caches" in window)) {
zeile("gut", "Zwischenspeicher", "Wird von diesem Browser nicht genutzt.");
} else if (!gefunden.length) {
zeile("gut", "Zwischenspeicher", "Leer.");
} else if (aktuell && !veraltet.length) {
zeile("gut", "Zwischenspeicher",
"Aktuelle Fassung (" + gefunden.join(", ") + ")" +
(sollFassung ? " — stimmt mit dem Server überein." : "."));
} else {
zeile("warn", "Zwischenspeicher",
"Veraltet: " + veraltet.join(", ") +
(sollFassung ? ". Der Server liefert " + sollFassung + "." : "."));
}
/* 3. Ist der Server erreichbar? Ein Endpunkt, der ohne Anmeldung
antwortet -- 401 ist hier die RICHTIGE Antwort und beweist,
dass alles bis dorthin funktioniert. */
try {
var t0 = Date.now();
var a = await fetch(API + "/webdesign/admin/anfragen", { cache: "no-store" });
var ms = Date.now() - t0;
if (a.status === 401) {
zeile("gut", "Server erreichbar", "Antwortet in " + ms + " ms (Status 401 = erwartet ohne Anmeldung).");
} else if (a.status === 404) {
zeile("bad", "Server veraltet", "Status 404 — der interne Dienst kennt diese Adresse noch nicht.");
} else {
zeile("warn", "Server antwortet", "Status " + a.status + " nach " + ms + " ms.");
}
} catch (e) {
zeile("bad", "Server NICHT erreichbar",
"Der Browser kommt nicht an " + API + " heran. " + (e.message || ""));
}
/* 4. Die neuen Adressen */
try {
var b = await fetch(API + "/webdesign/admin/einstellungen", { cache: "no-store" });
if (b.status === 401) zeile("gut", "Zahlungen-Bereich", "Vorhanden und geschützt.");
else if (b.status === 404) zeile("bad", "Zahlungen-Bereich fehlt",
"Der Server muss noch aktualisiert werden (git pull + Neustart).");
else zeile("warn", "Zahlungen-Bereich", "Status " + b.status + ".");
} catch (e) {
zeile("bad", "Zahlungen-Bereich", "Nicht erreichbar: " + (e.message || ""));
}
/* 4b. DER ENTSCHEIDENDE TEST: derselbe Ablauf wie in der Verwaltung,
mit einem echten Ausweis.
Ein 401 ohne Anmeldung beweist nur, dass die Adresse existiert.
Es beweist NICHT, dass die Antwort auch dann kommt, wenn man
angemeldet ist -- und genau dort blieb die Verwaltung stehen.
Deshalb hier der volle Weg: Ausweis holen, damit die Einstellungen
abrufen, Zeit messen. Bricht es ab, steht hier die Ursache. */
try {
var t1 = Date.now();
var aw = await fetch("/webdesign/api-ausweis", { cache: "no-store" });
var awMs = Date.now() - t1;
if (!aw.ok) {
zeile("bad", "Ausweis für die Verwaltung",
"Die Zugangswand gibt keinen aus (Status " + aw.status + "). "
+ "Dann fragt die Verwaltung nach dem Code — das ist die Ursache.");
} else {
var awD = await aw.json();
if (!awD.ausweis) {
zeile("bad", "Ausweis für die Verwaltung",
"Kein Ausweis" + (awD.grund ? " (" + awD.grund + ")" : "")
+ ". Ohne ihn kann die Verwaltung keine Daten laden.");
} else {
zeile("gut", "Ausweis für die Verwaltung", "Erhalten in " + awMs + " ms.");
/* Jetzt der eigentliche Abruf -- mit eigenem Zeitlimit, damit
die Diagnose nicht selbst haengt, wenn die Adresse haengt. */
var abbruch = new AbortController();
var wecker = setTimeout(function () { abbruch.abort(); }, 12000);
var t2 = Date.now();
try {
var ein = await fetch(API + "/webdesign/admin/einstellungen", {
headers: { Authorization: "Bearer " + awD.ausweis },
cache: "no-store",
signal: abbruch.signal,
});
var einMs = Date.now() - t2;
var txt = await ein.text();
if (ein.status === 200) {
zeile("gut", "Einstellungen abrufen",
"Erfolgreich in " + einMs + " ms (" + txt.length + " Zeichen Antwort).");
} else {
zeile("bad", "Einstellungen abrufen",
"Status " + ein.status + " nach " + einMs + " ms. Antwort: " + txt.slice(0, 160));
}
} catch (e2) {
zeile("bad", "Einstellungen abrufen",
e2.name === "AbortError"
? "KEINE Antwort innerhalb von 12 Sekunden — die Anfrage bleibt hängen."
: "Fehlgeschlagen: " + (e2.message || e2.name));
} finally { clearTimeout(wecker); }
}
}
} catch (e) {
zeile("bad", "Ausweis für die Verwaltung", "Nicht abrufbar: " + (e.message || ""));
}
/* 5. Anmeldung im Browser vorhanden? */
try {
var tok = sessionStorage.getItem("wd-vw-token");
if (tok) zeile("gut", "Anmeldung Verwaltung", "Ein Sitzungsschlüssel liegt vor.");
else zeile("gut", "Anmeldung Verwaltung",
"In diesem Tab keiner — das ist normal und KEIN Fehler. Der Schlüssel gilt "
+ "je Tab, und er soll beim Schliessen der Seite ohnehin verschwinden. "
+ "Die Verwaltung fragt dann einmal nach dem Zugangscode.");
} catch (e) {
zeile("warn", "Anmeldung", "Speicher nicht lesbar: " + e.message);
}
/* 6. Welche Fassung liegt im Browser? */
try {
var c = await fetch("/assets/css/webdesign.css?diagnose=" + Date.now(), { cache: "no-store" });
var txt = await c.text();
var hatNeu = txt.indexOf("--wd-blau-rgb") >= 0;
zeile(hatNeu ? "gut" : "bad", "Gestaltungsdatei",
hatNeu ? "Aktuelle Fassung wird ausgeliefert."
: "VERALTETE Fassung. Der Knopf unten hilft.");
} catch (e) {
zeile("warn", "Gestaltungsdatei", "Nicht prüfbar: " + e.message);
}
}
document.getElementById("nochmal").addEventListener("click", pruefen);
document.getElementById("reparieren").addEventListener("click", async function () {
var k = this;
k.disabled = true;
k.textContent = "Wird bereinigt …";
try {
if ("serviceWorker" in navigator) {
var regs = await navigator.serviceWorker.getRegistrations();
for (var i = 0; i < regs.length; i++) await regs[i].unregister();
}
if ("caches" in window) {
var namen = await caches.keys();
for (var j = 0; j < namen.length; j++) await caches.delete(namen[j]);
}
} catch (e) { /* weiter -- neu laden hilft meistens trotzdem */ }
k.textContent = "Fertig — wird neu geladen …";
/* Mit Zeitstempel, damit auch der Browser-eigene Zwischenspeicher
umgangen wird. */
setTimeout(function () {
location.href = "/webdesign/verwaltung.html?frisch=" + Date.now();
}, 700);
});
pruefen();
})();
</script>
</body>
</html>