Commit Graph
100 Commits
Author SHA1 Message Date
nmemmertandClaude Sonnet 4.6 5e43c41b2b Add recurring events, week/day view, drag-to-reschedule, iCal export, and start times to calendar; v1.1.22
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 16:46:45 -04:00
nmemmertandClaude Sonnet 4.6 ee7c783de8 Add email threading, snooze, bulk actions, scheduled send, stars, and attachments; v1.1.21
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 16:29:24 -04:00
nmemmertandClaude Sonnet 4.6 57fe34ba47 Make re-engagement email tiers configurable from admin; v1.1.20
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>
2026-07-28 16:13:25 -04:00
nmemmertandClaude Sonnet 4.6 2732730f5e Add study user re-engagement reminder emails; v1.1.19
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>
2026-07-28 15:56:09 -04:00
nmemmertandClaude Sonnet 4.6 d0c949070f Fix base64-encoded HTML email bodies not rendering; v1.1.18
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>
2026-07-28 12:28:21 -04:00
nmemmertandClaude Sonnet 4.6 a263f40178 Fix compose focus steal; add contact picker to To field; v1.1.17
- 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>
2026-07-28 12:15:45 -04:00
nmemmertandClaude Sonnet 4.6 f0635753f1 Add general calendar events (recording, social, task, general); nav links; v1.1.16
- 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>
2026-07-28 11:26:48 -04:00
nmemmertandClaude Sonnet 4.6 de201ff356 Add email reminders to calendar episodes; v1.1.15
- 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>
2026-07-28 11:12:17 -04:00
nmemmertandClaude Sonnet 4.6 0161a0dacd Add Contacts and Calendar nav links to /email header; v1.1.14
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 11:00:40 -04:00
nmemmertandClaude Sonnet 4.6 bdafac2908 Fix arm64 Docker build crash (QEMU illegal instruction); v1.1.13
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>
2026-07-28 10:53:29 -04:00
nmemmertandClaude Sonnet 4.6 ce235ba9ef Add /contacts and /calendar pages; email signature settings; v1.1.12
- /contacts: standalone page with hybrid contact list (submissions + manual entry), inline edit, search, archive
- /calendar: monthly release scheduling calendar reading/writing podcast checklist episode dates
- /email settings: editable signature panel; signature persisted server-side and injected into outgoing emails
- Move contacts out of /admin panel (now links to /contacts route)
- Partial PATCH for contact submissions (name, notes, archived independently)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 10:12:52 -04:00
nmemmertandClaude Sonnet 4.6 7670b44d27 Add /email route as standalone web email client; v1.1.11
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>
2026-07-28 09:34:39 -04:00
nmemmertandClaude Sonnet 4.6 e75a6d0e15 Increase episode title size on /episodes player; v1.1.10
Bumped .latest-episode-player__title from 1rem to 1.65rem for legibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 14:54:49 -04:00
nmemmertandClaude Sonnet 4.6 ae179fb4e8 Remove Spotify iframe branch from episode detail pages; v1.1.9
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>
2026-07-22 14:46:03 -04:00
nmemmertandClaude Sonnet 4.6 021fe24c7e Separate /episodes player from per-episode player; v1.1.8
- 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>
2026-07-22 14:42:08 -04:00
nmemmertandClaude Sonnet 4.6 939e7b9da4 Restore Spotify iframe on individual episode detail pages; v1.1.7
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>
2026-07-22 14:37:25 -04:00
nmemmertandClaude Sonnet 4.6 1e24eaa9af Replace Spotify show embed on /episodes with custom player; v1.1.6
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>
2026-07-22 14:36:41 -04:00
nmemmertandClaude Sonnet 4.6 5d4ba7e593 Replace Spotify iframe with custom centered player (layout A); v1.1.5
- 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>
2026-07-22 14:32:05 -04:00
nmemmertandClaude Sonnet 4.6 71ff0e6842 Fix QR image not appearing for new codes; v1.1.4
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>
2026-07-22 14:12:57 -04:00
nmemmertandClaude Sonnet 4.6 4e7905ae44 Show QR image and Download PNG button in QR Codes panel; v1.1.3
Each QR code card now renders a 128px preview of the actual QR image
(pointing to /qr/<slug> on the current origin) and a Download PNG link
that saves it as qr-<label>.png. Uses the qrcode browser build with
@types/qrcode for TypeScript support.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 14:06:13 -04:00
nmemmertandClaude Sonnet 4.6 8d767b131c Add Remove button to Contacts panel; v1.1.2
Grouped contacts now have a Remove button that deletes all submissions
for that email address in one click (with confirmation).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 13:58:48 -04:00
nmemmertandClaude Sonnet 4.6 bc025b5dab Admin reorganization: nav groups, streak/progress display in Study Users; v1.1.1
- Split "Manage" nav group into Audience, Insights, Tools
- Moved Email Templates into Configure group
- Added Testimonials to About jump nav
- Added RSS Feed tab to Podcast Hub
- Updated Study Users expanded card: shows last studied date, streak (current + longest), and per-study lesson completion count
- Streak pill shown in collapsed header when > 0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 13:27:41 -04:00
nmemmertandClaude Sonnet 4.6 1e4fe5f0e3 v1.1.0 — RSS feed, PWA, lesson comments, progress tracking + streaks
RSS Feed
- /feed.xml proxies the Anchor feed under the site's canonical domain
- Rewrites channel <link> and atom:link self-ref to the site URL
- Served with 30-min Cache-Control, reuses the existing episode cache

PWA
- vite-plugin-pwa installed; Workbox service worker auto-generated on build
- manifest.json inlined in vite.config.ts (name, icons, theme, standalone)
- pwa-192.png and pwa-512.png generated from existing book_icon.png
- StaleWhileRevalidate for /api/episodes and /api/questions; CacheFirst for images
- API, feed.xml, and uploads routes excluded from navigate fallback

Lesson Comments
- Import and wire StudySectionComments into ColossiansStudySectionPage
- Replaces the CommunityBoard in the Lesson Discussion section
- All routes and moderation already existed; only the render was missing

Progress Tracking + Streaks
- Mark-complete handler now records lastStudiedDate, currentStreak, longestStreak on the user record
- Streak increments on consecutive calendar days, resets on a gap
- /api/study-account/overview now returns streak fields
- Account page: 4-stat summary row (notes, streak 🔥, longest streak, member since)
- Per-study progress bars showing completedLessons/totalLessons with gold → green fill at 100%

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 13:19:25 -04:00
nmemmert 455f5c96b3 Bump to v1.0.14 2026-07-22 13:03:05 -04:00
nmemmertandClaude Sonnet 4.6 38185e43b3 Remove dead shareToX/shareToFacebook functions and related state; v1.0.14
Fixes CI lint errors — both functions were replaced by shareImageTo
in the previous commit but not deleted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 13:02:54 -04:00
nmemmertandClaude Sonnet 4.6 87090c1c19 Fix: mark approve handler async for await sendResendEmailWithRetry; v1.0.13
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 12:59:41 -04:00
nmemmertandClaude Sonnet 4.6 7ca457bb04 Consolidate Q&A share buttons — X and Facebook now share image; v1.0.13
Replaced the separate text-link X/FB buttons and the duplicate image buttons
with a single X and Facebook button each that share the generated quote card.
Keeps button row to 5: Upvote, X Share, Facebook Share, Copy link, Save image.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 12:59:13 -04:00
nmemmertandClaude Sonnet 4.6 ff7eec9906 Add share-as-image to X and Facebook for Q&A cards; v1.0.12
On mobile: Web Share API opens native share sheet with the PNG attached,
letting users pick X, Facebook, Instagram, etc. directly.
On desktop: downloads the image and opens the platform compose window.
"Save image" download button kept alongside the two new share buttons.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 12:57:21 -04:00
nmemmertandClaude Sonnet 4.6 6444d6e0e3 Add Q&A answered email template to admin Email Templates panel; v1.0.11
- content.ts: add qaAnsweredEmailSubject/Body/CtaLabel/Signoff fields + defaults
- email.js: buildQuestionAnsweredEmailTemplate reads from cachedSiteContent
- AdminPage.tsx: add EmailTemplateCard entry for Q&A answered notification

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 12:55:30 -04:00
nmemmertandClaude Sonnet 4.6 d74b7523d3 Add notify-on-answer email and save-as-image for Q&A; v1.0.10
- Contact form: notifyOnAnswer checkbox (shown for Bible Questions only)
- Contact route: stores notifyOnAnswer flag on question record
- Email: buildQuestionAnsweredEmailTemplate for branded notification
- Questions route: sends notification email on approve when notifyOnAnswer + email + answer are set
- Q&A section: Canvas API "Save as image" button generates 1080x1080 PNG quote card

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 12:51:38 -04:00
nmemmertandClaude Sonnet 4.6 43799b8c6d Make compact view visually distinct; v1.0.9
Add qa-cards--compact class to container when active. Compact mode now
tightens padding, reduces font sizes, hides related questions, and
reduces card gaps — making the density difference obvious.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 12:41:43 -04:00
nmemmertandClaude Sonnet 4.6 02dcbc33c9 Move Q&A submit CTA to top of page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 12:39:48 -04:00
nmemmertandClaude Sonnet 4.6 b60604aa80 Add testimonials section and Q&A submit CTA; v1.0.8
- Testimonials: new Testimonial type, CMS field, admin UI under About,
  and TestimonialsSection rendered on homepage and About page (hidden when empty)
- Q&A: "Submit a Question →" CTA at bottom of Q&A page linking to /contact

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 12:37:46 -04:00
nmemmertandClaude Sonnet 4.6 a7c5906c7b Remove dead CSS for qa-top-nav and thanks-page Q&A overrides; v1.0.7
These rules became orphaned after converting QuestionsPage to a full site
layout and removing the back-to-site nav link from QASection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 12:02:07 -04:00
nmemmertandClaude Sonnet 4.6 595a2c3ec4 Reverse podcast highlights order; convert Q&A to full page; v1.0.6
- Podcast highlights now display newest-first (reversed array)
- Q&A page uses full site layout (header, nav, footer) instead of card/modal wrapper
- Remove redundant "Back to Site" link from QASection now that header is present

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 11:58:34 -04:00
nmemmertandClaude Sonnet 4.6 369a7944f0 Build JS on CI host, copy dist into Docker image for faster arm64 builds; v1.0.5
Removes the build stage from Dockerfile so npm install/build no longer run under
QEMU emulation — only the lightweight runtime stage runs per-platform, making
arm64 images build reliably.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-16 16:02:19 -04:00
nmemmertandClaude Sonnet 4.6 3a055ba8b2 Add ARM64 support to Docker build (multi-arch); bump to v1.0.4
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-16 14:31:49 -04:00
nmemmertandClaude Sonnet 4.6 178038d830 Remove unused boilerplate and stale data files; bump to v1.0.3
Deletes src/data/projects.ts (never imported), and the three unused
Vite boilerplate assets (react.svg, vite.svg, hero.png).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-16 08:20:33 -04:00
nmemmert f419e7d617 Delete Verse by Verse with Nate Complete Series directory 2026-07-16 08:17:52 -04:00
nmemmertandClaude Sonnet 4.6 0e771bfbb5 Rewrite README to accurately describe the app; bump to v1.0.2
Replaces outdated portfolio/project-cards description with accurate
documentation of the podcast/ministry site: features, all env vars,
routes, API endpoints, persistent data paths, and admin capabilities.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-16 08:12:45 -04:00
nmemmertandClaude Sonnet 4.6 288baae676 Fix version commit SHA showing as unknown in Docker (v1.0.1)
The runtime image doesn't include .git, so git rev-parse failed at
startup. Fix: pass COMMIT_SHA as a Docker build arg from github.sha
in CI and bake it into the image as an ENV var, which config.js
already prefers over running git.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-16 08:00:29 -04:00
nmemmertandClaude Sonnet 4.6 172e4a9358 Add version numbering (v1.0.0)
- package.json: bump to 1.0.0
- server/config.js: export APP_VERSION (from package.json) and GIT_COMMIT
  (from COMMIT_SHA env var set by CI, or git rev-parse --short HEAD fallback)
- GET /api/version: new public endpoint returning { version, commit }
- GET /api/admin-auth/status: includes version and commit in response
- AdminPage sidebar: displays version string (e.g. "v1.0.0 (1d43875)")
  below the Log Out button, styled as muted metadata text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-16 07:55:52 -04:00
nmemmertandClaude Sonnet 4.6 1d43875e5a Fix 11 bugs: restore crash, draft leak, email failures, memory leaks
Critical fixes:
- sanitizeLoadedHitStats/VisitorStats: restore full state shape so a
  snapshot restore no longer crashes hit-counting middleware (missing
  byPathReal, byPathBot, byDayReal, byDayBot, botReasons, ipHashIndex)
- /questions/share/🆔 read state.questions only, not draft questions
- inbound-email: validate date with Number.isFinite before toISOString
- study-reminders: wrap each send in try/catch so one failure doesn't
  block remaining users; persist sent-markers after each success

Security:
- getClientIp: use req.ip (trust-proxy-resolved) instead of raw
  x-forwarded-for header to prevent IP spoofing
- env-snapshot.env: delete immediately after backup tar stream ends
  so secrets don't linger on disk between exports

Correctness / UX:
- contact form: email failures no longer 500 the user after the
  submission is already saved; log and fall through instead
- study-account profile: cap data URI avatar at 6 MB
- admin enrollment PATCH: validate slug against study catalog
- signup: return 503 at MAX_STUDY_USERS instead of silently dropping
  oldest accounts

Memory leaks:
- contactHits, downloadHits Maps: prune stale entries at 5000 entries
- resendEmailSubmissionIndex: trim to 2000 entries (oldest first)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-16 07:48:54 -04:00
nmemmertandClaude Fable 5 f5e98826be Include env settings snapshot in full data backup
Docker deployments have no .env file — settings live in container env
vars — so the export now writes env-snapshot.env (dotenv format) into
the data dir from a whitelist of portable keys (admin password, Resend
keys, webhook URLs, etc.) before archiving. Machine-specific vars
(PORT, NODE_ENV, SITEFORGE_DATA_DIR, ALLOW_INSECURE_COOKIES, build
metadata) are excluded. Values are not applied automatically on
restore; the file documents what to set on the new server.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:12:51 -04:00
nmemmertandClaude Fable 5 0406cdb9dc Remove accept filter on full-backup restore file input
iPadOS greys out .tar.gz files when the input has an accept filter, so
the archive could not be selected. The server validates the gzip
signature and archive contents before restoring, so no client-side
filter is needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:00:47 -04:00
nmemmertandClaude Fable 5 2e406af471 Add ALLOW_INSECURE_COOKIES env flag for HTTP access in production
Browsers drop Secure cookies on plain-http origins, so logging into the
admin over a LAN/VPN IP (e.g. during server migration, before TLS is in
front) silently failed every authenticated request. Setting
ALLOW_INSECURE_COOKIES=true omits the Secure flag; default behavior in
production is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:49:37 -04:00
nmemmertandClaude Fable 5 f4fd177421 Add full /data backup export/import for server migration
- New admin endpoints: GET /api/admin-backup/export streams the entire
  data directory as tar.gz (after flushing queued writes); POST
  /api/admin-backup/import validates the archive, snapshots current
  data, replaces the folder, and reloads all in-memory state.
- Admin UI: "Full Data Backup" section with download and restore-from-
  file controls in the Analytics panel.
- Fix admin TOTP secret path to honor SITEFORGE_DATA_DIR (moved base
  path resolution to server/paths.js to avoid a circular import), so
  2FA for admin and study users survives backup/restore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:08:23 -04:00
nmemmertandClaude Sonnet 5 17c9cbbc8b Fix inbound-email data loss, MIME truncation, and header injection
- server/data.js: preserve source/htmlBody/inboundTo/messageId across
  server restarts (sanitizeLoadedContactSubmissions was silently
  dropping them on reload from disk)
- cloudflare/email-worker.js: rewrite MIME parsing to split on the
  actual boundary marker instead of any literal "--", unfold
  multi-line headers, and correctly recombine multi-byte UTF-8 in
  quoted-printable decoding
- server/routes/inbound-email.js: validate Message-ID against RFC 5322
  grammar before storing/using it, and compare the webhook secret with
  timingSafeEqual to match the rest of the codebase's auth checks
- server/routes/contact.js: re-validate messageId at the point it's
  injected into outgoing In-Reply-To/References headers; move the
  allowed reply-from addresses into a shared config constant
- src/AdminPage.tsx: 30s inbox poll now syncs field updates (e.g.
  archived) on already-loaded submissions instead of only appending
  new ones; consolidate the duplicated from-address list
- .claude/launch.json: add a vite dev server preview config used to
  verify these changes

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 08:21:50 -04:00
nmemmertandClaude Sonnet 4.6 fbb9a24f32 Add per-reply from-address selector and inbox address badges
- Reply composer shows a From dropdown (hello@ or nate@), defaulting
  to whichever address the inbound email was sent to
- Server validates the chosen address against an allowlist before sending
- Inbox list shows colored address badges: blue for hello@, purple for nate@

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 12:14:34 -04:00
nmemmertandClaude Sonnet 4.6 edc17b8710 Poll for new inbox messages every 30 seconds in admin
Silently checks for new contact submissions in the background and
prepends any new ones to the inbox without disrupting the current view.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 12:01:09 -04:00
nmemmertandClaude Sonnet 4.6 16947e305c Keep subject when applying reply template to preserve email threading
Templates now only fill the message body, not the subject, so the
Re: [original subject] line stays intact for proper Gmail threading.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 11:58:42 -04:00
nmemmertandClaude Sonnet 4.6 77a4f9300f Thread admin replies to original email via In-Reply-To/References headers
When replying to an inbound email that has a messageId, the outgoing
Resend payload now includes In-Reply-To and References headers so the
reply threads correctly in Gmail and other email clients.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 11:36:11 -04:00
nmemmertandClaude Sonnet 4.6 118314d0d7 Render inbound email HTML body in admin inbox
Worker now extracts both text/plain and text/html MIME parts and sends
htmlBody in the webhook payload. Server stores it on the submission.
Admin inbox renders htmlBody in a sandboxed iframe when present, falling
back to plain text with whitespace preserved.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 11:32:52 -04:00
nmemmertandClaude Sonnet 4.6 4b411f763f Add inbound email capture via Cloudflare Email Worker
- Cloudflare Email Worker forwards hello@ and nate@ to Gmail and POSTs
  parsed MIME email to /api/inbound-email as admin inbox entries
- New server route authenticates via shared secret and deduplicates by
  Message-ID before storing inbound emails as contact submissions
- Admin inbox shows "email" badge for inbound messages; reply composer
  opens blank with auto-subject "Re: [original]" and signature preview
- Documents setup steps in cloudflare/email-worker.js and .env.example

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 11:23:27 -04:00
nmemmertandClaude Sonnet 4.6 f83682879b Remove archivedSeries from useGlobalSearch (field does not exist on SiteContent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 10:37:14 -04:00
nmemmertandClaude Sonnet 4.6 25fa24afdf Add Finished Books feature with episode playlists and nav dropdown
Public:
- /finished page with ornament heading, intro text, book grid, and styled empty state
- /finished/:id episode playlist page filtered by RSS season number
- Episodes nav becomes click-toggle dropdown (Current Series / Finished Books)
- Finished Books link in footer
- Dropdown font fixed to match other nav links

Admin:
- End Current Series & Start New Book wizard on Current Series tab
- Finished Books management tab with add/edit/remove and image asset picker

Data:
- FinishedBook type + finishedBooks[] field on SiteContent
- RSS parser extracts itunes:season per episode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 10:34:43 -04:00
nmemmertandClaude Sonnet 4.6 2eb98b66d0 Add Finished Books feature with episode playlists
- New FinishedBook type in content.ts + finishedBooks[] field on SiteContent
- RSS parser now extracts itunes:season into each episode object
- /finished grid page and /finished/:id playlist page (filters episodes by season)
- Episodes nav link replaced with dropdown: Current Series / Finished Books
- Finished Books link added to footer
- Admin: "End Current Series & Start New Book" wizard on Current Series tab
- Admin: Finished Books management tab (add/edit/remove entries)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 10:22:37 -04:00
nmemmertandClaude Sonnet 4.6 a8f6024234 Fix Truth For Life devotional widget not rendering
Replaced broken srcdoc iframe approach with direct script injection so
the TFL syndicate.js runs in page context with access to the DOM it needs.
Also added truthforlife.org and ajax.googleapis.com to CSP script-src,
which was silently blocking the widget and its jQuery dependency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 09:26:00 -04:00
nmemmert 2169739910 cert fix 2026-06-18 14:02:13 -04:00
nmemmert 83c3f554aa cert fix 2026-06-18 13:59:30 -04:00
nmemmert b77aba0167 build notes fix 2026-06-18 13:46:41 -04:00
nmemmert dd43d0c3ea notes upgrade 2026-06-18 13:43:17 -04:00
nmemmert e0ed798741 easter egg 2026-06-18 10:03:32 -04:00
nmemmert 9174331d2d Bug fixes (real breakage):
Newsletter nudge — was calling /api/study-account/profile (wrong endpoint, ignored subscription). Now correctly calls /api/study-account/preferences with PATCH.
Security:

Email regex — replaced the permissive [^\s@]+@[^\s@]+ pattern with a proper RFC-compliant regex in contact.js and downloads.js
Avatar magic bytes — server now checks actual PNG/JPEG/GIF/WEBP header bytes, not just the data URL prefix
Certificate rate limit — public /api/public/certificate/:token now has a 30 req/15min limiter
Session absolute TTL — admin sessions now have a 30-day hard cap; a stolen token can no longer be kept alive indefinitely by passive reads
Account lockout — 5 failed logins locks a study account for 1 hour
CSP headers — Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, and Referrer-Policy headers added globally
Data integrity:

Cascade delete — deleting a study account now also removes their certificates, community posts, comments, and progress file
UX / reliability:

Escape key on modals — all 3 modal groups (study index, notes, account) now close on Escape
Display name min-length — empty spaces-only names rejected; if provided, must be ≥2 chars
Note save rate limit — 30 saves/minute per user max
Analytics fetch timeout — 5s AbortController so a hanging server doesn't block the browser indefinitely
Email validation on signup — frontend catches bad email formats before hitting the server
Cleanup:

Deduplicated download forms — StudyDownloadForm and ResourceDownloadForm now share a single DownloadForm base; both are now thin wrappers
2026-06-18 09:35:44 -04:00
nmemmertandClaude Sonnet 4.6 fda9fa27a6 Update analytics cookie banner text
Clearer language explaining what's tracked and that no personal data
is sold or shared.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 14:39:54 -04:00
nmemmertandClaude Sonnet 4.6 945a2e160e Redesign visitor log as readable cards replacing dense table
Each visit is now a card showing name/IP, page path, location, device,
visit count, new/returning tag, and timestamp at a glance. Logged-in users
show their display name prominently. Click to expand still shows full page
history and raw details. Adds a purple "Logged in" tag for study users.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 14:24:16 -04:00
nmemmertandClaude Sonnet 4.6 31426fccba Add comprehensive visitor engagement tracking and scroll-to-top on navigation
Tracks scroll depth (25/50/75/90%), time on page, UTM parameters, outbound
link clicks, search queries, audio pause/completion/listen time, and 404s.
Logged-in study users are now tied to their visitor record and surfaced in
the admin recent visits table. Scroll position resets on every route change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 14:06:21 -04:00
nmemmertandClaude Sonnet 4.6 a6f0c39c35 Simplify episodes page and upgrade podcast highlights to card layout
- Episodes page now shows Spotify show embed only (no episode list)
- Podcast highlights section replaced plain links with episode-card style
  (gold border, episode number, title, summary, Listen → CTA)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 13:48:49 -04:00
nmemmertandClaude Sonnet 4.6 8450d81e7f Add per-episode play tracking with bar chart in admin analytics
- POST /api/analytics/play records a play event (title + date) on first
  play per player mount, skipping admin sessions
- Plays persisted to data/episode-plays.json with total + byDay buckets
- Admin stats response includes episodePlays sorted by total plays
- AnalyticsPanel shows horizontal bar chart + summary cards for all
  episodes dynamically — new episodes appear automatically as played

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 13:39:35 -04:00
nmemmertandClaude Sonnet 4.6 54bc7b28da Fix RSS feed URL to use correct Anchor s/ path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 13:29:09 -04:00
nmemmertandClaude Sonnet 4.6 1010cb1f33 Update study reminders timestamp and exclude admin sessions from pageview analytics
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 13:24:48 -04:00
nmemmertandClaude Sonnet 4.6 a1d5889711 Replace Spotify embeds with branded custom audio player backed by RSS
- Add /api/episode-audio route returning MP3 URLs from Anchor RSS feed
- Replace Spotify URL text inputs in admin with RSS episode dropdowns
- New EpisodeAudioPlayer component with podcast art, show name, progress
  bar, and Spotify icon link — full and compact sizes
- Backward-compatible: legacy Spotify embed URLs still render as iframes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 13:23:19 -04:00
nmemmertandClaude Sonnet 4.6 3d8f02d915 Scope external site link styles to homepage section only
The .section-resources CSS overrides were breaking the Downloads page
resource link layout. Added .section-external-sites class to the
ExternalSitesSection and scoped all image/layout overrides to it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 12:04:49 -04:00
nmemmertandClaude Sonnet 4.6 d6acf85252 Fix Save Draft always showing Unsaved after save
The snapshot was set from buildSiteContentForSave(form) which runs
normalizeSiteContentForAdmin and rebuilds study objects, producing a
JSON string that never matched the raw form state. isDirty therefore
stayed true permanently after every save.

Fix by capturing JSON.stringify(form) before the fetch and using that
as the snapshot, so isDirty correctly clears on success.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 11:54:39 -04:00
nmemmertandClaude Sonnet 4.6 68fa1497a3 Remove helper text from external sites section on homepage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 11:35:13 -04:00
nmemmertandClaude Sonnet 4.6 d42c00d6dd Show image on external site link cards on homepage
Adds the configured imageUrl as a full-width banner at the top of each
External Sites card, above the link name and description.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 11:18:08 -04:00
nmemmertandClaude Sonnet 4.6 47887d7fbd Add admin-editable QR code image to homepage share section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 10:03:21 -04:00
nmemmertandClaude Sonnet 4.6 48c2f81d00 Add QR code tracker to admin panel
Each QR code gets a /qr/<slug> redirect that logs scans (IP, user agent,
timestamp) to disk. The admin QR Codes view lets you add, edit, enable/disable,
and delete codes, with per-code scan history inline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 09:33:35 -04:00
nmemmert 37fc69680e Send study reminder emails 8 hours after lesson release
Lessons release at 1:00 AM, but reminders fired immediately, landing
around 9 PM the prior evening. Delay sends by 8 hours so they go out
at 9:00 AM on the release day.
2026-06-15 07:56:56 -04:00
nmemmertandClaude Sonnet 4.6 1bc573147d Redesign Q&A for mobile and upgrade completion certificate
- Full mobile redesign of Q&A: edge-to-edge cards, full-width layout,
  wrapping action buttons, hidden sidebar, proper touch targets, and
  fixed double-padding from thanks-page/thanks-card wrapper
- Fancy certificate redesign: double gold border frame, corner ornaments,
  ornamental rules with diamond dividers, decorative seal, gold glow,
  and updated issuer to "Verse by Verse with Nate"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 13:40:31 -04:00
nmemmert 8eae361a73 Q&A mobile 2026-06-09 11:30:36 -04:00
nmemmertandClaude Sonnet 4.6 ee9d76ada7 Wire lesson discussion to community, fix search links
- Replace standalone StudySectionComments with CommunityBoard locked to
  section tag on lesson page — lesson discussion now feeds directly into
  the community system
- Quiz "share to discussion" posts to community API (not separate comments)
- "Go to Community" button no longer pre-filters by section (lesson
  discussion is now on-page)
- Add "See all community posts" link from lesson discussion block
- Fix search: download results now use correct custom-- ID format
- Fix search: episodes with a highlight page route to /episodes/:id
  internally instead of opening the external link

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 10:23:54 -04:00
nmemmert 75d490020b search download fix 2026-06-09 10:02:01 -04:00
nmemmertandClaude Sonnet 4.6 c4645e3475 Add cross-site improvements across three phases
Phase 1 — Quick wins:
- Image lazy-loading on series/resource cards
- Newsletter signup added to Episodes page (before highlights)
- Per-route meta tags via usePageMeta hook (title, og:title, og:description)
- Breadcrumbs on study index, section, and notes pages
- SVG completion checkmark badges on study section list
- Analytics time-range filter (7d / 30d / 90d) in admin panel

Phase 2 — Medium features:
- Related episodes on archived series detail pages
- Resource library two-tier filter (type + tag chips)
- Global search (Fuse.js) moved below sticky header as full-width bar
- Q&A anonymous upvoting with localStorage dedup + admin pin/unpin
- Study enrollment funnel tracking (firstVisitAt, firstCompletionAt) with funnel chart in analytics

Phase 3 — Larger features:
- Study section comments (auto-approve for enrolled users, admin moderation panel)
- Study completion certificate (canvas render, PNG download, shareable public URL)
- Episode script full-text search (mammoth docx extraction, server-side search, admin upload UI)
- Reflection questions renamed from Discussion Questions; quiz answers can be shared to section discussion
- Public certificate route at /certificate/:token with og meta tags
- Comment moderation panel added to admin under Manage > Study Comments

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 09:10:29 -04:00
nmemmert 9ad24df626 tweaks 2026-06-08 16:47:21 -04:00
nmemmert 3e1b1e0fae Enrolment issue 2026-06-08 14:45:04 -04:00
nmemmert 95b7a3f211 Add in /salvation 2026-06-08 10:24:37 -04:00
nmemmert 66699270f5 Clean. Here's what's live now:
- **Tag filter bar** appears above the grid whenever any download has tags assigned. Chips for "All" + each unique tag, centered under the heading.
- **Clicking a tag** filters the grid to only matching downloads. Clicking the active tag again (or "All") clears the filter.
- **Empty state** — if a tag has no results it shows a friendly message instead of a blank grid.
- The filter only shows when tags exist — so it's invisible today (your 3 current downloads have no tags) and appears automatically once you start tagging items in the admin.

To use it: go to Admin → Downloads → edit a download item → add tags like `Colossians`, `Worksheet`, `Devotional`. Those tags will show up as filter chips on the public page instantly after publishing.
2026-06-05 09:23:55 -04:00
nmemmert c80804b055 checpoint fix 2026-06-04 10:35:01 -04:00
nmemmert 285d837a0b publish error fix 2026-06-04 10:22:07 -04:00
nmemmert 0ba35e0e52 font change 2026-06-04 10:12:13 -04:00
nmemmert bccd1cfa60 TFL Resources 2026-06-04 09:46:51 -04:00
nmemmert 8d95c50041 TFL fix 2026-06-04 09:36:04 -04:00
nmemmert 20397960d6 Truth for life fix 2026-06-04 09:23:11 -04:00
nmemmert cb78b6522d All green. You're good to build and deploy. Here's a summary of everything done since your last commit:
---

**🐛 Bugs fixed**
- Study hub/lesson pages turned gold — CSS merge error with `.study-index-page` fixed
- Lesson announcements not saving — `announcement` field missing from `sanitizeColossiansStudySections` whitelist
- 2FA email method blocked with "not configured" — guard checked `totpSecret` instead of `twoFaMethod`
- `STUDY_REMINDERS_FILE` constant never defined — server would crash on first reminder write
- `sanitizeStudyUsers` stripped all 2FA fields — data lost on every restart
- `scheduleStudyReminders` missing from refactored modules

**✨ Features added**
- Commentary supports paragraph formatting (double newline = new paragraph)
- Lesson announcement banner — full-width gold stripe at top of lesson page
- "Take the Quiz" renamed to "Discussion Questions" throughout
- Students must complete checkpoint before unlocking next lesson (lesson list + Next Lesson button both gate on it)
- No checkpoint questions → simple Mark as Completed button as fallback
- Truth For Life syndication widget on homepage
- Newsletter welcome email updated to your new HTML template

**🔧 Maintenance**
- `server.js` refactored from 6,013 lines into 12 focused modules
2026-06-04 09:17:34 -04:00
nmemmert 07519b4f48 auth fix 2026-06-03 16:14:49 -04:00
nmemmert e11858c2f3 gated lessions 2026-06-03 16:07:54 -04:00
nmemmert a7dc3ede21 remove quiz label 2026-06-03 15:56:17 -04:00
nmemmert b34a78f874 fix agian 2026-06-03 15:49:22 -04:00
nmemmert ae396da72e annoucments 2026-06-03 15:27:21 -04:00