Files
tessera-ctl/.planning/quick/260914-m97-fehler-melden-knopf-bildschirmfoto-der-a/260914-m97-SUMMARY.md
T
schalli e5098603b4
Tessera CI/CD / Build & Publish Images (push) Successful in 2m42s
Tessera CI/CD / Lint & Type Check (push) Successful in 46s
Tessera CI/CD / Tests (push) Successful in 53s
docs(quick-260914-m97): Fehler-melden-Knopf abgeschlossen, verifiziert 9/9 und im Browser bewiesen — Zusammenfassung, Verifikation, Aktenstand, Ledger #38 fixed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 17:18:28 +02:00

34 KiB
Raw Blame History

phase, plan, subsystem, tags, requires, provides, affects, actuals, plan_head_before, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
phase plan subsystem tags requires provides affects actuals plan_head_before tech-stack key-files key-decisions patterns-established requirements-completed coverage duration completed status
quick-260914-m97 01 api, ui, mail
bug-report
html-to-image
multipart
multer
nodemailer
prisma
smtp
next-intl
vitest
phase provides
quick-260914-ku1 Versionsstempel appVersion (Web) und formatAppVersionLine (API), CI-Skript mit Build-Args, Kanaele beta/live
phase provides
quick-260914-eym MailService mit Transport je Versand nach Mandant (resolveTransport)
POST /bug-reports (Multipart, 4 MiB je Route, alle angemeldeten Rollen, Drossel 5/10 min, PNG-Signatur, 409/413/429/502) mit E-Mail und PNG-Anhang ueber den Transport des Sitzungs-Mandanten
SmtpConfig.bugReportRecipient (additive Migration 20260914170000), Feld Fehlermeldungen an unter Administrator -> SMTP, Rueckfall TESSERA_BUGREPORT_TO in docker-compose.prod.yml
Fehler-melden-Knopf in der Kopfzeile: Bild VOR dem Dialog (html-to-image 1.11.13), Dialog mit Vorschau, Fehlerpuffer im Browser (error-buffer.ts)
Handbuecher Anwender/Administration/Betrieb mit Ablauf, Datenschutz-Hinweis, Feld und Rueckfall
erstfreigabe-v1.0.0
smtp
mail
handbuecher
tokens tasks commits
206676 3 4
17a7e5ef9b
added patterns
html-to-image 1.11.13 (apps/web
exakt gepinnt
0 Abhaengigkeiten)
Multipart je Route mit FileInterceptor-Limit statt globalem JSON-Limit (main.ts unangetastet)
MailService: Versandkern deliver wirft, Mantel sendViaTenantTransport verschluckt (T-02-12 nur fuer Kennwort-Reset/Willkommen)
Multipart-Wiederholfelder im DTO per @Expose() + @Transform normalisieren (String/undefined/Array -> Array)
Browser-Fehlerpuffer: fetch-Wrapper notiert nur !ok, nie Anfrage-Rumpf/Suchteil/Kopfzeilen (T-M97-02)
Komponententest liest Texte aus der echten de.json (next-intl-Mock mit Punktpfad-Lookup)
created modified
apps/api/prisma/migrations/20260914170000_smtp_config_bug_report_recipient/migration.sql
apps/api/src/bug-reports/bug-reports.module.ts
apps/api/src/bug-reports/bug-reports.controller.ts
apps/api/src/bug-reports/bug-reports.service.ts
apps/api/src/bug-reports/dto/bug-report.dto.ts
apps/api/src/bug-reports/bug-reports.service.spec.ts
apps/api/src/bug-reports/bug-reports.controller.spec.ts
apps/web/src/lib/error-buffer.ts
apps/web/src/lib/error-buffer.test.ts
apps/web/src/lib/bug-report-api.ts
apps/web/src/components/bug-report/bug-report-button.tsx
apps/web/src/components/bug-report/bug-report-dialog.tsx
apps/web/src/components/bug-report/bug-report-button.test.tsx
apps/web/src/components/settings/smtp-settings-form.test.tsx
apps/api/prisma/schema.prisma
apps/api/src/settings/settings.service.ts
apps/api/src/settings/settings.service.spec.ts
apps/api/src/settings/dto/smtp-config.dto.ts
apps/api/src/mail/mail.service.ts
apps/api/src/mail/mail.service.spec.ts
apps/api/src/app.module.ts
docs/mandantentrennung-zugriffsklassifikation.md
docker-compose.prod.yml
apps/web/package.json
pnpm-lock.yaml
apps/web/src/components/layout/header.tsx
apps/web/src/components/layout/app-shell.tsx
apps/web/src/lib/settings-api.ts
apps/web/src/components/settings/smtp-settings-form.tsx
apps/web/src/messages/de.json
apps/web/src/messages/en.json
apps/web/src/messages/umlaut-dictionary.ts
docs/anleitung-anwender.md
docs/anleitung-administration.md
docs/anleitung-betrieb.md
Multipart statt JSON+Base64: Limit nur auf POST /bug-reports (FileInterceptor 4 MiB), main.ts unangetastet, kein globales Body-Limit fuer /auth/login
Keine GET /bug-reports/status-Route: 409 beim Senden reicht, kein Aufruf je Seitenladung
Empfaenger lebt in SmtpConfig (Feld Fehlermeldungen an), Rueckfall TESSERA_BUGREPORT_TO nur ueber die Prod-Compose; Leerstring zaehlt als ungesetzt
sendBugReport laesst Transportfehler durch (502), sendPasswordResetEmail verschluckt weiter (T-02-12) — Regressionstest im selben Spec
Commits auf main (branching_strategy none, quick_branch_template null, wie 260914-ku1); CI-Lauf ueber Rerun-API wiederholt, weil die Ursache ein Gitea-Ausfall war, kein Code
Multipart-Wiederholfeld errors: @Expose() + @Transform im DTO, Pipe-Test mit metatype
Bild-vor-Dialog per toPng-Mock gepinnt (queryByRole('dialog') ist null waehrend der Aufnahme)
QUICK-260914-M97
id description requirement verification human_judgment
D1 POST /bug-reports: Drossel, PNG-Signatur, Empfaenger-Aufloesung, Mandant aus der Sitzung, E-Mail mit PNG-Anhang, 502 bei Versandfehler QUICK-260914-M97
kind ref status
unit apps/api/src/bug-reports/bug-reports.service.spec.ts#Test 1-8 (Falsifizierungen a-d) pass
kind ref status
unit apps/api/src/bug-reports/bug-reports.controller.spec.ts#Test 1-3 (whitelist, Grenzen, kein @Roles) pass
kind ref status
unit apps/api/src/mail/mail.service.spec.ts#Test 5-6 (Anhaenge, Fehler durch vs. verschluckt) pass
false
id description requirement verification human_judgment
D2 SmtpConfig.bugReportRecipient mit Migration, DTO, SAFE_SELECT, getBugReportRecipient; Feld Fehlermeldungen an im SMTP-Formular QUICK-260914-M97
kind ref status
unit apps/api/src/settings/settings.service.spec.ts#Test A-C pass
kind ref status
unit apps/web/src/components/settings/smtp-settings-form.test.tsx#Test 1-2 pass
kind ref status
other prisma migrate deploy / migrate status / migrate diff gegen tessera-ctl-db-1 pass
false
id description requirement verification human_judgment rationale
D3 Fehler-melden-Knopf: Bild VOR dem Dialog, Dialog mit Vorschau/Haekchen/Beschreibung, Multipart-Versand, Meldungen je Status, Fehlerpuffer ohne Kennwoerter QUICK-260914-M97
kind ref status
unit apps/web/src/components/bug-report/bug-report-button.test.tsx#Test 1-11 pass
kind ref status
unit apps/web/src/lib/error-buffer.test.ts#Test 1-4 pass
true html-to-image rastert nur im echten Browser (jsdom: HTMLVideoElement is not defined, kein Canvas); dass das Bild den Dialog NICHT zeigt und OKLCH-Farben stimmen, und dass die E-Mail mit PNG-Anhang in mailhog ankommt, muss der Browser-Check zeigen (siehe Fuer den Verifizierer)
id description requirement verification human_judgment rationale
D4 Handbuecher Anwender/Administration/Betrieb QUICK-260914-M97
kind ref status
other grep-Gates Task 3 (1 / 1 / 3 / 2 / 1) pass
true Alltagssprache und Verstaendlichkeit fuer Nicht-Programmierer kann nur ein Mensch beurteilen
27 min (14:37Z bis 15:04Z, davon ca. 3,5 min Suiten-Laeufe und 2 x 4 min CI-Beobachtung) 2026-09-14 complete

Quick 260914-m97 Plan 01: Fehler-melden-Knopf — Bildschirmfoto der aktuellen Seite per E-Mail mit PNG-Anhang — Summary

Ein Klick auf den Kaefer-Knopf rechts in der Kopfzeile nimmt zuerst ein Bild der Seite auf (html-to-image 1.11.13, laengste Kante 1600 px) und oeffnet erst danach den Dialog mit Vorschau, Haekchen und Feld „Was ist passiert?“; „Senden“ schickt Bild, Beschreibung, Seite, Web-/API-Version mit Kanal und Commit, Browser, Fenstergroesse, Zeitpunkt, angemeldeten Benutzer und die letzten 20 Browser-Fehler als Multipart an POST /bug-reports, das daraus eine E-Mail mit PNG-Anhang ueber den Transport des Sitzungs-Mandanten an SmtpConfig.bugReportRecipient (neues Feld „Fehlermeldungen an“ unter Administrator -> SMTP) oder den Rueckfall TESSERA_BUGREPORT_TO schickt. Drossel 5 je Benutzer je 10 Minuten (429), PNG-Signatur (400), kein Postfach (409), Versandfehler (502) — der Anwender erfaehrt immer, ob sein Bericht ankam. Vier Commits auf main, gepusht, CI-Lauf 299 nach einem Gitea-Datenbank-Ausfall im ersten Versuch per Rerun success; :beta-Abbilder tragen 77117de beta.

Ausgangslage und Bezugspunkt

Alle Gates gegen 5c42c55 (Code unangetastet seit Planung; HEAD bei Start 17a7e5e, Arbeitsbaum sauber, main == origin/main). Vorbedingung Task 1: docker ps | grep -c ^tessera-ctl-db-1$ -> 1, prisma migrate status -> 36 migrations found / Database schema is up to date! (IP 172.19.0.2). Vorbedingung Task 3: curl localhost:3002/api/v1/version -> {"version":"1.26.2"}, gitea-runner -> 1.

Baseline vor jeder Aenderung (erneut gemessen, identisch mit der Planung):

Suite Test Files Tests
API (pnpm -C apps/api exec vitest run) 65 passed (65) 1060 passed (1060)
Web (pnpm -C apps/web exec vitest run) 40 passed (40) 243 passed (243)

Konfiguration: branching_strategy: none, quick_branch_template: null, auto_advance: false, human_verify_mode: end-of-phase, commit_docs: true. Alle Commits liegen deshalb — wie die Plan-Commits dieses Auftrags und 260914-ku1 heute — auf main; git.allow_default_branch_commits ist nicht gesetzt, die Projektkonfiguration und der Plan (Push auf main, CI-Beobachtung des main-Laufs) verlangen es aber ausdruecklich.

Task 1 — API (Commit 54121c1, 16 Dateien)

Schritt A, RED (pnpm -C apps/api exec vitest run src/bug-reports src/mail/mail.service.spec.ts src/settings/settings.service.spec.ts):

 FAIL  src/bug-reports/bug-reports.controller.spec.ts — Error: Cannot find module './bug-reports.controller'
 FAIL  src/bug-reports/bug-reports.service.spec.ts — Error: Cannot find module './bug-reports.service'
 FAIL  mail.service.spec.ts > Test 5 / Test 6 — TypeError: service.sendBugReport is not a function
 FAIL  settings.service.spec.ts > Test A — TypeError: service.getBugReportRecipient is not a function
 FAIL  settings.service.spec.ts > Test B / Test C — AssertionError: expected undefined to be 'fehler@a.example.invalid'
 Test Files  4 failed (4)
      Tests  5 failed | 20 passed (25)

Schritt B, Schema und Migration, [BLOCKING] migrate deploy (DATABASE_URL nur in der Shell-Zeile, keine .env angefasst):

The following migration(s) have been applied:
migrations/
  └─ 20260914170000_smtp_config_bug_report_recipient/
    └─ migration.sql
All migrations have been successfully applied.
--- migrate status:   37 migrations found in prisma/migrations / Database schema is up to date!
--- migrate diff:     -- This is an empty migration.
--- prisma generate:  (ohne Fehler; nur der Accelerate-Tipp)

Migrationsordner-Zaehlung ls apps/api/prisma/migrations | grep -c "" -> 38 (36 + migration_lock.toml + 1 neu).

Schritte C-E: SmtpConfigDto.bugReportRecipient (@IsOptional() @IsEmail(), string | null), SMTP_SAFE_SELECT um das Feld, saveSmtpConfig mit bedingtem Spreading (fehlend = bewahren, null/leer = loeschen), neue Methode getBugReportRecipient (ein gebundener Klient, findUnique mit schmalem select). MailService: exportierte Typen OutgoingAttachment/OutgoingMail/BugReportMail, Versandkern deliver (wirft, close() im finally, Anhaenge/HTML nur wenn gesetzt), sendViaTenantTransport als verschluckender Mantel, sendBugReport ruft deliver direkt. Modul bug-reports mit DTO (Grenzen 4000/2000/100/20/64/1000/50/50, errors 30 x 1000), Dienst (Drossel-Map, PNG-Signatur, Empfaenger-Kette, gebundene Benutzerzeile const tenantPrisma = forTenant(, Betreff/Text/Anhang, 502-Uebersetzung, eine Protokollzeile), Controller (@Controller('bug-reports'), @Post(), FileInterceptor('screenshot', { limits: { fileSize: 4 * 1024 * 1024, files: 1 } }), kein @Roles), Modul in app.module.ts hinter TendersModule. Doku-Zeilen in docs/mandantentrennung-zugriffsklassifikation.md (Bestandsaufnahme alphabetisch hinter auth/, Bereichs-Tabelle bug-reports | 0 | 1 | 0). docker-compose.prod.yml: TESSERA_BUGREPORT_TO: ${TESSERA_BUGREPORT_TO:-} hinter TESSERA_SMTP_FROM mit englischem Kommentar.

Schritt F, GREEN (Zielspecs): Test Files 5 passed (5) / Tests 66 passed (66) (rls-inventory 30, mail 6, bug-reports.service 8, settings 19, bug-reports.controller 3 = 50 bisherige + 16 neue). Volle Suite: Test Files 67 passed (67) / Tests 1076 passed (1076). tsc --noEmit api: TSC_api=0.

Automatisierter Verify-Block Task 1 (Ausgabe in Planreihenfolge): 5 passed (5) / 66 passed (66); Database schema is up to date!; -- This is an empty migration.; 38; 1 (Schema); 1 (Migration); 1 (FileInterceptor); 1 (4-MiB-Limit); 0 (kein @Roles); 0 (kein tenantId im DTO); 1 (Zuweisungsform); 2 (sendBugReport in mail.service.ts); 2 (Import + Eintrag); 1 (Doku-Zeile); 0 (Compose-Grep — siehe Befund unten); M_EXIT=0; M_EMPTY=0; TSC_api=0.

Befund Compose-Grep (Messinstrument, nicht Datei): das Muster des Plans grep -c 'TESSERA_BUGREPORT_TO: ${TESSERA_BUGREPORT_TO:-}' docker-compose.prod.yml liefert 0 — dasselbe Muster liefert fuer die BESTEHENDE Zeile TESSERA_SMTP_HOST: ${TESSERA_SMTP_HOST:-} ebenfalls 0. Mit festem Text grep -c -F 'TESSERA_BUGREPORT_TO: ${TESSERA_BUGREPORT_TO:-}' -> 1, mit \$ -> 1; sed -n 52p | cat -A zeigt exakt TESSERA_BUGREPORT_TO: ${TESSERA_BUGREPORT_TO:-}$. Die Datei traegt die vorgeschriebene Zeile; das Regex-Muster ($ vor {) trifft sie nicht. Gate nicht angepasst, beide Zahlen hier festgehalten.

Task 2 — Web (Commit 2a 60b0ee8, 13 Dateien; Commit 2b b41be21, 5 Dateien)

Schritt A, Abhaengigkeit: "html-to-image": "1.11.13" alphabetisch unter dependencies; pnpm install -> Done in 5.5s (Lockfile +8 Zeilen: html-to-image@1.11.13 als Importer-Eintrag, Paket und Snapshot; die Warnung nunjucks 3.2.4 unmet peer chokidar ist Bestand). pnpm install --frozen-lockfile -> FROZEN=0. apps/web/node_modules/html-to-image/lib/index.d.ts vorhanden. git diff --stat 5c42c55 -- apps/api/package.json packages/shared/package.json package.json leer (U_EMPTY=0). Folge fuer die CI: die deps-Stufe beider Dockerfiles laeuft wegen des Lockfiles neu (gemessen: Lauf 299 baute 3 min 37 s bzw. 3 min 58 s statt 5 min 18 s bei 297 — der Bau war sogar schneller, weil kein Base-Image nachzuladen war).

Schritt B, RED Teil 2a (pnpm -C apps/web exec vitest run src/lib/error-buffer.test.ts src/components/bug-report):

 FAIL  src/lib/error-buffer.test.ts — Error: Failed to resolve import "./error-buffer"
 FAIL  src/components/bug-report/bug-report-button.test.tsx — Error: Failed to resolve import "@/lib/error-buffer"
 Test Files  2 failed (2)
      Tests  no tests

Schritte C-F: error-buffer.ts (Ringpuffer 20, MAX_MESSAGE 1000, BODY_EXCERPT 200, Guard __tesseraErrorBufferInstalled, error/unhandledrejection/console.error/fetch-Wrapper, uninstallErrorBuffer nur fuer Tests), installErrorBuffer() in app-shell.tsx als eigener useEffect; bug-report-api.ts (computeCaptureSize, captureScreenshot mit dynamischem Import und filter auf data-bug-report-ignore, dataUrlToBlob, sendBugReport ohne headers); i18n bugReport (20 Schluessel) in de.json/en.json je an Position 10 hinter theme; Dialog (role="dialog", aria-modal, aria-labelledby, Escape ausser waehrend sending, Fokus auf das Textfeld, Zustaende ready | sending | sent | failed, Meldung je Status, Admin-Link next/link auf /admin/smtp), Knopf (Kaefer-Symbol nach lucide bug, captureScreenshot() VOR setOpen(true)), <BugReportButton /> unmittelbar vor <ThemeToggle /> in header.tsx.

Umlaut-Waechter: nach dem Eintrag des Namensraums flaggte umlaut-guard.spec.ts GENAU EIN Wort: bugReport.descriptionLabel: "passiert" is a new word not on UMLAUT_ALLOWLIST -> 'passiert' mit Kommentar // 260914-m97 in UMLAUT_ALLOWLIST eingetragen; geschehen, aktuellen wurden nicht verlangt. Danach src/messages: Test Files 2 passed (2) / Tests 6 passed (6).

Schritt F2, Gate 2a: pnpm -C apps/web exec vitest run src/lib/error-buffer.test.ts src/components/bug-report src/messages -> Test Files 4 passed (4) / Tests 21 passed (21) (error-buffer 4, bug-report-button 11, Waechter 6); TSC_web=0. Commit 2a mit genau 13 Dateien (git show --stat HEAD -> 13 files changed, 968 insertions(+)).

Schritt G, RED Teil 2b (pnpm -C apps/web exec vitest run src/components/settings/smtp-settings-form.test.tsx):

 × Test 1: das Feld ist aus GET /settings/smtp vorbelegt
 × Test 2: PUT-Payload traegt den Wert; leeres Feld -> null
 Error: It looks like undefined was passed instead of a matcher. Did you do something like getByText(undefined)?
 Test Files  1 failed (1)
      Tests  2 failed (2)

(rot, weil de.settings.smtp.bugReportRecipient noch nicht existierte — das Label war undefined.)

Schritt G/H, GREEN Teil 2b: settings.smtp.bugReportRecipient / bugReportRecipientHelp hinter testFailed in beiden Dateien (jetzt 20 Schluessel unter settings.smtp), settings-api.ts (bugReportRecipient: string | null in SmtpConfig, optional in SaveSmtpPayload), Formular (FormState.bugReportRecipient, Vorbelegung config.bugReportRecipient ?? '', payload.bugReportRecipient = form.bugReportRecipient.trim() || null, Eingabefeld id="smtp-bug-report-recipient" type="email" zwischen Absenderadresse und „Test-E-Mail an“). Zielspecs Test Files 3 passed (3) / Tests 8 passed (8); volle Suite Test Files 43 passed (43) / Tests 260 passed (260); TSC_web=0.

Automatisierter Verify-Block Task 2: FROZEN=0; h2i=1.11.13; Test Files 5 passed (5) / Tests 23 passed (23) (6 Waechter + 17 neue); 1; ORDER=ok; 1; 1; 1; 2 (smtp-bug-report-recipient, htmlFor + id); Fehler melden | Was ist passiert? | Fehlermeldungen an | Report a problem | true; U_EMPTY=0; TSC_web=0. Commit 2b mit genau 5 Dateien (5 files changed, 115 insertions(+), 2 deletions(-)).

Task 3 — Handbuecher, Abschluss-Gates, Push, CI (Commit 77117de, 3 Dateien)

docs/anleitung-anwender.md: Inhaltsverzeichnis 8. „Einen Fehler melden“, 9. „Haeufige Stolpersteine“; Kopfleiste „drei Bedienelemente“ mit dem Knopf als erstem Punkt; neuer Abschnitt (fuenf Absaetze: Ablauf, Beschreibung, Haekchen mit fettem Datenschutz-Satz, was mitgeschickt wird und dass nichts in Tessera gespeichert wird, Rueckmeldungen); neuer Stolperstein „kein Postfach“. docs/anleitung-administration.md: Kapitel 6 nennt das Feld in der Feldaufzaehlung und in einem eigenen Absatz (Wirkung, Betreff, Anhang, immer sichtbar, Drossel, 4 MB, dieselben Zugangsdaten, Rueckfall, Datenschutz); zwei neue Zeilen in der Fehlersuche-Tabelle. docs/anleitung-betrieb.md: TESSERA_BUGREPORT_TO in der Konfigurationstabelle nach der TESSERA_SMTP_*-Zeile (Serverdatei von Hand, wie IMAGE_TAG), neue Zeile in der Symptomtabelle Kapitel 7.

Grep-Gates: ^## Einen Fehler melden -> 1; drei Bedienelemente -> 1; Fehlermeldungen an in administration -> 3 (erste Messung 2, weil grep -c Zeilen zaehlt und Absatz plus Tabellenzeile zwei Zeilen sind — die Feldaufzaehlung im ersten Absatz von Kapitel 6 hat das Feld dann sachlich richtig als dritte Nennung bekommen; grep -o | wc -l -> 3); TESSERA_BUGREPORT_TO in betrieb -> 2; in administration -> 1.

Abschluss-Gates (alle nach dem letzten Code-Commit gemessen):

Gate Ergebnis
API volle Suite Test Files 67 passed (67) / Tests 1076 passed (1076)
Web volle Suite Test Files 43 passed (43) / Tests 260 passed (260)
tsc --noEmit TSC_packages/shared=0, TSC_apps/api=0, TSC_apps/web=0
pnpm install --frozen-lockfile FROZEN=0
git diff --stat 5c42c55 -- . ':!.planning' GIT_EXIT=0, 35 files changed, 2026 insertions(+), 17 deletions(-)
Unangetastet-Stichprobe (main.ts, biome.json, .env*, apps/api/package.json, packages/shared, package.json, Migration 20260914120000) U_EXIT=0, U_EMPTY=0
migrate status (nach allem) Database schema is up to date!
git status -sb nach git fetch ## main...origin/main (kein [ahead)

Push: git push um 14:53:40Z -> 5c42c55..77117de main -> main (die zwei Plan-Commits des Orchestrators gingen mit).

CI-Lauf nach dem Push

Feld Versuch 1 Versuch 2
Lauf-ID 299 (event push, ref main, head_sha 77117de) 299 (Rerun per POST .../actions/runs/299/rerun, HTTP 201, 14:59:08Z)
status / conclusion completed / failure completed / success
started_at / completed_at 16:53:44 / 16:57:21 (+02:00) — 3 min 37 s 16:59:10 / 17:03:08 (+02:00) — 3 min 58 s
Jobs Lint & Type Check success, Tests success, Build & Publish failure im Schritt „Versionsstempel berechnen, Abbilder bauen und veroeffentlichen“ alle drei success
Ursache Versuch 1 Beide Abbilder wurden fertig gebaut (Web-Bundle inkl. /login-Route, naming to localhost:3002/schalli/tessera-ctl/web:beta done); der anschliessende docker push scheiterte sofort mit error from registry: unauthorized, obwohl Login Succeeded vorher stand. Gitea-Serverlog 16:57:09-16:57:19: dial tcp: lookup db on 127.0.0.11:53: no such host — Gitea konnte seine eigene Datenbank (gitea-db, laut docker ps in dieser Minute neu gestartet, nicht durch mich) zehn Sekunden lang nicht erreichen, jede authentifizierte Anfrage antwortete 401 (auch mein Poll 11 um 14:57:17Z: GET .../actions/runs 401 -> not-found, und die Registry-HEAD /v2/.../blobs -> 401). Kein Zusammenhang mit den 35 Dateien; kein fix-Commit moeglich oder noetig. —
api:beta node APP_VERSION APP_CHANNEL APP_COMMIT — 77117de beta 77117de (git rev-parse --short HEAD = 77117de)
docker image inspect Created web:beta / api:beta — 2026-09-14T17:01:00+02:00 / 2026-09-14T17:02:07+02:00 (nach dem Rerun-Start)
web:beta html-to-image, Probe des Plans (ls /app/apps/web/node_modules/html-to-image/package.json || ls /app/node_modules/.pnpm | grep -c html-to-image) — 0 — Messinstrument-Befund: das Standalone-Abbild traegt nur die vom Server benoetigten Pakete (/app/node_modules/.pnpm hat 25 Eintraege, kein html*); html-to-image wird ausschliesslich im Browser dynamisch importiert und liegt deshalb im Client-Bundle
web:beta html-to-image, korrigierte Probe — Chunk /app/apps/web/.next/static/chunks/3717.5ecd3f65b9b8111a.js (12.547 Bytes) enthaelt cacheBust und skipFonts (die Optionen des Aufrufs, in der minifizierten Bibliothek erhalten); 4 Chunks mit foreignObject, 1 Chunk mit bug-report-ignore -> Bibliothek und Knopf sind im Abbild

Falsifizierungen (a)-(d) — rot/gruen

Spec RED (vor Produktionscode) GREEN
(a) bug-reports.service.spec.ts Test 3: fuenf Berichte durch, der sechste -> HttpException mit getStatus() === 429, sendBugReport genau fuenfmal; u2 gleichzeitig frei; vi.advanceTimersByTime(600001) -> u1 wieder { sent: true } Cannot find module './bug-reports.service' ✓ 8 tests in bug-reports.service.spec.ts
(b) Test 4: Buffer.from('nicht png, aber lang genug') -> BadRequestException; die ersten 7 PNG-Bytes -> BadRequestException; sendBugReport nie gerufen wie oben ✓
(c) Test 5: Settings null + Variable undefined -> ConflictException mit Fehlermeldungen an in der Meldung; Variable '' -> ebenfalls ConflictException; nie versendet wie oben ✓
(d) Test 8: DTO mit tenantId: 'fremd', userId: 'u-fremd' -> getBugReportRecipient('t1'), jeder forTenant-Aufruf mit 't1', sendBugReport mit 't1'; Text enthaelt Anna Muster (anna), nicht Fremde Anna/Eindringling/fremd@x.invalid. Controller-Spec Test 1: ValidationPipe({ whitelist: true, transform: true }) entfernt tenantId, errors: 'einzeln' -> ['einzeln'], fehlend -> [], Array bleibt Service: wie oben; Controller: Cannot find module './bug-reports.controller'; nach dem ersten GREEN-Lauf war Test 1 noch rot (BadRequestException bei ganz fehlendem errors) — siehe Abweichung 1 ✓ 3 tests
Reihenfolge Bild-vor-Dialog: bug-report-button.test.tsx Test 1 (toPng-Mock prueft queryByRole('dialog') ist null, canvasWidth: 1600, canvasHeight: 500 bei 3200x1000) Failed to resolve import "@/lib/error-buffer" ✓ 11 tests
Kennwort-Reset bleibt verschluckend: mail.service.spec.ts Test 6 (sendBugReport -> rejects.toThrow('ECONNREFUSED'), sendPasswordResetEmail -> resolves.toBeUndefined(), close() zweimal) service.sendBugReport is not a function ✓ 6 tests
Fehlerpuffer ohne Geheimnisse: error-buffer.test.ts Test 2 (POST /api/x -> 500, enthaelt kaputt, NICHT geheim, NICHT password, NICHT token=; res.json() weiter lesbar; 200 nicht notiert) Failed to resolve import "./error-buffer" ✓ 4 tests

Task Commits

  1. Task 1: API — 54121c1 (feat) — 16 Dateien
  2. Task 2a: Web Knopf/Dialog/Puffer — 60b0ee8 (feat) — 13 Dateien
  3. Task 2b: SMTP-Formular — b41be21 (feat) — 5 Dateien
  4. Task 3: Handbuecher — 77117de (docs) — 3 Dateien

commits: 4 gemessen aus git rev-list --count 17a7e5e..HEAD (Ledger plan_head_before = 17a7e5ef9b77b9e6bd4cf5cb337e691b215bbabb). actuals.tokens = 826.706 Zeichen ueber die 35 geaenderten Dateien / 4 = 206.676 (Methode wie 260914-ku1; der reine Diff waere 121.245 Zeichen = 30.311). Der Plan schaetzte 150.000 bei confidence: low.

git log --oneline 17a7e5e..HEAD (vor dem SUMMARY):

77117de docs(quick-260914-m97): Handbuecher — Einen Fehler melden (Anwender), Feld Fehlermeldungen an (Administration), TESSERA_BUGREPORT_TO als Rueckfall (Betrieb)
b41be21 feat(quick-260914-m97): Feld Fehlermeldungen an im SMTP-Formular — settings-api, Formular, i18n settings.smtp
60b0ee8 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
54121c1 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

git status --porcelain (vor dem SUMMARY): nur M .planning/WINDOWS.md (Ledger-Eintrag der Abweichung 1, siehe unten) — kein Code ungeschrieben, kein Code uncommittet.

Entscheidungen

  • Multipart statt JSON+Base64 (Planungsmessung uebernommen und im Bau bestaetigt): FileInterceptor('screenshot', { limits: { fileSize: 4 MiB, files: 1 } }) begrenzt nur diese Route; main.ts unangetastet (Gate M_EMPTY=0). Ein globales app.useBodyParser('json', { limit }) haette jede JSON-Route inkl. /auth/login geoeffnet.
  • Keine GET /bug-reports/status-Route: der Knopf ist immer sichtbar, 409 beim Senden traegt die Information (mit Admin-Link); keine zusaetzliche Anfrage je Seitenladung.
  • @Expose() im DTO (Abweichung 1): ohne @Expose() ruft class-transformer @Transform fuer einen im Rumpf GANZ fehlenden Schluessel nicht auf; die Normalisierung undefined -> [] haette nur auf dem Papier gestanden.
  • Rerun statt fix-Commit fuer den CI-Lauf: die Ursache lag in Gitea (Datenbank kurz nicht erreichbar), nicht im Code; ein Leer-Commit haette nur die Historie verschmutzt.
  • Commits auf main: siehe Ausgangslage — Projektkonfiguration branching_strategy: none, Plan verlangt Push auf main und CI-Beobachtung; identisch mit 260914-ku1 heute.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] @Expose() auf errors, damit die @Transform-Normalisierung auch bei ganz fehlendem Feld greift

  • Found during: Task 1, Schritt F (erster GREEN-Lauf, Controller-Spec Test 1 rot: BadRequestException bei pipe.transform({ ...baseBody }, meta) ohne errors)
  • Issue: Das Rezept des Plans (@Transform gefolgt von @IsArray()) deckt den Fall „Feld fehlt im Multipart-Rumpf“ nicht: class-transformer laeuft @Transform nur fuer Schluessel, die im Quellobjekt vorhanden sind; errors blieb undefined, @IsArray() schlug fehl — ein Anwender ohne Browserfehler haette 400 bekommen.
  • Fix: @Expose() (aus class-transformer, bereits installiert) vor @Transform; Kommentar im DTO nennt die Messung.
  • Files modified: apps/api/src/bug-reports/dto/bug-report.dto.ts
  • Verification: bug-reports.controller.spec.ts Test 1 gruen (String -> ['einzeln'], fehlend -> [], Array bleibt), Test 2 (Grenzen) unveraendert gruen
  • Committed in: 54121c1 (Teil des Task-1-Commits)

2. [Rule 2 - Missing content] Dritte Nennung von „Fehlermeldungen an“ in der Feldaufzaehlung von Kapitel 6

  • Found during: Task 3, Grep-Gate (grep -c "Fehlermeldungen an" docs/anleitung-administration.md -> 2, Plan: mindestens 3)
  • Issue: grep -c zaehlt Zeilen; Absatz und Tabellenzeile sind zwei Zeilen. Inhaltlich fehlte das neue Feld in der Aufzaehlung der SMTP-Felder im ersten Absatz von Kapitel 6.
  • Fix: Aufzaehlung ergaenzt („… die Absenderadresse und optional das Feld „Fehlermeldungen an“ (siehe unten)“). Kein Gate angepasst.
  • Files modified: docs/anleitung-administration.md
  • Verification: grep -c -> 3, grep -o | wc -l -> 3
  • Committed in: 77117de

Total deviations: 2 auto-fixed (1 x Rule 1, 1 x Rule 2). Impact on plan: keine Erweiterung der 35 Dateien, keine Gate-Anpassung; beide Korrekturen sind fuer Korrektheit bzw. Vollstaendigkeit noetig.

Ledger: gsd_run windows append --kind deviation fuer Abweichung 1 ist geschrieben (.planning/WINDOWS.md, ok: true) — die Datei liegt uncommittet fuer den Docs-Commit des Orchestrators.

Issues Encountered

  1. CI-Lauf 299, Versuch 1 failure — Ursache Gitea-Datenbank-Ausfall 16:57:09-16:57:19 (siehe Tabelle „CI-Lauf nach dem Push“), Registry antwortete 401 beim Push. Behoben durch Rerun ueber die API (Versuch 2 success). Kein Code-Commit.
  2. Zwei Messinstrumente des Plans trafen die Wirklichkeit nicht: Compose-Grep-Muster (0 fuer die neue UND fuer die bestehende SMTP-Zeile; grep -F -> 1) und Abbild-Probe fuer html-to-image (0 in den Server-node_modules des Standalone-Abbilds; korrigierte Probe im Client-Chunk positiv). Beide Male ist die Datei/das Abbild wie vorgeschrieben; beide Zahlen stehen oben nebeneinander.
  3. Web-Test-Baseline unveraendert 40/243, API 65/1060 — keine Abweichung, hier nur als Kontrolle: die Zielzahlen 67/1076 und 43/260 wurden exakt erreicht (API +2 Dateien, +16 Tests; Web +3 Dateien, +17 Tests).

Was bewusst offen bleibt

  • Browser-Beweis (Bild ohne Dialog, OKLCH-Farben, E-Mail mit PNG-Anhang in mailhog, Groesse des Anhangs): nicht im Executor moeglich (jsdom rastert nicht) — Human-Check end-of-phase, Anleitung unten.
  • Serverdatei /opt/tessera/docker-compose.prod.yml bekommt die Zeile TESSERA_BUGREPORT_TO: ${TESSERA_BUGREPORT_TO:-} nur von Hand (wie IMAGE_TAG, Betriebshandbuch Kapitel 3/9). Ohne sie gilt auf dem Server ausschliesslich das UI-Feld — was fuer den Live-Betrieb reicht.
  • Basis-/Dev-Compose reichen TESSERA_BUGREPORT_TO nicht durch (Plan: nur docker-compose.prod.yml); lokal ist der Empfaenger ueber das UI-Feld zu setzen.
  • Drossel im Prozessspeicher: je API-Prozess, geht bei Neustart verloren und gilt je Instanz — fuer eine Instanz korrekt, bei mehreren Instanzen waere die Grenze n x 5.
  • Erstfreigabe v1.0.0 (Zweig live + Tag) ist nicht Teil dieses Plans.

Fuer den Verifizierer (Browser-Check mit mailhog)

  1. Mail-Senke starten: docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d mailhog (Ports 1025 SMTP, 8025 Web-Oberflaeche: http://localhost:8025). Dann docker compose up -d --build api web (die :latest-Abbilder sind lokal warm; --build ist Pflicht, up allein baut nicht neu).
  2. Im Browser anmelden, Administrator -> SMTP: Host mailhog, Port 1025, Verschluesselung „Keine“, Benutzername/Passwort leer, Absender tessera@tessera.local, Fehlermeldungen an fehler@example.invalid, speichern. (Env-Variante nur bei Bedarf: TESSERA_BUGREPORT_TO ist in der Basis-Compose NICHT durchgereicht — dafuer muesste man sie lokal, uncommittet, in den environment-Block von api in docker-compose.dev.yml eintragen; das UI-Feld ist der vorgesehene Weg.)
  3. Auf einer Seite mit Inhalt (z. B. Benutzerverwaltung, dunkles Erscheinungsbild) den Kaefer-Knopf rechts oben klicken: Dialog mit Vorschau — die Vorschau darf den Dialog NICHT zeigen und muss die Farben der Seite wiedergeben. Beschreibung eintragen, „Senden“ -> „Vielen Dank, die Meldung wurde gesendet.“
  4. http://localhost:8025: E-Mail mit Betreff [Tessera Fehlermeldung] dev dev - /admin/users (lokal ohne Build-Args: Version/Kanal dev), Text mit allen Kontextzeilen, Anhang fehlermeldung-<yyyymmdd-hhmm>.png — Groesse notieren (Erwartung unter 2 MB).
  5. Zweite Probe: Feld „Fehlermeldungen an“ leeren und speichern -> Senden zeigt „Fuer Fehlermeldungen ist noch kein Postfach eingerichtet.“ plus Admin-Hinweis mit Link „Zu den SMTP-Einstellungen“ (als ADMIN/SUPER_ADMIN).
  6. Dritte Probe: sechs Meldungen hintereinander -> die sechste zeigt „Zu viele Meldungen in kurzer Zeit …“.
  7. docker compose logs api | grep "Bug report" -> je gesendeter Meldung eine Zeile Bug report from <user> (tenant <id>) sent to <adresse> — page <pfad>, screenshot <n> bytes, ohne Beschreibung und ohne Bild.

Handgriffe fuer den User

  • Wo der Empfaenger eingestellt wird: In Tessera als Administrator oben rechts Administrator -> SMTP, Feld Fehlermeldungen an (unter der Absenderadresse), Adresse eintragen, „Einstellungen speichern“. Ab dann gehen alle Meldungen der Anwender dieses Mandanten mit Bild dorthin. Feld leeren und speichern schaltet den Versand wieder ab (der Knopf bleibt sichtbar und erklaert dann, dass kein Postfach eingerichtet ist).
  • Rueckfall ueber die Umgebung (nur fuer Installationen ohne gespeicherte SMTP-Einstellungen): TESSERA_BUGREPORT_TO=<adresse> in der .env des Servers UND die Zeile TESSERA_BUGREPORT_TO: ${TESSERA_BUGREPORT_TO:-} im environment-Block von api in /opt/tessera/docker-compose.prod.yml (von Hand, wie beim IMAGE_TAG), danach api neu erstellen. Das UI-Feld gewinnt immer, wenn beides gesetzt ist.
  • Datenbank: Die neue Spalte kommt beim naechsten Deploy automatisch mit (migrate deploy beim API-Start, additive Migration, nichts zu tun).

Self-Check: PASSED

  • Dateien: alle 14 neu angelegten Dateien vorhanden (bug-reports/* 7, error-buffer.ts/.test.ts, bug-report-api.ts, bug-report/* 3, smtp-settings-form.test.tsx, Migration).
  • Commits: 54121c1, 60b0ee8, b41be21, 77117de in git log --oneline --all gefunden; main == origin/main.
  • commits: 4 = git rev-list --count 17a7e5e..HEAD.