- 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>
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>
- 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>
Each host card on /nuggets now links to /nuggets/hosts/:hostId showing
the host's full photo, bio, and listen links. Archived hosts return a
graceful "not found" page with a back link.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- Restructure host card: image+name/role in a row header, bio spans full card width below
- Widen hosts column from 300px max to 360px max
- Add nuggets-host-card-header and nuggets-host-card-meta CSS classes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
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>
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>