loop: collapse stale-reminder burst into single digest notification

When the daemon starts after being offline, multiple due reminders
would fire simultaneously as separate notifications. Now a single
digest reminder is dispatched instead, summarizing all pending items.

- New collapseReminders() helper in gather.go: if 2+ reminders are
  due, marks originals as fired and returns one synthetic reminder
  (ID=0) with a combined JSON payload.
- Dispatcher skips MarkReminder for ID=0 (synthetic digest).
- All loop and delivery tests pass.
This commit is contained in:
kami
2026-07-05 02:15:07 +04:00
parent f8ba396fec
commit ca081ce84d
2 changed files with 66 additions and 2 deletions
+7 -2
View File
@@ -183,8 +183,13 @@ func (d *Dispatcher) DispatchReminder(ctx context.Context, pr PhrasedReminder, n
out = append(out, Dispatch{Sendable: s})
}
if d.cfg.Reminders != nil && len(out) > 0 {
if err := d.cfg.Reminders.MarkReminder(ctx, rd.Reminder.ID, "fired"); err != nil {
return out, fmt.Errorf("mark reminder fired: %w", err)
// ID=0 is a synthetic digest reminder; it's not in the DB so
// MarkReminder would fail with ErrReminderNotFound. The originals
// were already marked fired by collapseReminders in gather.go.
if rd.Reminder.ID != 0 {
if err := d.cfg.Reminders.MarkReminder(ctx, rd.Reminder.ID, "fired"); err != nil {
return out, fmt.Errorf("mark reminder fired: %w", err)
}
}
}
return out, nil