Die accessToken/refreshToken-Entschluesselung (Prisma-Extension in lib/prisma.ts)
faengt nur direkte calendarConnection-Queries ab, keine ueber user.findUnique({include})
geladenen Relationen. Outlook-Tokens werden bei jedem Refresh neu geschrieben (also
verschluesselt) und wurden dadurch als Chiffretext an Microsoft Graph geschickt -> 401
"JWT is not well formed". Apple/Synology blieben unbemerkt, weil ihre statischen
App-Passwoerter nie neu geschrieben und damit nie verschluesselt wurden.
Betroffen: calendar/events, calendar/sync, calendar/background-sync,
calendar/connections. Alle vier laden CalendarConnection jetzt direkt statt ueber
user-include.
v1.129.3
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
841 lines
46 KiB
Markdown
841 lines
46 KiB
Markdown
# 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 `<iframe>`, keine Drittanbieter-Bilder,
|
||
keine clientseitigen Fetches zu fremden Domains gefunden. Scharf geschaltet wird erst,
|
||
nachdem ein paar Tage Reports durchgesehen wurden — insbesondere die OAuth-Login-Flows
|
||
ließen sich hier nicht interaktiv durchklicken.
|
||
|
||
## [1.124.0] - 2026-09-12
|
||
|
||
**`CREDENTIAL_ENCRYPTION_KEY` wurde vor dem Deploy in der Produktions-`.env` gesetzt.**
|
||
|
||
### Security
|
||
- **Apple-/Synology-Kalenderpasswörter und OAuth-Tokens werden jetzt verschlüsselt
|
||
gespeichert.** `CalendarConnection.accessToken`/`refreshToken` enthielten bei Apple- und
|
||
Synology-Verbindungen das echte Passwort im Klartext (`"email:passwort"`). Neue
|
||
Prisma-Client-Extension (`src/lib/prisma.ts`) ver-/entschlüsselt diese Felder transparent
|
||
für alle bestehenden Aufrufstellen (AES-256-GCM, `src/lib/credential-crypto.ts`).
|
||
Bestehende Klartextzeilen bleiben lesbar (`enc1:`-Präfix unterscheidet altes von neuem
|
||
Format) und werden beim nächsten Schreiben automatisch verschlüsselt — keine Migration,
|
||
keine erzwungene Neuverbindung nötig.
|
||
|
||
### Erforderlich vor dem Deploy
|
||
`CREDENTIAL_ENCRYPTION_KEY` (32 Byte, base64) muss in der Produktions-`.env` stehen. Ohne
|
||
diese Variable wirft jeder Kalender-Request eine Exception. Erzeugen mit:
|
||
```bash
|
||
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
|
||
```
|
||
|
||
Verifiziert: Typecheck, Lint, 85 Unit-Tests (6 neu für `credential-crypto`),
|
||
Produktions-Build — alle grün, mit einem Test-Schlüssel gebaut.
|
||
|
||
## [1.123.2] - 2026-09-12
|
||
|
||
Erste drei Punkte aus dem TODO-Block L (Sicherheitsdurchgang vom 12.09.) — sichere, in
|
||
sich abgeschlossene Änderungen ohne Datenmodell-Migration.
|
||
|
||
### Security
|
||
- **Kein automatischer `db push --accept-data-loss`-Fallback mehr in
|
||
`scripts/update-app.sh`.** Eine fehlgeschlagene `prisma migrate deploy` bricht das
|
||
Deployment jetzt sauber ab statt unbeaufsichtigt das Schema zu überschreiben — genau
|
||
dieser Fallback ist am 11.09. beim Boot-Lauf tatsächlich ausgelöst worden.
|
||
- **14 API-Routen gaben rohe Fehlermeldungen (`error.message`) direkt an den Client
|
||
zurück** (Kalender-Connect/-Sync, Task-Import/-Sync, Someday-Listen, Datenimport). Neuer
|
||
Helper `src/lib/api-error.ts` (`safeErrorMessage`): generische Meldung in Produktion,
|
||
echte Meldung nur noch lokal beim Entwickeln. Volles Logging bleibt unverändert in
|
||
`console.error`.
|
||
- SMTP-Zertifikatsprüfung wieder aktiv: `tls: { rejectUnauthorized: false }` in
|
||
`src/lib/email.ts` entfernt — stand unbegründet seit dem allerersten Commit der Datei und
|
||
öffnete Tür für Man-in-the-Middle auf Passwort-Reset-/Verifizierungs-Mails.
|
||
|
||
Verifiziert: Typecheck, Lint, 79 Unit-Tests, Produktions-Build alle grün.
|
||
|
||
## [1.123.1] - 2026-09-12
|
||
|
||
### Fixed
|
||
- **Erster Deploy nach dem next@15-Upgrade schlug am Smoke-Test fehl**, weil
|
||
Next.js den Standalone-Output um einen Ordner tiefer verschachtelt hat als
|
||
erwartet: `.next-build/standalone/My-Weekly-ToDo-List/server.js` statt
|
||
`.next-build/standalone/server.js`. Ursache: Next sucht per Lockfile-Suche
|
||
nach oben einen "Workspace-Root" fuer das File-Tracing und fand dabei eine
|
||
verwaiste, leere `package-lock.json` in `/root` auf dem Produktionshost --
|
||
hielt `/root` faelschlich fuer die Projektwurzel und spiegelte deren
|
||
Ordnerstruktur in den Output. `scripts/update-app.sh` erwartet den flachen
|
||
Pfad fest. `outputFileTracingRoot: __dirname` in `next.config.js` verankert
|
||
die Wurzel jetzt explizit -- unabhaengig davon, was sonst noch im
|
||
Dateisystem oberhalb des Projekts liegt.
|
||
- Die laufende App war davon nicht betroffen: der Smoke-Test hat den kaputten
|
||
Build wie vorgesehen abgefangen, bevor umgeschaltet wurde.
|
||
|
||
## [1.123.0] - 2026-09-12
|
||
|
||
### Security
|
||
- **`next` 14.2.35 → 15.5.24.** Patcht mehrere kritische Advisories, darunter zwei
|
||
unauthentifizierte RCE-Klassen (Windows-Hosts, AVIF-Bildoptimierung — beide auf
|
||
diesem Linux-Host ohnehin nicht direkt erreichbar, aber jetzt geschlossen) sowie
|
||
diverse DoS-, SSRF- und Cache-Poisoning-Advisories. 15.5.24 patcht bereits alles,
|
||
was sonst erst next@16 fordern würde — ein Major-Sprung statt zwei.
|
||
- **`next-auth` 4.24.13 → 4.24.15**, plus `@auth/core` per `overrides` auf 0.41.3
|
||
erzwungen (next-auth pinnt es sonst exakt auf die verwundbare 0.34.3 — als
|
||
optionalen Peer, den dieses Projekt nirgends importiert). Behebt einen
|
||
E-Mail-Normalisierungs-Bypass, einen Absturz bei fehlerhaften Bearer-Headern und
|
||
eine fehlende Provider-Bindung bei OAuth-State/Nonce/PKCE-Cookies.
|
||
- **`nodemailer` 7 → 10** (`overrides`, da next-auth einen ungenutzten optionalen
|
||
Peer auf 7.x haelt). Behebt SMTP-Command-Injection, CRLF-Header-Injection, eine
|
||
SSRF/Arbitrary-File-Read-Luecke ueber den `raw`-Nachrichtenmodus und eine
|
||
IDN/Punycode-Domain-Bypass beim Empfaenger-Check.
|
||
- `postcss` (auch die von `next` selbst intern gebündelte Kopie, per `overrides`)
|
||
auf 8.5.28. Behebt XSS in der CSS-Ausgabe und mehrfache Pfad-Traversal-/
|
||
Arbitrary-File-Read-Lücken über `sourceMappingURL`.
|
||
- Ungenutztes `@auth/prisma-adapter` (v5-Adapter, nirgends importiert — das
|
||
Projekt nutzt `@next-auth/prisma-adapter` für v4) entfernt.
|
||
- **`npm audit`: 24 → 0 gemeldete Schwachstellen.**
|
||
|
||
### Fixed
|
||
- `google-auth-library` war ein undeklarierter, nur über `googleapis` transitiv
|
||
gehofftes Hoisting verfügbarer Import in `google-tasks.ts`. Ein frischer
|
||
`package-lock.json` (Nebeneffekt der obigen Audit-Fixes) hob ihn nicht mehr
|
||
passend, wodurch zwei strukturell inkompatible Kopien nebeneinander existierten
|
||
und der Typecheck brach. Jetzt eine direkte Abhängigkeit plus `overrides`-Eintrag,
|
||
damit immer genau eine Instanz installiert wird.
|
||
- `nodemailer.Transporter` gibt es als Namespace-Property seit `@types/nodemailer`
|
||
8 nicht mehr — `Transporter` ist jetzt ein benannter Type-Export. Einzige
|
||
betroffene Stelle: `src/lib/email.ts`.
|
||
- Next.js 15: dynamische Routen-Parameter (`{ params }`) sind jetzt ein Promise.
|
||
Einzige betroffene Route: `PATCH`/`DELETE` unter
|
||
`/api/calendar/cross-sync/rules/[id]`.
|
||
|
||
Verifiziert: Typecheck, Lint und 79 Unit-Tests grün; Produktions-Build erfolgreich;
|
||
Login-Seite, Sicherheitsheader und die Auth-Middleware (Redirect bei fehlendem
|
||
Token) im gebauten Standalone-Server manuell geprüft. Kein Playwright-E2E-Lauf,
|
||
da dafür eine echte Datenbank und OAuth-Zugangsdaten nötig sind.
|
||
|
||
## [1.122.0] - 2026-09-12
|
||
|
||
### Security
|
||
- **Login-Endpunkt hatte kein Rate-Limit.** signup/reset-password/verify-email/
|
||
resend-verification waren bereits gedrosselt, der eigentliche Login
|
||
(`authorize()` in `src/lib/auth.ts`) nicht -- ein Bruteforce gegen ein
|
||
bekanntes Konto lief unbegrenzt durch. Jetzt maximal 8 Versuche pro E-Mail
|
||
in 15 Minuten, mit eigener Fehlermeldung auf der Login-Seite.
|
||
- Fehlende Standard-Sicherheitsheader ergaenzt (`X-Content-Type-Options`,
|
||
`X-Frame-Options`, `Referrer-Policy`, `Permissions-Policy`) und
|
||
`X-Powered-By: Next.js` abgeschaltet. Bewusst ohne Content-Security-Policy --
|
||
die braucht eine sorgfaeltige Whitelist fuer OAuth, Google Fonts und die
|
||
FontAwesome-CDN und ist ein eigener Schritt.
|
||
- `npm audit fix` (ohne `--force`) eingespielt: 24 auf 10 gemeldete
|
||
Schwachstellen reduziert, alles ohne Breaking Changes.
|
||
|
||
### Fixed
|
||
- `scripts/update-app.sh` setzt jetzt selbst `PATH=/usr/local/bin:$PATH`.
|
||
Ein Lauf ohne Login-Shell (z. B. `ssh host 'scripts/update-app.sh'`) fand
|
||
`pm2` sonst nicht und brach direkt nach einem erfolgreichen Build beim
|
||
Neustart ab -- am 12.09. live aufgetreten, siehe Betriebsnotiz.
|
||
- `tsconfig.json` nimmt `.next-build/types/**/*.ts` mit auf. Next.js traegt
|
||
diesen Pfad beim Bauen mit `NEXT_DIST_DIR=.next-build` automatisch ein;
|
||
ohne den committeten Eintrag stand das auf dem Produktionshost als
|
||
staendige, nie eingecheckte Abweichung in `git status`.
|
||
|
||
### Known issues
|
||
- **`next` (14.2.35) und `next-auth`/`@auth/core` (4.24.x) bleiben mit
|
||
bekannten kritischen CVEs** (u. a. ein E-Mail-Normalisierungs-Bypass in
|
||
Auth.js und mehrere Next.js-Advisories bis hin zu RCE-Klassen). Der Fix
|
||
ist ein Major-Upgrade auf next@16 und next-auth v5 -- ein eigenes,
|
||
sorgfaeltig zu testendes Vorhaben, kein Patch. `npm audit` zeigt den
|
||
aktuellen Stand.
|
||
|
||
## [1.121.2] - 2026-09-12
|
||
|
||
### Changed
|
||
- Die Spaltenaufteilung sich überlappender Termine im Tagesraster ist aus
|
||
`WeeklyView.tsx` in `src/lib/weekly/event-layout.ts` ausgelagert
|
||
(`layoutDaySegments`/`computeEventOverlapLayout`), mit eigenen Tests
|
||
abgesichert. Erster Schnitt der CodeScene-getriebenen Sanierung; der
|
||
Algorithmus selbst ist unverändert, kein sichtbares Verhalten geändert.
|
||
|
||
## [1.121.1] - 2026-09-12
|
||
|
||
### Fixed
|
||
- **`/api/calendar/apple/sync` war ohne Anmeldung erreichbar** und hat beliebige
|
||
Apple-Zugangsdaten aus dem Request gegen Apple CalDAV geschickt. Die Instanz
|
||
liess sich damit als Relay zum Durchprobieren fremder Zugangsdaten
|
||
missbrauchen. Die Route verlangt jetzt dieselbe Session-Prüfung wie
|
||
`/api/calendar/apple/connect`.
|
||
- `AuthForm.tsx` schrieb E-Mail-Adresse und Passwort im Klartext in die
|
||
Browser-Konsole. Die Debug-Ausgaben sind entfernt.
|
||
|
||
## [1.121.0] - 2026-09-12
|
||
|
||
### Fixed
|
||
- **Termine über Mitternacht verlängerten den Starttag**, statt am Folgetag
|
||
weiterzulaufen. Ein Termin von 22:00 bis 02:00 erschien bisher als ein
|
||
überlanger Block im Starttag; jetzt wird er an der Tagesgrenze geteilt:
|
||
22:00–24:00 am Starttag, 00:00–02:00 am Folgetag — so, wie es Kalender-Apps
|
||
zeigen. Die Aufteilungsregel (`splitEventByDay` in
|
||
`src/lib/weekly/date-utils.ts`) ist mit Unit-Tests abgesichert. Klick, Drag
|
||
und Resize wirken weiterhin auf den ungeteilten Termin; Payloads und API
|
||
sind unverändert.
|
||
|
||
## [1.120.1] - 2026-09-07
|
||
|
||
### Fixed
|
||
- **Das Skript übersprang den Build, wenn vorher von Hand `git pull` ausgeführt wurde.**
|
||
Die Entscheidung hing an `HEAD` vs. `origin/main` — nach einem manuellen Pull sind beide
|
||
gleich, und das Skript sah keinen Grund zu bauen, obwohl der vorhandene Build aus einem
|
||
älteren Commit stammte. Es lieferte weiter den alten Stand aus und meldete dabei
|
||
„Nothing to do". Massgeblich ist jetzt `.next/BUILD_COMMIT`: aus welchem Commit der
|
||
vorhandene Build tatsächlich stammt.
|
||
- **Der Smoke-Test-Server überlebte das Beenden.** `( … ) &` startet eine Subshell, `$!`
|
||
ist deren PID — `node` darin lief als Waise weiter und hielt Port 3999 belegt, womit
|
||
jeder weitere Deploy am Smoke-Test gescheitert wäre. Mit `exec` ersetzt `node` die
|
||
Subshell, `kill` greift, und ein `kill -9` fasst nach. Zusätzlich prüft das Skript vorab,
|
||
ob der Port frei ist, und der ERR-Trap beendet den Server ebenfalls.
|
||
|
||
Beide Fehler kamen bei einem Testlauf ans Licht, nicht in Produktion.
|
||
|
||
## [1.120.0] - 2026-09-07
|
||
|
||
### Changed
|
||
- **Ein fehlgeschlagener Build reisst die laufende App nicht mehr mit.** `next build` lief
|
||
bisher direkt auf `.next` — also auf dem Verzeichnis, aus dem die laufende Instanz bedient
|
||
wird. Brach der Build ab, fand PM2 `server.js` nicht mehr (`MODULE_NOT_FOUND`) und geriet
|
||
in eine Neustartschleife; am 07.09. war die Seite deshalb rund eine Stunde unbrauchbar.
|
||
|
||
Der neue Ablauf in `scripts/update-app.sh`:
|
||
1. Build nach `.next-build` — `.next` bleibt dabei unangetastet
|
||
2. Statische Dateien und `.env` in den neuen Baum kopieren
|
||
3. **Smoke-Test:** Der neue Build wird auf Port 3999 gestartet und muss auf
|
||
`/auth/login` antworten. Ein Build kann durchlaufen und trotzdem nicht starten.
|
||
4. Erst danach umschalten; der alte Stand bleibt als `.next-previous` liegen
|
||
5. PM2 neu starten
|
||
|
||
Scheitert irgendetwas davon, wird `.next-build` weggeräumt und die laufende App bleibt
|
||
unverändert. Zum Zurückrollen genügt `mv .next-previous .next` plus PM2-Neustart.
|
||
|
||
- `next.config.js`: `distDir` kommt jetzt aus `NEXT_DIST_DIR` und fällt auf `.next` zurück.
|
||
|
||
Beide Fälle sind praktisch getestet: Ein abgebrochener Lauf liess `.next` samt Markierung
|
||
unberührt und räumte `.next-build` weg; ein erfolgreicher schaltete sauber um, legte
|
||
`.next-previous` an und bestand den Smoke-Test.
|
||
|
||
## [1.119.4] - 2026-09-07
|
||
|
||
### Fixed
|
||
- Heap-Limit für den Build von 2048 auf 4096 MB angehoben. Der Wert aus v1.119.3 stammte
|
||
aus der Zeit, als der Container 3 GB hatte, und war schlicht zu niedrig: Der Build starb
|
||
punktgenau bei 2045 MB — also an der gesetzten Grenze. Gemessener Bedarf sind rund 2,6 GB.
|
||
|
||
## [1.119.3] - 2026-09-07
|
||
|
||
### Fixed
|
||
- `scripts/update-app.sh` setzt beim Build `NODE_OPTIONS="--max-old-space-size=2048"`.
|
||
Nodes Standard-Heap richtet sich nach dem Arbeitsspeicher und landet auf dem
|
||
Produktionscontainer bei rund 1,5 GB — zu wenig für `next build`, der Deploy starb mit
|
||
`FATAL ERROR: Ineffective mark-compacts near heap limit`.
|
||
|
||
### Notes
|
||
- **Gemessen, nicht vermutet:** ESLint im Build ist *nicht* die Ursache. Spitzenspeicher
|
||
mit ESLint 2.590 MB, ohne 2.595 MB. `eslint.ignoreDuringBuilds` bleibt daher auf `false`
|
||
— eine zwischenzeitliche Rücknahme beruhte auf einer falschen Annahme.
|
||
- Der Build braucht rund **2,6 GB**. Bei 3 GB Container-RAM bleibt das knapp; mehr Speicher
|
||
für LXC 202 steht als Block J in `TODO.md`.
|
||
|
||
## [1.119.2] - 2026-09-07
|
||
|
||
### Changed
|
||
- Die restlichen fünf Fremdkomponenten aus `WeeklyView.tsx` liegen jetzt unter
|
||
`src/components/weekly/`: `TaskInput`, `SomedayAddTask`, `TaskItem` (mit über 900 Zeilen
|
||
die grösste), `ListVisualsPopover` und `TabVisualsPopover`.
|
||
- Die Prioritäts-Abzeichen sind nach `weekly/priority-badge.tsx` gewandert. `GridTaskBlock`
|
||
importierte `getPriorityBadge` aus `WeeklyView.tsx` und zog sich damit die gesamte
|
||
Wochenansicht in seinen Abhängigkeitsgraphen; `WeeklyView` re-exportiert die Funktion,
|
||
bestehende Importe bleiben gültig.
|
||
- **`WeeklyView.tsx`: 11.148 → 9.711 Zeilen** — erstmals unter 10.000. Gegenüber dem Stand
|
||
vor dem Audit (11.906) sind das 2.195 Zeilen oder 18 Prozent.
|
||
|
||
Verifiziert: Typecheck 0 Fehler, `npm run build` Exit 0, 63 Unit-Tests grün.
|
||
|
||
## [1.119.1] - 2026-09-07
|
||
|
||
### Changed
|
||
- `ProjectIcon`, `ProjectsSidebar` und `NotesSidebar` liegen jetzt als eigene Dateien unter
|
||
`src/components/weekly/`. Sie standen in `WeeklyView.tsx`, hatten dort aber bereits eigene
|
||
Props und nichts mit der Wochenansicht zu tun. Die Symbolliste `projectIconsGlobal` ist zu
|
||
`ProjectIcon` gewandert, wo sie als einzige gebraucht wird.
|
||
- `WeeklyView.tsx`: 11.556 → 11.148 Zeilen.
|
||
|
||
In `WeeklyView.tsx` stecken insgesamt **acht** solcher eigenständigen Komponenten mit
|
||
zusammen rund 1.870 Zeilen — fünf davon warten noch (siehe TODO.md D1).
|
||
|
||
Verifiziert: Typecheck 0 Fehler, `npm run build` Exit 0, 63 Unit-Tests grün.
|
||
|
||
## [1.119.0] - 2026-09-07
|
||
|
||
### Changed
|
||
- Der laufende Tag ist jetzt auch im Spaltenkopf hervorgehoben, nicht mehr nur im
|
||
Spaltenkörper. Der Kopf ist `position: sticky` und deckte mit `--weekly-bg` ab, weshalb
|
||
die Färbung erst unterhalb von Wochentag und Datum begann.
|
||
- Die Symbole in Kalenderterminen (Wiederholung, Anbieter) sitzen jetzt oben rechts statt
|
||
unten rechts. Titel und Uhrzeit bekommen dort Freiraum, damit sich nichts überlappt —
|
||
über `:has(.event-icons-row)` nur bei Terminen, die überhaupt Symbole zeigen. Umgesetzt
|
||
für beide Darstellungen: Zeitraster und Liste.
|
||
|
||
## [1.118.2] - 2026-09-07
|
||
|
||
### Fixed
|
||
- **Kein `@ts-nocheck` mehr im Projekt.** Drei Dateien hatten die Typprüfung komplett
|
||
abgeschaltet und blieben deshalb auch von `ignoreBuildErrors: false` unberührt:
|
||
- `calendar-cache.ts` brauchte die Ausnahme überhaupt nicht — ohne sie: null Fehler.
|
||
- `microsoft-todo.ts`: vier Fälle zirkulärer Typinferenz (`url` → `data` → `response`
|
||
→ `url`), aufgelöst durch explizite Annotationen.
|
||
- `outlook-calendar.ts`: Hinter der Ausnahme steckte ein möglicher Laufzeitfehler. Der
|
||
Code streute `response!`-Assertions, eine Stelle war vergessen. Statt einer weiteren
|
||
Assertion prüft der Code jetzt echt, ob nach den drei Wiederholversuchen überhaupt
|
||
eine Antwort vorliegt.
|
||
|
||
### Added
|
||
- `TODO.md`: Block I mit zwei UI-Wünschen — Hervorhebung des aktuellen Tages auch im
|
||
Spaltenkopf, und die Symbole in Kalenderterminen nach oben rechts. Beide mit den
|
||
konkreten Fundstellen in `globals.css`.
|
||
|
||
## [1.118.1] - 2026-09-07
|
||
|
||
### Fixed
|
||
- **Der Build prüft wieder Typen.** `typescript.ignoreBuildErrors` steht auf `false`.
|
||
Damit ist ein erfolgreicher Build endlich wieder ein Beleg dafür, dass der Code
|
||
übersetzbar ist — bisher war er es nicht.
|
||
- **Notion-Anbindung war defekt.** Der Code rief `notion.databases.query()` auf; in
|
||
`@notionhq/client` v5 gibt es diese Methode nicht mehr, der Aufruf lief zur Laufzeit in
|
||
ein "is not a function". Umgestellt auf `dataSources.query()`, die Suche filtert jetzt
|
||
auf `data_source`. **Ungetestet** (kein Notion-Zugang), und gespeicherte Verbindungen
|
||
enthalten alte `database_id`s — betroffene Nutzer müssen Notion neu verbinden.
|
||
- `WeeklyView.tsx` importierte `isAllDayEvent` weiterhin aus `format`, obwohl die Funktion
|
||
in v1.118.0 nach `date-utils` gewandert war. Wäre zur Laufzeit gescheitert.
|
||
- Export-Helfer (`EXPORT_FIELDS`, `isoWeek`, `weekLabel`, `csvCell` …) liegen jetzt in
|
||
`src/lib/export-fields.ts`. Next.js erlaubt in einer `route.ts` nur Route-Handler als
|
||
Exporte; zudem importierte `SettingsSidebar` sie direkt aus der Route und zog damit
|
||
einen Server-Handler in den Client-Graphen.
|
||
- `push-notifications.ts`: `applicationServerKey` braucht seit TypeScript 5.7 einen Cast
|
||
auf `BufferSource`.
|
||
- Typdeklaration für `web-push` in `src/types/web-push.d.ts` ergänzt — als lokale Datei
|
||
statt als Abhängigkeit, weil `npm install` im Projektordner nicht läuft.
|
||
|
||
### Changed
|
||
- `tsconfig.json`: `include` von `**/*` auf `src/**/*` eingegrenzt. **Das war die Ursache
|
||
dafür, dass der Typecheck über 30 Minuten lief** — nicht die Größe von `WeeklyView.tsx`,
|
||
wie zuvor vermutet. Der vollständige Lauf dauert jetzt **19 Sekunden**.
|
||
|
||
## [1.118.0] - 2026-09-07
|
||
|
||
### Added
|
||
- **63 Unit-Tests** in `tests/unit/` für die ausgelagerten reinen Funktionen. Sie laufen in
|
||
rund 170 ms über Nodes eingebauten Test-Runner — ohne Browser, ohne Datenbank, ohne neue
|
||
Abhängigkeit. `npm test` führt sie aus, `npm run test:e2e` startet weiterhin Playwright.
|
||
Geprüft werden ISO-Kalenderwochen, Wochenstart-Berechnung, das Zeitraster, die
|
||
12/24-Stunden-Anzeige, der Round-Trip zwischen `formatDateToISO` und `parseCalendarDate`
|
||
sowie Farbinvertierung und Spaltenbegrenzung.
|
||
|
||
### Fixed
|
||
- `isAllDayEvent` rief `parseCalendarDate` auf, ohne es zu importieren — beim Auslagern aus
|
||
`WeeklyView.tsx` in v1.117.7 übersehen. Der Build meldete nichts, weil
|
||
`typescript.ignoreBuildErrors` noch aktiv ist; zur Laufzeit hätte der erste ganztägige
|
||
Termin mit Uhrzeit einen `ReferenceError` ausgelöst. Die Funktion liegt jetzt in
|
||
`date-utils.ts`, wo sie thematisch hingehört.
|
||
- `resolveViewDays` nutzte `clampViewDays` ohne Import — derselbe Fehler beim selben Schnitt.
|
||
`clampViewDays` und `MAX_VIEW_DAYS` sind nach `device-settings.ts` gewandert; `DaysSelector`
|
||
re-exportiert sie, bestehende Importe bleiben gültig.
|
||
|
||
Beide Fehler sind genau die Sorte, die `typescript.ignoreBuildErrors` verdeckt — und beide
|
||
wären von den neuen Tests sofort gefunden worden.
|
||
|
||
## [1.117.7] - 2026-09-07
|
||
|
||
### Changed
|
||
- Geräteabhängige Einstellungen nach `src/lib/weekly/device-settings.ts` (Cookie-Helfer und
|
||
die vier Schlüssellisten, die bewusst nur im Cookie und nicht in der Datenbank landen).
|
||
- Darstellungs-Helfer nach `src/lib/weekly/format.ts` (`stripHtml`, `invertColor`,
|
||
`isAllDayEvent`, `getSomedaySlotCount`).
|
||
- `WeeklyView.tsx`: 11.655 → 11.556 Zeilen. Mit `npm run build` verifiziert (Exit 0).
|
||
|
||
Damit sind die reinen Funktionen ohne Komponenten-State heraus — insgesamt 11.906 → 11.556
|
||
Zeilen (rund 3 %). Der Rest ist die Komponente selbst und braucht echtes Props-Design statt
|
||
Verschieben.
|
||
|
||
## [1.117.6] - 2026-09-07
|
||
|
||
### Changed
|
||
- Datums-, Wochen- und Zeitraster-Helfer nach `src/lib/weekly/date-utils.ts` verschoben
|
||
(13 reine Funktionen ohne Komponenten-State: `getStartOfWeek`, `getDayName`,
|
||
`getTimeSlots`, `formatHour`, `getWeekNumber`, `formatCustomHeader` und weitere).
|
||
`WeeklyView.tsx`: 11.830 → 11.655 Zeilen. Mit `npm run build` verifiziert (Exit 0).
|
||
|
||
### Notes
|
||
- `getWeekNumber` existiert im Projekt **dreimal** — hier, in `WeekPrintModal.tsx` und in
|
||
`app/api/user/export-week-view/route.ts`. Die Duplikate zu ersetzen steht in TODO.md;
|
||
vorher müssen die drei Implementierungen auf Gleichheit geprüft werden.
|
||
|
||
## [1.117.5] - 2026-09-07
|
||
|
||
### Changed
|
||
- Die gemeinsamen Datentypen der Wochenansicht liegen jetzt in `src/lib/weekly/types.ts`
|
||
(`Task`, `CalendarEvent`, `SomedayList`, `ViewStyle`, `KanbanStage`, `CellDuration`,
|
||
`RecurringTaskException`). `SettingsSidebar` und `GridTaskBlock` importierten sie bisher
|
||
aus `WeeklyView.tsx` und zogen sich damit die gesamte 11.900-Zeilen-Komponente in ihren
|
||
Abhängigkeitsgraphen. `WeeklyView.tsx` re-exportiert die Typen weiterhin, bestehende
|
||
Importe funktionieren also unverändert.
|
||
- Erster Schritt der Aufteilung von `WeeklyView.tsx`: 11.906 → 11.830 Zeilen.
|
||
Mit `npm run build` verifiziert (Exit 0, 36 Seiten, `/tasks` unverändert bei 265 kB).
|
||
|
||
## [1.117.4] - 2026-09-07
|
||
|
||
### Fixed
|
||
- Der Build meldet wieder ESLint-Fehler: `eslint.ignoreDuringBuilds` steht auf `false`.
|
||
Es waren nur **zwei** echte Errors im gesamten Projekt (`let json` statt `const json` in
|
||
`SettingsSidebar.tsx`), beide behoben. `next lint` endet jetzt mit Exit 0.
|
||
Die 695 Warnungen — 470-mal `no-explicit-any`, 209 ungenutzte Variablen, 11 unvollständige
|
||
Hook-Abhängigkeiten — blockieren den Build nicht und werden fortlaufend abgebaut.
|
||
|
||
## [1.117.3] - 2026-09-06
|
||
|
||
### Security
|
||
- `.env.production.save` und `.env.local` aus dem Arbeitsverzeichnis gelöscht — mit `shred`
|
||
überschrieben, damit die Werte nicht aus freiem Speicher rekonstruierbar bleiben. Beide
|
||
waren identische Kopien derselben Zugangsdaten.
|
||
- Klartext-Werte in `deployment-notes.md` und `INFRASTRUCTURE_REQUIREMENTS.md` durch
|
||
Platzhalter ersetzt (je drei Vorkommen: DB-Passwort, `NEXTAUTH_SECRET`, SMTP-Passwort).
|
||
Die Dateien bleiben als Dokumentation lesbar.
|
||
- Damit liegt außerhalb der Git-Historie kein Zugangsdatum mehr im Projekt.
|
||
|
||
## [1.117.2] - 2026-09-06
|
||
|
||
### Security
|
||
- Weitere Dateien mit echten Zugangsdaten aus dem Git-Index genommen (lokal bleiben sie
|
||
erhalten): `deployment-notes.md` und `INFRASTRUCTURE_REQUIREMENTS.md` — letztere führt das
|
||
Datenbank-Passwort sogar unverschleiert als `**Password**: …` auf. Ebenfalls entfernt:
|
||
`.env.production.save`, der ursprüngliche Fund.
|
||
- Passwort aus `tests/debug/db-connection-test.spec.ts` entfernt; dort stand eine vollständige
|
||
Connection-URL samt echter IP.
|
||
- `.gitignore` greift jetzt auf `.env*` statt auf eine Liste fester Namen. Die alte Regel
|
||
listete `.env.production`, weshalb `.env.production.save` durchs Raster fiel.
|
||
|
||
### Changed
|
||
- `TODO.md`: Die Rotation des Datenbank-Passworts ist in einen neuen Block F verschoben —
|
||
bewusst zurückgestellt. Die Datenbank hört auf einer privaten LAN-Adresse, und bei
|
||
selbst gehostetem Supabase steckt dasselbe Passwort an mehreren Stellen, was einen
|
||
halben Wechsel gefährlich macht. Enthält jetzt einen Prüfbefehl, mit dem sich ohne
|
||
Klartext-Ausgabe feststellen lässt, ob eines der geleakten Passwörter noch aktiv ist.
|
||
|
||
## [1.117.1] - 2026-09-06
|
||
|
||
### Fixed
|
||
- `docs/secret-rotation.md`, Schritt 2: Die Anleitung ging von *Supabase Cloud* mit
|
||
Dashboard und "Reset database password"-Knopf aus. Tatsächlich läuft Supabase
|
||
**selbst gehostet per Docker auf einem eigenen LXC** (siehe `scripts/supabase-setup.sh`) —
|
||
es gibt kein Cloud-Dashboard. Korrigiert samt Ports (6543 Pooler, 5432 direkt,
|
||
8000 API, 8001 Studio) und dem tatsächlichen Weg über `POSTGRES_PASSWORD` plus
|
||
`ALTER USER`, inklusive Backup-Schritt.
|
||
|
||
## [1.117.0] - 2026-09-06
|
||
|
||
### Security
|
||
- Rate-Limiting für alle Auth-Endpunkte: `signup` (5/15 Min), `resend-verification`
|
||
(3/15 Min), `verify-email` (10/15 Min) und `reset-password` (5/15 Min), je Client-IP.
|
||
Bisher waren sie unbegrenzt aufrufbar — beliebig viele Mails über den eigenen
|
||
SMTP-Zugang, beliebig viele Rateversuche auf Verifizierungscodes.
|
||
- Passwort-Reset-Token wird nur noch als SHA-256-Hash gespeichert. Bisher lag er im
|
||
Klartext in der Datenbank: Lesezugriff auf die DB genügte, um fremde Konten zu übernehmen.
|
||
- Tote Mock-Login-Route `src/app/api/auth/login/route.ts` entfernt. Sie verglich gegen ein
|
||
hartcodiertes Fake-Passwort, erzeugte ein bedeutungsloses `mock-jwt-token-…` und war
|
||
über die Middleware-Ausnahme für `/api/auth/*` ohne Anmeldung erreichbar. Kein
|
||
Login-Bypass, aber sie protokollierte jede probierte E-Mail-Adresse.
|
||
- Passwort-Reset nutzt jetzt bcrypt-Kosten 12 statt 10 — gleicher Wert wie beim Signup.
|
||
|
||
### Fixed
|
||
- `npm test` lief ins Leere: Das Skript rief `jest` auf, obwohl es weder eine Jest-Config
|
||
noch einen einzigen Jest-Test gibt. Zeigt jetzt auf Playwright; `jest`, `ts-jest` und
|
||
`@types/jest` sind entfernt (letzteres kollidiert mit den Playwright-Globals).
|
||
- Falscher `sonar.projectKey` im GitHub-Workflow — er hätte in ein zweites, leeres
|
||
SonarQube-Projekt geschrieben statt in das bestehende.
|
||
|
||
### Changed
|
||
- Die zwölf Wegwerf-Skripte aus der Fehlersuche (`login-debug`, `css-debug-test`, …) liegen
|
||
jetzt unter `tests/debug/` und sind per `testIgnore` aus dem normalen Testlauf genommen.
|
||
Aktiv bleiben die drei echten Testdateien. Erreichbar über `npm run test:debug`.
|
||
|
||
### Added
|
||
- `TODO.md`: abhakbare Liste aus dem Repository-Audit.
|
||
- `docs/secret-rotation.md`: Anleitung zum Austausch der Zugangsdaten, ohne dass ein Wert
|
||
auf dem Bildschirm erscheint.
|
||
- `scripts/quality-check.sh`: bündelt `npm audit`, Typecheck, Lint und den SonarQube-Scan
|
||
für den nächtlichen Lauf. `npm audit` fehlte bisher komplett.
|
||
|
||
## [1.116.4] - 2026-09-06
|
||
|
||
### Fixed
|
||
- Improved calendar event text contrast while retaining the calendar-colored background.
|
||
|
||
## [1.116.3] - 2026-09-05
|
||
|
||
### Added
|
||
- Calendar events can now be reassigned to another editable calendar from the event editor.
|
||
- Moving an event updates the source and target calendar caches.
|
||
|
||
### Changed
|
||
- Calendar event backgrounds now use the calendar color with a lighter, readable contrast.
|
||
- Google event-specific colors no longer override the selected calendar's color.
|
||
|
||
## [1.116.2] - 2026-09-05
|
||
|
||
### Fixed
|
||
- The remove action now applies to saved local location suggestions only.
|
||
OpenStreetMap search results remain available, while removed saved locations
|
||
are deleted from the user's stored location list.
|
||
|
||
## [1.116.1] - 2026-09-05
|
||
|
||
### Fixed
|
||
- Display preferences such as the number of visible days now remain device-specific.
|
||
Automatic profile saving no longer sends browser-only settings back to the shared account.
|
||
- An explicitly selected day count now survives mobile reloads and orientation changes.
|
||
- Cancelled or superseded location autocomplete requests no longer update the event form.
|
||
- Photon autocomplete requests now have a five-second timeout and input length limit.
|
||
|
||
## [1.116.0] - 2026-09-04
|
||
|
||
### Added
|
||
- Added a two-day reminder preset for calendar events.
|
||
- Added optional address and business autocomplete using Photon/OpenStreetMap,
|
||
with debounced server-side search and OpenStreetMap attribution.
|
||
- Users can keep their original location text or explicitly select a suggested result.
|
||
|
||
## [1.115.3] - 2026-09-03
|
||
|
||
### Fixed
|
||
- Drag & drop in views wider than 7 days: the drop target was keyed by weekday
|
||
(`date.getDay()`), which is only unique inside a 7-day window. In a 21-day view every
|
||
column sharing the weekday of the hovered one lit up as a drop target — three mirrored
|
||
highlights, one per Tuesday. Now keyed by the local date.
|
||
- Same bug, worse half: `handleDrop` resolved the target date with
|
||
`visibleDays.find(d => d.getDay() === dayOfWeek)`, so dropping a task on the second or
|
||
third Tuesday of a wide view silently scheduled it on the *first* Tuesday. The drop now
|
||
uses the column's own date. Tasks dragged in a >7-day view before this fix may sit on the
|
||
wrong date.
|
||
- The inline "click an empty slot to add a task" input had the same weekday key, so clicking
|
||
one slot opened an input in every column with that weekday.
|
||
|
||
## [1.115.2] - 2026-09-03
|
||
|
||
### Changed
|
||
- `scripts/update-app.sh` is now the single update path, used both for a manual deploy and
|
||
for the boot run; `my-weekly-todo-update.service` points at it. There were two
|
||
near-identical scripts and they drifted — the `npm ci` fix from 1.114.1 only ever landed
|
||
in update-app.sh, so the boot path kept running `npm install`. `scripts/boot-update.sh`
|
||
is now a shim that calls it, kept only so a not-yet-recopied unit file still works.
|
||
- The update log moved from `/var/log/app-boot-update.log` to
|
||
`/var/log/my-weekly-todo-update.log`.
|
||
|
||
### Added
|
||
- `update-app.sh` skips the rebuild when `HEAD` already matches `origin/main` and
|
||
`.next/standalone/server.js` exists, so a reboot no longer costs a full rebuild. Pass
|
||
`--force` to rebuild anyway.
|
||
- `update-app.sh` takes a `flock` single-instance lock, so a boot run and a manual run can
|
||
no longer install into the same tree at once — which is how node_modules ended up in a
|
||
state npm could not clean up.
|
||
- `update-app.sh` runs `prisma migrate deploy` and `prisma generate` (previously only the
|
||
boot script did), and copies `.env` into the standalone output.
|
||
|
||
### Fixed
|
||
- `update-app.sh` no longer fails at boot trying to restart a PM2 process that does not
|
||
exist yet. It restarts only when the PM2 daemon is actually running, and otherwise leaves
|
||
the start to `pm2-root.service`, which is ordered after this unit. It deliberately does
|
||
not probe with `pm2 describe`, since any pm2 command spawns a daemon that would race the
|
||
real service.
|
||
|
||
## [1.115.1] - 2026-09-03
|
||
|
||
### Fixed
|
||
- `scripts/boot-update.sh` (the "update on reboot" unit): every command was piped into `tee`,
|
||
so `set -e` only ever saw tee's exit status. A failed `npm install` or `npm run build` did
|
||
not stop the script and did not fail the systemd unit — it logged "Boot update finished"
|
||
and left a half-built tree. Now uses `set -euo pipefail`, logs through a single `exec`
|
||
redirect, and traps ERR.
|
||
- `scripts/boot-update.sh`: switched from `npm install` to `npm ci`. Same fix update-app.sh
|
||
got in 1.114.1 — `npm install` rewrites package-lock.json, leaving the tree dirty so the
|
||
*next* `git pull` aborts.
|
||
- `scripts/boot-update.sh`: body wrapped in a function so bash parses it in one go. The
|
||
script lives inside the checkout it pulls, so `git pull` rewriting it mid-run made bash
|
||
resume at a stale file offset.
|
||
- `scripts/boot-update.sh`: set `GIT_TERMINAL_PROMPT=0` so a git credential prompt fails
|
||
fast instead of hanging the unit at boot, where there is no tty.
|
||
- `scripts/my-weekly-todo-update.service`: raised `TimeoutStartSec` from 300 to 1800.
|
||
`npm ci` + `prisma generate` + `next build` takes far longer than 5 minutes in the
|
||
container; systemd SIGTERMed the oneshot unit mid-install, which left a `node_modules`
|
||
tree npm could no longer remove (`ENOTEMPTY: rmdir node_modules/date-fns/isEqual`).
|
||
- `package-lock.json`: version resynced with `package.json` (missed in the 1.115.0 commit).
|
||
|
||
## [1.115.0] - 2026-09-03
|
||
|
||
### Added
|
||
- Day columns: a "Custom" option next to the 1/2/3/5/7 presets, where any number of days from
|
||
1 to 31 can be typed (e.g. 14, 21, 28). The chosen value shows up as its own chip beside the
|
||
presets and is available in every place the day count can be set — sidebar, both settings
|
||
flyouts, the desktop toolbar and the header overflow menu.
|
||
|
||
### Fixed
|
||
- Day grid and all-day grid only had CSS for `cols-1/2/3/5/7`, so any other day count silently
|
||
fell back to a 5-column layout. Both grids now derive `grid-template-columns` from the actual
|
||
day count.
|
||
|
||
## [1.114.2] - 2026-09-01
|
||
|
||
### Fixed
|
||
- Deploy script: copy `public/` and `.next/static/` into `.next/standalone/` after the build.
|
||
`next.config.js` sets `output: 'standalone'`, and Next.js does not copy those itself. Without
|
||
the copy every deploy left the old chunk hashes in the standalone tree, so the browser's
|
||
requests for the new chunks 404'd, React never hydrated, and the app hung on "Loading
|
||
session..." — while the API kept returning 200, which made it look like an auth problem.
|
||
|
||
## [1.114.1] - 2026-09-01
|
||
|
||
### Added
|
||
- `scripts/update-app.sh`: the production deploy script, now version-controlled instead of
|
||
living only on the server. Hardened with `set -euo pipefail` (a failed `git pull` used to
|
||
still trigger a rebuild and PM2 restart on unchanged code) and switched from `npm install`
|
||
to `npm ci` (npm install rewrote package-lock.json, leaving the tree dirty so the next
|
||
pull aborted).
|
||
|
||
## [1.114.0] - 2026-09-01
|
||
|
||
### Added
|
||
- Focus Mode: "SKIP" button to move on to the next task of the day without completing the
|
||
current one. Skipped tasks are remembered until Focus Mode is closed, and the timer
|
||
resets for each new task. The button is disabled when no other task is left for today.
|
||
|
||
### Changed
|
||
- Styling settings: removed the standalone "Element Colors" and "Weekend Colors" sections —
|
||
those controls now live only in the CSS Variables panel (added there in 1.113.1).
|
||
- Styling settings: larger, borderless color swatches and bigger labels in the CSS Variables
|
||
panel.
|
||
|
||
## [1.113.1] - 2026-07-08
|
||
|
||
### Changed
|
||
- CSS Variables panel: added the existing "Today Highlight", "Past Days", "Saturday" and
|
||
"Sunday" color controls as their own groups (bound to the same profile fields as the
|
||
standalone Element/Weekend Colors sections, so both stay in sync).
|
||
- CSS Variables panel: every row now shows the color swatch before its label.
|
||
|
||
## [1.113.0] - 2026-07-08
|
||
|
||
### Added
|
||
- Styling settings: "CSS Variables" panel with a color picker and description for every
|
||
customizable color token in `globals.css` (base colors, settings sidebar, design token
|
||
aliases, event colors, all-day chips).
|
||
- Styling settings: "Custom CSS" textarea to inject a personal stylesheet that loads after
|
||
the app's default styles and can override any rule.
|