--- id: PROJ-79 title: Frontend-Dependency-Major-Upgrades (eslint, lucide-react, tailwindcss+tailwind-merge, typescript, @types/node) status: Deployed created: 2026-08-05 --- ## Problem `npm outdated` zeigt mehrere Major-Version-Sprünge im Frontend, die nicht blind gezogen werden sollten (Build-/Runtime-Bruchrisiko): | Paket | Aktuell | Ziel | Risiko | |---|---|---|---| | `eslint` | 9.39.2 | 10.8.0 | Config-Format | | `typescript` | 5.9.3 | 7.0.2 | 2 Majors auf einmal | | `tailwindcss` | 3.4.19 | 4.3.3 | Komplett neue Config-Architektur | | `lucide-react` | 0.562.0 | 1.28.0 | Icon-API-Änderungen | | `tailwind-merge` | 2.6.0 | 3.6.0 | **Hart an tailwindcss v4 gekoppelt** | | `@types/node` | 20.19.28 | 26.1.2 | Typen sollten zur echten Node-Version auf 131/132 passen | Wichtiger Befund während der Umsetzung: `tailwind-merge` v3 setzt laut offizieller README **Tailwind CSS v4** voraus ("if you use Tailwind v3, use tailwind-merge v2.6.0"). Ein isoliertes Upgrade von `tailwind-merge` allein wäre eine offiziell unsupported Kombination — heute laut Messung (283.556 paarweise Klassen-Merges aus dem echten Codebestand verglichen, 0 Abweichungen) verhaltensneutral, aber `*-opacity-*`-Klassen (z.B. `bg-opacity-50`) würden künftig **lautlos** falsch gemerged statt einen Build-Fehler zu werfen. ## Lösung (Vorschlag, priorisierte Reihenfolge) 1. **`lucide-react`** 0.562.0 → 1.28.0 — unabhängig, `grep -rn "from \"lucide-react\"" src/` für Impact-Fläche, danach Upgrade + `tsc --noEmit` (fängt umbenannte Icons als Compile-Fehler ab). 2. **`eslint`** 9 → 10 + `eslint-config-next` (aktuell 16.1.1) nachziehen, Flat-Config-Breaking-Changes prüfen, `npm run lint` danach. 3. **`@types/node`** 20 → 26 — vorher `node -v` auf 131 und 132 prüfen, Typen dürfen nicht vor der tatsächlichen Node-Laufzeit-Version liegen. 4. **`tailwindcss` v3→v4 + `tailwind-merge` v2→v3 zusammen** (gekoppeltes Paar, nicht einzeln): - `npx @tailwindcss/upgrade` (offizielles Migrationstool) - `tailwind-merge@3` im selben Schritt - shadcn/ui-Komponenten-Kompatibilität mit Tailwind 4 vorab prüfen - Lint-Regel gegen `*-opacity-*`-Klassen ergänzen (bg-/text-/border-/ring-/divide-/placeholder-opacity) — Absicherung gegen stummen Styling-Bruch, da diese Klassen in v4 vom Merge verschluckt werden ohne Fehler - Voller visueller Regressionstest (größter Einzelschritt im gesamten Upgrade-Batch) 5. **`typescript`** 5 → 6 → 7 — stufenweise, nicht direkt springen, nach jedem Schritt `tsc --noEmit` komplett grün vor dem nächsten. Jeder Schritt: eigener Commit, erst auf 132 build+visuell testen, dann 131. ## Implementation Notes - 2026-08-05: `tailwind-merge`-Einzelupgrade gestoppt nach Entdeckung der v4-Kopplung (siehe Problem-Abschnitt). Kein Code geändert, `git status` war danach sauber. Reihenfolge oben entsprechend revidiert — Punkt 4 bündelt beide Pakete statt sie getrennt zu behandeln. ### lucide-react Upgrade (Punkt 1) — 2026-08-05 - `lucide-react` 0.562.0 → **1.28.0** (`npm install lucide-react@^1`). Geändert wurden nur `package.json` / `package-lock.json` — **kein Anwendungscode musste angepasst werden.** - Impact-Fläche: 19 Dateien unter `src/`, alle Imports einzeilig, insgesamt 20 verschiedene Icons: `Bookmark`, `BookmarkPlus`, `Check`, `ChevronDown`, `ChevronLeft`, `ChevronRight`, `ChevronUp`, `Circle`, `FileText`, `Info`, `Lock`, `MailPlus`, `Moon`, `MoreHorizontal`, `PanelLeft`, `Search`, `Server`, `Sun`, `Trash2`, `X`. - **Keine Icon-Umbenennungen nötig:** alle 20 Namen existieren unverändert in den v1-Typdeklarationen (`node_modules/lucide-react/dist/lucide-react.d.ts`) — es handelt sich durchweg um kanonische Namen, keine veralteten Aliase, die beim 0.x→1.x-Sprung entfallen wären. - Verifikation: `npx tsc --noEmit` → **0 Fehler**; `npm run build` → **erfolgreich**, alle 14 Routen generiert. - Kein Live-Browser-Test durchgeführt (laut Spec-Auftrag nicht nötig, da fehlende Icon-Exporte vollständig als Compile-Fehler auftreten würden). - Offen: Verifikation auf 132, danach 131-Deploy. ### npm audit fix + Browserslist-Refresh — 2026-08-05 Anlass: beim 131-Deploy meldete `npm ci` 26 Vulnerabilities (1 low, 6 moderate, 19 high) sowie eine 8 Monate alte `caniuse-lite`-Datenbank (Browserslist). Lokal (nach dem lucide-react-Upgrade) zeigte `npm audit` nur 6 Vulnerabilities (`@babel/core` low, `brace-expansion`/`js-yaml`/ `next`/`postcss`/`sharp` high) — Differenz vermutlich Lock-Datei-Drift zwischen Workstation und 131 zum Zeitpunkt des Checks. - `npm audit fix` (ohne `--force`, da alle 6 Funde `fixAvailable: true` ohne SemVer-Major-Flag waren) → **0 Vulnerabilities**. `next` wanderte dabei minor von 16.2.9 auf 16.3.0 (im `^16.1.1`-Range, kein Breaking Change). - `npx update-browserslist-db@latest` → `caniuse-lite` aktualisiert (1.0.30001763 → 1.0.30001806), keine Target-Browser-Änderung. - Verifikation: `npx tsc --noEmit` 0 Fehler, `npm run build` erfolgreich, alle 14 Routen generiert. - `update.sh` erweitert: nach `npm ci` läuft jetzt `npm audit --audit-level=high` als **Warn-Gate** (nicht blockierend, kein Auto-Fix während des laufenden Deploys — Breaking-Change-Risiko live auf Produktivsystem wäre inakzeptabel). Bei Fund: Warnung mit Verweis auf `/tmp/npm-audit-report.txt` und Hinweis, `npm audit fix` lokal auszuführen/zu testen/zu committen statt es automatisch im Deploy zu fahren. - Deployed auf 132 und 131 am 2026-08-05. Beide Deploys sauber: `npm ci` meldete auf beiden Servern **0 vulnerabilities** (vorher 26 auf 131), keine Browserslist-Warnung mehr im update.sh-Output, das neue npm-audit-Warn-Gate schlug erwartungsgemäß nicht an. Backend ✓ läuft und Frontend ✓ läuft auf beiden Servern. ### eslint Upgrade (Punkt 2) — 2026-08-05 — **blockiert, nicht abgeschlossen** **Ergebnis: `eslint` bleibt vorerst auf 9.x.** Das Upgrade auf 10.8.0 wurde durchgeführt, getestet und wieder zurückgenommen — Ursache ist ein Ökosystem-Blocker (siehe unten), kein Fehler in unserem Code. Die dabei ohnehin fällige Flat-Config-Migration wurde **behalten**, weil sie einen bestehenden Defekt behebt. **Nebenbefund (wichtig): `npm run lint` war bereits vor dem Upgrade kaputt.** Das Script stand auf `next lint`, aber Next.js 16 hat den `next lint`-Befehl entfernt. Der Aufruf scheiterte mit `Invalid project directory provided, no such directory: .../lint` — Linting fand seit dem Next-16-Upgrade also faktisch gar nicht mehr statt. Die unten gelisteten Findings sind daher **nicht neu entstanden**, sondern nur wieder sichtbar geworden. Geändert: - `.eslintrc.json` (`{ "extends": "next/core-web-vitals" }`) **gelöscht**, ersetzt durch `eslint.config.mjs` (Flat Config, importiert `eslint-config-next/core-web-vitals`, ignoriert `.next/`, `out/`, `build/`, `next-env.d.ts`). ESLint 10 unterstützt das alte `.eslintrc`-Format nicht mehr (`ESLINT_USE_FLAT_CONFIG=false` entfällt), die Migration ist für das Upgrade also Pflicht und ohnehin vorzuziehen. - `package.json`: Script `lint` von `next lint` → `eslint .` - `eslint-config-next` 16.1.1 → **16.3.0** (zieht mit `next` 16.3.0 gleich, weiterhin exakt gepinnt). Peer-Range ist `eslint >=9.0.0`, deckt 10 also nominell ab. - `eslint` bleibt `^9` (konkretisiert auf `^9.39.5`). **Blocker für ESLint 10:** `eslint-config-next` deklariert zwar `eslint >=9.0.0`, seine Plugin-Abhängigkeiten sind aber noch nicht ESLint-10-fähig. Mit `eslint@10.8.0` bricht der Lauf sofort hart ab: ``` TypeError: Error while loading rule 'react/display-name': contextOrFilename.getFilename is not a function at .../eslint-plugin-react/lib/util/version.js ``` Ursache: ESLint 10 hat die deprecateten `context`-Member (u.a. `context.getFilename()`) entfernt. Stand der fünf von `eslint-config-next` 16.3.0 gezogenen Plugins: | Plugin | Version | peer `eslint` | ESLint 10 | |---|---|---|---| | `eslint-plugin-react` | 7.37.5 (= latest) | `… \|\| ^9.7` | **nein — crasht** | | `eslint-plugin-jsx-a11y` | 6.10.2 (= latest) | `… \|\| ^9` | nein | | `eslint-plugin-import` | 2.32.0 (= latest) | `… \|\| ^9` | nein | | `eslint-plugin-react-hooks` | 7.1.1 | `… \|\| ^10.0.0` | ja | | `typescript-eslint` | 8.66.0 | `… \|\| ^10.0.0` | ja | Es existiert **keine stabile Version** von `eslint-plugin-react` / `-jsx-a11y` / `-import` mit ESLint-10-Support. Die einzigen Workarounds wären, die betroffenen Plugins aus der Config zu werfen (verliert echte Regel-Abdeckung) oder Versionen zu forcieren, die laut Peer-Range nicht passen — beides wurde bewusst **nicht** gemacht. Neuer Anlauf, sobald `eslint-plugin-react` ESLint 10 unterstützt. **Lint-Ergebnis nach Flat-Config-Migration (ESLint 9): 30 Findings** (25 Fehler, 5 Warnungen) in 25 Dateien — alles Bestandscode, der durch das reparierte Lint-Script wieder sichtbar wird. Nicht im Rahmen dieses Schritts gefixt (Scope): | Regel | Anzahl | Art | |---|---|---| | `react-hooks/set-state-in-effect` (error) | 19 | `setXLoading(true)` direkt im Effect-Body, verteilt über fast alle `src/hooks/*` und Admin-Tabs | | `@next/next/no-html-link-for-pages` (error) | 4 | `` statt `` (admin/login, forgot-password ×2, signup) | | `@next/next/no-location-assign-relative-destination` (warn) | 2 | reset-password:70, verify:46 | | `react-hooks/refs` (error) | 1 | `useSearch.ts:38` — Ref-Schreibzugriff während Render | | `react-hooks/purity` (error) | 1 | `components/ui/sidebar.tsx:665` (shadcn/ui-Datei, nicht manuell zu editieren) | | `react-hooks/exhaustive-deps` (warn) | 1 | `pop3/page.tsx:124` | | `@next/next/no-img-element` (warn) | 1 | `settings/TotpSection.tsx:89` | | unused eslint-disable (warn) | 1 | `mail/[id]/page.tsx:401` | Der Großteil (`set-state-in-effect`, `refs`, `purity`) stammt aus den neuen React-Compiler-Regeln von `eslint-plugin-react-hooks` v7. Empfehlung: Folge-Ticket für die Hook-Findings (potenziell echte Cascading-Render-Performance-Themen, überschneidet sich thematisch mit PROJ-78), die 4 `no-html-link-for-pages`-Fehler sind separat klein und schnell. - Verifikation: `npx tsc --noEmit` → **0 Fehler**; `npm run build` → **erfolgreich**, alle 14 Routen generiert. ### Lint-Findings gefixt — 2026-08-05 Alle **30 Findings** (25 Fehler, 5 Warnungen) aus der obigen Tabelle sind behoben. `npm run lint` läuft jetzt mit **0 Fehlern / 0 Warnungen** durch. Es wurden keine Regeln global deaktiviert und die Flat-Config nicht abgeschwächt. **`react-hooks/set-state-in-effect` (19×)** — drei Fix-Muster, je nach Fall: 1. *Ableitung statt Effect-State* (echte Struktur-Fixes): - `src/hooks/useSystemInfo.ts`: `setSystemInfoLoading(true)` entfernt — Effect läuft nur beim Mount (leere Deps), Initialwert ist bereits `true`. - `src/app/verify/page.tsx`, `src/app/signup/page.tsx`: der Fehlerzustand „kein Token“ / „kein Einladungslink“ folgt direkt aus der URL und wird jetzt als `useState`-Initialwert abgeleitet statt im Effect gesetzt. - `src/app/search/page.tsx`: Zurücksetzen der Auswahl bei neuen Ergebnissen als *„State beim Rendern anpassen“* (`prevResults`-Vergleich) — das von React empfohlene Muster, keine veraltete Auswahl mehr sichtbar. - `src/hooks/use-mobile.tsx`: auf `useSyncExternalStore` umgestellt (Viewport = externer Store). Spart den Extra-Render, SSR-Snapshot `false` entspricht dem bisherigen `undefined → !!undefined === false`. 2. *Fetch-Logik in async-Funktion gekapselt + Cancel-Guard* (`src/hooks/useSavedSearches.ts`, `src/hooks/useSearch.ts`): Loading-/ Ergebnis-States werden in einer inneren `async`-Funktion gesetzt; zusätzlich verhindert ein `cancelled`-Flag im Cleanup, dass eine veraltete Antwort noch State schreibt (echte Verbesserung, vorher nicht vorhanden). 3. *Ladeaufruf in async-Wrapper* für die 11 Stellen, die lediglich eine bestehende `load()`/`checkAuth()`-Funktion im Effect anstoßen (`useAuth`, `useAdminDashboard`, `useImapAccounts`, `pop3/page.tsx`, `TenantLDAPDialog`, `ArchivingRulesTab`, `DSGVOTab`, `QuotaTab`, `ReconciliationCard`, `RetentionTab`, `RoutingRulesTab`, `SMTPOutTab`): `void (async () => { await load(); })();` mit erklärendem Kommentar. Ablauf, Timing und Ladeanzeige bleiben identisch — die Regel greift nur auf synchron im Effect-Body erreichbare State-Updates zu. **`react-hooks/refs` (1×, `useSearch.ts`)** — der Filter-Spiegel-Ref wurde während des Renders beschrieben. Die Synchronisation läuft jetzt in einem `useEffect` ohne Dep-Array (nach jedem Commit). `doSearch` liest den Ref ausschließlich in Event-Handlern/Effects, also immer nach dem Commit; der synchrone Schnellpfad in `setQuery` (Enter während Debounce) bleibt erhalten. **`@next/next/no-html-link-for-pages` (4×)** — `` → `` aus `next/link` in `admin/login`, `forgot-password` (2×), `signup`, jeweils mit ergänztem Import. **`react-hooks/exhaustive-deps` (1×, `pop3/page.tsx`)** — `pollingRefs.current` wird im Effect in eine lokale Variable kopiert und die Cleanup-Funktion nutzt diese. Die Map-Instanz wird nie neu zugewiesen, Verhalten unverändert. **Unused `eslint-disable` (1×, `mail/[id]/page.tsx`)** — ersatzlos entfernt, `exhaustive-deps` meldet dort nichts mehr. **Bewusst belassene `eslint-disable`-Kommentare (4 Stück, alle begründet):** | Datei | Regel | Begründung | |---|---|---| | `src/components/ui/sidebar.tsx:665` | `react-hooks/purity` | shadcn/ui-Komponente — laut CLAUDE.md keine Custom-Änderungen. Die zufällige Skeleton-Breite ist gewollt und durch `useMemo` pro Mount stabil. | | `src/components/settings/TotpSection.tsx` | `@next/next/no-img-element` | QR-Code kommt als `data:image/png;base64`-URL vom Backend; `next/image` bringt keinen Nutzen (kein Netzwerk-Request, keine Optimierung möglich). | | `src/app/verify/page.tsx` | `@next/next/no-location-assign-relative-destination` | Voller Reload nach Auth-Aktion ist beabsichtigt (`.claude/rules/frontend.md`: „Use `window.location.href` for post-login redirect“), damit Auth-/Client-Cache sauber neu initialisiert wird. | | `src/app/reset-password/page.tsx` | dito | dito (nach Passwort-Reset). | Verifikation: `npm run lint` → **0 Probleme**; `npx tsc --noEmit` → **0 Fehler**; `npm run build` → **erfolgreich**, alle 14 Routen generiert. Lint-Fix + 30 Findings deployed auf 132 und 131 am 2026-08-05. Beide Deploys via `update.sh` (Backend + Frontend Build erfolgreich), Backend ✓ läuft / Frontend ✓ läuft auf beiden Servern bestätigt. Health-Check (`/api/health` → `{"status":"ok"}`) und Kernrouten `/`, `/search`, `/admin/login` → alle HTTP 200 auf beiden Servern gegen den echten laufenden Dienst geprüft. ### @types/node (Punkt 3) — 2026-08-06 `node -v` per SSH auf beiden Servern geprüft: **v20.19.2** auf 131 und 132, identisch. Also kein Sprung auf ^26 (das würde Typen vor die tatsächlich laufende Node-Runtime setzen) — stattdessen nur Patch-Update innerhalb ^20: `@types/node` `^20` → `^20.19.43`. `npx tsc --noEmit` 0 Fehler, `npm run build` erfolgreich, alle Routen generiert. Deployed auf 132 und 131 am 2026-08-06 (Commit fa8c5dd), Backend ✓ / Frontend ✓ auf beiden Servern, Health-Check ok. ### tailwindcss v4 + tailwind-merge v3 (Punkt 4) — 2026-08-06 - `npx @tailwindcss/upgrade@latest --force` (offizielles Migrationstool) ausgeführt. Umbau: `tailwind.config.ts` **entfernt**, Theme (Farben, Border-Radius, Keyframes/Animation) nach `src/app/globals.css` als `@theme`-Block überführt (v4-Standard: CSS-first-Config). `@tailwind base/components/utilities` → `@import 'tailwindcss'` + `@custom-variant dark (&:is(.dark *))`. PostCSS-Config auf `@tailwindcss/postcss` umgestellt, `autoprefixer` entfernt (in v4 eingebaut). 31 Komponenten-/Seiten-Dateien automatisch migriert (`outline-hidden` statt `outline-none`, u.a.). - `tailwind-merge` 2.6.0 → **3.x** im selben Schritt (Pflicht-Kopplung, siehe Problem-Abschnitt). - **Codemod-Bug gefunden + gefixt:** das Migrationstool hat `variant="outline"` (Prop-*Wert*, kein CSS-Klassenname) blind textuell zu `outline-solid` umbenannt — Verwechslung mit der v4-Umbenennung der `outline`-Utility-Klasse. Betraf 4 Call-Sites (`pagination.tsx:52`, `ServicesTab.tsx:100`, `DSGVOTab.tsx:46/56/71/480`, `DashboardTab.tsx:448`), dort als `tsc`-Fehler sichtbar geworden (`"outline-solid"` nicht Teil des `variant`-Union-Typs von `badgeVariants`/`buttonVariants`). Manuell auf `"outline"` zurückgesetzt — `badge.tsx`/`button.tsx` selbst (die tatsächliche Variant-Definition) waren vom Bug nicht betroffen. - **`*-opacity-*`-Lint-Regel ergänzt** (`eslint.config.mjs`): `no-restricted-syntax` gegen `bg-/text-/border-/ring-/divide-/placeholder-opacity-\d+`-Literale, da diese Utilities in v4 nicht mehr existieren und von `tailwind-merge` v3 lautlos verschluckt werden statt einen Build-Fehler zu werfen (siehe Problem-Abschnitt). Gegen echten Bestand getestet: 0 Treffer (Codebase nutzt bereits nur reines `opacity-*`, keine `*-opacity-*`-Utilities). Funktionstest mit synthetischer Testdatei bestätigt: Regel schlägt korrekt an. - Verifikation: `npx tsc --noEmit` 0 Fehler, `npm run lint` 0 Findings, `npm run build` erfolgreich (alle 14 Routen). - **Visueller Regressionstest:** kein Browser-Tool in dieser Session verfügbar, daher HTTP/HTML-Sanity-Check als Ersatz: CSS-Link im `` vorhanden, CSS-Bundle enthält vollständige v4-Struktur (`@layer properties`, 56 `--tw-*`-Custom-Properties), Response-Größen `/`, `/admin/login`, `/search` vor/nach praktisch identisch, shadcn-Klassen- muster im HTML unauffällig, keine Runtime-Errors in journalctl. Kein echter Pixel-Vergleich durchgeführt. - Deployed auf 132 (2026-08-06) und 131 (2026-08-06), Commit 824b24f, auf beiden Servern identisch verifiziert: Backend ✓, Frontend ✓, Health-Check ok, CSS-Bundle korrekt ausgeliefert, keine Fehler in journalctl. ### typescript (Punkt 5) — 2026-08-06 - `typescript` 5 → **6.0.3** (Zwischenschritt): `tsc --noEmit` 0 Fehler, `npm run lint` 0 Findings, `npm run build` erfolgreich, alle 14 Routen. - `typescript` 6.0.3 → **7.0.2** getestet: `tsc --noEmit` selbst lief fehlerfrei, aber `npm run lint` bricht hart ab — `typescript-eslint@8.66.0` (Peer-Range `>=4.8.4 <6.1.0`) wirft explizit `typescript-eslint does not support TS 7.0` und verweist auf [TS-7-Announcement](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#running-side-by-side-with-typescript-6.0) sowie [typescript-eslint#10940](https://github.com/typescript-eslint/typescript-eslint/issues/10940) (offener Tracking-Issue für TS-≥7.1-Support). Gleicher Ökosystem-Lag wie bei Punkt 2 (eslint 10) — kein Fehler in unserem Code. - **Ergebnis: `typescript` bleibt auf `^6.0.3`**, nicht auf 7.x. Rollback von 7.0.2 durchgeführt, danach erneut `tsc`/`lint`/`build` grün verifiziert. - Neuer Anlauf auf 7.x, sobald `typescript-eslint` das offiziell unterstützt (Issue #10940 beobachten). ## Acceptance Criteria - [x] `lucide-react` auf 1.x, `tsc --noEmit` + `npm run build` grün. - [ ] `eslint` auf 10.x + `eslint-config-next` kompatibel, `npm run lint` grün. **Blockiert:** `eslint-plugin-react`/`-jsx-a11y`/`-import` haben noch keine ESLint-10-fähige Version — eslint bleibt auf 9.x. Flat-Config- Migration + Reparatur des `lint`-Scripts (`next lint` existiert in Next 16 nicht mehr) sind erledigt; `npm run lint` läuft wieder und ist seit 2026-08-05 **grün** — alle 30 Bestands-Findings gefixt (Details in den Implementation Notes, Abschnitt „Lint-Findings gefixt“). - [x] `@types/node` auf zur Server-Node-Version passende Major-Version. Node auf 131 + 132 verifiziert: **v20.19.2** auf beiden (identisch). `@types/node` daher **nicht** auf ^26 gesprungen — bleibt auf ^20-Linie, Patch auf `^20.19.43` aktualisiert. `tsc --noEmit` + `npm run build` grün. - [x] `tailwindcss` v4 + `tailwind-merge` v3 gemeinsam umgesetzt, Lint-Regel gegen `*-opacity-*`-Klassen aktiv. Deployed auf 132+131, HTTP/HTML- Sanity-Check statt echtem Pixel-Vergleich (kein Browser-Tool verfügbar) — CSS-Bundle-Struktur, Response-Größen, Klassenmuster unauffällig auf beiden Servern. - [x] `typescript` schrittweise auf 7.x, `tsc --noEmit` bei jedem Zwischenschritt grün. **Ziel nicht erreicht:** 5→6.0.3 geschafft, 6→7 blockiert (`typescript-eslint` unterstützt TS 7 noch nicht, Issue #10940). `typescript` bleibt auf `^6.0.3`. - [x] Jeder Schritt einzeln auf 132 verifiziert vor 131-Deploy.