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

+14 -7
View File
@@ -6,6 +6,7 @@ import { bookOptions } from '../utils/bibleUtils.jsx';
import { getPlan, getPlanSchedule, findDayIndexForRef, formatDayReadings } from '../utils/readingPlans.js';
import DrawCanvas from './DrawCanvas.jsx';
import { READER_TRANSLATIONS } from '../hooks/useBibleReader.js';
import { useBibleSearch } from '../hooks/useBibleSearch.js';
import { themeVars } from '../components/reader/readerThemes.js';
import { TopBar, BottomBar } from '../components/reader/ReaderBars.jsx';
import ReaderText from '../components/reader/ReaderText.jsx';
@@ -45,7 +46,7 @@ export default function ReaderPage() {
readerSearchScope, setReaderSearchScope,
readerAudioState,
audioProgress,
bibleIndexStatus,
bibleIndexStatusFor,
audioNarrator, setAudioNarrator,
_bibleIndexCacheRef,
readerGoToPreviousChapter,
@@ -142,11 +143,15 @@ export default function ReaderPage() {
return m.done ? null : { label: 'Finish plan', done: false, onClick: markDone };
}, [planMatches]); // eslint-disable-line react-hooks/exhaustive-deps
// Whole-Bible search: lowercase the ~31k-verse index once (not per keystroke),
// and let typing stay responsive while results catch up via useDeferredValue.
const bibleIndex = bibleIndexStatus === 'ready' ? (_bibleIndexCacheRef.current.BSB ?? null) : null;
// Whole-Bible search. BSB and NET are indexed in the browser (lowercased once, not per
// keystroke, with typing kept responsive via useDeferredValue); ESV, NLT, CSB and NASB
// go through the server's search proxy.
const localSearch = READER_TRANSLATIONS.find((t) => t.id === readerTranslation)?.licensed !== true;
const indexStatus = bibleIndexStatusFor(readerTranslation);
const bibleIndex = localSearch && indexStatus === 'ready' ? (_bibleIndexCacheRef.current[readerTranslation] ?? null) : null;
const bibleIndexLower = useMemo(() => (bibleIndex ? bibleIndex.map((v) => v.text.toLowerCase()) : null), [bibleIndex]);
const deferredBibleQuery = useDeferredValue(sheet === 'search' && readerSearchScope === 'bible' ? readerSearch.trim().toLowerCase() : '');
const wholeBibleSearching = sheet === 'search' && readerSearchScope === 'bible';
const deferredBibleQuery = useDeferredValue(wholeBibleSearching && localSearch ? readerSearch.trim().toLowerCase() : '');
const bibleMatches = useMemo(() => {
if (!bibleIndex || !deferredBibleQuery) return [];
const out = [];
@@ -155,6 +160,7 @@ export default function ReaderPage() {
}
return out;
}, [bibleIndex, bibleIndexLower, deferredBibleQuery]);
const remoteSearch = useBibleSearch(readerTranslation, readerSearch, wholeBibleSearching && !localSearch);
const openSheet = (name) => {
setSheet(name);
@@ -407,8 +413,9 @@ export default function ReaderPage() {
query={readerSearch} onQuery={setReaderSearch}
scope={readerSearchScope} onScope={setReaderSearchScope}
verses={readerVerses} bookName={readerBook?.name} chapter={readerChapter} currentBook={readerBookAbbrev}
indexStatus={bibleIndexStatus} onLoadIndex={loadBibleIndex}
bibleMatches={bibleMatches} deferredBibleQuery={deferredBibleQuery}
translation={readerTranslation} isLocal={localSearch}
indexStatus={indexStatus} onLoadIndex={() => loadBibleIndex(readerTranslation)}
bibleMatches={bibleMatches} deferredBibleQuery={deferredBibleQuery} remote={remoteSearch}
onJump={jump}
/>
<BookmarksSheet
+1 -1
View File
@@ -18,7 +18,7 @@ const ctx = {
readerSearch: '', setReaderSearch: noop, setReaderSearchActive: noop,
readerSearchScope: 'chapter', setReaderSearchScope: noop,
readerAudioState: { status: 'idle' }, audioProgress: { currentTime: 0, duration: 0 },
bibleIndexStatus: 'idle', audioNarrator: 'david', setAudioNarrator: noop,
bibleIndexStatusFor: () => 'idle', audioNarrator: 'david', setAudioNarrator: noop,
_bibleIndexCacheRef: { current: {} },
readerGoToPreviousChapter: noop, readerGoToNextChapter: noop, jumpToReaderVerse: noop,
toggleReaderBookmark: noop, cycleBookmarkColor: noop, loadReaderCrossRefs: noop, loadBibleIndex: noop,