@bobfrankston/rmfmail 1.0.667 → 1.0.668

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -696,7 +696,7 @@ export class MailxService {
696
696
  buttons: string[];
697
697
  size?: { width: number; height: number };
698
698
  pos?: { x: number; y: number };
699
- }): Promise<{ button: string; reason?: string }> {
699
+ }): Promise<{ button: string; form?: any; reason?: string }> {
700
700
  if (!this.popupFn) return { button: "", reason: "no popup host" };
701
701
  try {
702
702
  const r = await this.popupFn({
@@ -723,7 +723,13 @@ export class MailxService {
723
723
  // on graceful shutdown.
724
724
  detach: false,
725
725
  });
726
- return { button: r.button || (r.dismissed ? "dismissed" : r.closed ? "closed" : "") };
726
+ return {
727
+ button: r.button || (r.dismissed ? "dismissed" : r.closed ? "closed" : ""),
728
+ // Pass msger's `form` field through. Used by the calendar
729
+ // reminder popup to send back arbitrary snooze durations
730
+ // (e.g. { minutes: 75 }) — see alarms.ts.
731
+ form: (r as any).form,
732
+ };
727
733
  } catch (e: any) {
728
734
  console.error(` [reminder] popup failed: ${e.message}`);
729
735
  return { button: "", reason: e.message };
@@ -1116,7 +1122,15 @@ export class MailxService {
1116
1122
  .catch(e => this.handleGoogleRefreshError("calendar", e));
1117
1123
  }
1118
1124
  }
1119
- return this.localStore.getCalendarEvents(acctId, fromMs, toMs);
1125
+ const rows = this.localStore.getCalendarEvents(acctId, fromMs, toMs);
1126
+ // Diagnostic: separate counts for plain / recurring / holiday rows
1127
+ // so log inspection reveals whether the issue is fetch-side
1128
+ // (refresh didn't write them) vs read/render-side (they're in DB
1129
+ // but not appearing in the UI).
1130
+ const recurring = rows.filter(r => r.recurringEventId).length;
1131
+ const holidays = rows.filter(r => r.isHoliday).length;
1132
+ console.log(` [calendar] getCalendarEvents → ${rows.length} rows (${recurring} recurring, ${holidays} holiday) for window ${new Date(fromMs).toISOString().slice(0, 10)}..${new Date(toMs).toISOString().slice(0, 10)}`);
1133
+ return rows;
1120
1134
  }
1121
1135
 
1122
1136
  /** Returns true if the feature is currently in a quota-exceeded cooldown. */
@@ -1177,24 +1191,36 @@ export class MailxService {
1177
1191
  const tp = await this.primaryTokenProvider("calendar");
1178
1192
  const events = await gsync.listCalendarEvents(tp, fromMs, toMs);
1179
1193
  console.log(` [calendar] pulled ${events.length} events from ${new Date(fromMs).toISOString().slice(0, 10)} to ${new Date(toMs).toISOString().slice(0, 10)}`);
1180
- // Q131: optionally pull from Google's public holiday calendar in the
1181
- // same window. The calendarId is the standard US holidays one; future
1182
- // refinement: per-locale selection via a settings field.
1194
+ // Holiday calendars (Q131 + Bob 2026-05-11 second-toggle request).
1195
+ // Each enabled toggle pulls a separate Google public calendar; rows
1196
+ // are tagged with the source `calendar_id` so toggling one off only
1197
+ // purges that source's rows, not the others. Future generalization
1198
+ // to arbitrary user-supplied calendars is filed as Q140.
1183
1199
  const settings = loadSettings() as any;
1184
- const showHolidays = !!settings?.calendar?.showHolidays;
1185
- let holidayEvents: any[] = [];
1186
- if (showHolidays) {
1200
+ const HOLIDAY_SOURCES: Array<{ enabled: boolean; calId: string; label: string }> = [
1201
+ {
1202
+ enabled: !!settings?.calendar?.showHolidays,
1203
+ calId: "en.usa#holiday@group.v.calendar.google.com",
1204
+ label: "US",
1205
+ },
1206
+ {
1207
+ enabled: !!settings?.calendar?.showJewishHolidays,
1208
+ calId: "en.jewish#holiday@group.v.calendar.google.com",
1209
+ label: "Jewish",
1210
+ },
1211
+ ];
1212
+ const holidayBundles: Array<{ calId: string; events: any[] }> = [];
1213
+ for (const src of HOLIDAY_SOURCES) {
1214
+ if (!src.enabled) continue;
1187
1215
  try {
1188
- holidayEvents = await gsync.listCalendarEvents(tp, fromMs, toMs,
1189
- "en.usa#holiday@group.v.calendar.google.com");
1190
- console.log(` [calendar] pulled ${holidayEvents.length} holiday events`);
1216
+ const ev = await gsync.listCalendarEvents(tp, fromMs, toMs, src.calId);
1217
+ console.log(` [calendar] pulled ${ev.length} ${src.label} holiday events`);
1218
+ holidayBundles.push({ calId: src.calId, events: ev });
1191
1219
  } catch (e: any) {
1192
- // Read-only public calendar most failures here are quota
1193
- // or network; the user's primary calendar already loaded,
1194
- // so we don't want to block render on holidays.
1195
- console.warn(` [calendar] holiday pull failed: ${e?.message || e}`);
1220
+ console.warn(` [calendar] ${src.label} holiday pull failed: ${e?.message || e}`);
1196
1221
  }
1197
1222
  }
1223
+ const enabledHolidayCalIds = new Set(HOLIDAY_SOURCES.filter(s => s.enabled).map(s => s.calId));
1198
1224
  let changed = false;
1199
1225
  // Upsert by provider_id — dedup globally, not just within the window,
1200
1226
  // so an event whose start moves outside the prior query range doesn't
@@ -1208,35 +1234,39 @@ export class MailxService {
1208
1234
  this.db.upsertCalendarEvent({ uuid: existing?.uuid, ...local, isHoliday: false });
1209
1235
  changed = true;
1210
1236
  }
1211
- for (const ev of holidayEvents) {
1212
- const local = gsync.calendarEventToLocal(ev, accountId);
1213
- seenProviderIds.add(ev.id);
1214
- const existing = this.db.getCalendarEventByProviderId(accountId, ev.id);
1215
- if (existing && calendarRowEquals(existing, local) && existing.isHoliday) continue;
1216
- this.db.upsertCalendarEvent({ uuid: existing?.uuid, ...local, isHoliday: true });
1217
- changed = true;
1237
+ for (const bundle of holidayBundles) {
1238
+ for (const ev of bundle.events) {
1239
+ const baseLocal = gsync.calendarEventToLocal(ev, accountId);
1240
+ // Override calendarId so reconcile (below) can tell which
1241
+ // holiday source this row belongs to — toggling one source
1242
+ // off must not purge rows from another active source.
1243
+ const local = { ...baseLocal, calendarId: bundle.calId };
1244
+ seenProviderIds.add(ev.id);
1245
+ const existing = this.db.getCalendarEventByProviderId(accountId, ev.id);
1246
+ if (existing && calendarRowEquals(existing, local) && existing.isHoliday) continue;
1247
+ this.db.upsertCalendarEvent({ uuid: existing?.uuid, ...local, isHoliday: true });
1248
+ changed = true;
1249
+ }
1218
1250
  }
1219
1251
  // Server-side delete reconciliation: any local non-dirty row whose
1220
1252
  // start falls in the queried window and whose provider_id wasn't
1221
- // returned must have been deleted on Google. Purge it. Dirty rows
1222
- // are local-only edits that haven't been pushed yet don't touch.
1223
- // When holidays are toggled off, skip purging holiday rows from the
1224
- // primary-calendar reconcile pass so they don't all vanish before
1225
- // the next toggle-on refresh re-pulls them.
1253
+ // returned must have been deleted on Google. Holiday rows are
1254
+ // partitioned by `calendarId`: only purge a holiday row when its
1255
+ // source calendar is currently disabled. Sources that are enabled
1256
+ // but returned nothing for this row keep the row (transient fail).
1226
1257
  const localWindow = this.db.getCalendarEvents(accountId, fromMs, toMs);
1227
1258
  for (const row of localWindow) {
1228
1259
  if (!row.providerId) continue; // local-only, never pushed
1229
1260
  if (row.dirty) continue; // locally edited, pending push
1230
1261
  if (seenProviderIds.has(row.providerId)) continue;
1231
- if (row.isHoliday && !showHolidays) {
1232
- // Holiday no longer wanted — purge so it disappears from view.
1233
- this.db.purgeCalendarEvent(row.uuid);
1234
- changed = true;
1235
- continue;
1236
- }
1237
- if (row.isHoliday && showHolidays) {
1238
- // Holiday wanted but missing in this pull — the holiday
1239
- // calendar fetch may have failed transiently. Keep it.
1262
+ if (row.isHoliday) {
1263
+ const fromActiveSource = enabledHolidayCalIds.has(row.calendarId || "");
1264
+ if (!fromActiveSource) {
1265
+ // Source toggled off — purge.
1266
+ this.db.purgeCalendarEvent(row.uuid);
1267
+ changed = true;
1268
+ }
1269
+ // Source enabled but row missing transient; keep.
1240
1270
  continue;
1241
1271
  }
1242
1272
  this.db.purgeCalendarEvent(row.uuid);