diff --git a/cmd/mavweb/handlers_test.go b/cmd/mavweb/handlers_test.go index 53aa286..45db011 100644 --- a/cmd/mavweb/handlers_test.go +++ b/cmd/mavweb/handlers_test.go @@ -54,7 +54,9 @@ type fakeCore struct { revertErr error // for handleNotifications tests - nudgesErr error + nudgesErr error + attempts []ipc.DeliveryAttempt + attemptStatus string // for handleHistory tests historyFacts []ipc.Fact @@ -1251,3 +1253,35 @@ func TestHandleWS_AssertedSession_PassesGate(t *testing.T) { t.Fatalf("status = 403 on an asserted session; body=%s", rr.Body.String()) } } + +func (f *fakeCore) DeliveryAttempts(_ context.Context, status string, _ int) ([]ipc.DeliveryAttempt, error) { + f.attemptStatus = status + return f.attempts, nil +} + +// TestHandleNotifications_ShowsTheOutbox — the outbox was written and never +// read, so a dropped or failed send was invisible (Vikunja #390). +func TestHandleNotifications_ShowsTheOutbox(t *testing.T) { + done := time.Date(2026, 8, 4, 9, 0, 30, 0, time.UTC) + core := &fakeCore{ + attempts: []ipc.DeliveryAttempt{ + {Kind: "nudge", Rule: "care-check", Channel: "telegram", Status: "dropped", + Created: done.Add(-30 * time.Second), Completed: &done}, + {Kind: "reminder", ReminderID: 7, Channel: "voice", Status: "pending", Created: done}, + }, + } + rr := httptest.NewRecorder() + handleNotifications(rr, httptest.NewRequest(http.MethodGet, "/notifications?status=dropped", nil), core) + if rr.Code != http.StatusOK { + t.Fatalf("status = %d, want 200; body=%s", rr.Code, rr.Body.String()) + } + if core.attemptStatus != "dropped" { + t.Errorf("status filter = %q, want it passed through", core.attemptStatus) + } + body := rr.Body.String() + for _, want := range []string{"care-check", "dropped", "reminder #7", "Delivery outbox"} { + if !strings.Contains(body, want) { + t.Errorf("rendered outbox missing %q", want) + } + } +} diff --git a/cmd/mavweb/main.go b/cmd/mavweb/main.go index a80d951..9d8368c 100644 --- a/cmd/mavweb/main.go +++ b/cmd/mavweb/main.go @@ -893,12 +893,59 @@ func handleNotifications(w http.ResponseWriter, r *http.Request, core ipc.CoreAP http.Error(w, "notifications error: "+err.Error(), http.StatusBadGateway) return } + // The outbox, on the page that already answers "what did she send". + // A failed or dropped attempt is why she went quiet, and until now it was + // recorded and unreadable (Vikunja #390). Filter with ?status=dropped. + status := r.URL.Query().Get("status") + attempts, err := core.DeliveryAttempts(ctx, status, 50) + if err != nil { + // The nudge list is still worth showing, so this is a note on the page + // rather than a dead page. + log.Printf("notifications: delivery attempts: %v", err) + } w.Header().Set("Content-Type", "text/html; charset=utf-8") - if err := notificationsTmpl.Execute(w, map[string]any{"Nudges": nudges}); err != nil { + if err := notificationsTmpl.Execute(w, map[string]any{ + "Nudges": nudges, + "Attempts": deliveryRows(attempts), + "Status": status, + }); err != nil { log.Printf("notifications template: %v", err) } } +// deliveryRow is one outbox line, with every timestamp already formatted so +// the template holds no date logic — same shape as taskRow. +type deliveryRow struct { + Kind string + Target string + Channel string + Status string + Created string + Completed string +} + +func deliveryRows(as []ipc.DeliveryAttempt) []deliveryRow { + out := make([]deliveryRow, 0, len(as)) + for _, a := range as { + target := a.Rule + if target == "" && a.ReminderID != 0 { + target = "reminder #" + strconv.FormatInt(a.ReminderID, 10) + } + row := deliveryRow{ + Kind: a.Kind, + Target: target, + Channel: a.Channel, + Status: a.Status, + Created: a.Created.Format("02.01 15:04"), + } + if a.Completed != nil { + row.Completed = a.Completed.Format("15:04") + } + out = append(out, row) + } + return out +} + func handleReminders(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI) { if core == nil { http.Error(w, "reminders disabled (no -core)", http.StatusServiceUnavailable) diff --git a/cmd/mavweb/notifications.html b/cmd/mavweb/notifications.html index 8d81cd9..4ebf044 100644 --- a/cmd/mavweb/notifications.html +++ b/cmd/mavweb/notifications.html @@ -14,5 +14,27 @@
+ every send is recorded before it leaves, so a failure is visible rather than silent. + all · + dropped · + failed · + pending · + unknown +
+{{if .Attempts}}| started | kind | rule | channel | status | finished |
|---|---|---|---|---|---|
| {{.Created}} | +{{.Kind}} | +{{.Target}} | +{{.Channel}} | +{{.Status}} | +{{.Completed}} | +