Skip to content

feat(ui): add Bible Reader search - #396

Open
camrun91 wants to merge 12 commits into
mainfrom
cl/ype-5766_search_ui
Open

feat(ui): add Bible Reader search#396
camrun91 wants to merge 12 commits into
mainfrom
cl/ype-5766_search_ui

Conversation

@camrun91

@camrun91 camrun91 commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Why

Reader hosts have no way to search verses from BibleReader. #395 (YPE-5750) ships SearchClient and the query helpers. This PR puts a search dialog on the Toolbar and a headless useBibleSearch hook under it. A host that renders BibleReader.Toolbar gets search with no new props. A headless host gets the same session without the UI.

The session is one SearchSession union with an exclusive lane. Either the suggestion lane or the submitted lane is live, never both. A pure reducer owns every transition, so the PRD rules (no suggestions for a query that was just submitted, drop stale pages, dedupe pages by token) are tested without timers or a network.

Closes YPE-5766. Parent: YPE-5622.

Scope

  • packages/core: parseUsfmReference and UsfmReference. isValidStructuralUsfmReference now delegates to it.
  • packages/hooks: useSearchClient, useBibleSearch, the SearchSession reducer in internal/bible-search-session.ts, a searchClient test seam on YouVersionContext, and useDebounce passes null through without delay so a paused lane stops fetching.
  • packages/ui: BibleReaderSearch, a search prop on BibleReader.Toolbar ('control' by default, 'none' opts out), useTransientVerseFocus, navigateToVerse and verseFocus on the Reader context, useBibleReaderContext exported, yv-v-focused styles in bible-reader.css, and ten bibleSearch* keys in en.json.
  • Out of scope: topic search, search history, and non-English locale strings.

Tradeoffs

  • One SearchSession union instead of two independent query hooks. Two hooks can both be live at once, and the PRD forbids that state. The union costs a reducer file and makes the forbidden state unrepresentable.
  • Verse focus is a seq mailbox on BibleReader.Root, separate from verse selection. Reusing selection would open the verse action popover on every search hit.
  • Each result row fetches its text with usePassage instead of one batch call. The search API returns references only, and usePassage already caches per reference.

Blast Radius

Every host that renders BibleReader.Toolbar gets a search button and one more grid column. search="none" restores the old layout. Core and hooks add exports only. The English keys land here so the UI can ship, and platform-localization still owns the canonical source, so the locale-ownership CI check may flag en.json until that repo syncs.

This PR stacks on #395. Retarget to main after #395 merges.

Verification

  • pnpm test in core, hooks, and ui is green. bible-search-session.test.ts covers every session phase through the reducer alone.
  • pnpm typecheck is green after a core dist build.
  • A review pass before opening fixed two defects. Suggestions re-fetched the query that was just submitted, and the focus-clear scroll listener sat on the renderer root, which does not scroll. Both fixes are the last two commits.
  • The design comparison across four candidates is in .arena/ype-5766-search-ui/SYNTHESIS.md, local and not committed.

Greptile Summary

Adds Bible Reader verse search across the core parser, search-session hooks, and toolbar UI.

  • Introduces structural USFM parsing and search-result projection.
  • Adds an exclusive-lane search reducer and headless useBibleSearch API.
  • Adds toolbar search, result navigation, transient verse focus, styles, and English copy.

Confidence Score: 3/5

The PR is not safe to merge until the unbounded USFM range expansion and locale-ownership CI failure are resolved.

Search-response validation can still enter a non-progressing or extremely large synchronous range expansion, while the direct English locale edit remains guaranteed to fail the repository's author-based ownership gate.

Files Needing Attention: packages/core/src/usfm-reference.ts; packages/ui/src/i18n/locales/en.json

Important Files Changed

Filename Overview
packages/core/src/usfm-reference.ts Adds the shared USFM parser, but the previously reported unbounded and non-progressing range expansion remains.
packages/hooks/src/internal/bible-search-session.ts Implements the exclusive browsing/submitted state machine, stale-page rejection, pagination, and result projection.
packages/hooks/src/useBibleSearch.ts Connects debounced query requests and verse searches to the reducer-backed public hook.
packages/ui/src/components/bible-reader-search.tsx Implements the search dialog, suggestions, result rendering, pagination controls, and reader navigation.
packages/ui/src/components/bible-reader.tsx Integrates search into the toolbar and adds reader navigation and transient-focus context behavior.
packages/ui/src/i18n/locales/en.json Still directly adds search copy to an upstream-owned locale bundle, leaving the previously reported ownership-gate failure unresolved.

Sequence Diagram

sequenceDiagram
  actor User
  participant UI as BibleReaderSearch
  participant Hook as useBibleSearch
  participant Reducer as SearchSession reducer
  participant API as SearchClient
  participant Reader as BibleReader
  User->>UI: Enter and submit query
  UI->>Hook: submit()
  Hook->>Reducer: Transition to submitted lane
  Hook->>API: Search verses
  API-->>Hook: References and page token
  Hook->>Reducer: Commit matching page
  Reducer-->>UI: Results phase
  User->>UI: Select result
  UI->>Reader: Navigate and focus verse
Loading

Reviews (2): Last reviewed commit: "fix(ui): keep Search key on mobile after..." | Re-trigger Greptile

Replace beforeEach setup with per-test factories per docs/testing.md and consolidate language-range parsing into schemas/search.ts.
Search hits and the structural validator both need the same chapter/verse
shape. parseUsfmReference owns that grammar so inverted ranges drop at
the boundary instead of leaking into the reader.
The PRD's suggestion-suppression, stale-page, and dedupe rules are
consequences of one SearchSession union plus a pure reducer, so the eight
phases can be proven without timers or network.
Headless hosts get one phase and five commands. Debounce, language-range
gating, and page commits stay inside the hook so UI never sees tokens or
TanStack Query.
Content can scroll and dim only after the destination chapter is on screen. A seq mailbox plus a hold timer keeps focus separate from verse selection.
Hosts get a zero-required-prop search dialog that talks to useBibleSearch and navigates through one Root-owned verse-focus mailbox. English keys land here so the UI can ship; platform-localization still owns the canonical source.
The public helpers and Reader search UI are new surface on core, hooks, and ui, so they ship as minors together.
The first keystroke after submit left the submitted lane while debounce still
held that query. Suggestions then re-fetched it and showed a spinner. Debounce
null while submitted, and skip queriesRequest until the new input settles.
The hold-clear scroll listener sat on the renderer root, which does not
scroll. Listen on the nearest overflow ancestor instead. Drop the unused
focusedVerses return. Content never read it.
@changeset-bot

changeset-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1773708

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@youversion/platform-core Minor
@youversion/platform-react-hooks Minor
@youversion/platform-react-ui Minor
vite-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment on lines +52 to +55
const verses: number[] = [];
for (let verse = verseStart; verse <= verseEnd; verse += 1) {
verses.push(verse);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Range expansion can hang

When a search response contains a reference such as JHN.6.9007199254740992-9007199254740993, both endpoints pass validation but verse += 1 no longer advances at the safe-integer boundary, causing the application to hang while processing the response; sufficiently large lower ranges also exhaust memory.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/src/usfm-reference.ts
Line: 52-55

Comment:
**Range expansion can hang**

When a search response contains a reference such as `JHN.6.9007199254740992-9007199254740993`, both endpoints pass validation but `verse += 1` no longer advances at the safe-integer boundary, causing the application to hang while processing the response; sufficiently large lower ranges also exhaust memory.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Cursor Fix in Codex

Comment on lines 84 to +93
"youVersionPlatformLogoAriaLabel": "YouVersion Platform",
"signInAppNameFallback": "This app",
"untitledSerifFontName": "Untitled Serif"
"untitledSerifFontName": "Untitled Serif",
"bibleSearchAriaLabel": "Search the Bible",
"bibleSearchDialogTitle": "Search",
"bibleSearchPlaceholder": "Search verses",
"bibleSearchClearAriaLabel": "Clear search",
"bibleSearchTrendingHeading": "Trending",
"bibleSearchSuggestionsHeading": "Suggestions",
"bibleSearchNoVerseResults": "No verses matched this search.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Locale ownership check fails

This directly edits an upstream-owned locale file, so the author-based locale-ownership check rejects the pull request. Add these English keys to platform-localization/sources/common/en.json and let the localization sync update this generated file.

Rule Used: Locale JSON is upstream-owned (platform-localization). CI locale-ownership job fails any PR that edits packages/ui/src/i18n/locales/ unless its author is platform-localization-pr-bot[bot]. The branch name is deliberately not part of the check (see do... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/src/i18n/locales/en.json
Line: 84-93

Comment:
**Locale ownership check fails**

This directly edits an upstream-owned locale file, so the author-based locale-ownership check rejects the pull request. Add these English keys to `platform-localization/sources/common/en.json` and let the localization sync update this generated file.

**Rule Used:** Locale JSON is upstream-owned (platform-localization). CI locale-ownership job fails any PR that edits packages/ui/src/i18n/locales/ unless its author is platform-localization-pr-bot\[bot\]. The branch name is deliberately not part of the check (see do... ([source](https://github.com/youversion/platform-sdk-react/blob/6d30613d98f72997736ded3d3967f17b26724762/greptile.json))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Cursor Fix in Codex

Base automatically changed from cl/ype-5750_search_api to main September 14, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant