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.