Conversation
…ious episodes as watched
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
📝 WalkthroughWalkthroughThis PR adds an Open Graph image rendering and JSON-LD structured-data system (takumi-js based rendering, card templates, media-source mapping, new API routes, route-tree wiring), integrates episode-backfill confirmation flows into anime and TV detail routes with a new dialog component and preference utilities, rebuilds the book modal as a React Query/React Hook Form persisted editor with a new getBookProgress endpoint, revamps the book detail route with API-backed metadata/editions/reviews, and applies minor hardcoverId identifier fixes plus search result deduplication and game route display updates. Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant AnimeRoute
participant BackfillEpisodesDialog
participant API
User->>AnimeRoute: mark episode watched
AnimeRoute->>AnimeRoute: getUnwatchedPreviousEpisodes()
AnimeRoute->>AnimeRoute: getBackfillPreference()
alt preference is "always"
AnimeRoute->>API: mark previous episodes watched
else preference is "never"
AnimeRoute->>API: mark only target episode
else no preference
AnimeRoute->>BackfillEpisodesDialog: open confirmation
User->>BackfillEpisodesDialog: confirm or decline
BackfillEpisodesDialog->>AnimeRoute: onConfirm/onDecline(remember)
AnimeRoute->>API: apply selected marking
end
AnimeRoute->>API: start Watching progress if needed
sequenceDiagram
participant Client
participant ApiOgRoute
participant renderMediaOg
participant loadMediaCard
participant API
Client->>ApiOgRoute: GET /api/og/media/type/slug
ApiOgRoute->>ApiOgRoute: validate media type
ApiOgRoute->>renderMediaOg: renderMediaOg(type, slug)
renderMediaOg->>loadMediaCard: loadMediaCard(type, slug)
loadMediaCard->>API: fetch media endpoint
API-->>loadMediaCard: media response
loadMediaCard-->>renderMediaOg: MediaCardProps
renderMediaOg->>renderMediaOg: build PNG ImageResponse
renderMediaOg-->>Client: OG image response
Possibly related PRs
🚥 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: 20
🧹 Nitpick comments (15)
src/components/shared/modals/book.tsx (1)
254-272: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winPartial delete failure leaves the cache stale. If the review delete succeeds but the progress delete throws,
onSuccessnever runs, so no invalidation happens and the UI keeps showing the deleted review until remount. Move the invalidations toonSettled(keeping the close/dialog handling inonSuccess).src/routes/book/$slug.tsx (5)
84-86: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winDedupe author names. Hardcover contributions can repeat the same person across roles (author, illustrator, translator), and
contributionis ignored here. Duplicates surface as repeated text inListWithMoreand as duplicate React keys at Line 148.♻️ Proposed dedupe
function authorNames(contributions: BookContribution[] | null | undefined): string[] { - return (contributions ?? []).map((c) => c.author?.name).filter((name): name is string => !!name); + return [...new Set((contributions ?? []).map((c) => c.author?.name).filter((name): name is string => !!name))]; }
99-123: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReuse the already-computed author list.
authorNames(book?.contributions)runs twice; Line 99 already has the array before joining.♻️ Suggested tidy-up
- const authors = authorNames(book?.contributions).join(", "); + const authorList = authorNames(book?.contributions); + const authors = authorList.join(", "); @@ - author: authorNames(book?.contributions).map((name) => ({ "`@type`": "Person", name })), + author: authorList.map((name) => ({ "`@type`": "Person", name })),
450-469: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueRating may render with excess precision.
{rating}printstgReviewScoreraw; if the API returns an unrounded average (e.g.4.333333) this leaks into the UI next toStarRating. Considerrating.toFixed(1).
166-171: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftBook data is untyped, forcing inline annotations. Neither the loader nor
useQuerytypesdata.book, which is why Line 344-345 needs(tagging: { tag: string })and Lines 346-348 need manual casts. Adding aGetBookDetailsResponsetype toapi.tsand usingapi.get<ApiTypes.GetBookDetailsResponse>would let the interfaces defined at Lines 39-72 be validated against the real payload instead of asserted.Also applies to: 343-348
485-489: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNit:
editions.length >= 1reads oddly. Useeditions.length > 0for consistency with the other emptiness checks in this file.Also applies to: 574-582
src/components/pages/details/backfill-episodes-dialog.tsx (1)
1-3: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
useTranslation()instead of importingtfromi18nextdirectly.Every other component in this PR (anime/tv routes) uses
useTranslation(). Importing the baretfromi18nextbinds to the default singleton and won't re-render this dialog if the language changes while it's open.♻️ Suggested fix
-import { t } from "i18next"; import { useState } from "react"; +import { useTranslation } from "react-i18next"; import { Button } from "`@/components/ui/button`";}: BackfillEpisodesDialogProps) { + const { t } = useTranslation(); const [remember, setRemember] = useState(false);src/lib/og/templates/user.tsx (1)
73-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicated logo markup — reuse
OgLogo.Same 1386/443 aspect math as
OgLogoinsrc/lib/og/templates/shell.tsx; add an optionalstyleprop there and reuse so the logo asset/ratio lives in one place.src/lib/og/resources.ts (1)
71-85: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winRemove rejected image fetches from the shared cache.
A rejected
fetchpromise is memoized infetchCache, so retries only happen after FIFO eviction. Attach acatchthat removes the entry if it is still the cached promise for that URL.src/lib/og/url.ts (1)
6-12: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winUnbounded
title/descriptionembedded into the OG image URL.
page()embeds the full, un-truncatedtitle/descriptioninto the query string used asog:image. Combined with the corresponding server handler (src/routes/api/og/page.tsx) not capping these values either, arbitrarily long content produces very large URLs assigned toog:image/twitter:image. See consolidated comment for the shared fix.src/lib/utils/json-ld.ts (2)
3-6: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winDuplicated, fragile origin fallback.
This origin/URL resolution logic (
import.meta.env.VITE_SITE_URL ?? "") is duplicated verbatim insrc/lib/utils/seo.ts. IfVITE_SITE_URLis unset during SSR, both files silently fall back to an empty string, producing relativeurl/logo/imagevalues in JSON-LD and OG meta tags — invalid for schema.org and social crawlers, which require absolute URLs. See consolidated comment.
1-1: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winConsolidate the duplicated origin-fallback logic. Both files independently resolve
originaswindow.location.origin(client) orimport.meta.env.VITE_SITE_URL ?? ""(server), silently degrading to relative URLs if the env var is unset.
src/lib/utils/json-ld.ts#L3-6: keep this as the single source of truth (exportgetOrigin/getUrl, or a smallresolveOrigin()util) and consider warning/throwing whenVITE_SITE_URLis missing during SSR, since it silently breaks OG/JSON-LD absolute URLs.src/lib/utils/seo.ts#L14-19: import and reuse the shared origin resolver instead of re-implementing the same fallback inline.src/routes/api/og/page.tsx (2)
6-11: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winNo length validation on public
title/descriptionquery params.This is an unauthenticated endpoint that performs server-side image rendering per request.
title/descriptionare passed straight from the query string intorenderPageOgwith no cap. Downstream truncation in the card template (outside this diff) likely bounds the rendered output, but capping the input here is a cheap, defense-in-depth improvement that also avoids embedding oversized values in theog:imageURL built bysrc/lib/og/url.ts'spage()helper (same pipeline).♻️ Proposed fix
GET: async ({ request }) => { const { renderPageOg } = await import("`@/lib/og/render`"); const params = new URL(request.url).searchParams; - return renderPageOg(params.get("title") ?? "TrackGeek", params.get("description")); + const title = (params.get("title") ?? "TrackGeek").slice(0, 200); + const description = params.get("description")?.slice(0, 400) ?? null; + + return renderPageOg(title, description); },Can you confirm whether
PageCard's truncation (insrc/lib/og/templates/page.tsx, not part of this diff) already bounds the actual rendering cost regardless of input size, so this is purely a defense-in-depth improvement rather than an active gap?
1-1: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winCap
title/descriptionlength across the OG page pipeline. Both the URL builder and the server route let arbitrarily long text flow through unchecked; capping length once removes the risk from both call sites.
src/routes/api/og/page.tsx#L6-11: slicetitle/descriptionfromsearchParamsto a reasonable max length before callingrenderPageOg.src/lib/og/url.ts#L6-12: also captitle/descriptionbefore building the query string, so the generatedog:image/twitter:imageURL itself stays bounded even for legitimate long content (e.g. long synopses).src/lib/utils/seo.ts (1)
14-19: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winDuplicated origin-fallback logic (same root cause as
json-ld.ts).See consolidated comment for
src/lib/utils/json-ld.ts— this file'soriginresolution (lines 16 in the annotated snippet) duplicates that logic and shares the same empty-string fallback risk whenVITE_SITE_URLis unset.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: cd56bd20-cf70-439a-8c80-2238df0e39f1
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (36)
package.jsonsrc/components/pages/details/backfill-episodes-dialog.tsxsrc/components/shared/modals/book.tsxsrc/lib/api.tssrc/lib/i18n/locales/en-US/library.jsonsrc/lib/og/media-source.tssrc/lib/og/render.tsxsrc/lib/og/resources.tssrc/lib/og/templates/media.tsxsrc/lib/og/templates/page.tsxsrc/lib/og/templates/shell.tsxsrc/lib/og/templates/user.tsxsrc/lib/og/theme.tssrc/lib/og/url.tssrc/lib/utils/episode-backfill.tssrc/lib/utils/genre-utils.tssrc/lib/utils/json-ld.tssrc/lib/utils/seo.tssrc/routeTree.gen.tssrc/routes/__root.tsxsrc/routes/anime/$slug.tsxsrc/routes/api/og/media.$type.$slug.tsxsrc/routes/api/og/page.tsxsrc/routes/api/og/user.$username.tsxsrc/routes/book/$slug.tsxsrc/routes/book/franchises/$slug.tsxsrc/routes/book/index.tsxsrc/routes/book/trending/index.tsxsrc/routes/book/upcoming/index.tsxsrc/routes/game/$slug.tsxsrc/routes/manga/$slug.tsxsrc/routes/movie/$slug.tsxsrc/routes/search.tsxsrc/routes/tv/$slug.tsxsrc/routes/user/$username/index.tsxsrc/vite-env.d.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
TrackGeek/api(manual) → reviewed against open PR#77fix/bugsinstead of the default branch
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Description
Acknowledgements
npm run checkScreenshots
Proof showing the creation/modification is working as expected