Files
tessera-ctl/.planning/quick/260929-if2-reminder-widget-mit-benachrichtigung/260929-if2-SUMMARY.md
T
schalli 8c644de5da
Tessera CI/CD / Lint & Type Check (push) Successful in 57s
Tessera CI/CD / Tests (push) Successful in 1m34s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 5m46s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m27s
docs(quick-260929-if2): Erinnerungen-Widget, Verifikation und Browser-Pruefung
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 14:17:41 +02:00

13 KiB

phase, plan, subsystem, tags, status, requires, provides, affects, tech-stack, key-files, key-decisions, duration, completed, commits, plan_head_before, plan_head_after, actuals
phase plan subsystem tags status requires provides affects tech-stack key-files key-decisions duration completed commits plan_head_before plan_head_after actuals
quick-260929-if2 01 dashboard-widgets, api-reminders, desktop-notifications
reminders
notifications
tauri
rls
scheduler
smtp
complete
Widget 'Erinnerungen' (type reminder) with create/edit/delete, due highlight, Erledigt, Spaeter erinnern
API /reminders (list, email-status, create, patch, snooze, delete) with owner scoping (404 for foreign ids)
Reminder table with tenant+user RLS policy and system_read_policy
Global ReminderNotifier in AppShell (browser Web Notification, Tauri plugin notification)
ReminderMailScheduler: atomic-claim e-mail once per due occurrence
Tauri runtime remote capability for notification permissions on the stored server origin only
dashboard
desktop
mail
rls-inventory
added patterns
claim-before-send scheduler (updateMany with count check), forSystem candidate query + forTenant per row
runtime Tauri capability with escaped and self-checked URL pattern (no catch_unwind)
created modified
apps/api/prisma/migrations/20260929140000_reminder/migration.sql
apps/api/src/reminders/ (module, controller, service, scheduler, dto, 4 specs)
apps/web/src/lib/reminders-api.ts
apps/web/src/lib/reminder-notify.ts
apps/web/src/lib/reminder-time.ts
apps/web/src/components/reminders/reminder-notifier.tsx
apps/web/src/components/dashboard/widgets/reminder-widget.tsx
apps/web/src/components/dashboard/widgets/reminder-form-modal.tsx
apps/api/prisma/schema.prisma
apps/api/src/app.module.ts
apps/api/src/mail/mail.service.ts
apps/api/src/prisma/rls-access-inventory.spec.ts
apps/desktop/src-tauri/src/lib.rs
packages/shared/src/index.ts
apps/web (registry, widget-icon, widget-wrapper, app-shell, page.tsx, messages de/en, umlaut-dictionary)
docs/mandantentrennung-zugriffsklassifikation.md, docs/anleitung-anwender.md, CHANGELOG.md
E-01 runtime remote capability for exactly the stored origin (escaped, self-checked with RemoteUrlPattern), window main, three notification permissions
E-02 Erledigt deletes the row; E-04 claim before send, release only on transport failure, max 3 attempts
Snooze resets emailSentAt/emailAttempts so the mail fires again (D-03)
about 1 h 15 min 2026-09-29 3 cd1f8f6cda 8027c4857080790bf9994553fbda8036d81f8eb3
tokens tasks commits
43000 3 3

Phase quick-260929-if2 Plan 01: Erinnerungen-Widget mit Benachrichtigung Summary

Persoenliche einmalige Erinnerungen als Dashboard-Widget: Benachrichtigung zur Faelligkeit im Browser und als native Windows-Meldung in der Desktop-App (auch im Infobereich), optional eine E-Mail, die der Server genau einmal je Faelligkeit ueber einen atomaren Anspruch versendet.

Commits (lokal, nicht gepusht)

Task Hash Betreff
1 (Tracer) 26f8f0f feat(260929-if2): Erinnerungen anlegen und zur Faelligkeit benachrichtigen (Tracer)
2 580c31c feat(260929-if2): faellige Erinnerungen erledigen, spaeter erinnern, bearbeiten und loeschen
3 8027c48 feat(260929-if2): Erinnerung zusaetzlich per E-Mail, Doku und Aenderungsliste

commits: gemessen mit git rev-list --count cd1f8f6..HEAD = 3. Der Tracer-Feedback-Gate (Auto-Modus: <verify> erneut ausfuehren) lief vor Task 2: API-, Web-, cargo-Tests, migrate diff und die curl-End-to-End-Kette waren gruen, also wurde erweitert.

Gemessene Gates (nach Task 3)

Gate Ergebnis
API vitest komplett 91 Dateien, 1570 Tests, alle gruen
Web vitest komplett 108 Dateien, 1069 Tests, alle gruen
pnpm turbo run type-check lint --force 9/9 Tasks erfolgreich
Biome-Warnungen web 55 (max 55), api 82 (max 82), also exakt die Grundlinie
cargo test --lib 57 gruen, davon 12 server_origin_* (Plan verlangte mindestens 10)
cargo fmt --check / clippy -D warnings sauber
rls-coverage + rls-access-inventory gruen (30 Zusicherungen im Inventar)
prisma migrate diff --exit-code "No difference detected" (Exit 0), Migration lokal angewendet
Gate-Schleife (Rohtreffer, ohne spec) Summe 61 / 235 / 7 (ungebunden / gebunden / System); Bereich reminders 0 / 12 / 1
Bestandsaufnahme-Doku 83 Paare (44 muss-mandantengebunden, 21 keine-mandantengebundene-tabelle, 16 beides, 2 bewusst-uebergreifend), mit grep -cE '^| apps/api/src/' nachgezaehlt
FORSYSTEM_ALLOWED_CALL_SITES neu reminder-mail.scheduler.ts = 1, Summe 6 Dateien / 7 Aufrufe

curl-Ergebnisse (lokal, gegen die neu gebauten Container)

  • Tracer: testuser POST + GET ok, admin sieht das Tracer-Test nicht (tracer e2e ok); Vergangenheit ergibt 400.
  • Task 2: admin bekommt fuer PATCH, snooze und DELETE auf eine testuser-id je 404; testuser: snooze auf nicht faellige Erinnerung 409, PATCH 200. Alle Test-Zeilen geloescht.
  • Task 3: GET /reminders/email-status liefert {"smtpConfigured":true,"hasEmail":true} (lokal ist SmtpConfig auf mailhog:1025 gesetzt, testuser hat testuser@example.com); POST mit emailEnabled:true ergibt 201. /health ok.
  • Planerlauf gegen die echte Datenbank: eine Erinnerung mit E-Mail wurde 1 Minute nach Anlage faellig; der Planer versuchte den Versand im 30-s-Takt genau dreimal (mailhog-Container laeuft lokal nicht, also Transportfehler und Freigabe), danach emailAttempts = 3 und keine weiteren Versuche. Damit ist Anspruch, Freigabe und die Grenze von 3 Versuchen live belegt. Alle Test-Zeilen danach geloescht (count(*) = 0).

Deviations from Plan

  1. [Rule 3 - Blocking] Bestehende Tests an die neue Kachel angepasst. widget-registry.test.tsx (Typliste, Groessentabelle, Zaehler 40 auf 44) und widget-catalog-modal.test.tsx (letzte Kachel nun reminder) pruefen die exakte Kachelliste. Ohne Anpassung waere die Suite rot. Commit 26f8f0f.
  2. [Rule 3] reminder-time.ts schon in Task 1. Der Plan legte localInputsToIso zuerst in die Kachel und verschob sie in Task 2; ich habe sie gleich in reminder-time.ts angelegt und in Task 2 nur erweitert (isoToLocalInputs, snoozeTarget). Kein Verhalten anders.
  3. [Rule 2 - Lesbarkeit] "Faellig"-Abzeichen mit 20 % Flaeche. Der Plan nannte bg-status-warn text-status-warn-fg; die Schrift-Variante erreicht auf voller Warnflaeche nur rund 2,6:1 (globals.css-Kommentar). Verwendet wird bg-status-warn/20 text-status-warn-fg (die dokumentierte Pillen-Form). Bitte im Dunkelmodus mitpruefen.
  4. [Rule 2] lässt auf die Umlaut-Allowlist (umlaut-dictionary.ts), weil der Text alreadyDue "lässt sich nicht mehr bearbeiten" korrektes Deutsch ist, das der Guard sonst meldet (im Plan vorgesehen, "nur wenn der Guard warnt").
  5. Formular-Details: emailEnabled wird beim Bearbeiten nur gesendet, wenn es sich aendert (sonst kippt eine unveraenderte Alt-Einstellung das Speichern mit 400, wenn SMTP inzwischen fehlt); ein bereits angehaktes Feld bleibt bedienbar, damit man es abwaehlen kann. Beim Bearbeiten wird nie die Browser-Erlaubnis abgefragt (D-04: nur beim ersten Anlegen). Zusaetzlich bekam ReminderFormModal die Props emailStatus und onStale (409 beim Bearbeiten laedt neu).
  6. Commit-Zeile: Die Co-Authored-By-Zeile ist Claude Sonnet 5.5 <noreply@anthropic.com> (das laufende Modell, wie die Umgebung sie vorgibt), nicht "Claude Opus 5.5 (1M context)" wie in der Aufgabenbeschreibung. Bei Bedarf per Umschreiben anzupassen, bevor gepusht wird.

Stop-Regel des Plans: nach jedem Commit lag der Kontext weit unter der Haelfte, daher alle drei Segmente in einem Lauf.

Known Stubs

Keine. Alle Daten der Kachel kommen aus der API; keine Platzhalter.

Threat Flags

Keine neue Flaeche ausserhalb des Plan-Threat-Models. Zur Beachtung (T-IF2-03, im Plan akzeptiert): nach einem Serverwechsel behaelt der alte Ursprung sein Benachrichtigungsrecht bis zum App-Neustart.

Nicht messbar ohne GUI (bitte pruefen)

  • Das Rust-Laufzeitrecht (add_capability mit remote) ist per Unit-Tests der Musterbildung abgesichert (Escaping, Selbstpruefung, exakte 3er-Menge), aber der echte Toast in der Desktop-App ist nur auf der Windows-VM pruefbar (siehe Schritte 10 bis 12 unten).

Manuelle Pruefschritte fuer den Orchestrator

Angepasst an den Ist-Zustand: lokal ist SMTP auf mailhog:1025 gesetzt, der mailhog-Container laeuft aber NICHT (docker ps zeigt keinen). Die E-Mail-Checkbox ist also aktiv (nicht ausgegraut), Mails scheitern lokal beim Transport, bis mailhog laeuft (z. B. aus docker-compose.dev.yml starten).

Browser (Playwright MCP, dunkel per Theme-Knopf; nie per fetch aus der Seite messen):

  1. Als testuser / Test1234!test anmelden. Benachrichtigungen fuer den Ursprung im Playwright-Kontext erlauben (grantPermissions(['notifications'])), sonst bleibt die Abfrage "default". Einmal auch mit neuem Kontext ohne Erlaubnis: die Abfrage erscheint erst nach Klick auf "Speichern" beim ersten Anlegen, nicht beim Laden der Seite.
  2. "Bearbeiten" -> "Widget hinzufuegen" -> Katalog zeigt "Erinnerungen" mit Glocken-Symbol -> hinzufuegen -> "Fertig". Dunkel-Screenshot: Kopfzeile mit Symbol-Chip, Leerzustand, Knopf "Neue Erinnerung".
  3. Erinnerung mit Faelligkeit in 2 Minuten anlegen (Titel + Beschreibung). Die Liste zeigt sie mit lokaler Zeit; der E-Mail-Haken ist bedienbar.
  4. Vor der Faelligkeit einen Spion in die Seite legen (page.evaluate, window.Notification umhuellen und Aufrufe zaehlen). Auf eine andere Portalseite (z. B. Marktplatz) wechseln und die Faelligkeit abwarten. Erwartet: genau ein Aufruf mit Titel "Erinnerung: ...", obwohl das Dashboard nicht sichtbar ist (der Melder ist global). Zurueck auf dem Dashboard: Zeile hervorgehoben, Abzeichen "Faellig", Knoepfe "Erledigt" und "Spaeter erinnern". Dunkel-Screenshot der hervorgehobenen Zeile (Lesbarkeit des Abzeichens beurteilen).
  5. Zweiten Tab derselben Sitzung oeffnen und 20 s warten: keine zweite Benachrichtigung fuer dieselbe Erinnerung.
  6. "Spaeter erinnern" -> "In 10 Minuten": Zeile ist wieder kuenftig mit neuer Zeit (Bearbeiten/Loeschen sichtbar). "Bearbeiten": Titel aendern, speichern. Loeschen mit Rueckfrage (Ja/Nein). Eine weitere Erinnerung faellig werden lassen, dann "Erledigt": sie verschwindet.
  7. E-Mail: lokal ist SMTP gesetzt (mailhog:1025), Checkbox ist bedienbar. Fuer den echten Mailfluss zuerst mailhog starten, dann eine Erinnerung mit Haken faellig werden lassen: genau eine Mail mit Zeit in Europe/Berlin. Nach "In 10 Minuten" kommt nach Ablauf eine zweite. (Den ausgegrauten Zustand mit Erklaerungstext kann man pruefen, indem man in Einstellungen -> SMTP die Einrichtung entfernt; danach wiederherstellen.)
  8. Als admin (admin123): das Widget zeigt keine Erinnerungen von testuser.
  9. Am Ende alle Test-Erinnerungen entfernen.

Windows-VM (Proxmox 8233, laut VM-Notizen): Die Rust-Aenderung wirkt nur in einem Desktop-Client, der aus diesem Stand gebaut ist. Da nichts gepusht wurde, baut CI keinen; der Windows-Test braucht entweder Push + CI-Paket oder ein lokal gebautes NSIS-Paket. Ein aelterer Client zeigt das Widget und bekommt E-Mails, aber keine Toasts (im CHANGELOG vermerkt). 10. Mit dem neuen Client, verbunden mit dem Server mit diesem Code: Erinnerung in 3 Minuten anlegen, Fenster mit X schliessen (Infobereich), warten. Ein Windows-Toast von "Tessera" mit "Erinnerung: ..." erscheint innerhalb rund einer Minute nach der Faelligkeit (WebView2 drosselt versteckte Timer, E-01). Danach das Fenster oeffnen: die Erinnerung zeigt "Faellig". 11. Desktop-App und Browser gleichzeitig offen: jede zeigt die Benachrichtigung genau einmal. 12. Nach "Server-Adresse aendern..." auf denselben Server funktionieren die Benachrichtigungen weiter (Berechtigung wird in save_server_url erneut erteilt).

Self-Check: PASSED

  • Erstellte Dateien vorhanden: Migration, apps/api/src/reminders/* (inkl. reminder-mail.scheduler.ts), reminders-api.ts, reminder-notify.ts, reminder-time.ts, reminder-notifier.tsx, reminder-widget.tsx, reminder-form-modal.tsx (alle in den Commits enthalten).
  • Commits 26f8f0f, 580c31c, 8027c48 liegen auf main (git log cd1f8f6..HEAD); git status zeigt ausser dem .planning-Verzeichnis nichts Uncommittetes.
  • Nichts gepusht.

Browser-Pruefung (Orchestrator, 29.09., dunkel, testuser)

  • Katalog zeigt „Erinnerungen“; Widget hinzugefuegt, Leerzustand + „Neue Erinnerung“.
  • „Kaffee holen“ faellig 14:14 mit E-Mail-Haken, danach auf den Marktplatz gewechselt: genau eine Browser-Benachrichtigung „Erinnerung: Kaffee holen“ um 14:14:08 (global, nicht nur auf dem Dashboard).
  • MailHog (lokal gestartet, Alias mailhog im backend-net): genau eine Mail an testuser@example.com um 14:14:15, Text „Dienstag, 29. September 2026 um 14:14 Uhr“ (Berlin).
  • Zweiter Tab derselben Sitzung, 20 s: keine zweite Benachrichtigung.
  • Faellig-Zustand dunkel gut lesbar (Rahmen + Abzeichen „Fällig“, Erledigt / Später erinnern).
  • Später erinnern: Menue In 10 Minuten / In 1 Stunde / Morgen um 14:14; „In 10 Minuten“ -> 14:26. Bearbeiten (Titel) ok. Löschen mit Rueckfrage Ja/Nein ok.
  • „Wasser trinken“ (ohne E-Mail) faellig -> Erledigt -> verschwindet; keine zweite Mail.
  • Aufgeraeumt: Reminder-Tabelle leer, MailHog-Container entfernt.
  • Offen: Windows-Toast der Desktop-App (braucht CI-Paket nach Push), Erlaubnisabfrage-erst-nach-Speichern nur per Komponententest belegt (Playwright-Kontext hatte die Erlaubnis vorab).