Commit graph

1 commit

Author SHA1 Message Date
Matt Johnson
c0572b04a1 feat: user-crafted scheduled announcements (custom_announcements)
Free-text broadcasts the owner types straight into the GUI on a clock-slot
schedule (daily / interval_days / weekly / monthly) -- no placeholders, no
data sources, no templating, no SQL from the user.

- v30 migration: custom_announcements table (own explicit channel list per
  row, new rows start disabled).
- CustomAnnouncementScheduler (notifications/scheduled/custom_announcements.py):
  60s tick modelled on ReminderScheduler; monthly day-of-month clamped via
  calendar.monthrange; restart-safe dedup keyed on the local calendar date
  of last_sent_at; spacing_seconds roll-call pacing between announcements
  firing in the same tick.
- Dispatcher.dispatch_scheduled_custom_broadcast(): delivers to the
  announcement's own channel list (no toggle/region_routes matrix), one
  mesh_broadcasts_out audit row per target, cold-start grace.
- New announcement_routes.py router: GET/POST/PUT/DELETE /api/announcements
  + POST .../preview (wire text + char/byte count, never sends). No
  send-now endpoint anywhere.
- Wired into notifications/pipeline/__init__.py (alongside ReminderScheduler)
  and dashboard/server.py; single_packet_max_chars runtime override added
  in main.py's budget list.

57 new tests (recurrence kinds, Feb-29/28 clamp, restart-safe dedup, pacing,
budget truncation, full input validation, multi-target audit rows, no
send-anywhere guarantee). Full suite: 2090 passed, 0 failed (up from 2033
baseline), 9 pre-existing warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 18:20:59 +00:00