Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
34 KiB
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 |
|
|
|
|
|
17a7e5ef9b |
|
|
|
|
|
|
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
- Task 1: API —
54121c1(feat) — 16 Dateien - Task 2a: Web Knopf/Dialog/Puffer —
60b0ee8(feat) — 13 Dateien - Task 2b: SMTP-Formular —
b41be21(feat) — 5 Dateien - 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.tsunangetastet (GateM_EMPTY=0). Ein globalesapp.useBodyParser('json', { limit })haette jede JSON-Route inkl./auth/logingeoeffnet. - 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@Transformfuer einen im Rumpf GANZ fehlenden Schluessel nicht auf; die Normalisierungundefined -> []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 — Projektkonfigurationbranching_strategy: none, Plan verlangt Push aufmainund 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:
BadRequestExceptionbeipipe.transform({ ...baseBody }, meta)ohneerrors) - Issue: Das Rezept des Plans (
@Transformgefolgt von@IsArray()) deckt den Fall „Feld fehlt im Multipart-Rumpf“ nicht: class-transformer laeuft@Transformnur fuer Schluessel, die im Quellobjekt vorhanden sind;errorsbliebundefined,@IsArray()schlug fehl — ein Anwender ohne Browserfehler haette 400 bekommen. - Fix:
@Expose()(ausclass-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.tsTest 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: mindestens3) - Issue:
grep -czaehlt 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
- 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 2success). Kein Code-Commit. - Zwei Messinstrumente des Plans trafen die Wirklichkeit nicht: Compose-Grep-Muster (
0fuer die neue UND fuer die bestehende SMTP-Zeile;grep -F->1) und Abbild-Probe fuerhtml-to-image(0in den Server-node_modulesdes Standalone-Abbilds; korrigierte Probe im Client-Chunk positiv). Beide Male ist die Datei/das Abbild wie vorgeschrieben; beide Zahlen stehen oben nebeneinander. - 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.ymlbekommt die ZeileTESSERA_BUGREPORT_TO: ${TESSERA_BUGREPORT_TO:-}nur von Hand (wieIMAGE_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_TOnicht durch (Plan: nurdocker-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)
- 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). Danndocker compose up -d --build api web(die:latest-Abbilder sind lokal warm;--buildist Pflicht,upallein baut nicht neu). - Im Browser anmelden, Administrator -> SMTP: Host
mailhog, Port1025, Verschluesselung „Keine“, Benutzername/Passwort leer, Absendertessera@tessera.local, Fehlermeldungen anfehler@example.invalid, speichern. (Env-Variante nur bei Bedarf:TESSERA_BUGREPORT_TOist in der Basis-Compose NICHT durchgereicht — dafuer muesste man sie lokal, uncommittet, in denenvironment-Block vonapiindocker-compose.dev.ymleintragen; das UI-Feld ist der vorgesehene Weg.) - 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.“
http://localhost:8025: E-Mail mit Betreff[Tessera Fehlermeldung] dev dev - /admin/users(lokal ohne Build-Args: Version/Kanaldev), Text mit allen Kontextzeilen, Anhangfehlermeldung-<yyyymmdd-hhmm>.png— Groesse notieren (Erwartung unter 2 MB).- 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).
- Dritte Probe: sechs Meldungen hintereinander -> die sechste zeigt „Zu viele Meldungen in kurzer Zeit …“.
docker compose logs api | grep "Bug report"-> je gesendeter Meldung eine ZeileBug 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.envdes Servers UND die ZeileTESSERA_BUGREPORT_TO: ${TESSERA_BUGREPORT_TO:-}imenvironment-Block vonapiin/opt/tessera/docker-compose.prod.yml(von Hand, wie beimIMAGE_TAG), danachapineu erstellen. Das UI-Feld gewinnt immer, wenn beides gesetzt ist. - Datenbank: Die neue Spalte kommt beim naechsten Deploy automatisch mit (
migrate deploybeim 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,77117deingit log --oneline --allgefunden;main == origin/main. commits: 4=git rev-list --count 17a7e5e..HEAD.