Files
tessera-ctl/.planning/quick/260929-if2-reminder-widget-mit-benachrichtigung/260929-if2-VERIFICATION.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

14 KiB

phase, verified, status, score, behavior_unverified, overrides_applied, behavior_unverified_items, human_verification
phase verified status score behavior_unverified overrides_applied behavior_unverified_items human_verification
quick-260929-if2 2026-09-29T12:20:00Z human_needed 8/9 must-haves verified 1 0
truth test expected why_human
At the due time the desktop app shows a native OS notification, also while the main window is hidden in the tray, through the notification plugin, which the page may call only from the stored server origin Windows VM with a desktop client built from commit 6879c75 (or CI package): create a reminder due in 3 minutes, close the window with X, wait; then open the window A Windows toast 'Erinnerung: ...' appears within about 1 minute of the due time; the reminder shows 'Faellig' afterwards. Also: after 'Server-Adresse aendern...' to the same server the toast still works Unit tests only pin the URL pattern (escape, self-check, match/no-match) and the exact 3-permission set. Whether Tauri accepts the runtime capability (remote + notification:allow-* identifiers) and delivers the toast cannot be seen by grep or cargo test; add_capability panics rather than returning Err on a bad pattern/identifier
test expected why_human
Browser (Playwright MCP, dark): create a reminder due in 2 min, switch to another portal page, wait past due time, spy on window.Notification Exactly one Notification call titled 'Erinnerung: ...' although the dashboard is not visible; on return the row is highlighted with 'Faellig', 'Erledigt' and 'Spaeter erinnern'; a second tab of the same session gives no second notification Real Notification permission flow and multi-tab Web Locks/localStorage dedup need a live browser (orchestrator runs these)
test expected why_human
Browser, fresh context without notification grant Permission prompt appears only after clicking 'Speichern' on the first reminder, never on page load Browser permission UI
test expected why_human
Dark-mode look of the 'Faellig' badge (bg-status-warn/20 text-status-warn-fg, deviation 3) and highlighted row Readable contrast Visual
test expected why_human
E-mail flow with a reachable SMTP (start mailhog or real SMTP), reminder with the e-mail tick due, then snooze +10 min Exactly one mail with time in Europe/Berlin; after the snooze a second one; e-mail checkbox greyed out with explanation when SMTP is removed Real transport; locally the mailhog container is not running (summary: 3 failed attempts then stop, as designed)
test expected why_human
Windows VM: desktop app and browser open simultaneously Each shows the notification exactly once Needs Windows GUI

Quick 260929-if2: Reminder widget "Erinnerungen" Verification Report

Phase Goal: Reminder widget with notification in desktop app, browser and optionally by e-mail; one-time, no advance warning, after due "Erledigt"/"Spaeter erinnern" (10 min / 1 h / tomorrow); personal with RLS; e-mail exactly once per due occurrence. Verified: 2026-09-29 Status: human_needed Re-verification: No, initial verification

Note on commits: verified against the re-created commits 325c5dd, 709b41a, 6879c75 (HEAD, three commits above cd1f8f6; nothing pushed, git log origin/main..HEAD shows exactly these). No source files were modified by this verification. The only untracked path is the task directory. During verification an unrelated test reminder ("Kaffee holen") appeared in the live DB, presumably from the orchestrator's browser check; it was not touched.

Goal Achievement

Observable Truths

# Truth Status Evidence
1 User adds widget, creates reminder (date/time/title/description, local time), sees only own open reminders sorted by due time (D-05) VERIFIED reminder-widget.tsx sorts by dueAt, lists via listReminders; reminders.service.ts list() uses forTenant(prisma, tenantId, userId) + where {tenantId,userId}, orderBy dueAt asc; registered in registry, page.tsx (registerWidget('reminder', ReminderWidget)), WIDGET_TYPES in shared; widget test green; live GET as testuser returns 200 array
2 At due time an open tab shows a Web Notification once granted; permission asked only from widget at first creation, never on page load (D-04) VERIFIED (unit-level; live browser in human items) reminder-notify.ts: requestBrowserPermissionOnce (flag in localStorage, no-op in Tauri/when not 'default'); called first in submit handler only when !reminder (create); ReminderNotifier mounted in app-shell.tsx:48; remindersToNotify only dueAt <= now (D-02) within 24 h; widget/notifier/notify tests green (410 web tests in scope, 1069 full)
3 Desktop app shows native OS notification also while window hidden in tray, via plugin, callable only from stored server origin PRESENT_BEHAVIOR_UNVERIFIED Code present and wired: showReminderNotification invokes plugin:notification|notify with {options:{title,body}}; grant_server_notifications called in setup() before first navigate and in save_server_url; server_origin_pattern escapes host, self-checks with RemoteUrlPattern; SERVER_NOTIFICATION_PERMISSIONS pinned to 3 ids; capability .local(false).window("main"); plugin registered (lib.rs:867). cargo: 57 passed, 12 server_origin_*; fmt ok. add_capability in tauri 2.11.3 appends (checked source), so repeated calls do not overwrite. Actual toast delivery / runtime acceptance is not exercised by any test, so routed to human (Windows)
4 Every client shows each (id, dueAt) at most once: tabs share local claim, desktop and browser each notify once VERIFIED (unit-level) claimNotification (localStorage record, key ${id}|${dueAt}, 7-day prune) under withNotifyLock (Web Locks); key changes after snooze; notifier test: one notification across several ticks, again after dueAt change. Separate webview storage means desktop and browser each notify once by design. Multi-tab live check in human items
5 Due reminder stays highlighted with "Erledigt" (removes) and "Spaeter erinnern" (+10 min, +1 h, tomorrow same time); snooze sets new dueAt so notifications and e-mail fire again (D-01, D-03) VERIFIED Widget: due rows (dueAt <= now, 10 s tick) get border-status-warn, badge, Erledigt (deleteReminder), snooze options via snoozeTarget; reminder-time.ts snoozeTarget (now+10m, now+1h, original time + calendar days until future); service snooze writes {dueAt, emailSentAt: null, emailAttempts: 0}; no recurrence field/UI anywhere in schema/DTO; time/widget/service tests green
6 Upcoming reminders editable/deletable; editing a due one is 409; snoozing a not-due one is 409 VERIFIED service.update 409 when dueAt <= now; snooze 409 when dueAt > now; controller has PATCH/POST snooze/DELETE below static email-status; route-order spec in controller spec (14 tests green); widget shows edit/delete only on non-due rows
7 With e-mail on, server sends exactly one mail per due occurrence via tenant SMTP (Europe/Berlin), no client needed, several instances safe (atomic claim); toggle disabled with explanation when SMTP missing / no e-mail VERIFIED (unit-level; real transport in human items) reminder-mail.scheduler.ts: single forSystem call, scalar select, candidate filter (emailEnabled, emailSentAt null, attempts<3, due within 24 h), claim updateMany with dueAt equality and emailSentAt: null, count===1 check before send, release only on transport failure with own timestamp, skip keeps claim, running reentrancy guard, 30 s addInterval. MailService.sendReminderEmail: CR/LF stripped subject, Europe/Berlin de-DE + " Uhr", text only, returns bool. Scheduler spec: 16 tests incl. two instances one mail, 3-attempt cap, snooze re-arm. Toggle: emailAvailable/emailHint in form modal; email-status endpoint live: {"smtpConfigured":true,"hasEmail":true}. Summary reports a live DB run with 3 attempts then stop
8 Foreign reminder id always 404 (never 403); Reminder has tenant+user RLS policy and system read policy; rls-coverage and rls-access-inventory green; classification doc re-measured VERIFIED loadOwn throws NotFoundException for unknown/foreign; live DELETE on unknown id returns 404; DB: relrowsecurity and relforcerowsecurity both true; policies tenant_isolation_policy (ALL, tenant AND user dim) and system_read_policy (SELECT, is_system_context()); migrate diff --exit-code "No difference detected"; rls-coverage and rls-access-inventory pass; FORSYSTEM_ALLOWED_CALL_SITES has reminder-mail.scheduler.ts, 1; doc updated in all three commits (numbers not independently re-counted)
9 All API/web tests green, type-check and lint green, Biome warnings web <= 55 and api <= 82, cargo test/fmt/clippy green VERIFIED (clippy not re-run) API full: 91 files / 1570 tests pass; web full: 108 files / 1069 tests pass; turbo run type-check lint --force: 9/9 successful; Biome web 55, api 82 (exactly baseline); cargo test 57 pass, fmt ok. Clippy -D warnings not re-run by me (summary claims clean)

Score: 8/9 truths verified (1 present, behavior-unverified)

Required Artifacts

Artifact Status Details
apps/api/prisma/migrations/20260929140000_reminder/migration.sql VERIFIED Table, indexes, FK cascade, ENABLE+FORCE RLS, both policies; applied locally, no schema drift
apps/api/src/reminders/reminders.service.ts / .controller.ts VERIFIED Owner-scoped CRUD/snooze/email-status, all through forTenant(..., tenantId, userId); REMINDER_SELECT excludes tenantId/userId/emailSentAt/emailAttempts
apps/api/src/reminders/reminder-mail.scheduler.ts VERIFIED Registered in RemindersModule providers; module registered in app.module.ts
apps/web/src/lib/reminder-notify.ts VERIFIED Tauri vs browser branch, one-time permission, dedup with Web Locks
apps/web/src/components/reminders/reminder-notifier.tsx VERIFIED Mounted in app-shell.tsx
apps/web/src/components/dashboard/widgets/reminder-widget.tsx (+ form modal) VERIFIED Wired via registry, page.tsx, widget-wrapper FRAME_HEADER_TYPES, icon
apps/desktop/src-tauri/src/lib.rs VERIFIED (code), see truth 3 server_origin_pattern, grant_server_notifications, 12 tests
From To Status Details
app-shell.tsx ReminderNotifier WIRED line 48
reminder-notify.ts plugin notification WIRED invoke('plugin:notification|notify', { options })
lib.rs Tauri runtime authority WIRED add_capability in grant_server_notifications, called in setup() and save_server_url
scheduler MailService.sendReminderEmail WIRED claim then send, release on false
snooze emailSentAt/emailAttempts reset WIRED data: { dueAt, emailSentAt: null, emailAttempts: 0 }

Data-Flow Trace (Level 4)

Widget and notifier data come from listReminders() -> GET /reminders -> Prisma query (live check returned real rows). FLOWING. No hardcoded/static fallbacks.

Behavioral Spot-Checks

Behavior Command Result Status
API reminders/mail/prisma/dashboard specs vitest run src/reminders src/mail src/prisma src/dashboard 17 files, 267 passed PASS
Web reminder specs vitest run src/lib/reminder src/components/reminders src/components/dashboard src/messages 30 files, 410 passed PASS
Full suites api / web vitest run 1570 / 1069 passed PASS
cargo cargo test --lib; server_origin_ filter 57 passed; 12 passed PASS
Schema drift prisma migrate diff --exit-code exit 0 PASS
RLS in DB pg_policies, pg_class both policies present, RLS+FORCE on PASS
Live API login, GET /reminders, GET /reminders/email-status, DELETE unknown id 200, 200, 200, 404 PASS
Type-check + lint, Biome turbo run type-check lint --force; biome lint 9/9; 55 / 82 warnings PASS

Probe Execution

No probes declared. SKIPPED.

Requirements Coverage

QUICK-260929-if2 (all decisions D-01..D-05, E-01..E-09) implemented as described; no REQUIREMENTS.md mapping.

Anti-Patterns Found

None. No TBD/FIXME/XXX/TODO in the new files; no stubs; no debt markers. Working tree clean apart from the task directory.

Notes / minor observations (non-blocking)

  • Deviation 3 (badge uses bg-status-warn/20 instead of the plan's solid fill) is documented and justified by contrast; flagged for a dark-mode visual check.
  • Summary deviation 6 mentions the trailer as "Claude Sonnet 5.5"; the re-created commits carry "Claude Opus 5.5 (1M context)". Immaterial to the goal.
  • claimNotification claims before showing: a browser whose permission is still 'default' at due time loses that occurrence's notification (still visible in the widget, highlighted). Consistent with the plan ("blocked notifications only show in the widget").
  • Fingerprint fields (covered_files/covered_digest) were not generated because the fingerprint verb was not run in this environment.

Human Verification Required

See frontmatter human_verification and behavior_unverified_items. Summary: (1) Windows toast in the tray, runtime capability accepted by Tauri, plus desktop+browser once-each; (2) live browser notification, permission prompt timing and two-tab dedup; (3) dark-mode look of the "Faellig" badge; (4) real SMTP flow (mailhog is not running locally).

Gaps Summary

No gaps. All code-verifiable must-haves hold in the codebase and the automated gates reproduce the summary's numbers (tests, type-check, lint, Biome baseline, migrate diff, RLS state in the live DB). The single behavior-dependent truth that cannot be proven without a GUI is the actual desktop toast through the runtime Tauri capability; it is left PRESENT_BEHAVIOR_UNVERIFIED and routed to the Windows check, so the status is human_needed, not passed.


Verified: 2026-09-29 Verifier: Claude (gsd-verifier)