From 30a7e8a6cbfbc69dc91c155a1053fdc260f5674d Mon Sep 17 00:00:00 2001 From: nmemmert Date: Mon, 19 Oct 2026 16:53:03 -0400 Subject: [PATCH] Study: lesson reminders on by default for new accounts; one-time backfill turns them on for existing accounts; v2.46.0 Co-Authored-By: Claude Sonnet 5.5 --- package.json | 2 +- server.js | 6 ++++++ server/data.js | 1 + server/routes/study-auth.js | 3 ++- server/study-helpers.js | 17 +++++++++++++++++ 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ef38130..18828bb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "siteforge", "private": true, - "version": "2.45.3", + "version": "2.46.0", "type": "module", "scripts": { "dev": "vite", diff --git a/server.js b/server.js index 233fad2..5788380 100644 --- a/server.js +++ b/server.js @@ -44,6 +44,7 @@ import { scheduleReengagementEmails, scheduleWeeklyDigestEmails, backfillCompletedStudies, + backfillReminderDefault, } from './server/study-helpers.js' import { recordVisitor } from './server/routes/analytics.js' @@ -210,6 +211,11 @@ Promise.all([ .catch(err => { console.error('[study] completed-study backfill failed:', err) }) + .then(() => backfillReminderDefault()) + .then(updated => { if (updated > 0) console.log(`[study] turned lesson reminders on for ${updated} existing accounts`) }) + .catch(err => { + console.error('[study] reminder-default backfill failed:', err) + }) .finally(() => { createBackupSnapshot('startup').catch(() => {}) setInterval(() => { diff --git a/server/data.js b/server/data.js index 586f604..394213c 100644 --- a/server/data.js +++ b/server/data.js @@ -1421,6 +1421,7 @@ export function sanitizeStudyUsers(value) { remindersPausedUntil: typeof item?.remindersPausedUntil === 'string' ? item.remindersPausedUntil : null, reminderFrequency: item?.reminderFrequency === 'weekly' ? 'weekly' : 'immediate', weeklyDigestSentAt: typeof item?.weeklyDigestSentAt === 'string' ? item.weeklyDigestSentAt : null, + remindersDefaultedAt: typeof item?.remindersDefaultedAt === 'string' ? item.remindersDefaultedAt : null, }) } return out.slice(0, MAX_STUDY_USERS) diff --git a/server/routes/study-auth.js b/server/routes/study-auth.js index 8a09187..1ecbaf3 100644 --- a/server/routes/study-auth.js +++ b/server/routes/study-auth.js @@ -117,7 +117,8 @@ export function register(app) { passwordHash: hashStudyPassword(password), displayName, subscribeNewsletter: subscribe, - studyRemindersEnabled: false, + studyRemindersEnabled: true, + remindersDefaultedAt: now, pendingEmailChange: null, emailVerified: false, pendingEmailVerification: { diff --git a/server/study-helpers.js b/server/study-helpers.js index 1a62ea0..b61e6d4 100644 --- a/server/study-helpers.js +++ b/server/study-helpers.js @@ -736,6 +736,23 @@ export function extractResendMessageId(result) { return '' } +// One-off pass: lesson reminders are now on by default, so switch them on for accounts that +// predate the default. Each account is marked as it is handled, so a later opt-out sticks. +export async function backfillReminderDefault() { + const now = new Date().toISOString() + let updated = 0 + for (const user of state.studyUsers) { + if (user.remindersDefaultedAt) continue + if (user.studyRemindersEnabled !== true) { + user.studyRemindersEnabled = true + updated++ + } + user.remindersDefaultedAt = now + } + if (state.studyUsers.some(user => user.remindersDefaultedAt === now)) queueStudyUsersWrite() + return updated +} + // ── Study reminder scheduler ─────────────────────────────────────────────── export async function scheduleStudyReminders(sendStudyReminderEmail) {