Files
schalli bf7f250c81
Tessera CI/CD / Lint & Type Check (push) Successful in 44s
Tessera CI/CD / Tests (push) Successful in 49s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m51s
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
2026-09-07 13:20:36 +02:00

396 lines
22 KiB
Markdown

---
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>