screentinker/server/services
ScreenTinker e9cf7801c3 Alert once per outage instead of once per dedup window
A display going offline is one event, but the alert loop re-evaluated every
still-offline device on each 60s tick, so the 2-hour dedup window re-qualified
the same outage over and over. One closed browser tab produced six "your
display is offline" mails overnight, and would have kept going to the 24h cap.

Repeat suppression now keys on devices.offline_alert_heartbeat: the heartbeat
value an alert was already sent for. A device can only come back by sending a
heartbeat, so a later outage always carries a later value and the marker
invalidates itself on recovery — no cleanup, no state to reset. Keeping it on
the row also fixes a second source of duplicates: the in-memory window used to
empty on restart and re-alert the whole offline fleet.

The window stays, doing the job it is actually suited to — bounding how often a
flapping device can alert — and is checked before the marker is written, so a
rate-limited alert is deferred rather than marked and dropped.

The backfill runs once, via schema_migrations rather than the migrations array:
statements there re-run every boot, and an IS NULL backfill would then swallow
the first alert of any outage beginning after the last restart. It marks
currently-offline devices as already-alerted so upgrading does not itself mail
about outages the owner has already been told about six times.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
2026-07-28 12:50:14 -05:00
..
activationNudge.js feat(signup): T+3 activation nudge for users with zero paired screens 2026-05-30 20:28:24 -05:00
activity.js Merge branch 'fix/recovery-grants' into release/auth-campaign 2026-07-26 16:22:59 -05:00
agency-digest.js feat(api): batched email digest for agency uploads (#73) 2026-06-14 13:59:37 -05:00
alerts.js Alert once per outage instead of once per dedup window 2026-07-28 12:50:14 -05:00
content-expiry.js feat(device-owner): tier foundation + QR provisioning + content-expiry & device enhancements (#168) 2026-07-12 19:41:07 -05:00
email.js feat(email): SMTP transport as an alternative to Microsoft Graph [#173] (#179) 2026-07-13 15:56:22 -05:00
heartbeat.js feat(diagnostics): device incident log — offline cause, network-vs-reboot, display-sleep (#175) 2026-07-13 11:26:04 -05:00
loop-lag.js fix(#146) P3.7: coalescer carries the PEAK numeric over the window 2026-06-30 22:10:45 -05:00
scheduler.js feat(device-owner): tier foundation + QR provisioning + content-expiry & device enhancements (#168) 2026-07-12 19:41:07 -05:00
signupEmails.js feat(auth): self-service password reset 2026-07-27 11:19:39 -05:00