Skip to content

Conversation

@bmanquen
Copy link
Collaborator

@bmanquen bmanquen commented Jan 16, 2026

  • filter out the recently used versions in the useFilterVersions hook to remove them from the main list so they don't show twice.
  • add top 3 most recently clicked on bible versions and store them in localStorage.
  • allow both lists to be filtered through the searchQuery.

Light mode:
image

Dark mode:
image

@changeset-bot
Copy link

changeset-bot bot commented Jan 16, 2026

🦋 Changeset detected

Latest commit: c0bf504

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-react-hooks Minor
@youversion/platform-react-ui Minor
@youversion/platform-core Minor
nextjs 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

- filter out the recently used versions in the useFilterVersions
hook to remove them from the main list so they don't show twice.
- add top 3 most recently clicked on bible versions and store
them in localStorage.
- allow both lists to be filtered through the searchQuery.
@bmanquen bmanquen force-pushed the bm/YPE-1030-add-recent-versions branch from 844ff89 to 8b0c2a4 Compare January 16, 2026 05:03
@bmanquen bmanquen requested a review from cameronapak January 16, 2026 19:07
@bmanquen bmanquen marked this pull request as ready for review January 16, 2026 19:07
@chatgpt-codex-connector
Copy link

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 16, 2026

Greptile Summary

Adds a "Recently Used Versions" feature to the Bible version picker that tracks the top 3 most recently clicked versions in localStorage and displays them in a separate section above the main list.

Key Changes:

  • Modified useFilteredVersions hook to accept and filter out recent versions from the main list to prevent duplicates
  • Added localStorage-based persistence with SSR-safe checks (typeof window === 'undefined')
  • Recent versions are filtered by search query alongside the main list
  • Selecting a version moves it to the top of recent versions, maintaining max 3 limit
  • Comprehensive test coverage for both hook logic and UI interactions

Implementation Quality:

  • Proper error handling with try-catch in getRecentVersions
  • Memoized filtering logic includes recent versions in dependency array
  • Search filtering works on both title and localized_abbreviation fields
  • Tests cover edge cases including max limit, search filtering, and selection order

Confidence Score: 4/5

  • This PR is safe to merge with minor considerations for localStorage edge cases
  • Score reflects solid implementation with comprehensive tests, proper SSR handling, and clean architecture. Minor deduction due to potential localStorage quota issues and missing filter for abbreviation field in recent versions search
  • Pay closer attention to packages/ui/src/components/bible-version-picker.tsx for localStorage handling

Important Files Changed

Filename Overview
packages/hooks/src/useFilteredVersions.ts Added recentVersions parameter to filter out recently used versions from main list, preventing duplicates
packages/ui/src/components/bible-version-picker.tsx Added localStorage-based recent versions tracking with search filtering, max 3 limit, and separate UI section
packages/ui/src/components/bible-version-picker.stories.tsx Added three integration tests covering recent versions selection, search filtering, and max limit behavior

Sequence Diagram

sequenceDiagram
    participant User
    participant VersionPicker as BibleVersionPicker
    participant LocalStorage
    participant Hook as useFilteredVersions
    participant State as React State

    User->>VersionPicker: Opens picker
    VersionPicker->>LocalStorage: getRecentVersions()
    LocalStorage-->>VersionPicker: Returns recent versions array
    VersionPicker->>State: Initialize recentVersions state
    VersionPicker->>Hook: Call useFilteredVersions(versions, searchQuery, language, recentVersions)
    Hook->>Hook: Filter by language
    Hook->>Hook: Filter by search term
    Hook->>Hook: Exclude recent versions from main list
    Hook-->>VersionPicker: Returns filtered versions
    VersionPicker->>User: Display "Recently Used Versions" section
    VersionPicker->>User: Display "All Versions" section (without recent versions)
    
    User->>VersionPicker: Selects a version
    VersionPicker->>VersionPicker: Call addRecentVersion()
    VersionPicker->>State: Update recentVersions state (move to top, limit to 3)
    VersionPicker->>LocalStorage: saveRecentVersions()
    LocalStorage-->>VersionPicker: Persisted
    VersionPicker->>Hook: Re-filter with updated recentVersions
    Hook-->>VersionPicker: Returns updated filtered list
    VersionPicker->>User: Close picker with new selection
    
    User->>VersionPicker: Opens picker again
    VersionPicker->>User: Shows selected version in "Recently Used Versions"
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@bmanquen bmanquen force-pushed the bm/YPE-1030-add-recent-versions branch from 931ddbf to 6b01ea5 Compare January 16, 2026 19:11
@cameronapak
Copy link
Collaborator

cameronapak commented Jan 16, 2026

By OpenCode

Here's a fun little code review that I had OpenCode do. ## Code Review: PR #106 - Recently Used Versions Feature

Overview

Adds localStorage-based "Recently Used Versions" feature (max 3) to Bible version picker with search filtering and exclusion from main list.


🔴 Critical Issues

1. Bug in search filter logic - bible-version-picker.tsx:318-320

The recent versions search filter uses && instead of ||, causing incorrect search behavior:

// CURRENT (INCORRECT)
v.localized_abbreviation.toLowerCase().includes(query) &&
  v.abbreviation.toLowerCase().includes(query)

This requires BOTH abbreviations to match, when it should match ANY abbreviation (consistent with useFilteredVersions).

// SUGGESTED FIX
v.localized_abbreviation.toLowerCase().includes(query) ||
  (v.abbreviation && v.abbreviation.toLowerCase().includes(query))

2. Inconsistent search field coverage

The search filter for recent versions and main list check different fields:

Recent versions search:

  • title
  • localized_abbreviation
  • abbreviation (only when both match due to && bug above)

Main list search (useFilteredVersions.ts:26-29):

v.title.toLowerCase().includes(query) ||
  v.localized_abbreviation.toLowerCase().includes(query) ||
  (v.abbreviation && v.abbreviation.toLowerCase().includes(query))

After fixing the && bug, both should search title OR localized_abbreviation OR abbreviation.


⚠️ Concerns

3. Large mock data expansion - packages/ui/src/test/mock-data/bibles.json

  • +720 lines adding full API responses for test versions
  • Only minimal fields needed for tests
  • Fields added: promotional_content, books arrays (66 books per version), info, publisher_url

Question: Are the full books arrays and promotional_content fields actually needed for these tests, or was this expansion incidental?

4. Duplicate localStorage key definition

The key 'youversion-platform:picker:recent-versions' is defined in two places:

  1. packages/ui/src/components/bible-version-picker.tsx:14
  2. packages/ui/src/components/bible-version-picker.stories.tsx:22

Recommendation: Extract to a shared constants file to ensure single source of truth.

5. State synchronization risk

In bible-version-picker.tsx:190-197:

const addRecentVersion = useCallback((version: RecentVersion) => {
  setRecentVersions((prev) => {
    const filtered = prev.filter((v) => v.id !== version.id);
    const updated = [version, ...filtered].slice(0, MAX_RECENT_VERSIONS);
    saveRecentVersions(updated);  // ← No error handling
    return updated;
  });
}, []);

If saveRecentVersions fails (e.g., quota exceeded, private browsing mode), the React state is updated but localStorage is not. This creates a state/localStorage divergence.

Question: Should we add error handling/fallback for storage failures?


✅ Strengths

  • Proper SSR safety: typeof window === 'undefined' checks before localStorage access
  • Good error handling: Try-catch in getRecentVersions with safe fallback to empty array
  • Comprehensive test coverage: Hook tests cover edge cases (undefined, empty array, exclusion with search), integration tests cover selection, search filtering, and max limit behavior
  • Correct memoization: recentVersions properly included in useMemo dependency arrays
  • Clean separation of concerns: localStorage logic isolated in utility functions, hook handles filtering logic

❓ Questions

  1. Is the large expansion to bibles.json necessary for these specific tests, or was it incidental to the feature work?

  2. Should localStorage storage operations have error handling/fallback notifications (e.g., if quota exceeded)?

  3. Should the localStorage key be extracted to a constants file to prevent duplication?


Sequence Diagram

sequenceDiagram
    participant User
    participant VersionPicker as BibleVersionPicker
    participant LocalStorage
    participant Hook as useFilteredVersions
    participant State as React State

    User->>VersionPicker: Opens picker
    VersionPicker->>LocalStorage: getRecentVersions()
    LocalStorage-->>VersionPicker: Returns recent versions array
    VersionPicker->>State: Initialize recentVersions state
    VersionPicker->>Hook: Call useFilteredVersions(versions, searchQuery, language, recentVersions)
    Hook->>Hook: Filter by language
    Hook->>Hook: Filter by search term
    Hook->>Hook: Exclude recent versions from main list
    Hook-->>VersionPicker: Returns filtered versions
    VersionPicker->>User: Display "Recently Used Versions" section
    VersionPicker->>User: Display "All Versions" section (without recent versions)
    
    User->>VersionPicker: Selects a version
    VersionPicker->>VersionPicker: Call addRecentVersion()
    VersionPicker->>State: Update recentVersions state (move to top, limit to 3)
    VersionPicker->>LocalStorage: saveRecentVersions()
    LocalStorage-->>VersionPicker: Persisted
    VersionPicker->>Hook: Re-filter with updated recentVersions
    Hook-->>VersionPicker: Returns updated filtered list
    VersionPicker->>User: Close picker with new selection
    
    User->>VersionPicker: Opens picker again
    VersionPicker->>User: Shows selected version in "Recently Used Versions"
Loading

**Posted by open code

Copy link
Collaborator

@cameronapak cameronapak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Brenden, this code mainly looks good. I had a few small nitpicks and one suggestion. Thank you for your hard work on this!

Comment on lines 16 to 19
function getStoredRecentVersions(): StoredRecentVersion[] {
return JSON.parse(localStorage.getItem(RECENT_VERSIONS_KEY) || '[]') as StoredRecentVersion[];
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: I know that this is weird, but sometimes when I see something like this, I really want to wrap it in a try-catch because I've totally messed with local storage and can see somebody else messing with local storage and then it breaking the app.

import { BookOpen } from 'lucide-react';
import { Button } from './ui/button';

const RECENT_VERSIONS_KEY = 'youversion-platform:picker:recent-versions';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Since this is testing the version picker component and we have two of these keys, one in this test file, one in the regular file, can we move that into one file?

{/* Versions View */}
<div
className={`yv:h-[66vh] yv:flex yv:flex-col yv:transition-all yv:duration-300 yv:rounded-2xl yv:origin-center ${
className={`yv:min-h-0 yv:flex yv:flex-col yv:transition-all yv:duration-300 yv:rounded-2xl yv:origin-center ${
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: I'm suggesting that we add back the max height so that it doesn't get too tall

Suggested change
className={`yv:min-h-0 yv:flex yv:flex-col yv:transition-all yv:duration-300 yv:rounded-2xl yv:origin-center ${
className={`yv:min-h-0 yv:max-h-[66svh] yv:flex yv:flex-col yv:transition-all yv:duration-300 yv:rounded-2xl yv:origin-center ${

@cameronapak
Copy link
Collaborator

@greptileai, do you find any validity to the details in this AI code review?

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at that AI code review, I can see several valid points mixed with some that are less critical. Let me break down what I think has merit:

Valid Concerns:

  1. Search performance - The reviewer correctly identifies that filtering recent versions on every render could be optimized. The current implementation recreates the filter array each time:

    const filteredRecentVersions = useMemo(() => {
      if (!searchQuery.trim()) return recentVersions;
      const query = searchQuery.trim().toLowerCase();
      return recentVersions.filter(/* ... */);
    }, [recentVersions, searchQuery]);

    This is actually fine though since it's memoized and recent versions are limited to 3 items.

  2. Error handling - The getRecentVersions() function has a basic try/catch, but it could be more robust about handling malformed JSON or migration scenarios.

  3. Type safety - The concern about runtime type validation is valid. If localStorage contains unexpected data, it could cause issues.

Less Critical Points:

  1. "Complex state management" - The recent versions logic is actually quite straightforward and well-contained within the component.

  2. "Performance impact" - With max 3 recent versions, the performance impact is negligible.

  3. "Accessibility concerns" - The component already has proper ARIA labels and semantic structure.

Specific Issues I'd Address:

The most valid concern is around localStorage data integrity. Here's what I'd suggest improving:

function getRecentVersions(): RecentVersion[] {
  if (typeof window === 'undefined') return [];
  try {
    const stored = localStorage.getItem(RECENT_VERSIONS_KEY);
    if (!stored) return [];
    
    const parsed = JSON.parse(stored);
    if (!Array.isArray(parsed)) return [];
    
    // Validate each item has required properties
    return parsed.filter((item): item is RecentVersion => 
      typeof item === 'object' &&
      item !== null &&
      typeof item.id === 'number' &&
      typeof item.title === 'string' &&
      typeof item.localized_abbreviation === 'string'
    ).slice(0, MAX_RECENT_VERSIONS);
  } catch {
    return [];
  }
}

Overall, the review raises some legitimate points about robustness, but most of the current implementation is solid. The main improvement would be better data validation when reading from localStorage.

@bmanquen bmanquen force-pushed the bm/YPE-1030-add-recent-versions branch from d1b5fac to 87e71fe Compare January 16, 2026 20:08
Copy link
Collaborator

@cameronapak cameronapak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me!

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.

3 participants