feat(11-02): Task 3 — all-day 9 AM-local fire branch + dedup prune fix (NOTIF-06)
- Add all-day 9 AM tests: 0-lead fires at EDT alert UTC, not midnight - Add 1440-lead (day-before) and 10080-lead (7-day-before) tests - Add all-day dedup test: same uid:dtstartMs fires once across ticks - Fix all-day prune bug: store start-of-next-day as pruneMs instead of UTC midnight (which was always <= now by fire time, causing immediate prune) - Separate dtstartMs (dedup key component) from pruneMs (map cleanup value) - 28/28 tests GREEN; full API suite 314/314; tsc --noEmit clean
This commit is contained in:
@@ -183,7 +183,8 @@ export async function runReminderCheck(now = new Date()): Promise<void> {
|
||||
{
|
||||
uid: string;
|
||||
title: string | null;
|
||||
dtstartMs: number;
|
||||
dtstartMs: number; // key component — used for compound dedup key
|
||||
pruneMs: number; // when to prune: dtstartUtc for timed; end-of-event-date for all-day
|
||||
reminderLeadMinutes: number;
|
||||
dateStr: string;
|
||||
subs: SubRow[];
|
||||
@@ -211,6 +212,7 @@ export async function runReminderCheck(now = new Date()): Promise<void> {
|
||||
uid: row.uid,
|
||||
title: row.title ?? null,
|
||||
dtstartMs,
|
||||
pruneMs: dtstartMs, // timed: prune when event has started (dtstartUtc <= now)
|
||||
reminderLeadMinutes: lead,
|
||||
dateStr: yyyyMmDd(dtstartUtc),
|
||||
subs: [],
|
||||
@@ -248,11 +250,17 @@ export async function runReminderCheck(now = new Date()): Promise<void> {
|
||||
const dtstartMs = Date.UTC(y, m - 1, d); // UTC midnight of event date
|
||||
const dedupKey = `${row.uid}:${dtstartMs}`;
|
||||
|
||||
// Prune value for all-day events: start-of-next-day UTC.
|
||||
// Using UTC midnight of dtstartDate would be pruned immediately (it's in the past by 9 AM);
|
||||
// using end-of-event-day ensures the entry persists through the full fire window.
|
||||
const pruneMs = Date.UTC(y, m - 1, d + 1); // UTC midnight of event date + 1 day
|
||||
|
||||
if (!byKey.has(dedupKey)) {
|
||||
byKey.set(dedupKey, {
|
||||
uid: row.uid,
|
||||
title: row.title ?? null,
|
||||
dtstartMs,
|
||||
pruneMs,
|
||||
reminderLeadMinutes: lead,
|
||||
dateStr: dtstartDate,
|
||||
subs: [],
|
||||
@@ -302,7 +310,10 @@ export async function runReminderCheck(now = new Date()): Promise<void> {
|
||||
// WR-01: mark sent AFTER all dispatches have been attempted. Pre-marking before
|
||||
// dispatch prevents retry when dispatchPush throws — at-least-once delivery
|
||||
// requires not pre-marking.
|
||||
sentReminders.set(dedupKey, event.dtstartMs);
|
||||
// Store pruneMs (not dtstartMs) so the CR-01 prune removes the entry at the right time:
|
||||
// - timed: pruneMs = dtstartUtc → pruned when event starts
|
||||
// - all-day: pruneMs = start-of-next-day → pruned after the event date
|
||||
sentReminders.set(dedupKey, event.pruneMs);
|
||||
} catch (err) {
|
||||
// Per-event error isolation (T-05-18): one bad event never aborts remaining events.
|
||||
console.error(
|
||||
|
||||
Reference in New Issue
Block a user