Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions .cursor/skills/verify-sdk-demo/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
---
name: verify-sdk-demo
description: Drive the YouVersion SDK Demo (Vite React web UI at examples/vite-react) to prove Bible Reader, Verse of the Day, Bible Card, sign-in chrome, and theme behavior. Use when verifying UI changes, demo regressions, or live API integration in the example app.
---

# Verify the YouVersion SDK Demo

This repo is an SDK monorepo. The surface a user actually touches is the Vite demo in `examples/vite-react` (page title **YouVersion SDK Demo**). It hosts `@youversion/platform-react-ui` against the live YouVersion Platform API.

Other surfaces (do not treat as the primary harness unless the change is isolated there):

- Storybook on port 6006 (`pnpm --filter @youversion/platform-react-ui storybook`) — mocked/composition journeys, not the demo.
- Package unit/RTL/Storybook `play` tests — not a substitute for driving the demo.
- Hosted demo at https://youversion.github.io/platform-sdk-react/ — production build, not this checkout.

There is no local API, Docker Compose, or database. Scripture comes from `api.youversion.com`.

## Launch

From the monorepo root, after `pnpm install` and `pnpm build` (`packages/ui` exports only `dist/`):

```bash
.cursor/skills/verify-sdk-demo/scripts/launch.sh
```

That starts:

```bash
pnpm --filter vite-react dev --host 127.0.0.1 --port 5177
```

Do **not** insert an extra `--` before `--host`. `pnpm --filter vite-react dev -- --host 127.0.0.1` becomes `vite -- --host 127.0.0.1`; Vite then ignores `--host` and may bind `::1` only, so `curl http://127.0.0.1:5177` fails. Root `pnpm dev:web` is stale (it still filters a removed `nextjs` package).

**Ready when** `curl -fsS http://127.0.0.1:5177/` returns HTML containing `YouVersion SDK Demo` and launch prints `ready pid=… origin=http://127.0.0.1:5177`. Instance metadata is `${VERIFY_DIR:-/tmp/verify-sdk-demo}/instance.json`.

**Env** (never commit secrets). `launch.sh` loads in this order and only exports into the Vite process:

1. Already-set `VITE_YVP_APP_KEY`
2. Monorepo-root `.env.local` / `.env` (AGENTS.md: use the main checkout, not a worktree-local file)
3. `examples/vite-react/.env.local` if the Vite-prefixed key is still empty
4. `YVP_APP_KEY` mapped to `VITE_YVP_APP_KEY` (Cloud injects this secret)

Without a non-empty app key, `YouVersionProvider` replaces the whole tree with the missing-app-key `role="alert"` panel. Bible features are not driveable.

`VITE_YVP_AUTH_REDIRECT_URL` defaults to the launch origin (`http://127.0.0.1:5177`). Navbar Sign in requests only `profile` and `email`. Highlights are granted later in the reader (tap a verse, tap a color).

Override isolation with `VERIFY_HOST`, `VERIFY_PORT`, `VERIFY_DIR`. Default port is **5177** so a human demo on 5173 is left alone.

## Doctor

```bash
.cursor/skills/verify-sdk-demo/scripts/doctor.sh
```

Read-only. Answers “is this instance worth driving?”

1. `${VERIFY_DIR}/instance.json` exists and the recorded pid is alive.
2. Something is listening on the instance origin, and that listener is our pid or a child of it. If the port belongs to someone else, **stop** — do not drive a shared session.
3. `GET /` is 200 and the shell title is `YouVersion SDK Demo`.
4. Playwright probe (`node …/drive.mjs doctor`): desktop nav **Bible Reader** is present, the missing-app-key alert is absent, and `[data-slot="yv-bible-renderer"]` plus at least one `.yv-v[v]` verse wrapper have loaded.

If step 4 fails with the missing-app-key copy, relaunch after setting `VITE_YVP_APP_KEY` / `YVP_APP_KEY`. If packages were edited, `pnpm build` (or `turbo build --force` when the cache looks stale) and relaunch.

## Drive

Harness: Playwright from `@youversion/platform-react-ui` (`playwright` 1.56), launching system Chrome (`/usr/local/bin/google-chrome` or `VERIFY_CHROME`). Default viewport **1280×800** so the desktop navbar is visible (`hidden md:flex` below `md`).

```bash
node .cursor/skills/verify-sdk-demo/scripts/drive.mjs bible-reader
node .cursor/skills/verify-sdk-demo/scripts/drive.mjs verse-of-the-day
node .cursor/skills/verify-sdk-demo/scripts/drive.mjs bible-card
node .cursor/skills/verify-sdk-demo/scripts/drive.mjs theme
```

The demo is a client-side page switcher (`App` state: `bible-reader` | `votd` | `bible-card`), not a router. There are no URL paths. Desktop nav buttons:

| Visible name | Page |
| --- | --- |
| Bible Reader | default |
| Verse of the Day | `votd` |
| Bible Card | `bible-card` |

Mobile: `getByRole('button', { name: 'Toggle menu' })` then the same labels.

Stable SDK handles (English locale; prefer roles over copy blobs when asserting):

| Control | Handle |
| --- | --- |
| Scripture body | `[data-slot="yv-bible-renderer"]` |
| A verse | `.yv-v[v="1"]` (attribute `v` is the verse number) |
| Selected verse | `.yv-v-selected` |
| Previous / next chapter | `getByRole('button', { name: /previous chapter/i })` / `/next chapter/i` |
| Book + chapter picker | `getByRole('button', { name: /change bible book and chapter/i })` |
| Version picker | `getByRole('button', { name: /change bible version/i })` |
| Reader settings | `getByRole('button', { name: /settings/i })` |
| Font size | `getByTestId('increase-font-size')` / `decrease-font-size` |
| Line spacing | `getByTestId('line-spacing')` |
| Verse actions | `getByRole('dialog', { name: /verse actions/i })` |
| Highlight colors | `getByRole('group', { name: /highlight colors/i })` |
| Passage loading | `getByRole('status', { name: /loading passage/i })` |
| Sign in (navbar, `size="short"`) | `getByRole('button', { name: /^sign in$/i })` |
| Sign out (navbar) | `getByRole('button', { name: /^sign out$/i })` |
| Theme | `getByRole('button', { name: 'Toggle theme' })` then menuitems Light / Dark / System |
| Missing app key | `getByRole('alert')` (replaces the whole app) |
| SDK scope | `[data-yv-sdk]` |

Reader defaults in this demo: book `JHN`, chapter `1`, version id `3034` (license-free default). VOTD and Bible Card also use `3034`. Bible Card reference is `JHN.3.16`.

Feature recipes: `.cursor/skills/verify-sdk-demo/features/`. A proof that only hits one entry point is incomplete when the map lists others for that change.

Do not complete YouVersion OAuth in an unattended run unless a real account and a registered redirect for this origin exist. Navbar Sign in starts PKCE and leaves the demo. Highlights are per Bible version and need the highlights permission (reader color tap, not the navbar button).

## Evidence

Proof artifacts go to **`/tmp/verify-sdk-demo/evidence/`** (`VERIFY_EVIDENCE_DIR` to override). Cleanup must not delete this directory.

Each drive writes a timestamped folder:

- `01-*.png` / `02-*.png` — state **before** the action and **after**
- `result.json` — origin, feature, `ok`, and the observable that changed (picker label, heading, `localStorage`)

Standards:

- Exercise the real demo (Vite + live API), not Storybook, not `YouVersionContext.hookOverrides`, not test-only endpoints.
- Capture the action and the resulting state, not only the final screen.
- Side effects to observe: `localStorage` keys `yv-sdk-demo-theme`, `youversion-platform:reader:font-size`, `youversion-platform:reader:font-family`; verse class `yv-v-selected`; network calls to `api.youversion.com` when diagnosing load failures.
- Mocks only where the production boundary already isolates the system (Storybook MSW). The demo has no mock mode.

## Cleanup

```bash
.cursor/skills/verify-sdk-demo/scripts/cleanup.sh
```

Kills **only** the pid recorded in `instance.json` (the `pnpm --filter vite-react dev` process this launch started). Never `pkill vite` / `pkill node`. Removes `instance.json`. Leaves `/tmp/verify-sdk-demo/evidence/` and the last `vite.log` (overwritten on the next launch).

## Isolate

Two Vite processes can run side by side on different ports. Default verification port is 5177. A developer demo on 5173 is a **different origin** (theme and reader `localStorage` are origin-scoped) but must not be driven or killed.

- If `instance.json` already points at a live pid, `launch.sh` refuses a second start in the same `VERIFY_DIR`.
- If port 5177 is owned by some other process, launch refuses.
- For a second instance: `VERIFY_DIR=/tmp/verify-sdk-demo-b VERIFY_PORT=5178 …/launch.sh`.
- Never attach Playwright to an origin you did not start.

## Helpers

All invocations are from the monorepo root. Shell scripts are executable.

| Script | Command |
| --- | --- |
| Launch | `.cursor/skills/verify-sdk-demo/scripts/launch.sh` |
| Doctor | `.cursor/skills/verify-sdk-demo/scripts/doctor.sh` |
| Drive | `node .cursor/skills/verify-sdk-demo/scripts/drive.mjs <feature>` |
| Cleanup | `.cursor/skills/verify-sdk-demo/scripts/cleanup.sh` |

`scripts/lib.sh` is sourced by the shell helpers (paths, env load, port pid). `drive.mjs` resolves `playwright` from `packages/ui`. Chrome needs `--no-sandbox` in this Cloud image; the script passes that.

## Feature map

Index: `.cursor/skills/verify-sdk-demo/features/README.md`.
13 changes: 13 additions & 0 deletions .cursor/skills/verify-sdk-demo/features/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# SDK Demo feature map

User-facing surfaces in `examples/vite-react`. Drive with `node .cursor/skills/verify-sdk-demo/scripts/drive.mjs <feature>`.

| Feature | Drive id | How a user gets there |
| --- | --- | --- |
| [Bible Reader](bible-reader.md) | `bible-reader` | Default page; nav **Bible Reader** |
| [Verse of the Day](verse-of-the-day.md) | `verse-of-the-day` | Nav **Verse of the Day** |
| [Bible Card](bible-card.md) | `bible-card` | Nav **Bible Card** |
| [Sign in](sign-in.md) | *(manual / account)* | Navbar **Sign in**; or tap a verse then a highlight color |
| [Theme](theme.md) | `theme` | Navbar **Toggle theme** |

There is no client router. `App` swaps `bible-reader` \| `votd` \| `bible-card` in memory. A proof that only loads the default reader is incomplete when the change also touches VOTD, the card, auth, or theme.
37 changes: 37 additions & 0 deletions .cursor/skills/verify-sdk-demo/features/bible-card.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Bible Card

Single embeddable card for **John 3:16** (`JHN.3.16`) in version `3034`, with the version picker enabled.

## Sub-features

- Passage reference heading (`h2`, uppercase tracking)
- Version picker trigger (**Change Bible version**)
- Scripture body in `[data-slot="yv-bible-renderer"]`
- Footer copyright + Bible App lockup
- Footnotes when the passage has them (`[data-verse-footnote] button`)

## How to get to it (user POV)

1. Open the demo.
2. Click **Bible Card** in the header (or the hamburger menu).

## Driving it with Playwright

```bash
node .cursor/skills/verify-sdk-demo/scripts/drive.mjs bible-card
```

1. Screenshot the current page (before).
2. Click **Bible Card**.
3. Wait for `section[data-yv-sdk]` with an `h2`, the version trigger, and the renderer.
4. Screenshot (`02-bible-card.png`).

To prove the picker (not in the helper): click **Change Bible version**, choose another abbreviation, wait for the `h2` to include the new abbreviation and the body to refresh (loading spinner on the header during refetch).

**End state that proves it:** heading is a passage reference (John 3:16 / localized equivalent plus abbreviation), renderer has verse text, version button is enabled.

## Gotchas

- The demo hard-codes `reference="JHN.3.16"` and `showVersionPicker`. A 404/unavailable passage in the selected version keeps the picker so the user can switch — that error heading is `Error` in the `h2` slot, not a crash.
- Card verses do not use the reader toolbar. Do not look for next/previous chapter here.
- Max width is the SDK default (700px), centered in the demo page padding.
51 changes: 51 additions & 0 deletions .cursor/skills/verify-sdk-demo/features/bible-reader.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Bible Reader

Full-height reader: John 1 in version `3034` by default, with a bottom toolbar for chapter, version, and settings. This is the demo landing page.

## Sub-features

- Chapter next / previous (`JHN.1` → `JHN.2` on first Next)
- Book + chapter picker (popover heading **Books**, search placeholder **Search**, chapter grid; intro cells use `data-testid="intro-chapter-button"`)
- Version picker (abbreviation on the trigger; language trigger aria **Select language**)
- Reader settings: font size (`increase-font-size` / `decrease-font-size`), font family (Inter / Untitled Serif / Source Serif), line spacing (`line-spacing`)
- Verse tap selects `.yv-v[v]` and opens **Verse actions** (copy, share, highlight colors when live)
- Highlight apply / clear (self-contained; needs a signed-in user with the **highlights** permission — not the navbar Sign in scopes)

## How to get to it (user POV)

1. Open the demo origin. The first screen is already the reader.
2. Or click **Bible Reader** in the desktop header (or hamburger **Toggle menu** on narrow viewports).

## Driving it with Playwright

```bash
node .cursor/skills/verify-sdk-demo/scripts/drive.mjs bible-reader
```

Recipe the helper runs:

1. Confirm desktop nav **Bible Reader**, no missing-app-key alert.
2. Wait for `[data-slot="yv-bible-renderer"]` and `.yv-v[v]`.
3. Screenshot (`01-john-1.png`).
4. Read the **Change Bible book and chapter** button label.
5. Click **Next chapter**.
6. Wait out `role="status"` **Loading passage** if it appears; wait until the chapter-picker label changes.
7. Screenshot (`02-after-next-chapter.png`).

Further probes (not in the helper; use the same page):

- `getByRole('button', { name: /change bible version/i })` then pick another abbreviation; renderer text/copyright should follow.
- `getByRole('button', { name: /settings/i })` → **Reader Settings**; click `getByTestId('increase-font-size')` and read `localStorage['youversion-platform:reader:font-size']`.
- Click `.yv-v[v="1"]`; expect `.yv-v-selected` and `getByRole('dialog', { name: /verse actions/i })`.

**End state that proves it:** chapter-picker label is no longer the John 1 label; renderer still has verse wrappers; loading status is gone.

## Gotchas

- Missing or empty `VITE_YVP_APP_KEY` replaces the entire app (no navbar, no reader).
- Desktop nav is `hidden` below the `md` breakpoint — keep the 1280×800 viewport or open the hamburger.
- Next on John 1 is John 2, not a new book. Previous on John 1 goes to the prior book’s last chapter and may disable at the start of the canon.
- `HIGHLIGHTS_LIVE` is on in this package. Color taps while signed out open the sign-in dialog (`Yes Please` / `No Thanks`), not a silent write. Navbar Sign in does **not** request the highlights permission.
- Highlights are per Bible version. Changing version hides another version’s marks.
- Toolbar version button shows **Loading Bible version** while metadata is in flight — wait for **Change Bible version** before asserting the abbreviation.
- Do not attach to a human’s `localhost:5173` session; reader `localStorage` and auth tokens are origin-scoped.
33 changes: 33 additions & 0 deletions .cursor/skills/verify-sdk-demo/features/sign-in.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Sign in

YouVersion account session for the demo. Two different entry points request different grants.

## Sub-features

- Navbar **Sign in** (`YouVersionAuthButton` `size="short"`, scopes `profile` and `email` only)
- Navbar **Sign out** when `auth.isAuthenticated`
- Reader **highlight auth flow**: signed-out color tap → sign-in dialog (**INTRODUCING**, **Yes Please**, **No Thanks**); signed-in without highlights → data-exchange permission dialog
- Toolbar user menu (`data-testid="user-menu-trigger"`) inside `BibleReader` when auth is enabled on the provider

## How to get to it (user POV)

- **Profile/email only:** click **Sign in** in the header. Completing OAuth returns to `VITE_YVP_AUTH_REDIRECT_URL` (launch defaults this to the verification origin).
- **Highlights:** on the reader, tap a verse, tap a color. That is the grant path AGENTS.md describes. Navbar Sign in does not request `highlights`.

## Driving it with Playwright

There is no unattended `drive.mjs` command that finishes OAuth. Prove chrome only:

1. Unsigned: `getByRole('button', { name: /^sign in$/i })` is visible next to **Toggle theme**.
2. Click it only if you will complete the YouVersion login yourself. The tab leaves the demo (PKCE).
3. After a real return: header shows the user’s name when present and **Sign out**.
4. For highlights: select `.yv-v[v="1"]`, open **Verse actions**, click a color in **Highlight colors**. Signed out → dialog with **Yes Please**. Confirming starts the same redirect with `requested_permissions` including highlights.

**End state that proves navbar chrome:** Sign in is present and enabled while signed out. **End state that proves a session:** Sign out is present and scripture still loads. **End state that proves highlight consent:** after return, a color tap paints `.yv-v` with a background and does not re-open the dialog.

## Gotchas

- Redirect URL must be registered for the app key (docs mention `http://localhost:5173` for the human demo). Verification uses `http://127.0.0.1:5177` by default — OAuth will fail if that origin is not registered.
- Do not drive a shared origin that already has someone else’s session cookies.
- Pending highlight intent lives in `sessionStorage` (~10 min) across the redirect. An abandoned flow must not apply later.
- Highlights are account data, per Bible version, never local-only.
36 changes: 36 additions & 0 deletions .cursor/skills/verify-sdk-demo/features/theme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Theme

Demo chrome dark/light/system toggle. It drives `ThemeProvider` (`storageKey="yv-sdk-demo-theme"`) and is passed into `YouVersionProvider` as `theme`, so SDK surfaces (`data-yv-theme`) follow the page.

## Sub-features

- Toggle control (sun/moon icons, accessible name **Toggle theme**)
- Menu: **Light**, **Dark**, **System**
- `document.documentElement` class `dark` when dark
- Persistence in `localStorage['yv-sdk-demo-theme']`

## How to get to it (user POV)

The control is always in the header, right of Sign in / Sign out, on every page.

## Driving it with Playwright

```bash
node .cursor/skills/verify-sdk-demo/scripts/drive.mjs theme
```

1. Screenshot current theme.
2. Click **Toggle theme** → **Dark**.
3. Wait for `document.documentElement.classList.contains('dark')`.
4. Assert `localStorage.getItem('yv-sdk-demo-theme') === 'dark'`.
5. Screenshot (`02-dark.png`).

Also check a page that shares the provider: after Dark, click **Bible Card** or **Verse of the Day** and confirm `[data-yv-theme="dark"]` on an SDK `section`.

**End state that proves it:** `html` has class `dark`, storage is `dark`, and at least one `[data-yv-sdk][data-yv-theme="dark"]` node is present.

## Gotchas

- Origin-scoped storage. A run on port 5177 does not see a human’s 5173 theme.
- System follows `prefers-color-scheme`. In headless Chrome that is usually light unless you emulate `colorScheme: 'dark'`.
- Missing-app-key panel still honors `theme` on `YouVersionProvider`, but the demo navbar (including this toggle) is **not** rendered in that state.
Loading
Loading