Skip to content

fix(catalog): stable pagination + variant grouping#2572

Merged
mikib0 merged 9 commits into
developmentfrom
feat/catalog-pagination-and-variant-grouping
Jun 8, 2026
Merged

fix(catalog): stable pagination + variant grouping#2572
mikib0 merged 9 commits into
developmentfrom
feat/catalog-pagination-and-variant-grouping

Conversation

@mikib0

@mikib0 mikib0 commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Pagination duplication bug: ORDER BY created_at DESC with no secondary sort key produced non-deterministic ordering for ETL-imported items (all sharing identical timestamps). Added desc(id) tiebreaker to every sort branch in getCatalogItems — measured 21 duplicates per 10 pages before, 0 after.
  • Variant grouping in list: catalog items are client-side grouped by name + brand so size/color variants (e.g. three rows of "Fineline Stretch Full-Zip Pant - Men's") show as one card. Tapping sets a Jotai atom with all variant items and navigates to the representative item's detail screen.
  • Variants section in detail screen: shown directly below the action buttons when the user arrived from a grouped card; lists each other variant's size/color/price/weight/availability + "Visit Site ↗" link. No picker UI.
  • List screen polish: removed raw-variant total count text, reduced header gap, categories filter is now full-bleed scrollable with contentPaddingX prop.
  • CTA polish: "View on Retailer Site" → "Visit Site" + open-in-new icon on both the main button and each variant row.

Test plan

  • Scroll through catalog list across multiple pages — confirm no repeated cards
  • Verify items with multiple variants show a single card (e.g. search "Fineline Stretch Full-Zip Pant")
  • Tap a grouped card → detail screen shows "Variants" section below action buttons with all other sizes listed
  • Tap "Visit Site ↗" in variants section and on main button — confirms external browser opens
  • Tap a non-grouped item — detail screen shows no variants section
  • Category filter chips scroll horizontally to screen edges
  • Sort by fields other than createdAt (price, ratingValue) — confirm no cross-page duplicates

Summary by CodeRabbit

Release Notes

  • New Features

    • Added variants section to catalog item detail screen, displaying alternate sizes, colors, and pricing for the same product group.
    • Catalog items now grouped by product name and brand for improved browsing experience.
  • Bug Fixes

    • Fixed non-deterministic ordering in catalog search results to ensure consistent pagination.
  • Improvements

    • Updated UI text and styling for better clarity and consistency.

mikib0 added 6 commits June 8, 2026 12:20
…ation

ETL-imported items share identical created_at timestamps (PostgreSQL NOW()
is transaction-scoped). ORDER BY created_at DESC with no secondary key
produces non-deterministic ordering within a tie group, causing the same
items to appear on multiple LIMIT/OFFSET pages and others to be skipped.

Measured: 10 pages × 20 items → 21 duplicates (10.5%) before; 0 after.
The default sort already had desc(id) as tiebreaker; add it to the
category, usage, and generic field branches for the same guarantee.
…creen

ETL-imported gear items are stored as one row per size/color variant with
a unique SKU. The catalog list was showing "Fineline Stretch Full-Zip Pant
- Men's" three times (sizes L, L/Short, M/Short) as separate cards.

Group the paginated items by name+brand before rendering:
- One CatalogItemCard per group (representative = first variant)
- "N options" badge on the card when the group has >1 variant
- Tapping the card stores all variant items in catalogGroupVariantsAtom
  and navigates to the representative item's detail screen

Detail screen reads the atom and shows an "Available Variants" section
listing each other variant's size, color, price, weight, availability,
and a retailer link. No picker or selection UI — just a plain list.

Search results (vector search) still show individual items unchanged.
…-bleed categories

- Fix "3 {{count}} options" badge: i18n key is now plain "options" so
  the manually-prepended count renders as "3 options" correctly
- Remove "197,717 items / Showing X of Y" from the catalog list header —
  that count reflects raw variants, not grouped products
- Reduce top gap: FlatList contentContainerStyle loses top/horizontal
  padding; horizontal padding moves to a px-4 wrapper in renderItem so
  the categories row can be full-bleed
- CategoriesFilter gains contentPaddingX prop: ScrollView uses it as
  paddingHorizontal in contentContainerStyle, letting chips scroll to the
  actual screen edge while starting at the same 16 px indent as other
  content
- Move variants section to directly below the action buttons in the
  detail screen; shorten title from "Available Variants" → "Variants"
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@mikib0, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 41 minutes and 4 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0c65eafe-bf84-41b7-ad7e-23f92df735a1

📥 Commits

Reviewing files that changed from the base of the PR and between 8a46c67 and 1af579a.

📒 Files selected for processing (3)
  • apps/expo/features/catalog/components/SimilarItems.tsx
  • apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx
  • apps/expo/lib/i18n/locales/en.json

Walkthrough

Catalog items are now grouped by normalized name and brand. The list screen renders grouped items as representative cards, stores the full variant list in a Jotai atom before navigation, and the detail screen retrieves and displays those variants without additional API calls.

Changes

Catalog Item Variants Grouping and Detail Display

Layer / File(s) Summary
Variant atom and grouping utility
apps/expo/atoms/catalogGroupAtom.ts, apps/expo/features/catalog/lib/groupCatalogItems.ts
catalogGroupVariantsAtom provides pre-navigation variant storage. groupCatalogItems normalizes item name and brand into a composite key and aggregates matching items into groups with a representative and variants array.
Detail screen variant rendering
apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx
Reads catalogGroupVariantsAtom and renders a new VariantRow component for each alternative variant, showing size/color label, price, weight, availability status, and optional retailer link. Variants section appears before specs when multiple variants exist for the group.
List screen grouped rendering and navigation
apps/expo/features/catalog/screens/CatalogItemsScreen.tsx, apps/expo/components/CategoriesFilter.tsx
List derives groupedItems from paginated data, renders group representatives in padded containers, and sets catalogGroupVariantsAtom with group variants on selection before navigating to detail. CategoriesFilter now accepts contentPaddingX prop to control horizontal padding.
Translations and pagination stabilization
apps/expo/lib/i18n/locales/en.json, packages/api/src/services/catalogService.ts
English translation keys added for variant UI (variantsSection, variantSize, variantColor, etc.); existing strings updated to use literal unicode characters instead of escapes. Backend pagination adds deterministic desc(catalogItems.id) tiebreaker to orderBy for stable sort results across pages.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PackRat-AI/PackRat#2274: Updates CatalogItemDetailScreen with a test selector on the same detail screen that receives variants grouping UI changes in this PR.

Suggested labels

mobile, api

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: pagination stability via deterministic ordering and client-side grouping of catalog variants.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/catalog-pagination-and-variant-grouping

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for packages/analytics (./packages/analytics)

Status Category Percentage Covered / Total
🔵 Lines 93.68% (🎯 80%) 697 / 744
🔵 Statements 93.68% (🎯 80%) 697 / 744
🔵 Functions 97.87% (🎯 85%) 46 / 47
🔵 Branches 85.8% (🎯 80%) 133 / 155
File CoverageNo changed files found.
Generated in workflow #196 for commit 1af579a by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for packages/overpass (./packages/overpass)

Status Category Percentage Covered / Total
🔵 Lines 100% (🎯 80%) 155 / 155
🔵 Statements 100% (🎯 80%) 155 / 155
🔵 Functions 100% (🎯 80%) 13 / 13
🔵 Branches 95.65% (🎯 70%) 44 / 46
File CoverageNo changed files found.
Generated in workflow #196 for commit 1af579a by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for packages/mcp (./packages/mcp)

Status Category Percentage Covered / Total
🔵 Lines 98.87% (🎯 95%) 176 / 178
🔵 Statements 98.87% (🎯 95%) 176 / 178
🔵 Functions 100% (🎯 95%) 13 / 13
🔵 Branches 98.38% (🎯 90%) 61 / 62
File CoverageNo changed files found.
Generated in workflow #196 for commit 1af579a by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for packages/units (./packages/units)

Status Category Percentage Covered / Total
🔵 Lines 100% (🎯 100%) 35 / 35
🔵 Statements 100% (🎯 100%) 35 / 35
🔵 Functions 100% (🎯 100%) 6 / 6
🔵 Branches 100% (🎯 100%) 11 / 11
File CoverageNo changed files found.
Generated in workflow #196 for commit 1af579a by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for packages/api (./packages/api)

Status Category Percentage Covered / Total
🔵 Lines 98.95% (🎯 95%) 1322 / 1336
🔵 Statements 98.95% (🎯 95%) 1322 / 1336
🔵 Functions 100% (🎯 97%) 74 / 74
🔵 Branches 95.62% (🎯 92%) 481 / 503
File CoverageNo changed files found.
Generated in workflow #196 for commit 1af579a by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for apps/expo (./apps/expo)

Status Category Percentage Covered / Total
🔵 Lines 97.52% (🎯 95%) 590 / 605
🔵 Statements 97.52% (🎯 95%) 590 / 605
🔵 Functions 100% (🎯 97%) 51 / 51
🔵 Branches 95.3% (🎯 92%) 203 / 213
File CoverageNo changed files found.
Generated in workflow #196 for commit 1af579a by the Vitest Coverage Report Action

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/expo/atoms/catalogGroupAtom.ts`:
- Around line 1-7: Move the catalogGroupVariantsAtom export out of this shared
atoms file into the catalog feature module’s state/atoms file so
catalog-specific state lives with the feature; create or add to the catalog
feature’s atoms/state file a exported atom named catalogGroupVariantsAtom (same
type atom<CatalogItem[]>() and initial value) and update all imports across the
app to point to the new location, then remove the original export from the
current file to avoid duplicate exports.

In `@apps/expo/components/CategoriesFilter.tsx`:
- Around line 62-67: The ScrollView's contentContainerStyle currently treats
contentPaddingX as falsy (so 0 is ignored); update the conditional in the
component (where ScrollView is rendered) to check for undefined (or the
property's type) instead of a truthy check—e.g., use contentPaddingX !==
undefined or typeof contentPaddingX === 'number' to return { paddingHorizontal:
contentPaddingX } when 0 is intended, otherwise undefined; this change involves
the ScrollView rendering block referencing contentPaddingX in
CategoriesFilter.tsx.

In `@apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx`:
- Around line 65-73: Remove the redundant type assertion on variant.productUrl
inside the Pressable onPress handler: update the onPress={() =>
Linking.openURL(variant.productUrl as string)} usage to call Linking.openURL
with variant.productUrl directly (no "as string" cast) in
CatalogItemDetailScreen so the Pressable and its onPress handler use the
already-typed variant.productUrl value.
- Line 287: In CatalogItemDetailScreen locate the View with className
"rounded-lg p-3 py-0 gap-4" and simplify the redundant padding by replacing "p-3
py-0" with "px-3" (so the class becomes "rounded-lg px-3 gap-4"); if vertical
padding was intended elsewhere, instead explicitly add the needed "py-*" value
rather than overriding p-3.
- Around line 26-76: VariantRow currently uses hardcoded hex colors for
availability icons; update it to use theme-based colors (via useColorScheme() or
NativeWind semantic tokens) so color follows the app theme. Import and call the
theme hook at the top of VariantRow (e.g., const { colors } = useColorScheme()
or retrieve semantic tokens), then replace '`#22c55e`' and '`#ef4444`' passed to
Ionicons with the corresponding theme values (e.g., colors.green / colors.red or
semantic tokens like 'text-success' / 'text-danger') and ensure the Icon/Text
state logic still uses the same availability checks (variant.availability ===
'in_stock').

In `@apps/expo/features/catalog/screens/CatalogItemsScreen.tsx`:
- Around line 80-87: The atom catalogGroupVariantsAtom is set in
CatalogItemsScreen via handleGroupPress before routing but isn't explicitly
cleared on back/deep-link navigation; update the code by adding a clear comment
near the catalogGroupVariantsAtom definition (and/or a short inline comment
above handleGroupPress) that documents the intended behavior: the atom is
populated only when a user navigates via the group press and may be empty on
direct/deep-link entry to the detail screen, and if desired implement explicit
cleanup on detail-screen unmount (e.g., reset atom in the detail screen's
useEffect cleanup) to guarantee clearing on back navigation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0d7cdf5a-8f21-448d-92ef-e1f7d56efaec

📥 Commits

Reviewing files that changed from the base of the PR and between ab76f4e and 8a46c67.

📒 Files selected for processing (7)
  • apps/expo/atoms/catalogGroupAtom.ts
  • apps/expo/components/CategoriesFilter.tsx
  • apps/expo/features/catalog/lib/groupCatalogItems.ts
  • apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx
  • apps/expo/features/catalog/screens/CatalogItemsScreen.tsx
  • apps/expo/lib/i18n/locales/en.json
  • packages/api/src/services/catalogService.ts

Comment on lines +1 to +7
import type { CatalogItem } from 'expo-app/features/catalog/types';
import { atom } from 'jotai';

// Holds all variants of the catalog group the user tapped in the list.
// Set immediately before pushing to /catalog/[id] so the detail screen can
// show a variants list without any extra API calls.
export const catalogGroupVariantsAtom = atom<CatalogItem[]>([]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider relocating this atom into the catalog feature module.

The atom is tightly coupled to the catalog feature and is only consumed by catalog screens. Per the feature module structure guidelines, catalog-specific state should live within apps/expo/features/catalog/ (e.g., features/catalog/atoms.ts or features/catalog/state.ts).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/expo/atoms/catalogGroupAtom.ts` around lines 1 - 7, Move the
catalogGroupVariantsAtom export out of this shared atoms file into the catalog
feature module’s state/atoms file so catalog-specific state lives with the
feature; create or add to the catalog feature’s atoms/state file a exported atom
named catalogGroupVariantsAtom (same type atom<CatalogItem[]>() and initial
value) and update all imports across the app to point to the new location, then
remove the original export from the current file to avoid duplicate exports.

Source: Coding guidelines

Comment on lines +62 to +67
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={
contentPaddingX ? { paddingHorizontal: contentPaddingX } : undefined
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix falsy check for contentPaddingX.

Line 66 treats contentPaddingX as falsy when it's 0, which is a valid padding value. The current logic contentPaddingX ? { paddingHorizontal: contentPaddingX } : undefined will skip applying the style when contentPaddingX={0}.

🐛 Proposed fix
-          contentContainerStyle={
-            contentPaddingX ? { paddingHorizontal: contentPaddingX } : undefined
-          }
+          contentContainerStyle={
+            contentPaddingX > 0 ? { paddingHorizontal: contentPaddingX } : undefined
+          }

Or more explicitly:

-          contentContainerStyle={
-            contentPaddingX ? { paddingHorizontal: contentPaddingX } : undefined
-          }
+          contentContainerStyle={
+            typeof contentPaddingX === 'number' && contentPaddingX > 0
+              ? { paddingHorizontal: contentPaddingX }
+              : undefined
+          }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/expo/components/CategoriesFilter.tsx` around lines 62 - 67, The
ScrollView's contentContainerStyle currently treats contentPaddingX as falsy (so
0 is ignored); update the conditional in the component (where ScrollView is
rendered) to check for undefined (or the property's type) instead of a truthy
check—e.g., use contentPaddingX !== undefined or typeof contentPaddingX ===
'number' to return { paddingHorizontal: contentPaddingX } when 0 is intended,
otherwise undefined; this change involves the ScrollView rendering block
referencing contentPaddingX in CategoriesFilter.tsx.

Comment thread apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx
Comment on lines +65 to +73
{variant.productUrl ? (
<Pressable
onPress={() => Linking.openURL(variant.productUrl as string)}
className="ml-3 flex-row items-center gap-1 rounded-md border border-border px-3 py-1.5"
>
<Text className="text-xs text-foreground">{t('catalog.viewOnRetailerSite')}</Text>
<Icon name="open-in-new" size={11} color="text-foreground" />
</Pressable>
) : null}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Remove unnecessary type cast.

Line 67 casts variant.productUrl as string, but according to the CatalogItemSchema, productUrl is already typed as string (non-nullable). The type cast is redundant.

♻️ Proposed fix
-          onPress={() => Linking.openURL(variant.productUrl as string)}
+          onPress={() => Linking.openURL(variant.productUrl)}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{variant.productUrl ? (
<Pressable
onPress={() => Linking.openURL(variant.productUrl as string)}
className="ml-3 flex-row items-center gap-1 rounded-md border border-border px-3 py-1.5"
>
<Text className="text-xs text-foreground">{t('catalog.viewOnRetailerSite')}</Text>
<Icon name="open-in-new" size={11} color="text-foreground" />
</Pressable>
) : null}
{variant.productUrl ? (
<Pressable
onPress={() => Linking.openURL(variant.productUrl)}
className="ml-3 flex-row items-center gap-1 rounded-md border border-border px-3 py-1.5"
>
<Text className="text-xs text-foreground">{t('catalog.viewOnRetailerSite')}</Text>
<Icon name="open-in-new" size={11} color="text-foreground" />
</Pressable>
) : null}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx` around lines
65 - 73, Remove the redundant type assertion on variant.productUrl inside the
Pressable onPress handler: update the onPress={() =>
Linking.openURL(variant.productUrl as string)} usage to call Linking.openURL
with variant.productUrl directly (no "as string" cast) in
CatalogItemDetailScreen so the Pressable and its onPress handler use the
already-typed variant.productUrl value.

{t('catalog.specifications')}
</Text>
<View className="rounded-lg p-3 gap-4">
<View className="rounded-lg p-3 py-0 gap-4">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Simplify redundant padding class.

The class string 'p-3 py-0' is redundant because py-0 overrides the vertical padding from p-3, leaving only the horizontal padding. This is effectively px-3.

Consider using 'px-3' for clarity, or if vertical padding is needed elsewhere, restructure the classes.

♻️ Proposed fix
-              <View className="rounded-lg p-3 py-0 gap-4">
+              <View className="rounded-lg px-3 gap-4">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<View className="rounded-lg p-3 py-0 gap-4">
<View className="rounded-lg px-3 gap-4">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/expo/features/catalog/screens/CatalogItemDetailScreen.tsx` at line 287,
In CatalogItemDetailScreen locate the View with className "rounded-lg p-3 py-0
gap-4" and simplify the redundant padding by replacing "p-3 py-0" with "px-3"
(so the class becomes "rounded-lg px-3 gap-4"); if vertical padding was intended
elsewhere, instead explicitly add the needed "py-*" value rather than overriding
p-3.

Comment on lines +80 to +87
const groupedItems = useMemo(() => groupCatalogItems(paginatedItems), [paginatedItems]);

const totalItemsText = `${Number(totalItems).toLocaleString()} ${
totalItems === 1 ? t('catalog.item') : t('catalog.items')
}`;
const showingText = t('catalog.showingItems', {
current: paginatedItems.length,
total: Number(totalItems).toLocaleString(),
});
const setGroupVariants = useSetAtom(catalogGroupVariantsAtom);

const handleGroupPress = (group: CatalogItemGroup) => {
setGroupVariants(group.variants);
router.push({ pathname: '/catalog/[id]', params: { id: group.representative.id } });
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Verify atom cleanup on back navigation.

The atom is set before navigation (line 85) but never explicitly cleared. If the user navigates back from the detail screen and then selects a different group, the atom will be overwritten correctly. However, if the user navigates to a detail screen via deep link or direct URL entry, the atom will be empty, which is the intended behavior per the design.

Consider documenting this behavior in a comment near the atom definition or in the handleGroupPress function.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/expo/features/catalog/screens/CatalogItemsScreen.tsx` around lines 80 -
87, The atom catalogGroupVariantsAtom is set in CatalogItemsScreen via
handleGroupPress before routing but isn't explicitly cleared on back/deep-link
navigation; update the code by adding a clear comment near the
catalogGroupVariantsAtom definition (and/or a short inline comment above
handleGroupPress) that documents the intended behavior: the atom is populated
only when a user navigates via the group press and may be empty on
direct/deep-link entry to the detail screen, and if desired implement explicit
cleanup on detail-screen unmount (e.g., reset atom in the detail screen's
useEffect cleanup) to guarantee clearing on back navigation.

@mikib0 mikib0 linked an issue Jun 8, 2026 that may be closed by this pull request
mikib0 added 3 commits June 8, 2026 17:44
- Replace hardcoded hex availability colors with colors.green/destructive
- Fix open-in-new icon invisible in dark mode (was passing NativeWind class
  string as color value; now uses colors.foreground)
- Move SimilarItems outside padded container for full-bleed scroll
- Add px-4 to SimilarItems title to maintain heading alignment
- Remove unused 'options' i18n key
@mikib0 mikib0 merged commit 593748a into development Jun 8, 2026
21 of 22 checks passed
@mikib0 mikib0 deleted the feat/catalog-pagination-and-variant-grouping branch June 8, 2026 17:02
@mikib0 mikib0 restored the feat/catalog-pagination-and-variant-grouping branch June 8, 2026 19:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Catalogue Displays Repeated Item Instead of Full Item List

1 participant