Reader: whole-Bible search in every translation
Build and Push Docker Image / build-and-push (push) Failing after 20s

BSB and NET are indexed in the browser; ESV, NLT, CSB and NASB are searched
through a server proxy (keys stay server-side). Results page in 25 at a time
and jump to the verse. Also fixes a stray trailing comma in CSB chapter text.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-09-29 09:25:00 -04:00
1 parent 3279dddae5
commit c11b6a60bf
8 files changed
+262 -55

No files matched your search

+26 -21
View File
@@ -62,7 +62,6 @@ export function useBibleReader(currentPage) {
const [readerSearch, setReaderSearch] = useState('');
const [readerSearchActive, setReaderSearchActive] = useState(false);
const [readerSearchScope, setReaderSearchScope] = useState('chapter'); // 'chapter' | 'bible'
const [bibleIndexStatus, setBibleIndexStatus] = useState('idle'); // 'idle' | 'loading' | 'ready' | 'error'
const _bibleIndexCacheRef = useRef({}); // translation -> flat verse array
const [readerInkByPage, setReaderInkByPage] = useState(() => {
try { return JSON.parse(localStorage.getItem('readerInkByPage') ?? '{}'); }
@@ -77,27 +76,33 @@ export function useBibleReader(currentPage) {
// Debounce timer ref so rapid stroke updates don't flood the server
const inkSaveTimerRef = useRef({});
// The HelloAO API has no search endpoint, so whole-Bible search fetches the
// entire translation once (~7MB) and searches an in-memory flat verse index
// client-side. Cached per translation for the rest of the session (not persisted
// across reloads, to avoid eating into localStorage/IndexedDB quota for a cache
// that's cheap enough to rebuild once per visit).
const loadBibleIndex = async () => {
if (_bibleIndexCacheRef.current.BSB) {
setBibleIndexStatus('ready');
return;
}
setBibleIndexStatus('loading');
// The HelloAO API has no search endpoint, so whole-Bible search for the free
// translations (BSB, NET) fetches the entire translation once (~7MB) and searches
// an in-memory flat verse index client-side. Cached per translation for the rest of
// the visit, and in IndexedDB for a while. Licensed translations use the server
// search proxy instead (see useBibleSearch).
const [bibleIndexState, setBibleIndexState] = useState({ translation: null, status: 'idle' });
const bibleIndexStatusFor = (translation) => {
if (_bibleIndexCacheRef.current[translation]) return 'ready';
return bibleIndexState.translation === translation ? bibleIndexState.status : 'idle';
};
const loadBibleIndex = async (translation = 'BSB') => {
const set = (status) => setBibleIndexState({ translation, status });
if (_bibleIndexCacheRef.current[translation]) { set('ready'); return; }
const source = READER_TRANSLATIONS.find((t) => t.id === translation);
if (!source || source.licensed) { set('error'); return; }
set('loading');
try {
// Check IndexedDB cache before hitting the network (~7 MB fetch)
const cached = await getBibleIndexFromIDB('BSB');
const cached = await getBibleIndexFromIDB(translation);
if (cached?.flat && Date.now() - cached.cachedAt < BIBLE_IDB_TTL) {
_bibleIndexCacheRef.current.BSB = cached.flat;
setBibleIndexStatus('ready');
_bibleIndexCacheRef.current[translation] = cached.flat;
set('ready');
return;
}
const res = await fetch('https://bible.helloao.org/api/BSB/complete.json');
const res = await fetch(`https://bible.helloao.org/api/${source.helloao ?? source.id}/complete.json`);
if (!res.ok) throw new Error('Unable to load the full Bible for search.');
const data = await res.json();
const flat = [];
@@ -111,12 +116,12 @@ export function useBibleReader(currentPage) {
}
}
}
_bibleIndexCacheRef.current.BSB = flat;
setBibleIndexStatus('ready');
_bibleIndexCacheRef.current[translation] = flat;
set('ready');
// Persist for future visits — non-blocking
saveBibleIndexToIDB('BSB', flat);
saveBibleIndexToIDB(translation, flat);
} catch {
setBibleIndexStatus('error');
set('error');
}
};
@@ -381,7 +386,7 @@ export function useBibleReader(currentPage) {
readerTextHighlights,
addTextHighlight,
removeTextHighlight,
bibleIndexStatus,
bibleIndexStatusFor,
loadBibleIndex,
_bibleIndexCacheRef,
loadReaderChapter,