Skip to content

feat(ui): native-text BibleReader body and footnotes (POC) - #233

Draft
Dustin-Kelley wants to merge 83 commits into
mainfrom
dk/native-text-poc
Draft

Dustin-Kelley wants to merge 83 commits into
mainfrom
dk/native-text-poc

Conversation

@Dustin-Kelley

@Dustin-Kelley Dustin-Kelley commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A proof of concept that the BibleReader scripture body and footnote sheets can render with React Native <Text> instead of Expo DOM WebViews, at visual parity with the Swift and Kotlin SDKs. No native modules.

https://www.loom.com/share/5ad15c233898465ca30c2968902905da

Native reader (POC, opt-in)

  • BibleReaderNative takes the same props as the DOM reader body and swaps in through the impl registry. unstable_setReaderRenderer('dom' | 'native') picks one; DOM stays the default.
  • Parsing reuses platform-core's transformBibleHtml under linkedom, flattened into blocks of verse-tagged runs (packages/ui/src/native/scripture/).
  • Styling follows the Swift and Kotlin readers: block rules, line spacing on Swift's model (natural line 1.24em + the step's fraction), verse labels, small caps, and words-of-Jesus colour.
  • Layout uses the style spec's 32pt horizontal gutter and Swift's 16pt top padding.
  • Small caps are synthesized at 0.85× with 0.055em tracking. On TOJB Luke 23 they measure the same width as Swift's lowercaseSmallCaps() and are within 1px of its height.
  • Text direction comes from a majority vote of the passage's letters, with headings left out. A rule based on the first letter flipped ASV Psalm 119 to right-to-left, because its acrostic lines open with Hebrew.
  • Load errors: 403 and 404 read as "unavailable in this version". Any other failed response, or a body that won't parse, shows the generic request error with Retry. A rejected fetch shows offline.
  • The selection underline is a 1pt strip clipped from a hidden copy of the paragraph, placed off onTextLayout line metrics. RN has no underline offset, and iOS breaks text underlines at descenders.
  • Highlights are drawn the same way, clipped per line to the text width and to Swift's band height, so line spacing shows as a gap between lines.
  • No chapter header above the text; as in Swift, the reference shows only in the toolbar.
  • The example Bible tab has a DOM / Native toggle.

Native footnote content (YPE-5832, changeset included)

  • FootnoteContent renders with native text in both reader modes. It keeps italic notes, bold locators and verse superscripts, sized to match the web sheet.

Tooling

  • pnpm dev:ios falls back to DeviceHub when Xcode has no Simulator.app (Xcode 27).

Cleanup

  • Removed the example app's first native-text prototype tab and the dependencies only it used.

Not yet covered

  • The highlight band uses em constants measured off the serif, so sans, fallback and non-Latin fonts may sit slightly off.
  • A BSB continuation line attaches to the previous verse, and verse focus centres the paragraph rather than the verse.
  • Prose sometimes wraps one word earlier than Swift. This is deliberate: the spec's 32pt gutter is 2pt wider each side than Swift's 30pt.
  • Indented poetry lines (q2) wrap later than Swift, which breaks them before the line is full. This predates this branch's fixes and is untraced.
  • Toolbar pills truncate earlier than Swift ("Song of…", "TOJB2…").
  • The generic error copy is English-only until platform-localization ships the other locales.
  • NIV is unlicensed for the example key, so 25 USFM classes were checked against Swift's styles in code rather than on screen.
  • Not yet compared side by side for the pickers, or for highlights painted after sign-in.

Test plan

  • pnpm lint, the UI package tsc, and UI Jest (952 passed)
  • iOS simulator: John 1 checked against the Swift reader for underline, highlights and header
  • Android device or emulator pass
  • Scripture-class audit: 15 passages across BSB, ASV, WEB, FBV and TOJB, compared side by side with the Swift sample, before and after the fixes
  • Right-to-left detection and long chapters (ASV and BSB Psalm 119)

🤖 Generated with Claude Code

camrun91 and others added 30 commits September 25, 2026 13:50
Bump the example dev client to expo@57.0.22 with RN 0.86.3, pin
@expo/dom-webview@57.0.1, and move publishable package peers to
>=57.0.0 <58.0.0. Add pnpm overrides for transitive Expo deps still
inside the supply-chain cooldown window.
Pick up the SDK patch without enabling ios.enableSceneSupport; workspace
override and example app pin expo@57.0.23 exactly.
Opt in via expo-build-properties enableSceneSupport and bump the plugin
to 57.0.20, which implements the scene delegate wiring required alongside
expo@57.0.23 on SDK 57.
Raise peer floors so consumers on React Native 0.86 cannot resolve
Reanimated 4.3.x or Worklets 0.8.x, which only declare support through
0.85. Add a major changeset recording the Expo 57 peer requirement.
5970824 raised packages/ui peer floors to >=4.4.0 / >=0.9.1 but
did not regenerate pnpm-lock.yaml, causing frozen-lockfile CI to fail.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>
Consumer-facing docs still listed SDK 56 while published peers require 57.
YPE-5830. Native Card owns header, version control, copyright, and
maxWidth. Scripture still paints through BibleTextViewDom.
Disable the version control and skip onVersionPickerPress when chrome
metadata has no languageTag, so consumers never receive an empty languageId.
Track which reference/versionId loaded metadata and return null during
render when inputs change, so chrome cannot flash stale values before
the refetch effect runs.
Apply provider version filters to chrome metadata, restore abbreviation and
web background theming, gate the version picker only for consumer handlers,
and keep Expo web from showing a dead built-in picker trigger.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>
Include provider filter lists in the chrome metadata key so a tighten
without reference/versionId change drops prohibited chrome immediately,
matching the reference-transition safety net.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>
Match the Web SDK full-bleed contract: the Card surface fills the parent
while header, scripture, and footer stay in a centered 600px column.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>
Include fetchBibleContent in the chrome metadata key so an appKey/apiHost
swap drops stale reference/copyright/language before the new lookup resolves.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>
Co-authored-by: camrun91 <16566029+camrun91@users.noreply.github.com>
…on (#194)

* feat(ui)!: compose YouVersionAuthButton on design-system Button

YPE-5833 / RNV2-6. The branded control is now Button + Button.Text with
the Bible App logo as a child. background still forces the light or dark
token scheme.

BREAKING CHANGE: YouVersionAuthButton no longer accepts outline, radius,
or size. Short, icon, and rectangular variants are gone. The canonical
look is a filled, rounded Button with the full localized label.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): keep Trans props off Button.Text in YouVersionAuthButton

Wrap the i18n Trans node inside Button.Text instead of using it as
Trans parent, so extra Trans props are not forwarded onto the label.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): address YouVersionAuthButton review findings

Drop the unlinked example preview route. Collapse sign-in versus
sign-out to one decision. Document that core majors only because the
packages version together. Cover theme isolation and the bold Trans face.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): pin YouVersionAuthButton fill to scheme background

Button default primary is red in dark and hides the Bible App logo.
Read tokens via useTokens inside the forced ThemeContext. Cover sign-in
error logging and keep outline/radius/size off the public props type.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* docs: match native i18n Trans example to auth button

The contributing snippet still taught parent={Text} and a hand-written
fontWeight. The auth button nests Trans in Button.Text and bolds with
sansFace.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* refactor(ui): read i18n inside YouVersionAuthButton surface

AuthButtonSurface already sits under the forced scheme. It can call
useSdkTranslation itself instead of taking i18n as a pass-through prop.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): treat empty auth button text as omitted

An empty text prop used to fall back to the localized Trans label. Nullish
coalescing made it a blank control. Scope the i18n sansFace note to
primitives so sheets can keep fontWeight.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): outline YouVersionAuthButton and let the label wrap

Bake a scheme border into the locked look (light 1px, dark 2px) without
bringing back the outline prop. Override Button.Text to two lines and
drop the fixed height so a long locale can wrap.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* refactor(ui): extract AuthButtonLabel from YouVersionAuthButton

Keep Trans named Trans. Early-return when the caller passes text.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* chore(changeset): ship YouVersionAuthButton DS work as patch

Drop core from the changeset; it has no API change. The fixed group still versions both packages together.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* refactor(ui): bold AuthButtonLabel with design-system Text

Trans bold uses DS Text plus sansFace. Pin typography.sm so the brand
name stays on the Button.Text size.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): let YouVersionAuthButton label wrap on web

Give Button.Text flex: 1 so a narrow parent wraps to two lines instead of
ellipsizing on one.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* docs: add PR 194 Expo web QA screenshots for local download

Temporary PNGs so Dustin can pull them via the GitHub API onto his Desktop.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* docs: remove temporary PR 194 screenshot drop folder

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): restore YouVersionAuthButton post-DS-9 shape on Button

Keep the old padding, logo gap, and no default border while composing
the design-system Button. Label color comes from Button context.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): match YouVersionAuthButton chrome to Swift

Compose Button outline, hug content, and use Swift padding, gap, and logo size so hosts place the pill.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>
* feat(ui): add native Bible chapter picker

Amp-Thread-ID: https://ampcode.com/threads/T-01a0c4d9-366a-702e-a0d8-e56d50e888bc
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>

* fix(ui): refine native chapter picker

Amp-Thread-ID: https://ampcode.com/threads/T-01a0c4d9-366a-702e-a0d8-e56d50e888bc
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>

* fix(ui): polish chapter picker interactions

Amp-Thread-ID: https://ampcode.com/threads/T-01a0c4d9-366a-702e-a0d8-e56d50e888bc
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>

* fix(ui): preserve chapter picker sheet theme

Amp-Thread-ID: https://ampcode.com/threads/T-01a0c5d4-3d6c-712f-b216-cb65fb75357f
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>

* fix(ui): address chapter picker review feedback

Keep the selected-book fade on one color, avoid reloading the catalog when provider filters are recreated with the same contents, and scroll the selected chapter back into view after an order change.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): ignore chapter picks for a revoked Bible version

Drop the loaded catalog as soon as provider filters change, and refuse a selection when the current filters no longer allow that version.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>
* feat(ui): hybridize BibleCard chrome with DOM BibleTextView

YPE-5830. Native Card owns header, version control, copyright, and
maxWidth. Scripture still paints through BibleTextViewDom.

* fix: clear BibleCard chrome metadata before refetch

* fix: guard BibleCard version picker until language tag loads

Disable the version control and skip onVersionPickerPress when chrome
metadata has no languageTag, so consumers never receive an empty languageId.

* fix: invalidate BibleCard chrome metadata synchronously on transition

Track which reference/versionId loaded metadata and return null during
render when inputs change, so chrome cannot flash stale values before
the refetch effect runs.

* chore: upgrade example app and packages to Expo SDK 57

Bump the example dev client to expo@57.0.22 with RN 0.86.3, pin
@expo/dom-webview@57.0.1, and move publishable package peers to
>=57.0.0 <58.0.0. Add pnpm overrides for transitive Expo deps still
inside the supply-chain cooldown window.

* chore: bump expo to 57.0.23 for iOS 27 lifecycle backport

Pick up the SDK patch without enabling ios.enableSceneSupport; workspace
override and example app pin expo@57.0.23 exactly.

* fix(example): enable iOS 27 UIScene lifecycle for Xcode 27 builds

Opt in via expo-build-properties enableSceneSupport and bump the plugin
to 57.0.20, which implements the scene delegate wiring required alongside
expo@57.0.23 on SDK 57.

* fix(ui): tighten Reanimated/Worklets peers for RN 0.86

Raise peer floors so consumers on React Native 0.86 cannot resolve
Reanimated 4.3.x or Worklets 0.8.x, which only declare support through
0.85. Add a major changeset recording the Expo 57 peer requirement.

* fix: sync lockfile with Reanimated/Worklets peer floors

5970824 raised packages/ui peer floors to >=4.4.0 / >=0.9.1 but
did not regenerate pnpm-lock.yaml, causing frozen-lockfile CI to fail.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

* docs: align READMEs with Expo SDK 57 peer contract

Consumer-facing docs still listed SDK 56 while published peers require 57.

* fix(ui): address BibleCard hybrid chrome review feedback

Apply provider version filters to chrome metadata, restore abbreviation and
web background theming, gate the version picker only for consumer handlers,
and keep Expo web from showing a dead built-in picker trigger.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

* fix(ui): clear BibleCard chrome when version filters change

Include provider filter lists in the chrome metadata key so a tighten
without reference/versionId change drops prohibited chrome immediately,
matching the reference-transition safety net.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

* fix(ui): keep 600px inner column for BibleCard maxWidth 100%

Match the Web SDK full-bleed contract: the Card surface fills the parent
while header, scripture, and footer stay in a centered 600px column.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

* fix(ui): clear BibleCard chrome when content client changes

Include fetchBibleContent in the chrome metadata key so an appKey/apiHost
swap drops stale reference/copyright/language before the new lookup resolves.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

* YPE-5833 / RNV2-6: compose YouVersionAuthButton on design-system Button (#194)

* feat(ui)!: compose YouVersionAuthButton on design-system Button

YPE-5833 / RNV2-6. The branded control is now Button + Button.Text with
the Bible App logo as a child. background still forces the light or dark
token scheme.

BREAKING CHANGE: YouVersionAuthButton no longer accepts outline, radius,
or size. Short, icon, and rectangular variants are gone. The canonical
look is a filled, rounded Button with the full localized label.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): keep Trans props off Button.Text in YouVersionAuthButton

Wrap the i18n Trans node inside Button.Text instead of using it as
Trans parent, so extra Trans props are not forwarded onto the label.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): address YouVersionAuthButton review findings

Drop the unlinked example preview route. Collapse sign-in versus
sign-out to one decision. Document that core majors only because the
packages version together. Cover theme isolation and the bold Trans face.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): pin YouVersionAuthButton fill to scheme background

Button default primary is red in dark and hides the Bible App logo.
Read tokens via useTokens inside the forced ThemeContext. Cover sign-in
error logging and keep outline/radius/size off the public props type.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* docs: match native i18n Trans example to auth button

The contributing snippet still taught parent={Text} and a hand-written
fontWeight. The auth button nests Trans in Button.Text and bolds with
sansFace.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* refactor(ui): read i18n inside YouVersionAuthButton surface

AuthButtonSurface already sits under the forced scheme. It can call
useSdkTranslation itself instead of taking i18n as a pass-through prop.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): treat empty auth button text as omitted

An empty text prop used to fall back to the localized Trans label. Nullish
coalescing made it a blank control. Scope the i18n sansFace note to
primitives so sheets can keep fontWeight.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): outline YouVersionAuthButton and let the label wrap

Bake a scheme border into the locked look (light 1px, dark 2px) without
bringing back the outline prop. Override Button.Text to two lines and
drop the fixed height so a long locale can wrap.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* refactor(ui): extract AuthButtonLabel from YouVersionAuthButton

Keep Trans named Trans. Early-return when the caller passes text.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* chore(changeset): ship YouVersionAuthButton DS work as patch

Drop core from the changeset; it has no API change. The fixed group still versions both packages together.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* refactor(ui): bold AuthButtonLabel with design-system Text

Trans bold uses DS Text plus sansFace. Pin typography.sm so the brand
name stays on the Button.Text size.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): let YouVersionAuthButton label wrap on web

Give Button.Text flex: 1 so a narrow parent wraps to two lines instead of
ellipsizing on one.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* docs: add PR 194 Expo web QA screenshots for local download

Temporary PNGs so Dustin can pull them via the GitHub API onto his Desktop.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* docs: remove temporary PR 194 screenshot drop folder

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): restore YouVersionAuthButton post-DS-9 shape on Button

Keep the old padding, logo gap, and no default border while composing
the design-system Button. Label color comes from Button context.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): match YouVersionAuthButton chrome to Swift

Compose Button outline, hug content, and use Swift padding, gap, and logo size so hosts place the pill.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* chore: sync react native localization (#200)

Co-authored-by: camrun91 <16566029+camrun91@users.noreply.github.com>

* feat(ui): YPE-5836: Add native Bible chapter picker (#204)

* feat(ui): add native Bible chapter picker

Amp-Thread-ID: https://ampcode.com/threads/T-01a0c4d9-366a-702e-a0d8-e56d50e888bc
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>

* fix(ui): refine native chapter picker

Amp-Thread-ID: https://ampcode.com/threads/T-01a0c4d9-366a-702e-a0d8-e56d50e888bc
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>

* fix(ui): polish chapter picker interactions

Amp-Thread-ID: https://ampcode.com/threads/T-01a0c4d9-366a-702e-a0d8-e56d50e888bc
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>

* fix(ui): preserve chapter picker sheet theme

Amp-Thread-ID: https://ampcode.com/threads/T-01a0c5d4-3d6c-712f-b216-cb65fb75357f
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>

* fix(ui): address chapter picker review feedback

Keep the selected-book fade on one color, avoid reloading the catalog when provider filters are recreated with the same contents, and scroll the selected chapter back into view after an order change.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): ignore chapter picks for a revoked Bible version

Drop the loaded catalog as soon as provider filters change, and refuse a selection when the current filters no longer allow that version.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* feat(ui): YPE-5834: Add native Bible version picker

Replace the Expo DOM picker body with a native sheet so version and language lists stay on device.

* fix(ui): satisfy oxlint on the version picker

Parse the language tab and device language codes without a type assertion or a typeof check.

* fix(ui): hide picker languages with no usable versions

Provider version and language filters were applied only on the versions
panel, so the language lists could open a language that then showed no
versions. Derive the suggested, all, and search lists from versions the
active filters still allow.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

* fix(ui): keep the version picker usable after a failed selection

A rejected onSelect was swallowed by the sheet, so the picker recorded
and highlighted the version the host refused. A current version with no
language also hid the catalog. Let the rejection reach the picker,
publish the catalog anyway, and reset the sheet by unmounting on close.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

* fix(ui): offer retry when the current version lookup fails

A thrown metadata lookup left the versions panel looking empty. Keep the
language catalog available, and show Retry on that transient failure so
the same lookup can be attempted again.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

* fix(ui): keep recent versions selectable when version lookup fails

The retry state replaced the whole versions list. Recent versions are
already in the catalog, so show them with Retry and let the user select
one while the current-version lookup is still failing.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

* feat(ui): refine native Bible version picker sheet (#217)

* feat(ui): refine native Bible version picker design

Amp-Thread-ID: https://ampcode.com/threads/T-01a0d979-8f8f-73f8-b9fe-8c5a5a518cae
Co-authored-by: Amp <amp@ampcode.com>

* fix(ui): address Bible version picker review feedback

Amp-Thread-ID: https://ampcode.com/threads/T-01a0d979-8f8f-73f8-b9fe-8c5a5a518cae
Co-authored-by: Amp <amp@ampcode.com>

* fix strings pulled down.

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Dustin Kelley <141975656+Dustin-Kelley@users.noreply.github.com>
Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>
Co-authored-by: platform-localization-pr-bot[bot] <294853593+platform-localization-pr-bot[bot]@users.noreply.github.com>
Co-authored-by: camrun91 <16566029+camrun91@users.noreply.github.com>
Co-authored-by: Dustin Kelley <Kelleydustin2018@gmail.com>
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>
Co-authored-by: Amp <amp@ampcode.com>
* feat(ui): hybridize BibleCard chrome with DOM BibleTextView

YPE-5830. Native Card owns header, version control, copyright, and
maxWidth. Scripture still paints through BibleTextViewDom.

* fix: clear BibleCard chrome metadata before refetch

* fix: guard BibleCard version picker until language tag loads

Disable the version control and skip onVersionPickerPress when chrome
metadata has no languageTag, so consumers never receive an empty languageId.

* fix: invalidate BibleCard chrome metadata synchronously on transition

Track which reference/versionId loaded metadata and return null during
render when inputs change, so chrome cannot flash stale values before
the refetch effect runs.

* chore: upgrade example app and packages to Expo SDK 57

Bump the example dev client to expo@57.0.22 with RN 0.86.3, pin
@expo/dom-webview@57.0.1, and move publishable package peers to
>=57.0.0 <58.0.0. Add pnpm overrides for transitive Expo deps still
inside the supply-chain cooldown window.

* chore: bump expo to 57.0.23 for iOS 27 lifecycle backport

Pick up the SDK patch without enabling ios.enableSceneSupport; workspace
override and example app pin expo@57.0.23 exactly.

* fix(example): enable iOS 27 UIScene lifecycle for Xcode 27 builds

Opt in via expo-build-properties enableSceneSupport and bump the plugin
to 57.0.20, which implements the scene delegate wiring required alongside
expo@57.0.23 on SDK 57.

* fix(ui): tighten Reanimated/Worklets peers for RN 0.86

Raise peer floors so consumers on React Native 0.86 cannot resolve
Reanimated 4.3.x or Worklets 0.8.x, which only declare support through
0.85. Add a major changeset recording the Expo 57 peer requirement.

* fix: sync lockfile with Reanimated/Worklets peer floors

5970824 raised packages/ui peer floors to >=4.4.0 / >=0.9.1 but
did not regenerate pnpm-lock.yaml, causing frozen-lockfile CI to fail.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

* docs: align READMEs with Expo SDK 57 peer contract

Consumer-facing docs still listed SDK 56 while published peers require 57.

* fix(ui): address BibleCard hybrid chrome review feedback

Apply provider version filters to chrome metadata, restore abbreviation and
web background theming, gate the version picker only for consumer handlers,
and keep Expo web from showing a dead built-in picker trigger.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

* fix(ui): clear BibleCard chrome when version filters change

Include provider filter lists in the chrome metadata key so a tighten
without reference/versionId change drops prohibited chrome immediately,
matching the reference-transition safety net.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

* fix(ui): keep 600px inner column for BibleCard maxWidth 100%

Match the Web SDK full-bleed contract: the Card surface fills the parent
while header, scripture, and footer stay in a centered 600px column.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

* fix(ui): clear BibleCard chrome when content client changes

Include fetchBibleContent in the chrome metadata key so an appKey/apiHost
swap drops stale reference/copyright/language before the new lookup resolves.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>

* feat(core): wrap platform-core SearchClient (YPE-5746) (#180)

* feat(core): add placeholder Search client

YPE-5706: temporary Search HTTP in expo-core until platform-core
ships SearchClient. Public hook and types only; wrapper stays internal.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(core): cover useSearch outside provider

YPE-5706: hook must throw when used without YouVersionProvider.
Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(core): wrap platform-core SearchClient (YPE-5746)

Delete the local Search HTTP client and wrap SearchClient from
@youversion/platform-core 2.13.0. Thrown errors map to the existing
RN Result kinds. Verse hits use id. Query lists are { queries }.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* style(core): drop restated Search comments

The USFM tests already pin verse-only three-segment parsing.
The exports tests already pin the public surface.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(core): reject unsafe integers in bibleReferenceFromUsfm

Digit-only USFM chapter and verse segments can parse past
Number.MAX_SAFE_INTEGER. Require a positive safe integer.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(core): reject malformed USFM book identifiers

Require a 3-character uppercase alphanumeric book code so punctuation, whitespace, and overlong ids return null.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* feat(ui): BibleReaderNavigation chapter jumps (YPE-5745) (#182)

* feat(core): add placeholder Search client

YPE-5706: temporary Search HTTP in expo-core until platform-core
ships SearchClient. Public hook and types only; wrapper stays internal.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(core): cover useSearch outside provider

YPE-5706: hook must throw when used without YouVersionProvider.
Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(core): wrap platform-core SearchClient (YPE-5746)

Delete the local Search HTTP client and wrap SearchClient from
@youversion/platform-core 2.13.0. Thrown errors map to the existing
RN Result kinds. Verse hits use id. Query lists are { queries }.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* style(core): drop restated Search comments

The USFM tests already pin verse-only three-segment parsing.
The exports tests already pin the public surface.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(core): reject unsafe integers in bibleReferenceFromUsfm

Digit-only USFM chapter and verse segments can parse past
Number.MAX_SAFE_INTEGER. Require a positive safe integer.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* feat(ui): add BibleReaderNavigation for chapter jumps

Public request/focusReference object so a host can jump the Reader
before mount. Scroll-to-verse waits. YPE-5745.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): keep chapter jumps across discarded renders

Consume the pending BibleReaderNavigation request after commit so a
retried render cannot drop the host's chapter jump.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(ui): request the chapter jump once in the host example

Call request when creating BibleReaderNavigation so a parent re-render
does not queue the same jump again.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): apply reader jumps after the paint commits

Keep the first-paint overlay, but wait to set book/chapter/version so a
discarded render cannot notify the host of a chapter that never showed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): hide reader navigation queue controls

Keep request and focusReference as the host API. Move subscribe,
snapshot, and consume helpers off the exported class so a host cannot
clear a jump before the Reader sees it. Drop unused consumePending.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): persist reader jumps as one location

Write book, chapter, and version in a single Reader Location patch so
an uncontrolled jump cannot save a mixed book with the old chapter.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* docs(ui): note one Reader per navigation object

Hosts should pass one BibleReaderNavigation to one mounted Reader.
Broadcast across stacked readers is left for a later issue if needed.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* feat(ui): match native Reader chrome to Swift

Drop the avatar row. Half-pills, overlay chapter buttons, and a More menu keep the same host presses. Search stays off this PR.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): lift Reader overlay nav above the iOS tab bar

Prev and next sat 24pt from the reader bottom, on SampleApp tabs, so those presses hit Bible or Profile instead of changing chapter.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(ui): retarget Reader chrome to Swift PR 268

Previous and next live in the chapter capsule. Overlay circles are gone so the toolbar matches the current Swift header.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): polish v2 Reader toolbar chrome

Match Figma capsules, a softer halo, side padding, and More-menu icons so the native row reads like the v2 file.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): finish v2 toolbar leftovers from self-review

Cap the version pill so Select version cannot squeeze the chapter row.
Drop unused GearIcon and avatar copy. Open More while auth is restoring.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(ui): delete unused Avatar primitive

The v2 toolbar no longer mounts it. Drop the component, its tests, and the internal barrel export.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): even the Reader toolbar gaps around More

Size More to the icon so the 8px gap matches the space between the chapter and version chips.

Co-authored-by: Cursor <cursoragent@cursor.com>

* revert(ui): keep userAvatarAlt in synced locale catalogs

Locale JSON is generated from platform-localization. Dropping the unused key here fails Greptile. Leave the string until that repo deletes it.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): match More menu icons to Swift glyphs

Use San Francisco textformat.size for Fonts & Settings and person for sign in/out.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(ui): restack Reader Search onto Swift chrome

Search belongs in the 5953 spacer. The old row put it between version and gear, and that chrome is gone.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): keep 5745 navigation consume API on Search rebase

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): keep combined Reader Location jumps after Search rebase

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(ui): delete unused components/ui PersonIcon

The More menu still uses native/icons/person-icon.tsx.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): stop the Reader example from jumping every render

The sample called navigation.request after every ReaderScreen render, so a copied example reset the reader to John 3 on unrelated updates. The request now happens once, inside the same useMemo that creates the navigation object.

Drop the hand-written Spanish search strings. Missing keys fall back to English until Crowdin returns them.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* YPE-5833 / RNV2-6: compose YouVersionAuthButton on design-system Button (#194)

* feat(ui)!: compose YouVersionAuthButton on design-system Button

YPE-5833 / RNV2-6. The branded control is now Button + Button.Text with
the Bible App logo as a child. background still forces the light or dark
token scheme.

BREAKING CHANGE: YouVersionAuthButton no longer accepts outline, radius,
or size. Short, icon, and rectangular variants are gone. The canonical
look is a filled, rounded Button with the full localized label.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): keep Trans props off Button.Text in YouVersionAuthButton

Wrap the i18n Trans node inside Button.Text instead of using it as
Trans parent, so extra Trans props are not forwarded onto the label.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): address YouVersionAuthButton review findings

Drop the unlinked example preview route. Collapse sign-in versus
sign-out to one decision. Document that core majors only because the
packages version together. Cover theme isolation and the bold Trans face.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): pin YouVersionAuthButton fill to scheme background

Button default primary is red in dark and hides the Bible App logo.
Read tokens via useTokens inside the forced ThemeContext. Cover sign-in
error logging and keep outline/radius/size off the public props type.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* docs: match native i18n Trans example to auth button

The contributing snippet still taught parent={Text} and a hand-written
fontWeight. The auth button nests Trans in Button.Text and bolds with
sansFace.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* refactor(ui): read i18n inside YouVersionAuthButton surface

AuthButtonSurface already sits under the forced scheme. It can call
useSdkTranslation itself instead of taking i18n as a pass-through prop.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): treat empty auth button text as omitted

An empty text prop used to fall back to the localized Trans label. Nullish
coalescing made it a blank control. Scope the i18n sansFace note to
primitives so sheets can keep fontWeight.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): outline YouVersionAuthButton and let the label wrap

Bake a scheme border into the locked look (light 1px, dark 2px) without
bringing back the outline prop. Override Button.Text to two lines and
drop the fixed height so a long locale can wrap.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* refactor(ui): extract AuthButtonLabel from YouVersionAuthButton

Keep Trans named Trans. Early-return when the caller passes text.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* chore(changeset): ship YouVersionAuthButton DS work as patch

Drop core from the changeset; it has no API change. The fixed group still versions both packages together.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* refactor(ui): bold AuthButtonLabel with design-system Text

Trans bold uses DS Text plus sansFace. Pin typography.sm so the brand
name stays on the Button.Text size.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): let YouVersionAuthButton label wrap on web

Give Button.Text flex: 1 so a narrow parent wraps to two lines instead of
ellipsizing on one.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* docs: add PR 194 Expo web QA screenshots for local download

Temporary PNGs so Dustin can pull them via the GitHub API onto his Desktop.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* docs: remove temporary PR 194 screenshot drop folder

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): restore YouVersionAuthButton post-DS-9 shape on Button

Keep the old padding, logo gap, and no default border while composing
the design-system Button. Label color comes from Button context.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): match YouVersionAuthButton chrome to Swift

Compose Button outline, hug content, and use Swift padding, gap, and logo size so hosts place the pill.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* chore: sync react native localization (#200)

Co-authored-by: camrun91 <16566029+camrun91@users.noreply.github.com>

* refactor(ui): inline the Reader Search field

The design-system Input had one caller, and that caller turned its border off. The sheet now uses TextInput directly.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* feat(ui): v2 toolbar (YPE-5953) (#202)

* feat(ui): match native Reader chrome to Swift

Drop the avatar row. Half-pills, overlay chapter buttons, and a More menu keep the same host presses. Search stays off this PR.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): lift Reader overlay nav above the iOS tab bar

Prev and next sat 24pt from the reader bottom, on SampleApp tabs, so those presses hit Bible or Profile instead of changing chapter.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(ui): retarget Reader chrome to Swift PR 268

Previous and next live in the chapter capsule. Overlay circles are gone so the toolbar matches the current Swift header.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): polish v2 Reader toolbar chrome

Match Figma capsules, a softer halo, side padding, and More-menu icons so the native row reads like the v2 file.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): finish v2 toolbar leftovers from self-review

Cap the version pill so Select version cannot squeeze the chapter row.
Drop unused GearIcon and avatar copy. Open More while auth is restoring.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(ui): delete unused Avatar primitive

The v2 toolbar no longer mounts it. Drop the component, its tests, and the internal barrel export.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): even the Reader toolbar gaps around More

Size More to the icon so the 8px gap matches the space between the chapter and version chips.

Co-authored-by: Cursor <cursoragent@cursor.com>

* revert(ui): keep userAvatarAlt in synced locale catalogs

Locale JSON is generated from platform-localization. Dropping the unused key here fails Greptile. Leave the string until that repo deletes it.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): match More menu icons to Swift glyphs

Use San Francisco textformat.size for Fonts & Settings and person for sign in/out.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(ui): delete unused components/ui PersonIcon

The More menu still uses native/icons/person-icon.tsx.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): address review feedback on host toolbar presses

The release note said hosts keep the same presses. Settings and sign-in now open from More, and the retired avatar test ids are named.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): address review feedback on capsule clipping

Light mode keeps the shadow on the outer pill. Labels and press fills clip on an inner view.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): address review feedback on the More hit target

The More pressable is 44 points. The icon stays 24, and the test checks that frame.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* feat(ui): polish Reader Search open state

Focus the field when the sheet opens, match the search pill to the other toolbar capsules, and show three trending and recent queries.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(ui): simplify styles in BibleReaderSearchSheet

* fix(ui): use synced Reader Search strings

The locale check blocks a hand-edited catalog. Call the synced keys, and read the focus spy from its context so typecheck accepts it.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(ui): YPE-5836: Add native Bible chapter picker (#204)

* feat(ui): add native Bible chapter picker

Amp-Thread-ID: https://ampcode.com/threads/T-01a0c4d9-366a-702e-a0d8-e56d50e888bc
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>

* fix(ui): refine native chapter picker

Amp-Thread-ID: https://ampcode.com/threads/T-01a0c4d9-366a-702e-a0d8-e56d50e888bc
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>

* fix(ui): polish chapter picker interactions

Amp-Thread-ID: https://ampcode.com/threads/T-01a0c4d9-366a-702e-a0d8-e56d50e888bc
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>

* fix(ui): preserve chapter picker sheet theme

Amp-Thread-ID: https://ampcode.com/threads/T-01a0c5d4-3d6c-712f-b216-cb65fb75357f
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>

* fix(ui): address chapter picker review feedback

Keep the selected-book fade on one color, avoid reloading the catalog when provider filters are recreated with the same contents, and scroll the selected chapter back into view after an order change.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): ignore chapter picks for a revoked Bible version

Drop the loaded catalog as soon as provider filters change, and refuse a selection when the current filters no longer allow that version.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): drop the Reader Search capsule

Search is a plain 24pt icon with 8pt of padding. The toolbar row uses 16pt on the leading edge and no trailing pad.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): inset the Reader More control from the trailing edge

The row had no trailing pad, so More sat on the screen edge. Add 8pt on that side.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): keep a trailing space after a submitted search

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): retry a search page when its hits cannot be titled

A later page with hits and no titles stays retryable instead of marking those verses seen.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): label search retry buttons Retry

The failed search and the page-error control were saying Error.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(ui): press a bad search result on the real sheet

The reader mock never called the sheet, so a bad id could still navigate and the test would pass.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(ui): say Cancel in the Reader Search changeset

The header already uses the Cancel string. The release note was still saying Done.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(ui): delete the unused font settings icon

The More menu still uses AaIcon, and nothing imported this file.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): narrow the search view before retrying in the test

Typecheck rejected footer on the full search view after the await.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(ui): focus a verse across the Expo DOM bridge

A Search tap should scroll to the verse. The WebView can only take a plain object, so the DOM wrapper builds BibleReaderNavigation and calls focusReference when seq changes.

* fix(ui): drop the example John 3:16 focus control

The example Reader is not a focus demo. Search still focuses through the Reader's own navigation.

* fix(ui): keep a repeated or rejected verse focus from moving the reader

A WebView reload was jumping back to the last focused verse, and a passage the Web SDK rejects was clearing the page.

* fix(ui): type the verse focus mocks with jest's two type arguments

tsc rejects a single type argument on jest.fn, which failed the PR typecheck.

* fix(ui): apply a verse focus queued before the Reader mounts

The WebView treated the first focus number as already handled, so a focus waiting at startup never scrolled. A later reload still skips that same number.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): show the suggestion spinner only after the request starts

The debounce wait stays blank. The spinner keeps the list height so the sheet does not collapse.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): skip a repeat search for the same query and version

Pressing Search again after results land was clearing the list and fetching it again. A failed search still retries.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): announce a failed search next to Retry

A failed search and a failed next page only showed Retry. Screen readers now hear Error when that state appears.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): load the next search page at the last five rows

The list waited until the last fifth of the viewport. It now requests the next page once a row in the last five is on screen.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): finish remaining Search review notes

Give the field a search role, grow Search and Clear to 44pt hits, and document that Search taps and focusReference scroll and dim.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): address Search review follow-ups

Prevent verse focus from replaying after a WebView reload, skip a
whitespace-only repeat search, and give Retry a 44pt hit target.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): match Search clear to the picker field

Use the same 18pt ClearIcon, trailing placement, and 10pt hit slop as the native picker search field.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): apply verse focus only after the WebView reports it

A focus requested while the WebView is loading was marked applied on the native side and then skipped when the WebView became ready.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): show completed search results again after a whitespace edit

Pressing Search on the same query restores the page already loaded and keeps the typed space.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): give Search Clear a 44pt touch target

The 18pt icon stays. The button itself is 44pt so the extra area does not cover the text field.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): drop a void call the linter rejects

The focus report already returns nothing, so the void operator is not allowed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): let Search delete whitespace after restoring results

setQuery ignored any edit equal to the submitted query. A restored page
keeps the typed text ("hope "), so backspacing to "hope" was dropped and
the field snapped back. Compare against the displayed field text instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ui): keep the last of two quick Search edits

Compare each edit against the reducer's current state instead of the
last render, so a type-then-delete before a re-render is not dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ui): keep paging Search past a page of repeated verses

A next page whose hits were all already shown added no rows, so the list
never scrolled to ask again and results stopped early. Follow the new
cursor instead; stop if the API hands back the same one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ui): stop Search paging when cursors cycle

Following the cursor past repeat-only pages looped forever if the API
cycled cursors (A -> B -> A). Remember every fetched cursor for the result
set and stop when a repeat-only page points back at one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ui): dedupe the first page of Search results

Later pages already dropped repeated hits, but page one used the raw list,
so a repeated id rendered two rows with one key and fetched the passage twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ui): keep Search focus and theme with the Reader

The navigation object stores the applied focus sequence, so remounting the Reader does not send an acknowledged verse to the Web SDK again. Search content uses the Reader theme, so result text matches the sheet when the provider scheme differs.

* fix(ui): scope verse focus to the navigation object

A replacement navigation object restarts its focus sequence at 1. The WebView now tracks which object that sequence belongs to, so the new object's first focus still runs once.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(ui): show a late verse-focus ack marks the replacement reader

The callback still accepts only the sequence. After a navigation swap, that sequence is stored on the reader now on screen.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): ignore a verse-focus ack from another navigation stream

Expo delivers the action to the reader on screen. The acknowledgment names its stream, so a late reply from the previous navigation object does not mark the replacement.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Dustin Kelley <141975656+Dustin-Kelley@users.noreply.github.com>
Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>
Co-authored-by: Dustin Kelley <Kelleydustin2018@gmail.com>
Co-authored-by: platform-localization-pr-bot[bot] <294853593+platform-localization-pr-bot[bot]@users.noreply.github.com>
Co-authored-by: camrun91 <16566029+camrun91@users.noreply.github.com>
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Make local iOS simulator development reliable (#197)

* chore: make iOS simulator development reliable

Amp-Thread-ID: https://ampcode.com/threads/T-01a0b05a-be6e-7792-8c35-a659fd7f4b18
Co-authored-by: Amp <amp@ampcode.com>

* fix: harden iOS launcher lifecycle

Amp-Thread-ID: https://ampcode.com/threads/T-01a0b05a-be6e-7792-8c35-a659fd7f4b18
Co-authored-by: Amp <amp@ampcode.com>

* fix: stop iOS launcher process trees

Amp-Thread-ID: https://ampcode.com/threads/T-01a0b05a-be6e-7792-8c35-a659fd7f4b18
Co-authored-by: Amp <amp@ampcode.com>

* fix: improve iOS doctor diagnostics

Amp-Thread-ID: https://ampcode.com/threads/T-01a0b05a-be6e-7792-8c35-a659fd7f4b18
Co-authored-by: Amp <amp@ampcode.com>

* fix: align iOS Metro and simulator selection

Amp-Thread-ID: https://ampcode.com/threads/T-01a0d8ed-5471-76a9-aa27-37954ee1b920
Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>

---------

Co-authored-by: Amp <amp@ampcode.com>

* chore: sync react native localization

---------

Co-authored-by: Cameron Pak <cameronandrewpak@gmail.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: camrun91 <16566029+camrun91@users.noreply.github.com>
The hybrid card accepts the Web prop and drops it, so a caller cannot set direction.
The public prop reached BibleCardBody and stopped there, so scripture and footnotes stayed in the default direction.
* feat(ui): native BibleReader settings content

Render font size, line spacing, and font family controls in the native
settings sheet, and remove the unused DOM settings and version picker
content impls.

Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>

* fix(ui): match native reader settings to the webview sheet

Let the controls grow with their labels, show the readable font names, and keep dark mode on the sheet surface instead of a white selected chip.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(ui): open the search sheet to the full viewport

The results area was half the screen, so the sheet stopped there. Size it to the viewport so it opens all the way up.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): satisfy typecheck in the sheet tests

ReactNode was imported from react-native, and text styles were read as view styles.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): take the synced reader settings strings

Drop the hand-edited English keys so this branch uses the catalog already on v2.0.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): keep search results above the keyboard

The full-height search sheet cannot rise, so the list pads by the keyboard overlap and the last row can scroll into view.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): wait to show the serif choice until the face is registered

The settings preview was drawing Untitled Serif before that face had loaded.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(ui): note the font-size letter is a size sample

The letter matches the web settings buttons. The spoken name is already the accessibility label.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ui): satisfy lint on font readiness and keyboard tests

The font hook names its return. The keyboard test uses a keyboard event, and the subscription stub states why the extra fields are unused.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: mark the retired DOM picker notes

The version picker is native, and reader settings no longer cross the font bridge. The old notes stay as history.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Dustin Kelley <Dustin-Kelley@users.noreply.github.com>
Settings previously hid the serif option until the Fonts API finished, so users on Inter could not switch back during a slow request despite the bundled fallback.
The theme directory scan rejects any source containing the substring rem; rephrase the useBrandFonts doc comment so CI passes.
Eager Source Serif 4 registration under Untitled Serif names blocked the Fonts API faces from replacing them in Expo Font; settings still show the serif choice while the request is pending.
Register Source Serif 4 immediately for native chrome during slow API calls, then unload and reload faces when remote TTFs arrive so Expo Font can apply Untitled Serif.
tokens.test.ts scans theme files for rem; rename API TTF helpers and wording so CI passes without changing font behavior.
When appKey or apiHost changes, cancelled effect scopes skip registry updates so an older Fonts API response cannot overwrite Untitled Serif faces.
Dustin-Kelley and others added 30 commits October 7, 2026 11:52
The example's Native tab was the first POC; the shipped reader in
packages/ui superseded it. Drops its tab, files, and the platform-core
and linkedom deps only it used.

Without the example's copy, hoisting nests linkedom's ESM deps beneath
it, so Jest now lets linkedom through to transform them. Also clears
three lint errors in passage.tsx.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Swift shows the book and chapter only in the toolbar, so the native
reader no longer repeats them above the text. Top padding drops from
48 to 16 to match Swift's vertical padding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Swift fills only a line's typographic bounds, so highlighted lines show
a gap. iOS line metrics span the whole line box, so the backdrop clips
each line to an em band measured off Swift's serif (0.98 up, 0.26 down).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ndroid

Android reads colour 0 as unset, so transparent ghost text fell back to
black; ghosts now use alpha 0 white. Band and underline windows that spill
past their line read from their own line instead of the next one.

A full-line inline box grows the line's ascent on Android and takes it
from the descent, which clipped a last line's descenders and made lines
with verse numbers taller. The box is capped at the band's ascent, with a
fill that keeps highlights continuous under labels and footnote icons.
The footnote icon rises less on Android to clear the line above.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selecting a verse remounts its paragraph (the Fabric iOS attachment
workaround), which reset the measured lines and size, so the highlight
vanished for a frame and returned against a zero-width backdrop. Each
block now keeps its last layout per look and starts a remount from it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASV Psalm 119 opens each stanza with a centred Hebrew letter line (qc),
so the first-letter rule laid the whole chapter out right-to-left. The
passage's scripture letters now vote; headings still abstain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Synthesized small caps were 0.75x and untracked; Swift's are about 0.86x
cap height and 23% wider. Runs now render at 0.85x with 0.055em tracking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Per the style spec, matching Kotlin's 32dp (Swift uses 30), so lines wrap
at the same words as the other native readers. Top spacing that borrowed
GUTTER now uses TOP_PADDING and stays at 16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Any non-200 read as "unavailable in this version", so a 429 or 5xx told
readers the chapter was missing. Only 403 and 404 do now; other failures
show the generic request error with Retry, and Retry hides when the
passage is unavailable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Xcode 27 replaced Simulator.app with DeviceHub, so `open -a Simulator`
failed and stopped dev:ios. Opening the window is now best-effort; the
launcher boots the device either way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Tag block layouts with their passage and cancel a pending focus landing
  on chapter or version change, so it never scrolls or dims the new text.
- Footnote sheet follows RTL scripture direction and uses the system serif
  stand-in until the brand serif registers (SERIF_STANDIN moves to fonts).
- A passage parse throw reads as 'failed', not 'offline'.
- Changeset: drop the core bump; note the unstable renderer switch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erse

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Derive highlight paint from the passage on screen, so the outgoing
  chapter never wears the incoming chapter's colors while it loads.
- serifReady reflects whether a serif actually registered; a failed load
  keeps the stand-in.
- Footnote HTML decodes typographic named entities (quotes, dashes, ellipsis).
- Cap a focus passage range at 250 verses.
- Label and link faces go through sansFace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The WebView reader stays only for comparison and will be removed. BibleTextView, BibleCard, and VerseOfTheDay still use the WebView.

Co-authored-by: Cursor <cursoragent@cursor.com>
Footnotes already render as native text. The old webview file, the sheet loader nothing called, and the DOM sizing helper go with it.

Co-authored-by: Cursor <cursoragent@cursor.com>
The native chapter body had no tests, so a broken selection, a cancelled focus, or a missing chapter could ship unnoticed.

Co-authored-by: Cursor <cursoragent@cursor.com>
An fp span was glued to the sentence before it. The reader test also sets includeAuth so those props typecheck.

Co-authored-by: Cursor <cursoragent@cursor.com>
Lint rejects a typeof check in the native reader test.

Co-authored-by: Cursor <cursoragent@cursor.com>
A highlighted paragraph was laying out three copies of itself on every line. Paint it once, cover the rest, and clip that paint so it does not streak into the gutter.

Co-authored-by: Cursor <cursoragent@cursor.com>
A full line already sat on the column edge, so the highlight stopped with the letters, and Android left a one-pixel streak under the last line. Give the column a little extra width, pad the words back, and cover that stray pixel.

Co-authored-by: Cursor <cursoragent@cursor.com>
The extra 3 points ran the paint into the next verse number, and the footnote bubble crossed the top of the highlight. 0.1em fits in the trailing space, and the bubble sits inside the paint.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
useSerifFace now owns the stand-in swap, so a serif consumer cannot name
the brand face before it registers. The footnote sheet also moves its
props type below the imports and parses its HTML once per change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

4 participants