Fix scheduled series launch: read schedule from draft, apply to draft+published, check every minute, local-time picker; v2.8.1
Publish Container / build (push) Successful in 16m22s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-06 10:12:05 -04:00
1 parent 134a2c403e
commit 0ff0b32b97
4 files changed
+68 -43

No files matched your search

+56 -38
View File
@@ -53,18 +53,8 @@ function invokeWebhook(url, action) {
.catch(err => ({ ok: false, message: err instanceof Error ? err.message : `${action} webhook failed.` }))
}
// Applies a scheduled series transition if its triggersAt time has passed.
// Called on a server interval (see server.js). Safe to call if no transition is set.
export async function runScheduledSeriesTransition() {
const content = state.cachedSiteContent
if (!content) return
const sched = content.scheduledSeriesTransition
if (!sched || !sched.triggersAt || !sched.newTitle?.trim()) return
if (new Date(sched.triggersAt) > new Date()) return
console.log('[series-scheduler] Firing scheduled series transition to:', sched.newTitle)
// Builds the content that results from applying a scheduled series transition.
function applySeriesTransition(content, sched) {
const archivedBook = {
id: Date.now().toString(36),
title: content.seriesTitle ?? '',
@@ -73,40 +63,56 @@ export async function runScheduledSeriesTransition() {
season: (sched.newSeason ?? 2) - 1 || 1,
}
const startHerePatch = {}
if (sched.newStartHereHeading?.trim()) startHerePatch.startHereHeading = sched.newStartHereHeading.trim()
if (sched.newStartHereIntro?.trim()) startHerePatch.startHereIntro = sched.newStartHereIntro.trim()
if (sched.newStartHereStep1Title?.trim()) startHerePatch.startHereStep1Title = sched.newStartHereStep1Title.trim()
if (sched.newStartHereStep1Body?.trim()) startHerePatch.startHereStep1Body = sched.newStartHereStep1Body.trim()
if (sched.newStartHereStep1Cta?.trim()) startHerePatch.startHereStep1Cta = sched.newStartHereStep1Cta.trim()
if (sched.newStartHereStep2Title?.trim()) startHerePatch.startHereStep2Title = sched.newStartHereStep2Title.trim()
if (sched.newStartHereStep2Body?.trim()) startHerePatch.startHereStep2Body = sched.newStartHereStep2Body.trim()
if (sched.newStartHereStep2Cta?.trim()) startHerePatch.startHereStep2Cta = sched.newStartHereStep2Cta.trim()
if (sched.newStartHereStep3Title?.trim()) startHerePatch.startHereStep3Title = sched.newStartHereStep3Title.trim()
if (sched.newStartHereStep3Body?.trim()) startHerePatch.startHereStep3Body = sched.newStartHereStep3Body.trim()
if (sched.newStartHereStep3Cta?.trim()) startHerePatch.startHereStep3Cta = sched.newStartHereStep3Cta.trim()
const patch = {}
const copy = (from, to) => {
if (typeof sched[from] === 'string' && sched[from].trim()) patch[to] = sched[from].trim()
}
copy('newStartHereHeading', 'startHereHeading')
copy('newStartHereIntro', 'startHereIntro')
for (const n of [1, 2, 3]) {
copy(`newStartHereStep${n}Title`, `startHereStep${n}Title`)
copy(`newStartHereStep${n}Body`, `startHereStep${n}Body`)
copy(`newStartHereStep${n}Cta`, `startHereStep${n}Cta`)
}
copy('newStudyGuideTitle', 'studyGuideTitle')
copy('newStudyGuideDescription', 'studyGuideDescription')
copy('newStudyGuideDownloadUrl', 'studyGuideDownloadUrl')
copy('newStudyGuideUrl', 'studyGuideUrl')
copy('newStudyGuideAmazonButtonLabel', 'studyGuideAmazonButtonLabel')
copy('newStudyGuideAppleBooksUrl', 'studyGuideAppleBooksUrl')
copy('newStudyGuideAppleBooksLabel', 'studyGuideAppleBooksLabel')
const studyGuidePatch = {}
if (sched.newStudyGuideTitle?.trim()) studyGuidePatch.studyGuideTitle = sched.newStudyGuideTitle.trim()
if (sched.newStudyGuideDescription?.trim()) studyGuidePatch.studyGuideDescription = sched.newStudyGuideDescription.trim()
if (sched.newStudyGuideDownloadUrl?.trim()) studyGuidePatch.studyGuideDownloadUrl = sched.newStudyGuideDownloadUrl.trim()
if (sched.newStudyGuideUrl?.trim()) studyGuidePatch.studyGuideUrl = sched.newStudyGuideUrl.trim()
if (sched.newStudyGuideAmazonButtonLabel?.trim()) studyGuidePatch.studyGuideAmazonButtonLabel = sched.newStudyGuideAmazonButtonLabel.trim()
if (sched.newStudyGuideAppleBooksUrl?.trim()) studyGuidePatch.studyGuideAppleBooksUrl = sched.newStudyGuideAppleBooksUrl.trim()
if (sched.newStudyGuideAppleBooksLabel?.trim()) studyGuidePatch.studyGuideAppleBooksLabel = sched.newStudyGuideAppleBooksLabel.trim()
const updatedContent = sanitizeSiteContent({
const next = sanitizeSiteContent({
...content,
seriesTitle: sched.newTitle.trim(),
seriesDescription: sched.newDescription?.trim() ?? '',
seriesImageUrl: sched.newImageUrl?.trim() ?? content.seriesImageUrl ?? '',
seriesImageUrl: sched.newImageUrl?.trim() || content.seriesImageUrl || '',
seriesLabel: 'Now Playing',
podcastFeaturedLinks: [],
finishedBooks: [...(Array.isArray(content.finishedBooks) ? content.finishedBooks : []), archivedBook],
...startHerePatch,
...studyGuidePatch,
scheduledSeriesTransition: undefined,
...patch,
})
delete next.scheduledSeriesTransition
return { next, archivedBook }
}
// Applies a scheduled series transition if its triggersAt time has passed.
// The schedule is saved with the draft (Save Schedule), so look in the draft as
// well as published content. Applies to both so a later publish doesn't undo it.
// Called on a server interval (see server.js). Safe to call if no transition is set.
export async function runScheduledSeriesTransition() {
const published = state.cachedSiteContent
const draft = state.cachedDraftSiteContent
if (!published) return
const sched = draft?.scheduledSeriesTransition ?? published.scheduledSeriesTransition
if (!sched || !sched.triggersAt || !sched.newTitle?.trim()) return
const triggerTime = new Date(sched.triggersAt).getTime()
if (Number.isNaN(triggerTime) || triggerTime > Date.now()) return
console.log('[series-scheduler] Firing scheduled series transition to:', sched.newTitle)
const { next: updatedContent, archivedBook } = applySeriesTransition(published, sched)
const publishedAt = new Date().toISOString()
try {
@@ -117,6 +123,18 @@ export async function runScheduledSeriesTransition() {
console.log('[series-scheduler] Transition complete — archived:', archivedBook.title)
} catch (err) {
console.error('[series-scheduler] Failed to write transition:', err)
return
}
if (draft) {
try {
const { next: updatedDraft } = applySeriesTransition(draft, sched)
await writeFile(DRAFT_DATA_FILE, JSON.stringify({ siteContent: updatedDraft, updatedAt: publishedAt }, null, 2), 'utf8')
state.cachedDraftSiteContent = updatedDraft
state.publishState.draftUpdatedAt = publishedAt
} catch (err) {
console.error('[series-scheduler] Failed to update draft after transition:', err)
}
}
}