From d5d8861ff4909cf5ebfce4c9301668919b45000f Mon Sep 17 00:00:00 2001 From: Kyleasmth Date: Tue, 22 Sep 2026 12:43:14 -0700 Subject: [PATCH 1/6] docs: add a generated root changelog merged from the package changelogs --- CHANGELOG.md | 223 +++++++++++++++++++++++++++++++ package.json | 5 +- scripts/build-root-changelog.mjs | 202 ++++++++++++++++++++++++++++ 3 files changed, 428 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 scripts/build-root-changelog.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..3e9e159f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,223 @@ +# Changelog + +All notable changes to the YouVersion Platform React Native (Expo) SDK. + +`@youversion/platform-react-native-expo-core` and +`@youversion/platform-react-native-expo-ui` are a `fixed` group in `.changeset/config.json`, +so they share a version number and release together. Each entry below notes which packages +it affected. + +Generated from the per-package changelogs by `scripts/build-root-changelog.mjs` — edit those, +or the changeset, rather than this file. + +## 1.6.0 + +### Minor Changes + +- _(@youversion/platform-react-native-expo-ui)_ 040d231: feat: name `tokens.radius` by role — `surface` (16, web `rounded-2xl`) and `full` (pill) — and drop the `sm`/`md`/`lg`/`xl` steps ported from web's shadcn calc ramp, which only Button read and which rendered as a pill anyway. Adds the internal `Card` compound primitive on `radius.surface`. + + ## Migration + + `tokens.radius.md` (and `sm` / `lg` / `xl`) maps to `tokens.radius.full`. Surfaces use `tokens.radius.surface`. + + ## Released as minor, not major + + `radius` reaches consumers through the public `getTokens` / `useTokens` / `Tokens` + surface, so dropping the size keys is technically a breaking type change. It ships + as `minor` deliberately: the ramp went public one release ago in 1.5.0, the design + system is still being built out, and no consumer reads `tokens.radius` yet. + +### Patch Changes + +- _(@youversion/platform-react-native-expo-ui)_ 233dc86: fix: YouVersionProvider holds children until bundled Inter registers, then native text always draws Inter. First paint waits on that local load. Theme toggles must not ellipsize button labels or drop a line from multi-line text. + +- _(@youversion/platform-react-native-expo-ui)_ a32975d: Sync localization from platform-localization (0ae8cca): update 29 keys in en. + +- _(@youversion/platform-react-native-expo-ui)_ cd9b2ff: Sync localization from platform-localization (5225dbc): update 1 keys in en. + +- _(@youversion/platform-react-native-expo-ui)_ 2dc28cc: fix: derive native sheet chrome from design tokens (YPE-5271). Handle, muted labels, stroke, and shadows now resolve from palette / semantic tokens instead of copied hex. A small shift on the handle and supporting labels is expected where the old hex sat off-palette. + +- _(@youversion/platform-react-native-expo-ui)_ b57174d: Replace auth-button hex with design tokens (YPE-5272). Border, fill, and label colors resolve from border/background/foreground for the background prop's scheme. Borders and white surfaces stay byte-identical; pure-black values move to #121212. + +- _(@youversion/platform-react-native-expo-ui)_ 52b742e: feat: add internal Tabs, Accordion, and Popover primitives (YPE-5439 / RNV2-1). Token-styled wrappers around `@rn-primitives` 1.4.0 (same line as the existing portal). Picker/chrome use only; not public API. + +- _(@youversion/platform-react-native-expo-ui)_ 3247134: fix: resolve BibleTextView light/dark on native and pass that scheme into the in-WebView provider (YPE-5442 / RNV2-4). Font size and family stay consumer props; `fontFamily` still crosses as an ADR 0009 token, not a `getTokens` value. + + Standalone `BibleTextView` now uses the same content-sized embed defaults as `BibleCard` / `VerseOfTheDay` (`matchContents`, `flex: 0`, scroll off). Pass `dom={{ matchContents: false }}` to opt out and size with flex styles. + +## 1.5.0 + +### Minor Changes + +- _(@youversion/platform-react-native-expo-core)_ 80d3718: Bible content is cached on device (YPE-5262). The Bible Content Client reads a per-version MMKV store before fetching and writes each 2xx body back with the lifetime the response's `Cache-Control` declares — `max-age` less `Age`, seven days when no usable `max-age` is present, and no write at all for `no-cache`, `no-store`, or a lifetime of zero — so previously read chapters, pickers, and BibleCard content render without a network, including offline. Content is scoped to the app key and survives sign-out. + +- _(all packages)_ 553757e: Native owns Bible content requests (YPE-5510). Core exposes a Bible Content Client and a required `fetchBibleContent` action on the provider context; the UI package weaves it under each DOM component's `fetch`, so eligible `/v1/bibles/*` requests cross the bridge and run natively with `X-YVP-Sdk: ReactNativeSDK=` headers. The SDK version stamp moves from ui to core (`@youversion/platform-react-native-expo-core/sdk-version`). + +- _(all packages)_ 5805e95: feat: replace the native highlight apply palette with the six YPE-5058 hexes, mix verse-action dots against `SHEET_SURFACE` via `mixSrgb`, and pin `@youversion/platform-react-ui` to 2.12.0 so reader fill and Words of Christ match that release (YPE-5059). Apply stays palette-only. Leftover `fffe00` still paints and clears. WOC stays unmixed `#94000C` / `#e4bfc2`. + +- _(all packages)_ 3dfe296: feat: load Inter, Untitled Serif, and Source Serif 4 from `YouVersionProvider` (YPE-5266) + + `YouVersionProvider` registers brand fonts in the background with `expo-font`. Untitled Serif is fetched from the Fonts API (`GET /v1/fonts/1` with `X-YVP-App-Key`). Inter and Source Serif 4 come from Google Font packages. There is no opt-out and no public fonts-ready hook. Children still render while fonts load. If Untitled Serif cannot load, native serif falls back to Source Serif 4. + + ## Action required + + Install the new `expo-font` peer and rebuild the dev client. A JS-only reload shows `Cannot find native module`. + + ```bash + npx expo install expo-font + ``` + +- _(@youversion/platform-react-native-expo-ui)_ a1f5751: feat: export `getTokens` with locally owned light/dark design tokens ported from the web theme (YPE-5264). Palette and semantic maps stay internal. Does not change live sheet or auth-button colors. + +- _(@youversion/platform-react-native-expo-ui)_ 3758182: feat: export `useTokens` from YouVersionProvider theme context (YPE-5265). + +- _(@youversion/platform-react-native-expo-ui)_ 9c8003b: feat: expose `typography` size scale on `Tokens` via `useTokens` (YPE-5268). + +## 1.4.0 + +### Minor Changes + +- _(all packages)_ dd11c3f: Export `hookOverrides` on `YouVersionProvider` as a test seam, plus the `HookOverrides` and `AuthContextValue` types. Production apps leave `hookOverrides` unset. + +- _(@youversion/platform-react-native-expo-ui)_ de27302: feat: expose BibleCard `maxWidth` (`number | '100%'`) and forward it to the web card (YPE-5197). Pin `@youversion/platform-react-ui` to 2.10.0 so the WebView runs the published card (platform-sdk-react#354). Native only forwards the prop; scripture fill (`--yv-reader-max-width: none` on the painted section) stays web-owned. + +## 1.3.1 + +### Patch Changes + +- _(@youversion/platform-react-native-expo-ui)_ f5501f8: fix: omit verse-action highlight colors when `auth` is not configured, so a kids app without sign-in still gets Copy and Share instead of dead swatches. + +- _(@youversion/platform-react-native-expo-ui)_ 9d90e75: fix: forward resolved provider locale into DOM WebViews so in-WebView copy matches native SDK language. + +## 1.3.0 + +### Minor Changes + +- _(all packages)_ 7388cbe: feat: paint host highlights on BibleTextView, BibleCard, and VerseOfTheDay + + Subscribe those surfaces at chapter scope and always pass Highlight[] into the DOM so paint uses the native cache. + +- _(all packages)_ f88fd12: Add optional version filter lists to `YouVersionProvider`: `permittedVersionIds`, `excludedVersionIds`, and `permittedLanguageTags`. The UI provider forwards them through native wrappers into each DOM web `YouVersionProvider`. Pin `@youversion/platform-react-ui` and `@youversion/platform-core` to 2.8.0 so the web SDK enforces those lists in Expo DOM WebViews (YPE-4657/YPE-4658). + +## 1.2.0 + +### Minor Changes + +- _(all packages)_ 624d008: Bible highlights on native. The reader paints the highlights of the signed-in user. Verse actions are a native bottom sheet. Highlights made offline survive a relaunch and land on their own. A user who taps a color before sign-in or grant still gets that highlight. + + ## Action required + + Install three new peer modules and rebuild the dev client. A JS-only reload shows `Cannot find native module`. + + ```bash + npx expo install expo-network expo-clipboard expo-application + ``` + + - `expo-network` is a core peer. It wakes parked writes when connectivity returns. + - `expo-clipboard` is a UI peer. It is the Copy fallback in the verse action sheet. + - `expo-application` is a UI peer. It supplies the app name on the sign-in sheet. Core no longer depends on it. + + CAUTION: The default serif font of the reader changes from Source Serif 4 to Untitled Serif. The WebView fetches a stylesheet from `api.youversion.com` and font files from `cdn.youversion.com`. There is no opt-out. If those hosts are blocked, serif text falls back to Source Serif 4. Readers who chose Source Serif are migrated. Any other `fontFamily` you pass is left untouched. + + ## BibleReader + + `BibleReader` owns highlights on native. It reads `useHighlights` for the current passage and passes the result as a controlled prop. The WebView does not fetch highlights, store them, or hold a token. + + A verse selection opens a native sheet with the reference, color swatches, Copy, and Share. No new prop turns this on. + - Swatches: a remove circle for each color on the selection, then an apply circle for the palette colors not already covering the selection. + - Sign-in and consent: the sheet asks for whatever is missing, then applies the chosen color. This needs `auth.permissions` to include `highlights`. + - Copy and Share fall back to `expo-clipboard` and React Native `Share`. Optional `onCopy` and `onShare` take either over. Both receive `BibleReaderShareData`. + + The sheet has no backdrop. A backdrop blocks the next verse tap. The user dismisses the sheet with a swipe down, a deselect, or an action on the sheet. Themed sheets draw an upward drop shadow so the sheet still separates from the page. + + Selection across the bridge: + - `onVerseSelect(selection)` fires on every change, including a clear (`verses: []`). + - `clearSelectionSignal` dismisses the selection from native. The host increments the value. The value at mount is the baseline. + + `BibleReaderVerseSelection` and `BibleReaderShareData` are re-exported from this package. + + `ref.refreshHighlights()` re-fetches the current passage. A screen that regains focus can call it. + + `onHighlightError` reports `{ status: 'queued' }` and `{ status: 'error', reason: 'transient' }` only. Other outcomes stay silent. The `HighlightWriteError` type is exported. + + Sign-out from the reader menu and from `YouVersionAuthButton` asks first. If parked writes are still waiting, the alert is "Save your highlights?". The Confirm action calls `signOut()`. `useYVAuth().signOut()` still signs out at once. `useSignOutGuard` is exported for a host sign-out UI. `hasQueuedHighlightWrites(userId)` chooses the alert variant and never throws. + + On web, the Web SDK popover is the verse-action UI. Sign-out is unprompted because `Alert.alert` is a no-op on React Native Web. + + ## useHighlights + + `useHighlights({ versionId, book, chapter })` is the public surface for highlight data. It paints from an MMKV cache on the first render. `apply` and `remove` are optimistic. If the server refuses a write, the paint reverts. + + `apply` and `remove` resolve a `HighlightWriteOutcome`: `ok`, `noop`, `queued`, or `error`. `queued` is new. It is a `minor` because no existing status changed meaning. An exhaustive `switch` with no `default` is the only consumer branch that breaks. An `error` carries `reason` (`not-signed-in` / `auth` / `invalid` / `transient`) plus `failedVerses` and `succeededVerses`. The hook `error` is fetch-only. + + Also exported: `deriveServerColors`, `HIGHLIGHT_COLORS`, `isHighlightColor`, `refresh()`, and the `Highlight` / `HighlightColor` / `HighlightScope` / `ServerColors` types. + + `apply` accepts only the five palette colors. A valid non-palette hex already on the account paints and clears by exact value. An unparseable hex is dropped. + + `isRefreshing` means a GET is in flight. `highlights` is always safe to render. Mounted subscriptions also refresh when the app becomes active. + + The GET runs only for an app that requested the `highlights` permission on `YouVersionProvider`. The gate reads the requested list, not a grant. + + ## Offline writes + + A tap with no service keeps its paint and parks the write. The write is stored per user and chapter. It survives a relaunch. When service returns, the write lands. + - Unreachable or 5xx: paint stands. Outcome is `{ status: 'queued', verses }`. + - 401, 403, or any other 4xx: paint reverts. Outcome reports the refusal. + + `queued` repeats on every tap of a verse that is still parked. If you show "saved offline" once, hold that copy in your own state. + + Sign-out drops every parked write with the highlights cache and the grant cache. A write parked on one account cannot land on the next account. + + A 401 or 403 on the drain earns one forced refresh and one retry. Only a second auth refusal under a minted token drops the entry. A failed force drops nothing. + + ## Highlighting before sign-in + + `useHighlightPermissionFlow` wraps `useHighlights` and guards `apply`. It holds the pending highlight, runs sign-in and/or consent, then applies. `remove` passes through. + + It returns the `useHighlights` result plus `isConfirming`, `confirm()`, `decline()`, and `flowError`. A cancel or decline resolves `noop`. `BibleReader` already wires the prompts. This hook is for a custom highlight UI. + + This needs `auth` on `YouVersionProvider` and the `highlights` permission. With no `auth`, the flow behaves as signed out. + + ## Permissions and tokens + + `useYVAuth()` now reports `grantedPermissions`, `hasPermission()`, `invalidatePermissions()`, and `requestedPermissions`. `grantedPermissions` is `null` (unknown), `[]` (denied), or a list (granted). The grant is read from the OAuth app redirect and cached per user. + + `requestPermissions(permissions)` asks a signed-in user for a grant without sign-out. It resolves a `DataExchangeOutcome` and never throws: `granted`, `cancel`, or `failure` (`not-signed-in` / `not-permitted` / `user-changed` / `in-progress` / `transient`). The grant merges. The consent page returns to your `redirectUri`. If that URI does not match the registered callback, the outcome is `cancel`. + + The cached grant is a hint. A privileged action gates on the pre-flight, not on a cached `true`. + + `getAccessToken(options?)` resolves `{ status: 'ok', token, userId }` or `{ status: 'unavailable', reason: 'signed-out' | 'refresh-failed' }`. It refreshes only near expiry unless you pass `{ force: true }`. It never rejects. `refresh-failed` keeps the session. Highlights writes and `requestPermissions` treat `refresh-failed` as `transient` and do not send the request. + + ## Dependencies + + `@youversion/platform-core` and `@youversion/platform-react-ui` move to 2.6.2. That release supplies controlled highlights, data-exchange primitives, and a fix that reads an empty-body 2xx DELETE as success. + +### Patch Changes + +- _(@youversion/platform-react-native-expo-core)_ 2902934: Installation IDs are now a random UUID persisted in MMKV, not the device identifier (iOS IDFV / Android `ANDROID_ID`). Kids' apps that ship this SDK must not transmit persistent device IDs under COPPA; this matches the Swift, Kotlin, and React web SDKs. Existing stored installation IDs are left unchanged. `expo-application` is no longer a peer dependency of core. `YouVersionProvider` resolves the installation ID synchronously, so the `fallback` prop is unused. + +## 1.1.1 + +### Patch Changes + +- _(@youversion/platform-react-native-expo-ui)_ 0726f7a: Sync localization from platform-localization (15b2da1): add French (fr); update 15 keys in en. + +## 1.1.0 + +### Minor Changes + +- _(@youversion/platform-react-native-expo-core)_ 89a4e50: Partners can now request YouVersion Platform permissions at sign-in. `AuthConfig` gains an optional `permissions` field typed by the new exported `AuthPermission` union (`'bibles' | 'highlights' | 'votd' | 'demographics' | 'bible_activity'`), and the PKCE flow appends each requested value to `/auth/authorize` as a repeated `requested_permissions[]` param — deduped and sorted, and omitted entirely when no permissions are configured. Permissions are deliberately kept separate from `scopes`: they are not OIDC scopes, and the auth server silently drops unknown values from `scope`, so requesting one there would grant nothing. This ships the request side only — reading back which permissions the user actually granted arrives in a later release. + +### Patch Changes + +- _(@youversion/platform-react-native-expo-ui)_ f0057ca: Syncs additional native UI locales from platform-localization (Czech, Finnish, Hungarian, Italian, Dutch, Norwegian, Serbian, Ukrainian) and registers them in the SDK locale catalog. Norwegian device tags (`nb` / `nn`) now resolve to the bundled `no` resource. + +## 1.0.0 + +### Major Changes + +- _(all packages)_ ce283a0: Release 1.0.0 — the first stable release of the YouVersion Platform React Native Expo SDK. + + This is a milestone version bump marking the SDK's official 1.0 launch. There are no breaking API changes from 0.9.1; the major bump signifies the transition to a stable, publicly supported release line. + +## 0.9.1 diff --git a/package.json b/package.json index cd684eab..9212f595 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,9 @@ "generate:locale-index": "node scripts/generate-locale-index.mjs", "check:locale-index": "node scripts/generate-locale-index.mjs --check", "changeset": "changeset", - "version-packages": "changeset version", - "release": "turbo build && pnpm exec changeset publish" + "version-packages": "changeset version && node scripts/build-root-changelog.mjs", + "release": "turbo build && pnpm exec changeset publish", + "build:root-changelog": "node scripts/build-root-changelog.mjs" }, "devDependencies": { "@changesets/cli": "2.29.7", diff --git a/scripts/build-root-changelog.mjs b/scripts/build-root-changelog.mjs new file mode 100644 index 00000000..f541a274 --- /dev/null +++ b/scripts/build-root-changelog.mjs @@ -0,0 +1,202 @@ +#!/usr/bin/env node +// Build the repo-root CHANGELOG.md by merging the per-package changelogs Changesets writes. +// +// Swift and Kotlin each ship a root CHANGELOG.md; this repo had only per-package files, so +// there was no single place to see what shipped in a release (YPE-4190). +// +// The two packages are a `fixed` group in .changeset/config.json, so they always share a +// version number and a changeset touching several of them writes the *same* entry into each +// of their changelogs. Copying all three verbatim would therefore triple most entries. This +// merges by version, shows each entry once, and notes which packages it affected. +// +// `Updated dependencies` blocks are dropped: they are the fixed group's internal bookkeeping, +// not something a consumer reading release notes needs. +// +// Run via `pnpm build:root-changelog`; wired into `version-packages` so the Version Packages +// PR carries an up-to-date root changelog. +import { readdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const PACKAGES = join(ROOT, 'packages') + +/** `## 2.12.1` ... up to the next `## ` */ +function versionSections(markdown) { + const out = [] + const lines = markdown.split('\n') + let current = null + for (const line of lines) { + const m = line.match(/^## (\d+\.\d+\.\d+.*)$/) + if (m) { + if (current) out.push(current) + current = { version: m[1].trim(), body: [] } + continue + } + if (current) current.body.push(line) + } + if (current) out.push(current) + return out +} + +/** + * Split a version body into `{ kind, entries }`, where kind is Major/Minor/Patch and each + * entry keeps its continuation lines (Changesets indents them by two spaces). + */ +function parseEntries(bodyLines) { + const groups = [] + let kind = null + let entry = null + const push = () => { + if (entry && kind) groups.push({ kind, text: entry.join('\n').trimEnd() }) + entry = null + } + for (const line of bodyLines) { + const heading = line.match(/^### (.+?) Changes\s*$/) + if (heading) { + push() + kind = heading[1] + continue + } + if (/^- /.test(line)) { + push() + entry = [line] + continue + } + if (entry) entry.push(line) + } + push() + return groups + .map((g) => ({ ...g, text: stripBookkeeping(g.text) })) + .filter((g) => g.text !== null) +} + +/** `- @youversion/platform-react-native-expo-core@1.6.0`, at any indent. The group bumping itself. */ +const DEPENDENCY_BUMP = /^\s*-\s+@[^@\s]+@\d[\w.-]*\s*$/ + +/** + * Remove the fixed group's own version bookkeeping, returning null when an entry is nothing else. + * + * Changesets records it two ways and both reach a consumer-facing changelog as noise: + * an `Updated dependencies` block, and bare `- @pkg@version` lines, either standing alone as + * their own entry or trailing a real note as continuation lines. + */ +function stripBookkeeping(text) { + if (/^- Updated dependencies/.test(text)) return null + const kept = text.split('\n').filter((line) => !DEPENDENCY_BUMP.test(line)) + const collapsed = kept + .join('\n') + .replace(/\n{3,}/g, '\n\n') + .trimEnd() + // Nothing but a bullet marker left, so the entry was only a version bump. + return /^-\s*$/.test(collapsed) || collapsed === '' ? null : collapsed +} + +/** + * Same note, written slightly differently in two packages' changelogs. Compared on collapsed + * whitespace so a stray blank line does not split one entry into two. + */ +function dedupeKey(text) { + return text.replace(/\s+/g, ' ').trim() +} + +// Take the package list from the `fixed` group rather than whatever directories happen to +// have a changelog. That group is the reason this merge is valid at all: fixed packages share +// a version, so the same entry appears in each of their changelogs. Reading the filesystem +// instead would silently drop a package whose changelog went missing, and would silently fold +// a future unrelated package into `(all packages)`. +const fixedGroups = JSON.parse(readFileSync(join(ROOT, '.changeset', 'config.json'), 'utf8')).fixed +if (fixedGroups?.length !== 1) { + throw new Error( + `Expected exactly one fixed group in .changeset/config.json, found ${fixedGroups?.length ?? 0}.`, + ) +} +const expected = new Map( + readdirSync(PACKAGES) + .filter((d) => existsSync(join(PACKAGES, d, 'package.json'))) + .map((d) => [JSON.parse(readFileSync(join(PACKAGES, d, 'package.json'), 'utf8')).name, d]), +) +const packages = fixedGroups[0].map((name) => { + const dir = expected.get(name) + if (!dir || !existsSync(join(PACKAGES, dir, 'CHANGELOG.md'))) { + throw new Error( + `Fixed-group package ${name} has no changelog; refusing to write a partial root changelog.`, + ) + } + return dir +}) + +/** Section order in the output. */ +const KIND_ORDER = ['Major', 'Minor', 'Patch'] + +/** version -> dedupe key -> { kind, packages, text } */ +const byVersion = new Map() +const order = [] + +for (const dir of packages) { + const pkgName = JSON.parse(readFileSync(join(PACKAGES, dir, 'package.json'), 'utf8')).name + const md = readFileSync(join(PACKAGES, dir, 'CHANGELOG.md'), 'utf8') + for (const { version, body } of versionSections(md)) { + if (!byVersion.has(version)) { + byVersion.set(version, new Map()) + order.push(version) + } + const entries = byVersion.get(version) + for (const { kind, text } of parseEntries(body)) { + const key = dedupeKey(text) + const existing = entries.get(key) + if (!existing) { + entries.set(key, { kind, packages: new Set([pkgName]), text }) + continue + } + existing.packages.add(pkgName) + // A fixed-group changeset can land under different headings per package: 71e4c1a is + // Patch for core but Minor for hooks and ui. Keep the most significant kind rather + // than whichever package readdir happened to return first, so the merged entry is + // filed where a reader looking for that change would go. + if (KIND_ORDER.indexOf(kind) < KIND_ORDER.indexOf(existing.kind)) { + existing.kind = kind + } + } + } +} + +// Newest first. Changesets already writes each file newest-first, and the fixed group means +// every package sees the same versions, so first-seen order is release order. +const out = [ + '# Changelog', + '', + 'All notable changes to the YouVersion Platform React Native (Expo) SDK.', + '', + '`@youversion/platform-react-native-expo-core` and', + '`@youversion/platform-react-native-expo-ui` are a `fixed` group in `.changeset/config.json`,', + 'so they share a version number and release together. Each entry below notes which packages', + 'it affected.', + '', + 'Generated from the per-package changelogs by `scripts/build-root-changelog.mjs` — edit those,', + 'or the changeset, rather than this file.', + '', +] + +for (const version of order) { + out.push(`## ${version}`, '') + const entries = [...byVersion.get(version).values()] + for (const kind of KIND_ORDER) { + const forKind = entries.filter((e) => e.kind === kind) + if (forKind.length === 0) continue + out.push(`### ${kind} Changes`, '') + for (const { text, packages: pkgs } of forKind) { + const scope = pkgs.size === packages.length ? 'all packages' : [...pkgs].sort().join(', ') + out.push(text.replace(/^- /, `- _(${scope})_ `), '') + } + } +} + +writeFileSync( + join(ROOT, 'CHANGELOG.md'), + out + .join('\n') + .replace(/\n{3,}/g, '\n\n') + .trimEnd() + '\n', +) +console.log(`Wrote CHANGELOG.md — ${order.length} versions from ${packages.length} packages.`) From d5890d0c2f2cbd67e17569039bee61d6bce414b6 Mon Sep 17 00:00:00 2001 From: Kyleasmth Date: Wed, 23 Sep 2026 09:16:21 -0700 Subject: [PATCH 2/6] fix: keep hand-written release sections out of the discard path The parser only recognised `### Changes`, the form Changesets emits. React's changelogs are entirely Changesets output so that held there, but this repo's 0.9.1 was written by hand under `### Added` and `### Package surface`, and prose sat directly under the version heading. Those entries never set a kind and were dropped, so 0.9.1 rendered empty. Any `###` heading is now kept, verbatim when it is not a Changesets level, and ordered after the levels. Prose above the first heading is carried over with the same package annotation as entries. All 49 entries and both release summaries now survive. Found by Greptile. --- CHANGELOG.md | 68 ++++++++++++++++++++++++++++++++ scripts/build-root-changelog.mjs | 49 ++++++++++++++++++++--- 2 files changed, 112 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e9e159f..e5516d00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -221,3 +221,71 @@ or the changeset, rather than this file. This is a milestone version bump marking the SDK's official 1.0 launch. There are no breaking API changes from 0.9.1; the major bump signifies the transition to a stable, publicly supported release line. ## 0.9.1 + +_(@youversion/platform-react-native-expo-core)_ Initial release. Installation id, optional PKCE authentication, and storage adapters for the YouVersion Platform React Native (Expo) SDK. + +_(@youversion/platform-react-native-expo-ui)_ Initial release. Drop YouVersion Bible content into an Expo app on iOS and Android, with native bottom sheets, theming, and optional sign-in. Built on the [React Web SDK](https://github.com/youversion/platform-sdk-react) wrapped as [Expo DOM Components](https://docs.expo.dev/guides/dom-components/), with native affordances layered on top. + +### Added + +- _(@youversion/platform-react-native-expo-core)_ `YouVersionProvider` — installation id plus optional `auth` config (forwarded by the UI provider), and the `useYouVersion` hook + +- _(@youversion/platform-react-native-expo-core)_ PKCE OAuth via `useYVAuth`, with auth types `AuthConfig`, `AuthScope`, and `YVUserInfo` + +- _(@youversion/platform-react-native-expo-core)_ Token storage in `expo-secure-store`; token expiry and cached user info in MMKV via `mmkvStorage` + +**Auth hardening** + +- _(@youversion/platform-react-native-expo-core)_ User info drops placeholder and non-`https` avatar URLs (blocked by iOS ATS and Android cleartext defaults anyway), so consumers never receive a broken picture URL + +- _(@youversion/platform-react-native-expo-core)_ Canceling sign-in (`access_denied` callback) is treated as a clean cancel rather than an error + +- _(@youversion/platform-react-native-expo-core)_ Cached user info is validated with a zod schema on read, so a corrupt or legacy cache entry can't surface wrong-typed fields + +- _(@youversion/platform-react-native-expo-ui)_ `BibleTextView` — render a verse or verse range from a USFM reference + +- _(@youversion/platform-react-native-expo-ui)_ `BibleCard` — a verse with built-in reader controls + +- _(@youversion/platform-react-native-expo-ui)_ `VerseOfTheDay` — the daily verse, ready to drop in + +**Bible reader** + +- _(@youversion/platform-react-native-expo-ui)_ `BibleReader` — a full reading experience with built-in chapter and version pickers; bring your own picker UI via `onChapterPickerPress` / `onVersionPickerPress` + +- _(@youversion/platform-react-native-expo-ui)_ Standalone sheets for advanced flows: `BibleChapterPickerSheet`, `BibleVersionPickerSheet`, `BibleReaderSettingsSheet` + +Every prop and option for these components is documented at [developers.youversion.com/sdks/react-native](https://developers.youversion.com/sdks/react-native). + +**Provider & theming** + +- _(@youversion/platform-react-native-expo-ui)_ `YouVersionProvider` — single root provider supplying your `appKey`, resolved theme, and native sheet support + +- _(@youversion/platform-react-native-expo-ui)_ `light` / `dark` / `system` themes, with per-component overrides + +**Authentication (optional)** + +- _(@youversion/platform-react-native-expo-ui)_ `YouVersionAuthButton` and the `auth` prop on `YouVersionProvider` for PKCE OAuth (auth primitives and storage live in `@youversion/platform-react-native-expo-core`) + +**Native presentation** + +- _(@youversion/platform-react-native-expo-ui)_ Footnotes, chapter, and version pickers open in native bottom sheets via `@gorhom/bottom-sheet` + +- _(@youversion/platform-react-native-expo-ui)_ WebView pre-warming so sheets open without a cold-start flash + +- _(@youversion/platform-react-native-expo-ui)_ Sheets cap at 640 wide and center on large screens like iPad; full-width below that breakpoint + +**Types** + +- _(@youversion/platform-react-native-expo-ui)_ Prop types are exported for each component, e.g. `BibleCardProps`, `BibleReaderProps`, `BibleTextViewProps` + +**Attribution** + +- _(@youversion/platform-react-native-expo-ui)_ SDK traffic is identified to YouVersion in the `x-yvp-sdk` header as `ReactNativeSDK={version}`; builds running from source report `{version}-dev`, matching the Web SDK's format + +### Package surface + +- _(@youversion/platform-react-native-expo-core)_ Imports are restricted to the package root via an `exports` map — import everything from `@youversion/platform-react-native-expo-core`. Deep imports (e.g. into `build/`) are not part of the public API. + +- _(@youversion/platform-react-native-expo-ui)_ Only the package root is importable — import everything from `@youversion/platform-react-native-expo-ui`. If you want to see how it all works, read the source on [GitHub](https://github.com/youversion/platform-sdk-reactnative-expo). + +- _(@youversion/platform-react-native-expo-ui)_ Runtime dependencies ship pinned to exact versions, so an install resolves exactly what was published and tested rather than silently picking up a newer release. Peer dependency ranges are unchanged. diff --git a/scripts/build-root-changelog.mjs b/scripts/build-root-changelog.mjs index f541a274..7d28d362 100644 --- a/scripts/build-root-changelog.mjs +++ b/scripts/build-root-changelog.mjs @@ -52,10 +52,15 @@ function parseEntries(bodyLines) { entry = null } for (const line of bodyLines) { - const heading = line.match(/^### (.+?) Changes\s*$/) + const heading = line.match(/^### (.+?)\s*$/) if (heading) { push() - kind = heading[1] + // Keep the heading verbatim unless it is one Changesets writes. React's changelogs are + // entirely Changesets output so only ever carry " Changes", but this repo has a + // hand-written 0.9.1 using `### Added` and `### Package surface`. Matching only the + // Changesets form left `kind` unset and silently dropped those entries. + const level = heading[1].match(/^(Major|Minor|Patch) Changes$/) + kind = level ? level[1] : heading[1] continue } if (/^- /.test(line)) { @@ -129,8 +134,23 @@ const packages = fixedGroups[0].map((name) => { /** Section order in the output. */ const KIND_ORDER = ['Major', 'Minor', 'Patch'] +/** + * Prose written directly under a `## version`, above any `###` heading. Changesets never + * emits this, but a hand-written release can: both packages summarise 0.9.1 that way. + */ +function preamble(bodyLines) { + const out = [] + for (const line of bodyLines) { + if (/^### /.test(line) || /^- /.test(line)) break + out.push(line) + } + return out.join('\n').trim() +} + /** version -> dedupe key -> { kind, packages, text } */ const byVersion = new Map() +/** version -> dedupe key -> { packages, text } for the prose above the first heading */ +const byVersionPreamble = new Map() const order = [] for (const dir of packages) { @@ -141,6 +161,14 @@ for (const dir of packages) { byVersion.set(version, new Map()) order.push(version) } + if (!byVersionPreamble.has(version)) byVersionPreamble.set(version, new Map()) + const lead = preamble(body) + if (lead) { + const leadMap = byVersionPreamble.get(version) + const leadKey = dedupeKey(lead) + if (leadMap.has(leadKey)) leadMap.get(leadKey).packages.add(pkgName) + else leadMap.set(leadKey, { packages: new Set([pkgName]), text: lead }) + } const entries = byVersion.get(version) for (const { kind, text } of parseEntries(body)) { const key = dedupeKey(text) @@ -154,7 +182,11 @@ for (const dir of packages) { // Patch for core but Minor for hooks and ui. Keep the most significant kind rather // than whichever package readdir happened to return first, so the merged entry is // filed where a reader looking for that change would go. - if (KIND_ORDER.indexOf(kind) < KIND_ORDER.indexOf(existing.kind)) { + if ( + KIND_ORDER.includes(kind) && + KIND_ORDER.includes(existing.kind) && + KIND_ORDER.indexOf(kind) < KIND_ORDER.indexOf(existing.kind) + ) { existing.kind = kind } } @@ -180,11 +212,18 @@ const out = [ for (const version of order) { out.push(`## ${version}`, '') + for (const { text, packages: pkgs } of (byVersionPreamble.get(version) ?? new Map()).values()) { + const scope = pkgs.size === packages.length ? 'all packages' : [...pkgs].sort().join(', ') + out.push(`_(${scope})_ ${text}`, '') + } const entries = [...byVersion.get(version).values()] - for (const kind of KIND_ORDER) { + const extraKinds = [...new Set([...entries.values()].map((e) => e.kind))].filter( + (k) => !KIND_ORDER.includes(k), + ) + for (const kind of [...KIND_ORDER, ...extraKinds]) { const forKind = entries.filter((e) => e.kind === kind) if (forKind.length === 0) continue - out.push(`### ${kind} Changes`, '') + out.push(`### ${KIND_ORDER.includes(kind) ? `${kind} Changes` : kind}`, '') for (const { text, packages: pkgs } of forKind) { const scope = pkgs.size === packages.length ? 'all packages' : [...pkgs].sort().join(', ') out.push(text.replace(/^- /, `- _(${scope})_ `), '') From ac3fed2bf8f8800a2c0ba9d337da8d8cb029e87e Mon Sep 17 00:00:00 2001 From: t Date: Thu, 24 Sep 2026 09:44:11 -0700 Subject: [PATCH 3/6] fix: narrow the changelog bookkeeping filter so it cannot eat real release notes Claude-Session: https://claude.ai/code/session_01Gnt93sVjs6ApXSzkDC2efU --- .github/workflows/ci.yml | 2 + package.json | 3 +- scripts/build-root-changelog.mjs | 57 ++++++++++++++++++--------- scripts/build-root-changelog.test.mjs | 34 ++++++++++++++++ 4 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 scripts/build-root-changelog.test.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0a81457..7d5cbbf6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,8 @@ jobs: run: pnpm lint:no-raw-color - name: Run iOS development tooling tests run: pnpm test:ios-dev + - name: Run repo script regression tests + run: pnpm test:scripts typecheck: name: Typecheck diff --git a/package.json b/package.json index 9212f595..6e45bc73 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "changeset": "changeset", "version-packages": "changeset version && node scripts/build-root-changelog.mjs", "release": "turbo build && pnpm exec changeset publish", - "build:root-changelog": "node scripts/build-root-changelog.mjs" + "build:root-changelog": "node scripts/build-root-changelog.mjs", + "test:scripts": "node --test scripts/*.test.mjs" }, "devDependencies": { "@changesets/cli": "2.29.7", diff --git a/scripts/build-root-changelog.mjs b/scripts/build-root-changelog.mjs index 7d28d362..3d3ae2dd 100644 --- a/scripts/build-root-changelog.mjs +++ b/scripts/build-root-changelog.mjs @@ -21,6 +21,15 @@ import { fileURLToPath } from 'node:url' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const PACKAGES = join(ROOT, 'packages') +// Read before the filters below, which need the group's package names: a bump line for a +// package outside the group is a note about a real dependency, not our own bookkeeping. +const fixedGroups = JSON.parse(readFileSync(join(ROOT, '.changeset', 'config.json'), 'utf8')).fixed +if (fixedGroups?.length !== 1) { + throw new Error( + `Expected exactly one fixed group in .changeset/config.json, found ${fixedGroups?.length ?? 0}.`, + ) +} + /** `## 2.12.1` ... up to the next `## ` */ function versionSections(markdown) { const out = [] @@ -76,8 +85,24 @@ function parseEntries(bodyLines) { .filter((g) => g.text !== null) } -/** `- @youversion/platform-react-native-expo-core@1.6.0`, at any indent. The group bumping itself. */ -const DEPENDENCY_BUMP = /^\s*-\s+@[^@\s]+@\d[\w.-]*\s*$/ +const escapeForRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + +/** + * ` - @youversion/platform-react-native-expo-core@1.6.0`, at any indent. Restricted to the + * fixed group's own packages: the same shape naming anything else is a real dependency the + * consumer is being told about, and dropping it loses information. + */ +const DEPENDENCY_BUMP = new RegExp( + `^\\s*-\\s+(?:${fixedGroups[0].map(escapeForRegExp).join('|')})@\\d[\\w.-]*\\s*$`, +) + +/** + * Changesets' own header for that block, always carrying the originating commit: + * `- Updated dependencies [80d3718]`. Matching the bare prefix instead would also swallow a + * hand-written note that happens to open the same way, such as + * `- Updated dependencies to address CVE-1234.` + */ +const UPDATED_DEPENDENCIES = /^- Updated dependencies \[[0-9a-f]+\]/ /** * Remove the fixed group's own version bookkeeping, returning null when an entry is nothing else. @@ -86,8 +111,8 @@ const DEPENDENCY_BUMP = /^\s*-\s+@[^@\s]+@\d[\w.-]*\s*$/ * an `Updated dependencies` block, and bare `- @pkg@version` lines, either standing alone as * their own entry or trailing a real note as continuation lines. */ -function stripBookkeeping(text) { - if (/^- Updated dependencies/.test(text)) return null +export function stripBookkeeping(text) { + if (UPDATED_DEPENDENCIES.test(text)) return null const kept = text.split('\n').filter((line) => !DEPENDENCY_BUMP.test(line)) const collapsed = kept .join('\n') @@ -110,12 +135,6 @@ function dedupeKey(text) { // a version, so the same entry appears in each of their changelogs. Reading the filesystem // instead would silently drop a package whose changelog went missing, and would silently fold // a future unrelated package into `(all packages)`. -const fixedGroups = JSON.parse(readFileSync(join(ROOT, '.changeset', 'config.json'), 'utf8')).fixed -if (fixedGroups?.length !== 1) { - throw new Error( - `Expected exactly one fixed group in .changeset/config.json, found ${fixedGroups?.length ?? 0}.`, - ) -} const expected = new Map( readdirSync(PACKAGES) .filter((d) => existsSync(join(PACKAGES, d, 'package.json'))) @@ -231,11 +250,13 @@ for (const version of order) { } } -writeFileSync( - join(ROOT, 'CHANGELOG.md'), - out - .join('\n') - .replace(/\n{3,}/g, '\n\n') - .trimEnd() + '\n', -) -console.log(`Wrote CHANGELOG.md — ${order.length} versions from ${packages.length} packages.`) +if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) { + writeFileSync( + join(ROOT, 'CHANGELOG.md'), + out + .join('\n') + .replace(/\n{3,}/g, '\n\n') + .trimEnd() + '\n', + ) + console.log(`Wrote CHANGELOG.md — ${order.length} versions from ${packages.length} packages.`) +} diff --git a/scripts/build-root-changelog.test.mjs b/scripts/build-root-changelog.test.mjs new file mode 100644 index 00000000..16a538c3 --- /dev/null +++ b/scripts/build-root-changelog.test.mjs @@ -0,0 +1,34 @@ +import assert from 'node:assert/strict' +import { test } from 'node:test' + +import { stripBookkeeping } from './build-root-changelog.mjs' + +const CORE = '@youversion/platform-react-native-expo-core' + +test('drops the fixed group version bookkeeping Changesets writes', () => { + assert.equal(stripBookkeeping('- Updated dependencies [80d3718]'), null) + assert.equal(stripBookkeeping(`- Updated dependencies [3dfe296]\n - ${CORE}@1.5.0`), null) + assert.equal(stripBookkeeping(`- ${CORE}@1.6.0`), null) +}) + +test('keeps a release note that only reads like bookkeeping', () => { + // The filter used to match the bare `- Updated dependencies` prefix, so this whole entry + // disappeared from the root changelog. + const note = '- Updated dependencies to address CVE-1234.' + assert.equal(stripBookkeeping(note), note) +}) + +test('keeps a bump for a package outside the fixed group', () => { + // Only the group's own packages share a version, so only their bumps are duplication. A + // third-party bump is something the consumer is being told about. + const note = '- Bumped the HTTP client.\n - @vendor/client@2.0.0' + assert.equal(stripBookkeeping(note), note) + assert.equal(stripBookkeeping('- @vendor/client@2.0.0'), '- @vendor/client@2.0.0') +}) + +test('strips a trailing group bump without eating the note above it', () => { + assert.equal( + stripBookkeeping(`- Added a native chapter picker.\n - ${CORE}@1.6.0`), + '- Added a native chapter picker.', + ) +}) From 33b1b4dbbe6a1dde4fb9a12f7959e5ff32d4f80c Mon Sep 17 00:00:00 2001 From: t Date: Thu, 24 Sep 2026 12:21:33 -0700 Subject: [PATCH 4/6] fix: keep a subheading that opens a changelog section before any bullet Claude-Session: https://claude.ai/code/session_01Gnt93sVjs6ApXSzkDC2efU --- CHANGELOG.md | 2 ++ scripts/build-root-changelog.mjs | 17 ++++++++++-- scripts/build-root-changelog.test.mjs | 38 ++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5516d00..7733d9c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -242,6 +242,8 @@ _(@youversion/platform-react-native-expo-ui)_ Initial release. Drop YouVersion B - _(@youversion/platform-react-native-expo-core)_ Cached user info is validated with a zod schema on read, so a corrupt or legacy cache entry can't surface wrong-typed fields +**Scripture display** + - _(@youversion/platform-react-native-expo-ui)_ `BibleTextView` — render a verse or verse range from a USFM reference - _(@youversion/platform-react-native-expo-ui)_ `BibleCard` — a verse with built-in reader controls diff --git a/scripts/build-root-changelog.mjs b/scripts/build-root-changelog.mjs index 3d3ae2dd..a777a26d 100644 --- a/scripts/build-root-changelog.mjs +++ b/scripts/build-root-changelog.mjs @@ -52,7 +52,7 @@ function versionSections(markdown) { * Split a version body into `{ kind, entries }`, where kind is Major/Minor/Patch and each * entry keeps its continuation lines (Changesets indents them by two spaces). */ -function parseEntries(bodyLines) { +export function parseEntries(bodyLines) { const groups = [] let kind = null let entry = null @@ -77,7 +77,20 @@ function parseEntries(bodyLines) { entry = [line] continue } - if (entry) entry.push(line) + if (entry) { + entry.push(line) + continue + } + // A subheading introducing the entries below it, with no entry open to + // attach to. Every other one in these changelogs follows a bullet, so it + // rides along as that entry's trailing lines and lands in the right place + // by accident. The first one under a `###` heading has nothing to ride, and + // was silently dropped. Emit it as its own group instead: the renderer only + // prefixes text starting with `- `, so it comes out verbatim. + if (line.trim() !== '') { + entry = [line] + push() + } } push() return groups diff --git a/scripts/build-root-changelog.test.mjs b/scripts/build-root-changelog.test.mjs index 16a538c3..ffd7cb03 100644 --- a/scripts/build-root-changelog.test.mjs +++ b/scripts/build-root-changelog.test.mjs @@ -1,7 +1,7 @@ import assert from 'node:assert/strict' import { test } from 'node:test' -import { stripBookkeeping } from './build-root-changelog.mjs' +import { parseEntries, stripBookkeeping } from './build-root-changelog.mjs' const CORE = '@youversion/platform-react-native-expo-core' @@ -32,3 +32,39 @@ test('strips a trailing group bump without eating the note above it', () => { '- Added a native chapter picker.', ) }) + +test('keeps a subheading that opens a section before any bullet', () => { + // Every other subheading in these changelogs follows a bullet and rides along + // as that entry's trailing lines. The first one under a `###` has nothing to + // ride, and used to be dropped outright. + const groups = parseEntries([ + '### Added', + '', + '**Scripture display**', + '', + '- `BibleTextView` renders a verse', + ]) + + assert.deepEqual( + groups.map((g) => g.text), + ['**Scripture display**', '- `BibleTextView` renders a verse'], + ) + assert.ok(groups.every((g) => g.kind === 'Added')) +}) + +test('a subheading after a bullet stays with the entry it trails', () => { + const groups = parseEntries([ + '### Added', + '', + '- first thing', + '', + '**Bible reader**', + '', + '- second thing', + ]) + + assert.deepEqual( + groups.map((g) => g.text), + ['- first thing\n\n**Bible reader**', '- second thing'], + ) +}) From 11a73cf19aea61fd76c84242c4a563affbaa3b8e Mon Sep 17 00:00:00 2001 From: t Date: Mon, 28 Sep 2026 12:13:24 -0700 Subject: [PATCH 5/6] fix: judge a changelog entry by what survives the bookkeeping filter Claude-Session: https://claude.ai/code/session_01Gnt93sVjs6ApXSzkDC2efU --- .github/workflows/ci.yml | 5 +++-- scripts/build-root-changelog.mjs | 26 +++++++++++++++++--------- scripts/build-root-changelog.test.mjs | 16 ++++++++++++++++ 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d5cbbf6..7a857110 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,8 +23,9 @@ jobs: run: pnpm lint:native-i18n - name: Run no-raw-color oxlint regression tests run: pnpm lint:no-raw-color - - name: Run iOS development tooling tests - run: pnpm test:ios-dev + # `test:scripts` runs every scripts/*.test.mjs, which includes the iOS tooling + # suite, so a separate step for it would run that suite twice. `pnpm test:ios-dev` + # stays in package.json as a focused command for local use. - name: Run repo script regression tests run: pnpm test:scripts diff --git a/scripts/build-root-changelog.mjs b/scripts/build-root-changelog.mjs index a777a26d..9c7c56cb 100644 --- a/scripts/build-root-changelog.mjs +++ b/scripts/build-root-changelog.mjs @@ -110,12 +110,12 @@ const DEPENDENCY_BUMP = new RegExp( ) /** - * Changesets' own header for that block, always carrying the originating commit: - * `- Updated dependencies [80d3718]`. Matching the bare prefix instead would also swallow a - * hand-written note that happens to open the same way, such as - * `- Updated dependencies to address CVE-1234.` + * Changesets' own header for that block, alone on its line and carrying the originating + * commit: `- Updated dependencies [80d3718]`. Anchored at both ends, because a prefix match + * also swallows a hand-written note that merely opens the same way, such as + * `- Updated dependencies [deadbee] to address CVE-1234.` */ -const UPDATED_DEPENDENCIES = /^- Updated dependencies \[[0-9a-f]+\]/ +const UPDATED_DEPENDENCIES = /^-\s+Updated dependencies \[[0-9a-f]+\]\s*$/ /** * Remove the fixed group's own version bookkeeping, returning null when an entry is nothing else. @@ -125,14 +125,22 @@ const UPDATED_DEPENDENCIES = /^- Updated dependencies \[[0-9a-f]+\]/ * their own entry or trailing a real note as continuation lines. */ export function stripBookkeeping(text) { - if (UPDATED_DEPENDENCIES.test(text)) return null - const kept = text.split('\n').filter((line) => !DEPENDENCY_BUMP.test(line)) + // Drop the bookkeeping lines and judge the entry by what survives, rather than discarding + // a whole entry because its first line looks like bookkeeping. An entry can open with the + // generated header and still carry something a consumer needs, such as a bump for a + // package outside the fixed group. + const kept = text + .split('\n') + .filter((line) => !UPDATED_DEPENDENCIES.test(line) && !DEPENDENCY_BUMP.test(line)) const collapsed = kept .join('\n') .replace(/\n{3,}/g, '\n\n') .trimEnd() - // Nothing but a bullet marker left, so the entry was only a version bump. - return /^-\s*$/.test(collapsed) || collapsed === '' ? null : collapsed + // Nothing but a bullet marker left, so the entry was only version bookkeeping. + if (/^-\s*$/.test(collapsed) || collapsed.trim() === '') return null + // Removing the header can leave a continuation line first. Promote it to a top-level + // bullet so the renderer can still tag it with its package scope. + return collapsed.replace(/^\s+- /, '- ') } /** diff --git a/scripts/build-root-changelog.test.mjs b/scripts/build-root-changelog.test.mjs index ffd7cb03..e4970c38 100644 --- a/scripts/build-root-changelog.test.mjs +++ b/scripts/build-root-changelog.test.mjs @@ -18,6 +18,22 @@ test('keeps a release note that only reads like bookkeeping', () => { assert.equal(stripBookkeeping(note), note) }) +test('keeps a note that opens with the generated header but says more', () => { + // The header regex used to match a prefix, so anything starting this way was discarded + // whole, including a hand-written note. + const note = '- Updated dependencies [deadbee] to address CVE-1234.' + assert.equal(stripBookkeeping(note), note) +}) + +test('keeps a third-party bump listed under the generated header', () => { + // The header is bookkeeping, the bump under it is not: that package is outside the fixed + // group, so its version is news. Dropping the entry wholesale lost it. + assert.equal( + stripBookkeeping(`- Updated dependencies [deadbee]\n - @vendor/client@2.0.0`), + '- @vendor/client@2.0.0', + ) +}) + test('keeps a bump for a package outside the fixed group', () => { // Only the group's own packages share a version, so only their bumps are duplication. A // third-party bump is something the consumer is being told about. From 008fed5206c63f833c956027809072d0e024c132 Mon Sep 17 00:00:00 2001 From: t Date: Tue, 6 Oct 2026 12:22:08 -0700 Subject: [PATCH 6/6] docs: regenerate the root changelog for v2.0.0 --- CHANGELOG.md | 66 +++++++++++++++++++++++++++++++- scripts/build-root-changelog.mjs | 4 +- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7733d9c5..34601046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,73 @@ All notable changes to the YouVersion Platform React Native (Expo) SDK. so they share a version number and release together. Each entry below notes which packages it affected. -Generated from the per-package changelogs by `scripts/build-root-changelog.mjs` — edit those, +Generated from the per-package changelogs by `scripts/build-root-changelog.mjs`. Edit those, or the changeset, rather than this file. +## 2.0.0 + +### Major Changes + +- _(all packages)_ 8bb6a02: Require Expo SDK 57 (`expo >=57.0.0 <58.0.0`, `react-native >=0.86.0`). Expo 56 is no longer supported. Consumers must upgrade Expo and align native peers (`react-native-reanimated >=4.4.0`, `react-native-worklets >=0.9.1`) before installing this release. + +### Minor Changes + +- _(all packages)_ 7e7fcec: VerseOfTheDay is now hybrid: native chrome (share, attribution, Card surface) wrapping the DOM BibleTextView for scripture (YPE-5440 / RNV2-2). Public props are unchanged. Light/dark resolve on native before they cross the bridge. Share uses the native Share API. Native share and the header reference honor the provider version filter lists, so a refused version cannot leak through Share. + +- _(@youversion/platform-react-native-expo-core)_ 8bb6a02: feat: wrap platform-core SearchClient (YPE-5746). Public `useSearch` and `biblePassageAnchorFromPassageId`. A verse hit `id` is a passage id. + +- _(all packages)_ dffc732: feat: draw a native Reader toolbar on iOS/Android instead of the in-WebView Web SDK toolbar (YPE-5712 / RNV2-9a). Avatar, chapter (with chevrons), version, and settings open the existing sheets. + +- _(all packages)_ 8bb6a02: BibleCard is now hybrid: native Card chrome (reference, version control, copyright, logo) wrapping the DOM BibleTextView for scripture (YPE-5830 / RNV2-3). Public props are unchanged. maxWidth defaults to 700 on the native Card and no longer crosses the bridge. + +- _(@youversion/platform-react-native-expo-ui)_ 8bb6a02: feat: add BibleReaderNavigation for chapter jumps (YPE-5745). Host can request a version/book/chapter before the Reader mounts. Scroll-to-verse and focus are not in this release. + +- _(@youversion/platform-react-native-expo-ui)_ 8bb6a02: feat: focus a verse from Reader Search + + Search taps and `focusReference` scroll to the verse through the Web SDK. A chapter `request` still opens the chapter without focusing. + +- _(@youversion/platform-react-native-expo-ui)_ 8bb6a02: feat: add native Reader Search (YPE-5748). Search sheet and a Search icon in the native toolbar. The header has the field and Cancel, the snippet sits above the title, and suggestions use the version language. A result tap loads the chapter; scroll-to-verse stays with YPE-5747. + +- _(@youversion/platform-react-native-expo-ui)_ 8bb6a02: Replace the version picker's Expo DOM content with a native React Native picker that keeps both the versions and language panels mounted on device (YPE-5834). + + `BibleVersionPickerSheet` no longer accepts a `dom` prop. The picker is native, so there is no Expo DOM surface to configure. This ships in the same major release as the Expo SDK 57 peer requirement. + +- _(@youversion/platform-react-native-expo-ui)_ 8bb6a02: Replace the chapter picker's Expo DOM content with a native React Native picker and export `BibleChapterPicker` for custom presentation (YPE-5836). + + `BibleChapterPickerSheet` no longer accepts a `dom` prop. The picker is native, so there is no Expo DOM surface to configure. This ships in the same major release as the Expo SDK 57 peer requirement. + +- _(@youversion/platform-react-native-expo-ui)_ 8bb6a02: feat: match native Reader chrome to Swift PR 268 chapter and version capsules (YPE-5953). Previous and next sit in the chapter capsule. The avatar is gone. Fonts and settings, sign-in, and sign-out are in the More menu. `onSettingsPress`, `onSignInPress`, and `onSignOutPress` are unchanged. Open `reader-toolbar-menu`, then `reader-toolbar-settings`, `reader-toolbar-sign-in`, or `reader-toolbar-sign-out`. `reader-toolbar-avatar` and `reader-toolbar-user` are removed. Search is a separate change. + +### Patch Changes + +- _(@youversion/platform-react-native-expo-ui)_ 8bb6a02: Search results scroll inside the sheet. A drag on the list no longer moves the sheet. The handle, the backdrop, and Cancel still close it. + +- _(@youversion/platform-react-native-expo-ui)_ 8bb6a02: Untitled Serif from the Fonts API registers on iOS and Android. The bundled Source Serif 4 fallback registers under its own names instead of the Untitled Serif names, because native Expo Font cannot replace a loaded face. When the Fonts API request or one of its font files fails, native serif text uses Source Serif 4, and a later request that succeeds after an `appKey` or `apiHost` change still registers Untitled Serif. + +- _(@youversion/platform-react-native-expo-ui)_ 8bb6a02: Search suggestions and trending wait for the Bible version language. A missing language no longer sends `language_ranges[]=*`, which the Search API rejects. + +- _(@youversion/platform-react-native-expo-ui)_ b583fc6: Sync localization from platform-localization (1fe6b5d): update 3 keys in en. + +- _(@youversion/platform-react-native-expo-ui)_ ea09740: Sync localization from platform-localization (21c52c1): update 7 keys in en. + +- _(@youversion/platform-react-native-expo-ui)_ 9b95630: Sync localization from platform-localization (413e2e7): update 1 keys in en. + +- _(@youversion/platform-react-native-expo-ui)_ a9ebfe4: Sync localization from platform-localization (b767ef1): update 10 keys in en, es. + +- _(@youversion/platform-react-native-expo-ui)_ 5586004: Sync localization from platform-localization (c99c472): update 1 keys in en. + +- _(@youversion/platform-react-native-expo-ui)_ 7b5bf5c: Sync localization from platform-localization (d8ad211): update 5 keys in en. + +- _(@youversion/platform-react-native-expo-ui)_ 6fd7a23: Sync localization from platform-localization (e4c7700): update 3 keys in en. + +- _(@youversion/platform-react-native-expo-ui)_ 4ad729d: Sync localization from platform-localization (ee03e27): update 49 keys in es. + +- _(@youversion/platform-react-native-expo-ui)_ a4184af: Sync localization from platform-localization (f7a8ff2): update 5 keys in en. + +- _(@youversion/platform-react-native-expo-ui)_ 8bb6a02: YouVersionAuthButton now composes the design-system Button `outline` look for press, radius, type, 1px border, and label color. Fill stays the forced scheme `background` so the Bible App logo stays readable in dark. Padding, logo gap, and logo size match the Swift sign-in button (20 / 12, 8px gap, 24px logo). The pill hugs its content. The host places it. The label can wrap to two lines. `outline`, `radius`, and `size` are no longer public props (YPE-5833 / RNV2-6). + +- _(@youversion/platform-react-native-expo-ui)_ 8bb6a02: Render Bible reader font settings as native controls inside the settings sheet (YPE-5835). + ## 1.6.0 ### Minor Changes diff --git a/scripts/build-root-changelog.mjs b/scripts/build-root-changelog.mjs index 9c7c56cb..f0eab1f0 100644 --- a/scripts/build-root-changelog.mjs +++ b/scripts/build-root-changelog.mjs @@ -245,7 +245,7 @@ const out = [ 'so they share a version number and release together. Each entry below notes which packages', 'it affected.', '', - 'Generated from the per-package changelogs by `scripts/build-root-changelog.mjs` — edit those,', + 'Generated from the per-package changelogs by `scripts/build-root-changelog.mjs`. Edit those,', 'or the changeset, rather than this file.', '', ] @@ -279,5 +279,5 @@ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process. .replace(/\n{3,}/g, '\n\n') .trimEnd() + '\n', ) - console.log(`Wrote CHANGELOG.md — ${order.length} versions from ${packages.length} packages.`) + console.log(`Wrote CHANGELOG.md: ${order.length} versions from ${packages.length} packages.`) }