Anfuehrungszeichen: 199 falsche Schlusszeichen im sichtbaren Text

Deutsch oeffnet mit „ und schliesst mit “. An 199 Stellen, die ein
Mensch liest, stand als Schlusszeichen ein GERADES " -- „Nächstes"
statt „Nächstes“. Auf sechzehn oeffentlichen Seiten, in vierzig
Dateien des Workspace und in zwoelf Servermodulen, die Texte
verschicken.

Auf dem Bildschirm sieht man den Unterschied sofort. Beim Schreiben
nicht: Das gerade " liegt auf der Tastatur, die anderen nicht.

WARUM EIN ERSTER ANLAUF ZURUECKGENOMMEN WURDE

Ein gerades " ist an vielen Stellen SYNTAX und kein Schriftzeichen --
Grenze einer Zeichenkette, Grenze eines HTML-Attributs, Zeichen in
einem regulaeren Ausdruck. Wer stumpf ersetzt, macht aus

    „<a href="https://…                  ein kaputtes Attribut
    /^["'„»\s]+|["'“«.\s]+$/              einen kaputten Ausdruck
    "… nichts „mal " + "eben …"          eine kaputte Zeichenkette

DIE UNTERSCHEIDUNG LAEUFT AN MERKMALEN, NICHT AN EINER LISTE

  < > = dazwischen        -> HTML-Marke oder Attribut
  endet auf Leerzeichen   -> die Zeichenkette hoert hier auf, der
                             Satz geht in der naechsten Zeile weiter.
                             Ein deutsches Schlusszeichen steht NIE
                             hinter einem Leerzeichen.
  Rueckstrich mittendrin  -> regulaerer Ausdruck
  ${ ohne }               -> mitten in einem Ausdruck

Eine Liste erlaubter Ausnahmen waere die naechste, die niemand
pflegt. Zwoelf Stellen bleiben dadurch stehen, alle zwoelf einzeln
angesehen und alle zwoelf zu Recht -- dort steht das richtige
Schlusszeichen ohnehin weiter unten im Satz.

Fuenf davon waren allerdings ECHTE Fehler HINTER dem Link
(`…>HasiDog</a>".`) -- die erste Regel hatte nur das Attribut
gesehen, nicht den Satz danach. Gezielt nachgezogen.

Ein maskiertes `\"` in workspace-vorlagen.js (28 Hooks) wird zu “ --
ohne Rueckstrich, denn “ begrenzt nichts.

KOMMENTARE BLEIBEN, WIE SIE SIND. Dort liest es niemand ausser mir;
eine Wache, die auch Kosmetik anmahnt, wird weggeklickt. Beim ersten
Messen fielen ausserdem acht Stellen aus buehne.html faelschlich an,
weil `/* */` in HTML (in <style> und <script>) nicht ausgeblendet
war -- jetzt schon.

DIE WACHE DAZU

pruef-struktur prueft es ab sofort mit derselben Regel: 322 Dateien
mit sichtbarem Text, 0 Funde, und die zwoelf bewussten Ausnahmen
werden GEZAEHLT und genannt (erlaubt: 12). Eine Ausnahme, die niemand
sieht, waechst -- und irgendwann steht der echte Fall darin.

GEPRUEFT

  pruef-struktur   59 -> 68 Pruefungen, 0 Fehler
  node --check auf allen geaenderten JS-Dateien
  nachgemessen: 199 geaendert, 12 mit Grund stehen geblieben

  gruen geblieben: bewerbung-aufgaben 163, nachwuchs 262,
  reaktion 421, support 63, content 45, terminregel 35, treff 85

  Und nachgesehen, ob eine Pruefung noch die alte Schreibweise
  ERWARTET: 13 Fundstellen, alle dreizehn nur Text in ihrer eigenen
  Ausgabe, keine einzige ein Vergleich mit dem Seitentext.

GEGENPROBE: In reaktion.html ein Schlusszeichen zurueckgedreht ->
„workspace/reaktion.html:546 „Nächstes"", mit Datei und Zeile. Und
die Erkennung einzeln gegen HTML-Attribut, fortgesetzte
Zeichenkette, regulaeren Ausdruck und eingesetzten Wert geprueft.

Stempel gesetzt: workspace 670 Verweise in 45 Dateien, oeffentlich
554 in 48 Seiten.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-10-01 01:56:15 +02:00
co-authored by Claude Opus 5
parent 7dc5356c80
commit 95c4599baa
138 changed files with 1510 additions and 1390 deletions
+121 -1
View File
@@ -28,7 +28,7 @@
werden. Eine Pruefung, die immer bestaetigt, bestaetigt nichts. */
import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
import { join, dirname, resolve } from "node:path";
import { sep, join, dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { brotliCompressSync, constants } from "node:zlib";
@@ -772,6 +772,126 @@ console.log(`\n=== Laedt jedes Server-Modul? ===`);
Deshalb wird es hier gesucht, statt darauf zu warten, dass es wieder
jemanden trifft. Gemeint ist nur das TAGESDATUM; ein Zeitstempel
(`erstellt`) darf und soll UTC sein. */
console.log(`\n=== Das deutsche Schlusszeichen ===`);
/* ===================================================================
AM 01.10.2026 NACHGEMESSEN: 199 Stellen, die ein Mensch liest,
schlossen ein deutsches Anfuehrungszeichen mit einem GERADEN " --
auf sechzehn oeffentlichen Seiten, in vierzig Dateien des
Workspace und in zwoelf Servermodulen, die Texte verschicken.
Deutsch oeffnet mit \u201E und schliesst mit \u201C. Auf dem
Bildschirm sieht man den Unterschied sofort; beim Schreiben nicht,
denn das gerade " liegt auf der Tastatur und die anderen nicht.
Ohne Wache sind es in vier Wochen wieder zwanzig.
WAS BEWUSST STEHEN BLEIBT -- und warum die Regel so und nicht
einfacher ist: Ein gerades " ist an vielen Stellen SYNTAX und kein
Schriftzeichen. Es begrenzt eine Zeichenkette, ein HTML-Attribut
oder steht in einem regulaeren Ausdruck. Ein erster Anlauf, der
stumpf ersetzte, musste zurueckgenommen werden.
Die Unterscheidung laeuft an Merkmalen, nicht an einer Liste von
Ausnahmen:
< > = dazwischen -> HTML-Marke oder Attribut
endet auf Leerzeichen -> die Zeichenkette hoert hier auf, der
Satz geht in der naechsten Zeile weiter. Ein deutsches
Schlusszeichen steht NIE hinter einem Leerzeichen.
Rueckstrich mittendrin -> regulaerer Ausdruck
${ ohne } -> mitten in einem Ausdruck
=================================================================== */
{
const AUF = "\u201E"; /* „ */
const ZU = "\u201C"; /* “ */
/* Zusammengesetzt und nicht ausgeschrieben: Sonst zeigte diese
Datei sich beim naechsten Lauf selbst an. */
const suche = new RegExp(AUF + '([^' + ZU + AUF + '"`\n]{1,140})"', "g");
const warumNicht = (innen) => {
if (/[<>=]/.test(innen)) return "HTML-Marke oder Attribut";
if (innen.endsWith(" ")) return "Satz geht in der naechsten Zeile weiter";
if (innen.replace(/\\+$/, "").includes("\\")) return "regulaerer Ausdruck";
if ((innen.match(/\$\{/g) || []).length !== (innen.match(/\}/g) || []).length) {
return "offener Ausdruck";
}
return null;
};
/* WAS EIN MENSCH LIEST: die oeffentlichen Seiten, der Workspace und
die Servermodule, die Texte verschicken. Pruef- und Messdateien
schreiben nur in meine Konsole -- dort waere es Kosmetik, und
eine Wache, die auch Kosmetik anmahnt, wird weggeklickt. */
const sammeln = (ordner, tiefe = 0) => {
const raus = [];
for (const e of readdirSync(ordner, { withFileTypes: true })) {
if (e.name === "node_modules" || e.name.startsWith(".")) continue;
const voll = join(ordner, e.name);
if (e.isDirectory()) { if (tiefe < 4) raus.push(...sammeln(voll, tiefe + 1)); continue; }
if (/\.(js|mjs|html|css)$/.test(e.name)) raus.push(voll);
}
return raus;
};
const dateien = [
...sammeln(join(SERVER, "..", "workspace")),
...sammeln(join(SERVER, "..", "assets")),
...readdirSync(join(SERVER, "..")).filter((n) => n.endsWith(".html"))
.map((n) => join(SERVER, "..", n)),
...readdirSync(SERVER).filter((n) => n.startsWith("workspace-") && n.endsWith(".js"))
.map((n) => join(SERVER, n)),
];
ok(dateien.length > 100, `${dateien.length} Dateien mit sichtbarem Text angesehen`);
const falsch = [];
const stehenGelassen = [];
for (const pfad of dateien) {
const art = pfad.slice(pfad.lastIndexOf("."));
let text = readFileSync(pfad, "utf8")
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " "))
.replace(/<!--[\s\S]*?-->/g, (m) => m.replace(/[^\n]/g, " "));
if (art === ".js" || art === ".mjs") {
text = text.replace(/^\s*\/\/.*$/gm, "");
}
/* Der Pfad AB DER WURZEL des Projekts -- ein abgeschnittener
Pfad ist eine Meldung, der man nicht folgen kann. */
const kurz = pfad.split(sep).join("/").split("/DogiHompage/").pop();
for (const t of text.matchAll(suche)) {
const zeile = text.slice(0, t.index).split("\n").length;
const nein = warumNicht(t[1]);
if (nein) stehenGelassen.push(`${kurz}:${zeile} (${nein})`);
else falsch.push(`${kurz}:${zeile} ${AUF}${t[1].slice(0, 40)}"`);
}
}
ok(falsch.length === 0,
falsch.length
? `schliessen ein deutsches Anfuehrungszeichen mit einem geraden ":\n `
+ falsch.slice(0, 15).join("\n ")
+ (falsch.length > 15 ? `\n ... und ${falsch.length - 15} weitere` : "")
: `kein sichtbarer Text schliesst mit einem geraden " (${dateien.length} Dateien)`);
/* DIE AUSNAHMEN WERDEN GEZAEHLT UND GENANNT. Eine Ausnahme, die
niemand sieht, waechst -- und irgendwann steht der echte Fall
darin und faellt nicht mehr auf. */
ok(stehenGelassen.length <= 12,
`${stehenGelassen.length} Stellen bleiben mit Grund stehen`
+ ` (erlaubt: 12) — ${stehenGelassen.slice(0, 3).join(", ") || "—"}`);
/* ---- GEGENPROBEN ------------------------------------------------ */
const findet = (code) => {
const treffer = [...code.matchAll(new RegExp(suche.source, "g"))];
return treffer.filter((t) => !warumNicht(t[1])).length;
};
ok(findet(`Er sagte ${AUF}hallo".`) === 1,
"Gegenprobe: ein gerades Schlusszeichen faellt auf");
ok(findet(`Er sagte ${AUF}hallo${ZU}.`) === 0, " das richtige NICHT");
ok(findet(`${AUF}<a href="x">y</a>`) === 0,
" ein HTML-Attribut ist kein Schlusszeichen");
ok(findet(`"… nichts ${AUF}mal " + "eben …"`) === 0,
" und die Begrenzung einer Zeichenkette, die weitergeht, auch nicht");
ok(findet('/^["\'' + AUF + '»\\s]+|["]/') === 0,
" ein regulaerer Ausdruck ebenfalls nicht");
ok(findet(AUF + "${a.titel}\"") === 1,
" ein eingesetzter Wert dagegen schon");
}
console.log(`\n=== Ruft ein Modul etwas, das es nie eingefuehrt hat? ===`);
/* ===================================================================
AM 01.10.2026 SELBST GEBAUT UND FAST AUSGELIEFERT.