# Changelog All notable changes to this project are documented in this file. ## [1.129.3] - 2026-09-15 ### Fixed - **"Failed to update event" bei Outlook-Terminen** ("IDX14100: JWT is not well formed" in den Server-Logs). Die Verschlüsselung von `accessToken`/`refreshToken` in `CalendarConnection` (siehe [1.x] Security-Härtung) läuft als Prisma-Client-Extension ausschließlich auf direkten `calendarConnection`-Queries — Next.js/Prisma-Extensions fangen keine verschachtelten `include`-Relationen ab, die über ein anderes Modell geladen werden. Vier Routen luden Verbindungen aber genau so: `prisma.user.findUnique({ include: { calendarConnections: true } })`. Für Apple/Synology (statisches App-Passwort, nie neu geschrieben) blieb das unbemerkt, weil deren Zeilen nie verschlüsselt wurden. Outlook-Tokens werden dagegen bei jedem Refresh neu geschrieben und damit verschlüsselt — ab dann schickte die App den Chiffretext statt des echten Tokens an Microsoft Graph, die Anfrage lief mit 401 auf. - Betroffen und korrigiert: `api/calendar/events/route.ts` (Termin erstellen/ändern/ löschen — der hier gemeldete Fehler), `api/calendar/sync/route.ts` (normale Anzeige- Synchronisation), `api/calendar/background-sync/route.ts` (Hintergrund-Sync), `api/calendar/connections/route.ts` (Verbindungsliste, inkl. Apple/Synology- Farbaktualisierung im Hintergrund). Alle vier laden `CalendarConnection` jetzt per direktem `calendarConnection.findMany`/`findFirst` statt über `user`-`include`. Verifiziert nicht nur mit Typecheck/Build: Wegwerf-Postgres (Docker) aufgesetzt, eine `CalendarConnection` mit echtem Prisma-Client (inkl. Verschlüsselungs-Extension) angelegt — Rohwert in der DB trägt das `enc1:`-Präfix. Der alte `include`-Pfad lieferte exakt den Chiffretext zurück (Reproduktion des Produktionsfehlers), der neue direkte Query-Pfad das korrekte Klartext-Token. ## [1.129.2] - 2026-09-14 ### Fixed - **Kalendertermine und der "All day"-Zähler fehlten in weiten Ansichten** (Tage-Auswahl > 14, z.B. 21 oder 31 Tage): `fetchCalendarEvents` fragte serverseitig immer nur ein festes Fenster von `currentWeekStart - 7` bis `currentWeekStart + 14` Tage ab, egal wie breit die Ansicht tatsächlich eingestellt war. Spalten jenseits von Tag 14 blieben dadurch leer, und der Zähler neben "all day" zählte nur die tatsächlich geladenen Termine mit — beides sah nach fehlenden Daten aus, obwohl die Termine existierten. `timeMax` skaliert jetzt mit `viewDays` (`currentWeekStart + max(14, viewDays + 7)` Tage), der Standard-7-Tage-Fall bleibt unverändert (weiterhin 14 Tage Vorlauf). Verifiziert: Typecheck und Produktions-Build grün (Scratchpad-Klon), 118 Unit-Tests grün, Datumsrechnung für viewDays 7/14/21/31 per Skript gegengeprüft — das neue Fenster deckt in jedem Fall den letzten sichtbaren Tag plus Puffer ab. ## [1.129.1] - 2026-09-14 ### Fixed - **"PrismaClient is unable to run in this browser environment"** beim Öffnen der Einstellungen (Zahnrad-Icon). `src/lib/export-fields.ts` importierte `prisma` auf Modulebene für das dortige `fetchExportData`, wurde aber von `SettingsSidebar.tsx` (Client-Komponente) wegen `EXPORT_FIELDS` mitimportiert — der komplette Import-Graph landete dadurch im Client-Bundle. `fetchExportData` (und damit der Prisma-Import) ist jetzt in `src/lib/export-data-server.ts` ausgelagert; `export-fields.ts` enthält nur noch reine, client-sichere Helfer und Konstanten. - Zusätzlicher Produktionsvorfall, unabhängig von diesem Bug: der v1.129.0-Build war durch einen externen Container-Reboot mitten im Deploy-Zyklus beschädigt (`.next/standalone/` unvollständig) und wurde per Rollback auf v1.128.0 wieder hergestellt, bevor dieser Fix gebaut und ausgerollt wurde — daher fehlte die "Überfällig"-Übersicht zwischenzeitlich komplett aus der Oberfläche. Verifiziert: Typecheck und Produktions-Build (Scratchpad-Klon, da `/home/sparky/Projekte` auf CIFS liegt) grün, `grep PrismaClient` über alle Client-Chunks von `/tasks` leer. ## [1.129.0] - 2026-09-14 ### Added - **Neue "Überfällig"-Übersicht** für Aufgaben, die in der Vergangenheit geplant und nicht erledigt wurden. Bislang wandern nur Aufgaben mit aktiviertem Rolling-Schalter (oder bei global aktiviertem Auto-Rolling) automatisch auf heute — alles andere blieb für immer auf seinem ursprünglichen Datum stehen und verschwand aus dem Blick, sobald man in der Wochenansicht weitergeklickt hat. - Neuer Menüpunkt "Überfällig" mit Anzahl-Badge, direkt neben "Wiederkehrend" in allen bestehenden Menü-Varianten (linke/obere Randleiste, mobile Leiste, Kopfzeile). - Dialog gruppiert nach dem ursprünglich geplanten Tag (älteste zuerst), pro Aufgabe Checkbox zum Erledigen und ein Knopf zum Verschieben auf heute, dazu "Alle auf heute verschieben". - Reine Erkennungs-/Gruppierungslogik in `src/lib/weekly/overdue.ts` (10 Unit-Tests), Darstellung in `src/components/OverdueTasksManager.tsx` — nutzt `LinkifiedText` (v1.128.0), Titel mit URLs bleiben also auch hier anklickbar. - Verschieben/Erledigen läuft über die bestehenden `moveTaskToSlot`/`toggleTask`-Pfade, keine neue API, keine neue Persistenzlogik. Verifiziert nicht nur mit Unit-Tests: lokale Instanz mit Wegwerf-Postgres (Docker) aufgesetzt, drei überfällige Aufgaben geseedet (eine davon mit URL im Titel), per Playwright eingeloggt und die Übersicht geöffnet — Badge, Gruppierung und Link-Rendering per Screenshot geprüft. Danach real geklickt: eine Aufgabe per Checkbox erledigt, eine per Knopf auf heute verschoben, die restlichen zwei per Sammelknopf — alle drei Änderungen per Datenbankabfrage bestätigt. Zusätzlich: Typecheck, Lint (0 Fehler), 118 Unit-Tests (10 neu), Produktions-Build. ## [1.128.0] - 2026-09-14 ### Fixed - **Eine im Aufgabentitel getippte URL war kein Link, sondern nur Text** — ein Klick darauf hat je nach Ansicht die Aufgabe durchgestrichen (Tagesraster) oder den Bearbeiten-Modus geöffnet (Kanban/Someday/Liste/Priorität), statt den Link zu öffnen. Neue reine Erkennungsfunktion `splitTextAndUrls` (`src/lib/weekly/format.ts`, 8 Unit-Tests) zerlegt den Titel in Text- und URL-Abschnitte; die neue Komponente `LinkifiedText` (`src/components/weekly/LinkifiedText.tsx`) rendert URL-Abschnitte als echten Link mit `stopPropagation`, damit ein Klick darauf ausschließlich den Link öffnet. Eingebaut in `GridTaskBlock.tsx`, `weekly/TaskItem.tsx` und `PriorityView.tsx` — überall dort, wo ein Aufgabentitel als reiner Text stand. Keine Änderung an Daten, API oder dem separaten `task.url`-Feld. Verifiziert: Typecheck, Lint (0 Fehler), 108 Unit-Tests (8 neu), Produktions-Build. ## [1.127.0] - 2026-09-14 ### Added - **Aufgaben lassen sich jetzt auch auf Touch-Geräten zwischen Zeitslots verschieben.** Bisher funktionierte das nur am Desktop, weil `WeeklyView.tsx` ausschließlich natives HTML5-Drag nutzte, das auf Touch-Geräten nicht feuert (siehe v1.126.0). Neuer Long-Press-Mechanismus in `GridTaskBlock`/`WeeklyView.tsx` (200 ms Halten, danach Ziehen), technisch dieselbe Zwei-Phasen-Architektur wie der bereits bestehende Touch-Long-Press für „Termin per Ziehen anlegen". Ruft am Ende dasselbe `handleDrop` wie der Desktop-Pfad auf — keine eigene Entscheidungslogik, keine geänderte API-Payload. Verifiziert nicht nur mit Unit-Tests, sondern gegen eine echte, lokal laufende Instanz mit eigener Wegwerf-Datenbank: ein per Playwright simulierter Touch-Drag (echte `TouchEvent`-Objekte, 200 ms Halten, schrittweise Bewegung, Loslassen) hat eine Aufgabe tatsächlich auf einen neuen Zeitslot verschoben, in der Datenbank nachgeprüft. Derselbe Test mit `dragTo()` (Maus/Desktop) bestätigt, dass der bestehende Pfad unverändert funktioniert. Zusätzlich: Typecheck, Lint (0 Fehler), 100 Unit-Tests, Produktions-Build. ## [1.126.0] - 2026-09-13 ### Changed - Erster Refactoring-Schnitt aus `TODO-CODESCENE-FORTSETZUNG-2026-09-12.md`: die fachliche Drop-Entscheidung aus `WeeklyView.tsx`s `handleDrop` (Ziel-Slot-Suche bei Belegung, Unterschied Subtask-Beförderung/Someday-Verschieben/normales Verschieben) ist jetzt eine reine, isoliert getestete Funktion `decideTaskDrop` in `src/lib/weekly/drag-drop.ts`. `handleDrop` liest nur noch Eingaben, ruft die Entscheidung auf und führt exakt die bisherigen Seiteneffekte aus (kein geändertes `fetch`, kein geändertes optimistisches UI-Update, keine geänderte API-Payload). - 15 neue Unit-Tests decken die Entscheidung ab: freier/belegter/geschützter Slot, Drop ohne Slot mit/ohne vorhandene Startzeit, Subtask-Beförderung, Someday-Provider-Vererbung, externe Sync-Bedingung, virtuelle wiederkehrende Instanzen (Server materialisiert sie weiterhin unverändert). ### Bekannter, nicht behobener Befund - Aufgaben lassen sich per Drag-and-Drop zwischen Zeitslots **nur auf dem Desktop** verschieben — `WeeklyView.tsx` nutzt dafür ausschließlich natives HTML5-Drag (`draggable`/ `onDragStart`/`onDrop`), das auf Touch-Geräten nicht feuert. Kein separater Touch-Mechanismus existiert; `react-dnd`/`react-dnd-html5-backend` sind installiert, aber nirgends importiert. `decideTaskDrop` kennt "Drag" gar nicht (nur Quelle/Ziel als Daten) und könnte von einem künftigen Touch-Mechanismus unverändert wiederverwendet werden — das Bauen dieses Mechanismus selbst ist ein eigenes Feature-Vorhaben, nicht Teil dieses Refactorings. Verifiziert: Typecheck, Lint (0 Fehler), 100 Unit-Tests (85 → 100, 15 neu), Produktions-Build — alle grün. Kein Playwright-E2E-Lauf für Drag-and-Drop (existiert im Repo bislang nicht, siehe `TODO-CODESCENE-FORTSETZUNG-2026-09-12.md`). ## [1.125.4] - 2026-09-13 ### Changed - `.gitignore`: `TODO.md` ausgeschlossen — persönliche Arbeitsliste, nicht Teil des Repos. ## [1.125.3] - 2026-09-12 ### Fixed - `SONAR_TOKEN`/`SONAR_HOST_URL` sind jetzt als GitHub-Secret/-Variable gesetzt (frisches Token vom Besitzer erhalten, gleichzeitig `~/.sonar-token` auf dem Spark wiederhergestellt — der nächtliche lokale Scan lief seit mindestens 09.09. fehl). Der Sonar-Schritt im Workflow authentifiziert jetzt erfolgreich, scheiterte im ersten echten Testlauf aber an einem neuen, unabhängigen Problem: der Standard-Node-Heap der JS/TS-Analyse-Bridge (~2,2 GB) reicht auf dem GitHub-Hosted-Runner für dieses Repo nicht — dieselbe Fehlerklasse wie der `next build`-OOM-Absturz von v1.119.3/v1.119.4. Behoben mit `-Dsonar.javascript.node.maxspace=4096`. ## [1.125.2] - 2026-09-12 ### Fixed - v1.125.1s `if: ${{ secrets.SONAR_TOKEN != '' }}` machte den Workflow bei GitHub selbst ungültig (0-Sekunden-Fehlschlag ohne Job-Log, "likely failed because of a workflow file issue") — live im Actions-Lauf verifiziert, nicht nur vermutet. Ersetzt durch reines `continue-on-error: true` auf dem Sonar-Schritt, ohne Bedingung. Erreicht dasselbe Ziel (fehlendes Secret reißt den Workflow nicht mit runter) ohne den ungültigen Ausdruck. ## [1.125.1] - 2026-09-12 ### Fixed - **`.github/workflows/sonarqube.yml` war seit dem 02.09. dauerhaft rot** — nicht mehr wegen Typecheck/Lint (die sind seit Block C1 grün), sondern weil `SONAR_TOKEN` als Repo-Secret nie gesetzt wurde. Der Sonar-Schritt läuft jetzt nur noch, wenn das Secret existiert, und ist zusätzlich `continue-on-error`, damit ein nicht erreichbarer selbst gehosteter SonarQube-Server nicht den ganzen Workflow mit runterreißt. Ergänzt: ein Unit-Test-Schritt (`npm run test:unit`), der bislang trotz des Workflow-Namens „Test and SonarQube" komplett fehlte. - **Der lokale nächtliche SonarQube-Scan auf dem Spark lief seit mindestens dem 09.09. jede Nacht fehl** (`ERROR: no readable token at ~/.sonar-token` — die Datei war schlicht weg), unbemerkt mangels Alarmierung. Kein Code-Fix hierfür möglich: braucht ein frisches Sonar-Token vom Besitzer (Login unter https://sq.martin-bierschenk.de), um sowohl `~/.sonar-token` auf dem Spark als auch das GitHub-Secret wiederherzustellen. ## [1.125.0] - 2026-09-12 ### Security - **Content-Security-Policy ergänzt, vorerst als `Content-Security-Policy-Report-Only`.** Blockiert nichts, meldet aber jeden Verstoß an den neuen Endpunkt `POST /api/csp-report` (loggt über `console.error`/PM2). Die Whitelist ist aus dem tatsächlichen Code abgeleitet: `fonts.googleapis.com`/`fonts.gstatic.com` für zur Laufzeit nachgeladene Schriftarten (`FontPicker.tsx`, `WeeklyView.tsx`), `'unsafe-inline'` für Next.js' App-Router-Hydration und den einen `dangerouslySetInnerHTML`-Block. Kein `