Two concurrent series lanes, Complete studies, self-paced finished books; v2.9.0
Publish Container / build (push) Successful in 17m50s

- Study status 'complete' (manual or auto when all lessons released); stays enrollable and self-paced (no sequential lock, Resume button, Finished Studies hub section)
- Studies get lane (A/B) and expected start date; planned studies show "Starts <date>"; hub shows next lesson date instead of hardcoded pace
- Second concurrent series (homepage card, admin panel) with its own scheduled launch; transitions are per lane and flip the ending study to Complete and the incoming study to Active
- Finished books link to their study: "Study This Book" on playlist page, audio-only link from hub (playlist stays public, no account)
- Admin: bulk release scheduler (every N days), same-day collision warning, linked-study selects
- Weekly digest no longer dumps a finished study's backlog on late enrollees
- CI: build and tag the container as :beta on pushes to the beta branch

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-08 10:14:02 -04:00
1 parent 2d8e4be5ea
commit ddcaec0ddf
12 files changed
+628 -105

No files matched your search

+58 -29
View File
@@ -14,7 +14,13 @@ export interface StudyProgram {
showOnHomepage?: boolean
showNewTag?: boolean
newTagLabel?: string
status: 'active' | 'planned'
// 'complete' = finished and self-paced (still open for enrollment). A study whose lessons
// have all been released is treated as complete even if left as 'active'.
status: 'active' | 'planned' | 'complete'
// Which of the two concurrent release lanes this study airs in (A = primary series, B = second series)
lane?: '' | 'A' | 'B'
// When a planned study is expected to start (ISO) — shown as "Starts <date>" on the hub
startsAt?: string
difficulty: 'beginner' | 'intermediate' | 'advanced'
estimatedHours: number
completionBadge: string
@@ -85,16 +91,60 @@ export interface Testimonial {
source?: string
}
export interface SecondSeries {
label: string
title: string
description: string
imageUrl: string
listenUrl: string
listenBtnLabel: string
studySlug?: string
season?: number
}
export interface FinishedBook {
id: string
title: string
description: string
imageUrl: string
season: number
// The study that goes with this book, so the card can offer both "Listen" and "Study"
studySlug?: string
downloadUrl?: string
downloadQrUrl?: string
}
export interface ScheduledSeriesTransition {
triggersAt: string // ISO datetime — server fires at or after this time (checks hourly)
// New series card
newTitle: string
newDescription?: string
newImageUrl?: string
newSeason: number
// Study that opens (planned → active) when this series launches; the ending series' study becomes Complete
newStudySlug?: string
// Start Here section (optional)
newStartHereHeading?: string
newStartHereIntro?: string
newStartHereStep1Title?: string
newStartHereStep1Body?: string
newStartHereStep1Cta?: string
newStartHereStep2Title?: string
newStartHereStep2Body?: string
newStartHereStep2Cta?: string
newStartHereStep3Title?: string
newStartHereStep3Body?: string
newStartHereStep3Cta?: string
// Companion Study Guide / Featured Download (optional)
newStudyGuideTitle?: string
newStudyGuideDescription?: string
newStudyGuideDownloadUrl?: string
newStudyGuideUrl?: string
newStudyGuideAmazonButtonLabel?: string
newStudyGuideAppleBooksUrl?: string
newStudyGuideAppleBooksLabel?: string
}
export interface PodcastHost {
id: string
name: string
@@ -184,6 +234,10 @@ export interface SiteContent {
seriesImageUrl: string
seriesListenUrl: string
seriesListenBtnLabel: string
seriesStudySlug?: string
seriesSeason?: number
// Optional second concurrent series (Lane B). Same card as the primary series.
secondSeries?: SecondSeries
seriesDownloadsBtnLabel: string
// ── Downloads ──
studyGuideTitle: string
@@ -334,34 +388,9 @@ export interface SiteContent {
// ── Scheduled Series Transition ──
// All fields fire together at triggersAt. Start Here and Study Guide fields are optional —
// omit them to leave those sections unchanged when the series transitions.
scheduledSeriesTransition?: {
triggersAt: string // ISO datetime — server fires at or after this time (checks hourly)
// New series card
newTitle: string
newDescription?: string
newImageUrl?: string
newSeason: number
// Start Here section (optional)
newStartHereHeading?: string
newStartHereIntro?: string
newStartHereStep1Title?: string
newStartHereStep1Body?: string
newStartHereStep1Cta?: string
newStartHereStep2Title?: string
newStartHereStep2Body?: string
newStartHereStep2Cta?: string
newStartHereStep3Title?: string
newStartHereStep3Body?: string
newStartHereStep3Cta?: string
// Companion Study Guide / Featured Download (optional)
newStudyGuideTitle?: string
newStudyGuideDescription?: string
newStudyGuideDownloadUrl?: string
newStudyGuideUrl?: string
newStudyGuideAmazonButtonLabel?: string
newStudyGuideAppleBooksUrl?: string
newStudyGuideAppleBooksLabel?: string
}
scheduledSeriesTransition?: ScheduledSeriesTransition
// Same, for the second series lane
scheduledSecondSeriesTransition?: ScheduledSeriesTransition
// ── Testimonials ──
testimonials: Testimonial[]
// ── Nuggets (sub-show) ──