feat: dynamic profile#123
Conversation
- 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.
…, comments, and favorites
…library, settings, and user profiles
…data, and streamline user details layout. Delete unused routes for manga, movie, reviews, screenshots, and series.
…g for name, username, and about fields
- 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.
…and remove ViteImage dependency
… 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.
… in settings route
|
Warning Review limit reached
Next review available in: 24 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (79)
📝 WalkthroughWalkthroughThis 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 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 winBanner preview should use
layout="fullWidth"
@unpic/reactstill 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 tolayout="fullWidth"with anaspectRatio.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 winEmpty
alton 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 winRender 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 winReviews tab still renders a hardcoded placeholder review instead of the fetched list.
reviewsDatais only used for the count/rating badge; this tab never maps the loaded reviews, so users will always see the same filler text. The droppeduserprop is fine here becauseReviewItem.useris optional.
♻️ Duplicate comments (6)
src/components/pages/user/tabs/movie.tsx (1)
1-83: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winSame legacy-file/
CardItemcontract issue asgame.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 simplifiedCardItem, which no longer accepts those props.src/components/pages/user/tabs/serie.tsx (1)
1-85: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winSame legacy-file/
CardItemcontract issue asgame.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 simplifiedCardItem, which no longer accepts those props.src/components/pages/user/tabs/manga.tsx (1)
1-99: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winSame legacy-file/
CardItemcontract issue asgame.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-simplifiedCardItem, which only acceptstitle/url/imageURL/isAdult.src/components/pages/feed/item.tsx (1)
29-35: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winEmpty
alton meaningful cover image.Same as
feed/entry.tsx:item.coverURLis meaningful content;alt=""hides it from assistive tech. Consideralt={item.title}.src/components/shared/sidebar/still-reading.tsx (1)
26-32: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winEmpty
alton 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 winEmpty
alton 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 winKeep
auto-fitfor the shared grid wrapper.src/components/layouts/grid.tsx:30is reused by sparse views like user screenshots and search results, soauto-fillwill 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 winAdd 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 winFix stylelint violations.
Static analysis flags two issues on the
font-familydeclaration 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 winKeep a base style for bare links —
src/components/shared/footer.tsxstill renders several bare<a>/Linkelements 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 winMake profile image fields nullable.
profile.bannerUrlandprofile.avatarUrlcan be cleared tonullby the backend, but this response type still marks them as required strings. Change them tostring | nullso 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 winFollow control defaults to "Follow" while follow-status is still loading.
followStatusQuery.data?.isFollowingisundefinedduring the initial fetch, so an already-followed user briefly sees the "Follow" button; a click then firesfollowUseron an existing relationship. Consider gating the buttons onfollowStatusQuery.isSuccess/isPending(or disabling until resolved). Also note L136 re-computes theisOwnProfilecheck 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 winSingle mutation instance disables all favorite cards during any single removal.
removeFavoriteis oneuseMutationinstance shared by every renderedFavoriteCard.removeFavorite.isPendingis true globally the moment any card's remove is clicked, soisRemovingdisables/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 winNo distinct error state for
favoritesQuery.When the query fails,
isLoadingis false andgroupsis 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 winHardcoded
"en-US"locale ignores current i18n language.
getDateSuffix,formatNumber, and the tooltip'sdate.toLocaleDateString("en-US", ...)all hard-code English (US) formatting, while the rest of the component usesreact-i18nextfor 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" })inrenderRect, 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 winIcon-only delete button lacks an accessible name.
The delete
Buttononly renders alucide:trash-2icon with noaria-label, unlike similar icon-only buttons elsewhere in the codebase (e.g.comment-thread.tsx's "More options" button usesaria-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 winLists 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 winSet the cover image to
fullWidthlayout
@unpic/reactdefaults toconstrained, sowidth={500}keeps this image capped around 500px even in the widerlg:w-1/3column. Switch this tolayout="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 winHardcoded Portuguese alt text.
alt="Capa do jogo"is hardcoded in Portuguese while the rest of the file/app usest(...)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 winEmpty
alton meaningful cover image.
item.coverURLis the show/movie/book poster — meaningful content, not decorative — butalt=""suppresses it from screen readers. Consideralt={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 winUse an explicit
objectFit: "contain"override here.@unpic/reactsetsobject-fit: coverinline by default, so the Tailwindobject-containclass won’t apply on its own.src/routes/anime/$slug.tsx-572-574 (1)
572-574: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRemove 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 winRemove the stale Lists count
src/routes/movie/$slug.tsx:320,477-479still showsLists (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 winAdd a fallback for
movie.backdropUrlin the carousel
src/lib/api.tsallowsmovie.backdropUrlto benull, but@unpic/react’sImageexpects 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 winNon-interactive "likes" indicator styled as clickable.
Lines 239-243: the heart/likes block has
cursor-pointer hover:text-red-500 transition-colorsbut noonClick, 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 winMissing null-fallback for poster/rating in the "more options" dialog.
The main poster at line 343 falls back to
"/placeholder/cover.webp"whenitem.posterUrlis null, but the dialog header'sImage(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 whenreviews.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 winUnhandled falsy
linkin preview thumbnails.
listItemToLink(item)can return a falsy value (seelist-items-modal.tsx, which explicitly doesif (!link) return null;before rendering). Here,previewsdoesn't filter those out, so an item with no resolvable link renders an emptyAvatarwithlink?.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 winAvoid
anyforsession.Typing
sessionasanyloses type safety onsession.data?.sessionbelow and any future property access. Prefer inferring the type fromcontext.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 winStale props:
rating,year,synopsis,mediaTypeare declared but never used.The simplified
CardItemonly consumestitle,url,imageURL, andisAdult, yet the interface still advertisesrating/year/synopsis/mediaType, and several callers (UserBookTab,UserMovieTab,UserSerieTab,UserGameTab,UserMangaTab) still pass them. These are silently ignored, which is misleading. Either drop them fromCardProps(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 valueDefault
query = "A"sits before required params and is effectively dead.
buildSearchUrlis only ever invoked asbuildSearchUrl(contentType, debouncedQuery, filters, page), wheredebouncedQuerydefaults to""(fromparseAsString.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 valueRemove the unused TV show review aliases in
src/lib/api.ts.TVShowReviewandGetTVShowReviewsResponsehave no remaining call sites; the TV review hooks useApiTypes.ReviewandApiTypes.GetReviewsResponseinstead.src/components/shared/star-rating.tsx (1)
14-34: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAdd accessible semantics to the rating display.
The component renders
maxunlabeled star icons twice with norole/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 winSilent 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 valueScreenshots tab still renders hardcoded mock data.
gamesWithScreenshotsis 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 winDynamic translation keys built from
medal.namehave 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 valueDeprecated 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 unifiederrorparam, 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 winEmpty
alttext 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 winConsider reusing
getInitialsFromNamefor the avatar fallback.
AvatarFallbackrenders the fullnamestring rather than initials, unlike the existing pattern inuser-dropdown.tsxwhich usesgetInitialsFromName. 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 winConsider
priorityfor the above-the-fold cover image.
@unpic/react'sImageBy default, the generated images use lazy loading and asynchronous decoding. This main poster is likely the LCP element on this page; consider addingpriorityto eagerly load it and improve LCP.src/components/shared/header/user-dropdown.tsx (1)
66-71: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove commented-out notifications entry instead of leaving dead code.
If notifications are being disabled intentionally, delete the block (and the now-possibly-unused
common:notificationslocale 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 winUntyped route cast and unguarded fallback branch.
to={/${routeName}/${entityId}as any}bypasses TanStack Router's typed-route checking, and the branch is entered wheneverreviewNameis truthy ORuseris falsy — but nothing verifiesrouteName/entityIdare actually defined in that case, which would produce a link to/undefined/undefined. Not exercised by the current caller (which always passesuser), but worth tightening before this component is reused (e.g., on a profile reviews tab withreviewName).♻️ 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 winLoose dynamic zod schema forces an
as anyescape hatch.
createReviewSchematypes its shape asRecord<string, z.ZodTypeAny>, which erases per-field types (e.g.recommendedboolean vs rating strings). This forcesresolver: zodResolver(schema) as anyat Line 97, silently disabling type-checking between the schema,ReviewFormData, and the submit payload built at Lines 110-124 — a typo incriterion.fieldhere would compile but fail silently at runtime.Consider typing the shape more precisely (e.g. building a
ZodRawShapewith a mapped type keyed bycriteria[number]['field']) so the resolver's inferred type actually matchesReviewFormDatawithout 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
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (105)
package.jsonsrc/components/layouts/grid.tsxsrc/components/layouts/home.tsxsrc/components/layouts/index.tsxsrc/components/layouts/main.tsxsrc/components/layouts/user.tsxsrc/components/pages/details/cast.tsxsrc/components/pages/details/character.tsxsrc/components/pages/details/list-items-modal.tsxsrc/components/pages/details/list.tsxsrc/components/pages/details/progress.tsxsrc/components/pages/details/review.tsxsrc/components/pages/feed/comment-thread.tsxsrc/components/pages/feed/entry.tsxsrc/components/pages/feed/item.tsxsrc/components/pages/feed/listFollowing.tsxsrc/components/pages/feed/review.tsxsrc/components/pages/user/cast-favorite-card.tsxsrc/components/pages/user/favorite-card.tsxsrc/components/pages/user/medal-icon.tsxsrc/components/pages/user/tabs/activity.tsxsrc/components/pages/user/tabs/anime.tsxsrc/components/pages/user/tabs/book.tsxsrc/components/pages/user/tabs/favorites.tsxsrc/components/pages/user/tabs/game.tsxsrc/components/pages/user/tabs/lists.tsxsrc/components/pages/user/tabs/manga.tsxsrc/components/pages/user/tabs/movie.tsxsrc/components/pages/user/tabs/overview/about-card.tsxsrc/components/pages/user/tabs/overview/activity-card.tsxsrc/components/pages/user/tabs/overview/favorite-card.tsxsrc/components/pages/user/tabs/overview/favorites-card.tsxsrc/components/pages/user/tabs/overview/index.tsxsrc/components/pages/user/tabs/overview/medals-card.tsxsrc/components/pages/user/tabs/overview/statistics-card.tsxsrc/components/pages/user/tabs/reviews.tsxsrc/components/pages/user/tabs/screenshots.tsxsrc/components/pages/user/tabs/serie.tsxsrc/components/pages/user/user-profile-header.tsxsrc/components/shared/cards/card.tsxsrc/components/shared/cards/contributors.tsxsrc/components/shared/cards/core-team.tsxsrc/components/shared/cards/screenshot.tsxsrc/components/shared/comments/index.tsxsrc/components/shared/comments/markdown.tsxsrc/components/shared/header/user-dropdown.tsxsrc/components/shared/loadings/filtered.tsxsrc/components/shared/modals/episodic-content.tsxsrc/components/shared/modals/game.tsxsrc/components/shared/modals/review.tsxsrc/components/shared/search-input.tsxsrc/components/shared/sidebar/still-reading.tsxsrc/components/shared/sidebar/still-watching.tsxsrc/components/shared/star-rating.tsxsrc/components/ui/avatar.tsxsrc/components/ui/rating-group-advanced.tsxsrc/global.csssrc/hooks/comment.tssrc/hooks/favorite.tssrc/hooks/list.tssrc/hooks/review.tssrc/lib/api.tssrc/lib/i18n/locales/en-US/comments.jsonsrc/lib/i18n/locales/en-US/common.jsonsrc/lib/i18n/locales/en-US/feed.jsonsrc/lib/i18n/locales/en-US/library.jsonsrc/lib/i18n/locales/en-US/settings.jsonsrc/lib/i18n/locales/en-US/user.jsonsrc/lib/image.tssrc/lib/tanstack-query.tssrc/lib/utils/list-item.tssrc/routeTree.gen.tssrc/routes/__root.tsxsrc/routes/_authenticated.tsxsrc/routes/_authenticated/settings.tsxsrc/routes/anime/$slug.tsxsrc/routes/anime/index.tsxsrc/routes/book/$slug.tsxsrc/routes/book/franchises/$slug.tsxsrc/routes/book/index.tsxsrc/routes/game/$slug.tsxsrc/routes/game/franchises/$slug.tsxsrc/routes/game/index.tsxsrc/routes/index.tsxsrc/routes/list/$slug.tsxsrc/routes/manga/$slug.tsxsrc/routes/manga/index.tsxsrc/routes/movie/$slug.tsxsrc/routes/movie/franchises/$slug.tsxsrc/routes/movie/index.tsxsrc/routes/post/$id.tsxsrc/routes/review/$id.tsxsrc/routes/search.tsxsrc/routes/tv/$slug.tsxsrc/routes/tv/index.tsxsrc/routes/user/$username/anime/index.tsxsrc/routes/user/$username/book/index.tsxsrc/routes/user/$username/game/index.tsxsrc/routes/user/$username/index.tsxsrc/routes/user/$username/manga/index.tsxsrc/routes/user/$username/movie/index.tsxsrc/routes/user/$username/reviews/index.tsxsrc/routes/user/$username/serie/index.tsxsrc/vite-env.d.tsvite.config.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
TrackGeek/api(manual) → reviewed against open PR#67feat/dynamic-profileinstead of the default branch
💤 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
| 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); | ||
| }, |
There was a problem hiding this comment.
🗄️ 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.
| 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); | |
| }, |
| 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")); | ||
| } | ||
| }; |
There was a problem hiding this comment.
🩺 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.
| 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> | ||
| ); | ||
| })} |
There was a problem hiding this comment.
🎯 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.
| 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> | |
| ); | |
| })} |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
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 winNo cover fallback + unused
Imageimport.Unlike manga/book/anime routes, this file never falls back to a placeholder when
movie.posterUrlis missing (coverUrl = movie.imageUrl || "/placeholder/cover.webp"pattern is absent) — both the sidebar<img>andMoreOptionsDialog'scoverUrlusemovie.posterUrldirectly, producing a broken image if it's null. AlsoImagefrom@unpic/reactis 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
handleToggleis aconsole.logstub;mySeasonstate never updates.
AnimeEpisodeProgresscallsonToggle, 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// TODOmarker; happy to wire this up if useful.Also applies to: 392-392
33-52: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAdd
notFoundComponentto 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
Imageimported but never used — cover still rendered with raw<img>.Other shared components in this PR (
ListItem,CastItem,CharacterItem,ReviewItem) all use@unpic/react'sImagefor 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 toImagefor 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 winLarge 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 winUnused
Imageimport — cover rendered with raw<img>instead.Same pattern as the other details routes:
Imagefrom@unpic/reactis 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 winUnused
Imageimport — 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
📒 Files selected for processing (11)
src/components/shared/cards/card.tsxsrc/components/shared/modals/game.tsxsrc/components/shared/sidebar/still-watching.tsxsrc/lib/i18n/locales/en-US/common.jsonsrc/lib/i18n/locales/en-US/user.jsonsrc/routes/anime/$slug.tsxsrc/routes/book/$slug.tsxsrc/routes/game/$slug.tsxsrc/routes/manga/$slug.tsxsrc/routes/movie/$slug.tsxsrc/routes/tv/$slug.tsx
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
TrackGeek/api(manual) → reviewed against branchfeat/dynamic-profileinstead 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
There was a problem hiding this comment.
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 winNo cover fallback + unused
Imageimport.Unlike manga/book/anime routes, this file never falls back to a placeholder when
movie.posterUrlis missing (coverUrl = movie.imageUrl || "/placeholder/cover.webp"pattern is absent) — both the sidebar<img>andMoreOptionsDialog'scoverUrlusemovie.posterUrldirectly, producing a broken image if it's null. AlsoImagefrom@unpic/reactis 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
handleToggleis aconsole.logstub;mySeasonstate never updates.
AnimeEpisodeProgresscallsonToggle, 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// TODOmarker; happy to wire this up if useful.Also applies to: 392-392
33-52: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAdd
notFoundComponentto 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
Imageimported but never used — cover still rendered with raw<img>.Other shared components in this PR (
ListItem,CastItem,CharacterItem,ReviewItem) all use@unpic/react'sImagefor 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 toImagefor 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 winLarge 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 winUnused
Imageimport — cover rendered with raw<img>instead.Same pattern as the other details routes:
Imagefrom@unpic/reactis 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 winUnused
Imageimport — 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
📒 Files selected for processing (11)
src/components/shared/cards/card.tsxsrc/components/shared/modals/game.tsxsrc/components/shared/sidebar/still-watching.tsxsrc/lib/i18n/locales/en-US/common.jsonsrc/lib/i18n/locales/en-US/user.jsonsrc/routes/anime/$slug.tsxsrc/routes/book/$slug.tsxsrc/routes/game/$slug.tsxsrc/routes/manga/$slug.tsxsrc/routes/movie/$slug.tsxsrc/routes/tv/$slug.tsx
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
TrackGeek/api(manual) → reviewed against branchfeat/dynamic-profileinstead 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 doneRepository: 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 doneRepository: TrackGeek/web
Length of output: 11207
Replace the hardcoded stats with route data.
rating, theCommunityStatspercentages, and the lists count are still fixed values here, and the same placeholder pattern is used insrc/routes/book/$slug.tsx,src/routes/game/$slug.tsx,src/routes/manga/$slug.tsx, andsrc/routes/movie/$slug.tsx. That leaves the details pages showing incorrect media-specific info.
416-423: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Trailer
<iframe>missing asandboxattribute.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 nosandboxis 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' || trueRepository: 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' || trueRepository: 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' || trueRepository: 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' || trueRepository: TrackGeek/web
Length of output: 9359
Replace the hardcoded review stub — This tab still renders a static
ReviewIteminstead of the fetched review data, so users see unrelated content wheneverreviews?.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 || trueRepository: 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.tsxRepository: 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}") PYRepository: TrackGeek/web
Length of output: 1729
<ListItem />is missing the requiredlistprop here, so opening the Lists tab throws. The same fix is needed insrc/routes/book/$slug.tsx,src/routes/game/$slug.tsx,src/routes/manga/$slug.tsx, andsrc/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/**' || trueRepository: 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 200Repository: 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 120Repository: 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 -nRepository: 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 200Repository: 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 300Repository: 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/**' || trueRepository: 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/**' || trueRepository: 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.tsxRepository: 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/**' || trueRepository: 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 thoughbookReviewalready returnsPaginatedResponse<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.tsxRepository: TrackGeek/web
Length of output: 2913
Pass the required
listprop to<ListItem />— this component readslist._count.listItems,list.listItems, andlist.userimmediately, 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 Accessfor every truthygame.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 . || trueRepository: 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.tsxRepository: TrackGeek/web
Length of output: 6404
Add a sandbox to this iframe.
video.videoIdpoints to a third-party YouTube embed, so this iframe currently gets more privileges than it needs. Add a restrictivesandboxand allow only the permissions required for playback.🧰 Tools
🪛 React Doctor (0.5.8)
[error] 441-441: An
<iframe>with nosandboxis 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.
reviewsQueryis 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.tsxRepository: 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.tsxRepository: 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 hardcodedReviewItemeven thoughreviewsDatais already loaded and the tab only appears whenreviews.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.tsxRepository: TrackGeek/web
Length of output: 2913
Pass the required
listprop here<ListItem />still renders withoutlist, andListItemimmediately readslist._count,list.name, andlist.user, so this tab will fail to render until a realApiTypes.ListWithPreviewis 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/**' || trueRepository: TrackGeek/web
Length of output: 50369
Bind the director and community stats to movie data The
DetailsCardalways rendersTom Gormican, andCommunityStatsuses 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 asandboxattribute.An unsandboxed
<iframe>embedding third-party content is a security hole; the embedded page gets full access to your origin. Add a curatedsandboxvalue 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 nosandboxis 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.tsxRepository: 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.tsxRepository: TrackGeek/web
Length of output: 11803
Reviews tab still uses a hardcoded placeholder review
TabsContent value="reviews"should render the fetchedreviewsdata instead of a staticReviewItem; 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 || trueRepository: TrackGeek/web
Length of output: 3621
🏁 Script executed:
#!/bin/bash set -euo pipefail sed -n '1,220p' src/components/pages/details/list.tsxRepository: TrackGeek/web
Length of output: 2913
Pass
listinto<ListItem />here. It dereferenceslist._countandlist.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. |
Description
Acknowledgements
npm run checkScreenshots
Proof showing the creation/modification is working as expected