August 2, 2026
Characters system, homepage revamp & unified search quota
LatestToday shipped the full Bible character system (list/detail, LLM generation pipeline with human review, ES indexing, personalized recommendation), revamped the homepage (no more empty states, clearer value proposition, anonymous ask), swapped the daily term for a daily character across Today/Share/Email, added banner search on reflections & characters with the unified quota, and fixed courseware anonymous preview, homepage spacing, and server-action probe noise.
- Homepage revamp: every section’s empty state replaced with actionable entries, new “Why BibleVibe” five-card strip (Characters / Stories / Three Traditions / Bilingual / Free), and an anonymous hero ask box that routes to /study with no login gate — the login prompt only appears once the chat limit is reached.
- Fixed first-paint html lang (cookie/browser), chapter-aware /read titles, anonymous read-only courseware preview with interactive login gate (P0-4 option A), and uniform homepage section spacing.
- Tradition landing pages launched: /protestant-bible-study, /catholic-bible-study, /jewish-bible-study, and /bible-traditions-compared with FAQPage JSON-LD and a three-column comparison table.
- Bible characters system: /characters list & detail (strict single-locale), a 48-person candidate list (first batch of 10 covering all categories), and an admin “Characters” tab with LLM drafts + human review + multi-select force-regenerate / batch status + ES sync.
- Character generation prompt unified grounding: key scripture text, Strong’s per-word originals, ES theology terms, TSK cross-references, and era context; also fixed the reflections pipeline’s theology-term lookup that never actually queried by text.
- Characters now live in their own ES domain (auto-index on publish, delete on unpublish, one-click full sync with orphan cleanup), fixing the missing lowercase_normalizer and published_at date-format issues in shared index creation.
- Daily picks: the term was replaced by a daily character (deterministic per date), My Space gained a “character for you” card tied to the user’s reading/saved reflections (rotating every 6 hours), and character is included in share text and the daily verse email.
- Unified search quota: 10 anonymous / 50 signed-in per 3-hour window, decoupled from read/reflection browse limits; reflections and characters banner search both count toward it.
- All character cards now carry a shared person icon badge in the top-left (matching reflection cards); the read page gained a “Characters in this book” section with a single horizontally-scrolling row on mobile.
- Frontend hardening: /_next/server-action requests without a valid Next-Action header are 404’d at the edge, stopping scanner noise from spamming container logs.
August 1, 2026
Cache unification + daily-picks timezone + hot-search/trending rework + reflections pipeline hardening + TTS to mp3
ArchiveFrontend content caching was unified under one control layer and the
daily-picks timezone/rotation mismatch was fixed; hot search switched from
static community hotness to aggregated real queries (with noise filtering),
and discover trending questions now use time-decayed ranking; the read page
related-reflection matching was upgraded and now reuses the list-page card;
the reflections pipeline was hardened (jsonrepair fallback, missing
section_key inference, transactional writes); TTS audio is now stored as
mp3 (~1/6 the size) with an ID3-header detection fix; study conversation
sharing shipped and the spacetime 3D Cube entry was hidden.
- [Cache] New unified frontend content-cache layer: lib/content-cache, client-content-cache, time-zone and CACHE_SCOPE metadata; serverFetchJson is now a thin shim, so every SSR/client content fetch shares one key/TTL/empty-value policy.
- [Timezone] Daily picks "today" now follows the user browser timezone and flips at local midnight instead of UTC; cache keys carry local date + UTC 6h slot, fixing the A/B half-rotation being masked by stale cache entries; archive matches the home date semantics.
- [Archive] Archive is limited to the most recent 30 days: older dates are unselectable and never trigger generation (calendar disabled + backend window guard).
- [Search] Hot search rebuilt on real queries: aggregates search_rate_log over a 14-day window with smart filtering (SQLi probes, URLs, noise words, pure numbers, too-short/too-long), replacing static community hotness.
- [Search] Discover Trending Question now uses time-decayed hotness (hot / (ageDays+1)^1.5), so new dialogs can surface while old leaders decay; frontend TTL dropped from 6h to 20min.
- [Reflections] Read-page related-reflection matching upgraded to chapter -> book -> era fallback with most-read priority; reuses the /reflections list-page card with a dedicated sunrise icon; reflections stay out of general search.
- [Reflections] Draft pipeline hardened: jsonrepair fallback for LLM JSON, missing section_key inference, transactional persistDraft (no more half-written rows), prompt hard-validation, plus an audit script (NEED_REDO / INCOMPLETE).
- [TTS] Audio now stored as mp3 via ffmpeg (~1/6 size, big mobile bandwidth win): transcode happens before save and the filename follows the real format; fixed mp3 detection for ID3-tagged output (valid mp3 was previously rejected as output_not_mp3 and stored as WAV); existing 106/107 WAV files were transcoded offline and pointers updated.
- [Share] Study conversation sharing: select-and-share on /study, full-conversation share on /me, reflection share button restored, with a new shared lib/share-conversation.ts.
- [UI] Hidden the 3D Cube entry on the spacetime page (the /channel/spacetimecube page itself is unchanged).
July 29, 2026
/read refactor: chapter/verse AI reflection + whole-book study + reader controls + auth caps + SSR speedup
ArchiveToday's work is a major refactor + feature wave on the /read page (BibleVibe's main scripture-reading surface, v1.2 read submodule). Five pieces: (1) Backend new ReflectionService at modules/reflection/ - one service consolidates four LLM-driven reflection kinds (chapter part1/part2, verse reflection at single-verse and chunk granularities, whole-book study guide with 8 sections, and study chat reply). All four share callLlmForJson + inflightDedupe + theo-vocabulary guard (a curated list of approved theology terms is appended to every system prompt so the LLM can't drift into unsanctioned theological territory). Each kind uses a two-stage lazy pipeline: ES lookup first, fall through to LLM generation + ES upsert + DB write on miss, then a 60s reuse window. chapter_reflection stores whole-chapter content (intro / themes_questions / key_insight), verse_reflection stores per-verse or per-chunk content (body_zh / body_en / key_phrase_zh / key_phrase_en, granularity ENUM 'verse'|'chunk'). (2) Backend new ChatUsageService at modules/chat-usage/ - STUDY_DAILY_CHAT_LIMIT (50 per 24h) gates study and reflection together, checkUserCanProceed returns {ok, remaining}, recordReflectionRound writes a chat_messages row after every successful LLM call (session_id is synthesized as 'reflection:{userId}:{kind}:{scopeKey}', context JSON carries source='reflection' + kind / scope_key / extra for analytics). The three book-reflection endpoints (chapter part1 / part2 / verse) and the books controller getStudyGuide endpoint all got the cap check + recordRound call; getStudyGuide moved from fully-anonymous to AuthGuard + AuthOptional + returns ok=false on cap. study.service.ts chat() now delegates the LLM call to ReflectionService.generateChatReply so all four kinds share one LLM call stack. (3) SSR speedup + endpoint consolidation - new GET /api/v1/books/list-by-traditions?traditions=protestant,catholic,judaism (returns book lists for multiple traditions in one call, page.tsx now fires 3 concurrent fetches instead of 3 sequential ones), new GET /:bookId/landing-data?chapter=N&tradition=X (one endpoint that delivers verses + meta + reflection cache-hit state + study-guide cache-hit state + chapter count, enough to fully SSR the /read first paint), getVerses wrapped in a 60s LRU cache (128 entries, per-process), getBookStudyGuideCached wrapped in a 60s LRU cache (256 entries, per-process), getBooksByTraditions now reads source_books.chapters_count directly instead of COUNT(source_verses). (4) Frontend /read UI - new BookBrowser (A-Z picker that also surfaces "my reading", driven by landing-data), ChapterReflectionCard (accepts fontSize / fontBold, three cards in the order Interpretation -> Key Insight -> Themes & Questions, themes_questions cut from 3-4 to exactly 2, shows a "today's conversation limit reached" toast when the cap fires), VerseReflectionToggle (per-verse toggle, blocks further clicks on cap), WholeBookStudyPanel (8 sections, mobile single-row horizontal scroll, desktop vertical stack), Toast system hoisted to layout, new EraStageList component, assessment merged into reading; bilingual verse rendering is automatic top-zh / bottom-en for non-en locales (no toggle button, uiLocale is never touched); per-chapter chapterReaderPrefs = Record<number, {fontSize: 'small'|'standard'|'big', fontBold: boolean, rate: 0.75|1|1.25}>, three icon buttons (A cycles font size, B toggles CJK 黑体 bold, clock cycles playback rate) sit below the chapter card's existing three action buttons, fontSize+fontBold propagate to every reflection card + VersePanel + TheologyMiniCard in that chapter, rate is set on the audio element's playbackRate only and NEVER triggers backend TTS regeneration (pre-rendered MP3 constraint); ReadPageClient fixes a "my-reading chip click triggers a re-fetch loop" bug - the main useEffect now uses a lastBookIdRef guard so only a real bookId change does the full reload, a targetChapter-only change just expands the target chapter; page.tsx now SSRs the landing-data prefetch whenever there's an accessToken, regardless of hasBookId (previously the !hasBookId guard meant the book-detail page still hit the client-side fetch on mount). (5) Mobile header cleanup + copy tweaks - the chapter card header's three action buttons (Enter Study / Add / Listen) move from mobile absolute top-3 right-3 to a wrap-row below the title on mobile (pr-[12rem] -> lg:pr-[12rem]) so they no longer crush the title; "Study this chapter ->" is shortened to "Study ->". The purge script scripts/purge-recent-zh-chapter-tts.ts lands alongside, cleaning zh-CN chapter TTS files + DB rows + now-unreferenced ai_audio_cache cache_hashes from the last 20 days in a reference-safe way (dry-run shows 5 chapter_audio candidates, 5 unique cache_hashes, all 5 safe to drop); same safety guards as the study purge.
- Backend - new ReflectionService at backend/app/app/modules/reflection/: callLlmForJson unifies LLM call + JSON parse + retry; inflightDedupe prevents concurrent same-request duplicate LLM calls; loadTheologyGuard pulls the approved term list from theology_terms and appends it to every system prompt; loadChapterVerses hard-truncates at 9k characters; loadScriptureGrounding pulls TSK / xrefs / commentary from ES; four public methods (generateWholeBookGuidePart / generateChapterReflectionPart / generateVerseReflection / generateChatReply) all use the same pipe.
- Backend - new ChatUsageService at backend/app/app/modules/chat-usage/: countUserRoundsLast24h uses `JSON_EXTRACT(context, '$.source') IN ('study','reflection')` + 24h WHERE; checkUserCanProceed returns {ok, remaining}; recordReflectionRound writes a chat_messages row (role='user', synthesized session_id 'reflection:{userId}:{kind}:{scopeKey}', context JSON carries source='reflection'/kind/scope_key/extra). Wired into all three book-reflection endpoints and the books/study-guide endpoint.
- Backend - new endpoints: GET /api/v1/books/list-by-traditions?traditions=protestant,catholic,judaism (parallel book-list fetch for multiple traditions, page.tsx now uses Promise.all instead of 3 sequential calls); GET /api/v1/books/:bookId/landing-data?chapter=N&tradition=X (single endpoint returns verses + meta + reflection cache-hit state + study-guide cache-hit state + chapter count, page.tsx SSRs the whole /read first paint in one prefetch).
- Backend - LRU caches: getVerses wrapped in 60s LRU (128 entries, per-process), getBookStudyGuideCached wrapped in 60s LRU (256 entries, per-process), cache hits take the ES-only path without DB or LLM round-trips.
- Backend - books.service.ts: getBooksByTraditions reads SELECT chapters_count FROM source_books directly, no more COUNT(source_verses); getBookLandingData combines the five pieces above into one response.
- Backend - study.service.ts chat(): the direct LLM call is delegated to ReflectionService.generateChatReply, so study chat shares the same LLM call stack as the four reflection kinds (callLlmForJson / theo-vocabulary guard / inflightDedupe / retry-parse all inherited).
- Backend - ES indices: SearchIndexDomain adds bible_chapter_reflections + bible_verse_reflections, ELASTICSEARCH_INDEX_ALIASES + DOMAIN_CONFIGS are updated, search-index.service.ts accepts the two new domain upsert paths.
- Backend - auth + cap integration: book-reflection controller (chapter part1 / part2 / verse reflection endpoints) gets @UseGuards(AuthGuard) + chatUsage.checkUserCanProceed + chatUsage.recordReflectionRound; books.controller getStudyGuide moves from fully-anonymous to AuthGuard + AuthOptional + returns {ok:false} on cap; study.service.ts chat() path is counted too (reflection and study share the same 50/24h pool).
- Backend - migrations: docs/mysql/migration_chapter_verse_reflection.sql (chapter_reflection + verse_reflection tables, 4-field unique key, IF NOT EXISTS idempotent); docs/mysql/migration_chat_messages_optional_session.sql (session_id + anon_visitor_id + courseware_id nullable + two helper indexes, IF NOT EXISTS idempotent).
- Backend - purge script scripts/purge-recent-zh-chapter-tts.ts: reference-safe zh-CN chapter TTS cleanup (dry-run by default, --execute for real deletes), cleans files (local + R2) + chapter_audio rows + ai_audio_cache cache_hashes that are now unreferenced (must confirm no other chapter_audio row in any language and no immersive_tts row still points to it before dropping the cache), path-traversal guard + transaction rollback + idempotent.
- Frontend - new components: BookBrowser (A-Z picker + my-reading merged), ChapterReflectionCard (three cards in the order Interpretation -> Key Insight -> Themes & Questions, themes_questions cut to 2, cap-hit toast), VerseReflectionToggle (per-verse toggle, cap blocks further clicks), WholeBookStudyPanel (8 sections, mobile single-row horizontal scroll, desktop vertical stack), EraStageList, Toast system hoisted to layout, browse-helpers utility module.
- Frontend - bilingual verse: non-en locale automatically renders top-zh / bottom-en, never touches uiLocale (hard constraint), no toggle button.
- Frontend - per-chapter reader controls: chapterReaderPrefs = Record<number, {fontSize, fontBold, rate}>, three icon buttons (A font size / B 黑体 bold / clock rate) sit below the chapter card's existing three action buttons, fontSize + fontBold propagate to every reflection card + VersePanel + TheologyMiniCard in that chapter, rate is set on the audio element's playbackRate only and NEVER triggers backend TTS regeneration.
- Frontend - "my reading" loop fix: ReadPageClient main useEffect uses a lastBookIdRef guard so only a real bookId change does the full reload, a targetChapter-only change just expands the target chapter; page.tsx SSRs the landing-data prefetch whenever an accessToken exists, regardless of hasBookId (previously the !hasBookId guard meant the book-detail page still hit a client-side fetch on mount).
- Frontend - mobile chapter card header: three action buttons move from absolute top-3 right-3 to a wrap-row below the title on mobile (pr-[12rem] -> lg:pr-[12rem]) so they no longer crush the title; "Study this chapter ->" is shortened to "Study ->".
- Changelog bookkeeping: today's commit 030d5adb on master, 37 files +6649 / -695; backend gains three new modules (book-reflection / chat-usage / reflection), four existing modules touched (books / study / search-index), two new-table DDLs, one purge script; frontend gains five new components, ReadPageClient / WholeBookStudyPanel / page.tsx / layout.tsx / content-item.ts are modified. tsc --noEmit is green on both ends; frontend dev verifies the reflection pipeline end-to-end with a real DEEPSEEK_API_KEY.
July 28, 2026
Reflections devotional subsystem: 10 tables + 7-step LLM pipeline + 21 APIs + admin UI
ArchiveToday BibleVibe gains its first long-form devotional subsystem, Reflections (v1.2), modeled on the "Bible in One Year" pattern but rebuilt on top of every existing pipeline (books / spacetime / architecture / courseware / study / search / TTS / LLM / ES) - zero new services invented. Five pieces: (1) Data layer - 10 new tables (themes, reflections, reflection_sections, reflection_verses, reflection_links, reflection_faqs, reflection_pipeline_status, reflection_auto_queue, reflection_redirect_log, reflection_seeds). Theme (主旨) is a first-class classification living in its own table, parallel to the existing topic (主题) table, hosting 10 devotional axes: prayer / faith / character / purpose / suffering / love / worship (the reference site's original 7) + hope / wisdom / kingdom (3 new in v1.2; covenant and identity were cut to avoid overlap with purpose and character). (2) Public read API - 9 endpoints under /api/v1/reflections: list, detail (sections+verses+faqs+related+prev/next in one shot), theme facet, by-theme, most-read, related, smart match (hit/redirect/none), view +1, TTS 302. (3) Admin API - 12 endpoints under /api/v1/admin/reflections: CRUD, four-state status switch (draft / private / unlist / public), auto-queue review (approve auto-creates a draft reflection, reject requires a >=3-char reason). (4) 7-step LLM pipeline (each step has its own SSE endpoint, idempotent timestamp, ?force=1 to re-run, verbose debug log): Step 1 collect-candidates gathers from priority_chapters (p<=2) + reflection_seeds; Step 2 fetch-verses pulls source text via BooksService.getChapterText (zh + en); Step 3 draft LLM produces the initial long-form, with three-source RAG grounding (loadBibleToolboxGrounding for Strong's+Macula+xrefs+commentary, ES theology_terms, MySQL TSK cross_references), small blocks (title/dek/FAQ/note) go bilingual in one pass, large blocks (context/meaning/apply/reflection body) English-only, and the context section is hard-validated to contain era_key / geo_keys / refs - schema failure blocks the write; Step 4 enrich LLM writes reflection_links to same-theme reflections and same-era architectures; Step 5 translate TogetherTranslateService only translates the large-block body_en->body_zh; Step 6 sync-es upserts the translation to ES immediately (no waiting on TTS); Step 7 tts TtsFactoryService.synthesize falls through edge_tts -> deepinfra and saves via LocalStorageDriver. (5) Admin UI at /admin/reflections, one page, three tabs: Pipeline (9 status cards + 7-step mini progress + force toggle + 7 step rows with per-row play/abort + inserted/updated/skipped/failed/duration counters + run-all); Auto-queue (pending list with value_score color-coded + Approve/Reject via modal); Reflections list (admin grid with status filter, one-click state switch, deep-link to /reflections/[slug]). SSE log streams every start / item / translate / tts_chunk / es_sync / step_done / error event. 6 commits already on master (d4f398f8 + f15e32da); the user-facing frontend (NavBar / Footer / home / /reflections list / /reflections/[slug] / /read CTA) is held for v1.3 as M4.
- Data layer - 10 new tables (docs/sql/init-reflections-tables.sql): themes (theme dictionary, 10 seeded), reflections (main table, four-state status: draft / private / unlist / public), reflection_sections (split by section_key, context.meta_json MUST contain era_key / geo_keys[] / era_label_zh|en / geo_labels_zh|en[] / refs[]>=2), reflection_verses (primary + related), reflection_links (6 link_types: reflection / architecture / courseware / topic / era / geo), reflection_faqs, reflection_pipeline_status (7-step idempotent timestamps + last_step + error_log), reflection_auto_queue (5 trigger_types + value_score), reflection_redirect_log (5-min debounce + reverse-enqueue signal), reflection_seeds (manual seeds).
- Data layer - naming rule: theme (主旨) is a brand-new first-class classification axis, parallel to the system's existing topic (主题) table. The DB field stays as theme_key (snake_case), the Chinese UI surface is always rendered as "主旨", the English UI surface is always "Theme" - to keep the two axes visually distinct everywhere and to avoid colliding with the existing topic (主题) table. The only FK that crosses into topic is reflection_links.link_type='topic'.
- Data layer - 10 seeded themes (docs/sql/seed_reflections_themes.sql): prayer Prayer and nearness to God, faith Faith and trust, character Heart words and character, purpose Purpose and calling, suffering Suffering and comfort, love God's love and goodness, worship Worship and the God who acts (the reference site's original 7) plus hope Hope and waiting, wisdom Wisdom and understanding, kingdom Kingdom and righteousness (3 new in v1.2). covenant Covenant and promise was cut (overlapped with purpose) and identity Identity in Christ was cut (sister to character); they can come back in v1.3 once the content volume justifies the extra axes.
- Public read API - 9 endpoints under /api/v1/reflections: GET / list (theme/status/limit/cursor/is_most_read filters), GET /most-read (homepage picks, top 6), GET /themes (theme facet with count + sample), GET /by-theme/:themeKey (everything in a theme), GET /:slug detail in one shot (primary_verse + related_verses + sections + faqs + related_reflections + related_architecture + prev + next), GET /:slug/related (related block only), GET /:slug/match (smart-match hit/redirect/none three-state), POST /:slug/view (view +1), GET /:slug/tts (302 to storage audio).
- Admin API - 12 endpoints under /api/v1/admin/reflections: GET /status status card, GET /reflections admin list, GET /reflections/:id admin detail, POST /reflections create, PUT /reflections/:id update, POST /reflections/:id/status state switch (auto-writes published_at on transition to public), DELETE /reflections/:id soft-delete (to draft); GET /auto-queue list pending, POST /auto-queue/:id/approve approve and auto-create a draft reflection (slug + theme_key inferred from context_json), POST /auto-queue/:id/reject reject requires >=3-char reason, POST /auto-queue/auto-archive 7-day stale auto-archive.
- Smart matching - ReflectionMatcherService: book_id+chapter exact match against reflection_verses.role='primary' (score=0.95), theme_key direct lookup (score=0.75), architecture_id reverse-lookup to book_ids/era/geo (ctx expansion then re-match), query LIKE fallback (score=0.40). Threshold split: score>=0.55 hit, 0.25-0.55 redirect, <0.25 none. 5-minute debounce on the same source->target via reflection_redirect_log; reverse-enqueue shouldAutoEnqueue (value_score<2 auto-rejected, frequency_7d<3 not enqueued).
- 7-step pipeline - each step has its own SSE endpoint (/api/v1/admin/reflections/pipeline/step/{step}) + idempotent timestamp + ?force=1 to redo + verbose debug log: Step 1 collect-candidates gathers from priority_chapters (p<=2) + reflection_seeds, infers theme_key from the note text / era_key, writes context_collected_at; Step 2 fetch-verses pulls source text via BooksService.getChapterText into reflection_verses.quote_zh|en, writes verse_pulled_at; Step 3 draft calls ModelFactoryService.compileWithPrompt with three-source RAG grounding, small blocks go bilingual in one pass, large blocks English-only, context section is hard-validated to contain era_key/geo_keys/refs (schema failure blocks the write), writes draft_generated_at; Step 4 enrich LLM writes reflection_links to same-theme reflections and same-era architectures and fills the spacetime summary, writes enrich_generated_at.
- 7-step pipeline (continued): Step 5 translate calls TogetherTranslateService.translateText only for section_key in {context, spacetime, meaning, apply, reflection} and where body_zh is empty, body_en->body_zh, plus context.meta_json era_label_zh / geo_labels_zh / refs[].note_zh, after completion reflections.lang_origin='both' is set, writes translated_at; Step 6 sync-es upserts to ES immediately after translation (no waiting for TTS) via ElasticsearchService.upsertDocuments("reflections", docs), body is the sections-joined long text for full-text search, writes es_synced_at; Step 7 tts calls TtsFactoryService.synthesize (role="head_ta", lang="en-US"/"zh-CN", edge_tts->deepinfra chain fallback) to produce mp3, LocalStorageDriver.save stores to /media/audio/reflection-.../tts-...-...mp3, fills tts_zh_url / tts_en_url / tts_*_provider / tts_*_duration_sec, writes tts_generated_at.
- RAG grounding - Step 3 LLM prompt gets three sources injected: (1) loadBibleToolboxGrounding({kind:"scripture", bookId, chapter}, elasticsearch) pulls Strong's lexicon + Macula word-level tags + TSK cross-references + Matthew Henry / JFB commentary (same loader the study service uses); (2) ElasticsearchService.searchDocuments("theology_terms") pulls top-5 theology terms (term_en + short_definition); (3) MySQL cross_references table pulls top-8 by votes (same source SQL as study.getCrossReferences). All three blocks fall back to empty string on failure - best-effort, never blocks the LLM call.
- Reuse existing services - zero new service invented: ModelFactoryService.compileWithPrompt({timeoutMs}, systemPrompt, userInput) for LLM (3-arg signature matches study); TogetherTranslateService.translateText({text, targetLang, sourceLang}) for translation; ElasticsearchService.upsertDocuments("reflections", docs) for ES ("reflections" already added to SearchIndexDomain union + ELASTICSEARCH_INDEX_ALIASES + DOMAIN_CONFIGS with textFields title^5 + dek^3 + body^2); TtsFactoryService.synthesize({text, role, lang}) for TTS (edge_tts -> deepinfra chain fallback); LocalStorageDriver.save(lectureId, lang, filename, buffer) for storage; BooksService.getChapterText(bookId, chapter) for scripture.
- Admin UI - /admin/reflections single-page three-tab (backend/app/app/admin/reflections/page.tsx, commit d4f398f8): (1) Pipeline tab - 9 status cards (total / 4 states / 2 TTS gaps / pipeline incomplete / queue pending) + 7-step mini progress (s1~s7 completed counts) + force toggle + 7 step rows (each with its own play/abort + per-step inserted/updated/skipped/failed/duration counters) + run-all button; (2) Auto-queue tab - pending candidates list with value_score color-coded (green/orange/red) + Approve (backend auto-creates a draft reflection) / Reject via modal with >=3-char reason; (3) Reflections list tab - all admin reflections + status filter + one-click four-state switch + deep-link to /reflections/[slug]. SSE log streams every start / item / translate / tts_chunk / es_sync / step_done / done / error event in color.
- Admin sidebar - backend/app/app/admin/layout.tsx sidebar menu adds "🪞 Reflections 灵修 -> /admin/reflections" right after "🏛️ 建筑索引", matching the existing admin page style. AuthGuard is wrapped at the layout level so all admin routes share the existing auth path.
- Debug / idempotency guarantees - per-step log format is uniform: [Step X: name] start force=true/false / [Step X] candidates rows=N / [Step X] reflection_id=N loading Bible Toolbox grounding book_id=X ch=Y / [Step X] reflection_id=N LLM prompt_chars=NNNN / [Step X] reflection_id=N LLM response_chars=NNNN / [Step X] markStepDone reflection_id=N xxx_at=NOW() / [Step X] done processed=N failed=N skipped=N. Idempotency mechanisms: SELECT candidates WHERE step_at IS NULL OR ?=TRUE; INSERT IGNORE / ON DUPLICATE KEY; write sections/verses/faqs via "DELETE then INSERT" (force replaces wholesale); last_step field supports resume from any point.
- Known deferred (M4 / M7+): user-facing frontend (NavBar / Footer / home hero CTA / Most-read block / theme grid / today's reflection / /reflections list / /reflections/[slug] 11-section page with TTS player + JSON-LD / /read "Read the reflection on this chapter" CTA calling /:slug/match), ES reflections index mapping (domain already added, mapping pending), /search join, sitemap addition, bilingual hreflang, cross-linking with read / study / spacetime / architecture / topic. reflection_seeds table is built but not seeded - held for v1.3.
- Changelog bookkeeping: today's 10-table DDL + 10-row theme seed + 21 APIs + 7-step pipeline + admin UI live in 2 SQL files in docs/sql/ and 11 backend module files plus 1 admin page. The system_change_logs entry follows the same docs/mysql/migration_system_change_logs_YYYY-MM-DD-*.sql naming convention as the other changelog migrations but does NOT bundle DDL into the migration - schema and changelog live in separate files of the same dated migration. This entry sits alongside the spacetime entries when listed with ORDER BY log_date DESC. Two commits pushed to master: f15e32da feat(reflections): M1+M2+M3 (10 tables DDL / seed / 21 APIs / 7-step pipeline / RAG grounding), d4f398f8 feat(admin): M5 (admin UI complete three tabs / sidebar / SSE log); tsc --noEmit is green and BOM has been stripped from every new file.
July 24, 2026
Bible-related buildings now in the search index and the spacetime matrix
ArchiveToday we added a new wave of Bible-related buildings and architecture (Solomon's Temple, the Second Temple, the Tower of Babel, Bethlehem, the synagogues, Herod's palace, Ur / Akkad, etc.) as first-class content nodes. They are now indexed by global search and visible on the spacetime matrix pages (the /spacetime gallery and the /channel/spacetimecube) under a new type="building" category. Each building carries scripture references, canonical book(s), and associated topics (sacrifice & ritual, Reformation, Promised Land...) with full Chinese / English localization. Searches like "圣殿 / Temple", "巴别塔 / Tower of Babel", or "伯利恒 / Bethlehem" now return direct hits that open a building detail card. The work reuses the existing era / place / topic dimensional model — no new tables, no new columns.
- Building nodes added: the most prominent Bible buildings / locations (Solomon's Temple, the Second Temple, the Tower of Babel, Bethlehem, the synagogues, Herod's palace, Ur, Akkad, the Capernaum synagogue, ...) are now persisted as type="building" content nodes in the source registry and content tables, fully reusing the existing era / place / topic dimensions.
- Searchable from the top bar: global search (/search) now returns building-class cards alongside courses / passages / topics — queries like "圣殿 / Temple", "巴别塔 / Tower of Babel", "伯利恒 / Bethlehem", or "会堂 / Synagogue" hit the corresponding building node with an icon, the canonical book(s), and a one-line summary.
- Visible on the spacetime matrix: every building is auto-placed at the matching era × place × topic coordinates. The /spacetime gallery now exposes a new "Buildings" tab under the type filter, and the /channel/spacetimecube 3D view highlights building nodes with a distinct icon and color while keeping the same hero / dust rendering pipeline as the existing era / topic / geo nodes.
- Detail card: clicking a building node opens a card with scripture references (book + chapter range, e.g. 1 Kings 5–8), the canonical book, the associated topics (Sacrifice & Ritual, Reformation, Promised Land, ...), and two jump links — "View in scripture" (→ /read) and "View on the map" (→ the map view under /spacetime).
- Bilingual: building names, summaries, scripture references, and topics all switch between Chinese and English through the same i18n pipeline (t() + locale-aware data fetch) as the rest of the app.
- Zero schema cost: implemented on top of the existing era / place / topic dimensional model and the source-registry ingestion path. No new tables, no new columns — the type="building" marker is enough to distinguish the category, and the search index plus the spacetime matrix both consume the same content query API, so there is no risk of double-write drift.
July 23, 2026
Elasticsearch takes over search indexing and content retrieval
ArchiveToday BibleVibe moved the core search stack from Qdrant to Elasticsearch while keeping MySQL as the source of truth. New and expanded ES domains now cover books, chapters/scripture, theology terms, commentary, courseware, dialogs, chats, images/media, and public-study content. The backend now exposes Elasticsearch through SearchIndexService instead of binding business code directly to an ES SDK, while the admin index-management page supports per-domain Sync and Rebuild all flows with idempotent batching, persisted progress, visible logs, and fail-fast error handling. The frontend search, study, stream, topics, today, and spacetime surfaces were also adjusted so scriptures, terms, books, chapters, questions, and related cards resolve through unified data helpers and respect the current locale.
- Elasticsearch infrastructure: added Elasticsearch service, SearchIndexService abstraction, index constants, and typed search-index contracts so business modules use a unified search layer instead of talking directly to an SDK.
- MySQL remains the source of truth: Elasticsearch is used as the search and retrieval accelerator, with books, chapters/scripture, terms, commentary, courseware, dialogs, chats, resource images, and public-study content built from MySQL or generated source content.
- Admin index management upgrade: /admin/index-management now includes ES domain coverage, per-domain Sync, implemented-domain Sync, Rebuild all, live progress, built / changed / unchanged / deleted counts, and visible error reporting.
- Idempotent incremental sync: Sync is no longer treated as a blind full rebuild. It uses source fingerprints, document hashes, and persisted progress so unfinished or changed content can continue safely; Rebuild all remains the explicit force-refresh path.
- Batched ES and DB persistence: long-running domains such as Chapter / Scripture, Terms, Commentary, and Book now update progress while running instead of waiting for the entire embedding job to finish before recording state.
- Stricter error handling: if any ES domain sync fails, backend debug/error logs and the admin UI both surface the error and the current queue stops immediately rather than silently continuing in a bad state.
- Embedding service abstraction: embedding calls were centralized in EmbeddingService, with EMBEDDING_PROVIDER provider chains such as minimax,together and shared truncation, retry, fallback, and debug logging behavior.
- QdrantService removal: direct Nest dependencies on QdrantService were removed or rewritten, moving search and indexing paths toward Elasticsearch.
- Search UX upgrade: /search now includes a Relevant Scriptures section, scripture hits link into the matching /study?type=scripture page, and book-level hits are merged into Relevant Books and Chapters.
- Scripture study mode fixes: /study?type=scripture supports a full left-side verse list for the selected chapter, URL-selected verses, stable left-panel selection without unwanted scrolling, and Thinking / 思考中 loading when switching verses.
- Locale consistency: stream, topics, living stream, today, eras, geo, search, and cards for terms/scripture/book/chapter/question/thread/compare now use shared localization helpers to avoid mixed Chinese and English display.
- Book summary indexing: whole-book study guide content is flattened from book_study_guide.guide_json into bible_books.study_guide_text_zh and bible_books.study_guide_text_en so searches can hit a book by its generated summary or study-guide text.
- Images / Media and Public Study domains: resource_images and public_study_content are covered by admin-triggered sync and status reporting, preparing image, media, and public-study content for ES-backed retrieval.
- Commit record: the ES migration and related frontend/backend changes were committed as 4f1a1525 with the message Migrate search indexing to Elasticsearch.
July 23, 2026
Search Media Merge · /me Growth Path Media · Mobile Landscape Polish
ArchiveMerged film cards into the search page multi-media section, sorted by ES score alongside courseware; added dynamic media recommendations per growth-path stage on /me with cross-stage deduplication and independent expand; mobile dock hides in landscape mode.
- Search multi-media: films + courseware mixed by ES score, 6 per page, scroll-to-load-more
- Film cards removed from Home, Stream, Topic, Spacetime, Read — search page only
- /me growth path: each stage fetches dynamic media recommendations (film + courseware) via /api/v1/search/bible, merged into a single Watch & Explore strip
- Cross-stage deduplication: a film or courseware only appears in the earliest stage that claims it
- Stages are now independently expandable — opening one no longer collapses others
- Mobile dock hidden in landscape via @media (orientation:landscape)
- Spacetime custom landscape toggle: adds body.spacetime-landscape class → CSS hides dock; class cleaned up on unmount
July 20, 2026
Spacetime gallery + admin-era/geo management: every era and every place now has its own cover, its own page
ArchiveOn top of the 7-19 spacetime-cube launch and the 7-20 v1.1 polish, today the bible_eras / bible_geolocations master data is promoted from 3D nodes + backend master rows into a fully browsable, viewable, AI-illustrated spacetime gallery experience. Three pieces: (1) on the admin side, two new management lists - Eras and Geolocations - show every record with its metadata (era range / verse count / linked books / modern-place coordinates etc.) and a one-click AI image generator; the result is written back to the same row as the era or geolocation cover image. (2) A new frontend route /spacetime renders a gallery where every era card and every geolocation card are interleaved on a time-axis x place-axis two-column layout; each card shows the name, era range or modern place, AI cover image, and key books - making the spacetime interleave of the Bible narrative visually obvious. (3) Clicking any card deep-links into /spacetime/[type]/[slug] where [type] is era or geo and [slug] is era_key or geo_key, and the same mixed content stream (books, passages, courseware, immersive narratives, visual stories) is rendered below and can be cross-filtered by the other dimension: pick an era, filter by geolocation; pick a geolocation, filter by era. This new surface runs alongside the existing /channel/spacetimecube 3D view and the Read / Study paths, acting as the flat counterpart of the same dimensional data.
- Admin - Eras list: a new /admin/eras page renders every bible_eras row with its era_key, name, time range, book_era_map count, content_eras count. Each row has a view-details action that opens the full metadata and the linked content preview for that era.
- Admin - Geolocations list: a sibling /admin/geolocations page renders every bible_geolocations row with its geo_key, ancient and modern names, lat-lng, bible_geolocation_relations count, content_geolocations count, again with a view-details action.
- Admin - One-click AI cover image: every era and every geolocation row gets an AI generate image button. Clicking it calls the backend image-generation pipeline with a prompt built from the era description and its key books (e.g., Patriarchal Era - Genesis 12-50) or from the geolocation ancient/modern names and its linked passages. The generated image URL is written back to the row image_url column and becomes the cover used on /spacetime.
- /spacetime gallery page: a brand-new frontend route /spacetime renders a two-column interlocked layout. The left column scrolls vertically through every era from creation to consummation; the right column scrolls through every geolocation in sync; matched-era-and-geolocation cards light up with crossfades and connecting lines, producing the spacetime interleave feel.
- Card content: every era card shows name, time range, AI cover image, 2-4 key books, and a one-paragraph summary; every geolocation card shows the ancient + modern name, AI cover image, linked-verse count, and modern coordinates when available. The tradition toggle (Protestant / Catholic / Judaism) filters both columns live.
- Click-through to detail: clicking any card deep-links to /spacetime/[type]/[slug] where [type] is era or geo. The detail page header shows the full info for that era or geolocation: AI cover image, summary, time range or modern name, key books or key passages.
- Filterable content stream below: below the header, the detail page uses the same mixed-content list component shared with Read / Study. It sequentially renders the linked books, priority (p0/p1) passages, related courseware, related immersive narratives, and related visual stories - each item has a type badge and a jump link.
- Cross-dimension filtering: at the top of the detail page, a filter bar exposes the opposite dimension (era or geolocation) as dropdowns and quick-tag chips. From any entry point the user can swap to a precise combination - e.g., from the Patriarchal Era page, switch the geolocation filter to Canaan to land directly on Patriarchal Era + Canaan content.
- Data reuse with the existing dimensional layer: eras and geolocations still come from bible_eras / bible_geolocations; the linked content still comes from bible_era_relations / bible_geolocation_relations / content_eras / content_geolocations / book_era_map / book_geo_map - all of which were already delivered in the 7-18 / 7-19 series of migrations, so todays work requires no new DDL.
- Composes with the rest of the spacetime surface: /channel/spacetimecube remains the global 3D node cloud view, the new /spacetime is the browseable / clickable / filterable catalog view; the two are bidirectionally linked from every era and geolocation card and share the same dimensional data.
- Changelog bookkeeping: the new admin pages, the /spacetime gallery, the /spacetime/[type]/[slug] detail page, and the AI-image backfill are all recorded as a single standalone INSERT in system_change_logs dated 2026-07-20, sitting alongside todays earlier spacetimecube v1.1 entry when listed by ORDER BY log_date DESC.
July 19, 2026
Spacetime cube + whole-book study: every Bible entry now has era × theme × place, and every book now has a cover-to-cover study path
ArchiveToday's two main pieces of work move BibleVibe from "a passage-search tool" toward "a three-dimensional Bible system organized by book." First, the Spacetime Cube (channel/spacetimecube) shipped: the era × theme × geolocation axes are now first-class in both the database (bible_eras, bible_geolocations, content_eras, content_geolocations) and the front-end, so books, passages, theology terms, and courseware items all carry an era_key / topic_key / geo_key and live as dots in a rotatable, zoomable 3D cube. Second, whole-book study: every book now has a populated overview, writing purpose, classic passages, and key themes, and the Spacetime Cube acts as the natural entry point into a continuous Read / Study flow that lets users move seamlessly between book, chapter, and book-level study.
- Added bible_geolocations and bible_geolocation_relations as first-class data: modern place names and per-(book, chapter-range) place mappings live in their own tables, so every passage, book, and theology term can be resolved to a specific place on the map.
- Added bible_eras / bible_era_relations (the era axis), linked courseware to topic_groups, and introduced content_eras / content_geolocations so multimedia content also has dimension tags. book_era_map and book_geo_map provide the book-level fallback so any passage can resolve to era + topic + place.
- Shipped the front-end route /channel/spacetimecube: an interactive 3D cube where X = era (creation to consummation), Y = theme (origins, patriarchs, exodus, ..., apocalypse), Z = place (Eden, Babel, Jerusalem, ...). Each dot is a passage / term / courseware item; users can rotate, zoom, and click for details.
- Backfilled the cube from existing data: every courseware, immersive_narrative, and visual_story got era / topic / geo tags derived from bible_geolocation_relations and book_geo_map, so the cube is populated on day one without any new content.
- Added a per-book "Whole-book study" entry on the Read / Study pages: every book now shows an Overview panel (summary, writing purpose, classic passages, key themes) before the chapter list, giving users a "see the whole book first, then drill in" path.
- Added a dedicated book-level Study mode: pick a book and walk through it chapter by chapter in canonical order, with per-chapter summary / key verses / reflection questions / cross-chapter links, so a reader can finish an entire book in one focused session.
- Connected the Spacetime Cube and book study: clicking a book entry inside the cube opens the book study page directly, and the book study page also offers a "view this book in the Spacetime Cube" jump — both views share the same era / topic / geo data.
- All changes are recorded as a single standalone INSERT in system_change_logs dated 2026-07-19; the backend listing just needs `ORDER BY log_date DESC` to surface today's entry alongside the rest of the changelog timeline.
July 18, 2026
Read and Study refreshed around classic entry points
ArchiveThis update refreshes the Read and Study experience around the Bible's most classic books, chapters, passages, and terms. Study now supports immediate entry, default recommendation cards, and friendlier loading feedback; Read now supports a no-parameter browse hub, banner-integrated search, tradition filtering, and My Reading for signed-in users. The overall goal is to reduce the friction of “where should I start?” and make it easier for users to begin with classic content, then continue into sustained reading and deeper study.
- Clicking Study now enters the page immediately instead of waiting for content preparation first; once inside, both panes show friendlier loading states and the AI panel gives clearer “thinking” feedback.
- Study now supports a default entry mode: when no specific passage, term, or question is provided, the system surfaces classic recommendation cards drawn from p0 / p1 scripture, term, and question content so users can start studying right away.
- Classic entry points are now more prominent: scripture cards can lead into either Study or Read, so users can begin with deeper study or first step back into direct reading from classic books and chapters.
- Read now supports a no-parameter entry state and presents classic p0 / p1 books and priority chapters grouped by book, making it easier to begin from the most foundational content.
- The Read page search area is now integrated into the banner, with book-name input, chapter autocomplete, and tradition filtering; selecting a suggestion opens Read directly, while the search button routes to Search.
- Signed-in users now see My Reading on the default Read page, combining personal reading history with learner-assessment recommendations; this entry is SSR-rendered and cached for 10 minutes to improve first-load stability.
- In dialog-based Study pages, source references such as Genesis 1:26 can now deep-link directly into Read with the correct book, chapter, verse-range, and tradition parameters, making the transition between reading and study feel more natural.
July 18, 2026
Era system connected across data and major surfaces
ArchiveThis update establishes a full era-based layer across both data and major user-facing surfaces. On the backend, it introduces core relationships such as bible_eras, bible_era_relations, and content_eras, and adds approx_year so a shared era dataset can classify books, chapters, passages, courseware, immersive narratives, and visual stories. On the frontend, eras are now wired into major surfaces including Read, Search, Stream, Topics, Interactive, and Study, enabling era filters, era badges on cards, and locale-aware era labels so biblical eras become a consistent site-wide organizing dimension rather than an isolated tag.
- Introduced bible_eras as the canonical era dataset with fields such as era_key, slug, sort_order, title_zh, title_en, and approx_year.
- Introduced bible_era_relations to persist representative_book and key_passage mappings from eras to books, chapters, and verses, enabling precise routing through system book and passage ids.
- Introduced content_eras as a dedicated relation table for courseware, immersive narratives, and visual stories, instead of overloading topic mappings with era semantics.
- Wired era filters into Read under the tradition filter; the no-parameter Read entry is now organized by eras, and both key passages and supplemental books align to internal book / chapter / verse identifiers.
- Wired era filtering and era badges into Stream, Topics, Search, and Interactive; when a topic or channel has no content for certain eras, those empty era filters are hidden automatically.
- Added era badges across major content cards and standardized them to the darker tradition-like style; badge text is locale-aware so cards no longer mix languages or show duplicate era labels.
- Added era / year context to the Study left panel and related-content areas, while also removing multiple hardcoded stage-name paths in favor of sourcing titles and ordering from bible_eras.
July 17, 2026
Study page adds follow-up prompt chips
ArchiveThe Study page now shows follow-up prompt chips under every AI reply — What/Why/When/Where/How/What-if — so a click lets the AI dig deeper into what it just said. Making the follow-ups actually build on prior context surfaced and fixed two gaps: the chat never sent conversation history to the model at all, and the anonymous chat limit was effectively unenforced.
- Added follow-up prompt chips under Study page AI replies — What/Why/When/Where/How/What-if — so a click lets the AI dig deeper into what it just said.
- Fixed a gap where the study chat never sent conversation history to the model at all — follow-ups now genuinely build on the AI's prior answer instead of starting from scratch every time.
- Anonymous users' chat limit is now enforced server-side against a persisted per-visitor id, instead of a browser session counter that a page refresh could reset.
- Once a follow-up button is clicked it stays gone for the rest of that conversation; its label and the question it sends both follow whatever page language is active at the moment of the click, not whichever language was active when the reply first loaded.
July 16, 2026
Changelog page launched + Bible Toolbox data integration
ArchiveThis day launched the changelog page, integrated four real public-domain data sources.
- Added a dedicated changelog page that presents key daily updates in a timeline format.
- Added a changelog link in the footer so it is reachable from anywhere on the site.
- Moved updates into database-backed records so future daily entries can be maintained with a single insert or update.
- Added a Strong's lexicon (Greek G0001-G5624, Hebrew H0001-H8674) and word-level original-language tagging (macula), sourced from mybibletoolbox-data (MIT/CC-BY-SA).
- Added Treasury of Scripture Knowledge cross-references (~340k entries), sourced from the scrollmapper/bible_databases public-domain mirror (MIT).
- Added Matthew Henry and Jamieson-Fausset-Brown public-domain commentary via the HelloAO API, tiered P0/P1/P2/P3/P4/P5 with safe incremental re-fetching.
- Theological term extraction now grounds its output in this real data and records a source_citation, cutting down on the LLM inventing original-language words or definitions from nothing.
- The study page's scripture view now shows real cross-references; a new bible_commentary semantic index lets free-form questions surface real commentary excerpts, not just exact chapter lookups.
- Added a "Bible Toolbox" admin page to trigger and monitor these data sources; QD index backfill/rebuild for them was consolidated into the existing Index Management page.