2026-09-23·by Sijie Wang#lucerna#software#architecture

full-text-search

Full-text search (per-book FlexSearch, disk-persisted)

Parent: key-designs

core/src/search/ (SearchEngine, SearchIndex, SearchService, SearchIndexStore, aggregateConsecutive, + searchTokenize/searchIndexVersion).

One FlexSearch index per book, at sentence granularity. Each CachedSentence's display text is a document; a query returns ranked sentence hits, which aggregateConsecutive collapses into contiguous passages (so a phrase spanning two sentences reads as one result). Results carry the sentence's raw offset, so jumping from a search hit lands on the right page via the coordinate backbone.

Restore-vs-rebuild is the load-bearing decision. Pouring a whole book into a fresh FlexSearch index is expensive, so the index is serialized to a disk blob (exportSections/importSections via SearchIndexStore) keyed by content hash + SEARCH_INDEX_VERSION. On reopen, importSections restores in <500 ms instead of re-indexing; a version bump discards the blob and rebuilds. A facade (markBuilding/attach/detach) coordinates the index's lifecycle with pagination (both consume the same sentence stream), so search doesn't re-tokenize independently.

Why it's a pillar, not a checkbox: an intensive-reader searches constantly, over books large enough that re-indexing on every open would be unacceptable — so the persist+restore+version machinery is the substance, exactly parallel to how dynamic-pagination and the sentence cache version and restore rather than recompute.

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →

full-text-search