Skip to content

feat: dynamic profile#123

Merged
izakdvlpr merged 25 commits into
developfrom
feat/dynamic-profile
Jul 8, 2026
Merged

feat: dynamic profile#123
izakdvlpr merged 25 commits into
developfrom
feat/dynamic-profile

Conversation

@izakdvlpr

Copy link
Copy Markdown
Contributor

Description

Acknowledgements

Screenshots

Proof showing the creation/modification is working as expected

izakdvlpr added 20 commits June 30, 2026 15:16
- Implemented Comments component for displaying and adding comments with user authentication.
- Added Markdown component for rendering comment content with proper formatting.
- Enhanced user dropdown to handle empty names gracefully.
- Refactored EpisodicContentModal to manage TV show progress and reviews with improved state management.
- Introduced StarRating component for visual representation of ratings.
- Updated RatingGroupAdvanced to streamline rating interactions and improve accessibility.
- Implemented UserMovieTab component for displaying movies with filters.
- Created UserSerieTab component for displaying series with similar filtering options.
- Added AboutCard, ActivityCard, and FavoritesCard components to enhance user overview.
- Introduced MedalsCard and StatisticsCard for displaying user achievements and statistics.
- Developed UserReviewsTab for managing user reviews with sorting and filtering.
- Added UserScreenshotsTab for showcasing game screenshots.
- Enhanced user profile header with follow/unfollow functionality and user statistics.
…data, and streamline user details layout. Delete unused routes for manga, movie, reviews, screenshots, and series.
- Refactored search page to utilize `useQueryState` for managing content type and search query.
- Introduced `SearchResults` component for better separation of concerns in the search results display.
- Added redirection logic in the search route to default to "anime" if no type is specified.
- Updated TV show details page to include new features such as user progress tracking, favorite toggling, and improved UI components.
- Integrated `ViteImage` for optimized image loading in both search and TV show details pages.
- Enhanced error handling and loading states across components.
- Improved accessibility and user experience with better state management and UI feedback.
… enhance user interface

- Implemented `useFavorites` and `useLists` hooks for managing favorites and lists with query support.
- Added `useRemoveFavorite` and `useUpdateList` mutations for modifying favorites and lists.
- Introduced `useUserReviews` and related hooks for handling user reviews with pagination and query support.
- Enhanced review management with new criteria and reaction handling.
- Updated API types to include new fields for lists, favorites, and reviews.
- Improved localization strings for user interface elements related to lists and reviews.
- Refactored image handling in components to use the new `Image` component from `@unpic/react`.
- Updated TV show and user detail pages to reflect new features and improved UI elements.
… handling

- Replace <img> with <Image> component from @unpic/react in GameModal and StillReading/StillWatching components for better image handling.
- Refactor ReviewModal to use react-hook-form with zod for form validation and state management.
- Remove unused state and handlers related to pros and cons in ReviewModal.
- Introduce SearchInput component for improved search functionality with clipboard support.
- Clean up Avatar component by removing unused AvatarImage export.
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@izakdvlpr, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d10d6dc2-592a-40aa-bb7e-990a7fe03259

📥 Commits

Reviewing files that changed from the base of the PR and between 18c760a and ceccdc5.

📒 Files selected for processing (79)
  • README.md
  • biome.json
  • src/components/pages/details/relations.tsx
  • src/components/pages/details/review.tsx
  • src/components/pages/home/cta.tsx
  • src/components/pages/home/demo.tsx
  • src/components/pages/home/features.tsx
  • src/components/pages/user/tabs/overview/activity-card.tsx
  • src/components/pages/user/tabs/overview/index.tsx
  • src/components/pages/user/tabs/overview/statistics-card.tsx
  • src/components/pages/user/user-profile-header.tsx
  • src/components/shared/cards/card.tsx
  • src/components/shared/cards/contributors.tsx
  • src/components/shared/comments/markdown.tsx
  • src/components/shared/modals/book.tsx
  • src/components/shared/modals/episodic-content.tsx
  • src/components/shared/modals/manga.tsx
  • src/components/shared/modals/movie.tsx
  • src/components/shared/modals/review.tsx
  • src/components/ui/editor.tsx
  • src/components/ui/empty.tsx
  • src/components/ui/field.tsx
  • src/components/ui/image-zoom.tsx
  • src/components/ui/input-group.tsx
  • src/components/ui/label.tsx
  • src/components/ui/popover.tsx
  • src/components/ui/select.tsx
  • src/components/ui/separator.tsx
  • src/components/ui/slider.tsx
  • src/components/ui/switch.tsx
  • src/components/ui/table.tsx
  • src/components/ui/tabs.tsx
  • src/components/ui/toggle.tsx
  • src/components/ui/tooltip.tsx
  • src/global.css
  • src/lib/i18n/config.ts
  • src/lib/tanstack-query.ts
  • src/main.tsx
  • src/providers/index.tsx
  • src/providers/tanstack-devtools-provider.tsx
  • src/routes/_authenticated.tsx
  • src/routes/_authenticated/billing.tsx
  • src/routes/_authenticated/settings.tsx
  • src/routes/anime/$slug.tsx
  • src/routes/anime/airing/index.tsx
  • src/routes/anime/recommendations/index.tsx
  • src/routes/anime/top/index.tsx
  • src/routes/anime/upcoming/index.tsx
  • src/routes/book/$slug.tsx
  • src/routes/book/trending/index.tsx
  • src/routes/book/upcoming/index.tsx
  • src/routes/credits.tsx
  • src/routes/donate.tsx
  • src/routes/feed.tsx
  • src/routes/game/$slug.tsx
  • src/routes/game/anticipated/index.tsx
  • src/routes/game/popular/index.tsx
  • src/routes/game/recent/index.tsx
  • src/routes/game/upcoming/index.tsx
  • src/routes/manga/$slug.tsx
  • src/routes/manga/publishing/index.tsx
  • src/routes/manga/recommendations/index.tsx
  • src/routes/manga/top/index.tsx
  • src/routes/manga/upcoming/index.tsx
  • src/routes/movie/$slug.tsx
  • src/routes/movie/airing/index.tsx
  • src/routes/movie/popular/index.tsx
  • src/routes/movie/trending/index.tsx
  • src/routes/movie/upcoming/index.tsx
  • src/routes/privacy-policy.tsx
  • src/routes/search.tsx
  • src/routes/tv/$slug.tsx
  • src/routes/tv/airing/index.tsx
  • src/routes/tv/popular/index.tsx
  • src/routes/tv/trending/index.tsx
  • src/routes/tv/upcoming/index.tsx
  • src/routes/user/$username/index.tsx
  • src/vite-env.d.ts
  • vite.config.ts
📝 Walkthrough

Walkthrough

This PR adds profile and social-data APIs, new user profile tabs, comments, favorites, lists, and reviews flows, and a TV details rewrite with episode/progress tracking. It also migrates image rendering from vite-image/AvatarImage to @unpic/react, refactors the root layout selection, simplifies shared cards, updates search and settings behavior, and removes legacy per-username routes.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main shift to a data-driven, dynamic user profile experience.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dynamic-profile

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.

@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: 3

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/routes/_authenticated/settings.tsx (1)

315-334: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Banner preview should use layout="fullWidth"

@unpic/react still uses the constrained layout here, so this banner can stay capped at 300px wide instead of filling the card like the other banner previews. Switch to layout="fullWidth" with an aspectRatio.

Proposed fix
                 <Image
                   className="size-full rounded-lg border-accent border object-cover"
                   src={session.data?.user?.profile.bannerUrl}
-                  width={300}
-                  height={220}
+                  layout="fullWidth"
+                  aspectRatio={300 / 220}
                   background={AVATAR_BLUR}
                   alt=""
                 />
src/routes/game/$slug.tsx (1)

602-609: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Empty alt on screenshot images.

Screenshots are real content, not decorative — alt="" hides them from screen readers/accessibility tooling.

♿ Suggested fix
                     <ImageZoom key={screenshot.checksum}>
-                      <Image src={screenshot.imageId} width={1920} height={1080} alt="" />
+                      <Image src={screenshot.imageId} width={1920} height={1080} alt={t("library:screenshotAlt")} />
                     </ImageZoom>
src/routes/anime/$slug.tsx (1)

555-570: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Render the fetched anime reviews here The tab still renders a hardcoded book-review placeholder instead of reviews.items, so the anime page can show unrelated content even when real reviews exist.

src/routes/movie/$slug.tsx (1)

462-476: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reviews tab still renders a hardcoded placeholder review instead of the fetched list. reviewsData is only used for the count/rating badge; this tab never maps the loaded reviews, so users will always see the same filler text. The dropped user prop is fine here because ReviewItem.user is optional.

♻️ Duplicate comments (6)
src/components/pages/user/tabs/movie.tsx (1)

1-83: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Same legacy-file/CardItem contract issue as game.tsx.

Slated for removal per the PR objectives yet still present with mock data, and passes rating/year/synopsis/mediaType (Lines 68-77) to the simplified CardItem, which no longer accepts those props.

src/components/pages/user/tabs/serie.tsx (1)

1-85: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Same legacy-file/CardItem contract issue as game.tsx.

Slated for removal per the PR objectives yet still present with mock data, and passes rating/year/synopsis/mediaType (Lines 70-79) to the simplified CardItem, which no longer accepts those props.

src/components/pages/user/tabs/manga.tsx (1)

1-99: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Same legacy-file/CardItem contract issue as game.tsx.

This file is slated for removal per the PR objectives ("Removal of hardcoded per-username tab route files") but is still present with hardcoded mock content, and passes rating/year/synopsis/mediaType (Lines 85-94) to the now-simplified CardItem, which only accepts title/url/imageURL/isAdult.

src/components/pages/feed/item.tsx (1)

29-35: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Empty alt on meaningful cover image.

Same as feed/entry.tsx: item.coverURL is meaningful content; alt="" hides it from assistive tech. Consider alt={item.title}.

src/components/shared/sidebar/still-reading.tsx (1)

26-32: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Empty alt on meaningful cover image.

The book cover here conveys meaningful info; alt="" marks it decorative to screen readers. Consider deriving alt text from item data (e.g., a title field) if available.

src/components/shared/sidebar/still-watching.tsx (1)

26-32: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Empty alt on meaningful cover image.

Same concern as still-reading.tsx: the show cover is meaningful content, not decorative.

🟡 Minor comments (21)
src/components/layouts/grid.tsx-30-30 (1)

30-30: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep auto-fit for the shared grid wrapper. src/components/layouts/grid.tsx:30 is reused by sparse views like user screenshots and search results, so auto-fill will reserve empty tracks and leave large gaps on wide screens instead of letting the cards stretch. Use a separate opt-in for infinite-scroll layouts if that spacing stability is intentional.

src/global.css-138-138 (1)

138-138: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add whitespace inside comment delimiters.

Stylelint flags missing whitespace after /* and before */.

🎨 Proposed fix
-/*Giving the thumb a gradient*/
+/* Giving the thumb a gradient */

Source: Linters/SAST tools

src/global.css-100-106 (1)

100-106: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix stylelint violations.

Static analysis flags two issues on the font-family declaration line: missing blank line before the declaration and unnecessary quotes around the font name.

🎨 Proposed fix
   body {
     `@apply` bg-background text-white;
-    font-family: "Archivo", sans-serif;
+
+    font-family: Archivo, sans-serif;
     font-optical-sizing: auto;

Source: Linters/SAST tools

src/global.css-99-115 (1)

99-115: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep a base style for bare linkssrc/components/shared/footer.tsx still renders several bare <a>/Link elements without classes. With Tailwind preflight, removing the global anchor rule leaves them styled like plain text, so they lose their link affordance.

src/lib/api.ts-131-142 (1)

131-142: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Make profile image fields nullable. profile.bannerUrl and profile.avatarUrl can be cleared to null by the backend, but this response type still marks them as required strings. Change them to string | null so the client matches the API contract.

🛠️ Suggested fix
     profile: {
       id: string;
       userId: string;
       color: string;
       language: string;
       timezone: string;
       about: string;
-      bannerUrl: string;
-      avatarUrl: string;
+      bannerUrl: string | null;
+      avatarUrl: string | null;
       createdAt: string;
       updatedAt: string;
     };
src/components/pages/user/user-profile-header.tsx-136-158 (1)

136-158: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Follow control defaults to "Follow" while follow-status is still loading.

followStatusQuery.data?.isFollowing is undefined during the initial fetch, so an already-followed user briefly sees the "Follow" button; a click then fires followUser on an existing relationship. Consider gating the buttons on followStatusQuery.isSuccess/isPending (or disabling until resolved). Also note L136 re-computes the isOwnProfile check already available above.

🛡️ Suggested guard
-          {session.data?.user && session.data?.user.id !== user.id && (
+          {sessionUser && !isOwnProfile && (
             <div className="flex items-center gap-3">
               <div className="flex items-center gap-2">
                 {followStatusQuery.data?.isFollowing ? (
                   <Button
                     variant="outline"
                     onClick={() => unfollowUserMutation.mutate(user.id)}
-                    disabled={followUserMutation.isPending || unfollowUserMutation.isPending}
+                    disabled={followStatusQuery.isPending || followUserMutation.isPending || unfollowUserMutation.isPending}
                   >
                     {t("user:unfollow")}
                   </Button>
                 ) : (
                   <Button
                     variant="secondary"
                     onClick={() => followUserMutation.mutate(user.id)}
-                    disabled={followUserMutation.isPending || unfollowUserMutation.isPending}
+                    disabled={followStatusQuery.isPending || followUserMutation.isPending || unfollowUserMutation.isPending}
                   >
                     {t("user:follow")}
                   </Button>
                 )}
src/components/pages/user/tabs/favorites.tsx-32-33 (1)

32-33: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Single mutation instance disables all favorite cards during any single removal.

removeFavorite is one useMutation instance shared by every rendered FavoriteCard. removeFavorite.isPending is true globally the moment any card's remove is clicked, so isRemoving disables/dims the remove button on every other favorite too, not just the one being removed.

🎯 Proposed fix: track the specific item being removed
+  const [removingMediaId, setRemovingMediaId] = useState<string | null>(null);
   const removeFavorite = useRemoveFavorite();
...
                   onRemove={
                     isOwner
-                      ? (favorite) =>
-                          removeFavorite.mutate({ contentType: favorite.contentType, mediaId: favorite.mediaId })
+                      ? (favorite) => {
+                          setRemovingMediaId(favorite.mediaId);
+                          removeFavorite.mutate(
+                            { contentType: favorite.contentType, mediaId: favorite.mediaId },
+                            { onSettled: () => setRemovingMediaId(null) },
+                          );
+                        }
                       : undefined
                   }
-                  isRemoving={removeFavorite.isPending}
+                  isRemoving={removeFavorite.isPending && removingMediaId === item.mediaId}

Also applies to: 76-83

src/components/pages/user/tabs/favorites.tsx-50-88 (1)

50-88: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

No distinct error state for favoritesQuery.

When the query fails, isLoading is false and groups is empty, so the UI silently falls into the "no favorites" empty state instead of surfacing the error.

src/components/pages/user/tabs/overview/activity-card.tsx-6-16 (1)

6-16: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Hardcoded "en-US" locale ignores current i18n language.

getDateSuffix, formatNumber, and the tooltip's date.toLocaleDateString("en-US", ...) all hard-code English (US) formatting, while the rest of the component uses react-i18next for localization. Non-English users will see English month names, English ordinal suffixes ("1st", "2nd"), and US-style number formatting in the heatmap tooltip.

💡 Suggested fix using current i18n language
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'react-i18next';
+import i18next from "i18next";

-function formatNumber(value: number) {
-	const formatter = new Intl.NumberFormat("en-US");
+function formatNumber(value: number) {
+	const formatter = new Intl.NumberFormat(i18next.language);
 
 	return formatter.format(value);
 }

And use date.toLocaleDateString(i18next.language, { month: "long" }) in renderRect, dropping the English-only ordinal suffix (or localizing it) for non-English locales.

Also applies to: 43-50

src/components/shared/comments/index.tsx-202-213 (1)

202-213: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Icon-only delete button lacks an accessible name.

The delete Button only renders a lucide:trash-2 icon with no aria-label, unlike similar icon-only buttons elsewhere in the codebase (e.g. comment-thread.tsx's "More options" button uses aria-label). Screen reader users can't tell what this control does.

♿ Suggested fix
             <Button
               variant="ghost"
               size="icon"
               type="button"
               onClick={onDelete}
               disabled={isDeleting}
+              aria-label={t("comments:delete")}
               className="size-6 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition-opacity text-muted-foreground hover:text-destructive"
             >
src/routes/book/$slug.tsx-400-402 (1)

400-402: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Lists tab now renders an empty grid with no content or empty-state message.

Previously showed a <ListItem /> placeholder; now it's just an empty <div>, which will look like a blank/broken section to users viewing the "lists" tab.

src/routes/book/$slug.tsx-89-95 (1)

89-95: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Set the cover image to fullWidth layout
@unpic/react defaults to constrained, so width={500} keeps this image capped around 500px even in the wider lg:w-1/3 column. Switch this to layout="fullWidth" with the same aspect ratio so the cover actually fills the container.

src/routes/game/$slug.tsx-208-212 (1)

208-212: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Hardcoded Portuguese alt text.

alt="Capa do jogo" is hardcoded in Portuguese while the rest of the file/app uses t(...) for all user-facing strings. This breaks localization consistency for non-Portuguese users.

🌐 Suggested fix
             <Image
               src={game.coverUrl || "/placeholder/cover.webp"}
               width={500}
               height={750}
-              alt="Capa do jogo"
+              alt={t("library:coverAlt", { title: game.name })}
               className="w-full h-auto object-cover"
             />
src/components/pages/feed/entry.tsx-29-29 (2)

29-29: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Empty alt on meaningful cover image.

item.coverURL is the show/movie/book poster — meaningful content, not decorative — but alt="" suppresses it from screen readers. Consider alt={item.title} (the avatar image below already uses a descriptive alt).

♿ Proposed fix
-        <Image src={item.coverURL} width={300} height={400} className="size-full object-contain rounded-l-2xl" alt="" />
+        <Image src={item.coverURL} width={300} height={400} className="size-full object-contain rounded-l-2xl" alt={item.title} />

Also applies to: 49-55


29-29: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use an explicit objectFit: "contain" override here. @unpic/react sets object-fit: cover inline by default, so the Tailwind object-contain class won’t apply on its own.

src/routes/anime/$slug.tsx-572-574 (1)

572-574: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove the empty “Lists” tab
The trigger shows a fixed (30) badge, but the tab panel renders an empty grid, so users hit a dead end. Hide the tab until it has data or populate the panel.

src/routes/movie/$slug.tsx-477-479 (1)

477-479: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove the stale Lists count
src/routes/movie/$slug.tsx:320,477-479 still shows Lists (30) even though the tab content is empty. Hide the count or wire it to real data.

src/routes/movie/index.tsx-79-85 (1)

79-85: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add a fallback for movie.backdropUrl in the carousel
src/lib/api.ts allows movie.backdropUrl to be null, but @unpic/react’s Image expects a string. Use the same placeholder guard used elsewhere in this file to avoid a broken image when a movie has no backdrop.

Suggested change
-                    src={movie.backdropUrl}
+                    src={movie.backdropUrl || "/placeholder/cover.webp"}
src/components/pages/details/review.tsx-203-269 (1)

203-269: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Non-interactive "likes" indicator styled as clickable.

Lines 239-243: the heart/likes block has cursor-pointer hover:text-red-500 transition-colors but no onClick, so it visually implies it's clickable while doing nothing.

🩹 Proposed fix
-            <div className="flex gap-1.5 items-center cursor-pointer hover:text-red-500 transition-colors">
+            <div className="flex gap-1.5 items-center">
               <Icon icon={"lucide:heart"} className="size-4" />
               <p className="text-muted-foreground min-w-6 text-center text-sm">{likes}</p>
             </div>
src/routes/tv/$slug.tsx-402-429 (1)

402-429: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Missing null-fallback for poster/rating in the "more options" dialog.

The main poster at line 343 falls back to "/placeholder/cover.webp" when item.posterUrl is null, but the dialog header's Image (403) and background (url("${item.posterUrl}"), 397) don't apply the same fallback. Similarly, rating (item.tgReviewScore, line 103) is rendered unguarded here (<span>{rating}</span>, line 419) with no gating, unlike the header rating block which is only shown when reviews.total >= 1 (542). A show without a poster/score would show a broken image and "undefined" here.

🩹 Proposed fix
                       <Image
-                        src={item.posterUrl}
+                        src={item.posterUrl || "/placeholder/cover.webp"}
                         width={112}
                         height={160}
                         alt="Cover"
                         className="w-28! h-40 shrink-0 object-cover rounded-lg shadow-2xl relative z-10 border-2 border-white/30"
                       />
                             <StarRating value={1} max={1} />
-                            <span>{rating}</span>
+                            <span>{rating ?? "—"}</span>
src/components/pages/details/list.tsx-30-45 (1)

30-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Unhandled falsy link in preview thumbnails.

listItemToLink(item) can return a falsy value (see list-items-modal.tsx, which explicitly does if (!link) return null; before rendering). Here, previews doesn't filter those out, so an item with no resolvable link renders an empty Avatar with link?.title (undefined) as fallback text — a blank avatar tile in the preview strip.

🩹 Proposed fix
-  const previews = list.listItems.slice(0, PREVIEW_COUNT).map((item) => ({ item, link: listItemToLink(item) }));
+  const previews = list.listItems
+    .map((item) => ({ item, link: listItemToLink(item) }))
+    .filter((preview) => !!preview.link)
+    .slice(0, PREVIEW_COUNT);
🧹 Nitpick comments (15)
src/routes/_authenticated.tsx (1)

6-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid any for session.

Typing session as any loses type safety on session.data?.session below and any future property access. Prefer inferring the type from context.auth.getSession()'s return.

♻️ Proposed fix
-    let session: any;
+    let session: Awaited<ReturnType<typeof context.auth.getSession>>;
src/components/shared/cards/card.tsx (1)

10-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stale props: rating, year, synopsis, mediaType are declared but never used.

The simplified CardItem only consumes title, url, imageURL, and isAdult, yet the interface still advertises rating/year/synopsis/mediaType, and several callers (UserBookTab, UserMovieTab, UserSerieTab, UserGameTab, UserMangaTab) still pass them. These are silently ignored, which is misleading. Either drop them from CardProps (and the callers) or render them.

♻️ Proposed trim of the contract
 interface CardProps {
   title: string;
   url: string;
   imageURL: string;
   isAdult?: boolean;
-  rating?: number;
-  year?: number;
-  synopsis?: string;
-  mediaType?: string;
 }
src/routes/search.tsx (1)

40-40: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Default query = "A" sits before required params and is effectively dead.

buildSearchUrl is only ever invoked as buildSearchUrl(contentType, debouncedQuery, filters, page), where debouncedQuery defaults to "" (from parseAsString.withDefault("")). Since an explicit "" is always passed, the "A" default never triggers, and placing a defaulted parameter before required ones is a code smell. Drop the default (or move it to a trailing optional) to avoid confusion.

src/lib/api.ts (1)

456-475: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused TV show review aliases in src/lib/api.ts. TVShowReview and GetTVShowReviewsResponse have no remaining call sites; the TV review hooks use ApiTypes.Review and ApiTypes.GetReviewsResponse instead.

src/components/shared/star-rating.tsx (1)

14-34: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add accessible semantics to the rating display.

The component renders max unlabeled star icons twice with no role/aria-label, so screen readers get no indication of the represented value (e.g., "3.5 out of 5"). Consider marking the icon rows as decorative and exposing the value on the wrapper.

♿ Suggested fix
   return (
-    <div className={cn("relative inline-flex", className)}>
-      <div className="flex">
+    <div
+      className={cn("relative inline-flex", className)}
+      role="img"
+      aria-label={`${value} out of ${max} stars`}
+    >
+      <div className="flex" aria-hidden="true">
         {Array.from({ length: max }).map((_, index) => (
src/components/shared/search-input.tsx (1)

16-24: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Silent failure on clipboard read error.

If readText() rejects (permission denied, insecure context, etc.), the error is only logged to the console — the user sees no feedback that the paste action did nothing.

src/components/pages/user/tabs/screenshots.tsx (1)

5-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Screenshots tab still renders hardcoded mock data.

gamesWithScreenshots is a static list, so this tab isn't yet dynamic despite the PR goal. This pairs with the hardcoded (0) screenshots count in the route. Fine if intentional for this iteration — flagging so it isn't shipped as "done."

Want me to open a follow-up issue to track wiring this to real user screenshot data?

src/components/pages/user/tabs/overview/medals-card.tsx (1)

10-28: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Dynamic translation keys built from medal.name have no fallback.

t(\medals:${name}.name`)/t(`medals:${name}.description`)derive the i18n key directly from the medal's DBnamefield. If a medal is added without a matching locale entry, i18next will render the raw key (e.g.medals:NewMedal.name`) to users instead of readable text.

💡 Suggested fix: fallback to raw medal name
-        <div className="font-semibold">{t(`medals:${name}.name`)}</div>
-        <div className="text-xs text-muted-foreground">{t(`medals:${name}.description`)}</div>
+        <div className="font-semibold">{t(`medals:${name}.name`, { defaultValue: name })}</div>
+        <div className="text-xs text-muted-foreground">{t(`medals:${name}.description`, { defaultValue: "" })}</div>
src/components/shared/comments/index.tsx (1)

45-48: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Deprecated positional message argument.

.min(1, t(...))/.max(MAX_LENGTH, t(...)) use zod v3's positional message string, which zod 4 keeps only for backward compatibility (deprecated in favor of the unified error param, e.g. .min(1, { error: t(...) })). Not urgent since it currently works.

♻️ Suggested modernization
-        content: z.string().trim().min(1, t("comments:required")).max(MAX_LENGTH, t("comments:tooLong")),
+        content: z.string().trim().min(1, { error: t("comments:required") }).max(MAX_LENGTH, { error: t("comments:tooLong") }),
src/routes/_authenticated/settings.tsx (1)

247-253: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Empty alt text on meaningful avatar/banner images.

Both preview images represent the user's actual avatar/banner content, not decorative filler, so an empty alt="" hides useful information from screen reader users.

Also applies to: 328-334

src/components/pages/details/cast.tsx (1)

1-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider reusing getInitialsFromName for the avatar fallback.

AvatarFallback renders the full name string rather than initials, unlike the existing pattern in user-dropdown.tsx which uses getInitialsFromName. A full name inside a small circular/rounded avatar can overflow or wrap awkwardly, and duplicates avatar-fallback logic that already exists elsewhere in the codebase.

♻️ Proposed fix
+import { getInitialsFromName } from "`@/lib/utils`"; // adjust import path as needed
...
-            <AvatarFallback className="rounded-lg">{name}</AvatarFallback>
+            <AvatarFallback className="rounded-lg">{getInitialsFromName(name)}</AvatarFallback>
src/routes/anime/$slug.tsx (1)

116-119: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider priority for the above-the-fold cover image.

@unpic/react's Image By default, the generated images use lazy loading and asynchronous decoding. This main poster is likely the LCP element on this page; consider adding priority to eagerly load it and improve LCP.

src/components/shared/header/user-dropdown.tsx (1)

66-71: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove commented-out notifications entry instead of leaving dead code.

If notifications are being disabled intentionally, delete the block (and the now-possibly-unused common:notifications locale key) rather than commenting it out. If this is meant to be temporary, consider a feature flag instead of dead code.

src/components/pages/details/review.tsx (1)

110-169: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Untyped route cast and unguarded fallback branch.

to={/${routeName}/${entityId} as any} bypasses TanStack Router's typed-route checking, and the branch is entered whenever reviewName is truthy OR user is falsy — but nothing verifies routeName/entityId are actually defined in that case, which would produce a link to /undefined/undefined. Not exercised by the current caller (which always passes user), but worth tightening before this component is reused (e.g., on a profile reviews tab with reviewName).

♻️ Suggested guard
-            ) : (
+            ) : routeName && entityId ? (
               <Link
                 to={`/${routeName}/${entityId}` as any}
                 className="min-w-0 w-auto shrink-0"
               >
                 <p className="font-bold truncate text-sm sm:text-base max-w-48">{reviewName}</p>
               </Link>
-            )}
+            ) : null}
src/components/shared/modals/review.tsx (1)

34-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Loose dynamic zod schema forces an as any escape hatch.

createReviewSchema types its shape as Record<string, z.ZodTypeAny>, which erases per-field types (e.g. recommended boolean vs rating strings). This forces resolver: zodResolver(schema) as any at Line 97, silently disabling type-checking between the schema, ReviewFormData, and the submit payload built at Lines 110-124 — a typo in criterion.field here would compile but fail silently at runtime.

Consider typing the shape more precisely (e.g. building a ZodRawShape with a mapped type keyed by criteria[number]['field']) so the resolver's inferred type actually matches ReviewFormData without the cast.

Also applies to: 96-99


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eb749981-8bb3-4515-a3aa-9e82d1fa5d7d

📥 Commits

Reviewing files that changed from the base of the PR and between 0bf79b7 and f7c8443.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (105)
  • package.json
  • src/components/layouts/grid.tsx
  • src/components/layouts/home.tsx
  • src/components/layouts/index.tsx
  • src/components/layouts/main.tsx
  • src/components/layouts/user.tsx
  • src/components/pages/details/cast.tsx
  • src/components/pages/details/character.tsx
  • src/components/pages/details/list-items-modal.tsx
  • src/components/pages/details/list.tsx
  • src/components/pages/details/progress.tsx
  • src/components/pages/details/review.tsx
  • src/components/pages/feed/comment-thread.tsx
  • src/components/pages/feed/entry.tsx
  • src/components/pages/feed/item.tsx
  • src/components/pages/feed/listFollowing.tsx
  • src/components/pages/feed/review.tsx
  • src/components/pages/user/cast-favorite-card.tsx
  • src/components/pages/user/favorite-card.tsx
  • src/components/pages/user/medal-icon.tsx
  • src/components/pages/user/tabs/activity.tsx
  • src/components/pages/user/tabs/anime.tsx
  • src/components/pages/user/tabs/book.tsx
  • src/components/pages/user/tabs/favorites.tsx
  • src/components/pages/user/tabs/game.tsx
  • src/components/pages/user/tabs/lists.tsx
  • src/components/pages/user/tabs/manga.tsx
  • src/components/pages/user/tabs/movie.tsx
  • src/components/pages/user/tabs/overview/about-card.tsx
  • src/components/pages/user/tabs/overview/activity-card.tsx
  • src/components/pages/user/tabs/overview/favorite-card.tsx
  • src/components/pages/user/tabs/overview/favorites-card.tsx
  • src/components/pages/user/tabs/overview/index.tsx
  • src/components/pages/user/tabs/overview/medals-card.tsx
  • src/components/pages/user/tabs/overview/statistics-card.tsx
  • src/components/pages/user/tabs/reviews.tsx
  • src/components/pages/user/tabs/screenshots.tsx
  • src/components/pages/user/tabs/serie.tsx
  • src/components/pages/user/user-profile-header.tsx
  • src/components/shared/cards/card.tsx
  • src/components/shared/cards/contributors.tsx
  • src/components/shared/cards/core-team.tsx
  • src/components/shared/cards/screenshot.tsx
  • src/components/shared/comments/index.tsx
  • src/components/shared/comments/markdown.tsx
  • src/components/shared/header/user-dropdown.tsx
  • src/components/shared/loadings/filtered.tsx
  • src/components/shared/modals/episodic-content.tsx
  • src/components/shared/modals/game.tsx
  • src/components/shared/modals/review.tsx
  • src/components/shared/search-input.tsx
  • src/components/shared/sidebar/still-reading.tsx
  • src/components/shared/sidebar/still-watching.tsx
  • src/components/shared/star-rating.tsx
  • src/components/ui/avatar.tsx
  • src/components/ui/rating-group-advanced.tsx
  • src/global.css
  • src/hooks/comment.ts
  • src/hooks/favorite.ts
  • src/hooks/list.ts
  • src/hooks/review.ts
  • src/lib/api.ts
  • src/lib/i18n/locales/en-US/comments.json
  • src/lib/i18n/locales/en-US/common.json
  • src/lib/i18n/locales/en-US/feed.json
  • src/lib/i18n/locales/en-US/library.json
  • src/lib/i18n/locales/en-US/settings.json
  • src/lib/i18n/locales/en-US/user.json
  • src/lib/image.ts
  • src/lib/tanstack-query.ts
  • src/lib/utils/list-item.ts
  • src/routeTree.gen.ts
  • src/routes/__root.tsx
  • src/routes/_authenticated.tsx
  • src/routes/_authenticated/settings.tsx
  • src/routes/anime/$slug.tsx
  • src/routes/anime/index.tsx
  • src/routes/book/$slug.tsx
  • src/routes/book/franchises/$slug.tsx
  • src/routes/book/index.tsx
  • src/routes/game/$slug.tsx
  • src/routes/game/franchises/$slug.tsx
  • src/routes/game/index.tsx
  • src/routes/index.tsx
  • src/routes/list/$slug.tsx
  • src/routes/manga/$slug.tsx
  • src/routes/manga/index.tsx
  • src/routes/movie/$slug.tsx
  • src/routes/movie/franchises/$slug.tsx
  • src/routes/movie/index.tsx
  • src/routes/post/$id.tsx
  • src/routes/review/$id.tsx
  • src/routes/search.tsx
  • src/routes/tv/$slug.tsx
  • src/routes/tv/index.tsx
  • src/routes/user/$username/anime/index.tsx
  • src/routes/user/$username/book/index.tsx
  • src/routes/user/$username/game/index.tsx
  • src/routes/user/$username/index.tsx
  • src/routes/user/$username/manga/index.tsx
  • src/routes/user/$username/movie/index.tsx
  • src/routes/user/$username/reviews/index.tsx
  • src/routes/user/$username/serie/index.tsx
  • src/vite-env.d.ts
  • vite.config.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

💤 Files with no reviewable changes (16)
  • src/components/layouts/home.tsx
  • src/components/pages/user/favorite-card.tsx
  • src/components/layouts/main.tsx
  • src/routes/user/$username/movie/index.tsx
  • src/components/pages/user/medal-icon.tsx
  • src/components/shared/loadings/filtered.tsx
  • src/routes/user/$username/manga/index.tsx
  • src/components/layouts/user.tsx
  • src/vite-env.d.ts
  • src/routes/user/$username/anime/index.tsx
  • src/routes/user/$username/book/index.tsx
  • src/routes/user/$username/serie/index.tsx
  • vite.config.ts
  • src/routes/user/$username/game/index.tsx
  • src/routes/user/$username/reviews/index.tsx
  • src/routeTree.gen.ts

Comment on lines +243 to +262
const saveReviewMutation = useMutation({
mutationFn: (data: ReviewFormData) => {
const body = {
tvShowId,
overall: Number(data.overall),
direction: Number(data.direction) || undefined,
production: Number(data.production) || undefined,
acting: Number(data.acting) || undefined,
summary: data.summary.trim() || undefined,
notes: data.notes.trim() || undefined,
story: data.story.trim() || undefined,
recommended: data.recommended,
};

const existing = reviewQuery.data;

return existing
? api.patch(`${apiEndpoints.tvShowReview}/${existing.id}`, body)
: api.post(apiEndpoints.tvShowReview, body);
},

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Clearing a criterion rating to 0 is silently dropped from the update payload.

Number(data.direction) || undefined (and the same for production/acting) treats a valid 0 as absent, since 0 is falsy. Because RatingGroupAdvanced supports allowClear (Lines 560-617), a user clearing one of these ratings will have the field entirely omitted from the request — leaving the stale previous value on the server instead of resetting it. overall doesn't have this issue since it's sent unconditionally.

🐛 Proposed fix
         overall: Number(data.overall),
-        direction: Number(data.direction) || undefined,
-        production: Number(data.production) || undefined,
-        acting: Number(data.acting) || undefined,
+        direction: Number(data.direction),
+        production: Number(data.production),
+        acting: Number(data.acting),
📝 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
const saveReviewMutation = useMutation({
mutationFn: (data: ReviewFormData) => {
const body = {
tvShowId,
overall: Number(data.overall),
direction: Number(data.direction) || undefined,
production: Number(data.production) || undefined,
acting: Number(data.acting) || undefined,
summary: data.summary.trim() || undefined,
notes: data.notes.trim() || undefined,
story: data.story.trim() || undefined,
recommended: data.recommended,
};
const existing = reviewQuery.data;
return existing
? api.patch(`${apiEndpoints.tvShowReview}/${existing.id}`, body)
: api.post(apiEndpoints.tvShowReview, body);
},
const saveReviewMutation = useMutation({
mutationFn: (data: ReviewFormData) => {
const body = {
tvShowId,
overall: Number(data.overall),
direction: Number(data.direction),
production: Number(data.production),
acting: Number(data.acting),
summary: data.summary.trim() || undefined,
notes: data.notes.trim() || undefined,
story: data.story.trim() || undefined,
recommended: data.recommended,
};
const existing = reviewQuery.data;
return existing
? api.patch(`${apiEndpoints.tvShowReview}/${existing.id}`, body)
: api.post(apiEndpoints.tvShowReview, body);
},

Comment on lines 314 to 339
const handleSave = async () => {
if (selectedStatus) {
onSaveSuccess?.(selectedStatus);
const progress = progressForm.getValues();

if (!tvShowId || !progress.status) {
onClose?.();
return;
}

try {
if (!(await progressForm.trigger())) return;

await saveProgressMutation.mutateAsync(progress);

const review = reviewForm.getValues();

if (progress.status === "completed" && Number(review.overall) > 0) {
if (!(await reviewForm.trigger())) return;

await saveReviewMutation.mutateAsync(review);
}

onClose?.();
} catch {
toast.error(t("api:INTERNAL_SERVER_ERROR"));
}
};

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Partial failure is reported as total failure.

If saveProgressMutation succeeds but the subsequent saveReviewMutation throws, the catch block shows a single generic error toast and keeps the dialog open — but progress was already persisted. The user has no way to tell that part of their change succeeded, and may re-submit or think the whole save failed.

Consider surfacing which step failed (e.g. distinct toasts for progress vs review save), or handling the review-save failure independently so the dialog can still close/report the progress-save success.

Comment on lines +106 to +152
const interactive = !disabled && !readOnly;

return (
<div {...props}>
{allowHalf ? (
<ToggleGroup
type="multiple"
value={[value]}
onValueChange={(groupValue: string[]) => onValueChange?.(groupValue[groupValue.length - 1] || "0")}
{...sharedToggleProps}
>
{renderItems()}
</ToggleGroup>
) : (
<ToggleGroup
type="single"
value={value}
onValueChange={(groupValue: string) => onValueChange?.(groupValue || "0")}
{...sharedToggleProps}
>
{renderItems()}
</ToggleGroup>
)}
// biome-ignore lint/a11y/noStaticElementInteractions: hover preview reset only, zones are focusable buttons
<div {...props} className={cn("flex w-fit items-center", className)} onMouseLeave={handleMouseLeave}>
{indices.map((index) => {
const fillPercent = getFillPercent(index);

return (
<div key={index} className={cn("relative", disabled && "opacity-50")}>
{/* Single stable structure: empty base + filled overlay clipped by width. */}
<EmptyIcon className={cn(sizeClasses[size], colors.empty)} />
<div className="absolute inset-0 overflow-hidden" style={{ width: `${fillPercent}%` }}>
<FilledIcon className={cn(sizeClasses[size], colors.filled)} />
</div>

{/* Hover/click zones layered on top. */}
{interactive &&
(allowHalf ? (
<>
<button
type="button"
aria-label={`${index - 0.5} rating`}
className="absolute inset-y-0 left-0 w-1/2 z-10 cursor-pointer"
onMouseEnter={() => handleMouseEnter(index, true)}
onClick={() => handleClick(index, true)}
/>
<button
type="button"
aria-label={`${index} rating`}
className="absolute inset-y-0 right-0 w-1/2 z-10 cursor-pointer"
onMouseEnter={() => handleMouseEnter(index, false)}
onClick={() => handleClick(index, false)}
/>
</>
) : (
<button
type="button"
aria-label={`${index} rating`}
className="absolute inset-0 z-10 cursor-pointer"
onMouseEnter={() => handleMouseEnter(index)}
onClick={() => handleClick(index)}
/>
))}
</div>
);
})}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Missing visible focus state on the new hover/click zone buttons.

Replacing ToggleGroup with raw buttons drops Radix's built-in keyboard/ARIA semantics (roving tabindex, role="radiogroup"), and none of the new button zones (lines 126-149) have a focus-visible ring/outline class — compare with the clipboard button in src/components/shared/search-input.tsx (line 38), which explicitly adds focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary. If this app's base styles reset default outlines, keyboard users get no visible indicator while tabbing through up to 2×max stops for an interactive rating input.

♿ Suggested fix
                   <button
                     type="button"
                     aria-label={`${index - 0.5} rating`}
-                    className="absolute inset-y-0 left-0 w-1/2 z-10 cursor-pointer"
+                    className="absolute inset-y-0 left-0 w-1/2 z-10 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded"
                     onMouseEnter={() => handleMouseEnter(index, true)}
                     onClick={() => handleClick(index, true)}
                   />
                   <button
                     type="button"
                     aria-label={`${index} rating`}
-                    className="absolute inset-y-0 right-0 w-1/2 z-10 cursor-pointer"
+                    className="absolute inset-y-0 right-0 w-1/2 z-10 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded"
                     onMouseEnter={() => handleMouseEnter(index, false)}
                     onClick={() => handleClick(index, 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
const interactive = !disabled && !readOnly;
return (
<div {...props}>
{allowHalf ? (
<ToggleGroup
type="multiple"
value={[value]}
onValueChange={(groupValue: string[]) => onValueChange?.(groupValue[groupValue.length - 1] || "0")}
{...sharedToggleProps}
>
{renderItems()}
</ToggleGroup>
) : (
<ToggleGroup
type="single"
value={value}
onValueChange={(groupValue: string) => onValueChange?.(groupValue || "0")}
{...sharedToggleProps}
>
{renderItems()}
</ToggleGroup>
)}
// biome-ignore lint/a11y/noStaticElementInteractions: hover preview reset only, zones are focusable buttons
<div {...props} className={cn("flex w-fit items-center", className)} onMouseLeave={handleMouseLeave}>
{indices.map((index) => {
const fillPercent = getFillPercent(index);
return (
<div key={index} className={cn("relative", disabled && "opacity-50")}>
{/* Single stable structure: empty base + filled overlay clipped by width. */}
<EmptyIcon className={cn(sizeClasses[size], colors.empty)} />
<div className="absolute inset-0 overflow-hidden" style={{ width: `${fillPercent}%` }}>
<FilledIcon className={cn(sizeClasses[size], colors.filled)} />
</div>
{/* Hover/click zones layered on top. */}
{interactive &&
(allowHalf ? (
<>
<button
type="button"
aria-label={`${index - 0.5} rating`}
className="absolute inset-y-0 left-0 w-1/2 z-10 cursor-pointer"
onMouseEnter={() => handleMouseEnter(index, true)}
onClick={() => handleClick(index, true)}
/>
<button
type="button"
aria-label={`${index} rating`}
className="absolute inset-y-0 right-0 w-1/2 z-10 cursor-pointer"
onMouseEnter={() => handleMouseEnter(index, false)}
onClick={() => handleClick(index, false)}
/>
</>
) : (
<button
type="button"
aria-label={`${index} rating`}
className="absolute inset-0 z-10 cursor-pointer"
onMouseEnter={() => handleMouseEnter(index)}
onClick={() => handleClick(index)}
/>
))}
</div>
);
})}
const interactive = !disabled && !readOnly;
return (
// biome-ignore lint/a11y/noStaticElementInteractions: hover preview reset only, zones are focusable buttons
<div {...props} className={cn("flex w-fit items-center", className)} onMouseLeave={handleMouseLeave}>
{indices.map((index) => {
const fillPercent = getFillPercent(index);
return (
<div key={index} className={cn("relative", disabled && "opacity-50")}>
{/* Single stable structure: empty base + filled overlay clipped by width. */}
<EmptyIcon className={cn(sizeClasses[size], colors.empty)} />
<div className="absolute inset-0 overflow-hidden" style={{ width: `${fillPercent}%` }}>
<FilledIcon className={cn(sizeClasses[size], colors.filled)} />
</div>
{/* Hover/click zones layered on top. */}
{interactive &&
(allowHalf ? (
<>
<button
type="button"
aria-label={`${index - 0.5} rating`}
className="absolute inset-y-0 left-0 w-1/2 z-10 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded"
onMouseEnter={() => handleMouseEnter(index, true)}
onClick={() => handleClick(index, true)}
/>
<button
type="button"
aria-label={`${index} rating`}
className="absolute inset-y-0 right-0 w-1/2 z-10 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded"
onMouseEnter={() => handleMouseEnter(index, false)}
onClick={() => handleClick(index, false)}
/>
</>
) : (
<button
type="button"
aria-label={`${index} rating`}
className="absolute inset-0 z-10 cursor-pointer"
onMouseEnter={() => handleMouseEnter(index)}
onClick={() => handleClick(index)}
/>
))}
</div>
);
})}

@socket-security

socket-security Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​unpic/​placeholder@​0.1.28810010079100
Added@​unpic/​react@​1.0.29910010081100
Addedremark-gfm@​4.0.19910010083100
Addedreact-markdown@​10.1.09910010083100
Added@​uiw/​react-heat-map@​2.3.49410010088100

View full report

@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: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/routes/movie/$slug.tsx (1)

4-4: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

No cover fallback + unused Image import.

Unlike manga/book/anime routes, this file never falls back to a placeholder when movie.posterUrl is missing (coverUrl = movie.imageUrl || "/placeholder/cover.webp" pattern is absent) — both the sidebar <img> and MoreOptionsDialog's coverUrl use movie.posterUrl directly, producing a broken image if it's null. Also Image from @unpic/react is imported but unused.

Suggested fix
+  const coverUrl = movie.posterUrl || "/placeholder/cover.webp";
+
   const sidebar = (
     <>
       <div className="mb-2 w-full h-auto mx-auto shadow-xl rounded-lg overflow-hidden">
-        <img src={movie.posterUrl} alt={`${movie.title} Cover`} className="w-full h-auto object-cover" />
+        <img src={coverUrl} alt={`${movie.title} Cover`} className="w-full h-auto object-cover" />
       </div>

Also applies to: 94-98, 133-142

src/routes/anime/$slug.tsx (2)

107-109: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

handleToggle is a console.log stub; mySeason state never updates.

AnimeEpisodeProgress calls onToggle, but the handler only logs and the paired setter is named _setMySeason (never called), so episode toggling is non-functional. If this is intentionally a WIP stub, consider a // TODO marker; happy to wire this up if useful.

Also applies to: 392-392


33-52: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add notFoundComponent to the anime route — missing slugs will fall back to the generic error page instead of the app’s 404 UI, unlike the book/manga/movie routes.

🧹 Nitpick comments (4)
src/routes/manga/$slug.tsx (2)

4-4: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Image imported but never used — cover still rendered with raw <img>.

Other shared components in this PR (ListItem, CastItem, CharacterItem, ReviewItem) all use @unpic/react's Image for optimized/lazy-loaded rendering, but this route's cover art (the most prominent image on the page) still uses a plain <img>. Either wire the cover to Image for consistency/perf, or drop the unused import.

Suggested fix
-        <img src={coverUrl} alt={`${manga.title} Cover`} className="w-full h-auto object-cover" />
+        <Image src={coverUrl} width={400} height={600} alt={`${manga.title} Cover`} className="w-full h-auto object-cover" />

Also applies to: 92-98


163-236: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Large inline IIFE duplicated verbatim in anime/$slug.tsx.

This external-links-building logic (official/instagram/facebook/twitter/ann/wikipedia detection) is copy-pasted identically in the anime route. Extract into a shared helper (e.g. buildExternalLinks(external, malId)) to avoid drift between the two.

src/routes/book/$slug.tsx (1)

4-4: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Unused Image import — cover rendered with raw <img> instead.

Same pattern as the other details routes: Image from @unpic/react is imported but the cover art (and only the cover) still uses a plain <img>.

Also applies to: 92-94

src/routes/anime/$slug.tsx (1)

4-4: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Unused Image import — cover rendered with raw <img>.

Consistent with the same finding in manga/book/movie routes.

Also applies to: 120-122


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 34da6f69-072e-4d2a-b368-a58fc127963b

📥 Commits

Reviewing files that changed from the base of the PR and between f7c8443 and 18c760a.

📒 Files selected for processing (11)
  • src/components/shared/cards/card.tsx
  • src/components/shared/modals/game.tsx
  • src/components/shared/sidebar/still-watching.tsx
  • src/lib/i18n/locales/en-US/common.json
  • src/lib/i18n/locales/en-US/user.json
  • src/routes/anime/$slug.tsx
  • src/routes/book/$slug.tsx
  • src/routes/game/$slug.tsx
  • src/routes/manga/$slug.tsx
  • src/routes/movie/$slug.tsx
  • src/routes/tv/$slug.tsx
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • TrackGeek/api (manual) → reviewed against branch feat/dynamic-profile instead of the default branch
💤 Files with no reviewable changes (1)
  • src/components/shared/modals/game.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/lib/i18n/locales/en-US/user.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/lib/i18n/locales/en-US/common.json
  • src/components/shared/sidebar/still-watching.tsx
  • src/components/shared/cards/card.tsx
  • src/routes/tv/$slug.tsx

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/routes/movie/$slug.tsx (1)

4-4: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

No cover fallback + unused Image import.

Unlike manga/book/anime routes, this file never falls back to a placeholder when movie.posterUrl is missing (coverUrl = movie.imageUrl || "/placeholder/cover.webp" pattern is absent) — both the sidebar <img> and MoreOptionsDialog's coverUrl use movie.posterUrl directly, producing a broken image if it's null. Also Image from @unpic/react is imported but unused.

Suggested fix
+  const coverUrl = movie.posterUrl || "/placeholder/cover.webp";
+
   const sidebar = (
     <>
       <div className="mb-2 w-full h-auto mx-auto shadow-xl rounded-lg overflow-hidden">
-        <img src={movie.posterUrl} alt={`${movie.title} Cover`} className="w-full h-auto object-cover" />
+        <img src={coverUrl} alt={`${movie.title} Cover`} className="w-full h-auto object-cover" />
       </div>

Also applies to: 94-98, 133-142

src/routes/anime/$slug.tsx (2)

107-109: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

handleToggle is a console.log stub; mySeason state never updates.

AnimeEpisodeProgress calls onToggle, but the handler only logs and the paired setter is named _setMySeason (never called), so episode toggling is non-functional. If this is intentionally a WIP stub, consider a // TODO marker; happy to wire this up if useful.

Also applies to: 392-392


33-52: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add notFoundComponent to the anime route — missing slugs will fall back to the generic error page instead of the app’s 404 UI, unlike the book/manga/movie routes.

🧹 Nitpick comments (4)
src/routes/manga/$slug.tsx (2)

4-4: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Image imported but never used — cover still rendered with raw <img>.

Other shared components in this PR (ListItem, CastItem, CharacterItem, ReviewItem) all use @unpic/react's Image for optimized/lazy-loaded rendering, but this route's cover art (the most prominent image on the page) still uses a plain <img>. Either wire the cover to Image for consistency/perf, or drop the unused import.

Suggested fix
-        <img src={coverUrl} alt={`${manga.title} Cover`} className="w-full h-auto object-cover" />
+        <Image src={coverUrl} width={400} height={600} alt={`${manga.title} Cover`} className="w-full h-auto object-cover" />

Also applies to: 92-98


163-236: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Large inline IIFE duplicated verbatim in anime/$slug.tsx.

This external-links-building logic (official/instagram/facebook/twitter/ann/wikipedia detection) is copy-pasted identically in the anime route. Extract into a shared helper (e.g. buildExternalLinks(external, malId)) to avoid drift between the two.

src/routes/book/$slug.tsx (1)

4-4: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Unused Image import — cover rendered with raw <img> instead.

Same pattern as the other details routes: Image from @unpic/react is imported but the cover art (and only the cover) still uses a plain <img>.

Also applies to: 92-94

src/routes/anime/$slug.tsx (1)

4-4: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Unused Image import — cover rendered with raw <img>.

Consistent with the same finding in manga/book/movie routes.

Also applies to: 120-122


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 34da6f69-072e-4d2a-b368-a58fc127963b

📥 Commits

Reviewing files that changed from the base of the PR and between f7c8443 and 18c760a.

📒 Files selected for processing (11)
  • src/components/shared/cards/card.tsx
  • src/components/shared/modals/game.tsx
  • src/components/shared/sidebar/still-watching.tsx
  • src/lib/i18n/locales/en-US/common.json
  • src/lib/i18n/locales/en-US/user.json
  • src/routes/anime/$slug.tsx
  • src/routes/book/$slug.tsx
  • src/routes/game/$slug.tsx
  • src/routes/manga/$slug.tsx
  • src/routes/movie/$slug.tsx
  • src/routes/tv/$slug.tsx
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • TrackGeek/api (manual) → reviewed against branch feat/dynamic-profile instead of the default branch
💤 Files with no reviewable changes (1)
  • src/components/shared/modals/game.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/lib/i18n/locales/en-US/user.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/lib/i18n/locales/en-US/common.json
  • src/components/shared/sidebar/still-watching.tsx
  • src/components/shared/cards/card.tsx
  • src/routes/tv/$slug.tsx
🛑 Comments failed to post (19)
src/routes/anime/$slug.tsx (4)

306-306: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file map =="
fd -a 'slug.tsx' src/routes || true

echo
echo "== targeted snippets in anime route =="
nl -ba src/routes/anime/\$slug.tsx | sed -n '260,430p'

echo
echo "== search for hardcoded rating / lists / community stats patterns =="
rg -n '\b4\.2\b|\(30\)|CommunityStats|community stats|rating =|lists count|TabsTrigger value="lists"' src/routes src/components src -g '!**/node_modules/**' || true

echo
echo "== other media routes with same pattern =="
for f in src/routes/*/\$slug.tsx; do
  [ -f "$f" ] || continue
  echo "--- $f"
  rg -n '\b4\.2\b|\(30\)|CommunityStats|rating =' "$f" || true
done

Repository: TrackGeek/web

Length of output: 731


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== anime route around the cited lines =="
sed -n '280,430p' src/routes/anime/\$slug.tsx | awk '{printf "%4d  %s\n", NR+279, $0}'

echo
echo "== search for the cited literals across routes =="
rg -n '\b4\.2\b|\(30\)|CommunityStats|rating =|TabsTrigger value="lists"' src/routes src/components -g '!**/node_modules/**' || true

echo
echo "== sibling slug routes with same literals =="
for f in src/routes/book/\$slug.tsx src/routes/game/\$slug.tsx src/routes/manga/\$slug.tsx src/routes/movie/\$slug.tsx src/routes/tv/\$slug.tsx; do
  [ -f "$f" ] || continue
  echo "--- $f"
  rg -n '\b4\.2\b|\(30\)|CommunityStats|rating =' "$f" || true
done

Repository: TrackGeek/web

Length of output: 11207


Replace the hardcoded stats with route data. rating, the CommunityStats percentages, and the lists count are still fixed values here, and the same placeholder pattern is used in src/routes/book/$slug.tsx, src/routes/game/$slug.tsx, src/routes/manga/$slug.tsx, and src/routes/movie/$slug.tsx. That leaves the details pages showing incorrect media-specific info.


416-423: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Trailer <iframe> missing a sandbox attribute.

Same security concern as movie/$slug.tsx.

Suggested fix
             <iframe
               src={anime.trailer.embedUrl.replace("&autoplay=1", "")}
               allowFullScreen
+              sandbox="allow-scripts allow-same-origin allow-presentation allow-popups"
+              referrerPolicy="strict-origin-when-cross-origin"
               className="w-full aspect-video"
               title="Trailer"
             />
📝 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.

          {anime.trailer.embedUrl && (
            <iframe
              src={anime.trailer.embedUrl.replace("&autoplay=1", "")}
              allowFullScreen
              sandbox="allow-scripts allow-same-origin allow-presentation allow-popups"
              referrerPolicy="strict-origin-when-cross-origin"
              className="w-full aspect-video"
              title="Trailer"
            />
          )}
🧰 Tools
🪛 React Doctor (0.5.8)

[error] 417-417: An <iframe> with no sandbox is a security hole: the embedded page gets full access to your site.

Add sandbox="" or a curated value so embedded pages cannot get full access to your site by default.

(iframe-missing-sandbox)

Source: Linters/SAST tools


447-468: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- file context ---'
nl -ba src/routes/anime/\$slug.tsx | sed -n '380,520p'

echo
echo '--- ReviewItem usages in src/routes ---'
rg -n "ReviewItem|reviewsQuery|reviews\?\." src/routes -g '*.tsx' -g '*.ts' || true

Repository: TrackGeek/web

Length of output: 212


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- file context ---'
awk 'NR>=380 && NR<=520 {printf "%6d  %s\n", NR, $0}' src/routes/anime/\$slug.tsx

echo
echo '--- ReviewItem and reviewsQuery usages ---'
rg -n "ReviewItem|reviewsQuery|reviews\?\." src/routes -g '*.tsx' -g '*.ts' || true

Repository: TrackGeek/web

Length of output: 9279


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- anime query context ---'
awk 'NR>=60 && NR<=120 {printf "%6d  %s\n", NR, $0}' src/routes/anime/\$slug.tsx

echo
echo '--- hardcoded review placeholders across routes/components ---'
rg -n '"John Doe"|Very foda!|criteria|criteries|reviewText=' src/routes src/components -g '*.tsx' -g '*.ts' || true

Repository: TrackGeek/web

Length of output: 11761


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- anime query context ---'
awk 'NR>=60 && NR<=120 {printf "%6d  %s\n", NR, $0}' src/routes/anime/\$slug.tsx

echo
echo '--- hardcoded review placeholders across routes/components ---'
rg -n '"John Doe"|Very foda!|criteries|reviewText=' src/routes src/components -g '*.tsx' -g '*.ts' || true

Repository: TrackGeek/web

Length of output: 9359


Replace the hardcoded review stub — This tab still renders a static ReviewItem instead of the fetched review data, so users see unrelated content whenever reviews?.total >= 1. The same stub also appears in the game, movie, book, and manga routes.

🧰 Tools
🪛 GitHub Actions: Build Files / 0_Build.txt

[error] 452-452: tsc (TS2353): Object literal may only specify known properties; 'avatarURL' does not exist in type 'User'.

🪛 GitHub Actions: Build Files / Build

[error] 452-452: TS2353: Object literal may only specify known properties, and 'avatarURL' does not exist in type 'User'.

🪛 OpenGrep (1.23.0)

[ERROR] 452-452: Possible credit card number (PAN) detected in source code. Credit card numbers should never be hardcoded or stored in source files. Use a secrets manager or tokenization service instead.

(coderabbit.pii.credit-card-number)


469-473: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the affected route and the ListItem component definition/uses.
rg -n "ListItem" src/routes src/components src -g '!**/*.map' || true

printf '\n--- anime route ---\n'
sed -n '430,500p' src/routes/anime/\$slug.tsx

printf '\n--- ListItem component candidates ---\n'
fd -a "ListItem" src || true

Repository: TrackGeek/web

Length of output: 8056


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- src/components/pages/details/list.tsx ---'
sed -n '1,180p' src/components/pages/details/list.tsx

printf '\n%s\n' '--- other detail routes with ListItem ---'
sed -n '350,390p' src/routes/movie/\$slug.tsx
printf '\n'
sed -n '320,350p' src/routes/book/\$slug.tsx
printf '\n'
sed -n '380,420p' src/routes/manga/\$slug.tsx
printf '\n'
sed -n '450,500p' src/routes/game/\$slug.tsx

Repository: TrackGeek/web

Length of output: 9039


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Quick structural check that the Lists tab data is available in the route, or if the tab is placeholder-only.
ast-grep outline src/routes/anime/\$slug.tsx --view expanded | sed -n '1,220p'

Repository: TrackGeek/web

Length of output: 272


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path("src/routes/anime/$slug.tsx")
text = p.read_text()
for needle in ["list", "Lists", "useQuery", "ApiTypes.ListItem", "anime.list", "listsQuery", "useLists"]:
    print(f"\n== {needle} ==")
    for i, line in enumerate(text.splitlines(), 1):
        if needle in line:
            print(f"{i}: {line}")
PY

Repository: TrackGeek/web

Length of output: 1729


<ListItem /> is missing the required list prop here, so opening the Lists tab throws. The same fix is needed in src/routes/book/$slug.tsx, src/routes/game/$slug.tsx, src/routes/manga/$slug.tsx, and src/routes/movie/$slug.tsx.

🧰 Tools
🪛 GitHub Actions: Build Files / 0_Build.txt

[error] 471-471: tsc (TS2741): Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

🪛 GitHub Actions: Build Files / Build

[error] 471-471: TS2741: Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

src/routes/book/$slug.tsx (4)

178-183: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Literal placeholder text rendered directly to users.

series_names (can be hidden if not exists) is dev placeholder copy hardcoded into the JSX — it will be shown verbatim on every book's page.

Suggested fix
-      <div className="flex items-center gap-2">
-        <Icon icon={"lucide:book-copy"} className="size-5 text-muted-foreground" />
-        <Link to={"/"} search={{ landing: "true" }} className="text-xl text-muted-foreground">
-          series_names (can be hidden if not exists)
-        </Link>
-      </div>
+      {book.series?.name && (
+        <div className="flex items-center gap-2">
+          <Icon icon={"lucide:book-copy"} className="size-5 text-muted-foreground" />
+          <Link to={"/"} search={{ landing: "true" }} className="text-xl text-muted-foreground">
+            {book.series.name}
+          </Link>
+        </div>
+      )}
📝 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.

      {book.series?.name && (
        <div className="flex items-center gap-2">
          <Icon icon={"lucide:book-copy"} className="size-5 text-muted-foreground" />
          <Link to={"/"} search={{ landing: "true" }} className="text-xl text-muted-foreground">
            {book.series.name}
          </Link>
        </div>
      )}

226-308: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== outline =='
ast-grep outline src/routes/book/\$slug.tsx --view expanded || true

echo '== relevant search =='
rg -n --hidden -S 'book\.|isbn|genre|mood|warning|content warning|contentWarnings|communityStatistics|DetailsCard|CommunityStats' src/routes/book src/components src/lib src/types src -g '!**/node_modules/**' || true

Repository: TrackGeek/web

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== book route slice =='
sed -n '1,340p' src/routes/book/\$slug.tsx | cat -n

echo '== book model search =='
rg -n --hidden -S 'interface .*Book|type .*Book|export .*Book|genres:|isbn|contentWarnings|mood' src -g '!**/node_modules/**' | head -n 200

Repository: TrackGeek/web

Length of output: 23146


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== src/routes/book/$slug.tsx relevant lines =='
sed -n '1,340p' src/routes/book/\$slug.tsx | nl -ba | sed -n '1,340p'

echo '== book-related type/data references =='
rg -n --hidden -S '\bgenres\b|\bisbn\b|\bcontentWarnings\b|\bmood\b|\bwarnings\b|\bGenrePills\b' src/routes/book src/lib src/components -g '!**/node_modules/**' | head -n 120

Repository: TrackGeek/web

Length of output: 238


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== api endpoints/type slice =='
rg -n -A4 -B4 'getBookDetails|bookReview|interface .*Book|type .*Book|export type .*Book|books?:' src/lib/api.ts src -g '!**/node_modules/**' | head -n 200

echo '== route comparisons =='
sed -n '250,340p' src/routes/movie/\$slug.tsx | cat -n
sed -n '340,430p' src/routes/game/\$slug.tsx | cat -n
sed -n '560,700p' src/routes/tv/\$slug.tsx | cat -n

Repository: TrackGeek/web

Length of output: 28769


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== book detail types/interfaces =='
rg -n -A20 -B20 'GetBookDetailsResponse|bookDetail|book: \{|interface .*Book|type .*Book|export interface .*Book' src/lib/api.ts src -g '!**/node_modules/**' | head -n 200

echo '== book detail route references =='
rg -n --hidden -S 'getBookDetails\(|/book/detail/|hardcoverId|numberOfPages|releaseYear|contributions|imageUrl|description' src/routes/book src/lib -g '!**/node_modules/**' | head -n 200

Repository: TrackGeek/web

Length of output: 35052


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== book controller/service =='
rg -n -A30 -B10 'book/detail|BookDetail|book detail|isbn|genre|mood|warning|content warning|contentWarnings|genres' src/modules/book src/modules -g '!**/node_modules/**' | head -n 300

Repository: TrackGeek/api

Length of output: 21540


Replace the placeholder book metadata src/routes/book/$slug.tsx
The genre pills, ISBNs, mood, and content warnings are hardcoded and render the same for every book. Drive this from book metadata or hide the section until the API exposes those fields.


310-331: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- file outline ---\n'
ast-grep outline src/routes/book/\$slug.tsx --view expanded || true

printf '\n--- relevant lines ---\n'
sed -n '250,380p' src/routes/book/\$slug.tsx

printf '\n--- search review wiring ---\n'
rg -n "reviewsData|ReviewItem|reviews\.total|reviewText|criteries|TabsContent value=\"reviews\"" src/routes/book/\$slug.tsx src -g '!**/node_modules/**' || true

Repository: TrackGeek/web

Length of output: 242


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- file outline ---'
ast-grep outline src/routes/book/\$slug.tsx --view expanded || true

echo
echo '--- relevant lines ---'
sed -n '250,380p' src/routes/book/\$slug.tsx

echo
echo '--- search review wiring ---'
rg -n "reviewsData|ReviewItem|reviews\.total|reviewText|criteries|TabsContent value=\"reviews\"" src/routes/book/\$slug.tsx src -g '!**/node_modules/**' || true

Repository: TrackGeek/web

Length of output: 15982


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- book route query and review tab context ---'
sed -n '50,90p' src/routes/book/\$slug.tsx
echo
sed -n '170,225p' src/routes/book/\$slug.tsx
echo
echo '--- review item component props ---'
sed -n '1,220p' src/components/pages/details/review.tsx

Repository: TrackGeek/web

Length of output: 11695


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- bookReviews usage ---'
rg -n "bookReviews" src -g '!**/node_modules/**' || true

echo
echo '--- book review endpoint / types ---'
rg -n "bookReview|reviewsData|reviews\.total" src/lib src/routes src/hooks -g '!**/node_modules/**' || true

Repository: TrackGeek/web

Length of output: 3265


Render fetched book reviews instead of a static placeholder The reviews tab still mounts a single hardcoded ReviewItem (John Doe, fixed text, fixed scores) even though bookReview already returns PaginatedResponse<Review>. Map the loaded review items here so the tab reflects the current book.

🧰 Tools
🪛 GitHub Actions: Build Files / 0_Build.txt

[error] 315-315: tsc (TS2353): Object literal may only specify known properties; 'avatarURL' does not exist in type 'User'.

🪛 GitHub Actions: Build Files / Build

[error] 315-315: TS2353: Object literal may only specify known properties, and 'avatarURL' does not exist in type 'User'.

🪛 OpenGrep (1.23.0)

[ERROR] 315-315: Possible credit card number (PAN) detected in source code. Credit card numbers should never be hardcoded or stored in source files. Use a secrets manager or tokenization service instead.

(coderabbit.pii.credit-card-number)


332-336: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== list item definition ==\n'
rg -n "export .*ListItem|function ListItem|const ListItem" src | sed -n '1,80p'

printf '\n== book slug route around lists tab ==\n'
sed -n '300,360p' src/routes/book/\$slug.tsx

printf '\n== other ListItem usages in routes/details ==\n'
rg -n "<ListItem" src/routes src/components src -g '!**/*.map' | sed -n '1,120p'

Repository: TrackGeek/web

Length of output: 3949


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,140p' src/components/pages/details/list.tsx

Repository: TrackGeek/web

Length of output: 2913


Pass the required list prop to <ListItem /> — this component reads list._count.listItems, list.listItems, and list.user immediately, so rendering it bare will break the Lists tab.

🧰 Tools
🪛 GitHub Actions: Build Files / 0_Build.txt

[error] 334-334: tsc (TS2741): Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

🪛 GitHub Actions: Build Files / Build

[error] 334-334: TS2741: Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

src/routes/game/$slug.tsx (4)

263-267: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Render the actual game status.

This prints Early Access for every truthy game.status, so released/beta/other states will be mislabeled. Map the real status value instead of hardcoding one label.


343-343: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the actual list count here.

(30) is a static placeholder, so this badge will drift as soon as the backend count changes. Wire it to real list data instead.


441-441: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== game slug iframe context ==\n'
sed -n '400,470p' src/routes/game/\$slug.tsx

printf '\n== iframe / sandbox usage in web ==\n'
rg -n --glob '!**/node_modules/**' '<iframe|sandbox=' src . || true

Repository: TrackGeek/web

Length of output: 4284


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== videoId usages ==\n'
rg -n --glob '!**/node_modules/**' '\bvideoId\b' src

printf '\n== sibling iframe blocks ==\n'
sed -n '400,440p' src/routes/anime/\$slug.tsx
sed -n '320,360p' src/routes/movie/\$slug.tsx
sed -n '740,780p' src/routes/tv/\$slug.tsx

Repository: TrackGeek/web

Length of output: 6404


Add a sandbox to this iframe.
video.videoId points to a third-party YouTube embed, so this iframe currently gets more privileges than it needs. Add a restrictive sandbox and allow only the permissions required for playback.

🧰 Tools
🪛 React Doctor (0.5.8)

[error] 441-441: An <iframe> with no sandbox is a security hole: the embedded page gets full access to your site.

Add sandbox="" or a curated value so embedded pages cannot get full access to your site by default.

(iframe-missing-sandbox)

Source: Linters/SAST tools


454-472: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Render the fetched reviews instead of the placeholder card.

reviewsQuery is fetched, but this tab ignores it and always shows a hard-coded sample review. Users will never see the actual review data returned by the API.

🧰 Tools
🪛 GitHub Actions: Build Files / 0_Build.txt

[error] 458-458: tsc (TS2353): Object literal may only specify known properties; 'avatarURL' does not exist in type 'User'.

🪛 GitHub Actions: Build Files / Build

[error] 458-458: TS2353: Object literal may only specify known properties, and 'avatarURL' does not exist in type 'User'.

🪛 OpenGrep (1.23.0)

[ERROR] 458-458: Possible credit card number (PAN) detected in source code. Credit card numbers should never be hardcoded or stored in source files. Use a secrets manager or tokenization service instead.

(coderabbit.pii.credit-card-number)

src/routes/manga/$slug.tsx (3)

271-276: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file list =="
git ls-files src/routes/manga/\$slug.tsx

echo
echo "== relevant line ranges =="
sed -n '1,90p' src/routes/manga/\$slug.tsx
echo
sed -n '250,390p' src/routes/manga/\$slug.tsx

echo
echo "== search for hardcoded stats/list placeholders in this file =="
rg -n '4\.2|30\)|CommunityStats|community|lists \(' src/routes/manga/\$slug.tsx

Repository: TrackGeek/web

Length of output: 11100


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact definitions around the disputed values.
sed -n '40,80p' src/routes/manga/\$slug.tsx
echo
sed -n '340,380p' src/routes/manga/\$slug.tsx
echo
sed -n '260,285p' src/routes/manga/\$slug.tsx

Repository: TrackGeek/web

Length of output: 4367


Replace the hardcoded manga stats placeholders (src/routes/manga/$slug.tsx:56, 356-370, 276). rating = 4.2, the community statistics percentages, and the lists tab count are all static, so every manga page shows the same values. Wire them to real data or hide these fields until they’re available.


375-394: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the referenced route and nearby review-tab logic.
file='src/routes/manga/$slug.tsx'
if [ ! -f "$file" ]; then
  echo "Missing file: $file" >&2
  exit 1
fi

wc -l "$file"
sed -n '320,430p' "$file"

Repository: TrackGeek/web

Length of output: 4402


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether this route fetches or maps real review data elsewhere in the file.
file='src/routes/manga/$slug.tsx'
rg -n "reviews|ReviewItem|criteries|reviewText|John Doe" "$file"

Repository: TrackGeek/web

Length of output: 891


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the top of the file for data fetching and available review-related state.
file='src/routes/manga/$slug.tsx'
sed -n '1,220p' "$file"

Repository: TrackGeek/web

Length of output: 8816


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the top of the file for loaders, query data, and any review model usage.
file='src/routes/manga/$slug.tsx'
sed -n '1,220p' "$file"

Repository: TrackGeek/web

Length of output: 8816


Render the fetched manga reviews here.
This tab still shows a single hardcoded ReviewItem even though reviewsData is already loaded and the tab only appears when reviews.total >= 1; map over the returned reviews instead of showing placeholder content.

🧰 Tools
🪛 GitHub Actions: Build Files / 0_Build.txt

[error] 379-379: tsc (TS2353): Object literal may only specify known properties; 'avatarURL' does not exist in type 'User'.

🪛 GitHub Actions: Build Files / Build

[error] 379-379: TS2353: Object literal may only specify known properties, and 'avatarURL' does not exist in type 'User'.

🪛 OpenGrep (1.23.0)

[ERROR] 379-379: Possible credit card number (PAN) detected in source code. Credit card numbers should never be hardcoded or stored in source files. Use a secrets manager or tokenization service instead.

(coderabbit.pii.credit-card-number)


395-399: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching ListItem references:\n'
rg -n --glob '!node_modules' --glob '!dist' --glob '!build' 'ListItem' src | sed -n '1,120p'

printf '\nRelevant file slice: src/routes/manga/$slug.tsx\n'
sed -n '360,430p' src/routes/manga/\$slug.tsx

printf '\nPotential component definitions for ListItem:\n'
rg -n --glob '!node_modules' --glob '!dist' --glob '!build' 'export (default )?function ListItem|const ListItem|function ListItem|type ListItem|interface ListItem' src | sed -n '1,120p'

Repository: TrackGeek/web

Length of output: 6091


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,140p' src/components/pages/details/list.tsx

Repository: TrackGeek/web

Length of output: 2913


Pass the required list prop here <ListItem /> still renders without list, and ListItem immediately reads list._count, list.name, and list.user, so this tab will fail to render until a real ApiTypes.ListWithPreview is passed in.

🧰 Tools
🪛 GitHub Actions: Build Files / 0_Build.txt

[error] 397-397: tsc (TS2741): Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

🪛 GitHub Actions: Build Files / Build

[error] 397-397: TS2741: Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

src/routes/movie/$slug.tsx (4)

270-321: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the target file and nearby symbols first.
ast-grep outline src/routes/movie/\$slug.tsx --view expanded || true

# Inspect the relevant sections with line numbers.
sed -n '240,360p' src/routes/movie/\$slug.tsx

# Search for the hardcoded director name and community stats usage.
rg -n 'Tom Gormican|CommunityStats|movieCharacteristics|library:directors|progress|stats|percentage|percent' src/routes/movie/\$slug.tsx src -g '!**/node_modules/**' || true

Repository: TrackGeek/web

Length of output: 50369


Bind the director and community stats to movie data The DetailsCard always renders Tom Gormican, and CommunityStats uses fixed percentages. That makes every movie page show the same factual data; wire these fields to the actual movie/community response instead of literals.


341-348: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Trailer <iframe> missing a sandbox attribute.

An unsandboxed <iframe> embedding third-party content is a security hole; the embedded page gets full access to your origin. Add a curated sandbox value that still allows the YouTube player to function.

Suggested fix
             <iframe
               src={`https://youtube.com/embed/${movie.trailerId}`}
               allowFullScreen
+              sandbox="allow-scripts allow-same-origin allow-presentation allow-popups"
+              referrerPolicy="strict-origin-when-cross-origin"
               className="w-full aspect-video"
               title="Trailer"
             />
📝 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.

          {movie.trailerId && (
            <iframe
              src={`https://youtube.com/embed/${movie.trailerId}`}
              allowFullScreen
              sandbox="allow-scripts allow-same-origin allow-presentation allow-popups"
              referrerPolicy="strict-origin-when-cross-origin"
              className="w-full aspect-video"
              title="Trailer"
            />
          )}
🧰 Tools
🪛 React Doctor (0.5.8)

[error] 342-342: An <iframe> with no sandbox is a security hole: the embedded page gets full access to your site.

Add sandbox="" or a curated value so embedded pages cannot get full access to your site by default.

(iframe-missing-sandbox)

Source: Linters/SAST tools


350-369: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target route and nearby review-tab logic.
ast-grep outline src/routes/movie/\$slug.tsx --view expanded || true

echo '--- file excerpt ---'
sed -n '300,420p' src/routes/movie/\$slug.tsx

Repository: TrackGeek/web

Length of output: 4406


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the route for any review data flow or conditional rendering around the reviews tab.
rg -n "reviews|ReviewItem|criteries|reviewText|movie\." src/routes/movie/\$slug.tsx

echo '--- surrounding excerpt ---'
sed -n '1,220p' src/routes/movie/\$slug.tsx

Repository: TrackGeek/web

Length of output: 11803


Reviews tab still uses a hardcoded placeholder review
TabsContent value="reviews" should render the fetched reviews data instead of a static ReviewItem; right now it shows unrelated content even when real reviews exist.

🧰 Tools
🪛 GitHub Actions: Build Files / 0_Build.txt

[error] 354-354: tsc (TS2353): Object literal may only specify known properties; 'avatarURL' does not exist in type 'User'.

🪛 GitHub Actions: Build Files / Build

[error] 354-354: TS2353: Object literal may only specify known properties, and 'avatarURL' does not exist in type 'User'.

🪛 OpenGrep (1.23.0)

[ERROR] 354-354: Possible credit card number (PAN) detected in source code. Credit card numbers should never be hardcoded or stored in source files. Use a secrets manager or tokenization service instead.

(coderabbit.pii.credit-card-number)


370-374: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== movie route snippet ==\n'
sed -n '330,410p' src/routes/movie/\$slug.tsx

printf '\n== find ListItem component ==\n'
fd -a 'ListItem' src || true

printf '\n== search ListItem usages ==\n'
rg -n --glob 'src/**/*.{ts,tsx}' '<ListItem\b|ListItem\s*\(' src || true

Repository: TrackGeek/web

Length of output: 3621


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' src/components/pages/details/list.tsx

Repository: TrackGeek/web

Length of output: 2913


Pass list into <ListItem /> here. It dereferences list._count and list.user, so the Lists tab throws immediately without data. Same fix is needed on the other detail routes.

🧰 Tools
🪛 GitHub Actions: Build Files / 0_Build.txt

[error] 372-372: tsc (TS2741): Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

🪛 GitHub Actions: Build Files / Build

[error] 372-372: TS2741: Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

@izakdvlpr

Copy link
Copy Markdown
Contributor Author

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)

src/routes/movie/$slug.tsx (1)> 4-4: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

No cover fallback + unused Image import.
Unlike manga/book/anime routes, this file never falls back to a placeholder when movie.posterUrl is missing (coverUrl = movie.imageUrl || "/placeholder/cover.webp" pattern is absent) — both the sidebar <img> and MoreOptionsDialog's coverUrl use movie.posterUrl directly, producing a broken image if it's null. Also Image from @unpic/react is imported but unused.

Suggested fix

+  const coverUrl = movie.posterUrl || "/placeholder/cover.webp";
+
   const sidebar = (
     <>
       <div className="mb-2 w-full h-auto mx-auto shadow-xl rounded-lg overflow-hidden">
-        <img src={movie.posterUrl} alt={`${movie.title} Cover`} className="w-full h-auto object-cover" />
+        <img src={coverUrl} alt={`${movie.title} Cover`} className="w-full h-auto object-cover" />
       </div>

Also applies to: 94-98, 133-142

src/routes/anime/$slug.tsx (2)> 107-109: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

handleToggle is a console.log stub; mySeason state never updates.
AnimeEpisodeProgress calls onToggle, but the handler only logs and the paired setter is named _setMySeason (never called), so episode toggling is non-functional. If this is intentionally a WIP stub, consider a // TODO marker; happy to wire this up if useful.
Also applies to: 392-392

33-52: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Add notFoundComponent to the anime route — missing slugs will fall back to the generic error page instead of the app’s 404 UI, unlike the book/manga/movie routes.

🧹 Nitpick comments (4)

src/routes/manga/$slug.tsx (2)> 4-4: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Image imported but never used — cover still rendered with raw <img>.
Other shared components in this PR (ListItem, CastItem, CharacterItem, ReviewItem) all use @unpic/react's Image for optimized/lazy-loaded rendering, but this route's cover art (the most prominent image on the page) still uses a plain <img>. Either wire the cover to Image for consistency/perf, or drop the unused import.

Suggested fix

-        <img src={coverUrl} alt={`${manga.title} Cover`} className="w-full h-auto object-cover" />
+        <Image src={coverUrl} width={400} height={600} alt={`${manga.title} Cover`} className="w-full h-auto object-cover" />

Also applies to: 92-98

163-236: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Large inline IIFE duplicated verbatim in anime/$slug.tsx.
This external-links-building logic (official/instagram/facebook/twitter/ann/wikipedia detection) is copy-pasted identically in the anime route. Extract into a shared helper (e.g. buildExternalLinks(external, malId)) to avoid drift between the two.

src/routes/book/$slug.tsx (1)> 4-4: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Unused Image import — cover rendered with raw <img> instead.
Same pattern as the other details routes: Image from @unpic/react is imported but the cover art (and only the cover) still uses a plain <img>.
Also applies to: 92-94

src/routes/anime/$slug.tsx (1)> 4-4: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Unused Image import — cover rendered with raw <img>.
Consistent with the same finding in manga/book/movie routes.
Also applies to: 120-122

ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 34da6f69-072e-4d2a-b368-a58fc127963b

📥 Commits
Reviewing files that changed from the base of the PR and between f7c8443 and 18c760a.

📒 Files selected for processing (11)

  • src/components/shared/cards/card.tsx
  • src/components/shared/modals/game.tsx
  • src/components/shared/sidebar/still-watching.tsx
  • src/lib/i18n/locales/en-US/common.json
  • src/lib/i18n/locales/en-US/user.json
  • src/routes/anime/$slug.tsx
  • src/routes/book/$slug.tsx
  • src/routes/game/$slug.tsx
  • src/routes/manga/$slug.tsx
  • src/routes/movie/$slug.tsx
  • src/routes/tv/$slug.tsx

🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • TrackGeek/api (manual) → reviewed against branch feat/dynamic-profile instead of the default branch

💤 Files with no reviewable changes (1)

  • src/components/shared/modals/game.tsx

✅ Files skipped from review due to trivial changes (1)

  • src/lib/i18n/locales/en-US/user.json

🚧 Files skipped from review as they are similar to previous changes (4)

  • src/lib/i18n/locales/en-US/common.json
  • src/components/shared/sidebar/still-watching.tsx
  • src/components/shared/cards/card.tsx
  • src/routes/tv/$slug.tsx

🛑 Comments failed to post (19)

src/routes/anime/$slug.tsx (4)> 306-306: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain
🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file map =="
fd -a 'slug.tsx' src/routes || true

echo
echo "== targeted snippets in anime route =="
nl -ba src/routes/anime/\$slug.tsx | sed -n '260,430p'

echo
echo "== search for hardcoded rating / lists / community stats patterns =="
rg -n '\b4\.2\b|\(30\)|CommunityStats|community stats|rating =|lists count|TabsTrigger value="lists"' src/routes src/components src -g '!**/node_modules/**' || true

echo
echo "== other media routes with same pattern =="
for f in src/routes/*/\$slug.tsx; do
  [ -f "$f" ] || continue
  echo "--- $f"
  rg -n '\b4\.2\b|\(30\)|CommunityStats|rating =' "$f" || true
done

Repository: TrackGeek/web
Length of output: 731

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== anime route around the cited lines =="
sed -n '280,430p' src/routes/anime/\$slug.tsx | awk '{printf "%4d  %s\n", NR+279, $0}'

echo
echo "== search for the cited literals across routes =="
rg -n '\b4\.2\b|\(30\)|CommunityStats|rating =|TabsTrigger value="lists"' src/routes src/components -g '!**/node_modules/**' || true

echo
echo "== sibling slug routes with same literals =="
for f in src/routes/book/\$slug.tsx src/routes/game/\$slug.tsx src/routes/manga/\$slug.tsx src/routes/movie/\$slug.tsx src/routes/tv/\$slug.tsx; do
  [ -f "$f" ] || continue
  echo "--- $f"
  rg -n '\b4\.2\b|\(30\)|CommunityStats|rating =' "$f" || true
done

Repository: TrackGeek/web
Length of output: 11207

Replace the hardcoded stats with route data. rating, the CommunityStats percentages, and the lists count are still fixed values here, and the same placeholder pattern is used in src/routes/book/$slug.tsx, src/routes/game/$slug.tsx, src/routes/manga/$slug.tsx, and src/routes/movie/$slug.tsx. That leaves the details pages showing incorrect media-specific info.

416-423: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Trailer <iframe> missing a sandbox attribute.
Same security concern as movie/$slug.tsx.

Suggested fix

             <iframe
               src={anime.trailer.embedUrl.replace("&autoplay=1", "")}
               allowFullScreen
+              sandbox="allow-scripts allow-same-origin allow-presentation allow-popups"
+              referrerPolicy="strict-origin-when-cross-origin"
               className="w-full aspect-video"
               title="Trailer"
             />

📝 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.

          {anime.trailer.embedUrl && (
            <iframe
              src={anime.trailer.embedUrl.replace("&autoplay=1", "")}
              allowFullScreen
              sandbox="allow-scripts allow-same-origin allow-presentation allow-popups"
              referrerPolicy="strict-origin-when-cross-origin"
              className="w-full aspect-video"
              title="Trailer"
            />
          )}

🧰 Tools

🪛 React Doctor (0.5.8)
[error] 417-417: An <iframe> with no sandbox is a security hole: the embedded page gets full access to your site.
Add sandbox="" or a curated value so embedded pages cannot get full access to your site by default.
(iframe-missing-sandbox)

Source: Linters/SAST tools

447-468: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain
🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- file context ---'
nl -ba src/routes/anime/\$slug.tsx | sed -n '380,520p'

echo
echo '--- ReviewItem usages in src/routes ---'
rg -n "ReviewItem|reviewsQuery|reviews\?\." src/routes -g '*.tsx' -g '*.ts' || true

Repository: TrackGeek/web
Length of output: 212

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- file context ---'
awk 'NR>=380 && NR<=520 {printf "%6d  %s\n", NR, $0}' src/routes/anime/\$slug.tsx

echo
echo '--- ReviewItem and reviewsQuery usages ---'
rg -n "ReviewItem|reviewsQuery|reviews\?\." src/routes -g '*.tsx' -g '*.ts' || true

Repository: TrackGeek/web
Length of output: 9279

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- anime query context ---'
awk 'NR>=60 && NR<=120 {printf "%6d  %s\n", NR, $0}' src/routes/anime/\$slug.tsx

echo
echo '--- hardcoded review placeholders across routes/components ---'
rg -n '"John Doe"|Very foda!|criteria|criteries|reviewText=' src/routes src/components -g '*.tsx' -g '*.ts' || true

Repository: TrackGeek/web
Length of output: 11761

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- anime query context ---'
awk 'NR>=60 && NR<=120 {printf "%6d  %s\n", NR, $0}' src/routes/anime/\$slug.tsx

echo
echo '--- hardcoded review placeholders across routes/components ---'
rg -n '"John Doe"|Very foda!|criteries|reviewText=' src/routes src/components -g '*.tsx' -g '*.ts' || true

Repository: TrackGeek/web
Length of output: 9359

Replace the hardcoded review stub — This tab still renders a static ReviewItem instead of the fetched review data, so users see unrelated content whenever reviews?.total >= 1. The same stub also appears in the game, movie, book, and manga routes.

🧰 Tools

🪛 GitHub Actions: Build Files / 0_Build.txt
[error] 452-452: tsc (TS2353): Object literal may only specify known properties; 'avatarURL' does not exist in type 'User'.

🪛 GitHub Actions: Build Files / Build
[error] 452-452: TS2353: Object literal may only specify known properties, and 'avatarURL' does not exist in type 'User'.

🪛 OpenGrep (1.23.0)
[ERROR] 452-452: Possible credit card number (PAN) detected in source code. Credit card numbers should never be hardcoded or stored in source files. Use a secrets manager or tokenization service instead.
(coderabbit.pii.credit-card-number)

469-473: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain
🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the affected route and the ListItem component definition/uses.
rg -n "ListItem" src/routes src/components src -g '!**/*.map' || true

printf '\n--- anime route ---\n'
sed -n '430,500p' src/routes/anime/\$slug.tsx

printf '\n--- ListItem component candidates ---\n'
fd -a "ListItem" src || true

Repository: TrackGeek/web
Length of output: 8056

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- src/components/pages/details/list.tsx ---'
sed -n '1,180p' src/components/pages/details/list.tsx

printf '\n%s\n' '--- other detail routes with ListItem ---'
sed -n '350,390p' src/routes/movie/\$slug.tsx
printf '\n'
sed -n '320,350p' src/routes/book/\$slug.tsx
printf '\n'
sed -n '380,420p' src/routes/manga/\$slug.tsx
printf '\n'
sed -n '450,500p' src/routes/game/\$slug.tsx

Repository: TrackGeek/web
Length of output: 9039

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Quick structural check that the Lists tab data is available in the route, or if the tab is placeholder-only.
ast-grep outline src/routes/anime/\$slug.tsx --view expanded | sed -n '1,220p'

Repository: TrackGeek/web
Length of output: 272

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path("src/routes/anime/$slug.tsx")
text = p.read_text()
for needle in ["list", "Lists", "useQuery", "ApiTypes.ListItem", "anime.list", "listsQuery", "useLists"]:
    print(f"\n== {needle} ==")
    for i, line in enumerate(text.splitlines(), 1):
        if needle in line:
            print(f"{i}: {line}")
PY

Repository: TrackGeek/web
Length of output: 1729

<ListItem /> is missing the required list prop here, so opening the Lists tab throws. The same fix is needed in src/routes/book/$slug.tsx, src/routes/game/$slug.tsx, src/routes/manga/$slug.tsx, and src/routes/movie/$slug.tsx.

🧰 Tools

🪛 GitHub Actions: Build Files / 0_Build.txt
[error] 471-471: tsc (TS2741): Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

🪛 GitHub Actions: Build Files / Build
[error] 471-471: TS2741: Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

src/routes/book/$slug.tsx (4)> 178-183: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Literal placeholder text rendered directly to users.
series_names (can be hidden if not exists) is dev placeholder copy hardcoded into the JSX — it will be shown verbatim on every book's page.

Suggested fix

-      <div className="flex items-center gap-2">
-        <Icon icon={"lucide:book-copy"} className="size-5 text-muted-foreground" />
-        <Link to={"/"} search={{ landing: "true" }} className="text-xl text-muted-foreground">
-          series_names (can be hidden if not exists)
-        </Link>
-      </div>
+      {book.series?.name && (
+        <div className="flex items-center gap-2">
+          <Icon icon={"lucide:book-copy"} className="size-5 text-muted-foreground" />
+          <Link to={"/"} search={{ landing: "true" }} className="text-xl text-muted-foreground">
+            {book.series.name}
+          </Link>
+        </div>
+      )}

📝 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.

      {book.series?.name && (
        <div className="flex items-center gap-2">
          <Icon icon={"lucide:book-copy"} className="size-5 text-muted-foreground" />
          <Link to={"/"} search={{ landing: "true" }} className="text-xl text-muted-foreground">
            {book.series.name}
          </Link>
        </div>
      )}

226-308: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain
🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== outline =='
ast-grep outline src/routes/book/\$slug.tsx --view expanded || true

echo '== relevant search =='
rg -n --hidden -S 'book\.|isbn|genre|mood|warning|content warning|contentWarnings|communityStatistics|DetailsCard|CommunityStats' src/routes/book src/components src/lib src/types src -g '!**/node_modules/**' || true

Repository: TrackGeek/web
Length of output: 50369

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== book route slice =='
sed -n '1,340p' src/routes/book/\$slug.tsx | cat -n

echo '== book model search =='
rg -n --hidden -S 'interface .*Book|type .*Book|export .*Book|genres:|isbn|contentWarnings|mood' src -g '!**/node_modules/**' | head -n 200

Repository: TrackGeek/web
Length of output: 23146

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== src/routes/book/$slug.tsx relevant lines =='
sed -n '1,340p' src/routes/book/\$slug.tsx | nl -ba | sed -n '1,340p'

echo '== book-related type/data references =='
rg -n --hidden -S '\bgenres\b|\bisbn\b|\bcontentWarnings\b|\bmood\b|\bwarnings\b|\bGenrePills\b' src/routes/book src/lib src/components -g '!**/node_modules/**' | head -n 120

Repository: TrackGeek/web
Length of output: 238

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== api endpoints/type slice =='
rg -n -A4 -B4 'getBookDetails|bookReview|interface .*Book|type .*Book|export type .*Book|books?:' src/lib/api.ts src -g '!**/node_modules/**' | head -n 200

echo '== route comparisons =='
sed -n '250,340p' src/routes/movie/\$slug.tsx | cat -n
sed -n '340,430p' src/routes/game/\$slug.tsx | cat -n
sed -n '560,700p' src/routes/tv/\$slug.tsx | cat -n

Repository: TrackGeek/web
Length of output: 28769

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== book detail types/interfaces =='
rg -n -A20 -B20 'GetBookDetailsResponse|bookDetail|book: \{|interface .*Book|type .*Book|export interface .*Book' src/lib/api.ts src -g '!**/node_modules/**' | head -n 200

echo '== book detail route references =='
rg -n --hidden -S 'getBookDetails\(|/book/detail/|hardcoverId|numberOfPages|releaseYear|contributions|imageUrl|description' src/routes/book src/lib -g '!**/node_modules/**' | head -n 200

Repository: TrackGeek/web
Length of output: 35052

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== book controller/service =='
rg -n -A30 -B10 'book/detail|BookDetail|book detail|isbn|genre|mood|warning|content warning|contentWarnings|genres' src/modules/book src/modules -g '!**/node_modules/**' | head -n 300

Repository: TrackGeek/api
Length of output: 21540

Replace the placeholder book metadata src/routes/book/$slug.tsx
The genre pills, ISBNs, mood, and content warnings are hardcoded and render the same for every book. Drive this from book metadata or hide the section until the API exposes those fields.

310-331: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain
🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- file outline ---\n'
ast-grep outline src/routes/book/\$slug.tsx --view expanded || true

printf '\n--- relevant lines ---\n'
sed -n '250,380p' src/routes/book/\$slug.tsx

printf '\n--- search review wiring ---\n'
rg -n "reviewsData|ReviewItem|reviews\.total|reviewText|criteries|TabsContent value=\"reviews\"" src/routes/book/\$slug.tsx src -g '!**/node_modules/**' || true

Repository: TrackGeek/web
Length of output: 242

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- file outline ---'
ast-grep outline src/routes/book/\$slug.tsx --view expanded || true

echo
echo '--- relevant lines ---'
sed -n '250,380p' src/routes/book/\$slug.tsx

echo
echo '--- search review wiring ---'
rg -n "reviewsData|ReviewItem|reviews\.total|reviewText|criteries|TabsContent value=\"reviews\"" src/routes/book/\$slug.tsx src -g '!**/node_modules/**' || true

Repository: TrackGeek/web
Length of output: 15982

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- book route query and review tab context ---'
sed -n '50,90p' src/routes/book/\$slug.tsx
echo
sed -n '170,225p' src/routes/book/\$slug.tsx
echo
echo '--- review item component props ---'
sed -n '1,220p' src/components/pages/details/review.tsx

Repository: TrackGeek/web
Length of output: 11695

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- bookReviews usage ---'
rg -n "bookReviews" src -g '!**/node_modules/**' || true

echo
echo '--- book review endpoint / types ---'
rg -n "bookReview|reviewsData|reviews\.total" src/lib src/routes src/hooks -g '!**/node_modules/**' || true

Repository: TrackGeek/web
Length of output: 3265

Render fetched book reviews instead of a static placeholder The reviews tab still mounts a single hardcoded ReviewItem (John Doe, fixed text, fixed scores) even though bookReview already returns PaginatedResponse<Review>. Map the loaded review items here so the tab reflects the current book.

🧰 Tools

🪛 GitHub Actions: Build Files / 0_Build.txt
[error] 315-315: tsc (TS2353): Object literal may only specify known properties; 'avatarURL' does not exist in type 'User'.

🪛 GitHub Actions: Build Files / Build
[error] 315-315: TS2353: Object literal may only specify known properties, and 'avatarURL' does not exist in type 'User'.

🪛 OpenGrep (1.23.0)
[ERROR] 315-315: Possible credit card number (PAN) detected in source code. Credit card numbers should never be hardcoded or stored in source files. Use a secrets manager or tokenization service instead.
(coderabbit.pii.credit-card-number)

332-336: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain
🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== list item definition ==\n'
rg -n "export .*ListItem|function ListItem|const ListItem" src | sed -n '1,80p'

printf '\n== book slug route around lists tab ==\n'
sed -n '300,360p' src/routes/book/\$slug.tsx

printf '\n== other ListItem usages in routes/details ==\n'
rg -n "<ListItem" src/routes src/components src -g '!**/*.map' | sed -n '1,120p'

Repository: TrackGeek/web
Length of output: 3949

🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,140p' src/components/pages/details/list.tsx

Repository: TrackGeek/web
Length of output: 2913

Pass the required list prop to <ListItem /> — this component reads list._count.listItems, list.listItems, and list.user immediately, so rendering it bare will break the Lists tab.

🧰 Tools

🪛 GitHub Actions: Build Files / 0_Build.txt
[error] 334-334: tsc (TS2741): Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

🪛 GitHub Actions: Build Files / Build
[error] 334-334: TS2741: Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

src/routes/game/$slug.tsx (4)> 263-267: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Render the actual game status.
This prints Early Access for every truthy game.status, so released/beta/other states will be mislabeled. Map the real status value instead of hardcoding one label.

343-343: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use the actual list count here.
(30) is a static placeholder, so this badge will drift as soon as the backend count changes. Wire it to real list data instead.

441-441: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain
🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== game slug iframe context ==\n'
sed -n '400,470p' src/routes/game/\$slug.tsx

printf '\n== iframe / sandbox usage in web ==\n'
rg -n --glob '!**/node_modules/**' '<iframe|sandbox=' src . || true

Repository: TrackGeek/web
Length of output: 4284

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== videoId usages ==\n'
rg -n --glob '!**/node_modules/**' '\bvideoId\b' src

printf '\n== sibling iframe blocks ==\n'
sed -n '400,440p' src/routes/anime/\$slug.tsx
sed -n '320,360p' src/routes/movie/\$slug.tsx
sed -n '740,780p' src/routes/tv/\$slug.tsx

Repository: TrackGeek/web
Length of output: 6404

Add a sandbox to this iframe.
video.videoId points to a third-party YouTube embed, so this iframe currently gets more privileges than it needs. Add a restrictive sandbox and allow only the permissions required for playback.

🧰 Tools

🪛 React Doctor (0.5.8)
[error] 441-441: An <iframe> with no sandbox is a security hole: the embedded page gets full access to your site.
Add sandbox="" or a curated value so embedded pages cannot get full access to your site by default.
(iframe-missing-sandbox)

Source: Linters/SAST tools

454-472: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Render the fetched reviews instead of the placeholder card.
reviewsQuery is fetched, but this tab ignores it and always shows a hard-coded sample review. Users will never see the actual review data returned by the API.

🧰 Tools

🪛 GitHub Actions: Build Files / 0_Build.txt
[error] 458-458: tsc (TS2353): Object literal may only specify known properties; 'avatarURL' does not exist in type 'User'.

🪛 GitHub Actions: Build Files / Build
[error] 458-458: TS2353: Object literal may only specify known properties, and 'avatarURL' does not exist in type 'User'.

🪛 OpenGrep (1.23.0)
[ERROR] 458-458: Possible credit card number (PAN) detected in source code. Credit card numbers should never be hardcoded or stored in source files. Use a secrets manager or tokenization service instead.
(coderabbit.pii.credit-card-number)

src/routes/manga/$slug.tsx (3)> 271-276: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain
🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file list =="
git ls-files src/routes/manga/\$slug.tsx

echo
echo "== relevant line ranges =="
sed -n '1,90p' src/routes/manga/\$slug.tsx
echo
sed -n '250,390p' src/routes/manga/\$slug.tsx

echo
echo "== search for hardcoded stats/list placeholders in this file =="
rg -n '4\.2|30\)|CommunityStats|community|lists \(' src/routes/manga/\$slug.tsx

Repository: TrackGeek/web
Length of output: 11100

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact definitions around the disputed values.
sed -n '40,80p' src/routes/manga/\$slug.tsx
echo
sed -n '340,380p' src/routes/manga/\$slug.tsx
echo
sed -n '260,285p' src/routes/manga/\$slug.tsx

Repository: TrackGeek/web
Length of output: 4367

Replace the hardcoded manga stats placeholders (src/routes/manga/$slug.tsx:56, 356-370, 276). rating = 4.2, the community statistics percentages, and the lists tab count are all static, so every manga page shows the same values. Wire them to real data or hide these fields until they’re available.

375-394: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain
🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the referenced route and nearby review-tab logic.
file='src/routes/manga/$slug.tsx'
if [ ! -f "$file" ]; then
  echo "Missing file: $file" >&2
  exit 1
fi

wc -l "$file"
sed -n '320,430p' "$file"

Repository: TrackGeek/web
Length of output: 4402

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether this route fetches or maps real review data elsewhere in the file.
file='src/routes/manga/$slug.tsx'
rg -n "reviews|ReviewItem|criteries|reviewText|John Doe" "$file"

Repository: TrackGeek/web
Length of output: 891

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the top of the file for data fetching and available review-related state.
file='src/routes/manga/$slug.tsx'
sed -n '1,220p' "$file"

Repository: TrackGeek/web
Length of output: 8816

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the top of the file for loaders, query data, and any review model usage.
file='src/routes/manga/$slug.tsx'
sed -n '1,220p' "$file"

Repository: TrackGeek/web
Length of output: 8816

Render the fetched manga reviews here.
This tab still shows a single hardcoded ReviewItem even though reviewsData is already loaded and the tab only appears when reviews.total >= 1; map over the returned reviews instead of showing placeholder content.

🧰 Tools

🪛 GitHub Actions: Build Files / 0_Build.txt
[error] 379-379: tsc (TS2353): Object literal may only specify known properties; 'avatarURL' does not exist in type 'User'.

🪛 GitHub Actions: Build Files / Build
[error] 379-379: TS2353: Object literal may only specify known properties, and 'avatarURL' does not exist in type 'User'.

🪛 OpenGrep (1.23.0)
[ERROR] 379-379: Possible credit card number (PAN) detected in source code. Credit card numbers should never be hardcoded or stored in source files. Use a secrets manager or tokenization service instead.
(coderabbit.pii.credit-card-number)

395-399: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain
🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching ListItem references:\n'
rg -n --glob '!node_modules' --glob '!dist' --glob '!build' 'ListItem' src | sed -n '1,120p'

printf '\nRelevant file slice: src/routes/manga/$slug.tsx\n'
sed -n '360,430p' src/routes/manga/\$slug.tsx

printf '\nPotential component definitions for ListItem:\n'
rg -n --glob '!node_modules' --glob '!dist' --glob '!build' 'export (default )?function ListItem|const ListItem|function ListItem|type ListItem|interface ListItem' src | sed -n '1,120p'

Repository: TrackGeek/web
Length of output: 6091

🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,140p' src/components/pages/details/list.tsx

Repository: TrackGeek/web
Length of output: 2913

Pass the required list prop here <ListItem /> still renders without list, and ListItem immediately reads list._count, list.name, and list.user, so this tab will fail to render until a real ApiTypes.ListWithPreview is passed in.

🧰 Tools

🪛 GitHub Actions: Build Files / 0_Build.txt
[error] 397-397: tsc (TS2741): Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

🪛 GitHub Actions: Build Files / Build
[error] 397-397: TS2741: Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

src/routes/movie/$slug.tsx (4)> 270-321: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain
🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the target file and nearby symbols first.
ast-grep outline src/routes/movie/\$slug.tsx --view expanded || true

# Inspect the relevant sections with line numbers.
sed -n '240,360p' src/routes/movie/\$slug.tsx

# Search for the hardcoded director name and community stats usage.
rg -n 'Tom Gormican|CommunityStats|movieCharacteristics|library:directors|progress|stats|percentage|percent' src/routes/movie/\$slug.tsx src -g '!**/node_modules/**' || true

Repository: TrackGeek/web
Length of output: 50369

Bind the director and community stats to movie data The DetailsCard always renders Tom Gormican, and CommunityStats uses fixed percentages. That makes every movie page show the same factual data; wire these fields to the actual movie/community response instead of literals.

341-348: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Trailer <iframe> missing a sandbox attribute.
An unsandboxed <iframe> embedding third-party content is a security hole; the embedded page gets full access to your origin. Add a curated sandbox value that still allows the YouTube player to function.

Suggested fix

             <iframe
               src={`https://youtube.com/embed/${movie.trailerId}`}
               allowFullScreen
+              sandbox="allow-scripts allow-same-origin allow-presentation allow-popups"
+              referrerPolicy="strict-origin-when-cross-origin"
               className="w-full aspect-video"
               title="Trailer"
             />

📝 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.

          {movie.trailerId && (
            <iframe
              src={`https://youtube.com/embed/${movie.trailerId}`}
              allowFullScreen
              sandbox="allow-scripts allow-same-origin allow-presentation allow-popups"
              referrerPolicy="strict-origin-when-cross-origin"
              className="w-full aspect-video"
              title="Trailer"
            />
          )}

🧰 Tools

🪛 React Doctor (0.5.8)
[error] 342-342: An <iframe> with no sandbox is a security hole: the embedded page gets full access to your site.
Add sandbox="" or a curated value so embedded pages cannot get full access to your site by default.
(iframe-missing-sandbox)

Source: Linters/SAST tools

350-369: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain
🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target route and nearby review-tab logic.
ast-grep outline src/routes/movie/\$slug.tsx --view expanded || true

echo '--- file excerpt ---'
sed -n '300,420p' src/routes/movie/\$slug.tsx

Repository: TrackGeek/web
Length of output: 4406

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the route for any review data flow or conditional rendering around the reviews tab.
rg -n "reviews|ReviewItem|criteries|reviewText|movie\." src/routes/movie/\$slug.tsx

echo '--- surrounding excerpt ---'
sed -n '1,220p' src/routes/movie/\$slug.tsx

Repository: TrackGeek/web
Length of output: 11803

Reviews tab still uses a hardcoded placeholder review
TabsContent value="reviews" should render the fetched reviews data instead of a static ReviewItem; right now it shows unrelated content even when real reviews exist.

🧰 Tools

🪛 GitHub Actions: Build Files / 0_Build.txt
[error] 354-354: tsc (TS2353): Object literal may only specify known properties; 'avatarURL' does not exist in type 'User'.

🪛 GitHub Actions: Build Files / Build
[error] 354-354: TS2353: Object literal may only specify known properties, and 'avatarURL' does not exist in type 'User'.

🪛 OpenGrep (1.23.0)
[ERROR] 354-354: Possible credit card number (PAN) detected in source code. Credit card numbers should never be hardcoded or stored in source files. Use a secrets manager or tokenization service instead.
(coderabbit.pii.credit-card-number)

370-374: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain
🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== movie route snippet ==\n'
sed -n '330,410p' src/routes/movie/\$slug.tsx

printf '\n== find ListItem component ==\n'
fd -a 'ListItem' src || true

printf '\n== search ListItem usages ==\n'
rg -n --glob 'src/**/*.{ts,tsx}' '<ListItem\b|ListItem\s*\(' src || true

Repository: TrackGeek/web
Length of output: 3621

🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' src/components/pages/details/list.tsx

Repository: TrackGeek/web
Length of output: 2913

Pass list into <ListItem /> here. It dereferences list._count and list.user, so the Lists tab throws immediately without data. Same fix is needed on the other detail routes.

🧰 Tools

🪛 GitHub Actions: Build Files / 0_Build.txt
[error] 372-372: tsc (TS2741): Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

🪛 GitHub Actions: Build Files / Build
[error] 372-372: TS2741: Property 'list' is missing in type '{}' but required in type 'ListItemProps'.

Irei ignorar isso no momento, pois vou tratar disso quando mexer em cada uma das páginas em especifico.

@izakdvlpr
izakdvlpr merged commit 51fbbcf into develop Jul 8, 2026
5 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jul 10, 2026
3 tasks
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.

1 participant