Skip to content

feat: add books tracking - #147

Merged
Kuriel23 merged 13 commits into
developfrom
fix/bugs
Jul 30, 2026
Merged

feat: add books tracking#147
Kuriel23 merged 13 commits into
developfrom
fix/bugs

Conversation

@Kuriel23

Copy link
Copy Markdown
Contributor

Description

Acknowledgements

Screenshots

Proof showing the creation/modification is working as expected

@Kuriel23
Kuriel23 changed the base branch from main to develop July 30, 2026 23:08
@socket-security

socket-security Bot commented Jul 30, 2026

Copy link
Copy Markdown

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

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedtakumi-js@​2.5.47510010096100

View full report

@socket-security

socket-security Bot commented Jul 30, 2026

Copy link
Copy Markdown

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.

Action Severity Alert  (click "▶" to expand/collapse)
Warn Medium
Low adoption: npm @takumi-rs/core-darwin-x64

Location: Package overview

From: ?npm/takumi-js@2.5.4npm/@takumi-rs/core-darwin-x64@2.5.4

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@takumi-rs/core-darwin-x64@2.5.4. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Low adoption: npm @takumi-rs/core-win32-arm64-msvc

Location: Package overview

From: ?npm/takumi-js@2.5.4npm/@takumi-rs/core-win32-arm64-msvc@2.5.4

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@takumi-rs/core-win32-arm64-msvc@2.5.4. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This 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
Loading
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
Loading

Possibly related PRs

  • TrackGeek/web#93: Both modify src/components/shared/modals/book.tsx, with this PR replacing the status-callback modal implementation introduced there.
  • TrackGeek/web#111: Both modify src/lib/utils/seo.ts and route-level SEO metadata, extended here with OG image and JSON-LD functionality.
  • TrackGeek/web#123: Both modify episode-progress mutations and EpisodeProgress integration in src/routes/tv/$slug.tsx.
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary book tracking functionality added by the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/bugs

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 20

🧹 Nitpick comments (15)
src/components/shared/modals/book.tsx (1)

254-272: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Partial delete failure leaves the cache stale. If the review delete succeeds but the progress delete throws, onSuccess never runs, so no invalidation happens and the UI keeps showing the deleted review until remount. Move the invalidations to onSettled (keeping the close/dialog handling in onSuccess).

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

84-86: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Dedupe author names. Hardcover contributions can repeat the same person across roles (author, illustrator, translator), and contribution is ignored here. Duplicates surface as repeated text in ListWithMore and 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 value

Reuse 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 value

Rating may render with excess precision. {rating} prints tgReviewScore raw; if the API returns an unrounded average (e.g. 4.333333) this leaks into the UI next to StarRating. Consider rating.toFixed(1).


166-171: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Book data is untyped, forcing inline annotations. Neither the loader nor useQuery types data.book, which is why Line 344-345 needs (tagging: { tag: string }) and Lines 346-348 need manual casts. Adding a GetBookDetailsResponse type to api.ts and using api.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 value

Nit: editions.length >= 1 reads oddly. Use editions.length > 0 for 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 win

Use useTranslation() instead of importing t from i18next directly.

Every other component in this PR (anime/tv routes) uses useTranslation(). Importing the bare t from i18next binds 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 value

Duplicated logo markup — reuse OgLogo.

Same 1386/443 aspect math as OgLogo in src/lib/og/templates/shell.tsx; add an optional style prop there and reuse so the logo asset/ratio lives in one place.

src/lib/og/resources.ts (1)

71-85: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Remove rejected image fetches from the shared cache.

A rejected fetch promise is memoized in fetchCache, so retries only happen after FIFO eviction. Attach a catch that 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 win

Unbounded title/description embedded into the OG image URL.

page() embeds the full, un-truncated title/description into the query string used as og: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 to og:image/twitter:image. See consolidated comment for the shared fix.

src/lib/utils/json-ld.ts (2)

3-6: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Duplicated, fragile origin fallback.

This origin/URL resolution logic (import.meta.env.VITE_SITE_URL ?? "") is duplicated verbatim in src/lib/utils/seo.ts. If VITE_SITE_URL is unset during SSR, both files silently fall back to an empty string, producing relative url/logo/image values 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 win

Consolidate the duplicated origin-fallback logic. Both files independently resolve origin as window.location.origin (client) or import.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 (export getOrigin/getUrl, or a small resolveOrigin() util) and consider warning/throwing when VITE_SITE_URL is 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 win

No length validation on public title/description query params.

This is an unauthenticated endpoint that performs server-side image rendering per request. title/description are passed straight from the query string into renderPageOg with 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 the og:image URL built by src/lib/og/url.ts's page() 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 (in src/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 win

Cap title/description length 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: slice title/description from searchParams to a reasonable max length before calling renderPageOg.
  • src/lib/og/url.ts#L6-12: also cap title/description before building the query string, so the generated og:image/twitter:image URL itself stays bounded even for legitimate long content (e.g. long synopses).
src/lib/utils/seo.ts (1)

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

Duplicated origin-fallback logic (same root cause as json-ld.ts).

See consolidated comment for src/lib/utils/json-ld.ts — this file's origin resolution (lines 16 in the annotated snippet) duplicates that logic and shares the same empty-string fallback risk when VITE_SITE_URL is unset.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cd56bd20-cf70-439a-8c80-2238df0e39f1

📥 Commits

Reviewing files that changed from the base of the PR and between cace792 and 297d564.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (36)
  • package.json
  • src/components/pages/details/backfill-episodes-dialog.tsx
  • src/components/shared/modals/book.tsx
  • src/lib/api.ts
  • src/lib/i18n/locales/en-US/library.json
  • src/lib/og/media-source.ts
  • src/lib/og/render.tsx
  • src/lib/og/resources.ts
  • src/lib/og/templates/media.tsx
  • src/lib/og/templates/page.tsx
  • src/lib/og/templates/shell.tsx
  • src/lib/og/templates/user.tsx
  • src/lib/og/theme.ts
  • src/lib/og/url.ts
  • src/lib/utils/episode-backfill.ts
  • src/lib/utils/genre-utils.ts
  • src/lib/utils/json-ld.ts
  • src/lib/utils/seo.ts
  • src/routeTree.gen.ts
  • src/routes/__root.tsx
  • src/routes/anime/$slug.tsx
  • src/routes/api/og/media.$type.$slug.tsx
  • src/routes/api/og/page.tsx
  • src/routes/api/og/user.$username.tsx
  • src/routes/book/$slug.tsx
  • src/routes/book/franchises/$slug.tsx
  • src/routes/book/index.tsx
  • src/routes/book/trending/index.tsx
  • src/routes/book/upcoming/index.tsx
  • src/routes/game/$slug.tsx
  • src/routes/manga/$slug.tsx
  • src/routes/movie/$slug.tsx
  • src/routes/search.tsx
  • src/routes/tv/$slug.tsx
  • src/routes/user/$username/index.tsx
  • src/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 #77 fix/bugs instead of the default branch

Comment thread src/components/shared/modals/book.tsx
Comment thread src/components/shared/modals/book.tsx
Comment thread src/components/shared/modals/book.tsx
Comment thread src/components/shared/modals/book.tsx
Comment thread src/components/shared/modals/book.tsx
Comment thread src/routes/book/$slug.tsx
Comment thread src/routes/book/$slug.tsx
Comment thread src/routes/game/$slug.tsx
Comment thread src/routes/search.tsx
Comment thread src/routes/tv/$slug.tsx
Kuriel23 and others added 7 commits July 31, 2026 00:38
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>
@Kuriel23
Kuriel23 merged commit 0313406 into develop Jul 30, 2026
5 checks passed
@Kuriel23
Kuriel23 deleted the fix/bugs branch July 30, 2026 23:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant