153 lines
13 KiB
Markdown
153 lines
13 KiB
Markdown
---
|
|
phase: quick-260929-if2
|
|
plan: 01
|
|
subsystem: dashboard-widgets, api-reminders, desktop-notifications
|
|
tags: [reminders, notifications, tauri, rls, scheduler, smtp]
|
|
status: complete
|
|
requires: []
|
|
provides:
|
|
- "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"
|
|
affects: [dashboard, desktop, mail, rls-inventory]
|
|
tech-stack:
|
|
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)"
|
|
key-files:
|
|
created:
|
|
- 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
|
|
modified:
|
|
- 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
|
|
key-decisions:
|
|
- "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)"
|
|
duration: about 1 h 15 min
|
|
completed: 2026-09-29
|
|
commits: 3
|
|
plan_head_before: cd1f8f6cda7d3b1b6fa22ab8ec9274201d9c2089
|
|
plan_head_after: 8027c4857080790bf9994553fbda8036d81f8eb3
|
|
actuals:
|
|
tokens: 43000
|
|
tasks: 3
|
|
commits: 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).
|