YPE-642 - verse action popover — highlights, copy & share #269
Merged
Conversation
…642) Tapping verses in BibleReader selects them (underline) and opens a popover anchored to the last-selected verse with 5 highlight colors, Copy, and Share. Highlights paint a 35%-opacity fill, persist to localStorage per Bible version (shaped like the future highlight API), and can be removed per-color. Copy/Share mirror bible.com formatting; Share uses the Web Share API with a clipboard fallback. Footnote markers darken on highlighted verses to stay AA-legible. Salvages the popover component and tests from the closed PR #131. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…642) Keep the verse action bar reachable while scrolling. An IntersectionObserver watches the anchored verse against the reader's scroll container; when the verse leaves the viewport the bar docks to the edge it exited through (scroll down → top, scroll up → bottom) instead of following the verse off-screen. A single Radix popover swaps its anchor between the real verse element and a virtual element pinned to the container edge, preserving focus/escape/outside-click and avoiding remount flicker. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the popover closes, the parent clears the selection and anchor synchronously. Without freezing the last rendered state, the animating bar would jump to a fallback position and flash an empty layout before fading out.
Drop the unnecessary `typeof navigator` guards inside the Copy/Share click handlers (handlers run client-side only, matching verse-of-the-day's pattern), remove a no-op catch reassignment, and fix a stale dock-direction doc comment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: bcf2ff8 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
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 |
cameronapak
commented
Jun 23, 2026
…errides (YPE-642) Addresses PR #269 review feedback: - Reset highlightStore synchronously on version change so the previous version's highlights never paint over new text for one frame (Greptile P2). - Add optional onCopy/onShare props to BibleReader. When provided they receive the structured selection payload and suppress the default Web Share/clipboard flow, so React Native/Expo hosts can bridge natively (mirrors VerseOfTheDay). - Document the highlightedVerses type change in the changeset (Greptile P2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ction-popover-with-highlight-colors-copy-and-share
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
VIDEO OVERVIEW
YPE-642
Adds a verse action popover to
BibleReader: tap verse(s) to highlight, copy, or share.What's in it
Book Ch:vv VERSION. Non-contiguous picks join with...; ranges collapse (1-3). Share uses the Web Share API, clipboard fallback.bible_id+ verse USFM to mirror the futurehighlightAPI shape. No network this PR (server sync is a separate ticket).Behavior worth a look
Notes
--yv-muted-foregroundover a highlight for AA contrast.docs/adr/YPE-642-verse-action-popover.md.Testing
verse-action-popover+verse-sharesuites); typecheck + lint clean.🤖 Generated with Claude Code
Greptile Summary
This PR adds a full verse-action popover to
BibleReader: tap verse(s) to reveal a floating bar with five highlight colors (localStorage-persisted), Copy, and Share. It includes the sticky/docked bar when the anchored verse scrolls off-screen, a "frozen snapshot" fade-out animation, and a cleanverse-shareutility layer.VerseActionPopover(RadixPopoverAnchor virtualRef, IntersectionObserver docking),verse-share.ts(formatting helpers), two icon components; wired intoBibleTextViewwithselectedVerses/highlightedVerses/onVerseSelect.onCopy/onShareoverride props expose the structuredBibleReaderShareDatapayload so React Native / Expo hosts can forward selections over the native bridge.youversion-platform:highlights:<versionId>→{ "<USFM>": "<hex>" }in localStorage; version-switch clears the store synchronously during render to prevent stale highlights flashing over new text.Confidence Score: 5/5
Safe to merge — no functional regressions or data-integrity issues found in the new verse-action popover, highlight persistence, or copy/share flows.
The core feature implementation is sound: highlight state is correctly scoped per version, the synchronous store-clear during render prevents stale flashes, the frozen-snapshot animation pattern handles the anchor-loss edge case, and the copy/share formatting utility is well-tested. The only findings are a misleading test comment, a theoretical Concurrent Mode edge case on a ref mutation during render, and a silent bad-CSS path when localStorage contains a malformed hex. None affect correctness under normal usage.
packages/ui/src/components/verse-action-popover.test.tsx — AC5a comment overstates which colours are hidden; packages/ui/src/components/verse-action-popover.tsx — frozen-view ref mutation during render; packages/ui/src/components/verse.tsx — hexToRgba lacks a length guard.
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant User participant BibleTextView participant Content as BibleReader.Content participant VAP as VerseActionPopover participant LS as localStorage User->>BibleTextView: Tap verse N BibleTextView->>Content: onVerseSelect([N]) Content->>Content: handleVerseSelect([N]) Content->>Content: "querySelector(.yv-v[v=N]) → anchorElement" Content->>VAP: "open=true, anchorElement, selectedVerses=[N]" VAP->>VAP: IntersectionObserver observes anchorElement Note over VAP: Verse scrolls off screen VAP->>VAP: setDockEdge(top or bottom) VAP->>VAP: Switches to virtual docked anchor User->>VAP: Tap highlight color VAP->>Content: onHighlight(color) Content->>Content: handleHighlight(color) Content->>LS: localStorage.setItem(key, JSON) Content->>Content: closeAndClearSelection() Content->>VAP: "open=false (frozen snapshot animates out)" User->>VAP: Tap Copy VAP->>Content: onCopy() Content->>Content: buildSelectionShareData() Content->>BibleTextView: getCleanVerseText(container, verse) Content->>Content: buildVerseShareText(...) alt onCopy prop provided Content->>User: onCopy(BibleReaderShareData) else Content->>User: navigator.clipboard.writeText(text) end Content->>Content: closeAndClearSelection()%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant User participant BibleTextView participant Content as BibleReader.Content participant VAP as VerseActionPopover participant LS as localStorage User->>BibleTextView: Tap verse N BibleTextView->>Content: onVerseSelect([N]) Content->>Content: handleVerseSelect([N]) Content->>Content: "querySelector(.yv-v[v=N]) → anchorElement" Content->>VAP: "open=true, anchorElement, selectedVerses=[N]" VAP->>VAP: IntersectionObserver observes anchorElement Note over VAP: Verse scrolls off screen VAP->>VAP: setDockEdge(top or bottom) VAP->>VAP: Switches to virtual docked anchor User->>VAP: Tap highlight color VAP->>Content: onHighlight(color) Content->>Content: handleHighlight(color) Content->>LS: localStorage.setItem(key, JSON) Content->>Content: closeAndClearSelection() Content->>VAP: "open=false (frozen snapshot animates out)" User->>VAP: Tap Copy VAP->>Content: onCopy() Content->>Content: buildSelectionShareData() Content->>BibleTextView: getCleanVerseText(container, verse) Content->>Content: buildVerseShareText(...) alt onCopy prop provided Content->>User: onCopy(BibleReaderShareData) else Content->>User: navigator.clipboard.writeText(text) end Content->>Content: closeAndClearSelection()Comments Outside Diff (2)
packages/ui/src/components/verse-action-popover.tsx, line 1275-1282 (link)All five "apply" buttons receive the same label (
"Apply highlight") and all "clear" buttons receive the same label ("Clear highlight"). A screen reader user navigating the toolbar hears the same announcement for every color swatch and has no way to know which color they are about to apply or remove. This blocks the highlight-by-color feature for keyboard/AT users entirely.The i18n keys
applyHighlightAriaLabelandclearHighlightAriaLabelare also locked to a bare string in all three locale files, so there is no placeholder available for the color name. A fix requires adding a color-name map (e.g.{ fffe00: 'yellow', '5dff79': 'green', ... }) and updating the i18n keys to accept an interpolation variable (e.g."Apply {{color}} highlight"/"Clear {{color}} highlight").Prompt To Fix With AI
packages/ui/src/components/verse-action-popover.tsx, line 1240-1244 (link)prefers-reduced-motionguard on popover animationsThe popover uses
fade-in-0,zoom-in-95,slide-in-from-top-2, and related Tailwind animation utilities with a 180 ms custom duration. Users who prefer reduced motion (via the OS setting) will still see these transitions. The review guidelines call out respecting this media query for all animations.Adding
motion-safe:variants (e.g.yv:motion-safe:data-[state=open]:animate-in) or a global@media (prefers-reduced-motion: reduce)override that setsanimation-duration: 0son[data-yv-sdk]elements would resolve this.Prompt To Fix With AI
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Reviews (3): Last reviewed commit: "Merge branch 'main' into cp/YPE-642-reac..." | Re-trigger Greptile