Systemabnahme: fuenf Altlasten geschlossen

Vollstaendiger Durchlauf ueber alle Pruefungen. Dabei kamen fuenf Dinge
ans Licht, die teils seit Wochen offen standen.

1. WCAG-KONTRAST -- der einzige Punkt, der echte Besucher betraf

Die lila Schilder erreichten nur 4,08:1, verlangt sind 4,5:1 fuer
normalen Text. 17 Fundstellen, alle dieselbe Ursache: Die Schrift nutzte
den vollen Ton Aurora Violet.

Gemessen wurde nicht geschaetzt: Untergrund rgb(38,41,81) aus dem
echten Bild ausgelesen, Schrift 12,5 Punkte -- und fett zaehlt erst ab
18,5 Punkten als "grosser Text".

Neu ist --wd-lila-hell (#C197FF, 6,02:1). Bewusst NICHT der knappste
Wert: #B380FF haette mit 4,91:1 gereicht, aber dasselbe Schild steht
auch ueber der Buehne im Verwaltungsbereich. Ein Ton, der nur an einer
Stelle knapp besteht, faellt beim naechsten Hintergrund wieder durch.
Dasselbe Muster wie --wd-blau-hell, das genau deshalb existiert.

2. GEHEIMNIS-TEST -- der Test war veraltet, nicht der Code

Er verlangte ".env hat Vorrang", die Umsetzung macht das Gegenteil. Die
Begruendung im Code ueberzeugt: Wer PayPal-Daten im Formular eintraegt,
erwartet, dass sie gelten. Andernfalls koennte ein alter Wert in der
.env sie stumm ueberstimmen -- und man sucht stundenlang.

Der Test prueft jetzt die tatsaechliche Reihenfolge, dazu neu, dass ein
gleichzeitig vorhandener Serverwert auch angezeigt wird.

Ausserdem endete der Lauf trotz gruener Pruefungen mit einer
Fehlermeldung: Unter Windows haelt eine offene SQLite-Datei eine Sperre,
das Aufraeumen scheiterte mit EPERM. Auf Linux waere es
durchgelaufen -- dasselbe Skript mit unterschiedlichem Ergebnis je
Rechner. Jetzt wird erst geschlossen, dann geloescht, und das Aufraeumen
kann den Lauf nicht mehr zum Scheitern bringen.

3. PORTAL-TEST -- ebenfalls veraltet

Er suchte "1500,00" und schlug fehl, seit der Formatierer den
Tausenderpunkt setzt. "1.500,00 EUR" ist die korrekte deutsche
Schreibweise; gerade ab vier Stellen macht der Punkt eine Zahl auf einen
Blick lesbar.

4. ZWEI SEITEN OHNE WOERTERBUCH -- eine Entscheidung, keine Luecke

"diagnose" ist ein Betriebswerkzeug. "rechtliches" ist der heiklere
Fall: Rechtstexte durch eine ungepruefte Uebersetzung zu schicken ist
gefaehrlicher, als sie einsprachig zu lassen. Ein Fehler in einer
Widerrufsbelehrung wirkt gegen den Verfasser.

Die Pruefung meldete beides als "Datei fehlt" -- das las sich wie ein
Versehen und stand deshalb dauerhaft in der Fehlerliste, ohne dass
jemand etwas tat. Jetzt sind beide benannt und begruendet.

5. TAG-UNGLEICHGEWICHT -- eine Fehlmessung

Die Pruefung zaehlte auch HTML-Schnipsel, die als Zeichenketten im
JavaScript stehen. Dort steht ein oeffnendes <div> regelmaessig in einer
anderen Zeichenkette als sein </div>, weil die Teile erst beim
Zusammensetzen ein Ganzes ergeben.

Gegengeprueft im Browser: geparster Baum einwandfrei, kein
Skriptfehler, Verschachtelung unauffaellig. Es war nie ein
Strukturfehler. Die Meldung stand aber monatelang als "2 Fehler" da und
haette jede echte Meldung entwertet, die dazugekommen waere.

STAND

  Browser  717 Pruefungen   0 offen
  Server   566 Pruefungen   0 offen
  i18n     keine Fehler
  WCAG     0 Fundstellen (vorher 17)

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-25 17:13:42 +02:00
co-authored by Claude Opus 5
parent 686e8924bb
commit 0ef64ba222
22 changed files with 109 additions and 11 deletions
+18 -1
View File
@@ -110,6 +110,23 @@
--wd-aqua: rgb(var(--wd-aqua-rgb)); /* Fokusring, Live-Zustand */ --wd-aqua: rgb(var(--wd-aqua-rgb)); /* Fokusring, Live-Zustand */
--wd-lila: rgb(var(--wd-lila-rgb)); /* sekundaere Bereiche, Spezial */ --wd-lila: rgb(var(--wd-lila-rgb)); /* sekundaere Bereiche, Spezial */
--wd-lila-tief: rgb(var(--wd-indigo-rgb)); --wd-lila-tief: rgb(var(--wd-indigo-rgb));
/* Aurora Violet als SCHRIFTFARBE auf einer lila Flaeche.
Der volle Ton (#A76BFF) erreicht dort nur 4,08:1 -- gemessen auf
dem echten Untergrund rgb(38,41,81), den eine lila Kachel unter
einem Schild erzeugt. WCAG AA verlangt 4,5:1, denn die Schrift ist
12,5 Punkte gross und damit normaler Text; fett zaehlt erst ab
18,5 Punkten als "gross".
#C197FF sind 30 % Richtung Weiss und erreichen 6,02:1. Bewusst
nicht der knappste Wert (#B380FF haette mit 4,91:1 gereicht):
Dasselbe Schild steht auch auf anderen Flaechen, etwa ueber der
Buehne im Verwaltungsbereich. Ein Ton, der nur an einer Stelle
knapp besteht, faellt beim naechsten Hintergrund wieder durch.
Dasselbe Muster wie --wd-blau-hell, das aus genau diesem Grund
existiert. */
--wd-lila-hell: #C197FF;
--wd-indigo: rgb(var(--wd-indigo-rgb)); --wd-indigo: rgb(var(--wd-indigo-rgb));
--wd-gold: rgb(var(--wd-gold-rgb)); /* NUR Hinweise, Zahlungen, VIP */ --wd-gold: rgb(var(--wd-gold-rgb)); /* NUR Hinweise, Zahlungen, VIP */
--wd-gold-tief: #b8934a; --wd-gold-tief: #b8934a;
@@ -612,7 +629,7 @@ a.wd-karte:hover, .wd-karte--klickbar:hover {
border: 1px solid var(--wd-rand-stark); border: 1px solid var(--wd-rand-stark);
color: var(--wd-blau-hell); color: var(--wd-blau-hell);
} }
.wd-tag--lila { background: rgb(var(--wd-lila-rgb) / .12); border-color: rgb(var(--wd-lila-rgb) / .34); color: var(--wd-lila); } .wd-tag--lila { background: rgb(var(--wd-lila-rgb) / .12); border-color: rgb(var(--wd-lila-rgb) / .34); color: var(--wd-lila-hell); }
.wd-tag--gold { background: rgb(var(--wd-gold-rgb) / .12); border-color: rgb(var(--wd-gold-rgb) / .34); color: var(--wd-gold); } .wd-tag--gold { background: rgb(var(--wd-gold-rgb) / .12); border-color: rgb(var(--wd-gold-rgb) / .34); color: var(--wd-gold); }
.wd-tag--ok { background: rgb(var(--wd-ok-rgb) / .12); border-color: rgb(var(--wd-ok-rgb) / .34); color: var(--wd-ok); } .wd-tag--ok { background: rgb(var(--wd-ok-rgb) / .12); border-color: rgb(var(--wd-ok-rgb) / .34); color: var(--wd-ok); }
/* Aqua fuer die Arcade. Als Schriftfarbe unbedenklich: Hyper Aqua liegt /* Aqua fuer die Arcade. Als Schriftfarbe unbedenklich: Hyper Aqua liegt
Binary file not shown.

Before

Width:  |  Height:  |  Size: 923 KiB

After

Width:  |  Height:  |  Size: 914 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 466 KiB

After

Width:  |  Height:  |  Size: 591 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 614 KiB

After

Width:  |  Height:  |  Size: 793 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 KiB

After

Width:  |  Height:  |  Size: 925 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 796 KiB

After

Width:  |  Height:  |  Size: 720 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

After

Width:  |  Height:  |  Size: 418 KiB

+44 -4
View File
@@ -56,12 +56,37 @@ pruefe("das Secret ist als geheim markiert", st.PAYPAL_CLIENT_SECRET.wert === nu
pruefe("die Betriebsart wird dagegen ausgeschrieben", st.PAYPAL_ENV.wert !== undefined); pruefe("die Betriebsart wird dagegen ausgeschrieben", st.PAYPAL_ENV.wert !== undefined);
pruefe("die Herkunft wird benannt", st.PAYPAL_CLIENT_SECRET.quelle === "verwaltung"); pruefe("die Herkunft wird benannt", st.PAYPAL_CLIENT_SECRET.quelle === "verwaltung");
L("\n4. DIE .env HAT VORRANG"); /* Frueher stand hier "DIE .env HAT VORRANG" -- und der Test schlug fehl,
weil die Umsetzung laengst die umgekehrte Reihenfolge hat.
Der Test war veraltet, nicht der Code. Die Begruendung steht im Kopf
von lib/webdesign-geheimnisse.js: Wer einen Wert im Formular setzt,
erwartet, dass er gilt. Waere es andersherum, koennte Filipe PayPal-
Zugangsdaten in der Verwaltung eintragen, ohne dass sich irgendetwas
aendert -- ein alter Wert in der .env wuerde sie stumm ueberstimmen.
Wichtig ist deshalb nicht nur die Reihenfolge selbst, sondern auch,
dass die Anzeige sie ehrlich benennt. Genau das wird hier mitgeprueft. */
L("\n4. DIE VERWALTUNG HAT VORRANG, DIE .env IST DER RUECKFALL");
process.env.PAYPAL_CLIENT_ID = "AUS-DER-ENV"; process.env.PAYPAL_CLIENT_ID = "AUS-DER-ENV";
pruefe("ein Wert auf dem Server sticht die Verwaltung", pruefe("ein Wert aus der Verwaltung sticht den Server",
geheim.einstellung("PAYPAL_CLIENT_ID") === "AbC-123");
pruefe("und die Anzeige nennt die Verwaltung als Quelle",
geheim.stand().PAYPAL_CLIENT_ID.quelle === "verwaltung");
/* Liegt in BEIDEN etwas, muss man das sehen koennen -- sonst sucht man
spaeter stundenlang, warum der Serverwert nicht wirkt. */
pruefe("dass auch auf dem Server einer liegt, wird vermerkt",
geheim.stand().PAYPAL_CLIENT_ID.auchAufServer === true);
/* Ohne Eintrag in der Verwaltung greift der Server-Wert. */
await geheim.einstellungSetzen("PAYPAL_CLIENT_ID", "");
pruefe("ohne Verwaltungswert greift der Server",
geheim.einstellung("PAYPAL_CLIENT_ID") === "AUS-DER-ENV"); geheim.einstellung("PAYPAL_CLIENT_ID") === "AUS-DER-ENV");
pruefe("und das wird auch so angezeigt", geheim.stand().PAYPAL_CLIENT_ID.quelle === "server"); pruefe("und die Anzeige nennt dann den Server",
geheim.stand().PAYPAL_CLIENT_ID.quelle === "server");
delete process.env.PAYPAL_CLIENT_ID; delete process.env.PAYPAL_CLIENT_ID;
await geheim.einstellungSetzen("PAYPAL_CLIENT_ID", "AbC-123");
pruefe("faellt der Server-Wert weg, greift wieder die Verwaltung", pruefe("faellt der Server-Wert weg, greift wieder die Verwaltung",
geheim.einstellung("PAYPAL_CLIENT_ID") === "AbC-123"); geheim.einstellung("PAYPAL_CLIENT_ID") === "AbC-123");
@@ -89,5 +114,20 @@ pruefe("das Laden mit falschem Schluessel wirft nicht", !gestuerzt);
pruefe("der Wert gilt dann als nicht vorhanden", geheim.einstellung("PAYPAL_CLIENT_SECRET") === ""); pruefe("der Wert gilt dann als nicht vorhanden", geheim.einstellung("PAYPAL_CLIENT_SECRET") === "");
L(`\n===== ${ok} bestanden, ${fehler} fehlgeschlagen =====`); L(`\n===== ${ok} bestanden, ${fehler} fehlgeschlagen =====`);
process.on("exit", () => rmSync(tmp, { recursive: true, force: true })); /* Erst die Datenbank schliessen, DANN das Verzeichnis loeschen.
Unter Windows haelt eine offene SQLite-Datei eine Sperre auf ihre
Datei. Der Loeschversuch scheiterte dadurch mit EPERM, und der
Testlauf endete mit einer Fehlermeldung -- obwohl jede einzelne
Pruefung gruen war. Auf Linux faellt das nicht auf, weil sich dort
offene Dateien loeschen lassen: Dasselbe Skript haette je nach
Rechner ein anderes Ergebnis geliefert.
Das Aufraeumen selbst darf den Lauf nie zum Scheitern bringen. Ein
liegengebliebenes Temp-Verzeichnis ist ein Schoenheitsfehler, kein
Testergebnis. */
process.on("exit", () => {
try { db.close(); } catch (e) { /* war schon zu */ }
try { rmSync(tmp, { recursive: true, force: true }); } catch (e) { /* egal */ }
});
process.exitCode = fehler ? 1 : 0; process.exitCode = fehler ? 1 : 0;
+8 -1
View File
@@ -200,7 +200,14 @@ console.log("\n4. Was Anna sehen darf");
pruefe("sieht ihre Angebotsdatei", t.includes("Annas Angebot.pdf")); pruefe("sieht ihre Angebotsdatei", t.includes("Annas Angebot.pdf"));
pruefe("sieht INTERNE Arbeitsdateien NICHT", !t.includes("INTERNE-ARBEITSDATEI"), pruefe("sieht INTERNE Arbeitsdateien NICHT", !t.includes("INTERNE-ARBEITSDATEI"),
"-> fuer_kunde = 0 bleibt unsichtbar"); "-> fuer_kunde = 0 bleibt unsichtbar");
pruefe("Betrag wird lesbar mitgeliefert", /1500,00/.test(t)); /* 1.500,00 -- MIT Tausenderpunkt.
Der Test suchte frueher nach "1500,00" und schlug fehl, seit der
Formatierer den Punkt setzt. Er war veraltet, nicht der Code:
"1.500,00 EUR" ist die korrekte deutsche Schreibweise, und gerade
bei Betraegen ab vier Stellen ist der Punkt das, was eine Zahl
ueberhaupt auf einen Blick lesbar macht. */
pruefe("Betrag wird lesbar mitgeliefert", /1\.500,00/.test(t),
"-> mit Tausenderpunkt");
pruefe("Speichername der Datei wird NICHT verraten", !t.includes("x1.pdf"), pruefe("Speichername der Datei wird NICHT verraten", !t.includes("x1.pdf"),
"-> interner Dateiname geht niemanden etwas an"); "-> interner Dateiname geht niemanden etwas an");
} }
+38 -4
View File
@@ -48,7 +48,24 @@ for (const datei of seiten) {
Franzosen oder Portugiesen sein. Ein Portal, das die Sprachwahl der Franzosen oder Portugiesen sein. Ein Portal, das die Sprachwahl der
oeffentlichen Seite ignoriert, waere genau der Bruch, den ein Kunde oeffentlichen Seite ignoriert, waere genau der Bruch, den ein Kunde
sofort bemerkt. */ sofort bemerkt. */
const NUR_DEUTSCH = ["verwaltung"]; /* Zwei weitere Seiten kamen hinzu, beide aus eigenem Grund:
"diagnose" ist ein Werkzeug fuer den Betrieb, bewusst ohne externe
Dateien gebaut. Es landet dort niemand ausser Dogfather selbst.
"rechtliches" ist der heiklere Fall und ausdruecklich eine
ENTSCHEIDUNG, keine Luecke: Rechtstexte durch eine nicht gepruefte
Uebersetzung zu schicken ist gefaehrlicher, als sie einsprachig zu
lassen -- ein Uebersetzungsfehler in einer Widerrufsbelehrung oder
in den Geschaeftsbedingungen wirkt gegen den Verfasser. Wenn die
Seite mehrsprachig werden soll, gehoert die Uebersetzung von
jemandem geprueft, der dafuer geradesteht, nicht aus einem
Woerterbuch nebenbei erzeugt.
Frueher meldete die Pruefung fuer beide schlicht "Datei fehlt".
Das las sich wie ein Versehen und stand deshalb monatelang als
Fehler in der Liste, ohne dass jemand etwas tat. */
const NUR_DEUTSCH = ["verwaltung", "diagnose", "rechtliches"];
const einsprachig = NUR_DEUTSCH.includes(name); const einsprachig = NUR_DEUTSCH.includes(name);
if (!existsSync(i18nDatei) && !einsprachig) { if (!existsSync(i18nDatei) && !einsprachig) {
@@ -57,10 +74,27 @@ for (const datei of seiten) {
continue; continue;
} }
if (einsprachig) { if (einsprachig) {
console.log(" bewusst nur Deutsch (interner Bereich) — nur Struktur geprueft"); console.log(" bewusst nur Deutsch — nur Struktur geprueft");
/* NUR das echte Markup zaehlen, nicht die Skriptbloecke.
Die Verwaltung baut ihre Listen im Browser zusammen. Im
JavaScript stehen deshalb HTML-Schnipsel als Zeichenketten --
und dort steht ein oeffnendes <div> regelmaessig in einer
anderen Zeichenkette als sein </div>, weil die Teile erst beim
Zusammensetzen ein Ganzes ergeben.
Ohne diesen Ausschluss meldete die Pruefung dauerhaft je ein
div und ein button zu viel. Gegengeprueft im Browser: Der
geparste Baum ist einwandfrei, es gibt keinen Skriptfehler und
die Verschachtelungstiefe ist unauffaellig. Es war also nie ein
Strukturfehler, sondern eine Fehlmessung -- die aber monatelang
als "2 Fehler" in der Liste stand und damit jede echte Meldung
entwertet hat, die dazugekommen waere. */
const nurMarkup = html.replace(/<script\b[\s\S]*?<\/script>/gi, "");
for (const tag of ["div", "section", "main", "article", "details", "form", "button"]) { for (const tag of ["div", "section", "main", "article", "details", "form", "button"]) {
const auf = (html.match(new RegExp(`<${tag}\\b`, "g")) || []).length; const auf = (nurMarkup.match(new RegExp(`<${tag}\\b`, "g")) || []).length;
const zu = (html.match(new RegExp(`</${tag}>`, "g")) || []).length; const zu = (nurMarkup.match(new RegExp(`</${tag}>`, "g")) || []).length;
if (auf !== zu) { console.log(` TAG-UNGLEICHGEWICHT <${tag}>: ${auf} offen, ${zu} geschlossen`); fehler++; } if (auf !== zu) { console.log(` TAG-UNGLEICHGEWICHT <${tag}>: ${auf} offen, ${zu} geschlossen`); fehler++; }
} }
continue; continue;
+1 -1
View File
@@ -34,7 +34,7 @@
Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den
Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten
Stand fest. */ Stand fest. */
const CACHE_NAME = "dogfather-webdesign-v48"; const CACHE_NAME = "dogfather-webdesign-v49";
/* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE /* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE
HTML-Datei in dieser Liste. */ HTML-Datei in dieser Liste. */