The Rust HTTP core always returns application/json content-type
regardless of the content_type field in the response dict. This is a
fundamental limitation of turboAPI's current Rust server - it was
designed for JSON APIs, not template-rendered HTML sites.
HTML content is correctly generated but served with wrong content-type,
causing browsers to download instead of render pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
turboAPI passes query string params as kwargs to handlers. Add **kwargs
to static file handlers so cache-bust params like ?v=30f664df don't
cause 500 errors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
turboAPI's Rust server doesn't serve mounted StaticFiles, so add
explicit routes for /static/{file} and /static/{subdir}/{file}.
Also patch ResponseHandler to properly extract body/content-type
from Response objects instead of JSON-serializing them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Monkey-patch turboAPI's route registration to wrap handlers that expect
a Request parameter with a Starlette-compatible Request object built
from the Rust server's raw HTTP data. Also skip unsupported middleware
during Rust server initialization instead of failing.
This enables all HTML template routes to work alongside the JSON API
routes under turboAPI's Zig/Rust HTTP core.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace FastAPI with turboAPI across the entire codebase:
- Swap all imports from fastapi to turboapi (TurboAPI, APIRouter, HTTPException, etc.)
- Keep starlette imports for Jinja2Templates and StaticFiles (not yet in turboapi)
- Remove unsupported `example=` params from Query/Path declarations
- Update entry point to use turboapi's native server (app.run())
- Update test clients to use turboapi.testclient.TestClient
The app loads successfully with all 200+ routes registered. turboAPI's
Zig HTTP core provides significant throughput improvements over uvicorn.
Note: turboAPI's TestClient has limited support for Request injection
and Query defaults, causing some test failures. The actual server
runtime handles these correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Search for common terms like "love" returned 363+ results, causing
the Jinja2 template to render 222KB of HTML (~8s on shared CPU).
Capping at 50 results drops render time to ~0.4s locally.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Pre-render ~1,277 high-traffic HTML pages (homepage, books, chapters)
at build time and serve them directly via nginx. All other routes
(verses, search, API, PDFs, Strong's) fall through to a FastAPI
sidecar. If the sidecar crashes, nginx continues serving static
pages and health checks.
Also harden the FastAPI app against the memory/crash issues:
- Switch from bare uvicorn to gunicorn with uvicorn workers
- Add --max-requests worker recycling to prevent memory leaks
- Add --timeout to kill hung workers
- Add per-IP rate limiting middleware (10 req/s, burst of 50)
- Add request timeout middleware (30s max per request)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Downscale VMs from performance-2x/4GB to shared-cpu-2x/2GB
- Set min_machines_running to 0 for autosleep
- Disable interlinear preload to prevent OOM kills
- Reduce workers from 4 to 2
- Add crawl-delay of 5s and block PDF/old verse-of-the-day crawling
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Wrapped blocking file I/O and CPU-bound operations with
asyncio.to_thread() to prevent blocking the event loop:
- about.py: stats() and cross_references_index() now compute
in thread pool (extensive JSON loading and iteration)
- commentary.py: commentary_index() file I/O in thread pool
- misc.py: OG image fallback read_bytes() in thread pool
These routes perform heavy file I/O (reading 66+ JSON files,
iterating 31k verses) which would block all other requests
if run in the async context directly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Pillow image generation is CPU-bound, so run it in thread pool
via asyncio.to_thread() to avoid blocking the event loop.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- New og_image.py module generates custom 1200x630 social images
- Images include verse title, subtitle, verse text, and branding
- Caches generated images to disk for performance
- Routes: /og/verse/, /og/chapter/, /og/book/, /og/topic/, /og/story/, /og/guide/
- Updated templates: verse, chapter, book, topic, story, study guide
- Images use Georgia serif font matching site typography
- Cream background (#fffff8) and green accent (#4a7c59)
When shared on social media, pages now show custom preview images
with the actual verse text instead of the generic site image.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add BreadcrumbList JSON-LD schema to verse, chapter, book, topic,
study guide, and story detail pages for rich SERP breadcrumbs
- Add Book schema to book pages with chapter count and authorship
- Add Article schema to topic detail pages
- All key content pages now have proper structured data for search
engines to understand site hierarchy
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added missing mobile media query to make max-width elements
(description, verse text, intro text) expand to 100% on mobile,
preventing the zoomed-out 55% width issue.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The epigraph on the homepage now displays plain verse text without
the link_names filter, keeping the display cleaner.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Removed 55% max-width constraints on page-header, breadcrumb, and
chapter-nav so they span the full width of the interlinear-page
container (90%).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added explicit [data-theme="dark"] styles for the word detail popup
which was showing a white background on dark theme.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Complete rewrite of all 104 verses with:
- Greek terms with transliterations throughout
- Special depth for Christ hymn (2:5-11) - kenosis theology
- Direct verse quotes in <strong> tags
- 2-3 paragraph analysis per verse
- Historical context (Roman colony, prison setting)
- Key themes: joy in suffering, pressing toward mark,
citizenship in heaven, contentment in Christ
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Complete rewrite of all 256 verses with:
- Greek terms with transliterations throughout
- Direct verse quotes in <strong> tags
- 2-3 paragraph analysis per verse
- Historical context (painful visit, super-apostles, collection)
- 3 verse-specific reflection questions
- Key themes: comfort in affliction, new covenant glory,
treasure in earthen vessels, reconciliation, grace in giving,
spiritual warfare, thorn in flesh, sufficient grace
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Remove invalid verse entries that exceeded actual Bible verse counts:
- 1 Chronicles 13:15-25 (11 entries) - chapter only has 14 verses
- 1 Chronicles 15:30-52 (23 entries) - chapter only has 29 verses
- Acts 12:28 (1 entry) - chapter only has 25 verses
- Ezra 3:14 (1 entry) - chapter only has 13 verses
This brings commentary from 31,138 to exactly 31,102 verses (100% coverage).
Also includes enhanced Romans commentary with deeper theological analysis,
Greek word studies, and improved reflection questions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Detects generic templated questions, boilerplate historical
sections, missing Greek/Hebrew terms, short analysis, and
templated patterns
- Found 1,911 verses with severe issues (3+ problems)
- All affected books are Pauline epistles
- Export list of problem verses to scripts/shallow_verses.txt
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added Greek terms with transliterations (ēgapēsa, emisēsa, kat' eklogēn prothesis)
- Explained Hebrew idiom of 'love/hate' as preferential choice
- Connected to Malachi 1:2-3 source and context
- Reformed theological perspective on unconditional election
- Historical context from Augustine through Reformation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>