docs(quick-260907-hyi): Abnahme dokumentiert, Umlaut-Korrektur abgeschlossen
Die Browser-Abnahme hat drei uebersehene Stellen gefunden — "Aenderungen speichern",
"Oeffnen" und einen LDAP-Hinweis — und damit die Luecke im Waechter aufgedeckt, durch
die sie geschluepft sind: sein Verdachtsmuster war case-sensitiv. Beides mit 85d2d77
behoben.
In der SUMMARY festgehalten, wie geprueft wurde: eine unabhaengige Analyse aller
Tokens in de.json findet keine Ersatzschreibung mehr, der Schluesselsatz ist
unveraendert bei 787, und die Modulbeschreibung in der bestehenden Datenbank ist beim
API-Neustart per Seed-Upsert mitgewandert — ohne Migration, wie geplant.
Ausserdem vermerkt, dass eine erste Sichtpruefung per fetch aus der laufenden Seite
faelschlich Entwarnung gab. Dieselbe Methode hatte in dieser Sitzung schon einmal ein
falsches Ergebnis geliefert; die berichteten Zahlen stammen aus echten
Seitenaufrufen.
Backlog-Punkt zu den Umlauten nach completed verschoben.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
This commit is contained in:
+395
@@ -0,0 +1,395 @@
|
||||
---
|
||||
quick_id: 260907-hyi
|
||||
title: Umlaute in der deutschen Oberflaeche korrigieren
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
autonomous: false
|
||||
files_modified:
|
||||
- apps/web/src/messages/umlaut-dictionary.ts
|
||||
- apps/web/src/messages/de.json
|
||||
- apps/web/src/messages/umlaut-guard.spec.ts
|
||||
- apps/api/src/mail/mail.service.ts
|
||||
- apps/api/src/domaincheck/domaincheck.seed.ts
|
||||
|
||||
estimate:
|
||||
tokens: 58000
|
||||
raw_tokens: 29000
|
||||
tasks: 3
|
||||
confidence: low
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Die Anmeldeseite zeigt 'Modulare Workflow-Plattform für Ihr Unternehmen'."
|
||||
- "Das leere Dashboard zeigt 'Klicken Sie auf Bearbeiten, um Widgets hinzuzufügen'."
|
||||
- "Der Marktplatz zeigt 'Verfügbar' und 'Domain-Verfügbarkeit prüfen'."
|
||||
- "Die Passwort-Reset-Mail schreibt 'zurücksetzen', 'gültig' und 'Mit freundlichen Grüßen'."
|
||||
- "Bereits korrekte Woerter wie Passwort, Adresse, Quelle, manuell, Ausschreibung bleiben unveraendert."
|
||||
- "de.json und en.json tragen weiterhin denselben Schluesselsatz (787 Schluessel)."
|
||||
artifacts:
|
||||
- apps/web/src/messages/umlaut-dictionary.ts
|
||||
- apps/web/src/messages/umlaut-guard.spec.ts
|
||||
key_links:
|
||||
- "umlaut-dictionary.ts ist einzige Quelle sowohl fuer die einmalige Ersetzung als auch fuer den Regressionswaechter."
|
||||
- "seedModule() ist ein upsert MIT update-Zweig -> API-Neustart repariert bestehende Module-Zeilen ohne Migration."
|
||||
---
|
||||
|
||||
<objective>
|
||||
Die deutsche Oberflaeche schreibt Umlaute als Buchstabenpaare aus ("fuer" statt "für",
|
||||
"Loeschen" statt "Löschen"). Das sieht jeder Nutzer auf jeder Seite. Dieser Plan korrigiert
|
||||
die Schreibweise an allen drei betroffenen Stellen — Oberflaechentexte, Passwort-Mail und
|
||||
Marktplatz-Beschreibung — und sichert das Ergebnis gegen Rueckfall ab.
|
||||
|
||||
Purpose: Ein Produkt, das an externe Kunden verkauft werden soll, kann keine Oberflaeche
|
||||
ohne Umlaute haben.
|
||||
|
||||
Output: Korrigierte `de.json`, korrigierte API-Texte, ein Woerterbuch-Modul als einzige
|
||||
Wahrheitsquelle und ein Vitest-Waechter, der neue Ersatzschreibweisen blockiert.
|
||||
|
||||
Ausdruecklich NICHT Teil dieses Plans: Umformulierungen. Es geht ausschliesslich um
|
||||
Schreibweise — gleicher Wortlaut, richtige Zeichen. Deutsche Code-Kommentare
|
||||
(z. B. in `tessera-logo.tsx`) bleiben unberuehrt, sie sind nicht nutzersichtbar.
|
||||
</objective>
|
||||
|
||||
<status>
|
||||
Waehrend der Planvalidierung wurde bereits parallel ausgefuehrt — Stand bei Planabgabe:
|
||||
|
||||
- **Task 1 erledigt** (Commit `3d351c6`): `umlaut-dictionary.ts` angelegt, 67 Tokens /
|
||||
140 Fundstellen in `de.json` korrigiert, 787 Schluessel unveraendert.
|
||||
- **Task 2 erledigt, noch nicht committet**: `umlaut-guard.spec.ts` liegt untracked vor.
|
||||
`pnpm --filter @tessera/web test -- src/messages/` laeuft gruen (2 Dateien, 6 Tests).
|
||||
- **Task 3 offen**: `mail.service.ts` und `domaincheck.seed.ts` sind unveraendert.
|
||||
- **Task 4 offen**: Sichtpruefung im Browser und in der Datenbank.
|
||||
|
||||
Die beiden Korrekturen, die aus der Planvalidierung stammen (`Bestaetigen→Bestätigen` sowie
|
||||
die korrigierten Formen auf der Positivliste), sind im ausgefuehrten Stand bereits enthalten
|
||||
— nachgeprueft: kein `Bestaetigen` mehr in `de.json`, Waechter gruen.
|
||||
</status>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/gsd-core/workflows/execute-plan.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@CLAUDE.md
|
||||
@.planning/todos/pending/2026-09-07-umlaute-in-der-oberflaeche.md
|
||||
@apps/web/src/messages/tenderRadar-parity.spec.ts
|
||||
@apps/api/src/mail/mail.service.ts
|
||||
@apps/api/src/domaincheck/domaincheck.seed.ts
|
||||
</context>
|
||||
|
||||
<findings>
|
||||
Diese Punkte wurden beim Planen am Code verifiziert und ersparen dem Executor die Suche:
|
||||
|
||||
1. **`de.json` ist bereits gemischt.** 86 Zeilen tragen heute schon korrekte Umlaute
|
||||
(`Verschlüsselung`, `müssen`, `Feuerlösch-`, `Mineralölerzeugnisse`) — direkt neben
|
||||
den falschen Varianten (`Verschluesselung`, `muessen`). Eine Datei-weite Umschaltung
|
||||
ist also weder noetig noch moeglich; nur die falschen Tokens werden angefasst.
|
||||
|
||||
2. **92 verschiedene Tokens** in den `de.json`-Werten enthalten `ae`/`oe`/`ue`,
|
||||
davon sind **25 bereits korrekt** und duerfen nicht angefasst werden.
|
||||
43 Tokens enthalten `ss`, davon sind **40 korrekt**.
|
||||
|
||||
3. **`seedModule()` ist ein `upsert` MIT `update`-Zweig**
|
||||
(`apps/api/src/module-registry/module-registry.service.ts:167`) und laeuft in
|
||||
`onModuleInit` (`domaincheck.module.ts:30`). Der `update`-Zweig schreibt `description`
|
||||
bei jedem Start neu. **Es ist also keine Migration und kein Backfill noetig** — die
|
||||
Korrektur der Quellzeichenkette repariert bestehende Zeilen beim naechsten API-Start
|
||||
genauso wie frische Installationen.
|
||||
|
||||
4. **Die anderen drei Modul-Seeds sind sauber.** `cert-manager`, `dkv`, `tenders` tragen
|
||||
keine Ersatzschreibweisen in ihren Beschreibungen — geprueft, nichts zu tun.
|
||||
|
||||
5. **`de.json` und `en.json` haben heute identische Schluesselsaetze (je 787).**
|
||||
Dieser Plan aendert ausschliesslich Werte in `de.json`, nie Schluessel.
|
||||
|
||||
6. **Nur zwei API-Dateien betroffen.** Ein Scan ueber `apps/api/src` findet
|
||||
Ersatzschreibweisen ausschliesslich in `mail.service.ts` und `domaincheck.seed.ts`.
|
||||
</findings>
|
||||
|
||||
<hazards>
|
||||
**Gefahr 1 — blinde Ersetzung zerstoert korrektes Deutsch.**
|
||||
Eine allgemeine `ue`→`ü`-Regel macht aus `Passwort` … nichts, aber aus `Quelle`→`Qülle`,
|
||||
`manuell`→`manüll`, `neuen`→`nün`, `Erzeugnisse`→`Erzügnisse`. Deshalb: **ausschliesslich
|
||||
Ersetzung ueber eine explizite Token-Liste**, nie ueber eine Zeichenregel.
|
||||
|
||||
**Gefahr 2 — Teilwort-Ueberlappung.**
|
||||
`hinzuzufuegen` enthaelt `hinzufuegen`? Nein — aber `Verfuegbarkeit` enthaelt `Verfuegbar`,
|
||||
und `ueberpruefen` enthaelt `pruefen`. Deshalb: **Ersetzung auf ganzen Tokens**, nicht per
|
||||
Teilzeichenkette. Der Tokenizer (`/[A-Za-zÄÖÜäöüß]+/g`) trennt an Bindestrich und `@`,
|
||||
sodass `Export-Empfaenger` als `Export` + `Empfaenger` zerfaellt.
|
||||
|
||||
**Gefahr 3 — die E-Mail-Platzhalteradresse.**
|
||||
`de.json` enthaelt den Wert `"empfaenger@example.com"` (Schluessel `testToPlaceholder`).
|
||||
Das ist eine Beispiel-E-Mail-Adresse, kein deutscher Fliesstext. Ein Umlaut darin wuerde
|
||||
suggerieren, dass Umlaut-Adressen das erwartete Format sind. **Dieser Wert bleibt
|
||||
unveraendert** — Regel: Werte, die ein `@` enthalten, werden uebersprungen.
|
||||
|
||||
**Gefahr 4 — der Waechter darf sich nicht selbst ausloesen.**
|
||||
`umlaut-dictionary.ts` enthaelt die falschen Schreibweisen zwangslaeufig als Map-Schluessel
|
||||
(`'fuer'`, `'loeschen'`, …). Ein repo-weites `grep` nach diesen Zeichenketten wuerde
|
||||
deshalb immer anschlagen. **Alle Pruefungen lesen ausschliesslich die Werte aus `de.json`
|
||||
(via JSON-Parse), niemals repo-weit ueber Quelldateien.**
|
||||
</hazards>
|
||||
|
||||
<decision name="ss-vs-ß">
|
||||
Die `ss`/`ß`-Frage wird **mitbehandelt, nicht ausgeklammert** — sie ist derselbe Defekt und
|
||||
betrifft nur drei eindeutige Woerter in `de.json`:
|
||||
|
||||
| falsch | richtig |
|
||||
|---|---|
|
||||
| `Groesse` | `Größe` |
|
||||
| `Schliessen` | `Schließen` |
|
||||
| `ausschliessen` | `ausschließen` |
|
||||
|
||||
Dazu in `mail.service.ts`: `Gruessen` → `Grüßen`.
|
||||
|
||||
Alle anderen 40 `ss`-Tokens sind korrekt (kurzer Vokal davor) und stehen auf der
|
||||
Positivliste: `Passwort`, `Adresse`, `muss`, `musste`, `lassen`, `vergessen`, `stattdessen`,
|
||||
`Wasser`, `Abwasser`, `Hessen`, `Erzeugnisse`, `Ausschreibung*`, `erfasst`, `Erfassung`,
|
||||
`Ergebnisse`, `gedrosselt`, `ausgeschlossen`, `Ausschlussliste`, `Informationssysteme`,
|
||||
`Zeitmessung`, `Tessera`, `Passen`, `Passt`, `Fasst`.
|
||||
</decision>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Woerterbuch anlegen und de.json korrigieren</name>
|
||||
<files>apps/web/src/messages/umlaut-dictionary.ts, apps/web/src/messages/de.json</files>
|
||||
<action>
|
||||
Lege `apps/web/src/messages/umlaut-dictionary.ts` an. Es exportiert zwei Konstanten und ist
|
||||
die einzige Wahrheitsquelle sowohl fuer die einmalige Korrektur als auch fuer den Waechter
|
||||
aus Task 2.
|
||||
|
||||
`UMLAUT_REPLACEMENTS` — `Record<string, string>`, Schluessel ist das falsche Token, Wert das
|
||||
richtige. Die vollstaendige Liste, aus den Werten von `de.json` erhoben und Eintrag fuer
|
||||
Eintrag geprueft:
|
||||
|
||||
loeschen→löschen, Loeschen→Löschen, geloescht→gelöscht,
|
||||
hinzufuegen→hinzufügen, Hinzufuegen→Hinzufügen, hinzuzufuegen→hinzuzufügen,
|
||||
einfuegen→einfügen, fuege→füge, Fuege→Füge, Fuegen→Fügen,
|
||||
fuer→für, pruefen→prüfen, Pruefen→Prüfen, Pruefe→Prüfe, Pruefung→Prüfung,
|
||||
geprueft→geprüft, ueberpruefen→überprüfen, Ueberpruefe→Überprüfe,
|
||||
Zertifikatspruefung→Zertifikatsprüfung,
|
||||
aendern→ändern, geaendert→geändert, Moechten→Möchten, moeglich→möglich,
|
||||
moegliche→mögliche, koennen→können,
|
||||
verfuegbar→verfügbar, Verfuegbar→Verfügbar, Verfuegbare→Verfügbare,
|
||||
Verfuegbarkeit→Verfügbarkeit,
|
||||
Zurueck→Zurück, Zuruecksetzen→Zurücksetzen, zuruecksetzen→zurücksetzen,
|
||||
zurueckgesetzt→zurückgesetzt, rueckgaengig→rückgängig,
|
||||
Zusammenfuehren→Zusammenführen, zusammenfuehren→zusammenführen,
|
||||
bestaetigen→bestätigen, Bestaetigen→Bestätigen, Passwoerter→Passwörter,
|
||||
spaeter→später, ueberein→überein, uebersprungen→übersprungen, ueber→über,
|
||||
ungueltig→ungültig, Verschluesselung→Verschlüsselung, entschluesselt→entschlüsselt,
|
||||
ausgewaehlt→ausgewählt, Ausgewaehlt→Ausgewählt, ausgewaehlte→ausgewählte,
|
||||
Ausgewaehlte→Ausgewählte, waehle→wähle,
|
||||
benoetigt→benötigt, noetig→nötig, muessen→müssen,
|
||||
Domaene→Domäne, Einschraenkung→Einschränkung, Empfaenger→Empfänger,
|
||||
Oberflaeche→Oberfläche, Postfaecher→Postfächer, Praefix→Präfix,
|
||||
Sicherheitsgruenden→Sicherheitsgründen, unterstuetzt→unterstützt,
|
||||
vertrauenswuerdigen→vertrauenswürdigen,
|
||||
zusaetzlich→zusätzlich, zusaetzliche→zusätzliche,
|
||||
Groesse→Größe, Schliessen→Schließen, ausschliessen→ausschließen
|
||||
|
||||
`UMLAUT_ALLOWLIST` — `readonly string[]` der Tokens, die `ae`/`oe`/`ue`/`ss` enthalten und
|
||||
**bereits korrekt** sind, also niemals ersetzt werden duerfen:
|
||||
manuell, Manuell, Quelle, Quellen, Quell, Quellportal, Kalenderquelle, Kalenderquellen,
|
||||
Energiequellen, neue, neuen, Neue, Neues, aktuelle, Aktuell, Aktuelle, Aktuelles,
|
||||
zuerst, dauerhaft, Schauen, Taktfrequenz, Feuerlösch, Bergbauerzeugnisse, true, guest,
|
||||
Passwort, Adresse, Absenderadresse, muss, musste, müssen, lassen, vergessen, stattdessen,
|
||||
Wasser, Abwasser, Hessen, Erzeugnisse, Druckerzeugnisse, Gummierzeugnisse,
|
||||
Mineralölerzeugnisse, Ausschreibung, Ausschreibungen, Ausschreibungs, Ausschreibungsdetails,
|
||||
erfasst, erfasster, Erfassung, Ergebnisse, gedrosselt, ausgeschlossen, Ausgeschlossen,
|
||||
Ausschlussliste, Informationssysteme, Zeitmessung, Tessera, Passen, Passt, Fasst,
|
||||
Verschlüsselung
|
||||
|
||||
Dazu **zwingend** die korrigierten Formen, die nach der Ersetzung immer noch ein `ss` oder
|
||||
`ue` enthalten — sonst schlaegt der Waechter aus Task 2 sofort auf der frisch korrigierten
|
||||
Datei an: `Passwörter` (`ss`), `entschlüsselt` (`ss`), `ausschließen` (`ss`),
|
||||
`vertrauenswürdigen` (`ue` in "vertra-ue-ns"), `Verschlüsselung` (`ss`), `müssen` (`ss`).
|
||||
Das ist der unintuitive Teil der Positivliste: sie beschreibt den Zustand **nach** dem Fix,
|
||||
nicht davor.
|
||||
|
||||
`empfaenger` steht bewusst **nicht** auf der Positivliste: der einzige Fundort ist die
|
||||
Beispiel-Adresse `empfaenger@example.com`, und die wird bereits ueber die `@`-Regel
|
||||
uebersprungen. Ein Positivlisten-Eintrag waere nicht nur ueberfluessig, er wuerde auch
|
||||
kuenftigen deutschen Fliesstext mit `empfaenger` unbemerkt durchlassen.
|
||||
|
||||
Kommentiere im Dateikopf, warum die Liste explizit ist (eine Zeichenregel zerstoert `Quelle`,
|
||||
`manuell`, `neuen`) und dass die `@`-Regel die Beispiel-Adresse schuetzt.
|
||||
|
||||
Wende die Ersetzung dann per Wegwerf-Node-Skript auf `apps/web/src/messages/de.json` an:
|
||||
Datei JSON-parsen, rekursiv ueber alle Blattwerte laufen, Werte mit `@` ueberspringen,
|
||||
in den uebrigen Werten jedes Token per `\b`-begrenztem Ganzwort-Treffer gegen
|
||||
`UMLAUT_REPLACEMENTS` ersetzen, mit 2 Leerzeichen Einrueckung und abschliessendem
|
||||
Zeilenumbruch zurueckschreiben. Nur Werte anfassen, Schluessel und Reihenfolge nie.
|
||||
|
||||
Das Skript ist ein Hilfsmittel und wird nicht eingecheckt.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>node -e 'const WRONG="loeschen Loeschen geloescht hinzufuegen Hinzufuegen hinzuzufuegen einfuegen fuege Fuege Fuegen fuer pruefen Pruefen Pruefe Pruefung geprueft ueberpruefen Ueberpruefe Zertifikatspruefung aendern geaendert Moechten moeglich moegliche koennen verfuegbar Verfuegbar Verfuegbare Verfuegbarkeit Zurueck Zuruecksetzen zuruecksetzen zurueckgesetzt rueckgaengig Zusammenfuehren zusammenfuehren bestaetigen Passwoerter spaeter ueberein uebersprungen ueber ungueltig Verschluesselung entschluesselt ausgewaehlt Ausgewaehlt ausgewaehlte Ausgewaehlte waehle benoetigt noetig muessen Domaene Einschraenkung Empfaenger Oberflaeche Postfaecher Praefix Sicherheitsgruenden unterstuetzt vertrauenswuerdigen zusaetzlich zusaetzliche Groesse Schliessen ausschliessen".split(" ");const S=new Set(WRONG);const d=require("./apps/web/src/messages/de.json");const hits=[];const walk=(o,p)=>{for(const k of Object.keys(o)){const v=o[k],q=p?p+"."+k:k;if(typeof v==="string"){if(v.includes("@"))continue;for(const w of v.match(/[A-Za-zÄÖÜäöüß]+/g)||[])if(S.has(w))hits.push(q+": "+w)}else if(v&&typeof v==="object")walk(v,q)}};walk(d,"");if(hits.length){console.error("noch falsch:\n"+hits.join("\n"));process.exit(1)}console.log("de.json ok - keine Ersatzschreibweise mehr")'</automated>
|
||||
<automated>node -e 'const a=require("./apps/web/src/messages/de.json"),b=require("./apps/web/src/messages/en.json");const K=(o,p="")=>Object.entries(o).flatMap(([k,v])=>v&&typeof v==="object"?K(v,p+k+"."):[p+k]);const A=K(a).sort(),B=K(b).sort();if(A.length!==787||JSON.stringify(A)!==JSON.stringify(B)){console.error("KEY DRIFT");process.exit(1)}console.log("keys ok",A.length)'</automated>
|
||||
</verify>
|
||||
<done>
|
||||
`de.json` enthaelt keinen Schluessel aus `UMLAUT_REPLACEMENTS` mehr. Jedes verbliebene Token
|
||||
mit `ae`/`oe`/`ue`/`ss` steht auf `UMLAUT_ALLOWLIST`. `de.json` und `en.json` haben weiterhin
|
||||
je 787 identische Schluessel. Der Wert `empfaenger@example.com` ist unveraendert.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Regressionswaechter als Vitest-Spec</name>
|
||||
<files>apps/web/src/messages/umlaut-guard.spec.ts</files>
|
||||
<action>
|
||||
Lege `apps/web/src/messages/umlaut-guard.spec.ts` an, im Stil der bestehenden
|
||||
`tenderRadar-parity.spec.ts` daneben (gleicher Ordner, gleiche Import-Form, Kopfkommentar
|
||||
mit Zweck).
|
||||
|
||||
Die Spec importiert `de.json` sowie `UMLAUT_REPLACEMENTS` und `UMLAUT_ALLOWLIST` aus
|
||||
`./umlaut-dictionary`. Sie laeuft rekursiv ueber alle Blattwerte, ueberspringt Werte mit `@`,
|
||||
tokenisiert mit `/[A-Za-zÄÖÜäöüß]+/g` und prueft drei Dinge:
|
||||
|
||||
Erstens: kein Token ist ein Schluessel aus `UMLAUT_REPLACEMENTS`. Die Fehlermeldung nennt das
|
||||
gefundene Token, die richtige Schreibweise und den Schluesselpfad.
|
||||
|
||||
Zweitens: jedes Token, das `ae`, `oe`, `ue` oder `ss` enthaelt, steht auf `UMLAUT_ALLOWLIST`.
|
||||
Die Fehlermeldung sagt, dass ein neues Wort aufgetaucht ist, und fordert auf, es entweder in
|
||||
`UMLAUT_REPLACEMENTS` (falls Ersatzschreibweise) oder in `UMLAUT_ALLOWLIST` (falls korrektes
|
||||
Deutsch) einzutragen. Genau dieser zweite Test ist der eigentliche Waechter: er faellt bei
|
||||
jeder NEU eingefuehrten Ersatzschreibweise, ohne heutige Texte festzunageln — er pinnt
|
||||
Vokabular, nicht Wortlaut.
|
||||
|
||||
Drittens: `de.json` und `en.json` tragen denselben rekursiv abgeflachten Schluesselsatz.
|
||||
|
||||
Wichtig: Die Spec liest ausschliesslich das geparste JSON. Sie darf niemals repo-weit ueber
|
||||
Quelldateien greppen, sonst schlaegt sie am Woerterbuch selbst an (siehe Gefahr 4).
|
||||
|
||||
Halte den Waechter fuer verlaesslich: Falsch-Positive sind auf neue Woerter beschraenkt, und
|
||||
die Behebung ist ein Ein-Zeilen-Eintrag mit klarer Anleitung in der Fehlermeldung. Das ist
|
||||
billiger als eine unbemerkte Regression.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pnpm --filter @tessera/web test -- src/messages/umlaut-guard.spec.ts</automated>
|
||||
<automated>pnpm --filter @tessera/web run type-check</automated>
|
||||
</verify>
|
||||
<done>
|
||||
`umlaut-guard.spec.ts` laeuft gruen. Ein probeweise in einen `de.json`-Wert eingefuegtes
|
||||
`fuer` laesst die Spec fehlschlagen mit einer Meldung, die `für` und den Schluesselpfad nennt
|
||||
(danach zuruecknehmen). `type-check` bleibt sauber.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: API-Texte — Passwort-Mail und Modul-Beschreibung</name>
|
||||
<files>apps/api/src/mail/mail.service.ts, apps/api/src/domaincheck/domaincheck.seed.ts</files>
|
||||
<action>
|
||||
Korrigiere in `apps/api/src/mail/mail.service.ts` ausschliesslich die deutschen
|
||||
Zeichenketten — Wortlaut identisch, nur Zeichen richtig. Betroffen sind beide Mails, nicht
|
||||
nur die Passwort-Mail:
|
||||
|
||||
Im Betreff (Z. 34) `zuruecksetzen` → `zurücksetzen`. Im Fliesstext der Passwort-Mail
|
||||
(Z. 41, 43, 46, 48, 50): `Passwortzuruecksetzung` → `Passwortzurücksetzung`, `fuer` → `für`,
|
||||
`zurueckzusetzen` → `zurückzusetzen` (Template-Literal), `gueltig` → `gültig`,
|
||||
`koennen` → `können`, `Gruessen` → `Grüßen`. In der Willkommens-Mail (Z. 104, 106):
|
||||
`koennen` → `können` (Template-Literal), `Gruessen` → `Grüßen`.
|
||||
|
||||
Die englischen Zweige bleiben unberuehrt.
|
||||
|
||||
Korrigiere in `apps/api/src/domaincheck/domaincheck.seed.ts` Zeile 20 die deutsche
|
||||
Beschreibung: `'Domain-Verfuegbarkeit pruefen'` → `'Domain-Verfügbarkeit prüfen'`.
|
||||
Der `en`-Wert bleibt unberuehrt.
|
||||
|
||||
Keine Migration, kein Backfill, kein manueller Schritt: `seedModule()` ist ein `upsert`,
|
||||
dessen `update`-Zweig `description` bei jedem Start neu schreibt, und der Seed laeuft in
|
||||
`onModuleInit`. Ein API-Neustart bringt bestehende wie frische Installationen auf denselben
|
||||
Stand. Die anderen drei Modul-Seeds sind bereits korrekt und werden nicht angefasst.
|
||||
|
||||
Nodemailer kodiert `text` selbsttaetig als UTF-8; am Mail-Versand ist nichts umzustellen.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>node -e 'const s=require("fs").readFileSync("apps/api/src/mail/mail.service.ts","utf8");const bad=s.match(/[A-Za-z]*(zuruecksetz|zurueckzusetz|Passwortzuruecksetzung|gueltig|koennen|Gruessen)[a-z]*|\bfuer\b/g)||[];if(bad.length){console.error("noch falsch:",[...new Set(bad)].join(" "));process.exit(1)}for(const need of ["zurücksetzen","zurückzusetzen","Passwortzurücksetzung","für","gültig","können","Grüßen"])if(!s.includes(need)){console.error("Korrektur fehlt:",need);process.exit(1)}if((s.match(/Grüßen/g)||[]).length!==2){console.error("Grüßen muss in beiden Mails stehen");process.exit(1)}console.log("mail ok")'</automated>
|
||||
<automated>node -e 'const s=require("fs").readFileSync("apps/api/src/domaincheck/domaincheck.seed.ts","utf8");if(!/Domain-Verfügbarkeit prüfen/.test(s)){console.error("seed nicht korrigiert");process.exit(1)}console.log("seed ok")'</automated>
|
||||
<automated>pnpm --filter @tessera/api run build</automated>
|
||||
</verify>
|
||||
<done>
|
||||
Beide deutschen Mail-Texte und die `domaincheck`-Beschreibung tragen echte Umlaute, die
|
||||
englischen Texte sind unveraendert, und die API baut fehlerfrei.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify">
|
||||
<name>Task 4: Sichtpruefung im Browser und in der Datenbank</name>
|
||||
<action>
|
||||
Der Orchestrator fuehrt diese Pruefung durch — nicht der Executor.
|
||||
|
||||
Beide Container muessen vorher neu gebaut werden, weil Web und API aus gebauten Images
|
||||
laufen:
|
||||
|
||||
```
|
||||
docker compose build web api && docker compose up -d --force-recreate web api
|
||||
```
|
||||
|
||||
Danach als `admin`/`admin123` auf http://localhost:3000 anmelden und die fuenf Stellen
|
||||
ansehen, an denen die falsche Schreibweise heute sichtbar ist:
|
||||
|
||||
1. Anmeldeseite, Slogan: muss `Modulare Workflow-Plattform für Ihr Unternehmen` lauten.
|
||||
2. Leeres Dashboard: `Klicken Sie auf Bearbeiten, um Widgets hinzuzufügen`.
|
||||
3. Administration, Rueck-Link: `Zurück zum Dashboard`.
|
||||
4. Benutzerverwaltung, Schaltflaeche: `Löschen`.
|
||||
5. Marktplatz: `Verfügbar` sowie die Modulbeschreibung `Domain-Verfügbarkeit prüfen`.
|
||||
|
||||
Punkt 5 belegt zugleich, dass der Seed die bestehende Datenbankzeile ueberschrieben hat.
|
||||
Zur Gegenprobe direkt in der Datenbank:
|
||||
|
||||
```
|
||||
docker compose exec db psql -U tessera -d tessera -c \
|
||||
"select slug, description->>'de' from \"Module\" where slug='domaincheck';"
|
||||
```
|
||||
|
||||
Erwartet: `Domain-Verfügbarkeit prüfen`.
|
||||
|
||||
Zusaetzlich stichprobenartig bestaetigen, dass korrektes Deutsch unbeschaedigt blieb —
|
||||
in der Benutzerverwaltung stehen weiterhin `Passwort`, `Adresse`, `manuell`, und im
|
||||
Ausschreibungs-Modul `Quellen` und `Ausschreibungen` (nicht `Qülle`, nicht `Erzügnisse`).
|
||||
</action>
|
||||
<verify>
|
||||
<human-check>Alle fuenf Bildschirme zeigen echte Umlaute, die Datenbankzeile ebenfalls, und kein korrektes Wort wurde beschaedigt.</human-check>
|
||||
</verify>
|
||||
<done>Der Nutzer bestaetigt die Schreibweise auf allen fuenf Bildschirmen und in der Datenbankzeile.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `pnpm --filter @tessera/web test` laeuft vollstaendig gruen (inkl. neuem Waechter und
|
||||
bestehender `tenderRadar-parity.spec.ts`).
|
||||
- `pnpm --filter @tessera/web run type-check` ist sauber.
|
||||
- `pnpm --filter @tessera/api run build` ist sauber.
|
||||
- `de.json` und `en.json` tragen je 787 identische Schluessel.
|
||||
- Kein Token aus `UMLAUT_REPLACEMENTS` steht mehr in einem `de.json`-Wert.
|
||||
- Sichtpruefung aus Task 4 bestaetigt.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
Die deutsche Oberflaeche, die Passwort-Mail und die Marktplatz-Beschreibung schreiben Umlaute
|
||||
als Umlaute. Kein zuvor korrektes Wort wurde beschaedigt. Ein Vitest-Waechter blockiert neue
|
||||
Ersatzschreibweisen in `de.json`. Weder Wortlaut noch Schluessel wurden veraendert.
|
||||
</success_criteria>
|
||||
|
||||
<backlog_notes>
|
||||
Beim Planen aufgefallen, aber ausdruecklich NICHT Teil dieses Plans (Wortlaut, nicht
|
||||
Schreibweise) — gehoert ins Backlog:
|
||||
|
||||
- Der Loeschdialog schreibt `1 Mitglieder` statt `1 Mitglied` (fehlende Pluralregel).
|
||||
- Der Aktivierungsdialog duzt, waehrend die uebrige Oberflaeche siezt.
|
||||
|
||||
Beide stammen aus `.planning/phases/15-modul-berechtigungen-gruppen-user-grants/15-UAT-2026-09-07.md`
|
||||
und sind bereits dort vermerkt.
|
||||
</backlog_notes>
|
||||
|
||||
<output>
|
||||
Quick-Task — kein SUMMARY noetig. Commits atomar pro Task:
|
||||
- `fix(i18n): Umlaute in de.json korrigieren`
|
||||
- `test(i18n): Waechter gegen neue Umlaut-Ersatzschreibweisen`
|
||||
- `fix(api): Umlaute in Passwort-Mail und Modulbeschreibung`
|
||||
</output>
|
||||
+57
@@ -123,3 +123,60 @@ Fertigstellung des in Task 1/2 beschriebenen Woerterbuchs, keine architektonisch
|
||||
- Commit `3d351c6` — FOUND (`git log --oneline --all`)
|
||||
- Commit `caa4827` — FOUND
|
||||
- Commit `d9f1422` — FOUND
|
||||
|
||||
## Task 4 — Abnahme am 2026-09-07, mit Nachbesserung
|
||||
|
||||
Der Orchestrator hat Web und API neu gebaut und die Oberflaeche im Browser geprueft.
|
||||
Die Abnahme hat **drei uebersehene Stellen** gefunden, darunter zwei gut sichtbare
|
||||
Schaltflaechen:
|
||||
|
||||
| falsch | richtig | Ort |
|
||||
|---|---|---|
|
||||
| `Aenderungen speichern` | Änderungen speichern | Dashboard-Bearbeitungsleiste |
|
||||
| `Oeffnen` | Öffnen | Modul oeffnen |
|
||||
| `Eine Aenderung ist hier nicht moeglich` | Eine Änderung … | LDAP-Hinweis im Konto |
|
||||
|
||||
### Warum der Waechter sie durchgelassen hat
|
||||
|
||||
Sein Verdachtsmuster war `/(ae|oe|ue|ss)/` — **case-sensitiv**. "Aenderungen" beginnt
|
||||
mit "Ae", nicht mit "ae", und konnte deshalb gar nicht anschlagen. Derselbe Fehler
|
||||
war auch in der ersten Analyse des Orchestrators und hat dort zunaechst zu einem
|
||||
falschen Entwarnungssignal gefuehrt.
|
||||
|
||||
Behoben in Commit `85d2d77`:
|
||||
|
||||
- Muster auf `/(ae|oe|ue|ss)/i` umgestellt — erfasst jetzt auch grossgeschriebene
|
||||
Formen.
|
||||
- `Aenderung`, `Aenderungen`, `Oeffnen` ins Woerterbuch aufgenommen und in `de.json`
|
||||
korrigiert (4 Fundstellen).
|
||||
- Durch die schaerfere Pruefung melden sich neu die Abkuerzungen `RSS`,
|
||||
`RSSGenerator` und `SSL`. Sie tragen ein doppeltes S ohne Umlaut-Bezug und stehen
|
||||
jetzt auf der Positivliste.
|
||||
|
||||
### Zwei weitere Nutzertexte, die der erste Durchgang nicht als solche erkannt hatte
|
||||
|
||||
`ldap.service.ts` schreibt zwei Meldungen in `result.errors`. Das ist kein Protokoll,
|
||||
sondern die Fehlerliste, die dem Administrator auf der LDAP-Seite angezeigt wird
|
||||
(`admin/ldap/page.tsx:1028`). Beide korrigiert: "ungueltiger ldapObjectGuid-Wert" und
|
||||
"Base-DN-Konfiguration pruefen. Nicht geloescht." Drei Tests pinnen diese Texte
|
||||
bewusst und wurden mitgezogen.
|
||||
|
||||
**Bewusst nicht angefasst:** die Warnung in `crypto.service.ts`. Sie geht ueber
|
||||
`logger.warn` ins Protokoll und nicht an einen Nutzer.
|
||||
|
||||
### Gegenproben
|
||||
|
||||
| Pruefung | Ergebnis |
|
||||
|---|---|
|
||||
| Unabhaengige Analyse aller Tokens in `de.json` mit ae/oe/ue | keine Ersatzschreibung mehr; die 26 verbleibenden Treffer sind korrektes Deutsch (Quelle, manuell, Aktuell, neue, dauerhaft …) |
|
||||
| Schluesselsatz de/en | 787 / 787, identisch |
|
||||
| Modulbeschreibung in der **bestehenden** Datenbank nach API-Neustart | `Domain-Verfügbarkeit prüfen` — der Seed-Upsert hat die Altzeile mitgezogen, ohne Migration |
|
||||
| Browser, echte Navigation | "Änderungen speichern", "Widget hinzufügen", "hinzuzufügen", "Verfügbar", "Domain-Verfügbarkeit prüfen" korrekt |
|
||||
| Testlaeufe | 642 API-Tests, 225 Web-Tests, beide Typpruefungen gruen |
|
||||
|
||||
### Nachtrag zur Belastbarkeit der Messmethode
|
||||
|
||||
Eine erste Sichtpruefung per `fetch()` aus der laufenden Seite heraus meldete alle
|
||||
Seiten als sauber. Dieselbe Methode hatte in dieser Sitzung schon einmal ein falsches
|
||||
Ergebnis geliefert, weil sie die Sitzung nicht wie eine echte Navigation mitfuehrt.
|
||||
Die oben berichteten Ergebnisse stammen deshalb aus echten Seitenaufrufen.
|
||||
|
||||
Reference in New Issue
Block a user