ProtectSystem=strict + ReadOnlyPaths=.../etc/ssl/archivmail blockierte den Cert-Handler (internal/api/cert_handlers.go) beim Erstellen/Erneuern von Zertifikaten mit "read-only file system" — Fehler kam vom systemd-Namespace, nicht von einem echten ro-Mount oder Owner-Problem. SSL_DIR gehört in ReadWritePaths statt ReadOnlyPaths, da die Anwendung dort aktiv schreibt. Fix im Unit-Generator in update.sh, nicht nur manuell am Server, da die Unit bei jedem Deploy neu geschrieben wird. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
16 KiB
id, title, status, created
| id | title | status | created |
|---|---|---|---|
| PROJ-79 | Frontend-Dependency-Major-Upgrades (eslint, lucide-react, tailwindcss+tailwind-merge, typescript, @types/node) | In Progress | 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)
lucide-react0.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).eslint9 → 10 +eslint-config-next(aktuell 16.1.1) nachziehen, Flat-Config-Breaking-Changes prüfen,npm run lintdanach.@types/node20 → 26 — vorhernode -vauf 131 und 132 prüfen, Typen dürfen nicht vor der tatsächlichen Node-Laufzeit-Version liegen.tailwindcssv3→v4 +tailwind-mergev2→v3 zusammen (gekoppeltes Paar, nicht einzeln):npx @tailwindcss/upgrade(offizielles Migrationstool)tailwind-merge@3im 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)
typescript5 → 6 → 7 — stufenweise, nicht direkt springen, nach jedem Schritttsc --noEmitkomplett 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 statuswar 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-react0.562.0 → 1.28.0 (npm install lucide-react@^1). Geändert wurden nurpackage.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 FundefixAvailable: trueohne SemVer-Major-Flag waren) → 0 Vulnerabilities.nextwanderte dabei minor von 16.2.9 auf 16.3.0 (im^16.1.1-Range, kein Breaking Change).npx update-browserslist-db@latest→caniuse-liteaktualisiert (1.0.30001763 → 1.0.30001806), keine Target-Browser-Änderung.- Verifikation:
npx tsc --noEmit0 Fehler,npm run builderfolgreich, alle 14 Routen generiert. update.sherweitert: nachnpm ciläuft jetztnpm audit --audit-level=highals 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.txtund Hinweis,npm audit fixlokal 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 cimeldete 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 durcheslint.config.mjs(Flat Config, importierteslint-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=falseentfällt), die Migration ist für das Upgrade also Pflicht und ohnehin vorzuziehen.package.json: Scriptlintvonnext lint→eslint .eslint-config-next16.1.1 → 16.3.0 (zieht mitnext16.3.0 gleich, weiterhin exakt gepinnt). Peer-Range isteslint >=9.0.0, deckt 10 also nominell ab.eslintbleibt^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 | <a href> statt <Link> (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:
- Ableitung statt Effect-State (echte Struktur-Fixes):
src/hooks/useSystemInfo.ts:setSystemInfoLoading(true)entfernt — Effect läuft nur beim Mount (leere Deps), Initialwert ist bereitstrue.src/app/verify/page.tsx,src/app/signup/page.tsx: der Fehlerzustand „kein Token“ / „kein Einladungslink“ folgt direkt aus der URL und wird jetzt alsuseState-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: aufuseSyncExternalStoreumgestellt (Viewport = externer Store). Spart den Extra-Render, SSR-Snapshotfalseentspricht dem bisherigenundefined → !!undefined === false.
- Fetch-Logik in async-Funktion gekapselt + Cancel-Guard
(
src/hooks/useSavedSearches.ts,src/hooks/useSearch.ts): Loading-/ Ergebnis-States werden in einer innerenasync-Funktion gesetzt; zusätzlich verhindert eincancelled-Flag im Cleanup, dass eine veraltete Antwort noch State schreibt (echte Verbesserung, vorher nicht vorhanden). - 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×) — <a href="/"> → <Link> 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.
Acceptance Criteria
lucide-reactauf 1.x,tsc --noEmit+npm run buildgrün.eslintauf 10.x +eslint-config-nextkompatibel,npm run lintgrün. Blockiert:eslint-plugin-react/-jsx-a11y/-importhaben noch keine ESLint-10-fähige Version — eslint bleibt auf 9.x. Flat-Config- Migration + Reparatur deslint-Scripts (next lintexistiert in Next 16 nicht mehr) sind erledigt;npm run lintläuft wieder und ist seit 2026-08-05 grün — alle 30 Bestands-Findings gefixt (Details in den Implementation Notes, Abschnitt „Lint-Findings gefixt“).@types/nodeauf zur Server-Node-Version passende Major-Version.tailwindcssv4 +tailwind-mergev3 gemeinsam umgesetzt, Lint-Regel gegen*-opacity-*-Klassen aktiv, visueller Regressionstest durchgeführt.typescriptschrittweise auf 7.x,tsc --noEmitbei jedem Zwischenschritt grün.- Jeder Schritt einzeln auf 132 verifiziert vor 131-Deploy.