- Notes: two-pane reader with searchable lesson list and snippets, track chips, friendlier empty states
- Reflection questions: numbered question cards, answered progress bar, unsaved-changes status, sticky save bar, share-to-discussion only when discussion is open
- Community: compact header, lesson browser sidebar with post counts (select on mobile), guidelines card, card-style posts
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A finished study is just no longer the main study, so students who take it later still need discussion.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Community posts, replies, likes and lesson comments are refused (403) for finished and planned studies
- Reading a closed study's community/comments returns an empty list with closed:true
- Discussion hub lists only active studies; community page shows a closed notice
- Lesson page hides Lesson Discussion, 'Discuss this lesson' and 'Go to Community' for inactive studies; Community buttons hidden on study/dashboard pages
- Tests for isStudyDiscussionOpen
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Completion is stored on the user (completedStudySlugs, sticky) when every lesson is done; backfilled at startup
- Lessons, notes, progress, quiz, community and certificate stay open for completed studies after leaving the enrollment
- Dashboard/My Account list completed studies with Review Lessons and certificate access
- Study page shows a completed banner
- Lesson-release reminders, weekly digest and re-engagement emails skip finished studies
- Fix: streak fields (current/longest/last studied) and firstCompletionAt were dropped on every restart when users were loaded
- Tests for completion/access/re-engagement
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Certificate URLs in the account overview and the issuance email pointed at /study/certificate/:token,
which the router treated as a lesson page; now /certificate/:token (old links redirect)
- My Account Certificates card lists every enrolled study: progress, Generate, View, Copy link, Download PNG, Share
- Certificate canvas used a non-existent /api/study-account route and always printed 'Student'; now reads the account name
- Panel no longer collapses after generating
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Atomic temp-file + rename for all JSON data writes (no truncated files on crash)
- /api/bible-passage: rate limit, range caps (60 verses), bounded cache
- Admin webhooks: https-only, block private/internal/Tailscale addresses (checked at registration and delivery), no redirects
- Resend webhook token compare is timing-safe
- DOMPurify on rendered study notes and broadcast preview
- CI runs lint + tests before build
- Permissions-Policy and HSTS (production) headers; JSON error handler for /api
- Tests for URL safety and atomic writes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Second series gets its own guide (Resources page card, download page, admin fields)
- When a series ends (scheduled or manual) its guide moves onto its Finished Book, which shows a guide button; the main series' guide is cleared unless the launch supplies a new one
- Lane B launch schedule can supply the incoming series' guide
- Download lookup covers second-series and finished-book guides; guide URLs are sanitized on save and on use
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Scripts keyed "<season>-<episode>" (legacy ones migrate on first start; unseasoned ones keep their plain key)
- Season backfill handles episode numbers shared across seasons (title match fallback)
- Fix: "Backfill seasons from RSS" was swallowed by the upload route
- Global search labels script results with the title of the series playing now (both lanes), not just finished books
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Shared tab bar; chosen tab is remembered per page
- Current Series: Main Series / Second Series / Launch: Main / Launch: Second
- Resources, Footer & Global, SEO, Legal, Start Here, Custom Links: former "Jump to" scroll links are now tabs
- Dashboard: Overview / Recent Activity / Study Users
- Studies editor: Lessons / Study Settings
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Highlights get a Series field (Lane A / Lane B / General); legacy highlights count as Lane A
- Episodes page groups highlights under each series' title
- Ending a series (scheduled or manual) moves only that series' highlights onto its Finished Book page; other lane and General stay
- Archived highlights keep working: detail pages, sitemap, scripture index and search include them
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- 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>
- Broadcast Email creates and sends a Resend Broadcast to the Resend segment (unsubscribe handling, reports, New Episode topic, scheduling, test send)
- Subscribers: import chosen Resend-only contacts into the app list
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Subscribers: compare in-app list with Resend audience/segment, with one-click fixes
- syncContactToResend no longer resets unsubscribed on existing contacts
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Asset Manager: search/filter/sort, list+grid views, rename, move between R2 folders, tag chips, bulk actions, used-in counts; fix R2 delete lookup
- Inbox: readable wrapped rows with subject/preview, real read state, type filters, bulk actions, mojibake repair
- Broadcast: pick a Resend template to fill subject and body
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Resend email HTML uses align="left" on its outer wrapper table and a
white background, both fine inside a narrow inbox pane but broken in a
full-width browser tab: the content stuck to the left edge with a large
white field to its right. Override with CSS that centers the content and
matches the site's dark background.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Web-hosted copies of episode newsletters, published from Resend broadcasts
tagged with the "New Episode" topic. Gives Facebook (and anyone else) a
proper link to share instead of raw HTML email, and makes issues findable
via site search and Google.
- Admin "Newsletter Archive" tab lists sent Resend broadcasts, flags ones
tagged "New Episode", and publishes them with one click (pulls the HTML
straight from Resend's API)
- Public /newsletter archive index and /newsletter/:slug issue pages,
server-rendered with per-issue Open Graph tags for social previews
- Unsubscribe merge-tag stripped from the web copy; archive + subscribe
links appended
- Issues added to sitemap.xml and the on-site search index
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Self-heal consent desync: if the server's consent cookie and the
client's localStorage flag fall out of sync, the client now notices
and stops silently sending events into a void.
- Stop double-counting hard page loads in hit/visitor stats.
- Flush pending disk writes on SIGTERM/SIGINT so redeploys don't drop
the last few events.
- Log rate-limiter drops once per window (diagnoses proxy misconfig).
- Widen the bot-detection allowlist to avoid flagging real browsers
(Chrome-iOS, Samsung Browser, etc.) as bots.
- Stop merging distinct visitors who share an IP when a real session
cookie is already present — only fall back to IP correlation when no
cookie was sent at all.
- Include analyticsEvents/episodePlays in backup snapshots and restore
them, so a restore no longer wipes engagement analytics.
- Regroup admin dashboard KPI cards by time scope (range vs all-time)
and remove a duplicated Top Countries/States block.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>