docker-compose.yml was still pointing to the old GHCR image
(ghcr.io/nmemmert/siteforge:latest) after CI was switched to push
to git.necloud.us/nate/siteforge:latest, so servers were pulling
a stale build that predated the download/header changes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace hero study guide form with PageBanner + featured download card in library
- Add PageBanner component used consistently across all interior pages (Downloads, Episodes, About, Contact, Q&A, Finished Books)
- Remove duplicate headings/eyebrows that conflicted with banner titles on About, Contact, Q&A, Finished Books pages
- Compact download card sizing (padding, border-radius, image size)
- Add Books & Resources tabbed section with fallback to TruthForLife widget
- Fix server sanitizer to pass through 'resources' placement items without URL
- Add featured-badge and featured card styling for primary guide
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 'books' and 'recommended' to the placement whitelist in sanitizeCustomLinks
- Allow items with no url when placement is books/recommended (they use amazonUrl)
- Preserve linkUrl and linkLabel through sanitizeCustomBlocks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CustomBlock: add linkUrl/linkLabel fields for a styled button below the body
- Body text auto-linkifies raw URLs into gold anchor tags
- New 'books' placement: Reading List grid on Downloads page (cover, desc, Amazon btn)
- New 'recommended' placement: editable Recommended Resources section on Downloads
page; falls back to Truth For Life widget when empty
- Admin: Books and Recommended Resources panels added to Content nav
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a one-click "Fetch from BSB" button in the lesson passage text field
that calls the BSB API and overwrites the field with verse-numbered text.
Also fixes reference parsing to tolerate leading book abbreviations like
t:1-2 or col:1-2 by stripping letters before the chapter:verse pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds /api/bible-passage server route that proxies the Berean Standard Bible
from Bolls.life per-verse, with in-memory caching. Study lesson scripture
block now displays live BSB text instead of static passageText. Adds
bibleBook field to StudyProgram so admins can set the book name explicitly
when the study slug doesn't match (e.g. a study titled "ephesians-part-2").
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Splits the analytics panel into Traffic / Engagement / System tabs so
episode plays, engagement metrics, and contact/study data are no longer
buried under the visitor log.
Fixes a bug where topCountries, topStates, topCities, device breakdown,
and referrers were computed from only the 100 most-recent visitor rows
instead of all rows in the selected range — causing older geographic data
(e.g. visits from India, Texas, Georgia) to silently disappear from the
aggregate totals.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces outbound-only tracking with a useLinkClickTracking hook that
captures every <a> click on the site. Stores as link_click events keyed
by destination URL, with an internal flag. Admin analytics page gains a
Link Clicks table sorted by count, showing type (internal/external).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The SW's navigateFallback was using a denylist, so any path not explicitly
excluded (e.g. /spotify, /apple, /amazon, custom admin redirects) got served
the cached index.html instead of reaching the server's redirect handler.
Switch to navigateFallbackAllowlist: the SW only intercepts known SPA routes;
all other paths hit the network so server-side 301/302 redirects work correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Admin can set birthday (month/day) per student in Study Users panel
- Summer birthdays (Jun–Aug) prompt admin to set an alternate school-year date
- 🎂 badge on student card header during birthday week
- Dismissible happy birthday banner shown to student on their study hub
- Admin receives email notification on the celebration day, once per year
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
package.json was missing from the runtime stage, causing readPackageVersion()
to throw and fall back to 'unknown'. The git commit hash (from COMMIT_SHA env)
was the only identifier shown.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Day thresholds and per-tier subject/body/CTA are now editable in the
Email Templates panel. Tier keys are position-based (t1/t2/t3) so
changing day values never re-sends emails already fired.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sends 7/14/30-day inactivity emails to study users who haven't logged in,
with one-click HMAC-signed unsubscribe and automatic re-arm on next login.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inbound emails from some clients (Outlook, mobile) send htmlBody as
base64. Decode it on the server before storing (future emails) and on
the client before passing to the iframe srcDoc (existing stored emails).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Focus effect now only fires when compose/reply first opens, not on every
keystroke — typing in the To field no longer jumps focus to the body
- 👥 button next to To field opens a searchable contact picker dropdown;
selecting a contact fills in email and name instantly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Four event types on the calendar: General, Recording, Social, Task — each color-coded
- + Event button opens form with type picker, title, date, notes, reminder
- Task events have a Mark Complete toggle; completed events show strikethrough
- Edit/delete popover for each event
- Email reminders work for calendar events same as episodes
- Added Email/Calendar nav links to /contacts header
- Added Email link to /calendar header (already had Admin)
- /email already links to Contacts and Calendar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Per-episode reminder select (1d/2d/3d/1wk/2wk before publish date)
- Server checks hourly; fires Resend email to admin on reminder day
- reminderSentAt persisted so reminders don't re-fire; cleared if date or setting changes
- Bell indicator on calendar chips; sent confirmation in edit popover
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Run npm ci on the native build platform using --platform=\$BUILDPLATFORM in a
deps stage, then copy node_modules to the runtime image. Avoids running Node.js
under QEMU emulation, which crashes with SIGILL on arm64 cross-builds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extracts the email inbox from /admin and builds it as a full-viewport
email client at /email with the same admin auth. Key improvements over
the old embedded panel:
- Full-height split-pane layout (sidebar list + detail pane)
- Subject line extracted and shown separately for inbound emails
- Inline reply composer inside the detail pane (no more page-jump)
- Compose-new button for outbound messages to arbitrary addresses
- Search/filter across name, email, subject, and body
- Unread dot indicators with localStorage tracking (marks read on open)
- Keyboard navigation: ↑/↓ or j/k to move, r to reply, e to archive, Esc to close
- Source badges (contact-form / inbound-email) on list items
- Sent history and reply templates accessible via footer drawers
- 30-second polling for new messages
- New POST /api/admin-email/compose server endpoint for outbound sends
- Dashboard card "Open Inbox" now links to /email
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Individual episode pages always use EpisodeAudioPlayer — the Spotify
iframe check is removed since all per-episode embeds use direct audio URLs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- EpisodeAudioPlayer restored to original horizontal layout (unchanged
for individual episode detail pages chosen in /admin)
- LatestEpisodePlayer on /episodes page gets its own markup + CSS
(.latest-episode-player__*) with the centered layout A — large gold
play button, skip-back 15s / skip-forward 30s, full-width scrubber
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The per-episode player (chosen in /admin) reverts to the Spotify embed
iframe — only the /episodes listing page uses the new custom player.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LatestEpisodePlayer fetches the most recent episode from /api/episodes
(Anchor RSS) and renders it with the centered cinematic layout. The
Spotify show iframe is removed; platform buttons and HeadlinerWidget
remain below as before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- EpisodeAudioPlayer full variant: centered title, large gold play button,
skip-back 15s / skip-forward 30s flanking controls, full-width scrubber
- Compact variant (study sections) unchanged
- Episode detail page fetches direct Anchor MP3 URL from /api/episodes by
title match; falls back to resolvedEmbedUrl while loading
- Plays stream from Anchor CDN so stats still count
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch from toDataURL (which silently failed in browser) to toCanvas on
a real <canvas> ref — more reliable and renders immediately on mount.
Download PNG link appears once the canvas is drawn.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>