Skip to content

[APP] Dish detail page#359

Open
timothyrusso wants to merge 6 commits into
mainfrom
feature/356
Open

[APP] Dish detail page#359
timothyrusso wants to merge 6 commits into
mainfrom
feature/356

Conversation

@timothyrusso
Copy link
Copy Markdown
Owner

@timothyrusso timothyrusso commented Jun 4, 2026

Fixes #356

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a detailed dish information modal displaying ingredients, dietary attributes, and high-resolution images.
    • Introduced vegan dietary flag support for trip dishes.
    • New dietary badge indicators for gluten-free, vegan, and vegetarian dishes.
  • UI/UX Improvements

    • Added new Badge and BottomSheetHeader components.
    • Enhanced dish list interactions with detailed view access.
    • Improved color palette and visual component dimensions.
  • Localization

    • Added translation keys for dietary labels and trip UI elements.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Jun 4, 2026

Reviewer's Guide

Implements a new dish details modal flow and supporting UI components/styles, refines typical dishes interactions and layout, and extends domain/navigation schemas with vegan flags and dish detail navigation.

Sequence diagram for the new dish details modal flow

sequenceDiagram
  actor User
  participant FoodCard
  participant NavigationService
  participant TypicalDishesModalPage
  participant TypicalDishesList
  participant DishItem
  participant DishDetailsModalPage
  participant useDishDetailsModalPageLogic
  participant useGetTripById
  participant useGetWikimediaDishImage

  User ->> FoodCard: Pressable onPress handleOpenModal
  FoodCard ->> NavigationService: toTypicalDishesModal tripId
  NavigationService ->> TypicalDishesModalPage: push Modals.TypicalDishes with tripId

  User ->> DishItem: Pressable onPress
  DishItem ->> TypicalDishesList: onPress searchTerm
  TypicalDishesList ->> TypicalDishesModalPage: onDishPress searchTerm
  TypicalDishesModalPage ->> NavigationService: toDishDetailsModal { tripId, searchTerm }
  NavigationService ->> DishDetailsModalPage: push Modals.DishDetails with params

  DishDetailsModalPage ->> useDishDetailsModalPageLogic: useDishDetailsModalPageLogic
  useDishDetailsModalPageLogic ->> useGetTripById: useGetTripById tripId
  useDishDetailsModalPageLogic ->> useGetWikimediaDishImage: useGetWikimediaDishImage searchTerm
  useGetTripById -->> useDishDetailsModalPageLogic: trip
  useGetWikimediaDishImage -->> useDishDetailsModalPageLogic: { url, isLoading }
  useDishDetailsModalPageLogic -->> DishDetailsModalPage: dishName, dishDescription, dishIngredients, isGlutenFree, isVegetarian, isVegan, image, imageIsLoading

  User ->> DishDetailsModalPage: Tap close
  DishDetailsModalPage ->> useDishDetailsModalPageLogic: handleClose
  useDishDetailsModalPageLogic ->> NavigationService: back
Loading

File-Level Changes

Change Details Files
Revamp FoodCard and typical dishes trigger UI to use a tappable info box instead of a button.
  • Extend FoodCard styles with title container, typical dishes box, pressed state, and box text/button styles using shared opacity.
  • Replace CustomButtonMedium with a Pressable that shows an icon, title, and 'more details' text and triggers the typical dishes modal.
  • Import opacity token in FoodCard styles and Pressable in FoodCard component.
features/trips/ui/components/FoodCard/FoodCard.style.ts
features/trips/ui/components/FoodCard/FoodCard.tsx
Make typical dish items tappable and visually richer, and propagate dish press handling through the list and modal.
  • Update DishItem styles with grey background container, padding, rounded corners, image border, and pressed opacity state.
  • Wrap DishItem in a Pressable that calls an onPress prop while preserving existing skeleton/image/text layout.
  • Update TypicalDishesList to accept an onDishPress callback and pass it to each DishItem with the dish searchTerm.
  • Wire TypicalDishesModalPage logic and view to handle dish presses via navigationService.toDishDetailsModal.
  • Visually soften TypicalDishesList separators and modal header by changing separator color/margins and removing header bottom border.
features/trips/ui/components/FoodCard/components/DishItem/DishItem.style.ts
features/trips/ui/components/FoodCard/components/DishItem/DishItem.tsx
features/trips/ui/components/TypicalDishesList/TypicalDishesList.tsx
features/trips/ui/components/TypicalDishesList/TypicalDishesList.style.ts
features/trips/ui/pages/TypicalDishesModalPage/TypicalDishesModalPage.tsx
features/trips/ui/pages/TypicalDishesModalPage/TypicalDishesModalPage.logic.ts
features/trips/ui/components/TypicalDishesModalHeader/TypicalDishesModalHeader.style.ts
Add navigation, routing, and modal wiring for dish details.
  • Extend Modals enum with DishDetails and register a DishDetails screen in the create-trip stack using formSheetOptions.
  • Add toDishDetailsModal to INavigationService and implement it in NavigationService to push the new modal with tripId and searchTerm params; generalize toTypicalDishesModal param handling.
  • Export DishDetailsModalPage from trips pages and add Expo Router entrypoint dish-details-modal.tsx that renders it.
features/core/navigation/data/services/NavigationService.ts
features/core/navigation/domain/entities/Modals.ts
features/core/navigation/domain/entities/services/INavigationService.ts
app/(main)/(authenticated)/create-trip/_layout.tsx
features/trips/pages.ts
app/(main)/(authenticated)/create-trip/dish-details-modal.tsx
Introduce DishDetailsModalPage with supporting logic, ingredients chips list, and dietary badges UI.
  • Create DishDetailsModalPage component that shows a header, dish image/skeleton, ingredients list, description, and three dietary badges (gluten free, vegan, vegetarian).
  • Implement DishDetailsModalPage styles for layout, full-size dish image, description text, and badges row.
  • Add IngredientsList component and styles that render an uppercased title and a wrap layout of Cheap chips for each ingredient using a checkmark icon and no uppercasing for ingredient text.
  • Add BottomSheetHeader composite component to render a title and a close icon button for bottom-sheet modals.
  • Add Badge composite component (with dynamic active styling) showing a circular image, check/close icon overlay, and an uppercased label; wire in new badge-related dimensions and icons.
  • Implement DishDetailsModalPage.logic hook to read tripId/searchTerm from route params, fetch Wikimedia image, resolve the dish from the trip entity, and expose dish metadata, booleans, and static badge images along with a close handler.
features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.tsx
features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.style.ts
features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.logic.ts
features/trips/ui/components/IngredientsList/IngredientsList.tsx
features/trips/ui/components/IngredientsList/IngredientsList.style.ts
features/core/ui/components/composite/BottomSheetHeader/BottomSheetHeader.tsx
features/core/ui/components/composite/BottomSheetHeader/BottomSheetHeader.style.ts
features/core/ui/components/composite/Badge/Badge.tsx
features/core/ui/components/composite/Badge/Badge.style.ts
features/core/ui/style/dimensions/components.ts
features/core/ui/style/icons.ts
Extend domain and UI primitives to support vegan flag on dishes and reusable chip behavior.
  • Add isVegan field to TypicalDish entity, Convex validator, and generated trip schema so AI/generated data includes vegan info.
  • Extend colors with tertiaryGreen and images dimensions with dishFullImageSize for new UI elements.
  • Update Cheap component to accept an optional uppercase flag (default true) and use it in IngredientsList to keep ingredient labels case-sensitive.
  • Re-export Badge and BottomSheetHeader from the core UI barrel for app-wide usage.
  • Update translations files (en/it) to support new copy keys used in dish details and badges (content not shown in diff).
convex/validators/Trips.ts
features/trip-generation/domain/schemas/GenerateTripSchema.ts
features/trips/domain/entities/TypicalDish.ts
features/core/ui/style/colors.ts
features/core/ui/style/dimensions/images.ts
features/core/ui/components/basic/Cheap/Cheap.tsx
features/core/ui/index.ts
features/core/translations/libraries/locales/en.json
features/core/translations/libraries/locales/it.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

Warning

Review limit reached

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

More reviews will be available in 42 minutes and 57 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e7e4835b-7d8f-47b7-9e9b-6df0146c1492

📥 Commits

Reviewing files that changed from the base of the PR and between c1affe4 and fc83f8c.

📒 Files selected for processing (3)
  • features/trips/ui/components/FoodCard/components/DishItem/DishItem.logic.ts
  • features/trips/ui/components/FoodCard/components/DishItem/DishItem.style.ts
  • features/trips/ui/components/FoodCard/components/DishItem/DishItem.tsx
📝 Walkthrough

Walkthrough

This PR adds a new dish details modal feature to the create-trip flow. Users can now press individual dishes in the typical dishes modal to view dietary attributes (vegan, vegetarian, gluten-free status), ingredients, description, and a fetched image. The modal is accessible from the typical dishes list via a new navigation route wired through the navigation service and router.

Changes

Dish Details Modal Feature

Layer / File(s) Summary
Schema and domain model updates
features/trips/domain/entities/TypicalDish.ts, convex/validators/Trips.ts, features/trip-generation/domain/schemas/GenerateTripSchema.ts
TypicalDish is extended with isVegan: boolean field across domain, validator, and generation schema layers to track vegan status alongside existing gluten-free and vegetarian attributes.
Navigation infrastructure and modal routing
features/core/navigation/domain/entities/Modals.ts, features/core/navigation/domain/entities/services/INavigationService.ts, features/core/navigation/data/services/NavigationService.ts
Modal registry gains DishDetails identifier; INavigationService interface and NavigationService implementation add toDishDetailsModal(tripId, searchTerm) method to enable routing to the modal with both trip and dish context. toTypicalDishesModal params signature is normalized.
Design system extensions
features/core/ui/style/colors.ts, features/core/ui/style/dimensions/components.ts, features/core/ui/style/dimensions/images.ts, features/core/ui/style/icons.ts, features/core/ui/components/basic/Cheap/Cheap.tsx, features/core/ui/components/composite/Badge/*, features/core/ui/components/composite/BottomSheetHeader/*, features/core/ui/index.ts
New color (tertiaryGreen), dimensions (badge sizing, dish image size), and icons (checkmark, close-circle) are added. Cheap component gains uppercase prop control. New Badge component renders circular status indicators with overlay icons. New BottomSheetHeader component provides modal title with close action. Both components are exported from core UI barrel.
Ingredients list component
features/trips/ui/components/IngredientsList/*
New IngredientsList component renders a titled list of ingredient chips using Cheap component with checkmark icons and fixed styling.
Dish details modal page
features/trips/ui/pages/DishDetailsModalPage/*, features/trips/pages.ts
Logic hook wires route params (searchTerm, tripId), fetches trip and Wikimedia dish image, and derives matching dish. Page component renders BottomSheetHeader, conditional image loading state, IngredientsList, description, and three Badge components for dietary status. Exported from feature barrel.
Typical dishes integration
features/trips/ui/components/TypicalDishesList/TypicalDishesList.tsx, features/trips/ui/components/FoodCard/components/DishItem/*, features/trips/ui/components/TypicalDishesModalHeader/TypicalDishesModalHeader.style.ts, features/trips/ui/pages/TypicalDishesModalPage/*
TypicalDishesList and DishItem receive onPress callback wired to handleDishPress. Typical dishes modal logic defines handleDishPress that navigates to dish details with trip and search term. Header bottom border styling is removed. Dish item container now includes background, padding, and border styling with pressed-state opacity.
FoodCard button interaction
features/trips/ui/components/FoodCard/FoodCard.tsx, features/trips/ui/components/FoodCard/FoodCard.style.ts
Typical dishes action section refactored from CustomButtonMedium to Pressable with pressed-state opacity. New styles added for layout, pressed state, and text styling. Cafe icon, "TYPICAL_DISHES" title, and "MORE_DETAILS" text render within the pressable.
Router and localization
app/(main)/(authenticated)/create-trip/_layout.tsx, app/(main)/(authenticated)/create-trip/dish-details-modal.tsx, features/core/translations/libraries/locales/en.json, features/core/translations/libraries/locales/it.json
New DishDetailsModal screen registered in create-trip layout with form-sheet options. Wrapper component re-exports DishDetailsModalPage. Translation keys added for dietary attributes (GLUTEN_FREE, VEGAN, VEGETARIAN) and UI text (MORE_DETAILS, UPCOMING_TRIP) in English and Italian locales.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • timothyrusso/HolidAI#301: Refactors NavigationService with updated method signatures for modal navigation parameter handling.
  • timothyrusso/HolidAI#353: Implements Expo Router bottom-sheet modal flows for create-trip screen navigation and form-sheet modal registration.

Poem

A rabbit hops through trips with glee,
Now dishes show their secrets free—
With vegan badges, ingredients bright,
Each modal opened, detail in sight! 🥕🐰

🚥 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 PR title '[APP] Dish detail page' accurately and concisely describes the main feature addition - a new dish detail modal page component with supporting navigation and UI infrastructure.
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 feature/356

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 changed the title Feature/356 [APP] Dish detail page Jun 4, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

Architecture violations found

The following dependency-cruiser rules were violated. Fix them before merging.


> holidai@1.0.0 arch
> depcruise features convex app types --config .dependency-cruiser.js


  error enforce-index-boundary-features-core-images: features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.logic.ts → features/core/images/facades/useGetWikimediaDishImage.ts

x 1 dependency violations (1 errors, 0 warnings). 528 modules, 1600 dependencies cruised.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new 'Dish Details' modal page to display typical dish information, including its description, ingredients, and dietary badges (Gluten-Free, Vegan, Vegetarian), along with supporting navigation, schemas, and translations. Feedback on the changes suggests avoiding dynamic StyleSheet.create calls in the Badge component to prevent performance issues, exposing and handling the trip query loading state with skeleton screens in the modal page to avoid visual flashes, and localizing the hardcoded 'Ingredients' string.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +8 to +47
export const styles = (active: boolean, backgroundColor: string) =>
StyleSheet.create({
container: {
alignItems: 'center',
gap: spacing.Single,
},
circleWrapper: {
width: components.badgeCircleSize,
height: components.badgeCircleSize,
},
circle: {
width: components.badgeCircleSize,
height: components.badgeCircleSize,
borderRadius: components.badgeCircleSize / 2,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: active ? backgroundColor : colors.primaryRed,
},
checkBadge: {
position: 'absolute',
bottom: 0,
right: 0,
width: components.badgeCheckSize,
height: components.badgeCheckSize,
borderRadius: components.badgeCheckSize / 2,
backgroundColor: colors.primaryWhite,
alignItems: 'center',
justifyContent: 'center',
},
badgeImage: {
width: components.badgeCircleSize * 0.6,
height: components.badgeCircleSize * 0.6,
},
label: {
fontSize: fontSize.XS,
fontFamily: fontFamily.interBold,
color: colors.primaryBlack,
textAlign: 'center',
},
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Creating a StyleSheet dynamically inside a function or render path is a performance anti-pattern in React Native. StyleSheet.create should be called once at the module level so that styles are sent to the native bridge only once. Dynamic values (like backgroundColor based on active) should be passed as inline styles or merged with static styles.

export const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
    gap: spacing.Single,
  },
  circleWrapper: {
    width: components.badgeCircleSize,
    height: components.badgeCircleSize,
  },
  circle: {
    width: components.badgeCircleSize,
    height: components.badgeCircleSize,
    borderRadius: components.badgeCircleSize / 2,
    alignItems: 'center',
    justifyContent: 'center',
  },
  checkBadge: {
    position: 'absolute',
    bottom: 0,
    right: 0,
    width: components.badgeCheckSize,
    height: components.badgeCheckSize,
    borderRadius: components.badgeCheckSize / 2,
    backgroundColor: colors.primaryWhite,
    alignItems: 'center',
    justifyContent: 'center',
  },
  badgeImage: {
    width: components.badgeCircleSize * 0.6,
    height: components.badgeCircleSize * 0.6,
  },
  label: {
    fontSize: fontSize.XS,
    fontFamily: fontFamily.interBold,
    color: colors.primaryBlack,
    textAlign: 'center',
  },
});

Comment on lines +1 to +39
import { CustomIcon } from '@/features/core/ui/components/basic/CustomIcon/CustomIcon';
import { CustomImage } from '@/features/core/ui/components/basic/CustomImage/CustomImage';
import { CustomText } from '@/features/core/ui/components/basic/CustomText/CustomText';
import { styles as badgeStyle } from '@/features/core/ui/components/composite/Badge/Badge.style';
import { colors } from '@/features/core/ui/style/colors';
import { spacing } from '@/features/core/ui/style/dimensions/spacing';
import { icons } from '@/features/core/ui/style/icons';
import type { FC } from 'react';
import type { ImageSourcePropType } from 'react-native';
import { View } from 'react-native';

type BadgeProps = {
label: string;
image: ImageSourcePropType;
backgroundColor: string;
active: boolean;
};

export const Badge: FC<BadgeProps> = ({ label, image, backgroundColor, active }) => {
const styles = badgeStyle(active, backgroundColor);

return (
<View style={styles.container}>
<View style={styles.circleWrapper}>
<View style={styles.circle}>
<CustomImage source={image} style={styles.badgeImage} />
</View>
<View style={styles.checkBadge}>
<CustomIcon
name={active ? icons.success : icons.closeCircle}
size={spacing.TripleAndHalf}
color={active ? colors.tertiaryGreen : colors.primaryRed}
/>
</View>
</View>
<CustomText text={label.toLocaleUpperCase()} style={styles.label} />
</View>
);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Update the Badge component to use the static stylesheet and apply the dynamic background color inline.

import { CustomIcon } from '@/features/core/ui/components/basic/CustomIcon/CustomIcon';
import { CustomImage } from '@/features/core/ui/components/basic/CustomImage/CustomImage';
import { CustomText } from '@/features/core/ui/components/basic/CustomText/CustomText';
import { styles } from '@/features/core/ui/components/composite/Badge/Badge.style';
import { colors } from '@/features/core/ui/style/colors';
import { spacing } from '@/features/core/ui/style/dimensions/spacing';
import { icons } from '@/features/core/ui/style/icons';
import type { FC } from 'react';
import type { ImageSourcePropType } from 'react-native';
import { View } from 'react-native';

type BadgeProps = {
  label: string;
  image: ImageSourcePropType;
  backgroundColor: string;
  active: boolean;
};

export const Badge: FC<BadgeProps> = ({ label, image, backgroundColor, active }) => {
  return (
    <View style={styles.container}>
      <View style={styles.circleWrapper}>
        <View style={[styles.circle, { backgroundColor: active ? backgroundColor : colors.primaryRed }]}>
          <CustomImage source={image} style={styles.badgeImage} />
        </View>
        <View style={styles.checkBadge}>
          <CustomIcon
            name={active ? icons.success : icons.closeCircle}
            size={spacing.TripleAndHalf}
            color={active ? colors.tertiaryGreen : colors.primaryRed}
          />
        </View>
      </View>
      <CustomText text={label.toLocaleUpperCase()} style={styles.label} />
    </View>
  );
};

Comment on lines +10 to +33
export const useDishDetailsModalPageLogic = () => {
const { tripId, searchTerm } = useLocalSearchParams<{ tripId: string; searchTerm: string }>();
const { data, isLoading } = useGetWikimediaDishImage(searchTerm);
const { trip } = useGetTripById(tripId);

const dish = trip?.tripAiResp?.food?.typicalDishes.find(d => d.searchTerm === searchTerm);

const handleClose = () => navigationService.back();

return {
dishName: dish?.name ?? '',
dishDescription: dish?.description ?? '',
dishIngredients: dish?.ingredients ?? [],
handleClose,
image: data?.url,
imageIsLoading: isLoading,
isVegetarian: dish?.isVegetarian ?? false,
isGlutenFree: dish?.isGlutenFree ?? false,
isVegan: dish?.isVegan ?? false,
glutenFreeImage,
veganImage,
vegetarianImage,
};
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To prevent an unwanted visual flash of empty content before the trip data loads, avoid prematurely applying fallback values (like ?? '' or ?? []) in the hook. Instead, expose the loading state of the trip query so that the consumer component (DishDetailsModalPage) can render appropriate skeleton screens for the title, description, and ingredients list.

export const useDishDetailsModalPageLogic = () => {
  const { tripId, searchTerm } = useLocalSearchParams<{ tripId: string; searchTerm: string }>();
  const { data, isLoading: imageIsLoading } = useGetWikimediaDishImage(searchTerm);
  const { trip, isLoading: tripIsLoading } = useGetTripById(tripId);

  const dish = trip?.tripAiResp?.food?.typicalDishes.find(d => d.searchTerm === searchTerm);

  const handleClose = () => navigationService.back();

  return {
    dish,
    isLoading: tripIsLoading,
    handleClose,
    image: data?.url,
    imageIsLoading,
    glutenFreeImage,
    veganImage,
    vegetarianImage,
  };
};
References
  1. Do not prematurely apply fallback values to query data in custom hooks if the consumer component handles the loading state (e.g., by showing a skeleton), as this can cause an unwanted visual flash instead of a smooth loading transition.

Comment on lines +7 to +52
export const DishDetailsModalPage = () => {
const {
dishName,
dishDescription,
dishIngredients,
handleClose,
image,
imageIsLoading,
isVegetarian,
isGlutenFree,
isVegan,
glutenFreeImage,
veganImage,
vegetarianImage,
} = useDishDetailsModalPageLogic();

return (
<ScrollView contentContainerStyle={styles.contentContainer} style={styles.container}>
<BottomSheetHeader title={dishName} onClose={handleClose} />
<View style={styles.bodyContainer}>
{imageIsLoading ? (
<BaseSkeleton style={styles.image} />
) : (
<CustomImage source={typeof image === 'string' ? { uri: image } : image} style={styles.image} />
)}
<IngredientsList title="Ingredients" ingredients={dishIngredients} />
</View>
<CustomText text={dishDescription} style={styles.description} />
<View style={styles.badgesContainer}>
<Badge
label="MY_TRIP.GLUTEN_FREE"
image={glutenFreeImage}
backgroundColor={colors.tertiaryGreen}
active={isGlutenFree}
/>
<Badge label="MY_TRIP.VEGAN" image={veganImage} backgroundColor={colors.tertiaryGreen} active={isVegan} />
<Badge
label="MY_TRIP.VEGETARIAN"
image={vegetarianImage}
backgroundColor={colors.tertiaryGreen}
active={isVegetarian}
/>
</View>
</ScrollView>
);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To prevent an unwanted visual flash of empty content before the trip data loads, avoid prematurely applying fallback values in the hook. Instead, handle the loading state of the trip query by rendering appropriate skeleton screens for the title, description, and ingredients list. Additionally, the string 'Ingredients' is hardcoded; it should be defined in the translation files and referenced using an i18n key (e.g., MY_TRIP.INGREDIENTS) to support internationalization.

export const DishDetailsModalPage = () => {
  const {
    dish,
    isLoading,
    handleClose,
    image,
    imageIsLoading,
    glutenFreeImage,
    veganImage,
    vegetarianImage,
  } = useDishDetailsModalPageLogic();

  if (isLoading || !dish) {
    return (
      <ScrollView contentContainerStyle={styles.contentContainer} style={styles.container}>
        <BottomSheetHeader title="" onClose={handleClose} />
        <View style={styles.bodyContainer}>
          <BaseSkeleton style={styles.image} />
          <View style={{ flex: 1, gap: 10 }}>
            <BaseSkeleton style={{ height: 20, width: '60%' }} />
            <BaseSkeleton style={{ height: 40, width: '100%' }} />
          </View>
        </View>
        <BaseSkeleton style={{ height: 100, width: '100%', marginTop: 20 }} />
      </ScrollView>
    );
  }

  return (
    <ScrollView contentContainerStyle={styles.contentContainer} style={styles.container}>
      <BottomSheetHeader title={dish.name} onClose={handleClose} />
      <View style={styles.bodyContainer}>
        {imageIsLoading ? (
          <BaseSkeleton style={styles.image} />
        ) : (
          <CustomImage source={typeof image === 'string' ? { uri: image } : image} style={styles.image} />
        )}
        <IngredientsList title="MY_TRIP.INGREDIENTS" ingredients={dish.ingredients} />
      </View>
      <CustomText text={dish.description} style={styles.description} />
      <View style={styles.badgesContainer}>
        <Badge
          label="MY_TRIP.GLUTEN_FREE"
          image={glutenFreeImage}
          backgroundColor={colors.tertiaryGreen}
          active={dish.isGlutenFree}
        />
        <Badge label="MY_TRIP.VEGAN" image={veganImage} backgroundColor={colors.tertiaryGreen} active={dish.isVegan} />
        <Badge
          label="MY_TRIP.VEGETARIAN"
          image={vegetarianImage}
          backgroundColor={colors.tertiaryGreen}
          active={dish.isVegetarian}
        />
      </View>
    </ScrollView>
  );
};
References
  1. Do not prematurely apply fallback values to query data in custom hooks if the consumer component handles the loading state (e.g., by showing a skeleton), as this can cause an unwanted visual flash instead of a smooth loading transition.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • In Badge.tsx you reference icons.success, but the icons map only declares checkmark and closeCircle, so this will resolve to undefined at runtime—either add success to the icons map or reuse an existing icon key.
  • The Badge component uppercases translation keys via label.toLocaleUpperCase() before passing them to CustomText, which likely expects the raw i18n key; this will break lookups—consider leaving the key unchanged and handling casing via styles or by uppercasing the translated text instead.
  • The new IngredientsList and DishDetailsModalPage components mix plain strings (e.g. "Ingredients") and translation keys; for consistency with the rest of the app, consider standardizing on translation keys and letting CustomText handle localization.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `Badge.tsx` you reference `icons.success`, but the icons map only declares `checkmark` and `closeCircle`, so this will resolve to `undefined` at runtime—either add `success` to the icons map or reuse an existing icon key.
- The `Badge` component uppercases translation keys via `label.toLocaleUpperCase()` before passing them to `CustomText`, which likely expects the raw i18n key; this will break lookups—consider leaving the key unchanged and handling casing via styles or by uppercasing the translated text instead.
- The new `IngredientsList` and `DishDetailsModalPage` components mix plain strings (e.g. `"Ingredients"`) and translation keys; for consistency with the rest of the app, consider standardizing on translation keys and letting `CustomText` handle localization.

## Individual Comments

### Comment 1
<location path="features/core/ui/components/composite/Badge/Badge.tsx" line_range="29-30" />
<code_context>
+          <CustomImage source={image} style={styles.badgeImage} />
+        </View>
+        <View style={styles.checkBadge}>
+          <CustomIcon
+            name={active ? icons.success : icons.closeCircle}
+            size={spacing.TripleAndHalf}
+            color={active ? colors.tertiaryGreen : colors.primaryRed}
</code_context>
<issue_to_address>
**issue (bug_risk):** The `icons.success` key is not defined in the icons map and will likely cause a runtime error.

`icons.checkmark` is the defined success glyph in the icons map; `icons.success` does not exist. As written, this will pass `undefined` as the icon name, leading to a missing or broken icon at runtime. Please switch to `icons.checkmark` (or another existing key) so the success state renders properly.
</issue_to_address>

### Comment 2
<location path="features/trips/ui/components/IngredientsList/IngredientsList.tsx" line_range="14-24" />
<code_context>
+  <View style={styles.container}>
+    <CustomText text={title.toLocaleUpperCase()} style={styles.title} />
+    <View style={styles.chipsRow}>
+      {ingredients.map(ingredient => (
+        <Cheap
+          key={ingredient}
+          title={ingredient}
+          color={colors.secondaryGrey}
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Using the ingredient value as the React key can lead to duplicate keys if the list contains repeated ingredients.

If an ingredient appears more than once, React will get duplicate keys, which can cause unstable rendering. Use a guaranteed-unique key instead, such as the map index or a combination of value and index (e.g. `key={`${ingredient}-${index}`}`).

```suggestion
    <View style={styles.chipsRow}>
      {ingredients.map((ingredient, index) => (
        <Cheap
          key={`${ingredient}-${index}`}
          title={ingredient}
          color={colors.secondaryGrey}
          icon={icons.checkmark}
          uppercase={false}
        />
      ))}
    </View>
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +29 to +30
<CustomIcon
name={active ? icons.success : icons.closeCircle}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The icons.success key is not defined in the icons map and will likely cause a runtime error.

icons.checkmark is the defined success glyph in the icons map; icons.success does not exist. As written, this will pass undefined as the icon name, leading to a missing or broken icon at runtime. Please switch to icons.checkmark (or another existing key) so the success state renders properly.

Comment on lines +14 to +24
<View style={styles.chipsRow}>
{ingredients.map(ingredient => (
<Cheap
key={ingredient}
title={ingredient}
color={colors.secondaryGrey}
icon={icons.checkmark}
uppercase={false}
/>
))}
</View>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (bug_risk): Using the ingredient value as the React key can lead to duplicate keys if the list contains repeated ingredients.

If an ingredient appears more than once, React will get duplicate keys, which can cause unstable rendering. Use a guaranteed-unique key instead, such as the map index or a combination of value and index (e.g. key={${ingredient}-${index}}).

Suggested change
<View style={styles.chipsRow}>
{ingredients.map(ingredient => (
<Cheap
key={ingredient}
title={ingredient}
color={colors.secondaryGrey}
icon={icons.checkmark}
uppercase={false}
/>
))}
</View>
<View style={styles.chipsRow}>
{ingredients.map((ingredient, index) => (
<Cheap
key={`${ingredient}-${index}`}
title={ingredient}
color={colors.secondaryGrey}
icon={icons.checkmark}
uppercase={false}
/>
))}
</View>

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 `@features/trips/ui/components/FoodCard/components/DishItem/DishItem.tsx`:
- Line 14: The Pressable in the DishItem component needs accessibility
semantics: add accessibilityRole="button" to the Pressable and supply an
accessibilityLabel derived from the dish name prop (e.g., `${name}, button` or
similar) so screen readers announce it properly; also forward accessibilityState
(e.g., disabled if onPress is undefined) and keep the existing onPress and style
props (look for the Pressable using styles.container and the onPress handler in
DishItem) so behavior and visual styling are unchanged.

In `@features/trips/ui/components/IngredientsList/IngredientsList.tsx`:
- Around line 15-18: The current IngredientsList map uses key={ingredient} which
can collide for duplicate ingredients; update the map in the IngredientsList
component to use a guaranteed-unique key (e.g., include the map index or a
unique id) by changing the map callback to accept the index and pass a composite
key such as `${ingredient}-${index}` (or use an ingredient unique identifier if
one exists) to the Cheap component's key prop to avoid reconciliation issues.

In `@features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.tsx`:
- Around line 27-31: When imageIsLoading is false but image is missing,
CustomImage may receive an undefined source; update the rendering logic in
DishDetailsModalPage so it checks for a loaded-but-no-image case before
rendering CustomImage. Specifically, inside the JSX that currently switches on
imageIsLoading, add an explicit branch for when imageIsLoading === false &&
!image to render a fallback (e.g., BaseSkeleton or a placeholder view) using
styles.image, otherwise render CustomImage with the existing source logic;
adjust the conditional that references imageIsLoading, image, CustomImage, and
BaseSkeleton accordingly.
- Line 32: Replace the hardcoded "Ingredients" string passed to IngredientsList
in DishDetailsModalPage with a localized string lookup (e.g., use your i18n
translate function such as t('dishDetails.ingredients') or a locale prop) so the
title comes from translations; update the IngredientsList invocation to:
title={t('...')} (or equivalent) and add the corresponding translation key to
your locale files (refer to IngredientsList, dishIngredients, and
DishDetailsModalPage to locate the change).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6abe06b9-c0a8-4400-a4b5-a51e851ab70c

📥 Commits

Reviewing files that changed from the base of the PR and between bba3281 and c1affe4.

⛔ Files ignored due to path filters (3)
  • features/core/ui/assets/images/gluten_free.png is excluded by !**/*.png
  • features/core/ui/assets/images/vegan.png is excluded by !**/*.png
  • features/core/ui/assets/images/vegetarian.png is excluded by !**/*.png
📒 Files selected for processing (35)
  • app/(main)/(authenticated)/create-trip/_layout.tsx
  • app/(main)/(authenticated)/create-trip/dish-details-modal.tsx
  • convex/validators/Trips.ts
  • features/core/navigation/data/services/NavigationService.ts
  • features/core/navigation/domain/entities/Modals.ts
  • features/core/navigation/domain/entities/services/INavigationService.ts
  • features/core/translations/libraries/locales/en.json
  • features/core/translations/libraries/locales/it.json
  • features/core/ui/components/basic/Cheap/Cheap.tsx
  • features/core/ui/components/composite/Badge/Badge.style.ts
  • features/core/ui/components/composite/Badge/Badge.tsx
  • features/core/ui/components/composite/BottomSheetHeader/BottomSheetHeader.style.ts
  • features/core/ui/components/composite/BottomSheetHeader/BottomSheetHeader.tsx
  • features/core/ui/index.ts
  • features/core/ui/style/colors.ts
  • features/core/ui/style/dimensions/components.ts
  • features/core/ui/style/dimensions/images.ts
  • features/core/ui/style/icons.ts
  • features/trip-generation/domain/schemas/GenerateTripSchema.ts
  • features/trips/domain/entities/TypicalDish.ts
  • features/trips/pages.ts
  • features/trips/ui/components/FoodCard/FoodCard.style.ts
  • features/trips/ui/components/FoodCard/FoodCard.tsx
  • features/trips/ui/components/FoodCard/components/DishItem/DishItem.style.ts
  • features/trips/ui/components/FoodCard/components/DishItem/DishItem.tsx
  • features/trips/ui/components/IngredientsList/IngredientsList.style.ts
  • features/trips/ui/components/IngredientsList/IngredientsList.tsx
  • features/trips/ui/components/TypicalDishesList/TypicalDishesList.style.ts
  • features/trips/ui/components/TypicalDishesList/TypicalDishesList.tsx
  • features/trips/ui/components/TypicalDishesModalHeader/TypicalDishesModalHeader.style.ts
  • features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.logic.ts
  • features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.style.ts
  • features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.tsx
  • features/trips/ui/pages/TypicalDishesModalPage/TypicalDishesModalPage.logic.ts
  • features/trips/ui/pages/TypicalDishesModalPage/TypicalDishesModalPage.tsx
💤 Files with no reviewable changes (1)
  • features/trips/ui/components/TypicalDishesModalHeader/TypicalDishesModalHeader.style.ts


return (
<View style={styles.container}>
<Pressable style={({ pressed }) => [styles.container, pressed && styles.pressed]} onPress={onPress}>
Copy link
Copy Markdown

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

Add accessibility semantics to the new tappable row.

On Line 14, the new Pressable should expose button semantics for screen readers (accessibilityRole, and ideally a label based on the dish name) so assistive-tech users can reliably identify and activate it.

Suggested patch
-    <Pressable style={({ pressed }) => [styles.container, pressed && styles.pressed]} onPress={onPress}>
+    <Pressable
+      style={({ pressed }) => [styles.container, pressed && styles.pressed]}
+      onPress={onPress}
+      accessibilityRole="button"
+      accessibilityLabel={dish.name}
+    >
📝 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
<Pressable style={({ pressed }) => [styles.container, pressed && styles.pressed]} onPress={onPress}>
<Pressable
style={({ pressed }) => [styles.container, pressed && styles.pressed]}
onPress={onPress}
accessibilityRole="button"
accessibilityLabel={dish.name}
>
🤖 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 `@features/trips/ui/components/FoodCard/components/DishItem/DishItem.tsx` at
line 14, The Pressable in the DishItem component needs accessibility semantics:
add accessibilityRole="button" to the Pressable and supply an accessibilityLabel
derived from the dish name prop (e.g., `${name}, button` or similar) so screen
readers announce it properly; also forward accessibilityState (e.g., disabled if
onPress is undefined) and keep the existing onPress and style props (look for
the Pressable using styles.container and the onPress handler in DishItem) so
behavior and visual styling are unchanged.

Comment on lines +15 to +18
{ingredients.map(ingredient => (
<Cheap
key={ingredient}
title={ingredient}
Copy link
Copy Markdown

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

Use a guaranteed-unique key for ingredient chips.

On Line 17, key={ingredient} will collide when the same ingredient appears more than once, which can cause incorrect list reconciliation.

Suggested fix
-      {ingredients.map(ingredient => (
+      {ingredients.map((ingredient, index) => (
         <Cheap
-          key={ingredient}
+          key={`${ingredient}-${index}`}
           title={ingredient}
           color={colors.secondaryGrey}
           icon={icons.checkmark}
           uppercase={false}
         />
       ))}
📝 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
{ingredients.map(ingredient => (
<Cheap
key={ingredient}
title={ingredient}
{ingredients.map((ingredient, index) => (
<Cheap
key={`${ingredient}-${index}`}
title={ingredient}
color={colors.secondaryGrey}
icon={icons.checkmark}
uppercase={false}
/>
))}
🤖 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 `@features/trips/ui/components/IngredientsList/IngredientsList.tsx` around
lines 15 - 18, The current IngredientsList map uses key={ingredient} which can
collide for duplicate ingredients; update the map in the IngredientsList
component to use a guaranteed-unique key (e.g., include the map index or a
unique id) by changing the map callback to accept the index and pass a composite
key such as `${ingredient}-${index}` (or use an ingredient unique identifier if
one exists) to the Cheap component's key prop to avoid reconciliation issues.

Comment on lines +27 to +31
{imageIsLoading ? (
<BaseSkeleton style={styles.image} />
) : (
<CustomImage source={typeof image === 'string' ? { uri: image } : image} style={styles.image} />
)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle the resolved-without-image case before rendering CustomImage.

On Line 30, when imageIsLoading is false and image is missing, CustomImage gets an undefined source. Add an explicit fallback branch for “loaded but no image”.

Suggested fix
-        {imageIsLoading ? (
+        {imageIsLoading ? (
           <BaseSkeleton style={styles.image} />
+        ) : !image ? (
+          <View style={styles.image} />
         ) : (
           <CustomImage source={typeof image === 'string' ? { uri: image } : image} style={styles.image} />
         )}
📝 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
{imageIsLoading ? (
<BaseSkeleton style={styles.image} />
) : (
<CustomImage source={typeof image === 'string' ? { uri: image } : image} style={styles.image} />
)}
{imageIsLoading ? (
<BaseSkeleton style={styles.image} />
) : !image ? (
<View style={styles.image} />
) : (
<CustomImage source={typeof image === 'string' ? { uri: image } : image} style={styles.image} />
)}
🤖 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 `@features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.tsx` around
lines 27 - 31, When imageIsLoading is false but image is missing, CustomImage
may receive an undefined source; update the rendering logic in
DishDetailsModalPage so it checks for a loaded-but-no-image case before
rendering CustomImage. Specifically, inside the JSX that currently switches on
imageIsLoading, add an explicit branch for when imageIsLoading === false &&
!image to render a fallback (e.g., BaseSkeleton or a placeholder view) using
styles.image, otherwise render CustomImage with the existing source logic;
adjust the conditional that references imageIsLoading, image, CustomImage, and
BaseSkeleton accordingly.

) : (
<CustomImage source={typeof image === 'string' ? { uri: image } : image} style={styles.image} />
)}
<IngredientsList title="Ingredients" ingredients={dishIngredients} />
Copy link
Copy Markdown

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

Avoid hardcoded English copy in the modal body.

Line 32 uses a literal "Ingredients", which bypasses localization.

Suggested fix
-        <IngredientsList title="Ingredients" ingredients={dishIngredients} />
+        <IngredientsList title="MY_TRIP.INGREDIENTS" ingredients={dishIngredients} />
🤖 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 `@features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.tsx` at
line 32, Replace the hardcoded "Ingredients" string passed to IngredientsList in
DishDetailsModalPage with a localized string lookup (e.g., use your i18n
translate function such as t('dishDetails.ingredients') or a locale prop) so the
title comes from translations; update the IngredientsList invocation to:
title={t('...')} (or equivalent) and add the corresponding translation key to
your locale files (refer to IngredientsList, dishIngredients, and
DishDetailsModalPage to locate the change).

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="features/trips/ui/components/FoodCard/components/DishItem/DishItem.logic.ts">

<violation number="1" location="features/trips/ui/components/FoodCard/components/DishItem/DishItem.logic.ts:4">
P2: Duplicate `require()` calls for the same dietary badge images across two logic files. The same three images (`gluten_free.png`, `vegan.png`, `vegetarian.png`) are required independently in `DishItem.logic.ts` and `DishDetailsModalPage.logic.ts`, creating a maintenance burden when adding/removing badges.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

export const useDishItemLogic = (searchTerm: string) => {
const { data, isLoading } = useGetWikimediaDishImage(searchTerm);
return { image: data?.url, isLoading };
const glutenFreeImage = require('@/features/core/ui/assets/images/gluten_free.png');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Duplicate require() calls for the same dietary badge images across two logic files. The same three images (gluten_free.png, vegan.png, vegetarian.png) are required independently in DishItem.logic.ts and DishDetailsModalPage.logic.ts, creating a maintenance burden when adding/removing badges.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At features/trips/ui/components/FoodCard/components/DishItem/DishItem.logic.ts, line 4:

<comment>Duplicate `require()` calls for the same dietary badge images across two logic files. The same three images (`gluten_free.png`, `vegan.png`, `vegetarian.png`) are required independently in `DishItem.logic.ts` and `DishDetailsModalPage.logic.ts`, creating a maintenance burden when adding/removing badges.</comment>

<file context>
@@ -1,6 +1,23 @@
-export const useDishItemLogic = (searchTerm: string) => {
-  const { data, isLoading } = useGetWikimediaDishImage(searchTerm);
-  return { image: data?.url, isLoading };
+const glutenFreeImage = require('@/features/core/ui/assets/images/gluten_free.png');
+const veganImage = require('@/features/core/ui/assets/images/vegan.png');
+const vegetarianImage = require('@/features/core/ui/assets/images/vegetarian.png');
</file context>

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 39 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="features/trips/ui/components/FoodCard/components/DishItem/DishItem.tsx">

<violation number="1" location="features/trips/ui/components/FoodCard/components/DishItem/DishItem.tsx:42">
P2: Badge images missing accessibilityLabel — dietary indicators have no accessible description for screen readers.</violation>
</file>

<file name="features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.tsx">

<violation number="1" location="features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.tsx:32">
P2: The ingredients section title is hardcoded in English, so it won’t localize with the rest of the modal.</violation>
</file>

<file name="convex/validators/Trips.ts">

<violation number="1" location="convex/validators/Trips.ts:57">
P2: `isVegan` is introduced as a required persisted field without a compatibility path, which can break mutations on existing trip documents missing that field.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@@ -3,15 +3,25 @@ import type { TypicalDish } from '@/features/trips/domain/entities/TypicalDish';
import { useDishItemLogic } from '@/features/trips/ui/components/FoodCard/components/DishItem/DishItem.logic';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Badge images missing accessibilityLabel — dietary indicators have no accessible description for screen readers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At features/trips/ui/components/FoodCard/components/DishItem/DishItem.tsx, line 42:

<comment>Badge images missing accessibilityLabel — dietary indicators have no accessible description for screen readers.</comment>

<file context>
@@ -21,8 +31,20 @@ export const DishItem: FC<DishItemProps> = ({ dish }) => {
+        />
+        {hasBadge && (
+          <View style={styles.badgeContainer}>
+            {isGlutenFree && <CustomImage source={glutenFreeImage} style={styles.badge} />}
+            {isVegan && <CustomImage source={veganImage} style={styles.badge} />}
+            {isVegetarian && <CustomImage source={vegetarianImage} style={styles.badge} />}
</file context>

) : (
<CustomImage source={typeof image === 'string' ? { uri: image } : image} style={styles.image} />
)}
<IngredientsList title="Ingredients" ingredients={dishIngredients} />
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The ingredients section title is hardcoded in English, so it won’t localize with the rest of the modal.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At features/trips/ui/pages/DishDetailsModalPage/DishDetailsModalPage.tsx, line 32:

<comment>The ingredients section title is hardcoded in English, so it won’t localize with the rest of the modal.</comment>

<file context>
@@ -0,0 +1,52 @@
+        ) : (
+          <CustomImage source={typeof image === 'string' ? { uri: image } : image} style={styles.image} />
+        )}
+        <IngredientsList title="Ingredients" ingredients={dishIngredients} />
+      </View>
+      <CustomText text={dishDescription} style={styles.description} />
</file context>

ingredients: v.array(v.string()),
isGlutenFree: v.boolean(),
isVegetarian: v.boolean(),
isVegan: v.boolean(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: isVegan is introduced as a required persisted field without a compatibility path, which can break mutations on existing trip documents missing that field.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At convex/validators/Trips.ts, line 57:

<comment>`isVegan` is introduced as a required persisted field without a compatibility path, which can break mutations on existing trip documents missing that field.</comment>

<file context>
@@ -54,6 +54,7 @@ export const TypicalDish = v.object({
   ingredients: v.array(v.string()),
   isGlutenFree: v.boolean(),
   isVegetarian: v.boolean(),
+  isVegan: v.boolean(),
 });
 
</file context>
Suggested change
isVegan: v.boolean(),
isVegan: v.optional(v.boolean()),

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.

[APP] Dish detail page

1 participant