diff --git a/kjvstudy_org/routes/api.py b/kjvstudy_org/routes/api.py
index 9434240..7146318 100644
--- a/kjvstudy_org/routes/api.py
+++ b/kjvstudy_org/routes/api.py
@@ -14,7 +14,7 @@ from ..utils.pdf import render_html_to_pdf, render_html_to_pdf_async, WEASYPRINT
from ..kjv import bible
from ..cross_references import get_cross_references
from ..reading_plans import get_plan, get_plan_summary
-from ..topics import get_all_topics, get_topic
+from ..topics import get_all_topics, get_topic_with_text
from ..interlinear_loader import get_interlinear_data, has_interlinear_data
from ..utils.books import normalize_book_name, OT_BOOKS
from ..utils.search import perform_full_text_search
@@ -1230,7 +1230,7 @@ async def api_get_topics():
@router.get("/topics/{topic_name}")
async def api_get_topic(topic_name: str = Path(..., description="Topic name", example="faith")):
"""Get details about a specific topic."""
- topic = get_topic(topic_name)
+ topic = get_topic_with_text(topic_name)
if not topic:
raise HTTPException(status_code=404, detail="Topic not found")
diff --git a/kjvstudy_org/server.py b/kjvstudy_org/server.py
index 3186e33..46362de 100644
--- a/kjvstudy_org/server.py
+++ b/kjvstudy_org/server.py
@@ -21,7 +21,7 @@ from starlette.middleware.base import BaseHTTPMiddleware
from .kjv import bible, VerseReference
from .cross_references import get_cross_references
from .reading_plans import get_plan, get_all_plans, get_plan_summary
-from .topics import get_all_topics, get_topic, search_topics
+from .topics import get_all_topics, get_topic_with_text, search_topics
from .interlinear_loader import get_interlinear_data, has_interlinear_data, get_all_interlinear_verses, preload_data, find_verses_by_strongs, count_strongs_occurrences
from .strongs import format_strongs_entry, search_strongs, get_all_strongs
from .books import get_book_data, has_book_data
@@ -1880,7 +1880,7 @@ async def resources_page(request: Request):
async def topic_detail(request: Request, topic_name: str):
"""View verses for a specific topic"""
books = bible.get_books()
- topic = get_topic(topic_name)
+ topic = get_topic_with_text(topic_name)
if not topic:
raise HTTPException(status_code=404, detail="Topic not found")
@@ -1914,7 +1914,7 @@ async def topic_detail_pdf(topic_name: str):
detail="PDF generation is not available. WeasyPrint system libraries are not installed."
)
- topic = get_topic(topic_name)
+ topic = get_topic_with_text(topic_name)
if not topic:
raise HTTPException(status_code=404, detail="Topic not found")
diff --git a/kjvstudy_org/templates/topic_detail.html b/kjvstudy_org/templates/topic_detail.html
index a067ba4..b098d30 100644
--- a/kjvstudy_org/templates/topic_detail.html
+++ b/kjvstudy_org/templates/topic_detail.html
@@ -153,33 +153,45 @@
{% for verse in subtopic_data.verses %}
- {% set ref_parts = verse.ref.rsplit(' ', 1) %}
- {% if ref_parts|length == 2 %}
- {% set book_name = ref_parts[0] %}
- {% set chapter_verse = ref_parts[1] %}
- {% if ':' in chapter_verse %}
- {% set ch = chapter_verse.split(':')[0] %}
- {% set v = chapter_verse.split(':')[1] %}
- {% if '-' not in v %}
- {# Single verse - make it a link #}
- {{ verse.ref }}
+ {% set reference = verse.reference if verse.reference is defined else (verse.ref if verse.ref is defined else verse) %}
+ {% if reference is not string %}
+ {% set reference = reference|string %}
+ {% endif %}
+ {% set reference = reference.strip() %}
+ {% if reference %}
+ {% set ref_parts = reference.rsplit(' ', 1) %}
+ {% if ref_parts|length == 2 %}
+ {% set book_name = ref_parts[0] %}
+ {% set chapter_verse = ref_parts[1] %}
+ {% if ':' in chapter_verse %}
+ {% set ch = chapter_verse.split(':')[0] %}
+ {% set v = chapter_verse.split(':')[1] %}
+ {% if '-' not in v %}
+ {# Single verse - make it a link #}
+ {{ reference }}
+ {% else %}
+ {# Verse range - link to chapter with anchor #}
+ {% set v_start = v.split('-')[0] %}
+ {% set v_end = v.split('-')[1] %}
+ {{ reference }}
+ {% endif %}
{% else %}
- {# Verse range - link to chapter with anchor #}
- {% set v_start = v.split('-')[0] %}
- {% set v_end = v.split('-')[1] %}
- {{ verse.ref }}
+ {# No colon, just display text #}
+ {{ reference }}
{% endif %}
{% else %}
- {# No colon, just display text #}
- {{ verse.ref }}
+ {{ reference }}
{% endif %}
- {% else %}
- {{ verse.ref }}
{% endif %}
- {% if verse.note %}
+ {% if verse.note is defined and verse.note %}
— {{ verse.note }}
{% endif %}
+ {% if verse.text is defined and verse.text %}
+
+ {{ verse.text }}
+
+ {% endif %}
{% endfor %}
diff --git a/kjvstudy_org/templates/topic_pdf.html b/kjvstudy_org/templates/topic_pdf.html
index b43ea55..f1576f8 100644
--- a/kjvstudy_org/templates/topic_pdf.html
+++ b/kjvstudy_org/templates/topic_pdf.html
@@ -97,8 +97,17 @@
{% for verse in subtopic.verses %}
-
- {{ verse.ref }}
- {% if verse.note %}— {{ verse.note }}{% endif %}
+ {% set reference = verse.reference if verse.reference is defined else (verse.ref if verse.ref is defined else verse) %}
+ {% if reference is not string %}
+ {% set reference = reference|string %}
+ {% endif %}
+ {{ reference }}
+ {% if verse.note is defined and verse.note %}— {{ verse.note }}{% endif %}
+ {% if verse.text is defined and verse.text %}
+
+ {{ verse.text }}
+
+ {% endif %}
{% endfor %}
diff --git a/kjvstudy_org/topics.py b/kjvstudy_org/topics.py
index 3d0a25f..f06404a 100644
--- a/kjvstudy_org/topics.py
+++ b/kjvstudy_org/topics.py
@@ -4,9 +4,12 @@ Organized by major theological and practical topics.
"""
import json
+from functools import lru_cache
from pathlib import Path
+from typing import Dict, Any
+@lru_cache(maxsize=1)
def _load_topics():
"""Load topics from per-topic JSON files, fallback to legacy single file."""
base_dir = Path(__file__).parent / "data"
@@ -27,17 +30,78 @@ def _load_topics():
return aggregated
-TOPICS = _load_topics()
+def _get_verse_text_for_reference(reference: str) -> str:
+ """Look up a single verse text from a reference like 'John 3:16'."""
+ if not reference or not isinstance(reference, str):
+ return ""
+
+ parts = reference.rsplit(" ", 1)
+ if len(parts) != 2 or ":" not in parts[1]:
+ return ""
+
+ book = parts[0]
+ chapter_part, verse_part = parts[1].split(":", 1)
+ # Use the first verse in a range (e.g., 3:16-17 -> 3:16)
+ verse_segment = verse_part.split("-")[0]
+
+ try:
+ from .kjv import bible
+ return bible.get_verse_text(book, int(chapter_part), int(verse_segment)) or ""
+ except Exception:
+ return ""
+
+
+def _enrich_topic_with_text(topic: Dict[str, Any]) -> Dict[str, Any]:
+ """Attach verse text (and normalized reference fields) to a topic's verses."""
+ subtopics = topic.get("subtopics", {})
+ enriched_subtopics = {}
+
+ for subtopic_name, subtopic_data in subtopics.items():
+ verses_with_text = []
+ for entry in subtopic_data.get("verses", []):
+ if isinstance(entry, dict):
+ reference = entry.get("reference") or entry.get("ref") or ""
+ note = entry.get("note", "")
+ else:
+ reference = str(entry)
+ note = ""
+
+ verse_text = _get_verse_text_for_reference(reference)
+ verses_with_text.append({
+ "reference": reference,
+ "ref": reference,
+ "text": verse_text,
+ "note": note
+ })
+
+ enriched_subtopics[subtopic_name] = {
+ **subtopic_data,
+ "verses": verses_with_text
+ }
+
+ return {
+ **topic,
+ "subtopics": enriched_subtopics
+ }
def get_all_topics():
"""Get all topics"""
- return TOPICS
+ return _load_topics()
def get_topic(topic_name: str):
"""Get a specific topic"""
- return TOPICS.get(topic_name)
+ return _load_topics().get(topic_name)
+
+
+@lru_cache(maxsize=None)
+def get_topic_with_text(topic_name: str):
+ """Get a topic with verse text attached to each reference."""
+ topic = _load_topics().get(topic_name)
+ if not topic:
+ return None
+ return _enrich_topic_with_text(topic)
def search_topics(query: str):
@@ -45,7 +109,7 @@ def search_topics(query: str):
query_lower = query.lower()
results = []
- for topic_name, topic_data in TOPICS.items():
+ for topic_name, topic_data in _load_topics().items():
if query_lower in topic_name.lower() or query_lower in topic_data.get("description", "").lower():
results.append({
"name": topic_name,