Skip to content

feat(ui): highlight auth flow state machine (YPE-1034, PR 2/3)#285

Draft
cameronapak wants to merge 2 commits into
claude/ype-1034-pr1-highlights-apifrom
claude/ype-1034-pr2-highlight-auth-flow
Draft

feat(ui): highlight auth flow state machine (YPE-1034, PR 2/3)#285
cameronapak wants to merge 2 commits into
claude/ype-1034-pr1-highlights-apifrom
claude/ype-1034-pr2-highlight-auth-flow

Conversation

@cameronapak

@cameronapak cameronapak commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Description

PR 2 of the YPE-1034 stack (stacked on #283do not merge before it; retarget to main once PR 1 lands). Implements the highlight auth flow state machine: tapping a highlight color without a session or the highlights permission now routes through sign-in or a just-in-time data-exchange grant, with the user's intent preserved across the redirect.

Everything remains dark-launched behind the internal HIGHLIGHTS_LIVE flag (off).

What's in it

  • core: DataExchangeClient (POST /data-exchange/token → consent URL → callback parsing, mirroring platform-sdk-swift#179), granted_permissions parsed from the sign-in callback, optimistic permission cache on YouVersionPlatformConfiguration (cleared on sign-out, invalidated by any 401/403)
  • hooks: useHighlightAuthActions — one-fell-swoop sign-in requesting highlights, data-exchange redirect, permission reads/invalidation; safe without an auth provider
  • ui: pending highlight in sessionStorage (~10-min expiry, survives the redirect round-trip), state machine in the seam hook, permission confirm dialog (copy verbatim from Swift, en/fr/es), FIFO operation queue serializing apply/remove (closes the two concurrency windows documented in PR 1)

Failure routing

  • 401/403 → invalidate permission cache, keep pending, re-prompt
  • network/5xx → revert optimistic overlay, console.error (snackbar is YPE-3873), discard pending
  • decline/cancel → discard pending, keep verse selection

Known assumptions / deferred (documented in code + changeset)

  • The hosted /data-exchange page is assumed to redirect back with data_exchange_status + granted_permissions; handling is isolated in buildDataExchangeUrl/parseDataExchangeCallback — needs one live round-trip before flag flip
  • Sign-in return without the permission shows the confirm dialog instead of auto-redirecting to data-exchange (avoids an unattended redirect on load)
  • Deferred follow-ups: expired-token 401 vs permission-denied disambiguation, resume-write failure routing, no re-prompt on remove failures; sign-out pending-highlights warning is out of scope

Verification

Adversarially reviewed; the one blocker (declined consent return re-opening the dialog under real async session hydration) is fixed in f275bbf with tests rewritten to real provider timing. Full gate: 838 tests green (core 334, hooks 285, ui 219), typecheck + lint clean.

Related Issues

Relates to YPE-1034
Stacked on #283

🤖 Generated with Claude Code

cameronapak and others added 2 commits July 10, 2026 14:35
Turn a color tap without a session or the `highlights` permission into an
applied highlight across the two auth paths, behind the internal
HIGHLIGHTS_LIVE flag.

Core: DataExchangeClient (POST /data-exchange/token, Zod-validated), the
hosted-grant URL builder + callback parser/handler, and an optimistic
permission cache on YouVersionPlatformConfiguration seeded from
`granted_permissions` on the sign-in and data-exchange callbacks (server
401/403 invalidates it). SignInWithYouVersionResult gains `permissions`.

Hooks: useHighlightAuthActions exposes one-fell-swoop sign-in (requesting
`highlights`), the just-in-time data-exchange redirect, permission
reads/invalidation, and the data-exchange return handler.

UI: useBibleReaderHighlights runs the state machine — pending highlights
persist to sessionStorage (~10-min expiry) across the redirect round-trip and
apply on a granted return; a permission confirm dialog (copy matched to the
native SDK, en/fr/es) gates the grant. Write failures route by status
(401/403 re-prompts and keeps pending; 5xx/network reverts and discards).
Apply/remove writes are serialized through a FIFO queue with per-verse
ownership, closing the two concurrency windows PR1 documented. Copy/share-only
behavior with no auth provider is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on hydrates

The resume effect consumed the data-exchange return status into a local, but
bailed on `!isAuthenticated` first. The shipped YouVersionAuthProvider
hydrates userInfo asynchronously, so the first effect run after a redirect
return is always unauthenticated: the status was consumed and lost, and when
the session flipped the cancel/failure branch was skipped — the pending
highlight survived a decline and the just-declined dialog re-opened.

Discard the pending highlight at consume time for any non-granted return,
before the auth gate: it runs exactly once and needs no session (a decline
kills the intent regardless of who signs in).

Tests: the cancel-return test previously set signedIn=true synchronously — a
timing the real provider never produces — which hid the bug. It now mounts
signed out and flips the session after mount (failed against the old code,
passes now), plus a failure-status variant with the same timing and the
granted-return test aligned for realism.

Also document the three review-deferred follow-ups (expired-token 401
misrouting, resume-write failure handling, remove-failure re-prompt) at their
code sites and in the changeset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f275bbf

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

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

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant