Commit Graph

573 Commits

Author SHA1 Message Date
schalli 474d17082b feat(quick-260917-gyd): Sitzungswaechter erkennt tote Sitzung, Header leitet ab
- auth-actions.ts: fetchSessionState() unterscheidet tote Sitzung (401/403/leere 200-Antwort, Cookie wird geloescht) von API-Ausfall (5xx/Netzwerkfehler/Nicht-JSON, unavailable ohne Redirect); fetchCurrentUser bleibt unveraendert
- header.tsx: Waechter im useEffect leitet bei toter Sitzung per Vollnavigation auf /login?next=… um, bleibt bei API-Ausfall still

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 12:28:38 +02:00
schalli 4b279eac70 feat(quick-260917-gyd): Ruecksprung nach Anmeldung auf urspruenglich angeforderte Seite
- safe-next.ts: buildNextParam()/sanitizeNextPath() als reine, getestete Funktionen (Open-Redirect-Schutz)
- middleware.ts: haengt next-Parameter an beide Login-Umleitungen (fehlendes Cookie, ungueltige Signatur)
- login/page.tsx: springt nach erfolgreicher Anmeldung auf den bereinigten next-Wert

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 12:26:14 +02:00
schalli 1601d97f62 feat(brand): gedrehte Kachel der Bildmarke übernimmt die Akzentfarbe
- LogoMark: gedrehte Signalkachel per Inline-Style fill: var(--primary, BRAND_YELLOW) statt festem fill-Attribut
- folgt damit der persoenlichen Akzentfarbe (applyAccentColor in auth-store.ts) in Kopfzeile, Seitenleiste und leerem Dashboard; ohne Nutzer (Anmeldeseite) gilt der CSS-Standard Markengelb
- brand.ts: Kommentar zu BRAND_YELLOW als Rueckfallwert ergaenzt
- tessera-logo.test.tsx: Kacheltest auf Inline-Style-Fuellung umgeschrieben (jsdom haelt var() in style.fill)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 12:15:33 +02:00
schalli 795c6a492c feat(settings): Akzentfarbe zusätzlich als Hex-Code eingebbar
- normalizeHexColor() (apps/web/src/lib/color.ts) normalisiert Hex-Eingaben (fuehrendes # optional, 3-stellige Kurzform, Gross-/Kleinschreibung, Leerraum)
- Kontoformular: Hex-Textfeld neben dem Farbwaehler, beide bidirektional synchron, ersetzt den reinen Anzeige-Span
- Ungueltiger Text: aria-invalid + roter Rand + Fehlertext, "Farbe speichern" gesperrt; Zuruecksetzen setzt beides auf #ffed00
- i18n: settings.account.accentColorHex/accentColorHexInvalid in de.json und en.json
- Unit-Tests: color.test.ts (9 Faelle), account-settings-form.test.tsx (7 Faelle)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 12:14:43 +02:00
schalli 9cb9d2e340 style(desktop): rustfmt-Umbruch des ExitRequested-Musters
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 10:49:19 +02:00
schalli 68a69c67fe fix(desktop): Tray „Beenden“ beendet die App (ExitRequested nur bei code None verhindern); „Öffnen“/Linksklick holen minimiertes Fenster per unminimize zurück
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 10:47:08 +02:00
schalli 16564f4d1c fix(desktop): App-Icon-Satz mit resvg (tauri icon) neu erzeugt – gedrehte gelbe Kachel wieder vorhanden, T statt 1
- Die fuenf Icon-Dateien in apps/desktop/src-tauri/icons/ durch einen mit
  der Tauri-CLI (tauri icon, Renderer resvg) erzeugten Satz ersetzt
- ImageMagick/MSVG hatte die gedrehte gelbe Kachel (transform rotate(12 51 21))
  aus apps/web/src/app/icon.svg nicht gerendert, dadurch zeigte das Symbol
  eine "1" statt des Tessera-T
- tauri.conf.json, Web-Code und Rust-Code unveraendert

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 10:14:35 +02:00
schalli b6d90136d1 fix(desktop): Hauptfenster startet mit setup.html statt fehlender index.html; Rust-Bau in der Pipeline auf 4 Prozesse begrenzt
Bedienprobe des Users: Installer und Start liefen, dann schwarzes Fenster
"asset not found: index.html". Das Fenster "main" hatte keine Startseite,
Tauri nimmt dann index.html; die Erststart-Seite heisst setup.html (Altlast
aus Phase 6, in keinem echten Paket sichtbar geworden). CARGO_BUILD_JOBS=4,
weil 8 parallele rustc-Prozesse den gemeinsam genutzten Runner-Host (15 GB)
an die Speichergrenze brachten; Betriebshandbuch Kap. 10 ergaenzt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 09:19:19 +02:00
schalli 72e488eea4 fix(desktop): Commit-Stempel aus TESSERA_COMMIT (Pipeline) statt nur git rev-parse; build.rs laeuft bei Aenderung neu
Mit dem Cargo-Zwischenspeicher wuerde ein einmal einkompilierter Stempel
sonst veralten und der Beta-Update-Hinweis dauerhaft erscheinen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 17:43:48 +02:00
schalli 579e24b81a fix(desktop): Beta-Update-Hinweis auch bei gleicher Version aber neuerem Commit anzeigen (WR-02)
desktop-collect.sh vergibt fuer den Beta-Kanal (main) jedem Commit dieselbe
X.Y.Z-Version aus dem letzten Freigabe-Tag (D-07) -- der bisherige Vergleich
nur ueber `version` liess Nutzer zwischen zwei Tags nie eine neuere
Beta-Version sehen, obwohl manifest.json ein neues `commit`-Feld traegt.
build.rs bettet jetzt per `git rev-parse --short=7 HEAD` denselben
Commit-Stempel, den desktop-collect.sh fuer manifest.json schreibt, als
APP_COMMIT zur Kompilierzeit ein; lib.rs vergleicht bei channel == "beta"
zusaetzlich den Commit. Fuer den Live-Kanal bleibt es beim reinen
Versionsvergleich, D-07 (X.Y.Z in tauri.conf.json/Cargo.toml) bleibt
unveraendert. cargo check + cargo clippy -- -D warnings sind sauber.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 17:40:52 +02:00
schalli 1b2f803c3e fix(desktop): Tauri-CSP auf tatsaechlichen Bedarf von setup.html verengen (WR-01)
Die CSP erlaubte 'unsafe-eval' und Wildcard-Quellen (connect-src/img-src/
font-src/style-src *), obwohl setup.html -- die einzige lokale Seite der App --
kein eval() nutzt, keine externen Schriften/Bilder laedt und ausschliesslich
ueber die Tauri-IPC-Bruecke (window.__TAURI__.core.invoke) mit dem Rust-Teil
spricht. Diese CSP gilt nur fuer vom App-eigenen Protokoll ausgelieferte
Seiten; nach save_server_url() navigiert das Hauptfenster auf die
Server-Adresse, und diese Navigation wird von der CSP der Zielseite selbst
bestimmt, nicht mehr von dieser Konfiguration. Verengt auf
default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self'
'unsafe-inline'; img-src 'self' data:; connect-src 'self'.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 17:39:51 +02:00
schalli 0d5c80fbbf fix(18): dot-only Manifest-Dateinamen ("."/"..") in getPackage() ablehnen (CR-01)
Die Zeichen-Whitelist /^[A-Za-z0-9._-]+$/ liess Namen wie ".." durch, weil
Punkt und Bindestrich erlaubte Zeichen sind -- path.join(desktopDistDir, '..')
loest aber in den Elternordner auf und unterlaeuft genau die Verteidigung in
der Tiefe (T-18-02), die diese Zeile laut Kommentar herstellen soll. Jetzt
werden "." und ".." explizit abgelehnt UND der aufgeloeste Pfad zusaetzlich
gegen desktopDistDir geprueft (haelt auch kuenftige Varianten ab, falls die
Whitelist anderswo wiederverwendet wird). Neue Testfaelle fuer manifest-Eintraege
namens "..", "." und "../manifest.json".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 17:39:29 +02:00
schalli a8964f1a23 fix(18): Manifest-Eintraege in getManifest() auf gueltige Form pruefen (WR-03)
Ein kaputter Plattform-Eintrag (z. B. fehlendes name-Feld oder ungueltiger
sha256) fiel bisher erst spaeter unbemerkt durch -- entry.name === undefined
wurde zu "undefined" gecoerct und als Dateiname gesucht. getManifest()
prueft jetzt jeden vorhandenen Plattform-Eintrag (name: string, size: number,
sha256: 64-stelliger Hex-String) und behandelt ein kaputtes Manifest wie ein
fehlendes (404 + Warn-Log), statt die kaputte Form durchzureichen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 17:38:50 +02:00
schalli 2d55f07729 feat(18-04): Task 2 — Erststart-Seite in Tessera-Gestalt, echtes App-Icon, lokaler AppImage-Beweis
- setup.html spricht nur noch über window.__TAURI__.core.invoke (kein
  Modul-Import mehr, der im gebauten Client ohnehin scheiterte), prüft die
  Adresse über check_server und speichert sie über save_server_url
- Sie-Form-Texte, Tessera-Zeichen als inline-SVG, Markengelb/-Olive,
  keine vorbelegte Server-Adresse
- CSP ohne Fremdhost (unpkg.com entfernt), da nichts mehr von außen geladen wird
- Icon-Satz aus apps/web/src/app/icon.svg neu erzeugt (512, 128, 128@2x, 32,
  ICO mit 16-256) statt der bisherigen flachen gelben Quadrate
- Versehentlich verschachteltes apps/desktop/src-tauri/apps/ entfernt
- Lokaler AppImage-Bau (Tessera-1.1.0.AppImage, ~103 MB) erfolgreich, von
  desktop-collect.sh eingesammelt

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 16:41:21 +02:00
schalli 8b130fddbd feat(18-04): Task 1 — Kommandos, Versionsprüfung und Tray für Update/Autostart
- tauri-plugin-opener@2 hinzugefügt (Legitimität in RESEARCH geprüft)
- Neue Rust-Kommandos check_server/save_server_url für die Erststart-Seite,
  Adressen laufen über api_url() ausschließlich über /api-proxy/*
- Versionsprüfung gegen /api-proxy/desktop/latest statt /health/version,
  schaltet den Tray-Eintrag "Update herunterladen" frei und benennt ihn um
- Tray-Menü: Öffnen · Update herunterladen · Autostart-Haken · Beenden,
  echte Umlaute, Update-Eintrag öffnet die Download-Seite im Systembrowser
- capabilities/default.json: opener:allow-open-url für http/https
- cargo check und cargo clippy grün

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 16:36:53 +02:00
schalli e96d460b8e feat(18-03): Task 2 — Einstellungsseite Desktop-App mit Knoepfen, Groesse und Erklaerung
- Neue Route /settings/general/desktop (DesktopSettingsPage)
- DesktopAppSettings: Version, zwei Primaerknoepfe mit Plattform-Symbol,
  Dateiname/Groesse je Knopf, Beta-Kanal-Hinweis, vier erklaerende Saetze,
  Hinweistext wenn keine Pakete hinterlegt sind
- Seitenleiste: Eintrag "Desktop-App" unter "Allgemein"
- de/en: settings.categoryDesktopApp, settings.desktop.*
- umlaut-dictionary.ts: "neuere" zur Allowlist ergaenzt (Rule 1 —
  Waechter-Test brach an einer bereits korrekten Umlautschreibung, die
  zufaellig die Buchstabenfolge "ue" enthaelt)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 16:30:34 +02:00
schalli 026d9c39af feat(18-03): Task 1 — Fetch-Helfer und Download-Link auf der Anmeldeseite
- apps/web/src/lib/desktop.ts: loadDesktopLatest (memoisiert, still bei
  Fehler), desktopDownloadUrl, formatFileSize (lokalisierte MB-Werte)
- DesktopDownloadLinks: unauffaelliger Link-Block, rendert nichts ohne
  Daten; Windows fuehrt, Linux als Kurzlink wenn beide Pakete vorliegen
- Anmeldeseite bindet den Block nach dem Formular ein (D-12)
- de/en: auth.desktopDownload.* ergaenzt

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 16:27:44 +02:00
schalli 614289a350 feat(18-01): Task 2 — Version aus dem Freigabe-Tag, Basislinie 1.1.0
desktop-version.sh (D-07): schreibt die reine X.Y.Z-Form des letzten
Freigabe-Tags in tauri.conf.json und Cargo.toml; --print nur zur
Anzeige, keine Vorab-/Metadatenform wird je geschrieben (Pitfall 2 —
NSIS-Ressourcen sind rein numerisch). Negativproben (v1.2.3-beta
abgelehnt, v2.0.0 akzeptiert aber ungeschrieben) bestehen.

Basislinie 1.1.0 (aktueller Tag v1.1.0) in tauri.conf.json, Cargo.toml,
Cargo.lock (cargo check) und package.json eingecheckt — die Wahrheit im
CI bleibt der Tag, dies ist nur die Vorgabe fuer lokale Baue.

Frischer AppImage-Bau mit der Basislinie bestaetigt: desktop-collect.sh
liefert jetzt Tessera-1.1.0.AppImage samt Manifest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 16:10:07 +02:00
schalli ae8fecb538 feat(18-01): Task 1 — Linux-Paket bis zum Download aus der API (Durchstich)
Die duenne Strecke Skript -> Abbild -> API beweisen (D-08, D-10):

- desktop-collect.sh sammelt AppImage/exe ein, schreibt manifest.json
  (Version, Kanal, Commit, Groesse, SHA-256) ohne Secrets
- apps/api/src/desktop/: neues Modul mit GET /desktop/latest und
  GET /desktop/download/:platform, beide @Public(); Plattform-Whitelist
  vor jedem Dateisystemzugriff, Dateiname ausschliesslich aus dem
  Manifest (T-18-01, T-18-02)
- packages/shared: DesktopPlatform/-Manifest(File)/-Latest(Response)
  Typen
- Dockerfile kopiert desktop-dist/ in die runner-Stufe; desktop-dist/
  per .gitkeep + .gitignore versioniert (leeres Verzeichnis, Pakete
  bleiben ungetrackt)
- HTTP-Durchstich-Spec (8 Tests) via NestFactory, kein fs-Mock; echtes
  Temp-Verzeichnis + unabhaengig berechneter SHA-256

Abweichung: DesktopController braucht @Inject(DesktopService) explizit
— Vitest transpiliert ueber esbuild, das emitDecoratorMetadata nicht
abbildet, sonst bleibt desktopService bei einem echten NestFactory-Bau
undefined (Rule 3, Blocker).

Lokal bewiesen: neu gebautes API-Abbild liefert /desktop/latest (200)
und /desktop/download/linux (200, attachment) aus, auch ueber
/api-proxy/ des Web-Containers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 16:05:55 +02:00
schalli 7429c5bd8d feat(web): Kalender-Widget zeigt je Kalender einen kleinen Kreis am Tag; Changelog
- Plaketten-Wrapper calendar-day-badges rendert buildDayBadges(day.events) statt der alten Einzel-Plakette
- Ein Kalender: unveraendert eine Plakette in Kalenderfarbe/Akzentfarbe (Tests 3/3b/3c bleiben gruen)
- Zwei/drei Kalender: je ein kleiner Kreis pro Kalender in Startreihenfolge (Test 3d)
- Vier oder mehr Kalender: zwei Kreise plus grauer Restkreis bg-muted-foreground/text-background mit Summe (Test 3e)
- CHANGELOG-Stichpunkt erweitert (mehrere Kalender am selben Tag)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 14:37:01 +02:00
schalli 4ddadc63ab feat(web): Kalender-Tag nach Kalender gruppieren, Kreise je Kalender berechnen (calendar-month)
- groupDayBySource gruppiert Tagestermine nach sourceId (nicht Farbe), Reihenfolge = erstes Auftreten, Farbe = Farbe des ersten Termins der Gruppe
- buildDayBadges baut daraus bis zu drei Plaketten-Kreise, ab dem vierten Kalender einen grauen Restkreis mit Summe
- Tests 8/9 in calendar-month.test.ts (TDD: RED bestaetigt vor Implementierung)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 14:34:49 +02:00
schalli c85cf9a47b fix(web): Aufzählungspunkte in Markdown-Ansichten (Was ist neu, Notiz) wieder sichtbar; Changelog
- Tailwind-Preflight setzt list-style: none in @layer base, markdown.css stellt es nicht wieder her
- Ungeschichteter CSS-Block in globals.css schlaegt die Layer-Regel fuer .wmde-markdown ul/ol
- Aufgabenlisten mit Kaestchen bleiben ohne Punkt
- CHANGELOG.md: zwei Stichpunkte unter Unveroeffentlicht ergaenzt

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 14:26:12 +02:00
schalli 1e4ec30190 feat(web): Kalender-Plakette in der Farbe des Kalenders, Farbpunkt je Tooltip-Zeile
- groupEventsByDate sortiert jede Tagesgruppe jetzt nach Start aufsteigend
- Zaehl-Plakette im Monatsraster traegt die Farbe des Kalenders des fruehesten Termins (weisse Schrift), ohne Farbe unveraendert bg-primary
- Tooltip-Zeilen bekommen denselben Farbpunkt wie die Liste "Naechste Termine" (Rueckfall var(--muted-foreground))
- 3 neue Tests: calendar-month Test 2b, calendar-widget Test 3b/3c

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 14:25:21 +02:00
schalli 4c2495bb64 fix(web): Notiz-Widget folgt dem Hell/Dunkel-Schalter von Tessera statt der Systemeinstellung
- useTheme (next-themes) + mounted-Guard wie changelog-view.tsx
- data-color-mode am Wurzel-Div folgt resolvedTheme statt fest 'auto'
- zwei neue Tests (dark/light) via vi.hoisted-Themenzustand

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:55:18 +02:00
schalli b16e4b8e67 fix(web): Kalender-Tooltip bricht lange Termintitel um (Breite/Klemmung aus einer Konstante)
- Titel-Span im Tooltip: min-w-0 break-words statt truncate
- Breite (288px) und Rand-Klemmung aus TOOLTIP_WIDTH_PX/TOOLTIP_EDGE_PX
- Test 4c: belegt Klassen und Tooltip-Breite

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:54:34 +02:00
schalli 39ea1474a5 feat: Link-Widget restlos entfernt (Web, API, Migration, Handbuch, Changelog)
- Web: Union-Mitglied, Constraints, Icon, Registry-Eintrag, wire-Funktion,
  Katalog, Seiten-Verdrahtung, Tests und i18n (de/en) fuer den Typ link
  entfernt; link-widget.tsx/.test.tsx geloescht
- API: create-widget.dto.ts (@IsIn-Liste) und widget-module-map.ts
  (Kommentare) auf sieben Typen angepasst; schema.prisma unveraendert
- Neue Migration 20260916120000_remove_link_widget: eine idempotente
  DELETE-Anweisung auf WidgetInstance, FavoriteLink kaskadiert ueber den
  bestehenden FK; wird in diesem Auftrag NICHT ausgefuehrt
- Neuer widget-wrapper-Test belegt den grauen Fallback fuer unbekannte
  Widget-Typen (Bestandsverhalten, jetzt festgeschrieben)
- Handbuch (Widget-Tabelle, Einstellungs-Hinweise) und CHANGELOG
  (Geaendert/Entfernt/Behoben) aktualisiert
- Web: 52 Dateien / 344 Tests gruen, tsc Exit 0; API: tsc Exit 0,
  dashboard-Spec 31 Tests gruen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:44:09 +02:00
schalli 7f1ee3b1f0 feat(web): Favoriten-Widget — optionaler Titel (Kopfzeile, FavoritesConfig, i18n)
- Favoriten-Widget: config.title (nur typeof string) steuert eine Kopfzeile
  im Notiz-Look; leer -> keine Kopfzeile; im Bearbeitungsmodus immer ein
  Titelfeld (widgetNoDrag, 1500 ms entprellt, { title })
- Einstellungen -> Dashboard -> Widgets: FavoritesConfig (Muster NoteConfig)
  mit uebersetzter Beschriftung, Instanz-Kopfzeile zeigt "— {title}" jetzt
  fuer note UND favorites
- NoteConfig: hart kodiertes "Title" durch uebersetzten Schluessel ersetzt
- 3 neue Uebersetzungsschluessel (note.titleLabel, favorites.titleLabel,
  favorites.titlePlaceholder) in de/en
- favorites-Tests 7+4, Panel-Tests 7+3, Umlaut-Waechter 3/3 gruen, tsc Exit 0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:39:48 +02:00
schalli 684f063a8e feat(web): Notiz-Widget — Aufgabenlisten in der Ansicht abhakbar
- Hilfsmodul note-task-list.tsx mit isTaskLine/toggleTaskLine (GFM-konforme
  Regex, Code-Zaeune werden uebersprungen) und NoteCheckbox (kein disabled)
- previewOptions des Notiz-Widgets reicht components.input = NoteCheckbox
  durch react-markdown nach rehypeSanitize durch (bleibt aktiv, T-IEX-01)
- Klick im Vorschau-Container kippt genau die N-te Aufgabenzeile und
  speichert sofort (verwirft einen laufenden Tipp-Entprell-Timer)
- Hinweis: note-task-list als .tsx statt .ts angelegt (Komponente braucht
  JSX) — Abweichung dokumentiert in SUMMARY.md
- 16 neue/erweiterte Tests gruen, tsc Exit 0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:36:17 +02:00
schalli 6d8c7c46a8 feat(web): CalendarConfig im Einstellungsfeld, Changelog- und Handbuch-Eintrag
- Kalender-Zweig im Einstellungsfeld ersetzt den englischen Fliesstext durch CalendarConfig (Monatsansicht ein/aus, Anzahl Termine 0..10, Zeitraum 7/14/30/60/90) nach Muster ClockConfig; Speichern per partiellem updateWidgetConfig
- Paneltest um drei Kalender-Tests erweitert (7/7 gruen), next-intl-Mock interpoliert jetzt {platzhalter}
- CHANGELOG.md: zweiter Eintrag unter Unveroeffentlicht/Geaendert; Anwenderhandbuch: Widget-Tabellenzeile und Dashboard-Widgets-Absatz ergaenzt
- Gesamtlauf: 51 Testdateien / 332 Tests gruen, Type-Check Exit 0 (quick-260916-htc)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:12:48 +02:00
schalli 61996dceef feat(web): Kalender-Widget neu gebaut mit Monatsraster + Naechste Termine, Mindestgroesse 6x8
- calendar-widget.tsx komplett neu: Monatsraster (Nav-Zeile, Wochentagskopf, 42 Zellen, heutiger Tag, Zaehl-Plakette, Portal-Tooltip) + Block "Naechste Termine"; fetchEvents bekommt immer zwei Tagesgrenzen-ISO-Strings ueber computeFetchWindow
- Komponententest komplett neu geschrieben (9 Tests: Laden/Quellen, Raster, Plakette, Tooltip inkl. 5+-Kuerzung, Naechste-Termine-Filter, showMonth/maxEvents=0, Ladefenster, Blaettern)
- WIDGET_CONSTRAINTS.calendar auf { minW: 6, minH: 8, defaultW: 8, defaultH: 12 } angehoben (quick-260916-htc), Registry-Test A mitgezogen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:10:33 +02:00
schalli 0858102439 feat(web): Uebersetzungsschluessel + Hilfsmodul calendar-month.ts fuer Kalender-Widget
- 16 neue Uebersetzungsschluessel unter widgets.calendar in de.json/en.json (Monatsnavigation, Naechste-Termine-Block, Einstellungsfelder)
- Neues Hilfsmodul calendar-month.ts: resolveCalendarConfig, buildCalendarDays, groupEventsByDate, computeFetchWindow, selectUpcomingEvents, Formatierungsfunktionen (quick-260916-htc)
- 8 Unit-Tests fuer das Hilfsmodul, Umlaut-Waechter weiterhin 3/3 gruen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 13:06:15 +02:00
schalli 2306a6dee1 feat(web): typabhaengiger URL-Platzhalter + EWS-Hinweis im Kalenderquellen-Formular
- Platzhalter je Typ (CalDAV/ICS/Exchange-Graph/Exchange-EWS), ohne Typ weiterhin https://
- Grauer Hinweis unter dem Feld nur bei Exchange + EWS, unterhalb eines eventuellen Fehlers
- Neuer Komponententest mit 6 Faellen (TDD: rot vor der Implementierung)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 12:47:09 +02:00
schalli 618fbd6845 feat(web): fuenf Uebersetzungsschluessel fuer URL-Platzhalter im Kalenderquellen-Formular
- widgets.calendar.formFieldUrlPlaceholderEws|Graph|Caldav|Ics + formFieldUrlHintEws in de.json und en.json
- Platzhalter identisch in beiden Sprachen, Hinweistext uebersetzt

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 12:46:16 +02:00
schalli c3d8e16eb3 fix(web): fehlende Uebersetzungen — Kalenderquellen-Formular (18 Schluessel), Kalender-Einstellungen (2), Marktplatz (1) in de/en nachgetragen; Changelog Behoben
Tessera CI/CD / Lint & Type Check (push) Successful in 51s
Tessera CI/CD / Tests (push) Successful in 1m41s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m16s
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 11:34:09 +02:00
schalli ba06db98c5 feat(quick-260916-dcz): CHANGELOG.md, Seite "Was ist neu" mit Kanalfilter, Versionszeile als Link, Bauzeit-Einbettung
- CHANGELOG.md (Wurzel): Unveroeffentlicht (bwo + dyv + diese Seite, 8 Punkte) und 1.0.0 – 2026-09-15 (11 Punkte aus den Handbuechern), Alltagssprache, echte Umlaute
- next.config.ts liest CHANGELOG.md zur Bauzeit nach env.TESSERA_CHANGELOG_MD; .dockerignore-Ausnahme !CHANGELOG.md und COPY-Zeile im Web-Dockerfile
- lib/changelog.ts: filterChangelogForChannel (live ohne Unveroeffentlicht, beta/dev mit "Noch nicht freigegeben (Beta)", leerer Abschnitt ausgeblendet), 10 Tests
- Server-Seite /changelog (MDEditor.Markdown + rehype-sanitize, Hinweis auf Beta, Leer-Text), 3 Tests; Versionszeile ist Link mit aria-label, 2 Tests
- de/en: sidebar.whatsNew und Namensraum changelog; Web-Suite 49/309, tsc 0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 11:20:22 +02:00
schalli 8792819e34 fix(quick-260916-dyv): Rechner-Mindesthoehe 10 statt 9 Zeilen — sechs Tastenreihen, unterste Reihe war bei 9 um 25 px abgeschnitten (Browser-Messung)
Tessera CI/CD / Lint & Type Check (push) Successful in 54s
Tessera CI/CD / Tests (push) Successful in 1m1s
Tessera CI/CD / Build & Publish Images (push) Successful in 2m58s
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 10:57:55 +02:00
schalli dbbd54f5a1 feat(quick-260916-dyv): Bearbeiten-Schalter unten rechts (Rand oben 28 px), ganze Kachel als Griff mit Kopfleiste, cancel-Selektor, kein Ueberlappen beim Ablegen
- page.tsx: feste Aktionsleiste `fixed bottom-6 right-6 z-20` mit "Widget
  hinzufuegen" (nur Bearbeitungsmodus) links neben dem Stift/Haekchen; Block
  oben rechts und mt-8-Wrapper entfernt, Grid direkt im Container p-2
  (12 + 8 + 8 = 28 px statt 60 px); page.test.tsx NEU mit 3 Tests
- widget-wrapper.tsx: Karte ist im Bearbeitungsmodus der Griff (cursor-grab),
  20-px-Overlay-Kopfleiste mit Griff-Symbol und Tooltip dragHint, Loesch-Knopf
  in der Kopfleiste mit data-no-drag; Rumpf h-full unveraendert (Hoehenkette)
- dashboard-grid.tsx: WIDGET_DRAG_HANDLE_SELECTOR, WIDGET_DRAG_CANCEL_SELECTOR
  (input, textarea, select, button, a, [contenteditable], [data-no-drag],
  .widgetNoDrag), threshold 3; FREE_PLACEMENT_COMPACTOR = noCompactor +
  preventCollision: true (freie Platzierung bleibt, Commit c8f3361)
- dashboard-grid.test.tsx: Mock per importOriginal (echter noCompactor),
  Tests 6-8 (dragConfig-Pin, Compactor-Pin, cancel/handle-Semantik im DOM)
- edit-mode-toggle.tsx: schwebend (shadow-lg, inaktiv border + bg-card)
- de.json/en.json: widgets.dragHint

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 10:41:49 +02:00
schalli dc992c9bc6 fix(quick-260916-dyv): Mindestgroessen inhaltsgetrieben (kleinste bedienbare Kachel je Typ), gespeicherte Minima ueberschrieben, Stoppuhr-Bedienleiste kompakt
- WIDGET_CONSTRAINTS: Minima aus dem Innenaufbau gerechnet (clock 2/2, search 6/2,
  calendar 3/3, note 4/4, calculator 3/9, favorites 3/3, link 3/2, stopwatch 4/3),
  Vorgaben unveraendert; Test A pinnt alle 32 Werte per toEqual
- dashboard-grid.tsx: effectiveLayouts (useMemo) ueberschreibt minW/minH jedes
  gespeicherten Eintrags in jedem Breakpoint aus den Konstanten und hebt zu
  kleine w/h auf das Minimum an — RGL 2.2.3 nimmt gespeicherte Eintraege
  woertlich (synchronizeLayoutWithChildren) und liest data-grid nur ohne Eintrag
- dashboard-grid.test.tsx: Test 5 angepasst, Test 9 (Ueberschreibung je
  Breakpoint, unbekannter Typ unveraendert) und Test 9b (h 8 -> 9, w >= minW bleibt)
- stopwatch-widget.tsx: Bedienleiste px-2 py-1 text-xs, Zeile gap-1 py-1, damit
  Stop + Runde + Reset in 4 Spalten passen und die Kachel auf 4x3 schrumpfen kann

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 10:37:55 +02:00
schalli a175c00c18 feat(quick-260916-bwo): Abstaende halbiert — Seitenrahmen p-3 (alle Seiten), Dashboard p-2, Grid 8 px, Widget-Innenabstaende
- app-shell.tsx: main p-3 (12 px statt 24) — gilt fuer alle Seiten, Nachtrag des Users
- page.tsx: Container relative p-2, Umschalter right-2 top-2; mt-8 bleibt (Umschalter 36 px hoch, sonst Ueberlappung)
- link/favorites p-1, calendar p-1.5 (Zustandsansichten p-2), search px-1.5, note-Kopfzeile px-1.5
- Grid-Abstand 8 px bereits in Task 1, Uhr/Stoppuhr/Rechner-Innenabstaende bereits in 2a

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 09:13:23 +02:00
schalli 2d8efe1a97 feat(quick-260916-bwo): Widget-Inhalt skaliert per Container-Queries (Uhr, Stoppuhr, Rechner), Punktgroesse der Uhrzeit einstellbar
- widget-wrapper.tsx: Rumpf ist Groessen-Container (@container-size) fuer cqw/cqh
- clock-font-size.ts (neu): Grenzen 8..200, resolveClockTimeFontSizePt nur fuer endliche Zahlen (T-BWO-01)
- clock-widget.tsx: Container-Query-Klassen mit clamp(), data-font-mode auto/fixed, Inline-pt nur bei Zahl, keine vw-Formel mehr, p-1
- stopwatch-widget.tsx: Anzeige per Container-Query, p-1.5; calculator-widget.tsx: Anzeige und Tasten skalieren, Tasten h-full min-h-7, p-1
- widget-settings-panel.tsx: Zahlenfeld Schriftgroesse der Uhrzeit (Blur/Enter, leer = automatisch, Fehlermeldung ausserhalb 8..200)
- de.json/en.json: widgets.clock.fontSizeLabel/fontSizeAuto/fontSizeHint/fontSizeInvalid (Sie-Form, Waechter gruen)
- Tests: Uhr +4, Stoppuhr +1, Rechner +1, widget-settings-panel.test.tsx neu (4)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 09:12:02 +02:00
schalli 3f5afb0f54 feat(quick-260916-bwo): Dashboard-Raster verdoppelt (24 Spalten, 20 px, 8 px Abstand), Konstanten x2, einmalige Umrechnung gespeicherter Anordnungen mit Marker __gridVersion
- dashboard-grid.tsx: COLS 24/20/12/8/2, rowHeight 20, margin 8 (containerPadding folgt), Rueckfallwerte 4
- widget-registry.tsx: alle 32 Werte in WIDGET_CONSTRAINTS verdoppelt
- grid-layout-migration.ts (neu): migrateGridLayouts/withGridVersion, Marker nur im JSON, Idempotenz (T-BWO-02)
- dashboard-store.ts: Umrechnung beim Laden, Sofort-Speichern mit Marker, withGridVersion bei jedem saveLayout
- Tests: Migration 7 (neu), Store 6 (neu), Registry +1 (Tabelle), Grid +2 (Props ueber Mock), API-Spec +2 (Durchreichung __gridVersion, timeFontSizePt)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 09:09:00 +02:00
schalli b41be21190 feat(quick-260914-m97): Feld Fehlermeldungen an im SMTP-Formular — settings-api, Formular, i18n settings.smtp
- SmtpConfig.bugReportRecipient (string | null) und SaveSmtpPayload.bugReportRecipient (null loescht, fehlend bewahrt)
- Eingabefeld type=email hinter der Absenderadresse mit Hinweistext; Payload traegt immer trim() || null
- Zwei Komponententests: Vorbelegung aus GET, PUT-Payload mit Wert bzw. null
- i18n settings.smtp.bugReportRecipient / bugReportRecipientHelp in de und en

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 16:51:36 +02:00
schalli 60b0ee8409 feat(quick-260914-m97): Fehler-melden-Knopf in der Kopfzeile — Bildschirmfoto vor dem Dialog (html-to-image 1.11.13), Fehlerpuffer, Dialog mit Vorschau, i18n bugReport
- Knopf (Kaefer-Symbol) unmittelbar vor dem Erscheinungsbild-Schalter; captureScreenshot() laeuft VOR dem Oeffnen, Test pinnt es im toPng-Mock
- computeCaptureSize (laengste Kante 1600 px, reine Funktion, getestet), dataUrlToBlob, sendBugReport als Multipart mit credentials und ohne Content-Type-Header
- error-buffer: Ringpuffer 20, window error/unhandledrejection, console.error (Original bleibt), fetch-Wrapper nur bei !ok ohne Anfrage-Rumpf/Suchteil/Kopfzeilen (T-M97-02), idempotent, SSR-sicher; installiert in app-shell
- Dialog mit Vorschau, Haekchen, Beschreibung, Meldungen je Status 409/413/429/502/allgemein, Admin-Link auf /admin/smtp
- i18n bugReport de/en, Allowlist um "passiert"; html-to-image exakt 1.11.13, Lockfile aktualisiert

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 16:50:01 +02:00
schalli 54121c1721 feat(quick-260914-m97): Fehlermeldungen per E-Mail — Empfaenger in SmtpConfig (Migration), MailService-Anhaenge, Modul bug-reports mit Drossel, PNG-Pruefung und Mandant aus der Sitzung
- SmtpConfig.bugReportRecipient (nullable, additive Migration 20260914170000), DTO @IsOptional @IsEmail, SAFE_SELECT, getBugReportRecipient gebunden
- MailService: Versandkern deliver (wirft, Anhaenge), sendViaTenantTransport bleibt verschluckender Mantel (T-02-12), sendBugReport laesst Fehler durch
- POST /bug-reports: Multipart 4 MiB je Route, alle angemeldeten Rollen, Drossel 5/10 min -> 429, PNG-Signatur -> 400, kein Empfaenger -> 409, Versandfehler -> 502, eine Protokollzeile
- Falsifizierungen (a)-(d) als Specs; @Expose() im DTO, damit errors auch bei fehlendem Feld zu [] wird
- Doku-Zeile fuer rls-access-inventory, TESSERA_BUGREPORT_TO in docker-compose.prod.yml

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 16:45:09 +02:00
schalli 9731501718 ci(quick-260914-ku1): zwei Kanaele — main -> beta+latest, Tag v* -> live+vX.Y.Z, Versionsstempel als Build-Args in beide Dockerfiles, IMAGE_TAG in docker-compose.prod.yml
- Dockerfiles: globale ARG APP_VERSION/APP_CHANNEL/APP_COMMIT/APP_BUILD_TIME (Vorgabe dev),
  web-builder setzt NEXT_PUBLIC_APP_* vor pnpm build (Bauzeit-Einbettung), runner-Stufen
  setzen ENV APP_* fuer die Laufzeit
- .gitea/scripts/publish-images.sh: Kanal und Etiketten allein aus GITHUB_REF, --print-plan
  ohne Docker, Zweig live ohne Tag = nichts zu tun (T-KU1-07)
- ci.yml: Trigger main, live und Tags v*; publish mit fetch-depth: 0 und Skriptaufruf
- docker-compose.prod.yml: image ...:${IMAGE_TAG:-beta} fuer web und api
- Falsifizierung lokal: mit Build-Args v9.9.9-test in API-Env, dist-Zeile und Web-Bundle
  (1 Datei), ohne Build-Args dev/dev und 0 Treffer im Bundle

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 15:42:01 +02:00
schalli cdb571c509 feat(quick-260914-ku1): Versionsstempel — GET /health/version aus APP_*, VersionResponse, app-version.ts und Abzeichen v<Version> · <Kanal> in der Seitenleiste
- packages/shared: VersionResponse { name, version, channel, commit, buildTime }
- apps/api: health/app-version.ts (getAppVersion mit ||-Vorgaben dev/dev, formatAppVersionLine),
  HealthController.getVersion delegiert (bleibt @Public, T-KU1-03), main.ts protokolliert
  "Tessera API <version> (<channel>) <commit>" beim Start; neuer Spec mit 6 Tests
- apps/web: lib/app-version.ts (NEXT_PUBLIC_APP_* mit vollem Literalnamen, loadApiVersion
  memoisiert und still bei Fehler), AppVersionBadge unten in der Seitenleiste (auch mobil,
  nicht eingeklappt), sidebar.channel.{beta,live,dev} in de/en; 5 + 4 + 1 neue Tests
- Suiten: API 65 Dateien / 1060 Tests, Web 40 / 243, tsc in shared/api/web Exit 0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 15:32:50 +02:00
schalli 6e2a641d76 feat(quick-260914-eym): Mail-Transport je Versand nach Mandant (WINDOWS #30), ldap/digest/matching ueber Systemkontext, vier Tabellen im Werkzeug, Erlaubnisliste vollstaendig
- mail: MailerModule-Fabrik und DB-Startpfad (findFirst beim Boot) ersatzlos
  entfernt; MailService baut je Versand einen nodemailer-Transport aus
  getDecryptedSmtpConfig(tenantId) des Empfaenger-Mandanten, Umgebungs-Kette
  (MAIL_* -> TESSERA_SMTP_* -> localhost:1025) nur als Rueckfall; Fehler
  weiter verschluckt (T-02-12), close() im finally; neue mail.service.spec.ts
  (4 Tests, T-GWH-03 geschlossen)
- settings: Startpfad-Methode samt vier Spec-Tests geloescht;
  auth: requestPasswordReset reicht user.tenantId durch (Spec-Zusicherung)
- ldap: getAllActiveConfigs und Nachverschluesselung lesen ueber forSystem
  (zwei Zuweisungen), Schreibzeile je Altzeile ueber forTenant(config.tenantId);
  Tests 301/306 umgedreht, neuer Altzeilen-Test
- tender-digest: Kandidatenabfrage ueber forSystem, Schleife gebunden (+1 Test)
- tender-matching: Profilabfrage ueber forSystem, Katalog (D-03) ungebunden (+1 Test)
- tender-notifications.integration.spec: Mock um forSystem
- Werkzeug: LdapConfig (15 Spalten), LdapFieldMapping (6), TenderMatch (8),
  TenderSavedSearch (8) je neun Kennungen plus Relations-Kennung
  ldapconfig-systemkontext-include-fieldmappings-beider-mandanten
  -> Alle 253 Pruefungen bestanden
- Detektor: FORSYSTEM_ALLOWED_CALL_SITES auf 4 Dateien / 5 Aufrufe;
  Proben-Empfaenger sysPrisma (Gate-Zaehlung, Name nicht hartkodiert)
- Klassifikation: 6 Zeilen system-gebunden, settings/smtpConfig gebunden
- Falsifizierung durch Rueckbau ausgefuehrt und zurueckgenommen:
  (a) FOR SELECT bei TenderMatch entfernt -> 5 von 253 rot (Insert gelingt,
  cmd ALL); (b) Regel TenderSavedSearch aus der Datei entfernt -> 1 von 245
  rot (Extraktion), lebende DB bleibt bei 34; (c) local=false -> gruen, plus
  Reset entfernt -> 5 rot (Erben sichtbar); (d) Zahl 0 -> 2 rot, Fremddatei
  admin-seed -> 3 rot
- Baseline: 64 Dateien / 1054 Tests, tsc 0, Werkzeug 253

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 11:46:08 +02:00
schalli 3d645674f0 feat(quick-260914-eym): forSystem(), is_system_context(), Systemleseregel auf fuenf Tabellen, DKV-Planer je Mandant — ein Pfad (WINDOWS #21)
- Helfer forSystem(prisma) in prisma-tenant.extension.ts (Array-Form,
  setzt app.system_context='true' und die beiden anderen Variablen
  ausdruecklich leer); forTenant()/withTenantTransaction() setzen
  app.system_context='' als Literal (4 neue Spec-Tests)
- Migration 20260914120000_rls_system_context_read: is_system_context()
  (COALESCE, STABLE) und system_read_policy FOR SELECT auf DkvModuleConfig,
  LdapConfig, LdapFieldMapping, TenderMatch, TenderSavedSearch — lokal
  angewendet (36 Migrationen, pg_proc 1, 5 system_read_policy, 34 Regeln)
- migration-sql.spec.ts: describe-Block fuer die neue Migration (6 Tests)
- rls-scratch-check.mjs: Funktion aus der Migration geschnitten,
  forSystemQuery/buildInlineSystemClient, Reset in forTenantQuery/
  buildInlineExtendedClient, runSystemContextChecks (4 Funktionsfaelle +
  9 Kennungen DkvModuleConfig) -> Alle 216 Pruefungen bestanden
- rls-access-inventory.spec.ts: fuenfte Erkennungsform const X = forSystem(,
  Stand system-gebunden mit Vorrangregel, FORSYSTEM_ALLOWED_CALL_SITES
  (exakte Zahl je Datei, 3 Tests), Proben C/D/E
- DKV: loadActiveConfigsForScheduler() ueber forSystem (findMany isActive,
  CONFIG_SAFE_SELECT, orderBy tenantId); DkvSchedulerService mit Auftrag je
  Mandant dkv-inbox-poll:<tenantId>, activeTenantId ersatzlos entfernt,
  setInterval/stopJob je Mandant, registeredTenantIds(); Controller
  stopJob(tenantId); neue dkv-scheduler.service.spec.ts (7 Tests),
  dkv.service.spec.ts Tests 6/7 umgestellt
- Klassifikation: dkv.service.ts/dkvModuleConfig system-gebunden, Header
  mit fuenfter Erkennungsform und viertem Stand-Wert
- Baseline: 63 Dateien / 1051 Tests, tsc 0, Werkzeug 216

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 11:32:54 +02:00
schalli 63f9df0afb docs(quick-260914-ebg): Kopfkommentar adminResetPassword — Schwesterwege PATCH/DELETE /users/:id geschlossen (WINDOWS #29)
- auth.service.ts: letzter Satz des Kopfkommentars ueber adminResetPassword nennt T-FH9-05 nicht mehr als offen, sondern verweist auf den seit 260914-ebg (WINDOWS #29) identischen Riegel in UserController.update()/remove()
- Falsifizierung: Rueckbau des Task-1-Commits (git apply -R) macht Test 9 und Test 13 rot (Tests  2 failed | 14 passed (16)), danach byte-identisch wiederhergestellt (git checkout --, git status --porcelain leer)
- Rule 1 Nebenfund: acht neue Tests in user.controller.spec.ts trugen sechs ueberfluessige `as any`-Umschreibungen (UpdateUserDto ist vollstaendig optional, siehe planning_measurements), die die Biome-Warnungen dieser Datei von 25 auf 31 trieben — entfernt, damit die relative Biome-Schwelle der Baseline (25) wieder eingehalten wird, ohne die Schwelle anzuheben

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 10:37:40 +02:00
schalli 759ea3b2ca fix(quick-260914-ebg): Zielrollen-Riegel in UserController.update/remove — ADMIN kann SUPER_ADMIN nicht mehr aendern oder loeschen (WINDOWS #29)
- update(): Riegel nach der Mandantengrenze, vor der dto.role-Pruefung — Nicht-SUPER_ADMIN darf SUPER_ADMIN-Ziel nicht mehr aendern (Kennwort, isActive, Rolle, Anmeldename, E-Mail)
- remove(): derselbe Riegel nach der Mandantengrenze, vor userService.delete
- acht neue Tests (Test 9-16): drei Angriffsformen, SUPER_ADMIN-gegen-SUPER_ADMIN-Regression, ADMIN-gegen-USER-Regression, Reihenfolge-Ordnungstests je Handler
- RED-Lauf vor dem Riegel: Tests  2 failed | 14 passed (16) (Test 9, Test 13 rot); GREEN danach: Tests  16 passed (16)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 10:35:23 +02:00