Commit Graph
76 Commits
Author SHA1 Message Date
nmemmertandClaude Sonnet 5 8496e227b3 Stop showing email-derived names in student discussions; v2.2.1
Publish Container / build (push) Successful in 14m56s
Community posts/replies fell back to the local part of the student's email
(e.g. "student.test" from student.test@versebyverse.local) whenever they
hadn't set a display name. The per-lesson discussion comments were worse —
they fell back to the FULL email address, and that endpoint has no auth
requirement, so it was publicly readable.

Adds getCommunityDisplayName() in study-helpers.js: uses the student's
display name when set, otherwise assigns a stable "Student XXXX" handle
derived from their account id — never their email. Both the community board
and lesson comments now resolve names live from the author's current
account at read time (falling back to the stored snapshot only if the
account was deleted), so this also fixes any already-posted content
without a data migration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 11:15:54 -04:00
nmemmertandClaude Sonnet 5 841e754e16 Add student account self-service security features and discoverability fixes; v2.1.0
Adds forgot/reset password, signup email verification, sign-out-of-all-devices,
note-usage and certificate display in account settings, and password-changed
security emails. Fixes study sessions being lost on server restart by
persisting them to disk, and fixes sanitizeStudyUsers silently dropping the
new pending-token fields on load. Also surfaces the lesson discussion board
in the main site nav, trims the sign-in page for returning users, and renames
colossiansStudy.tsx/colossiansStudyData.ts to studyPages.tsx/legacyColossiansData.ts
now that the study system covers more than Colossians.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 09:21:54 -04:00
nmemmertandClaude Sonnet 5 a6e8243705 Add season field to episode scripts and improve global search; v2.0.50
Publish Container / build (push) Successful in 16m53s
Tag scripts with a season number (with an RSS-feed backfill for
existing uploads), surface it in search results, and fix word-boundary
matching plus a title/script-match dedup bug in the search merge logic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 08:45:40 -04:00
nmemmertandClaude Sonnet 4.6 8ba300eddc Fix host upload: handle multer errors as JSON, pre-create upload-tmp dir; v2.0.41
Publish Container / docker (push) Successful in 15m21s
If multer failed to write the temp file (e.g. data-volume permission issue),
the error was propagating unhandled through Express, returning a non-JSON 500
that the client couldn't parse — showing only "Upload failed." with no details.

Changes:
- Import mkdir and pre-create UPLOAD_TEMP_DIR on startup (non-blocking)
- Wrap upload.single('file') in a callback so multer errors return JSON
  responses with the actual message instead of a bare HTML 500

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-22 07:49:00 -04:00
nmemmertandClaude Sonnet 4.6 c79589b311 Add local vs R2 destination picker for admin asset uploads; v2.0.39
Publish Container / docker (push) Successful in 14m12s
Upload dropdown lets you choose R2 folder (uploads/images/guides/finished)
or local server disk. Asset table shows location column for each file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-18 12:20:21 -04:00
nmemmertandClaude Sonnet 4.6 f525e7e784 Add Cloudflare R2 storage for admin asset uploads; v2.0.37
New uploads route to R2 with folder selection (uploads/images/guides/finished).
Existing local files continue to be served via express.static for backward compat.
Adds R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET,
R2_PUBLIC_URL env vars. listUploadedAssets merges R2 objects + local files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-18 12:04:55 -04:00
nmemmertandClaude Sonnet 4.6 f254e3ec19 Add scheduled series auto-launch for series card, Start Here, and study guide; v2.0.36
Publish Container / docker (push) Successful in 14m20s
One scheduled date/time in the Current Series admin panel archives the active series to
Finished Books and atomically swaps in new content for the series card, Start Here steps,
and Companion Study Guide. Server checks every hour via setInterval; also runs on restart
so the transition fires even if the server was down at the scheduled moment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-18 11:45:02 -04:00
nmemmertandClaude Sonnet 4.6 65623ce0eb Wire up deployment metadata: build commit, number, and server start time; v2.0.30
Publish Container / docker (push) Successful in 14m11s
- Dockerfile: expose BUILD_COMMIT and BUILD_NUMBER env vars from build args
  (was COMMIT_SHA only; server was reading BUILD_COMMIT so it always fell through to null)
- CI: pass github.run_number as BUILD_NUMBER build arg
- state.js: record serverStartedAt at module load so deployedAt always shows
  when the current container instance came up, even without an env var
- admin-content.js: fall back COMMIT_SHA → BUILD_COMMIT for older images,
  use state.serverStartedAt when DEPLOYED_AT env var not set

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 16:52:44 -04:00
nmemmertandClaude Sonnet 4.6 4002f1ee93 Analytics overhaul: privacy fixes, All Time range, readability improvements; v2.0.29
Publish Container / docker (push) Successful in 14m17s
- Remove raw IP storage; store only SHA-256 hash prefix in visitor records
- Non-blocking geo lookup: recordVisitor now synchronous, geo resolved in background
- UTM capture race condition fixed via sessionStorage pre-consent capture
- Add consent + bot checks to play endpoint (was missing)
- Add rate limiter to pageview endpoint (60 req/min per IP)
- Add 'All time' range option alongside 7d/30d/90d with trendData aggregation
- AnalyticsPanel: tabbed layout (Traffic/Engagement/System), dynamic chart titles,
  range buttons with active state, visitor log uses ipHash instead of raw IP,
  trendData drives main chart with weekly bucketing for all-time view
- Re-add prop-types dependency (required by react-simple-maps bundler)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 16:27:53 -04:00
nmemmertandClaude Sonnet 4.6 e158e1e53a Codebase audit: remove dead code, fix broken patterns, consolidate duplicates; v2.0.27
Publish Container / docker (push) Failing after 10m40s
- Remove unused prop-types dep, deprecated useOutboundLinkTracking export
- Fix missing linkClicks key in analyticsEvents initial state (B-01)
- Export ADMIN_SESSION_COOKIE from auth.js; remove local copy in admin-auth.js (D-02)
- Remove cookieFlags() wrapper in auth.js and study-helpers.js; use cookieSecureFlag() directly (U-04)
- Remove ADMIN_SESSION_TTL_MS duplicate from config.js (D-01)
- Remove DEFAULT_SEO/DEFAULT_LEGAL/DEFAULT_REDIRECT_RULES/DEFAULT_PODCAST_FEATURED_LINKS/DEFAULT_PUBLISH_STATE
  duplicates from config.js; re-export from helpers.js (D-04, fixes sitemapPaths divergence)
- Fix email.js hardcoded fallback strings to use DEFAULT_RESEND_TO/DEFAULT_RESEND_REPLY_TO (M-02)
- Remove duplicate normalizeIp, isPrivateOrLocalIp, normalizeRedirectPath, sanitizeUrl,
  sanitizeRedirectRules bodies from study-helpers.js; re-export from helpers.js (D-03)
- Fix pruneStatsByDays to import VISITOR_RETENTION_DAYS_DEFAULT from config.js (B-02)
- Fix createTitusDownloadToken to use DOWNLOAD_TOKEN_TTL_MS constant (B-03)
- Remove normalizeMessageType duplicate from study-helpers.js (U-03/D-08)
- Remove dead wantsWelcome variable in contact.js; use shouldSendWelcome (B-04)
- Replace dynamic imports in contact.js with static imports (B-07)
- Remove dead wantsWelcome=true in study-auth.js (B-05)
- Merge duplicate import blocks in analytics.js and public.js (D-05, D-06)
- Replace dynamic readFile re-import in nuggets-host-upload.js (B-06)
- Replace hardcoded SITE_URL fallbacks with getCanonicalBaseUrl() (M-06)
- Replace raw resend.emails.send in study-account.js with sendResendEmailWithRetry (M-01)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 16:10:31 -04:00
nmemmertandClaude Sonnet 4.6 1ddb8ee7be Add Nuggets to global search and RSS-sourced scripture refs to index; v2.0.26
Publish Container / docker (push) Successful in 15m20s
- Raise RSS description truncation from 220 → 800 chars so scripture
  references in episode descriptions are preserved
- Global search now indexes Nuggets podcast episodes from
  /api/nuggets/episodes/all (title, subtitle, description via Fuse)
- Scripture Index (/api/public/scripture-index) now extracts verse
  references from both main podcast and Nuggets RSS descriptions
  using a regex built from the shared BIBLE_BOOKS list; duplicate refs
  across sources are merged by the existing client-side dedup logic
- Export fetchNuggetsEpisodes from nuggets.js for reuse

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 10:27:12 -04:00
nmemmertandClaude Sonnet 4.6 590885f6b1 Add contact form inbox to admin portal; v2.0.25
- New Inbox view in the Audience nav group shows all contact form submissions
- Filter by Unread / All / Archived with a search bar for name, email, or message
- Click any message to open a detail pane with full text, metadata, and a Reply via Email link
- Archive / Unarchive button to clear messages without deleting them
- Unread badge on the Inbox nav item; dashboard card now links directly to Inbox
- Added PATCH /api/admin-contact-submissions/:id to persist archived state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 10:20:49 -04:00
nmemmertandClaude Sonnet 4.6 86de45dbc3 Make recording guidelines editable from admin panel; v2.0.18
Publish Container / docker (push) Successful in 14m23s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-09 08:38:02 -04:00
nmemmertandClaude Sonnet 4.6 ef1d9fd8fc Add full portal expansion: 2FA, change password, history, announcements, recording guidelines, Seafile link, draft saving, file validation; v2.0.17
Publish Container / docker (push) Successful in 14m36s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-09 08:08:42 -04:00
nmemmertandClaude Sonnet 4.6 51543396e3 Fix Seafile upload response parsed as JSON; v2.0.15
Publish Container / docker (push) Successful in 14m18s
Seafile seafhttp returns a plain file ID string, not JSON. Replaced
res.json() with res.text() — the return value was unused anyway.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 15:42:02 -04:00
nmemmertandClaude Sonnet 4.6 26a51f9089 Fix Seafile API path from /api/v2.1/ to /api2/; v2.0.14
Publish Container / docker (push) Successful in 13m54s
This Seafile instance exposes /api2/ not /api/v2.1/. Updated both the
upload-link call in the host upload route and the dir listing in the
Seafile watcher. Confirmed /api2/ works via node fetch test on the server.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 15:25:48 -04:00
nmemmertandClaude Sonnet 4.6 3febad351d Fix Seafile upload-link using wrong query param ?path= instead of ?p=; v2.0.12
Publish Container / docker (push) Successful in 14m0s
Seafile API uses ?p= for the directory path parameter. Using ?path= caused
the request to never match an API route, returning the web UI 404 HTML page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 14:45:51 -04:00
nmemmertandClaude Sonnet 4.6 ce12ae1d0d Fix reset-password email sending undefined as email field; v2.0.11
Publish Container / docker (push) Has been cancelled
Was passing data.hosts[idx].username (doesn't exist on stored hosts)
instead of data.hosts[idx].email for the welcome/reset email username param.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 14:41:41 -04:00
nmemmertandClaude Sonnet 4.6 684181c34c Use email as host login credential; remove separate username field; v2.0.8
Publish Container / docker (push) Failing after 10m44s
- Login form now asks for email + password (no username)
- Admin creates hosts with name + email + password; email is always
  required and welcome email always sends on creation
- Duplicate email check replaces duplicate username check
- Admin table shows Email (login) column only; username field removed
  throughout data model, routes, and UI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 14:05:02 -04:00
nmemmertandClaude Sonnet 4.6 a12a471c41 Add host welcome/reset emails, email field, and /host URL; v2.0.7
- Host accounts now have an optional email field
- Creating a host with an email sends a welcome email with the portal
  URL, username, and password via Resend
- Resetting a password also emails the new credentials if the host
  has an email on file; admin sees a confirmation either way
- Admin panel shows email column and confirmation messages
- Portal URL changed from /host-upload to /host

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 14:01:51 -04:00
nmemmertandClaude Sonnet 4.6 1e396644a9 Add upload email notifications with Seafile direct-upload watcher; v2.0.6
- Portal uploads send one email immediately (host, episode, passage,
  description, filenames) via Resend to NUGGETS_NOTIFY_EMAIL
- Seafile watcher polls /Episode Upload every 15 min for direct uploads
  and sends a notification for any new file not already seen via portal
- Files uploaded through the portal are marked seen so the watcher
  never sends a duplicate notification
- Credentials kept out of git: SEAFILE_TOKEN, SEAFILE_NUGGETS_REPO_ID,
  NUGGETS_NOTIFY_EMAIL now sourced from docker .env file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 13:54:46 -04:00
nmemmertandClaude Sonnet 4.6 6a03813914 Redesign Nuggets upload portal: single episode submission form; v2.0.5
- Replace two-tab file picker with a combined form: episode name, passage,
  description, and audio file in one submit
- Server now generates an Episode Info text file (name, host, passage,
  description, timestamp) and uploads it to /Episode Info/ automatically,
  while the audio goes to /Episode Upload/
- Drop zone changes color when a valid file is selected (green border)
- Success shows submitted episode name and filename in the session log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 13:46:20 -04:00
nmemmertandClaude Sonnet 4.6 afdc00114a Add Nuggets host upload portal with Seafile integration; v2.0.4
- New /host-upload page: per-host login (username + password) with
  session cookie, drag-and-drop file upload to Seafile via server proxy
- Two upload targets: Episode Upload (audio) and Episode Info (docs)
- Server route: /api/nuggets-upload/* for host auth + file proxy,
  /api/admin/nuggets-upload/hosts for admin CRUD (create/disable/reset pw/delete)
- Admin panel: Nuggets → Upload Portal view to manage host accounts
- Seafile config via SEAFILE_URL / SEAFILE_TOKEN / SEAFILE_NUGGETS_REPO_ID env vars
- Directories default to /Episode Upload and /Episode Info (configurable)
- Uses multer for multipart uploads; 500 MB per-file limit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 13:38:57 -04:00
nmemmertandClaude Sonnet 4.6 4378fad66f Fix 5 bugs: analytics range keys, stale visitor rows, NuggetsPage init state, episodes timer leak, unsanitized nuggets object; v2.0.1
Publish Container / docker (push) Successful in 14m54s
- analytics: union byDayReal+byDayBot keys so bot-only days aren't dropped from range totals
- analytics: exclude timestamp-less visitor rows from range filters (was unconditionally including them)
- App.tsx: init `configured` to false so a network failure doesn't show a misleading "no episodes" state; add r.ok guard before r.json()
- episodes.js: wrap fetch in try/catch so clearTimeout is called on network failure, matching the nuggets.js pattern
- helpers.js: add sanitizeNuggets + wire it into sanitizeSiteContent so corrupted hosts/urls can't crash NuggetsPage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 08:21:02 -04:00
nmemmertandClaude Sonnet 5 31e79ad542 Add Verse By Verse: Nuggets sub-show with editable hosts and RSS feed
Publish Container / docker (push) Successful in 14m16s
New /nuggets page for the companion show, linked from the Episodes nav
dropdown. Hosts (name, role, bio, photo) show in a left column with the
episode list on the right — latest episode featured, rest below.
Episodes are pulled from an admin-configurable RSS feed URL (empty by
default, shows "coming soon" until set) rather than hardcoded like the
main show's feed, since the show doesn't have one yet.

Everything is editable in /admin under a new Nuggets section: Show
Settings (title, tagline, description, podcast art, RSS/platform URLs)
and Hosts (add freely; removing a host archives them instead of
deleting — kept on record and restorable, never lost).

Extracted the RSS item-parsing logic shared with the main show's feed
(server/routes/episodes.js) into server/rss-helpers.js so both feeds
use the same parser instead of duplicating it.

Also fixes the local Vite dev server not proxying /uploads/* to the
backend — it was silently serving the SPA shell (200, text/html)
instead of the actual file, so any admin-uploaded image (podcast art,
host photos, etc.) appeared broken in local dev only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Yvx4rPVy2HWe7ubuk3X3J
2026-09-07 14:27:52 -04:00
nmemmertandClaude Sonnet 5 335a9cbe31 Consolidate the three duplicate Bible-book lookup tables into one shared registry
Publish Container / docker (push) Successful in 14m9s
src/App.tsx (Scripture Index book map) and server/routes/bible-passage.js
(BSB/bolls.life passage fetch) each hand-maintained their own independent
66-book list with a different key format (3-letter USFM id vs bolls.life's
numeric id), so a future alias/rename fix in one would silently not apply to
the other.

Add shared/bible-books.js as the single canonical list (name + aliases +
USFM id, in canonical order); the server derives its numeric bolls.life ids
from the array position instead of hand-maintaining a second table. Requires
allowJs + including shared/ in tsconfig.app.json so the client can import the
plain-JS module alongside its TypeScript sources.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Yvx4rPVy2HWe7ubuk3X3J
2026-09-07 09:49:52 -04:00
nmemmertandClaude Sonnet 5 c158fb6317 Fix contact form crash, analytics range filter, Scripture Index parsing, and unsafe-delete UI bugs
Publish Container / docker (push) Successful in 13m12s
- server/routes/contact.js: import fireWebhooks (was undefined, throwing on every submission)
- server/routes/analytics.js: filter admin-stats by row.at, the field recordVisitor actually sets, instead of the never-populated row.visitedAt
- src/App.tsx: parseSIRef now supports chapter-spanning verse ranges (e.g. "5:15-6:2"); SI_BOOK_MAP indexes both spaced and unspaced book names so multi-word Bible Book values from the admin field resolve correctly
- src/App.css / src/App.tsx: fix Episodes/Studies nav dropdown menus being unclickable — a positioning gap between the trigger and menu created a hover dead zone; menu is now flush with spacing moved into padding so hover carries through
- src/components/StudySectionComments.tsx: check response status before removing a comment locally, so a rejected delete no longer appears to succeed
- src/components/QASection.tsx: don't bail out of search scoring for 1-2 character queries, so short substrings like "OT" or "AI" can still match
- src/AdminPage.tsx: check response status in study-user, QR code, and episode-script delete handlers before updating local state

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Yvx4rPVy2HWe7ubuk3X3J
2026-09-07 09:39:37 -04:00
nmemmertandClaude Sonnet 5 6ed29ea527 Rebuild Scripture Index as a true cross-content reference index
Publish Container / docker (push) Successful in 15m2s
Previously it just grouped study lessons under each study (essentially
another list of lessons) and ignored podcast episodes entirely. Now it
parses every reference from studies and episode show notes, merges
entries that point at the same verse(s) into one row with links to all
sources, and groups rows by book in canonical Bible order. References
that don't parse (e.g. whole-chapter refs) fall into an "Other
References" section instead of being dropped.

Also fixes the reference passed to the index for studies without a
bibleBook set — it was falling back to the study's marketing title
instead of its slug, producing unparseable references.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Yvx4rPVy2HWe7ubuk3X3J
2026-09-07 09:20:12 -04:00
nmemmertandClaude Sonnet 4.6 49e3a80c2c Redesign community section with social media feed UX; v1.2.6
- Twitter/X-style post cards: avatar thread line, author + relative timestamp
- Heart like button with optimistic toggle and like count per post
- Comment button with reply count; inline threaded replies always visible
- Compose card with user avatar, borderless textarea, pill Post button
- Horizontal scrollable lesson filter chips (All + per-lesson)
- New POST /api/study-community/posts/:postId/like endpoint (toggle)
- GET community posts now returns likedByMe + likeCount per post
- sanitizeStudyCommunityPosts preserves likedBy array on disk
- Relative timestamps (just now, Xm ago, Xh ago, Xd ago, then date)
- UserAvatar helper renders img or initials fallback consistently

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-03 16:34:28 -04:00
nmemmertandClaude Sonnet 4.6 038ef2b039 Add study account engagement features; v1.2.4
- Pause reminders: remindersPausedUntil field + scheduler skip + account UI with 7/14/30-day presets
- Email digest mode: reminderFrequency field (immediate/weekly) + weekly digest email + scheduler + account UI toggle
- Admin user list improvements: streak, reengagement, frequency, pause, per-study lesson progress
- Admin manual re-engagement: POST endpoint + tier selector button in admin panel
- Study bookmarks: bookmarkedSectionIds in progress file + POST/DELETE endpoints + toggle button in lesson UI
- Study completion certificate email: auto-sent on first certificate issuance
- Content fields for certificate and weekly digest email templates in admin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-03 15:16:47 -04:00
nmemmertandClaude Sonnet 4.6 63d4f9e316 v1.2.0: Broadcast emails, webhook admin UI, dynamic sitemap, scripture index
Publish Container / docker (push) Successful in 14m11s
- Admin > Audience > Broadcast Email: compose and send one-off HTML emails
  to all subscribers via Resend batch API; {{name}} personalization; confirm step
- Admin > Configure > Webhooks: CRUD UI for managing POST event webhooks;
  backend GET/POST/DELETE /api/admin-webhooks routes wired to existing state.webhooks
- Sitemap: dynamically appends released study lesson URLs (/study/:slug/:id)
  and episode highlight URLs (/episodes/:id) to /sitemap.xml
- Scripture Index: public /scripture page listing every released lesson grouped
  by study with passage reference links; served by /api/public/scripture-index

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-03 09:54:43 -04:00
nmemmertandClaude Sonnet 4.6 0d830d6e79 Remove dead admin routes and broken UI elements; v1.1.45
Publish Container / docker (push) Successful in 14m54s
- Deleted ~24 unused API routes from contact.js (email inbox/CRM feature
  never wired to frontend): reply, compose, draft, broadcast, webhooks,
  audit-log, contact CRUD, merge, import, and health endpoints
- Removed dead imports from contact.js (config, data, email modules)
- Removed broken /email Link from admin dashboard Inbox card
- Cleaned up AdminView type: removed unreachable 'current-series' and
  'episode-highlights' top-level values (only valid as podcast sub-tabs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-31 12:48:24 -04:00
nmemmertandClaude Sonnet 4.6 2ba9c9f7f4 Add Fetch from BSB button to admin lesson editor; v1.1.36
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>
2026-08-10 10:23:48 -04:00
nmemmertandClaude Sonnet 4.6 9e602e29ec Fetch BSB passage text from Bolls.life API in study lesson view; v1.1.35
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>
2026-08-10 10:12:25 -04:00
nmemmertandClaude Sonnet 4.6 6acc3f1d04 Analytics: 3-tab layout + fix geographic data loss; v1.1.34
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>
2026-08-10 09:22:47 -04:00
nmemmertandClaude Sonnet 4.6 bf3154378b Track all link clicks (internal + external) in analytics; v1.1.33
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>
2026-08-10 09:11:53 -04:00
nmemmert c56e468115 Revert "Add student birthday tracking with celebration banners and admin notification; v1.1.31"
This reverts commit c0a79b9ed0.
2026-08-10 08:43:53 -04:00
nmemmertandClaude Sonnet 4.6 c0a79b9ed0 Add student birthday tracking with celebration banners and admin notification; v1.1.31
- 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>
2026-08-10 08:42:52 -04:00
nmemmertandClaude Sonnet 4.6 111b559865 Add webhook outbound, Contact→Calendar, checklist automation; v1.1.26
- Webhook CRUD (GET/POST/DELETE /api/admin-webhooks, POST .../test) with
  ⚡ Webhooks drawer in EmailPage; fires contact.new and reply.sent events
- Contact→Calendar: history panel shows checklist episodes within ±30 days
- Checklist automation: saveEdit auto-sets productionStatus (idea→scheduled
  for future dates, idea→published for past); ⚙ Tasks button generates
  Record (−14d) and Edit (−7d) calendar task events for scheduled episodes
- Audit log captures webhook-added, webhook-removed, webhook-test events
- Webhook infrastructure: state, disk persistence, fire-and-forget with
  AbortSignal.timeout(8000), loaded on startup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 08:03:30 -04:00
nmemmertandClaude Sonnet 4.6 f5c4557e9f Add Tier 2 remaining features: publishing pipeline, broadcasts, audit log, drip trigger, today banner; v1.1.25
- CalendarPage: productionStatus field on episodes (idea→recorded→edited→scheduled→published), color dot on chips, dropdown in edit popover; "publishing today" banner with draft announcement button
- EmailPage: Broadcasts drawer (plain-text → Resend Broadcasts API); Audit Log drawer showing activity history; em-status-msg--ok style
- ContactsPage: ▶ Drip button in edit mode syncs contact to Resend audience and triggers drip automation
- server: appendAuditEntry() logged on reply-sent, email-sent, submission-deleted, contacts-merged, contacts-imported, broadcast-sent, drip-triggered; GET /api/admin-audit-log; POST /api/admin-broadcasts/send; POST /api/admin-contacts/trigger-drip; AUDIT_LOG_FILE persisted to data/audit-log.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 07:52:07 -04:00
nmemmertandClaude Sonnet 4.6 bf16deea92 Add Tier 2 email features: AI draft, Calendar→Email announce, delivery status, engagement badges; v1.1.24
- EmailPage: useLocation to accept pre-filled compose from Calendar; delivery status pill on sent bubbles; AI Draft button calling ANTHROPIC_API_KEY endpoint
- CalendarPage: useNavigate + announceEpisode(); ✉ Announce button in episode edit popover
- ContactsPage: emailStatus types; unreplied badge; best delivery status pill; engagement score stars
- server/routes/contact.js: POST /api/admin-contact-submissions/:id/draft-reply → Anthropic claude-haiku-4-5

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 17:10:25 -04:00
nmemmertandClaude Sonnet 4.6 e2c560a1ab Add contacts features: tags, history, CSV import/export, merge, last-contacted; fix CalendarPage unused var; v1.1.23
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 17:00:21 -04:00
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 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 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 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 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