From ae2bb005b07ee3e2062501caa4d75eccebcd2a26 Mon Sep 17 00:00:00 2001
From: Harshith Mohan <26010946+harshithmohan@users.noreply.github.com>
Date: Tue, 10 Dec 2024 00:13:34 +0530
Subject: [PATCH] Upgrade to react 19, add titles, add duplicate files util
 (#1154)

* Upgrade to react 19, add titles

* Add duplicate files util
---
 package.json                                  |  10 +-
 pnpm-lock.yaml                                | 612 +++++++++---------
 src/components/Collection/constants.ts        |   2 +
 src/components/Layout/TopNav.tsx              |   4 +-
 .../Utilities/ReleaseManagement/Episode.tsx   | 175 +++++
 .../MultiplesUtilEpisode.tsx                  | 101 ---
 .../{MultiplesUtilList.tsx => SeriesList.tsx} | 142 ++--
 .../Utilities/ReleaseManagement/Title.tsx     |   4 +-
 src/components/Utilities/constants.tsx        |   6 +-
 src/core/react-query/file/mutations.ts        |   6 +
 src/core/react-query/file/types.ts            |   6 +
 .../react-query/release-management/queries.ts |  26 +-
 .../react-query/release-management/types.ts   |   4 +-
 src/core/router/index.tsx                     |   5 +-
 src/core/types/api/file.ts                    |   2 +
 src/core/types/api/series.ts                  |   2 +-
 src/pages/collection/Collection.tsx           |  99 +--
 src/pages/collection/Series.tsx               |   2 +-
 src/pages/collection/series/SeriesCredits.tsx |  83 +--
 .../collection/series/SeriesEpisodes.tsx      | 215 +++---
 .../collection/series/SeriesFileSummary.tsx   |  54 +-
 src/pages/collection/series/SeriesImages.tsx  | 159 ++---
 .../collection/series/SeriesOverview.tsx      |  27 +-
 src/pages/collection/series/SeriesTags.tsx    |  73 +--
 src/pages/dashboard/DashboardPage.tsx         |   1 +
 src/pages/logs/LogsPage.tsx                   | 129 ++--
 src/pages/settings/SettingsPage.tsx           |   2 +-
 src/pages/settings/tabs/AniDBSettings.tsx     |   1 +
 src/pages/settings/tabs/ApiKeys.tsx           |   1 +
 .../settings/tabs/CollectionSettings.tsx      |   1 +
 src/pages/settings/tabs/GeneralSettings.tsx   |   1 +
 src/pages/settings/tabs/ImportSettings.tsx    |   1 +
 .../settings/tabs/IntegrationsSettings.tsx    |   1 +
 .../settings/tabs/MetadataSitesSettings.tsx   |   1 +
 .../settings/tabs/UserManagementSettings.tsx  |   1 +
 src/pages/utilities/FileSearch.tsx            | 137 ++--
 src/pages/utilities/ReleaseManagement.tsx     | 210 ++++++
 .../MultiplesUtil.tsx                         | 180 ------
 src/pages/utilities/Renamer.tsx               | 347 +++++-----
 .../utilities/SeriesWithoutFilesUtility.tsx   |  93 +--
 .../IgnoredFilesTab.tsx                       |  93 +--
 .../ManuallyLinkedTab.tsx                     | 171 ++---
 .../UnrecognizedTab.tsx                       |   1 +
 43 files changed, 1690 insertions(+), 1501 deletions(-)
 create mode 100644 src/components/Utilities/ReleaseManagement/Episode.tsx
 delete mode 100644 src/components/Utilities/ReleaseManagement/MultiplesUtilEpisode.tsx
 rename src/components/Utilities/ReleaseManagement/{MultiplesUtilList.tsx => SeriesList.tsx} (63%)
 create mode 100644 src/pages/utilities/ReleaseManagement.tsx
 delete mode 100644 src/pages/utilities/ReleaseManagementUtilityTabs/MultiplesUtil.tsx

diff --git a/package.json b/package.json
index 536127864..ed04d3174 100644
--- a/package.json
+++ b/package.json
@@ -32,10 +32,10 @@
     "lodash": "^4.17.21",
     "monaco-editor": "^0.52.0",
     "pretty-bytes": "^6.1.1",
-    "react": "^18.3.1",
+    "react": "^19.0.0",
     "react-animate-height": "^3.2.3",
     "react-avatar-editor": "^13.0.2",
-    "react-dom": "^18.3.1",
+    "react-dom": "^19.0.0",
     "react-grid-layout": "^1.5.0",
     "react-modal": "^3.16.1",
     "react-redux": "^9.1.2",
@@ -60,9 +60,9 @@
     "@types/format-thousands": "^2.0.3",
     "@types/lodash": "^4.17.13",
     "@types/node": "^22.10.1",
-    "@types/react": "^18.3.14",
+    "@types/react": "^19.0.1",
     "@types/react-avatar-editor": "^13.0.3",
-    "@types/react-dom": "^18.3.2",
+    "@types/react-dom": "^19.0.1",
     "@types/react-grid-layout": "^1.3.5",
     "@types/react-modal": "^3.16.3",
     "@types/react-redux": "^7.1.34",
@@ -95,7 +95,7 @@
     "tailwindcss": "^3.4.16",
     "tailwindcss-text-fill-stroke": "2.0.0-beta.1",
     "typescript": "5.6.3",
-    "vite": "^5.4.11",
+    "vite": "^6.0.3",
     "vite-plugin-webpackchunkname": "^1.0.3"
   },
   "scripts": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e33d13b17..d1f58aebd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -13,13 +13,13 @@ importers:
         version: 5.1.0
       '@headlessui/react':
         specifier: ^2.2.0
-        version: 2.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 2.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       '@headlessui/tailwindcss':
         specifier: ^0.2.1
         version: 0.2.1(tailwindcss@3.4.16)
       '@hello-pangea/dnd':
         specifier: ^17.0.0
-        version: 17.0.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 17.0.0(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       '@mdi/js':
         specifier: ^7.4.47
         version: 7.4.47
@@ -31,25 +31,25 @@ importers:
         version: 8.0.7
       '@monaco-editor/react':
         specifier: ^4.6.0
-        version: 4.6.0(monaco-editor@0.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 4.6.0(monaco-editor@0.52.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       '@reduxjs/toolkit':
         specifier: ^2.4.0
-        version: 2.4.0(react-redux@9.1.2(@types/react@18.3.14)(react@18.3.1)(redux@5.0.1))(react@18.3.1)
+        version: 2.4.0(react-redux@9.1.2(@types/react@19.0.1)(react@19.0.0)(redux@5.0.1))(react@19.0.0)
       '@sentry/browser':
         specifier: ^8.42.0
         version: 8.42.0
       '@sentry/react':
         specifier: ^8.42.0
-        version: 8.42.0(react@18.3.1)
+        version: 8.42.0(react@19.0.0)
       '@tanstack/react-query':
         specifier: ^5.62.3
-        version: 5.62.3(react@18.3.1)
+        version: 5.62.3(react@19.0.0)
       '@tanstack/react-query-devtools':
         specifier: ^5.62.3
-        version: 5.62.3(@tanstack/react-query@5.62.3(react@18.3.1))(react@18.3.1)
+        version: 5.62.3(@tanstack/react-query@5.62.3(react@19.0.0))(react@19.0.0)
       '@tanstack/react-virtual':
         specifier: ^3.11.0
-        version: 3.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 3.11.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       axios:
         specifier: ^1.7.9
         version: 1.7.9
@@ -81,47 +81,47 @@ importers:
         specifier: ^6.1.1
         version: 6.1.1
       react:
-        specifier: ^18.3.1
-        version: 18.3.1
+        specifier: ^19.0.0
+        version: 19.0.0
       react-animate-height:
         specifier: ^3.2.3
-        version: 3.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 3.2.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       react-avatar-editor:
         specifier: ^13.0.2
-        version: 13.0.2(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 13.0.2(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       react-dom:
-        specifier: ^18.3.1
-        version: 18.3.1(react@18.3.1)
+        specifier: ^19.0.0
+        version: 19.0.0(react@19.0.0)
       react-grid-layout:
         specifier: ^1.5.0
-        version: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 1.5.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       react-modal:
         specifier: ^3.16.1
-        version: 3.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 3.16.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       react-redux:
         specifier: ^9.1.2
-        version: 9.1.2(@types/react@18.3.14)(react@18.3.1)(redux@5.0.1)
+        version: 9.1.2(@types/react@19.0.1)(react@19.0.0)(redux@5.0.1)
       react-resizable:
         specifier: ^3.0.5
-        version: 3.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 3.0.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       react-responsive:
         specifier: ^10.0.0
-        version: 10.0.0(react@18.3.1)
+        version: 10.0.0(react@19.0.0)
       react-router:
         specifier: ^6.28.0
-        version: 6.28.0(react@18.3.1)
+        version: 6.28.0(react@19.0.0)
       react-router-dom:
         specifier: ^6.28.0
-        version: 6.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 6.28.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       react-toastify:
         specifier: ^10.0.6
-        version: 10.0.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 10.0.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       react-tooltip:
         specifier: ^5.28.0
-        version: 5.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 5.28.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       react-use-measure:
         specifier: ^2.1.1
-        version: 2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+        version: 2.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       redux:
         specifier: ^5.0.1
         version: 5.0.1
@@ -133,10 +133,10 @@ importers:
         version: 13.20.0
       use-immer:
         specifier: ^0.10.0
-        version: 0.10.0(immer@10.1.1)(react@18.3.1)
+        version: 0.10.0(immer@10.1.1)(react@19.0.0)
       usehooks-ts:
         specifier: ^3.1.0
-        version: 3.1.0(react@18.3.1)
+        version: 3.1.0(react@19.0.0)
     devDependencies:
       '@sentry/vite-plugin':
         specifier: ^2.22.7
@@ -160,14 +160,14 @@ importers:
         specifier: ^22.10.1
         version: 22.10.1
       '@types/react':
-        specifier: ^18.3.14
-        version: 18.3.14
+        specifier: ^19.0.1
+        version: 19.0.1
       '@types/react-avatar-editor':
         specifier: ^13.0.3
         version: 13.0.3
       '@types/react-dom':
-        specifier: ^18.3.2
-        version: 18.3.2
+        specifier: ^19.0.1
+        version: 19.0.1
       '@types/react-grid-layout':
         specifier: ^1.3.5
         version: 1.3.5
@@ -188,7 +188,7 @@ importers:
         version: 7.18.0(eslint@8.57.1)(typescript@5.6.3)
       '@vitejs/plugin-react':
         specifier: ^4.3.4
-        version: 4.3.4(vite@5.4.11(@types/node@22.10.1))
+        version: 4.3.4(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1))
       cross-env:
         specifier: ^7.0.3
         version: 7.0.3
@@ -265,8 +265,8 @@ importers:
         specifier: 5.6.3
         version: 5.6.3
       vite:
-        specifier: ^5.4.11
-        version: 5.4.11(@types/node@22.10.1)
+        specifier: ^6.0.3
+        version: 6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1)
       vite-plugin-webpackchunkname:
         specifier: ^1.0.3
         version: 1.0.3(rollup@4.28.1)
@@ -682,141 +682,147 @@ packages:
   '@dual-bundle/import-meta-resolve@4.1.0':
     resolution: {integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==}
 
-  '@esbuild/aix-ppc64@0.21.5':
-    resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
-    engines: {node: '>=12'}
+  '@esbuild/aix-ppc64@0.24.0':
+    resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==}
+    engines: {node: '>=18'}
     cpu: [ppc64]
     os: [aix]
 
-  '@esbuild/android-arm64@0.21.5':
-    resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
-    engines: {node: '>=12'}
+  '@esbuild/android-arm64@0.24.0':
+    resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [android]
 
-  '@esbuild/android-arm@0.21.5':
-    resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
-    engines: {node: '>=12'}
+  '@esbuild/android-arm@0.24.0':
+    resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==}
+    engines: {node: '>=18'}
     cpu: [arm]
     os: [android]
 
-  '@esbuild/android-x64@0.21.5':
-    resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
-    engines: {node: '>=12'}
+  '@esbuild/android-x64@0.24.0':
+    resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [android]
 
-  '@esbuild/darwin-arm64@0.21.5':
-    resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
-    engines: {node: '>=12'}
+  '@esbuild/darwin-arm64@0.24.0':
+    resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [darwin]
 
-  '@esbuild/darwin-x64@0.21.5':
-    resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
-    engines: {node: '>=12'}
+  '@esbuild/darwin-x64@0.24.0':
+    resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [darwin]
 
-  '@esbuild/freebsd-arm64@0.21.5':
-    resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
-    engines: {node: '>=12'}
+  '@esbuild/freebsd-arm64@0.24.0':
+    resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [freebsd]
 
-  '@esbuild/freebsd-x64@0.21.5':
-    resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
-    engines: {node: '>=12'}
+  '@esbuild/freebsd-x64@0.24.0':
+    resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [freebsd]
 
-  '@esbuild/linux-arm64@0.21.5':
-    resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-arm64@0.24.0':
+    resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [linux]
 
-  '@esbuild/linux-arm@0.21.5':
-    resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-arm@0.24.0':
+    resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==}
+    engines: {node: '>=18'}
     cpu: [arm]
     os: [linux]
 
-  '@esbuild/linux-ia32@0.21.5':
-    resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-ia32@0.24.0':
+    resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==}
+    engines: {node: '>=18'}
     cpu: [ia32]
     os: [linux]
 
-  '@esbuild/linux-loong64@0.21.5':
-    resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-loong64@0.24.0':
+    resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==}
+    engines: {node: '>=18'}
     cpu: [loong64]
     os: [linux]
 
-  '@esbuild/linux-mips64el@0.21.5':
-    resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-mips64el@0.24.0':
+    resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==}
+    engines: {node: '>=18'}
     cpu: [mips64el]
     os: [linux]
 
-  '@esbuild/linux-ppc64@0.21.5':
-    resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-ppc64@0.24.0':
+    resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==}
+    engines: {node: '>=18'}
     cpu: [ppc64]
     os: [linux]
 
-  '@esbuild/linux-riscv64@0.21.5':
-    resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-riscv64@0.24.0':
+    resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==}
+    engines: {node: '>=18'}
     cpu: [riscv64]
     os: [linux]
 
-  '@esbuild/linux-s390x@0.21.5':
-    resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-s390x@0.24.0':
+    resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==}
+    engines: {node: '>=18'}
     cpu: [s390x]
     os: [linux]
 
-  '@esbuild/linux-x64@0.21.5':
-    resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-x64@0.24.0':
+    resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [linux]
 
-  '@esbuild/netbsd-x64@0.21.5':
-    resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
-    engines: {node: '>=12'}
+  '@esbuild/netbsd-x64@0.24.0':
+    resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [netbsd]
 
-  '@esbuild/openbsd-x64@0.21.5':
-    resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
-    engines: {node: '>=12'}
+  '@esbuild/openbsd-arm64@0.24.0':
+    resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openbsd]
+
+  '@esbuild/openbsd-x64@0.24.0':
+    resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [openbsd]
 
-  '@esbuild/sunos-x64@0.21.5':
-    resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
-    engines: {node: '>=12'}
+  '@esbuild/sunos-x64@0.24.0':
+    resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [sunos]
 
-  '@esbuild/win32-arm64@0.21.5':
-    resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
-    engines: {node: '>=12'}
+  '@esbuild/win32-arm64@0.24.0':
+    resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [win32]
 
-  '@esbuild/win32-ia32@0.21.5':
-    resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
-    engines: {node: '>=12'}
+  '@esbuild/win32-ia32@0.24.0':
+    resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==}
+    engines: {node: '>=18'}
     cpu: [ia32]
     os: [win32]
 
-  '@esbuild/win32-x64@0.21.5':
-    resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
-    engines: {node: '>=12'}
+  '@esbuild/win32-x64@0.24.0':
+    resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [win32]
 
@@ -1283,14 +1289,11 @@ packages:
   '@types/node@22.10.1':
     resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==}
 
-  '@types/prop-types@15.7.14':
-    resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
-
   '@types/react-avatar-editor@13.0.3':
     resolution: {integrity: sha512-icRAOKLKjkIsExFAiFSquztByJwpyTKEgnBRYSuLG2V81bM3LtQZi7hRS+Hr+4AXreq0yNRjVZiMOVeEeh6DLg==}
 
-  '@types/react-dom@18.3.2':
-    resolution: {integrity: sha512-Fqp+rcvem9wEnGr3RY8dYNvSQ8PoLqjZ9HLgaPUOjJJD120uDyOxOjc/39M4Kddp9JQCxpGQbnhVQF0C0ncYVg==}
+  '@types/react-dom@19.0.1':
+    resolution: {integrity: sha512-hljHij7MpWPKF6u5vojuyfV0YA4YURsQG7KT6SzV0Zs2BXAtgdTxG6A229Ub/xiWV4w/7JL8fi6aAyjshH4meA==}
 
   '@types/react-grid-layout@1.3.5':
     resolution: {integrity: sha512-WH/po1gcEcoR6y857yAnPGug+ZhkF4PaTUxgAbwfeSH/QOgVSakKHBXoPGad/sEznmkiaK3pqHk+etdWisoeBQ==}
@@ -1301,8 +1304,8 @@ packages:
   '@types/react-redux@7.1.34':
     resolution: {integrity: sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==}
 
-  '@types/react@18.3.14':
-    resolution: {integrity: sha512-NzahNKvjNhVjuPBQ+2G7WlxstQ+47kXZNHlUvFakDViuIEfGY926GqhMueQFZ7woG+sPiQKlF36XfrIUVSUfFg==}
+  '@types/react@19.0.1':
+    resolution: {integrity: sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ==}
 
   '@types/semver@7.5.8':
     resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
@@ -1904,9 +1907,9 @@ packages:
     resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
     engines: {node: '>= 0.4'}
 
-  esbuild@0.21.5:
-    resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
-    engines: {node: '>=12'}
+  esbuild@0.24.0:
+    resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==}
+    engines: {node: '>=18'}
     hasBin: true
 
   escalade@3.2.0:
@@ -3160,10 +3163,10 @@ packages:
       react: ^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
       react-dom: ^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
 
-  react-dom@18.3.1:
-    resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
+  react-dom@19.0.0:
+    resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==}
     peerDependencies:
-      react: ^18.3.1
+      react: ^19.0.0
 
   react-draggable@4.4.6:
     resolution: {integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==}
@@ -3248,8 +3251,8 @@ packages:
       react: '>=16.13'
       react-dom: '>=16.13'
 
-  react@18.3.1:
-    resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+  react@19.0.0:
+    resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
     engines: {node: '>=0.10.0'}
 
   read-cache@1.0.0:
@@ -3345,8 +3348,8 @@ packages:
     resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==}
     engines: {node: '>= 0.4'}
 
-  scheduler@0.23.2:
-    resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+  scheduler@0.25.0:
+    resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==}
 
   semver@6.3.1:
     resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
@@ -3671,22 +3674,27 @@ packages:
   vite-plugin-webpackchunkname@1.0.3:
     resolution: {integrity: sha512-88lt6IrgCumnf4Up8eyaSJbmo4V0ZIaR4M94fbZvGGmK2aWMmPGVsiFBszYE7Kq04I9tGjLFnyremn+KEgEGyw==}
 
-  vite@5.4.11:
-    resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==}
-    engines: {node: ^18.0.0 || >=20.0.0}
+  vite@6.0.3:
+    resolution: {integrity: sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==}
+    engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
     hasBin: true
     peerDependencies:
-      '@types/node': ^18.0.0 || >=20.0.0
+      '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+      jiti: '>=1.21.0'
       less: '*'
       lightningcss: ^1.21.0
       sass: '*'
       sass-embedded: '*'
       stylus: '*'
       sugarss: '*'
-      terser: ^5.4.0
+      terser: ^5.16.0
+      tsx: ^4.8.1
+      yaml: ^2.4.2
     peerDependenciesMeta:
       '@types/node':
         optional: true
+      jiti:
+        optional: true
       less:
         optional: true
       lightningcss:
@@ -3701,6 +3709,10 @@ packages:
         optional: true
       terser:
         optional: true
+      tsx:
+        optional: true
+      yaml:
+        optional: true
 
   warning@4.0.3:
     resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==}
@@ -4235,73 +4247,76 @@ snapshots:
 
   '@dual-bundle/import-meta-resolve@4.1.0': {}
 
-  '@esbuild/aix-ppc64@0.21.5':
+  '@esbuild/aix-ppc64@0.24.0':
+    optional: true
+
+  '@esbuild/android-arm64@0.24.0':
     optional: true
 
-  '@esbuild/android-arm64@0.21.5':
+  '@esbuild/android-arm@0.24.0':
     optional: true
 
-  '@esbuild/android-arm@0.21.5':
+  '@esbuild/android-x64@0.24.0':
     optional: true
 
-  '@esbuild/android-x64@0.21.5':
+  '@esbuild/darwin-arm64@0.24.0':
     optional: true
 
-  '@esbuild/darwin-arm64@0.21.5':
+  '@esbuild/darwin-x64@0.24.0':
     optional: true
 
-  '@esbuild/darwin-x64@0.21.5':
+  '@esbuild/freebsd-arm64@0.24.0':
     optional: true
 
-  '@esbuild/freebsd-arm64@0.21.5':
+  '@esbuild/freebsd-x64@0.24.0':
     optional: true
 
-  '@esbuild/freebsd-x64@0.21.5':
+  '@esbuild/linux-arm64@0.24.0':
     optional: true
 
-  '@esbuild/linux-arm64@0.21.5':
+  '@esbuild/linux-arm@0.24.0':
     optional: true
 
-  '@esbuild/linux-arm@0.21.5':
+  '@esbuild/linux-ia32@0.24.0':
     optional: true
 
-  '@esbuild/linux-ia32@0.21.5':
+  '@esbuild/linux-loong64@0.24.0':
     optional: true
 
-  '@esbuild/linux-loong64@0.21.5':
+  '@esbuild/linux-mips64el@0.24.0':
     optional: true
 
-  '@esbuild/linux-mips64el@0.21.5':
+  '@esbuild/linux-ppc64@0.24.0':
     optional: true
 
-  '@esbuild/linux-ppc64@0.21.5':
+  '@esbuild/linux-riscv64@0.24.0':
     optional: true
 
-  '@esbuild/linux-riscv64@0.21.5':
+  '@esbuild/linux-s390x@0.24.0':
     optional: true
 
-  '@esbuild/linux-s390x@0.21.5':
+  '@esbuild/linux-x64@0.24.0':
     optional: true
 
-  '@esbuild/linux-x64@0.21.5':
+  '@esbuild/netbsd-x64@0.24.0':
     optional: true
 
-  '@esbuild/netbsd-x64@0.21.5':
+  '@esbuild/openbsd-arm64@0.24.0':
     optional: true
 
-  '@esbuild/openbsd-x64@0.21.5':
+  '@esbuild/openbsd-x64@0.24.0':
     optional: true
 
-  '@esbuild/sunos-x64@0.21.5':
+  '@esbuild/sunos-x64@0.24.0':
     optional: true
 
-  '@esbuild/win32-arm64@0.21.5':
+  '@esbuild/win32-arm64@0.24.0':
     optional: true
 
-  '@esbuild/win32-ia32@0.21.5':
+  '@esbuild/win32-ia32@0.24.0':
     optional: true
 
-  '@esbuild/win32-x64@0.21.5':
+  '@esbuild/win32-x64@0.24.0':
     optional: true
 
   '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)':
@@ -4336,48 +4351,48 @@ snapshots:
       '@floating-ui/core': 1.6.8
       '@floating-ui/utils': 0.2.8
 
-  '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
     dependencies:
       '@floating-ui/dom': 1.6.12
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
 
-  '@floating-ui/react@0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@floating-ui/react@0.26.28(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
     dependencies:
-      '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       '@floating-ui/utils': 0.2.8
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
       tabbable: 6.2.0
 
   '@floating-ui/utils@0.2.8': {}
 
   '@fontsource/sora@5.1.0': {}
 
-  '@headlessui/react@2.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@headlessui/react@2.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
     dependencies:
-      '@floating-ui/react': 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@react-aria/focus': 3.19.0(react@18.3.1)
-      '@react-aria/interactions': 3.22.5(react@18.3.1)
-      '@tanstack/react-virtual': 3.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
+      '@floating-ui/react': 0.26.28(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+      '@react-aria/focus': 3.19.0(react@19.0.0)
+      '@react-aria/interactions': 3.22.5(react@19.0.0)
+      '@tanstack/react-virtual': 3.11.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
 
   '@headlessui/tailwindcss@0.2.1(tailwindcss@3.4.16)':
     dependencies:
       tailwindcss: 3.4.16
 
-  '@hello-pangea/dnd@17.0.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@hello-pangea/dnd@17.0.0(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
     dependencies:
       '@babel/runtime': 7.26.0
       css-box-model: 1.2.1
       memoize-one: 6.0.0
       raf-schd: 4.0.3
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
-      react-redux: 9.1.2(@types/react@18.3.14)(react@18.3.1)(redux@5.0.1)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
+      react-redux: 9.1.2(@types/react@19.0.1)(react@19.0.0)(redux@5.0.1)
       redux: 5.0.1
-      use-memo-one: 1.1.3(react@18.3.1)
+      use-memo-one: 1.1.3(react@19.0.0)
     transitivePeerDependencies:
       - '@types/react'
 
@@ -4442,12 +4457,12 @@ snapshots:
       monaco-editor: 0.52.0
       state-local: 1.0.7
 
-  '@monaco-editor/react@4.6.0(monaco-editor@0.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@monaco-editor/react@4.6.0(monaco-editor@0.52.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
     dependencies:
       '@monaco-editor/loader': 1.4.0(monaco-editor@0.52.0)
       monaco-editor: 0.52.0
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
 
   '@nodelib/fs.scandir@2.1.5':
     dependencies:
@@ -4466,55 +4481,55 @@ snapshots:
   '@pkgjs/parseargs@0.11.0':
     optional: true
 
-  '@react-aria/focus@3.19.0(react@18.3.1)':
+  '@react-aria/focus@3.19.0(react@19.0.0)':
     dependencies:
-      '@react-aria/interactions': 3.22.5(react@18.3.1)
-      '@react-aria/utils': 3.26.0(react@18.3.1)
-      '@react-types/shared': 3.26.0(react@18.3.1)
+      '@react-aria/interactions': 3.22.5(react@19.0.0)
+      '@react-aria/utils': 3.26.0(react@19.0.0)
+      '@react-types/shared': 3.26.0(react@19.0.0)
       '@swc/helpers': 0.5.15
       clsx: 2.1.1
-      react: 18.3.1
+      react: 19.0.0
 
-  '@react-aria/interactions@3.22.5(react@18.3.1)':
+  '@react-aria/interactions@3.22.5(react@19.0.0)':
     dependencies:
-      '@react-aria/ssr': 3.9.7(react@18.3.1)
-      '@react-aria/utils': 3.26.0(react@18.3.1)
-      '@react-types/shared': 3.26.0(react@18.3.1)
+      '@react-aria/ssr': 3.9.7(react@19.0.0)
+      '@react-aria/utils': 3.26.0(react@19.0.0)
+      '@react-types/shared': 3.26.0(react@19.0.0)
       '@swc/helpers': 0.5.15
-      react: 18.3.1
+      react: 19.0.0
 
-  '@react-aria/ssr@3.9.7(react@18.3.1)':
+  '@react-aria/ssr@3.9.7(react@19.0.0)':
     dependencies:
       '@swc/helpers': 0.5.15
-      react: 18.3.1
+      react: 19.0.0
 
-  '@react-aria/utils@3.26.0(react@18.3.1)':
+  '@react-aria/utils@3.26.0(react@19.0.0)':
     dependencies:
-      '@react-aria/ssr': 3.9.7(react@18.3.1)
-      '@react-stately/utils': 3.10.5(react@18.3.1)
-      '@react-types/shared': 3.26.0(react@18.3.1)
+      '@react-aria/ssr': 3.9.7(react@19.0.0)
+      '@react-stately/utils': 3.10.5(react@19.0.0)
+      '@react-types/shared': 3.26.0(react@19.0.0)
       '@swc/helpers': 0.5.15
       clsx: 2.1.1
-      react: 18.3.1
+      react: 19.0.0
 
-  '@react-stately/utils@3.10.5(react@18.3.1)':
+  '@react-stately/utils@3.10.5(react@19.0.0)':
     dependencies:
       '@swc/helpers': 0.5.15
-      react: 18.3.1
+      react: 19.0.0
 
-  '@react-types/shared@3.26.0(react@18.3.1)':
+  '@react-types/shared@3.26.0(react@19.0.0)':
     dependencies:
-      react: 18.3.1
+      react: 19.0.0
 
-  '@reduxjs/toolkit@2.4.0(react-redux@9.1.2(@types/react@18.3.14)(react@18.3.1)(redux@5.0.1))(react@18.3.1)':
+  '@reduxjs/toolkit@2.4.0(react-redux@9.1.2(@types/react@19.0.1)(react@19.0.0)(redux@5.0.1))(react@19.0.0)':
     dependencies:
       immer: 10.1.1
       redux: 5.0.1
       redux-thunk: 3.1.0(redux@5.0.1)
       reselect: 5.1.1
     optionalDependencies:
-      react: 18.3.1
-      react-redux: 9.1.2(@types/react@18.3.14)(react@18.3.1)(redux@5.0.1)
+      react: 19.0.0
+      react-redux: 9.1.2(@types/react@19.0.1)(react@19.0.0)(redux@5.0.1)
 
   '@remix-run/router@1.21.0': {}
 
@@ -4673,12 +4688,12 @@ snapshots:
 
   '@sentry/core@8.42.0': {}
 
-  '@sentry/react@8.42.0(react@18.3.1)':
+  '@sentry/react@8.42.0(react@19.0.0)':
     dependencies:
       '@sentry/browser': 8.42.0
       '@sentry/core': 8.42.0
       hoist-non-react-statics: 3.3.2
-      react: 18.3.1
+      react: 19.0.0
 
   '@sentry/vite-plugin@2.22.7':
     dependencies:
@@ -4720,22 +4735,22 @@ snapshots:
 
   '@tanstack/query-devtools@5.61.4': {}
 
-  '@tanstack/react-query-devtools@5.62.3(@tanstack/react-query@5.62.3(react@18.3.1))(react@18.3.1)':
+  '@tanstack/react-query-devtools@5.62.3(@tanstack/react-query@5.62.3(react@19.0.0))(react@19.0.0)':
     dependencies:
       '@tanstack/query-devtools': 5.61.4
-      '@tanstack/react-query': 5.62.3(react@18.3.1)
-      react: 18.3.1
+      '@tanstack/react-query': 5.62.3(react@19.0.0)
+      react: 19.0.0
 
-  '@tanstack/react-query@5.62.3(react@18.3.1)':
+  '@tanstack/react-query@5.62.3(react@19.0.0)':
     dependencies:
       '@tanstack/query-core': 5.62.3
-      react: 18.3.1
+      react: 19.0.0
 
-  '@tanstack/react-virtual@3.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@tanstack/react-virtual@3.11.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
     dependencies:
       '@tanstack/virtual-core': 3.10.9
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
 
   '@tanstack/virtual-core@3.10.9': {}
 
@@ -4766,7 +4781,7 @@ snapshots:
 
   '@types/hoist-non-react-statics@3.3.6':
     dependencies:
-      '@types/react': 18.3.14
+      '@types/react': 19.0.1
       hoist-non-react-statics: 3.3.2
 
   '@types/json5@0.0.29': {}
@@ -4777,34 +4792,31 @@ snapshots:
     dependencies:
       undici-types: 6.20.0
 
-  '@types/prop-types@15.7.14': {}
-
   '@types/react-avatar-editor@13.0.3':
     dependencies:
-      '@types/react': 18.3.14
+      '@types/react': 19.0.1
 
-  '@types/react-dom@18.3.2':
+  '@types/react-dom@19.0.1':
     dependencies:
-      '@types/react': 18.3.14
+      '@types/react': 19.0.1
 
   '@types/react-grid-layout@1.3.5':
     dependencies:
-      '@types/react': 18.3.14
+      '@types/react': 19.0.1
 
   '@types/react-modal@3.16.3':
     dependencies:
-      '@types/react': 18.3.14
+      '@types/react': 19.0.1
 
   '@types/react-redux@7.1.34':
     dependencies:
       '@types/hoist-non-react-statics': 3.3.6
-      '@types/react': 18.3.14
+      '@types/react': 19.0.1
       hoist-non-react-statics: 3.3.2
       redux: 4.2.1
 
-  '@types/react@18.3.14':
+  '@types/react@19.0.1':
     dependencies:
-      '@types/prop-types': 15.7.14
       csstype: 3.1.3
 
   '@types/semver@7.5.8': {}
@@ -4933,14 +4945,14 @@ snapshots:
 
   '@ungap/structured-clone@1.2.1': {}
 
-  '@vitejs/plugin-react@4.3.4(vite@5.4.11(@types/node@22.10.1))':
+  '@vitejs/plugin-react@4.3.4(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1))':
     dependencies:
       '@babel/core': 7.26.0
       '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0)
       '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0)
       '@types/babel__core': 7.20.5
       react-refresh: 0.14.2
-      vite: 5.4.11(@types/node@22.10.1)
+      vite: 6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1)
     transitivePeerDependencies:
       - supports-color
 
@@ -5510,31 +5522,32 @@ snapshots:
       is-date-object: 1.0.5
       is-symbol: 1.1.0
 
-  esbuild@0.21.5:
+  esbuild@0.24.0:
     optionalDependencies:
-      '@esbuild/aix-ppc64': 0.21.5
-      '@esbuild/android-arm': 0.21.5
-      '@esbuild/android-arm64': 0.21.5
-      '@esbuild/android-x64': 0.21.5
-      '@esbuild/darwin-arm64': 0.21.5
-      '@esbuild/darwin-x64': 0.21.5
-      '@esbuild/freebsd-arm64': 0.21.5
-      '@esbuild/freebsd-x64': 0.21.5
-      '@esbuild/linux-arm': 0.21.5
-      '@esbuild/linux-arm64': 0.21.5
-      '@esbuild/linux-ia32': 0.21.5
-      '@esbuild/linux-loong64': 0.21.5
-      '@esbuild/linux-mips64el': 0.21.5
-      '@esbuild/linux-ppc64': 0.21.5
-      '@esbuild/linux-riscv64': 0.21.5
-      '@esbuild/linux-s390x': 0.21.5
-      '@esbuild/linux-x64': 0.21.5
-      '@esbuild/netbsd-x64': 0.21.5
-      '@esbuild/openbsd-x64': 0.21.5
-      '@esbuild/sunos-x64': 0.21.5
-      '@esbuild/win32-arm64': 0.21.5
-      '@esbuild/win32-ia32': 0.21.5
-      '@esbuild/win32-x64': 0.21.5
+      '@esbuild/aix-ppc64': 0.24.0
+      '@esbuild/android-arm': 0.24.0
+      '@esbuild/android-arm64': 0.24.0
+      '@esbuild/android-x64': 0.24.0
+      '@esbuild/darwin-arm64': 0.24.0
+      '@esbuild/darwin-x64': 0.24.0
+      '@esbuild/freebsd-arm64': 0.24.0
+      '@esbuild/freebsd-x64': 0.24.0
+      '@esbuild/linux-arm': 0.24.0
+      '@esbuild/linux-arm64': 0.24.0
+      '@esbuild/linux-ia32': 0.24.0
+      '@esbuild/linux-loong64': 0.24.0
+      '@esbuild/linux-mips64el': 0.24.0
+      '@esbuild/linux-ppc64': 0.24.0
+      '@esbuild/linux-riscv64': 0.24.0
+      '@esbuild/linux-s390x': 0.24.0
+      '@esbuild/linux-x64': 0.24.0
+      '@esbuild/netbsd-x64': 0.24.0
+      '@esbuild/openbsd-arm64': 0.24.0
+      '@esbuild/openbsd-x64': 0.24.0
+      '@esbuild/sunos-x64': 0.24.0
+      '@esbuild/win32-arm64': 0.24.0
+      '@esbuild/win32-ia32': 0.24.0
+      '@esbuild/win32-x64': 0.24.0
 
   escalade@3.2.0: {}
 
@@ -6824,120 +6837,117 @@ snapshots:
 
   raf-schd@4.0.3: {}
 
-  react-animate-height@3.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+  react-animate-height@3.2.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
     dependencies:
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
 
-  react-avatar-editor@13.0.2(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+  react-avatar-editor@13.0.2(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
     dependencies:
       '@babel/plugin-transform-runtime': 7.25.9(@babel/core@7.26.0)
       '@babel/runtime': 7.26.0
       prop-types: 15.8.1
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
     transitivePeerDependencies:
       - '@babel/core'
       - supports-color
 
-  react-dom@18.3.1(react@18.3.1):
+  react-dom@19.0.0(react@19.0.0):
     dependencies:
-      loose-envify: 1.4.0
-      react: 18.3.1
-      scheduler: 0.23.2
+      react: 19.0.0
+      scheduler: 0.25.0
 
-  react-draggable@4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+  react-draggable@4.4.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
     dependencies:
       clsx: 1.2.1
       prop-types: 15.8.1
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
 
-  react-grid-layout@1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+  react-grid-layout@1.5.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
     dependencies:
       clsx: 2.1.1
       fast-equals: 4.0.3
       prop-types: 15.8.1
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
-      react-draggable: 4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      react-resizable: 3.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
+      react-draggable: 4.4.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+      react-resizable: 3.0.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       resize-observer-polyfill: 1.5.1
 
   react-is@16.13.1: {}
 
   react-lifecycles-compat@3.0.4: {}
 
-  react-modal@3.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+  react-modal@3.16.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
     dependencies:
       exenv: 1.2.2
       prop-types: 15.8.1
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
       react-lifecycles-compat: 3.0.4
       warning: 4.0.3
 
-  react-redux@9.1.2(@types/react@18.3.14)(react@18.3.1)(redux@5.0.1):
+  react-redux@9.1.2(@types/react@19.0.1)(react@19.0.0)(redux@5.0.1):
     dependencies:
       '@types/use-sync-external-store': 0.0.3
-      react: 18.3.1
-      use-sync-external-store: 1.4.0(react@18.3.1)
+      react: 19.0.0
+      use-sync-external-store: 1.4.0(react@19.0.0)
     optionalDependencies:
-      '@types/react': 18.3.14
+      '@types/react': 19.0.1
       redux: 5.0.1
 
   react-refresh@0.14.2: {}
 
-  react-resizable@3.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+  react-resizable@3.0.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
     dependencies:
       prop-types: 15.8.1
-      react: 18.3.1
-      react-draggable: 4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react: 19.0.0
+      react-draggable: 4.4.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
     transitivePeerDependencies:
       - react-dom
 
-  react-responsive@10.0.0(react@18.3.1):
+  react-responsive@10.0.0(react@19.0.0):
     dependencies:
       hyphenate-style-name: 1.1.0
       matchmediaquery: 0.4.2
       prop-types: 15.8.1
-      react: 18.3.1
+      react: 19.0.0
       shallow-equal: 3.1.0
 
-  react-router-dom@6.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+  react-router-dom@6.28.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
     dependencies:
       '@remix-run/router': 1.21.0
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
-      react-router: 6.28.0(react@18.3.1)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
+      react-router: 6.28.0(react@19.0.0)
 
-  react-router@6.28.0(react@18.3.1):
+  react-router@6.28.0(react@19.0.0):
     dependencies:
       '@remix-run/router': 1.21.0
-      react: 18.3.1
+      react: 19.0.0
 
-  react-toastify@10.0.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+  react-toastify@10.0.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
     dependencies:
       clsx: 2.1.1
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
 
-  react-tooltip@5.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+  react-tooltip@5.28.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
     dependencies:
       '@floating-ui/dom': 1.6.12
       classnames: 2.5.1
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
 
-  react-use-measure@2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+  react-use-measure@2.1.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
     dependencies:
       debounce: 1.2.1
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
+      react: 19.0.0
+      react-dom: 19.0.0(react@19.0.0)
 
-  react@18.3.1:
-    dependencies:
-      loose-envify: 1.4.0
+  react@19.0.0: {}
 
   read-cache@1.0.0:
     dependencies:
@@ -7058,9 +7068,7 @@ snapshots:
       es-errors: 1.3.0
       is-regex: 1.2.0
 
-  scheduler@0.23.2:
-    dependencies:
-      loose-envify: 1.4.0
+  scheduler@0.25.0: {}
 
   semver@6.3.1: {}
 
@@ -7479,23 +7487,23 @@ snapshots:
       querystringify: 2.2.0
       requires-port: 1.0.0
 
-  use-immer@0.10.0(immer@10.1.1)(react@18.3.1):
+  use-immer@0.10.0(immer@10.1.1)(react@19.0.0):
     dependencies:
       immer: 10.1.1
-      react: 18.3.1
+      react: 19.0.0
 
-  use-memo-one@1.1.3(react@18.3.1):
+  use-memo-one@1.1.3(react@19.0.0):
     dependencies:
-      react: 18.3.1
+      react: 19.0.0
 
-  use-sync-external-store@1.4.0(react@18.3.1):
+  use-sync-external-store@1.4.0(react@19.0.0):
     dependencies:
-      react: 18.3.1
+      react: 19.0.0
 
-  usehooks-ts@3.1.0(react@18.3.1):
+  usehooks-ts@3.1.0(react@19.0.0):
     dependencies:
       lodash.debounce: 4.0.8
-      react: 18.3.1
+      react: 19.0.0
 
   util-deprecate@1.0.2: {}
 
@@ -7508,14 +7516,16 @@ snapshots:
     transitivePeerDependencies:
       - rollup
 
-  vite@5.4.11(@types/node@22.10.1):
+  vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1):
     dependencies:
-      esbuild: 0.21.5
+      esbuild: 0.24.0
       postcss: 8.4.49
       rollup: 4.28.1
     optionalDependencies:
       '@types/node': 22.10.1
       fsevents: 2.3.3
+      jiti: 1.21.6
+      yaml: 2.6.1
 
   warning@4.0.3:
     dependencies:
diff --git a/src/components/Collection/constants.ts b/src/components/Collection/constants.ts
index 1d5492e06..47ccbbbc9 100644
--- a/src/components/Collection/constants.ts
+++ b/src/components/Collection/constants.ts
@@ -1,6 +1,7 @@
 import type React from 'react';
 
 import type { ImageType } from '@/core/types/api/common';
+import type { SeriesType } from '@/core/types/api/series';
 
 export const posterItemSize = {
   width: 209,
@@ -18,4 +19,5 @@ export const listItemSize = {
 export type SeriesContextType = {
   backdrop?: ImageType;
   scrollRef: React.RefObject<HTMLDivElement>;
+  series: SeriesType;
 };
diff --git a/src/components/Layout/TopNav.tsx b/src/components/Layout/TopNav.tsx
index 853677704..42ced171d 100644
--- a/src/components/Layout/TopNav.tsx
+++ b/src/components/Layout/TopNav.tsx
@@ -185,7 +185,7 @@ function TopNav() {
             <NavLink
               to="settings"
               className={({ isActive }) =>
-                cx({ 'text-header-icon-primary': isActive, 'opacity-65 pointer-events-none': layoutEditMode })}
+                cx({ 'text-topnav-text-primary': isActive, 'opacity-65 pointer-events-none': layoutEditMode })}
               onClick={closeModalsAndSubmenus}
               data-tooltip-id="tooltip"
               data-tooltip-content="Settings"
@@ -348,7 +348,7 @@ function TopNav() {
               icon={mdiFileSearchOutline}
               onClick={closeModalsAndSubmenus}
               path="utilities/file-search"
-              text="Files Search"
+              text="File Search"
             />
             <LinkMenuItem
               icon={mdiFileDocumentEditOutline}
diff --git a/src/components/Utilities/ReleaseManagement/Episode.tsx b/src/components/Utilities/ReleaseManagement/Episode.tsx
new file mode 100644
index 000000000..a87d96fdd
--- /dev/null
+++ b/src/components/Utilities/ReleaseManagement/Episode.tsx
@@ -0,0 +1,175 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import { mdiOpenInNew } from '@mdi/js';
+import { Icon } from '@mdi/react';
+import { countBy, flatMap, forEach, map, toNumber } from 'lodash';
+
+import FileInfo from '@/components/FileInfo';
+import Select from '@/components/Input/Select';
+import { getEpisodePrefix } from '@/core/utilities/getEpisodePrefix';
+
+import type { ReleaseManagementOptionsType } from '@/components/Utilities/constants';
+import type { EpisodeType } from '@/core/types/api/episode';
+
+type Props = {
+  type: 'multiples' | 'duplicates';
+  episode: EpisodeType | undefined;
+  setFileOptions: (options: ReleaseManagementOptionsType) => void;
+};
+
+const Episode = ({ episode, setFileOptions, type }: Props) => {
+  const [options, setOptions] = useState<ReleaseManagementOptionsType>(
+    () => {
+      const tempOptions: ReleaseManagementOptionsType = {};
+      if (!episode) return tempOptions;
+
+      if (type === 'multiples') {
+        forEach(episode.Files, (file) => {
+          if (file.IsVariation) tempOptions[file.ID] = 'variation';
+          else tempOptions[file.ID] = 'keep';
+        });
+        return tempOptions;
+      }
+
+      forEach(
+        flatMap(episode.Files, file => file.Locations),
+        (location) => {
+          tempOptions[location.ID] = 'keep';
+        },
+      );
+      return tempOptions;
+    },
+  );
+
+  const optionCounts = useMemo(() => countBy(options), [options]);
+
+  useEffect(() => {
+    setFileOptions(options);
+  }, [options, setFileOptions]);
+
+  if (!episode) return null;
+
+  const handleOptionChange = (fileId: number, value: 'keep' | 'variation' | 'delete') => {
+    setOptions(tempOptions => (
+      { ...tempOptions, [fileId]: value }
+    ));
+  };
+
+  return (
+    <>
+      <div className="flex justify-between rounded-lg border border-panel-border bg-panel-table-header p-4 font-semibold">
+        {`${getEpisodePrefix(episode.AniDB?.Type)}${episode.AniDB?.EpisodeNumber} - ${episode.Name}`}
+        <div>
+          <span className="text-panel-text-important">{optionCounts.keep ?? 0}</span>
+          &nbsp;Kept |&nbsp;
+          {type === 'multiples' && (
+            <>
+              <span className="text-panel-text-warning">{optionCounts.variation ?? 0}</span>
+              &nbsp;Variation |&nbsp;
+            </>
+          )}
+          <span className="text-panel-text-danger">{optionCounts.delete ?? 0}</span>
+          &nbsp;Delete
+        </div>
+      </div>
+
+      {type === 'duplicates' && flatMap(episode.Files, file =>
+        map(file.Locations, (location, index) => {
+          const absolutePath = location.AbsolutePath ?? '??';
+          const fileName = absolutePath.split(/[/\\]+/).pop();
+          const folderPath = absolutePath.slice(0, absolutePath.replaceAll('\\', '/').lastIndexOf('/') + 1);
+
+          return (
+            <div
+              key={location.ID}
+              className="flex justify-between gap-x-4 rounded-lg border border-panel-border bg-panel-background-alt p-4"
+            >
+              <div className="flex flex-col gap-y-4">
+                <div className="flex flex-col gap-y-1">
+                  <div className="flex">
+                    <div className="min-w-[9.375rem] font-semibold">File Name</div>
+                    {fileName}
+                  </div>
+                  <div className="flex">
+                    <div className="min-w-[9.375rem] font-semibold">Location</div>
+                    {folderPath}
+                  </div>
+                </div>
+              </div>
+
+              <div className="flex flex-col gap-y-4">
+                <Select
+                  id="mark-variation"
+                  value={options[toNumber(`${file.ID}${index}`)]}
+                  onChange={event =>
+                    handleOptionChange(
+                      location.ID,
+                      event.target.value as 'keep' | 'delete',
+                    )}
+                >
+                  <option value="keep">Will be kept</option>
+                  <option value="delete">Will be deleted</option>
+                </Select>
+
+                {file.AniDB?.ID && (
+                  <div className="flex gap-x-2">
+                    <div className="metadata-link-icon AniDB" />
+                    <a
+                      href={`https://anidb.net/file/${file.AniDB.ID}`}
+                      target="_blank"
+                      rel="noreferrer noopener"
+                      className="flex cursor-pointer gap-x-1 font-semibold text-panel-text-primary"
+                      aria-label="Open AniDB file page"
+                    >
+                      {file.AniDB.ID}
+                      <span>(AniDB)</span>
+                      <Icon path={mdiOpenInNew} size={1} />
+                    </a>
+                  </div>
+                )}
+              </div>
+            </div>
+          );
+        }))}
+
+      {type === 'multiples' && episode.Files?.map(file => (
+        <div
+          key={file.ID}
+          className="flex justify-between gap-x-4 rounded-lg border border-panel-border bg-panel-background-alt p-4"
+        >
+          <FileInfo file={file} compact />
+
+          <div className="flex flex-col gap-y-4">
+            <Select
+              id="mark-variation"
+              value={options[file.ID]}
+              onChange={event => handleOptionChange(file.ID, event.target.value as 'keep' | 'variation' | 'delete')}
+            >
+              <option value="keep">Will be kept</option>
+              <option value="delete">Will be deleted</option>
+              <option value="variation">Marked as Variation</option>
+            </Select>
+
+            {file.AniDB?.ID && (
+              <div className="flex gap-x-2">
+                <div className="metadata-link-icon AniDB" />
+                <a
+                  href={`https://anidb.net/file/${file.AniDB.ID}`}
+                  target="_blank"
+                  rel="noreferrer noopener"
+                  className="flex cursor-pointer gap-x-1 font-semibold text-panel-text-primary"
+                  aria-label="Open AniDB file page"
+                >
+                  {file.AniDB.ID}
+                  <span>(AniDB)</span>
+                  <Icon path={mdiOpenInNew} size={1} />
+                </a>
+              </div>
+            )}
+          </div>
+        </div>
+      ))}
+    </>
+  );
+};
+
+export default Episode;
diff --git a/src/components/Utilities/ReleaseManagement/MultiplesUtilEpisode.tsx b/src/components/Utilities/ReleaseManagement/MultiplesUtilEpisode.tsx
deleted file mode 100644
index 6039efd19..000000000
--- a/src/components/Utilities/ReleaseManagement/MultiplesUtilEpisode.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import React, { useEffect, useMemo, useState } from 'react';
-import { mdiOpenInNew } from '@mdi/js';
-import { Icon } from '@mdi/react';
-import { countBy, forEach } from 'lodash';
-
-import FileInfo from '@/components/FileInfo';
-import Select from '@/components/Input/Select';
-import { getEpisodePrefix } from '@/core/utilities/getEpisodePrefix';
-
-import type { MultipleFileOptionsType } from '@/components/Utilities/constants';
-import type { EpisodeType } from '@/core/types/api/episode';
-
-type Props = {
-  episode: EpisodeType | undefined;
-  setFileOptions: (options: MultipleFileOptionsType) => void;
-};
-
-const MultiplesUtilEpisode = ({ episode, setFileOptions }: Props) => {
-  const [options, setOptions] = useState<MultipleFileOptionsType>(
-    () => {
-      const tempOptions: MultipleFileOptionsType = {};
-      if (!episode) return tempOptions;
-
-      forEach(episode.Files, (file) => {
-        if (file.IsVariation) tempOptions[file.ID] = 'variation';
-        else tempOptions[file.ID] = 'keep';
-      });
-      return tempOptions;
-    },
-  );
-
-  const optionCounts = useMemo(() => countBy(options), [options]);
-
-  useEffect(() => {
-    setFileOptions(options);
-  }, [options, setFileOptions]);
-
-  if (!episode) return null;
-
-  const handleOptionChange = (fileId: number, value: 'keep' | 'variation' | 'delete') => {
-    setOptions(tempOptions => (
-      { ...tempOptions, [fileId]: value }
-    ));
-  };
-
-  return (
-    <>
-      <div className="flex justify-between rounded-lg border border-panel-border bg-panel-table-header p-4 font-semibold">
-        {`${getEpisodePrefix(episode.AniDB?.Type)}${episode.AniDB?.EpisodeNumber} - ${episode.Name}`}
-        <div>
-          <span className="text-panel-text-important">{optionCounts.keep ?? 0}</span>
-          &nbsp;Kept |&nbsp;
-          <span className="text-panel-text-warning">{optionCounts.variation ?? 0}</span>
-          &nbsp;Variation |&nbsp;
-          <span className="text-panel-text-danger">{optionCounts.delete ?? 0}</span>
-          &nbsp;Delete
-        </div>
-      </div>
-
-      {episode.Files?.map(file => (
-        <div
-          key={file.ID}
-          className="flex justify-between gap-x-4 rounded-lg border border-panel-border bg-panel-background-alt p-4"
-        >
-          <FileInfo file={file} compact />
-
-          <div className="flex flex-col gap-y-4">
-            <Select
-              id="mark-variation"
-              value={options[file.ID]}
-              onChange={event => handleOptionChange(file.ID, event.target.value as 'keep' | 'variation' | 'delete')}
-            >
-              <option value="keep">Will be kept</option>
-              <option value="delete">Will be deleted</option>
-              <option value="variation">Marked as Variation</option>
-            </Select>
-
-            {file.AniDB?.ID && (
-              <div className="flex gap-x-2">
-                <div className="metadata-link-icon AniDB" />
-                <a
-                  href={`https://anidb.net/file/${file.AniDB.ID}`}
-                  target="_blank"
-                  rel="noreferrer noopener"
-                  className="flex cursor-pointer gap-x-1 font-semibold text-panel-text-primary"
-                  aria-label="Open AniDB file page"
-                >
-                  {file.AniDB.ID}
-                  <span>(AniDB)</span>
-                  <Icon path={mdiOpenInNew} size={1} />
-                </a>
-              </div>
-            )}
-          </div>
-        </div>
-      ))}
-    </>
-  );
-};
-
-export default MultiplesUtilEpisode;
diff --git a/src/components/Utilities/ReleaseManagement/MultiplesUtilList.tsx b/src/components/Utilities/ReleaseManagement/SeriesList.tsx
similarity index 63%
rename from src/components/Utilities/ReleaseManagement/MultiplesUtilList.tsx
rename to src/components/Utilities/ReleaseManagement/SeriesList.tsx
index 0361e2eec..d0de8c38f 100644
--- a/src/components/Utilities/ReleaseManagement/MultiplesUtilList.tsx
+++ b/src/components/Utilities/ReleaseManagement/SeriesList.tsx
@@ -1,20 +1,21 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
 import { mdiLoading, mdiOpenInNew } from '@mdi/js';
 import { Icon } from '@mdi/react';
+import { forEach } from 'lodash';
 
 import UtilitiesTable from '@/components/Utilities/UtilitiesTable';
 import {
-  useSeriesEpisodesWithMultipleReleases,
-  useSeriesWithMultipleReleases,
+  useReleaseManagementSeries,
+  useReleaseManagementSeriesEpisodes,
 } from '@/core/react-query/release-management/queries';
 import { getEpisodePrefix } from '@/core/utilities/getEpisodePrefix';
 import useFlattenListResult from '@/hooks/useFlattenListResult';
 
 import type { UtilityHeaderType } from '@/components/Utilities/constants';
 import type { EpisodeType } from '@/core/types/api/episode';
-import type { SeriesWithMultipleReleasesType } from '@/core/types/api/series';
+import type { ReleaseManagementSeriesType } from '@/core/types/api/series';
 
-const seriesColumns: UtilityHeaderType<SeriesWithMultipleReleasesType>[] = [
+const seriesColumns: UtilityHeaderType<ReleaseManagementSeriesType>[] = [
   {
     id: 'series',
     name: 'Series (AniDB ID)',
@@ -57,58 +58,76 @@ const seriesColumns: UtilityHeaderType<SeriesWithMultipleReleasesType>[] = [
   },
 ];
 
-const episodeColumns: UtilityHeaderType<EpisodeType>[] = [
-  {
-    id: 'episode',
-    name: 'Episode Name',
-    className: 'line-clamp-1 grow basis-0 overflow-hidden',
-    item: episode => (
-      <div
-        className="flex items-center gap-x-1"
-        data-tooltip-id="tooltip"
-        data-tooltip-content={episode.Name}
-      >
-        <span className="line-clamp-1">
-          {getEpisodePrefix(episode.AniDB?.Type)}
-          {episode.AniDB?.EpisodeNumber}
-          &nbsp;-&nbsp;
-          {episode.Name}
-        </span>
-        <div>
-          (
-          <span className="text-panel-text-primary">{episode.IDs.AniDB}</span>
-          )
-        </div>
-        <a
-          href={`https://anidb.net/episode/${episode.IDs.AniDB}`}
-          target="_blank"
-          rel="noreferrer noopener"
-          className="cursor-pointer text-panel-text-primary"
-          aria-label="Open AniDB episode page"
-          onClick={event => event.stopPropagation()}
-        >
-          <Icon path={mdiOpenInNew} size={1} />
-        </a>
+const episodeNameColumn: UtilityHeaderType<EpisodeType> = {
+  id: 'episode',
+  name: 'Episode Name',
+  className: 'line-clamp-1 grow basis-0 overflow-hidden',
+  item: episode => (
+    <div
+      className="flex items-center gap-x-1"
+      data-tooltip-id="tooltip"
+      data-tooltip-content={episode.Name}
+    >
+      <span className="line-clamp-1">
+        {getEpisodePrefix(episode.AniDB?.Type)}
+        {episode.AniDB?.EpisodeNumber}
+        &nbsp;-&nbsp;
+        {episode.Name}
+      </span>
+      <div>
+        (
+        <span className="text-panel-text-primary">{episode.IDs.AniDB}</span>
+        )
       </div>
-    ),
+      <a
+        href={`https://anidb.net/episode/${episode.IDs.AniDB}`}
+        target="_blank"
+        rel="noreferrer noopener"
+        className="cursor-pointer text-panel-text-primary"
+        aria-label="Open AniDB episode page"
+        onClick={event => event.stopPropagation()}
+      >
+        <Icon path={mdiOpenInNew} size={1} />
+      </a>
+    </div>
+  ),
+};
+
+const multiplesEpisodeFileCountColumn: UtilityHeaderType<EpisodeType> = {
+  id: 'file-count',
+  name: 'File Count',
+  className: 'w-28',
+  item: (episode) => {
+    const count = episode.Files?.length ?? 0;
+    return (
+      <>
+        <span className="text-panel-text-important">{count}</span>
+        {count === 1 ? ' File' : ' Files'}
+      </>
+    );
   },
-  {
-    id: 'file-count',
-    name: 'File Count',
-    className: 'w-28',
-    item: (episode) => {
-      const count = episode.Files?.length ?? 0;
-      return (
-        <>
-          <span className="text-panel-text-important">{count}</span>
-          {count === 1 ? ' File' : ' Files'}
-        </>
-      );
-    },
+};
+
+const duplicatesEpisodeFileCountColumn: UtilityHeaderType<EpisodeType> = {
+  id: 'duplicate-count',
+  name: 'Duplicate Count',
+  className: 'w-40',
+  item: (episode) => {
+    let count = 0;
+    forEach(episode.Files, (file) => {
+      if (file.Locations.length > 1) count += 1;
+    });
+    return (
+      <>
+        <span className="text-panel-text-important">{count}</span>
+        {count === 1 ? ' Duplicate' : ' Duplicates'}
+      </>
+    );
   },
-];
+};
 
 type Props = {
+  type: 'multiples' | 'duplicates';
   ignoreVariations: boolean;
   onlyFinishedSeries: boolean;
   setSelectedEpisode: (episode: EpisodeType) => void;
@@ -116,15 +135,16 @@ type Props = {
   setSeriesCount: (count: number) => void;
 };
 
-const MultiplesUtilList = (
-  { ignoreVariations, onlyFinishedSeries, setSelectedEpisode, setSelectedSeriesId, setSeriesCount }: Props,
+const SeriesList = (
+  { ignoreVariations, onlyFinishedSeries, setSelectedEpisode, setSelectedSeriesId, setSeriesCount, type }: Props,
 ) => {
   const [selectedSeries, setSelectedSeries] = useState(0);
 
-  const seriesQuery = useSeriesWithMultipleReleases({ ignoreVariations, onlyFinishedSeries, pageSize: 25 });
+  const seriesQuery = useReleaseManagementSeries(type, { ignoreVariations, onlyFinishedSeries, pageSize: 25 });
   const [series, seriesCount] = useFlattenListResult(seriesQuery.data);
 
-  const episodesQuery = useSeriesEpisodesWithMultipleReleases(
+  const episodesQuery = useReleaseManagementSeriesEpisodes(
+    type,
     selectedSeries,
     { ignoreVariations, includeDataFrom: ['AniDB'], includeAbsolutePaths: true, pageSize: 25 },
     selectedSeries > 0,
@@ -141,6 +161,11 @@ const MultiplesUtilList = (
     setSelectedSeries(0);
   }, [seriesQuery.data]);
 
+  const episodeColumns = useMemo(() => [
+    episodeNameColumn,
+    type === 'multiples' ? multiplesEpisodeFileCountColumn : duplicatesEpisodeFileCountColumn,
+  ], [type]);
+
   return (
     <>
       <div className="flex w-1/2 overflow-y-auto rounded-md border border-panel-border bg-panel-background p-6">
@@ -152,7 +177,8 @@ const MultiplesUtilList = (
 
         {!seriesQuery.isPending && seriesCount === 0 && (
           <div className="flex grow items-center justify-center text-lg font-semibold">
-            No series with multiple files!
+            No series with
+            {type === 'multiples' ? ' multiple releases!' : ' duplicate files!'}
           </div>
         )}
 
@@ -196,4 +222,4 @@ const MultiplesUtilList = (
   );
 };
 
-export default MultiplesUtilList;
+export default SeriesList;
diff --git a/src/components/Utilities/ReleaseManagement/Title.tsx b/src/components/Utilities/ReleaseManagement/Title.tsx
index 4300cd9fa..1fdcb7089 100644
--- a/src/components/Utilities/ReleaseManagement/Title.tsx
+++ b/src/components/Utilities/ReleaseManagement/Title.tsx
@@ -19,8 +19,8 @@ const Title = () => (
     Release Management
     <Icon path={mdiChevronRight} size={1} />
     <TabButton id="multiples" name="Multiples" />
-    {/* <div>|</div> */}
-    {/* <TabButton id="duplicates" name="Duplicates" /> */}
+    <div>|</div>
+    <TabButton id="duplicates" name="Duplicates" />
   </div>
 );
 
diff --git a/src/components/Utilities/constants.tsx b/src/components/Utilities/constants.tsx
index 476a8c2cd..4bad515c9 100644
--- a/src/components/Utilities/constants.tsx
+++ b/src/components/Utilities/constants.tsx
@@ -6,16 +6,16 @@ import { dayjs } from '@/core/util';
 
 import type { EpisodeType } from '@/core/types/api/episode';
 import type { FileType } from '@/core/types/api/file';
-import type { SeriesType, SeriesWithMultipleReleasesType } from '@/core/types/api/series';
+import type { ReleaseManagementSeriesType, SeriesType } from '@/core/types/api/series';
 
-export type UtilityHeaderType<T extends EpisodeType | FileType | SeriesType | SeriesWithMultipleReleasesType> = {
+export type UtilityHeaderType<T extends EpisodeType | FileType | SeriesType | ReleaseManagementSeriesType> = {
   id: string;
   name: string;
   className: string;
   item: (_: T) => React.ReactNode;
 };
 
-export type MultipleFileOptionsType = Record<number, 'keep' | 'variation' | 'delete'>;
+export type ReleaseManagementOptionsType = Record<number, 'keep' | 'variation' | 'delete'>;
 
 export const criteriaMap = {
   importFolder: FileSortCriteriaEnum.ImportFolderName,
diff --git a/src/core/react-query/file/mutations.ts b/src/core/react-query/file/mutations.ts
index 90c4894ef..802187c9b 100644
--- a/src/core/react-query/file/mutations.ts
+++ b/src/core/react-query/file/mutations.ts
@@ -6,6 +6,7 @@ import { axios } from '@/core/axios';
 import queryClient, { invalidateQueries } from '@/core/react-query/queryClient';
 
 import type {
+  DeleteFileLocationRequestType,
   DeleteFileRequestType,
   DeleteFilesRequestType,
   IgnoreFileRequestType,
@@ -68,6 +69,11 @@ export const useDeleteFileLinkMutation = () =>
       ),
   });
 
+export const useDeleteFileLocationMutation = () =>
+  useMutation({
+    mutationFn: ({ locationId }: DeleteFileLocationRequestType) => axios.delete(`File/Location/${locationId}`),
+  });
+
 export const useIgnoreFileMutation = () =>
   useMutation({
     mutationFn: ({ fileId, ignore }: IgnoreFileRequestType) =>
diff --git a/src/core/react-query/file/types.ts b/src/core/react-query/file/types.ts
index 2e039bfd7..c61d67b3e 100644
--- a/src/core/react-query/file/types.ts
+++ b/src/core/react-query/file/types.ts
@@ -8,6 +8,12 @@ export type DeleteFileRequestType = {
   removeFolder: boolean;
 };
 
+export type DeleteFileLocationRequestType = {
+  locationId: number;
+  deleteFile?: boolean;
+  deleteFolder?: boolean;
+};
+
 export type IgnoreFileRequestType = {
   fileId: number;
   ignore: boolean;
diff --git a/src/core/react-query/release-management/queries.ts b/src/core/react-query/release-management/queries.ts
index 9d4b6e35e..1ad7de7db 100644
--- a/src/core/react-query/release-management/queries.ts
+++ b/src/core/react-query/release-management/queries.ts
@@ -3,19 +3,22 @@ import { useInfiniteQuery } from '@tanstack/react-query';
 import { axios } from '@/core/axios';
 
 import type {
-  SeriesEpisodesWithMultipleReleasesType,
-  SeriesWithMultipleReleasesRequestType,
+  ReleaseManagementSeriesEpisodesType,
+  ReleaseManagementSeriesRequestType,
 } from '@/core/react-query/release-management/types';
 import type { ListResultType } from '@/core/types/api';
 import type { EpisodeType } from '@/core/types/api/episode';
-import type { SeriesWithMultipleReleasesType } from '@/core/types/api/series';
+import type { ReleaseManagementSeriesType } from '@/core/types/api/series';
 
-export const useSeriesWithMultipleReleases = (params: SeriesWithMultipleReleasesRequestType) =>
-  useInfiniteQuery<ListResultType<SeriesWithMultipleReleasesType>>({
-    queryKey: ['release-management', 'series', params],
+export const useReleaseManagementSeries = (
+  type: 'multiples' | 'duplicates',
+  params: ReleaseManagementSeriesRequestType,
+) =>
+  useInfiniteQuery<ListResultType<ReleaseManagementSeriesType>>({
+    queryKey: ['release-management', 'series', type, params],
     queryFn: ({ pageParam }) =>
       axios.get(
-        'ReleaseManagement/Series',
+        `ReleaseManagement/${type === 'multiples' ? 'MultipleReleases' : 'DuplicateFiles'}/Series`,
         {
           params: {
             ...params,
@@ -31,16 +34,17 @@ export const useSeriesWithMultipleReleases = (params: SeriesWithMultipleReleases
     },
   });
 
-export const useSeriesEpisodesWithMultipleReleases = (
+export const useReleaseManagementSeriesEpisodes = (
+  type: 'multiples' | 'duplicates',
   seriesId: number,
-  params: SeriesEpisodesWithMultipleReleasesType,
+  params: ReleaseManagementSeriesEpisodesType,
   enabled = true,
 ) =>
   useInfiniteQuery<ListResultType<EpisodeType>>({
-    queryKey: ['release-management', 'series', 'episodes', seriesId, params],
+    queryKey: ['release-management', 'series', 'episodes', type, seriesId, params],
     queryFn: ({ pageParam }) =>
       axios.get(
-        `ReleaseManagement/Series/${seriesId}`,
+        `ReleaseManagement/${type === 'multiples' ? 'MultipleReleases' : 'DuplicateFiles'}/Series/${seriesId}/Episodes`,
         {
           params: {
             ...params,
diff --git a/src/core/react-query/release-management/types.ts b/src/core/react-query/release-management/types.ts
index 262b71e92..17b4575e6 100644
--- a/src/core/react-query/release-management/types.ts
+++ b/src/core/react-query/release-management/types.ts
@@ -1,13 +1,13 @@
 import type { PaginationType } from '@/core/types/api';
 import type { DataSourceType } from '@/core/types/api/common';
 
-export type SeriesWithMultipleReleasesRequestType = {
+export type ReleaseManagementSeriesRequestType = {
   ignoreVariations?: boolean;
   includeDataFrom?: DataSourceType[];
   onlyFinishedSeries?: boolean;
 } & PaginationType;
 
-export type SeriesEpisodesWithMultipleReleasesType = {
+export type ReleaseManagementSeriesEpisodesType = {
   includeDataFrom?: DataSourceType[];
   includeFiles?: boolean;
   includeMediaInfo?: boolean;
diff --git a/src/core/router/index.tsx b/src/core/router/index.tsx
index 0cc0a0c53..d9db4e36e 100644
--- a/src/core/router/index.tsx
+++ b/src/core/router/index.tsx
@@ -41,7 +41,7 @@ import MetadataSitesSettings from '@/pages/settings/tabs/MetadataSitesSettings';
 import UserManagementSettings from '@/pages/settings/tabs/UserManagementSettings';
 import UnsupportedPage from '@/pages/unsupported/UnsupportedPage';
 import FileSearch from '@/pages/utilities/FileSearch';
-import MultiplesUtil from '@/pages/utilities/ReleaseManagementUtilityTabs/MultiplesUtil';
+import ReleaseManagement from '@/pages/utilities/ReleaseManagement';
 import Renamer from '@/pages/utilities/Renamer';
 import SeriesWithoutFilesUtility from '@/pages/utilities/SeriesWithoutFilesUtility';
 import IgnoredFilesTab from '@/pages/utilities/UnrecognizedUtilityTabs/IgnoredFilesTab';
@@ -91,7 +91,8 @@ const router = sentryCreateBrowserRouter(
             <Route path="unrecognized/manually-linked-files" element={<ManuallyLinkedTab />} />
             <Route path="unrecognized/ignored-files" element={<IgnoredFilesTab />} />
             <Route path="release-management" element={<Navigate to="multiples" replace />} />
-            <Route path="release-management/multiples" element={<MultiplesUtil />} />
+            <Route path="release-management/multiples" element={<ReleaseManagement type="multiples" />} />
+            <Route path="release-management/duplicates" element={<ReleaseManagement type="duplicates" />} />
             <Route path="series-without-files" element={<SeriesWithoutFilesUtility />} />
             <Route path="file-search" element={<FileSearch />} />
             <Route path="renamer" element={<Renamer />} />
diff --git a/src/core/types/api/file.ts b/src/core/types/api/file.ts
index 6676d853f..0acf23146 100644
--- a/src/core/types/api/file.ts
+++ b/src/core/types/api/file.ts
@@ -9,6 +9,8 @@ type XRefsType = {
 };
 
 type FileTypeLocation = {
+  ID: number;
+  FileID: number;
   ImportFolderID: number;
   RelativePath: string;
   AbsolutePath?: string;
diff --git a/src/core/types/api/series.ts b/src/core/types/api/series.ts
index acde6fff3..243590d7d 100644
--- a/src/core/types/api/series.ts
+++ b/src/core/types/api/series.ts
@@ -19,7 +19,7 @@ export type SeriesType = {
   };
 };
 
-export type SeriesWithMultipleReleasesType = {
+export type ReleaseManagementSeriesType = {
   EpisodeCount: number;
 } & SeriesType;
 
diff --git a/src/pages/collection/Collection.tsx b/src/pages/collection/Collection.tsx
index 2b1b29ca3..28b8172d8 100644
--- a/src/pages/collection/Collection.tsx
+++ b/src/pages/collection/Collection.tsx
@@ -219,57 +219,60 @@ function Collection() {
   });
 
   return (
-    <div className="flex grow flex-col gap-y-6">
-      <div className="sticky -top-6 z-10 flex items-center justify-between rounded-lg border border-panel-border bg-panel-background p-6">
-        <CollectionTitle
-          // eslint-disable-next-line no-nested-ternary
-          count={(total === 0 && isFetching) ? -1 : (isSeries ? total : groupsTotal)}
-          filterName={filterQuery?.data?.Name}
-          groupName={groupQuery?.data?.Name}
-          filterActive={!!activeFilter}
-          searchQuery={isSeries ? seriesSearch : groupSearch}
-        />
-        <TitleOptions
-          groupSearch={groupSearch}
-          isSeries={isSeries}
-          item={item}
-          mode={mode}
-          seriesSearch={seriesSearch}
-          setSearch={setSearch}
-          toggleFilterSidebar={handleFilterSidebarToggle}
-          toggleMode={toggleMode}
-        />
-      </div>
-      <div className="flex grow">
-        <CollectionView
-          groupExtras={groupExtras ?? []}
-          fetchNextPage={groupsQuery.fetchNextPage}
-          isFetchingNextPage={groupsQuery.isFetchingNextPage}
-          isFetching={isFetching}
-          isSeries={isSeries}
-          isSidebarOpen={showFilterSidebar}
-          items={items}
-          mode={mode}
-          total={total}
-        />
-        <div
-          className={cx(
-            'flex items-start',
-            !isSeries && 'transition-all',
-            showFilterSidebar
-              ? 'w-[28rem] opacity-100'
-              : 'w-0 opacity-0 overflow-hidden ',
+    <>
+      <title>{`${isSeries ? groupQuery?.data?.Name : 'Collection'} | Shoko`}</title>
+      <div className="flex grow flex-col gap-y-6">
+        <div className="sticky -top-6 z-10 flex items-center justify-between rounded-lg border border-panel-border bg-panel-background p-6">
+          <CollectionTitle
+            // eslint-disable-next-line no-nested-ternary
+            count={(total === 0 && isFetching) ? -1 : (isSeries ? total : groupsTotal)}
+            filterName={filterQuery?.data?.Name}
+            groupName={groupQuery?.data?.Name}
+            filterActive={!!activeFilter}
+            searchQuery={isSeries ? seriesSearch : groupSearch}
+          />
+          <TitleOptions
+            groupSearch={groupSearch}
+            isSeries={isSeries}
+            item={item}
+            mode={mode}
+            seriesSearch={seriesSearch}
+            setSearch={setSearch}
+            toggleFilterSidebar={handleFilterSidebarToggle}
+            toggleMode={toggleMode}
+          />
+        </div>
+        <div className="flex grow">
+          <CollectionView
+            groupExtras={groupExtras ?? []}
+            fetchNextPage={groupsQuery.fetchNextPage}
+            isFetchingNextPage={groupsQuery.isFetchingNextPage}
+            isFetching={isFetching}
+            isSeries={isSeries}
+            isSidebarOpen={showFilterSidebar}
+            items={items}
+            mode={mode}
+            total={total}
+          />
+          <div
+            className={cx(
+              'flex items-start',
+              !isSeries && 'transition-all',
+              showFilterSidebar
+                ? 'w-[28rem] opacity-100'
+                : 'w-0 opacity-0 overflow-hidden ',
+            )}
+          >
+            <FilterSidebar />
+          </div>
+          {isSeries && !showFilterSidebar && (
+            <TimelineSidebar series={timelineSeries} isFetching={seriesQuery.isPending} />
           )}
-        >
-          <FilterSidebar />
         </div>
-        {isSeries && !showFilterSidebar && (
-          <TimelineSidebar series={timelineSeries} isFetching={seriesQuery.isPending} />
-        )}
+        <EditSeriesModal />
+        <EditGroupModal />
       </div>
-      <EditSeriesModal />
-      <EditGroupModal />
-    </div>
+    </>
   );
 }
 
diff --git a/src/pages/collection/Series.tsx b/src/pages/collection/Series.tsx
index b1322bff0..b6228cbb7 100644
--- a/src/pages/collection/Series.tsx
+++ b/src/pages/collection/Series.tsx
@@ -155,7 +155,7 @@ const Series = () => {
 
       <EditSeriesModal />
 
-      <Outlet context={{ backdrop, scrollRef } satisfies SeriesContextType} />
+      <Outlet context={{ backdrop, scrollRef, series } satisfies SeriesContextType} />
 
       <div
         className="fixed left-0 top-0 -z-10 w-full bg-cover bg-fixed opacity-5"
diff --git a/src/pages/collection/series/SeriesCredits.tsx b/src/pages/collection/series/SeriesCredits.tsx
index 562b7649a..d6d2b155f 100644
--- a/src/pages/collection/series/SeriesCredits.tsx
+++ b/src/pages/collection/series/SeriesCredits.tsx
@@ -1,6 +1,5 @@
 import React, { useMemo, useState } from 'react';
-import { useParams } from 'react-router';
-import { toNumber } from 'lodash';
+import { useOutletContext } from 'react-router-dom';
 
 import CreditsSearchAndFilterPanel from '@/components/Collection/Credits/CreditsSearchAndFilterPanel';
 import StaffPanelVirtualizer from '@/components/Collection/Credits/CreditsStaffVirtualizer';
@@ -10,6 +9,7 @@ import { useRefreshSeriesAniDBInfoMutation } from '@/core/react-query/series/mut
 import { useSeriesCastQuery } from '@/core/react-query/series/queries';
 import useEventCallback from '@/hooks/useEventCallback';
 
+import type { SeriesContextType } from '@/components/Collection/constants';
 import type { SeriesCast } from '@/core/types/api/series';
 
 export type CreditsModeType = 'Character' | 'Staff';
@@ -24,12 +24,12 @@ const modeStates: { label?: string, value: CreditsModeType }[] = [
 ];
 
 const SeriesCredits = () => {
-  const { seriesId } = useParams();
+  const { series } = useOutletContext<SeriesContextType>();
 
   const { isPending: pendingRefreshAniDb, mutate: refreshAniDbMutation } = useRefreshSeriesAniDBInfoMutation();
 
   const refreshAniDb = useEventCallback(() => {
-    refreshAniDbMutation({ seriesId: toNumber(seriesId), force: true }, {
+    refreshAniDbMutation({ seriesId: series.IDs.ID, force: true }, {
       onSuccess: () => toast.success('AniDB refresh queued!'),
     });
   });
@@ -61,7 +61,7 @@ const SeriesCredits = () => {
     setSearch(event.target.value);
   });
 
-  const cast = useSeriesCastQuery(toNumber(seriesId!), !!seriesId).data;
+  const cast = useSeriesCastQuery(series.IDs.ID).data;
   const castByType = useMemo(() => ({
     Character: cast?.filter(credit => credit.RoleName === 'Seiyuu') ?? [],
     Staff: cast?.filter(credit => credit.RoleName !== 'Seiyuu') ?? [],
@@ -84,47 +84,48 @@ const SeriesCredits = () => {
     return 0;
   })), [castByType, mode, search, roleFilter]);
 
-  if (!seriesId) return null;
-
   return (
-    <div className="flex w-full gap-x-6">
-      <div className="flex flex-col gap-y-6">
-        <CreditsSearchAndFilterPanel
-          inputPlaceholder={mode === 'Character' ? 'Character or Seiyuu\'s Name...' : 'Staff Name...'}
-          search={search}
-          roleFilter={roleFilter}
-          uniqueRoles={uniqueRoles[mode]}
-          handleSearchChange={handleSearchChange}
-          handleFilterChange={handleFilterChange}
-          refreshAniDbAction={refreshAniDb}
-          aniDbRefreshing={pendingRefreshAniDb}
-        />
-      </div>
+    <>
+      <title>{`${series.Name} > Credits | Shoko`}</title>
+      <div className="flex w-full gap-x-6">
+        <div className="flex flex-col gap-y-6">
+          <CreditsSearchAndFilterPanel
+            inputPlaceholder={mode === 'Character' ? 'Character or Seiyuu\'s Name...' : 'Staff Name...'}
+            search={search}
+            roleFilter={roleFilter}
+            uniqueRoles={uniqueRoles[mode]}
+            handleSearchChange={handleSearchChange}
+            handleFilterChange={handleFilterChange}
+            refreshAniDbAction={refreshAniDb}
+            aniDbRefreshing={pendingRefreshAniDb}
+          />
+        </div>
 
-      <div className="flex w-full grow flex-col gap-x-6 gap-y-4">
-        <div className="flex h-[6.125rem] items-center justify-between rounded-lg border border-panel-border bg-panel-background-transparent px-6 py-4">
-          <div className="text-xl font-semibold">
-            Credits |&nbsp;
-            {(search !== '' || roleFilter.size > 0) && (
-              <>
-                <span className="text-panel-text-important">
-                  {filteredCast.length}
-                </span>
-                &nbsp;of&nbsp;
-              </>
-            )}
-            <span className="text-panel-text-important">
-              {castByType[mode].length ?? 0}
-            </span>
-            &nbsp;
-            {mode === 'Character' ? 'Characters' : mode}
-            &nbsp;Listed
+        <div className="flex w-full grow flex-col gap-x-6 gap-y-4">
+          <div className="flex h-[6.125rem] items-center justify-between rounded-lg border border-panel-border bg-panel-background-transparent px-6 py-4">
+            <div className="text-xl font-semibold">
+              Credits |&nbsp;
+              {(search !== '' || roleFilter.size > 0) && (
+                <>
+                  <span className="text-panel-text-important">
+                    {filteredCast.length}
+                  </span>
+                  &nbsp;of&nbsp;
+                </>
+              )}
+              <span className="text-panel-text-important">
+                {castByType[mode].length ?? 0}
+              </span>
+              &nbsp;
+              {mode === 'Character' ? 'Characters' : mode}
+              &nbsp;Listed
+            </div>
+            <MultiStateButton activeState={mode} states={modeStates} onStateChange={handleModeChange} />
           </div>
-          <MultiStateButton activeState={mode} states={modeStates} onStateChange={handleModeChange} />
+          <StaffPanelVirtualizer castArray={filteredCast} mode={mode} />
         </div>
-        <StaffPanelVirtualizer castArray={filteredCast} mode={mode} />
       </div>
-    </div>
+    </>
   );
 };
 
diff --git a/src/pages/collection/series/SeriesEpisodes.tsx b/src/pages/collection/series/SeriesEpisodes.tsx
index 134475715..ae76ac647 100644
--- a/src/pages/collection/series/SeriesEpisodes.tsx
+++ b/src/pages/collection/series/SeriesEpisodes.tsx
@@ -1,10 +1,9 @@
 import React, { useEffect, useMemo, useState } from 'react';
-import { useParams } from 'react-router';
 import { useOutletContext, useSearchParams } from 'react-router-dom';
 import { mdiCloseCircleOutline, mdiEyeOutline, mdiLoading } from '@mdi/js';
 import { Icon } from '@mdi/react';
 import { useVirtualizer } from '@tanstack/react-virtual';
-import { debounce, toNumber } from 'lodash';
+import { debounce } from 'lodash';
 import { useDebounceValue } from 'usehooks-ts';
 
 import EpisodeSearchAndFilterPanel from '@/components/Collection/Episode/EpisodeSearchAndFilterPanel';
@@ -13,7 +12,7 @@ import EpisodeWatchModal from '@/components/Collection/Episode/EpisodeWatchModal
 import Button from '@/components/Input/Button';
 import toast from '@/components/Toast';
 import { useWatchSeriesEpisodesMutation } from '@/core/react-query/series/mutations';
-import { useSeriesEpisodesInfiniteQuery, useSeriesQuery } from '@/core/react-query/series/queries';
+import { useSeriesEpisodesInfiniteQuery } from '@/core/react-query/series/queries';
 import { IncludeOnlyFilterEnum } from '@/core/react-query/series/types';
 import { EpisodeTypeEnum } from '@/core/types/api/episode';
 import { dayjs } from '@/core/util';
@@ -34,7 +33,8 @@ type FilterOptionsType = {
 };
 
 const SeriesEpisodes = () => {
-  const { seriesId } = useParams();
+  const { series } = useOutletContext<SeriesContextType>();
+
   const [searchParams, setSearchParams] = useSearchParams();
 
   const [showOptionsModal, setShowOptionsModal] = useState(false);
@@ -71,15 +71,13 @@ const SeriesEpisodes = () => {
     setSelectedEpisodes(new Set());
   }, [filterOptions]);
 
-  const seriesQueryData = useSeriesQuery(toNumber(seriesId!), { includeDataFrom: ['AniDB', 'TMDB'] }, !!seriesId).data;
   const seriesEpisodesQuery = useSeriesEpisodesInfiniteQuery(
-    toNumber(seriesId!),
+    series.IDs.ID,
     {
       pageSize,
       includeDataFrom: ['AniDB'],
       ...filterOptions,
     },
-    !!seriesId,
   );
   const {
     data,
@@ -92,20 +90,18 @@ const SeriesEpisodes = () => {
 
   const { mutate: watchEpisode } = useWatchSeriesEpisodesMutation();
 
-  const anidbSeriesId = useMemo(() => seriesQueryData?.IDs.AniDB ?? 0, [seriesQueryData]);
-
   const hasMissingEpisodes = useMemo(
-    () => ((seriesQueryData?.Sizes.Missing.Episodes ?? 0) > 0),
-    [seriesQueryData?.Sizes],
+    () => ((series.Sizes.Missing.Episodes ?? 0) > 0),
+    [series.Sizes],
   );
 
   const startDate = useMemo(
-    () => (seriesQueryData?.AniDB?.AirDate != null ? dayjs(seriesQueryData?.AniDB?.AirDate) : null),
-    [seriesQueryData],
+    () => (series.AniDB?.AirDate != null ? dayjs(series.AniDB?.AirDate) : null),
+    [series],
   );
   const endDate = useMemo(
-    () => (seriesQueryData?.AniDB?.EndDate != null ? dayjs(seriesQueryData?.AniDB?.EndDate) : null),
-    [seriesQueryData],
+    () => (series.AniDB?.EndDate != null ? dayjs(series.AniDB?.EndDate) : null),
+    [series],
   );
   const hasUnairedEpisodes = useMemo(
     () => (!!startDate && (endDate === null || endDate.isAfter(dayjs()))),
@@ -133,7 +129,7 @@ const SeriesEpisodes = () => {
 
   const handleMarkWatched = useEventCallback((watched: boolean) => {
     watchEpisode({
-      seriesId: toNumber(seriesId),
+      seriesId: series.IDs.ID,
       value: watched,
       ...filterOptions,
     }, {
@@ -150,105 +146,108 @@ const SeriesEpisodes = () => {
   const openOptionsModal = useEventCallback(() => setShowOptionsModal(true));
 
   return (
-    <div className="flex w-full gap-x-6">
-      <EpisodeSearchAndFilterPanel
-        onFilterChange={onFilterChange}
-        search={filterOptions.search}
-        type={filterOptions.type[0]}
-        availability={filterOptions.includeMissing}
-        watched={filterOptions.includeWatched}
-        hidden={filterOptions.includeHidden}
-        unaired={filterOptions.includeUnaired}
-        hasUnaired={hasUnairedEpisodes}
-        hasMissing={hasMissingEpisodes}
-      />
-      <div className="flex grow flex-col gap-y-4">
-        <div className="flex h-[6.125rem] items-center justify-between rounded-lg border border-panel-border bg-panel-background-transparent px-6 py-4">
-          <div className="flex flex-wrap text-xl font-semibold 2xl:flex-nowrap">
-            <span>Episodes</span>
-            <span className="hidden px-2 2xl:inline">|</span>
-            <span>
-              <span className="pr-2 text-panel-text-important">
-                {isSuccess ? episodeCount : '-'}
+    <>
+      <title>{`${series.Name} > Episodes | Shoko`}</title>
+      <div className="flex w-full gap-x-6">
+        <EpisodeSearchAndFilterPanel
+          onFilterChange={onFilterChange}
+          search={filterOptions.search}
+          type={filterOptions.type[0]}
+          availability={filterOptions.includeMissing}
+          watched={filterOptions.includeWatched}
+          hidden={filterOptions.includeHidden}
+          unaired={filterOptions.includeUnaired}
+          hasUnaired={hasUnairedEpisodes}
+          hasMissing={hasMissingEpisodes}
+        />
+        <div className="flex grow flex-col gap-y-4">
+          <div className="flex h-[6.125rem] items-center justify-between rounded-lg border border-panel-border bg-panel-background-transparent px-6 py-4">
+            <div className="flex flex-wrap text-xl font-semibold 2xl:flex-nowrap">
+              <span>Episodes</span>
+              <span className="hidden px-2 2xl:inline">|</span>
+              <span>
+                <span className="pr-2 text-panel-text-important">
+                  {isSuccess ? episodeCount : '-'}
+                </span>
+                Entries Listed
+                {selectedEpisodes.size > 0 && (
+                  <>
+                    &nbsp;|&nbsp;
+                    <span className="text-panel-text-important">
+                      {selectedEpisodes.size}
+                    </span>
+                    &nbsp;Entries Selected
+                  </>
+                )}
               </span>
-              Entries Listed
+            </div>
+            <div className="flex flex-row gap-x-2">
               {selectedEpisodes.size > 0 && (
-                <>
-                  &nbsp;|&nbsp;
-                  <span className="text-panel-text-important">
-                    {selectedEpisodes.size}
-                  </span>
-                  &nbsp;Entries Selected
-                </>
+                <Button buttonType="secondary" buttonSize="normal" className="flex gap-x-2" onClick={resetSelection}>
+                  <Icon path={mdiCloseCircleOutline} size={1} />
+                  Cancel Selection
+                </Button>
               )}
-            </span>
-          </div>
-          <div className="flex flex-row gap-x-2">
-            {selectedEpisodes.size > 0 && (
-              <Button buttonType="secondary" buttonSize="normal" className="flex gap-x-2" onClick={resetSelection}>
-                <Icon path={mdiCloseCircleOutline} size={1} />
-                Cancel Selection
+              <Button buttonType="secondary" buttonSize="normal" className="flex gap-x-2" onClick={openOptionsModal}>
+                <Icon path={mdiEyeOutline} size={1} />
+                Options
               </Button>
-            )}
-            <Button buttonType="secondary" buttonSize="normal" className="flex gap-x-2" onClick={openOptionsModal}>
-              <Icon path={mdiEyeOutline} size={1} />
-              Options
-            </Button>
+            </div>
+          </div>
+          <div className="grow">
+            {isPending
+              ? (
+                <div className="flex h-full items-center justify-center text-panel-text-primary">
+                  <Icon path={mdiLoading} spin size={4} />
+                </div>
+              )
+              : (
+                <div className="relative w-full" style={{ height: rowVirtualizer.getTotalSize() }}>
+                  {virtualItems.map((virtualItem) => {
+                    const page = Math.ceil((virtualItem.index + 1) / pageSize);
+                    const episode = episodes[virtualItem.index];
+
+                    if (!episode && !isFetchingNextPage) fetchNextPageDebounced();
+
+                    return (
+                      <div
+                        key={episode ? episode.IDs.ID : `loading-${virtualItem.key}`}
+                        className="absolute left-0 top-0 flex w-full flex-col rounded-lg border border-panel-border bg-panel-background-transparent"
+                        data-index={virtualItem.index}
+                        style={{ transform: `translateY(${virtualItem.start ?? 0}px)` }}
+                        ref={rowVirtualizer.measureElement}
+                      >
+                        {episode
+                          ? (
+                            <EpisodeSummary
+                              selected={selectedEpisodes.has(episode.IDs.ID)}
+                              onSelectionChange={() => onSelectionChange(episode.IDs.ID)}
+                              seriesId={series.IDs.ID}
+                              anidbSeriesId={series.IDs.AniDB}
+                              episode={episode}
+                              page={page}
+                            />
+                          )
+                          : (
+                            <div className="flex h-[20.75rem] items-center justify-center p-6 text-panel-text-primary">
+                              <Icon path={mdiLoading} spin size={3} />
+                            </div>
+                          )}
+                      </div>
+                    );
+                  })}
+                </div>
+              )}
           </div>
         </div>
-        <div className="grow">
-          {isPending
-            ? (
-              <div className="flex h-full items-center justify-center text-panel-text-primary">
-                <Icon path={mdiLoading} spin size={4} />
-              </div>
-            )
-            : (
-              <div className="relative w-full" style={{ height: rowVirtualizer.getTotalSize() }}>
-                {virtualItems.map((virtualItem) => {
-                  const page = Math.ceil((virtualItem.index + 1) / pageSize);
-                  const episode = episodes[virtualItem.index];
-
-                  if (!episode && !isFetchingNextPage) fetchNextPageDebounced();
-
-                  return (
-                    <div
-                      key={episode ? episode.IDs.ID : `loading-${virtualItem.key}`}
-                      className="absolute left-0 top-0 flex w-full flex-col rounded-lg border border-panel-border bg-panel-background-transparent"
-                      data-index={virtualItem.index}
-                      style={{ transform: `translateY(${virtualItem.start ?? 0}px)` }}
-                      ref={rowVirtualizer.measureElement}
-                    >
-                      {episode
-                        ? (
-                          <EpisodeSummary
-                            selected={selectedEpisodes.has(episode.IDs.ID)}
-                            onSelectionChange={() => onSelectionChange(episode.IDs.ID)}
-                            seriesId={toNumber(seriesId)}
-                            anidbSeriesId={anidbSeriesId}
-                            episode={episode}
-                            page={page}
-                          />
-                        )
-                        : (
-                          <div className="flex h-[20.75rem] items-center justify-center p-6 text-panel-text-primary">
-                            <Icon path={mdiLoading} spin size={3} />
-                          </div>
-                        )}
-                    </div>
-                  );
-                })}
-              </div>
-            )}
-        </div>
+        <EpisodeWatchModal
+          show={showOptionsModal}
+          onRequestClose={() => setShowOptionsModal(false)}
+          markFilteredWatched={markFilteredWatched}
+          markFilteredUnwatched={markFilteredUnwatched}
+        />
       </div>
-      <EpisodeWatchModal
-        show={showOptionsModal}
-        onRequestClose={() => setShowOptionsModal(false)}
-        markFilteredWatched={markFilteredWatched}
-        markFilteredUnwatched={markFilteredUnwatched}
-      />
-    </div>
+    </>
   );
 };
 
diff --git a/src/pages/collection/series/SeriesFileSummary.tsx b/src/pages/collection/series/SeriesFileSummary.tsx
index 7cbf090fa..e3a7f0bdf 100644
--- a/src/pages/collection/series/SeriesFileSummary.tsx
+++ b/src/pages/collection/series/SeriesFileSummary.tsx
@@ -1,8 +1,7 @@
 import React, { useState } from 'react';
-import { useParams } from 'react-router';
+import { useOutletContext } from 'react-router-dom';
 import { mdiLoading } from '@mdi/js';
 import { Icon } from '@mdi/react';
-import { toNumber } from 'lodash';
 
 import FileMissingEpisodes from '@/components/Collection/Files/FilesMissingEpisodes';
 import FileOverview from '@/components/Collection/Files/FilesOverview';
@@ -10,6 +9,7 @@ import FilesSummaryGroups from '@/components/Collection/Files/FilesSummaryGroup'
 import MultiStateButton from '@/components/Input/MultiStateButton';
 import { useSeriesFileSummaryQuery } from '@/core/react-query/webui/queries';
 
+import type { SeriesContextType } from '@/components/Collection/constants';
 import type { WebuiSeriesFileSummaryType } from '@/core/types/api/webui';
 
 type ModeType = 'Series' | 'Missing';
@@ -47,43 +47,43 @@ const FilesSelectionHeader = React.memo(({ fileSummary, mode, setMode }: FileSel
 ));
 
 const SeriesFileSummary = () => {
-  const { seriesId } = useParams();
+  const { series } = useOutletContext<SeriesContextType>();
 
   const [mode, setMode] = useState<ModeType>('Series');
 
   const { data: fileSummary, isLoading } = useSeriesFileSummaryQuery(
-    toNumber(seriesId!),
+    series.IDs.ID,
     { groupBy: 'GroupName,FileVersion,FileLocation,AudioLanguages,SubtitleLanguages,VideoResolution' },
-    !!seriesId,
   );
 
-  if (!seriesId) return null;
-
   return (
-    <div className="flex w-full gap-x-6">
-      <div className="flex flex-col gap-y-6">
-        <FileOverview overview={fileSummary?.Overview} />
-      </div>
+    <>
+      <title>{`${series.Name} > Files | Shoko`}</title>
+      <div className="flex w-full gap-x-6">
+        <div className="flex flex-col gap-y-6">
+          <FileOverview overview={fileSummary?.Overview} />
+        </div>
 
-      <div className="flex w-full flex-col gap-y-6">
-        <FilesSelectionHeader
-          mode={mode}
-          setMode={setMode}
-          fileSummary={fileSummary}
-        />
+        <div className="flex w-full flex-col gap-y-6">
+          <FilesSelectionHeader
+            mode={mode}
+            setMode={setMode}
+            fileSummary={fileSummary}
+          />
 
-        <div className="flex grow flex-col gap-y-6">
-          {isLoading && (
-            <div className="flex grow items-center justify-center text-panel-text-primary">
-              <Icon path={mdiLoading} spin size={3} />
-            </div>
-          )}
-          {mode === 'Series'
-            ? <FilesSummaryGroups groups={fileSummary?.Groups} />
-            : <FileMissingEpisodes missingEps={fileSummary?.MissingEpisodes} />}
+          <div className="flex grow flex-col gap-y-6">
+            {isLoading && (
+              <div className="flex grow items-center justify-center text-panel-text-primary">
+                <Icon path={mdiLoading} spin size={3} />
+              </div>
+            )}
+            {mode === 'Series'
+              ? <FilesSummaryGroups groups={fileSummary?.Groups} />
+              : <FileMissingEpisodes missingEps={fileSummary?.MissingEpisodes} />}
+          </div>
         </div>
       </div>
-    </div>
+    </>
   );
 };
 
diff --git a/src/pages/collection/series/SeriesImages.tsx b/src/pages/collection/series/SeriesImages.tsx
index aa2be1330..d94b38c07 100644
--- a/src/pages/collection/series/SeriesImages.tsx
+++ b/src/pages/collection/series/SeriesImages.tsx
@@ -1,9 +1,10 @@
 import React, { useMemo, useState } from 'react';
 import { useNavigate, useParams } from 'react-router';
+import { useOutletContext } from 'react-router-dom';
 import { mdiStarCircleOutline } from '@mdi/js';
 import { Icon } from '@mdi/react';
 import cx from 'classnames';
-import { capitalize, split, toNumber } from 'lodash';
+import { capitalize, split } from 'lodash';
 
 import BackgroundImagePlaceholderDiv from '@/components/BackgroundImagePlaceholderDiv';
 import Button from '@/components/Input/Button';
@@ -14,6 +15,7 @@ import { useChangeSeriesImageMutation } from '@/core/react-query/series/mutation
 import { useSeriesImagesQuery } from '@/core/react-query/series/queries';
 import useEventCallback from '@/hooks/useEventCallback';
 
+import type { SeriesContextType } from '@/components/Collection/constants';
 import type { ImageType } from '@/core/types/api/common';
 
 type ImageTabType = 'Posters' | 'Backdrops' | 'Logos';
@@ -37,7 +39,9 @@ const sizeMap = {
 };
 
 const SeriesImages = () => {
-  const { imageType, seriesId } = useParams();
+  const { imageType } = useParams();
+
+  const { series } = useOutletContext<SeriesContextType>();
 
   const navigate = useNavigate();
 
@@ -46,7 +50,7 @@ const SeriesImages = () => {
     return capitalize(imageType) as ImageTabType;
   }, [imageType]);
   const [selectedImage, setSelectedImage] = useState<ImageType | null>(null);
-  const images = useSeriesImagesQuery(toNumber(seriesId!), !!seriesId).data;
+  const images = useSeriesImagesQuery(series.IDs.ID).data;
   const { mutate: changeImage } = useChangeSeriesImageMutation();
 
   const splitPath = split(selectedImage?.RelativeFilepath ?? '-', '/');
@@ -59,7 +63,7 @@ const SeriesImages = () => {
 
   const handleSetPreferredImage = useEventCallback(() => {
     if (!selectedImage) return;
-    changeImage({ seriesId: toNumber(seriesId), image: selectedImage }, {
+    changeImage({ seriesId: series.IDs.ID, image: selectedImage }, {
       onSuccess: () => {
         setSelectedImage(null);
         toast.success(`Series ${selectedImage.Type} image has been changed.`);
@@ -72,85 +76,86 @@ const SeriesImages = () => {
     navigate(`../images/${newType.toLowerCase()}`);
   });
 
-  if (!seriesId) return null;
-
   return (
-    <div className="flex w-full gap-x-6">
-      <div className="flex w-100 min-w-64 flex-col">
-        <ShokoPanel
-          title="Selected Image Info"
-          contentClassName="gap-y-6"
-          fullHeight={false}
-          transparent
-          sticky
-        >
-          <InfoLine title="Filename" value={filename} />
-          <InfoLine title="Location" value={filepath} />
-          <InfoLine title="Source" value={selectedImage?.Source ?? '-'} />
-          <InfoLine
-            title="Size"
-            value={selectedImage?.Width && selectedImage?.Height
-              ? `${selectedImage.Width} x ${selectedImage.Height}`
-              : '-'}
-          />
-          <Button
-            buttonType="primary"
-            buttonSize="normal"
-            disabled={!selectedImage || selectedImage.Preferred}
-            onClick={handleSetPreferredImage}
+    <>
+      <title>{`${series.Name} > Images | Shoko`}</title>
+      <div className="flex w-full gap-x-6">
+        <div className="flex w-100 min-w-64 flex-col">
+          <ShokoPanel
+            title="Selected Image Info"
+            contentClassName="gap-y-6"
+            fullHeight={false}
+            transparent
+            sticky
           >
-            {`Set As Preferred ${tabType.slice(0, -1)}`}
-          </Button>
-        </ShokoPanel>
-      </div>
-      <div className="flex grow flex-col gap-y-6">
-        <div className="flex h-[6.125rem] items-center justify-between rounded-lg border border-panel-border bg-panel-background-transparent p-6">
-          <div className="text-xl font-semibold">
-            Images |&nbsp;
-            <span className="text-panel-text-important">{images?.[tabType]?.length ?? '-'}</span>
-            &nbsp;
-            {tabType}
-            &nbsp;Listed
-          </div>
-          <MultiStateButton activeState={tabType} onStateChange={handleTabChange} states={tabStates} />
-        </div>
-        <div
-          className={cx(
-            sizeMap[tabType].grid,
-            'grid gap-6 rounded-lg border border-panel-border bg-panel-background-transparent p-6',
-          )}
-        >
-          {images?.[tabType].map(item => (
-            <div
-              onClick={() => handleSelectionChange(item)}
-              key={`${item.Source}-${item.Type}-${item.ID}`}
-              className="group flex cursor-pointer items-center justify-between"
+            <InfoLine title="Filename" value={filename} />
+            <InfoLine title="Location" value={filepath} />
+            <InfoLine title="Source" value={selectedImage?.Source ?? '-'} />
+            <InfoLine
+              title="Size"
+              value={selectedImage?.Width && selectedImage?.Height
+                ? `${selectedImage.Width} x ${selectedImage.Height}`
+                : '-'}
+            />
+            <Button
+              buttonType="primary"
+              buttonSize="normal"
+              disabled={!selectedImage || selectedImage.Preferred}
+              onClick={handleSetPreferredImage}
             >
-              <BackgroundImagePlaceholderDiv
-                image={item}
-                contain={tabType === 'Logos'}
-                className={cx(
-                  'rounded-lg drop-shadow-md transition-transform outline grow',
-                  item === selectedImage
-                    ? 'outline-panel-text-important outline-4'
-                    : 'outline-2 outline-panel-border',
-                  sizeMap[tabType].image,
-                )}
-                linkToImage
-                zoomOnHover
-              >
-                {item.Preferred && (
-                  <div className="absolute bottom-2 mx-[5%] flex w-[90%] justify-center gap-2.5 rounded-lg bg-panel-background-overlay py-2 text-sm font-semibold text-panel-text opacity-100 transition-opacity group-hover:opacity-0">
-                    <Icon path={mdiStarCircleOutline} size={1} />
-                    Preferred
-                  </div>
-                )}
-              </BackgroundImagePlaceholderDiv>
+              {`Set As Preferred ${tabType.slice(0, -1)}`}
+            </Button>
+          </ShokoPanel>
+        </div>
+        <div className="flex grow flex-col gap-y-6">
+          <div className="flex h-[6.125rem] items-center justify-between rounded-lg border border-panel-border bg-panel-background-transparent p-6">
+            <div className="text-xl font-semibold">
+              Images |&nbsp;
+              <span className="text-panel-text-important">{images?.[tabType]?.length ?? '-'}</span>
+              &nbsp;
+              {tabType}
+              &nbsp;Listed
             </div>
-          ))}
+            <MultiStateButton activeState={tabType} onStateChange={handleTabChange} states={tabStates} />
+          </div>
+          <div
+            className={cx(
+              sizeMap[tabType].grid,
+              'grid gap-6 rounded-lg border border-panel-border bg-panel-background-transparent p-6',
+            )}
+          >
+            {images?.[tabType].map(item => (
+              <div
+                onClick={() => handleSelectionChange(item)}
+                key={`${item.Source}-${item.Type}-${item.ID}`}
+                className="group flex cursor-pointer items-center justify-between"
+              >
+                <BackgroundImagePlaceholderDiv
+                  image={item}
+                  contain={tabType === 'Logos'}
+                  className={cx(
+                    'rounded-lg drop-shadow-md transition-transform outline grow',
+                    item === selectedImage
+                      ? 'outline-panel-text-important outline-4'
+                      : 'outline-2 outline-panel-border',
+                    sizeMap[tabType].image,
+                  )}
+                  linkToImage
+                  zoomOnHover
+                >
+                  {item.Preferred && (
+                    <div className="absolute bottom-2 mx-[5%] flex w-[90%] justify-center gap-2.5 rounded-lg bg-panel-background-overlay py-2 text-sm font-semibold text-panel-text opacity-100 transition-opacity group-hover:opacity-0">
+                      <Icon path={mdiStarCircleOutline} size={1} />
+                      Preferred
+                    </div>
+                  )}
+                </BackgroundImagePlaceholderDiv>
+              </div>
+            ))}
+          </div>
         </div>
       </div>
-    </div>
+    </>
   );
 };
 
diff --git a/src/pages/collection/series/SeriesOverview.tsx b/src/pages/collection/series/SeriesOverview.tsx
index faedebbba..065372d27 100644
--- a/src/pages/collection/series/SeriesOverview.tsx
+++ b/src/pages/collection/series/SeriesOverview.tsx
@@ -1,9 +1,9 @@
 import React, { useMemo, useState } from 'react';
-import { useParams } from 'react-router';
+import { useOutletContext } from 'react-router-dom';
 import { mdiEarth, mdiOpenInNew } from '@mdi/js';
 import { Icon } from '@mdi/react';
 import cx from 'classnames';
-import { flatMap, get, map, round, toNumber } from 'lodash';
+import { flatMap, get, map, round } from 'lodash';
 
 import CharacterImage from '@/components/CharacterImage';
 import EpisodeSummary from '@/components/Collection/Episode/EpisodeSummary';
@@ -15,11 +15,11 @@ import {
   useRelatedAnimeQuery,
   useSeriesCastQuery,
   useSeriesNextUpQuery,
-  useSeriesQuery,
   useSimilarAnimeQuery,
 } from '@/core/react-query/series/queries';
 import useEventCallback from '@/hooks/useEventCallback';
 
+import type { SeriesContextType } from '@/components/Collection/constants';
 import type { ImageType } from '@/core/types/api/common';
 import type { SeriesCast } from '@/core/types/api/series';
 
@@ -27,20 +27,15 @@ import type { SeriesCast } from '@/core/types/api/series';
 const MetadataLinks = ['AniDB', 'TMDB', 'TraktTv'] as const;
 
 const SeriesOverview = () => {
-  const { seriesId } = useParams();
+  const { series } = useOutletContext<SeriesContextType>();
 
-  const { data: series, ...seriesQuery } = useSeriesQuery(
-    toNumber(seriesId!),
-    { includeDataFrom: ['AniDB', 'TMDB'] },
-    !!seriesId,
-  );
-  const nextUpEpisodeQuery = useSeriesNextUpQuery(toNumber(seriesId!), {
+  const nextUpEpisodeQuery = useSeriesNextUpQuery(series.IDs.ID, {
     includeDataFrom: ['AniDB'],
     includeMissing: false,
     onlyUnwatched: false,
-  }, !!seriesId);
-  const relatedAnimeQuery = useRelatedAnimeQuery(toNumber(seriesId!), !!seriesId);
-  const similarAnimeQuery = useSimilarAnimeQuery(toNumber(seriesId!), !!seriesId);
+  });
+  const relatedAnimeQuery = useRelatedAnimeQuery(series.IDs.ID);
+  const similarAnimeQuery = useSimilarAnimeQuery(series.IDs.ID);
 
   const tabStates = [
     { label: 'Metadata Sites', value: 'metadata' },
@@ -54,7 +49,7 @@ const SeriesOverview = () => {
 
   const relatedAnime = useMemo(() => relatedAnimeQuery?.data ?? [], [relatedAnimeQuery.data]);
   const similarAnime = useMemo(() => similarAnimeQuery?.data ?? [], [similarAnimeQuery.data]);
-  const cast = useSeriesCastQuery(toNumber(seriesId!), !!seriesId).data;
+  const cast = useSeriesCastQuery(series.IDs.ID).data;
 
   const getThumbnailUrl = (item: SeriesCast, mode: string) => {
     const thumbnail = get<SeriesCast, string, ImageType | null>(item, `${mode}.Image`, null);
@@ -64,6 +59,7 @@ const SeriesOverview = () => {
 
   return (
     <>
+      <title>{`${series.Name} > Overview | Shoko`}</title>
       <div className="flex gap-x-6">
         <div className="flex w-full gap-x-6">
           <ShokoPanel
@@ -74,7 +70,6 @@ const SeriesOverview = () => {
             options={
               <MultiStateButton states={tabStates} activeState={currentTab} onStateChange={handleTabStateChange} />
             }
-            isFetching={seriesQuery.isFetching}
           >
             {series && currentTab === 'metadata' && (
               <div
@@ -157,7 +152,7 @@ const SeriesOverview = () => {
             isFetching={nextUpEpisodeQuery.isFetching}
           >
             {nextUpEpisodeQuery.isSuccess && nextUpEpisodeQuery.data
-              ? <EpisodeSummary seriesId={toNumber(seriesId)} episode={nextUpEpisodeQuery.data} nextUp />
+              ? <EpisodeSummary seriesId={series.IDs.ID} episode={nextUpEpisodeQuery.data} nextUp />
               : (
                 <div className="flex grow items-center justify-center font-semibold">
                   All available episodes have already been watched
diff --git a/src/pages/collection/series/SeriesTags.tsx b/src/pages/collection/series/SeriesTags.tsx
index 0cc0fb6d8..3d67fecb9 100644
--- a/src/pages/collection/series/SeriesTags.tsx
+++ b/src/pages/collection/series/SeriesTags.tsx
@@ -1,8 +1,7 @@
 import React, { useMemo, useState } from 'react';
-import { useParams } from 'react-router';
+import { useOutletContext } from 'react-router-dom';
 import { mdiLoading, mdiTagTextOutline } from '@mdi/js';
 import { Icon } from '@mdi/react';
-import { toNumber } from 'lodash';
 import { useDebounceValue, useToggle } from 'usehooks-ts';
 
 import CleanDescription from '@/components/Collection/CleanDescription';
@@ -11,6 +10,7 @@ import TagsSearchAndFilterPanel from '@/components/Collection/Tags/TagsSearchAnd
 import { useSeriesTagsQuery } from '@/core/react-query/series/queries';
 import useEventCallback from '@/hooks/useEventCallback';
 
+import type { SeriesContextType } from '@/components/Collection/constants';
 import type { TagType } from '@/core/types/api/tags';
 
 const cleanString = (input = '') => input.replaceAll(' ', '').toLowerCase();
@@ -47,7 +47,7 @@ const SingleTag = React.memo(({ onTagExpand, tag }: { tag: TagType, onTagExpand:
 });
 
 const SeriesTags = () => {
-  const { seriesId } = useParams();
+  const { series } = useOutletContext<SeriesContextType>();
 
   const [selectedTag, setSelectedTag] = useState<TagType>();
   const [showTagModal, toggleShowTagModal] = useToggle(false);
@@ -83,11 +83,7 @@ const SeriesTags = () => {
     }
   });
 
-  const { data: tagsQueryData, isLoading, isSuccess } = useSeriesTagsQuery(
-    toNumber(seriesId!),
-    { filter: 1 },
-    !!seriesId,
-  );
+  const { data: tagsQueryData, isLoading, isSuccess } = useSeriesTagsQuery(series.IDs.ID, { filter: 1 });
 
   const filteredTags = useMemo(
     () =>
@@ -133,39 +129,40 @@ const SeriesTags = () => {
   });
   const clearTagSelection = useEventCallback(toggleShowTagModal);
 
-  if (!seriesId) return null;
-
   return (
-    <div className="flex w-full gap-x-6">
-      <TagsSearchAndFilterPanel
-        seriesId={toNumber(seriesId)}
-        search={search}
-        tagSourceFilter={tagSourceFilter}
-        showSpoilers={showSpoilers}
-        sort={sort}
-        handleInputChange={handleInputChange}
-        toggleSort={toggleSort}
-      />
-      <div className="flex w-full flex-col gap-y-6">
-        {header}
-        <div className="flex grow flex-col gap-y-6">
-          {isLoading
-            ? (
-              <div className="flex grow items-center justify-center text-panel-text-primary">
-                <Icon path={mdiLoading} spin size={1} />
-              </div>
-            )
-            : (
-              <div className="grid grid-cols-3 gap-4 2xl:gap-6">
-                {filteredTags?.map(tag => (
-                  <SingleTag key={`${tag.Source}-${tag.ID}`} tag={tag} onTagExpand={onTagSelection} />
-                ))}
-              </div>
-            )}
+    <>
+      <title>{`${series.Name} > Tags | Shoko`}</title>
+      <div className="flex w-full gap-x-6">
+        <TagsSearchAndFilterPanel
+          seriesId={series.IDs.ID}
+          search={search}
+          tagSourceFilter={tagSourceFilter}
+          showSpoilers={showSpoilers}
+          sort={sort}
+          handleInputChange={handleInputChange}
+          toggleSort={toggleSort}
+        />
+        <div className="flex w-full flex-col gap-y-6">
+          {header}
+          <div className="flex grow flex-col gap-y-6">
+            {isLoading
+              ? (
+                <div className="flex grow items-center justify-center text-panel-text-primary">
+                  <Icon path={mdiLoading} spin size={1} />
+                </div>
+              )
+              : (
+                <div className="grid grid-cols-3 gap-4 2xl:gap-6">
+                  {filteredTags?.map(tag => (
+                    <SingleTag key={`${tag.Source}-${tag.ID}`} tag={tag} onTagExpand={onTagSelection} />
+                  ))}
+                </div>
+              )}
+          </div>
         </div>
+        <TagDetailsModal show={showTagModal} tag={selectedTag} onClose={clearTagSelection} />
       </div>
-      <TagDetailsModal show={showTagModal} tag={selectedTag} onClose={clearTagSelection} />
-    </div>
+    </>
   );
 };
 
diff --git a/src/pages/dashboard/DashboardPage.tsx b/src/pages/dashboard/DashboardPage.tsx
index 8fa0267a9..e1c6ba608 100644
--- a/src/pages/dashboard/DashboardPage.tsx
+++ b/src/pages/dashboard/DashboardPage.tsx
@@ -150,6 +150,7 @@ function DashboardPage() {
 
   return (
     <>
+      <title>Dashboard | Shoko</title>
       <ResponsiveGridLayout
         layouts={currentLayout}
         breakpoints={{ lg: 1024, md: 768, sm: 640 }} // These match tailwind breakpoints (for consistency)
diff --git a/src/pages/logs/LogsPage.tsx b/src/pages/logs/LogsPage.tsx
index 7a97dacfb..c78eaf286 100644
--- a/src/pages/logs/LogsPage.tsx
+++ b/src/pages/logs/LogsPage.tsx
@@ -50,76 +50,79 @@ const LogsPage = () => {
   };
 
   return (
-    <div className="flex grow flex-col gap-y-6">
-      <div className="flex items-center justify-between rounded-lg border border-panel-border bg-panel-background p-6">
-        <div className="text-xl font-semibold">Logs</div>
-        <div className="flex gap-x-2">
-          {/* TODO: Disabled until functionality is implemented */}
-          {/* <Input */}
-          {/*   id="search" */}
-          {/*   onChange={event => setSearch(event.target.value)} */}
-          {/*   type="text" */}
-          {/*   value={search} */}
-          {/*   placeholder="Search Logs..." */}
-          {/*   startIcon={mdiMagnify} */}
-          {/*   className="w-80" */}
-          {/*   disabled */}
-          {/* /> */}
-          {/* <IconButton icon={mdiFilterOutline} buttonType="secondary" buttonSize="normal" tooltip="Filter"/> */}
-          {/* <IconButton icon={mdiCogOutline} buttonType="secondary" buttonSize="normal" tooltip="Settings"/> */}
-          <IconButton
-            icon={mdiArrowVerticalLock}
-            buttonType="secondary"
-            buttonSize="normal"
-            className={cx(scrollToBottom ? 'text-panel-text-primary' : '!text-panel-text')}
-            onClick={() => setScrollToBottom(prev => !prev)}
-            tooltip={`${scrollToBottom ? 'Disable' : 'Enable'} scroll to bottom`}
-          />
+    <>
+      <title>Logs | Shoko</title>
+      <div className="flex grow flex-col gap-y-6">
+        <div className="flex items-center justify-between rounded-lg border border-panel-border bg-panel-background p-6">
+          <div className="text-xl font-semibold">Logs</div>
+          <div className="flex gap-x-2">
+            {/* TODO: Disabled until functionality is implemented */}
+            {/* <Input */}
+            {/*   id="search" */}
+            {/*   onChange={event => setSearch(event.target.value)} */}
+            {/*   type="text" */}
+            {/*   value={search} */}
+            {/*   placeholder="Search Logs..." */}
+            {/*   startIcon={mdiMagnify} */}
+            {/*   className="w-80" */}
+            {/*   disabled */}
+            {/* /> */}
+            {/* <IconButton icon={mdiFilterOutline} buttonType="secondary" buttonSize="normal" tooltip="Filter"/> */}
+            {/* <IconButton icon={mdiCogOutline} buttonType="secondary" buttonSize="normal" tooltip="Settings"/> */}
+            <IconButton
+              icon={mdiArrowVerticalLock}
+              buttonType="secondary"
+              buttonSize="normal"
+              className={cx(scrollToBottom ? 'text-panel-text-primary' : '!text-panel-text')}
+              onClick={() => setScrollToBottom(prev => !prev)}
+              tooltip={`${scrollToBottom ? 'Disable' : 'Enable'} scroll to bottom`}
+            />
+          </div>
         </div>
-      </div>
 
-      <div className="flex grow rounded-lg border border-panel-border bg-panel-background p-6">
-        <div
-          className="w-full overflow-y-auto rounded-lg border-16 border-panel-input bg-panel-input contain-strict"
-          ref={parentRef}
-          onScroll={handleScroll}
-        >
-          {logLines.length === 0
-            ? (
-              <div className="flex h-full items-center justify-center text-panel-text-primary">
-                <Icon path={mdiLoading} size={4} spin />
-              </div>
-            )
-            : (
-              <div
-                className="relative w-full"
-                style={{ height: rowVirtualizer.getTotalSize() }}
-              >
+        <div className="flex grow rounded-lg border border-panel-border bg-panel-background p-6">
+          <div
+            className="w-full overflow-y-auto rounded-lg border-16 border-panel-input bg-panel-input contain-strict"
+            ref={parentRef}
+            onScroll={handleScroll}
+          >
+            {logLines.length === 0
+              ? (
+                <div className="flex h-full items-center justify-center text-panel-text-primary">
+                  <Icon path={mdiLoading} size={4} spin />
+                </div>
+              )
+              : (
                 <div
-                  className="absolute left-4 top-0 w-[95%]"
-                  style={{ transform: `translateY(${virtualItems[0]?.start ?? 0}px)` }}
+                  className="relative w-full"
+                  style={{ height: rowVirtualizer.getTotalSize() }}
                 >
-                  {virtualItems.map((virtualRow) => {
-                    const row = logLines[virtualRow.index];
-                    return (
-                      <div
-                        className="flex gap-x-6 pt-2"
-                        key={virtualRow.key}
-                        data-index={virtualRow.index}
-                        ref={rowVirtualizer.measureElement}
-                      >
-                        <div className="w-44 shrink-0 opacity-65">{row.TimeStamp}</div>
-                        <div className="w-[2.8rem] shrink-0">{row.Level}</div>
-                        <div className="break-all">{row.Message}</div>
-                      </div>
-                    );
-                  })}
+                  <div
+                    className="absolute left-4 top-0 w-[95%]"
+                    style={{ transform: `translateY(${virtualItems[0]?.start ?? 0}px)` }}
+                  >
+                    {virtualItems.map((virtualRow) => {
+                      const row = logLines[virtualRow.index];
+                      return (
+                        <div
+                          className="flex gap-x-6 pt-2"
+                          key={virtualRow.key}
+                          data-index={virtualRow.index}
+                          ref={rowVirtualizer.measureElement}
+                        >
+                          <div className="w-44 shrink-0 opacity-65">{row.TimeStamp}</div>
+                          <div className="w-[2.8rem] shrink-0">{row.Level}</div>
+                          <div className="break-all">{row.Message}</div>
+                        </div>
+                      );
+                    })}
+                  </div>
                 </div>
-              </div>
-            )}
+              )}
+          </div>
         </div>
       </div>
-    </div>
+    </>
   );
 };
 
diff --git a/src/pages/settings/SettingsPage.tsx b/src/pages/settings/SettingsPage.tsx
index eeee929f4..a23a58ca5 100644
--- a/src/pages/settings/SettingsPage.tsx
+++ b/src/pages/settings/SettingsPage.tsx
@@ -36,7 +36,7 @@ function SettingsPage() {
 
   const { pathname } = useLocation();
 
-  const toastId = useRef<number | string>();
+  const toastId = useRef<number | string>(undefined);
 
   const settingsQuery = useSettingsQuery();
   const settings = settingsQuery.data;
diff --git a/src/pages/settings/tabs/AniDBSettings.tsx b/src/pages/settings/tabs/AniDBSettings.tsx
index 3ae1292e7..5da966364 100644
--- a/src/pages/settings/tabs/AniDBSettings.tsx
+++ b/src/pages/settings/tabs/AniDBSettings.tsx
@@ -53,6 +53,7 @@ function AniDBSettings() {
 
   return (
     <>
+      <title>Settings &gt; AniDB | Shoko</title>
       <div className="flex flex-col gap-y-1">
         <div className="text-xl font-semibold">AniDB</div>
         <div>
diff --git a/src/pages/settings/tabs/ApiKeys.tsx b/src/pages/settings/tabs/ApiKeys.tsx
index 713931924..5934ac460 100644
--- a/src/pages/settings/tabs/ApiKeys.tsx
+++ b/src/pages/settings/tabs/ApiKeys.tsx
@@ -97,6 +97,7 @@ const ApiKeys = () => {
 
   return (
     <>
+      <title>Settings &gt; API Keys | Shoko</title>
       <div className="flex flex-col gap-y-1">
         <div className="text-xl font-semibold">API Keys</div>
         <div>
diff --git a/src/pages/settings/tabs/CollectionSettings.tsx b/src/pages/settings/tabs/CollectionSettings.tsx
index a04908886..968387724 100644
--- a/src/pages/settings/tabs/CollectionSettings.tsx
+++ b/src/pages/settings/tabs/CollectionSettings.tsx
@@ -150,6 +150,7 @@ const CollectionSettings = () => {
 
   return (
     <>
+      <title>Settings &gt; Collection | Shoko</title>
       <div className="flex flex-col gap-y-1">
         <div className="text-xl font-semibold">Collection</div>
         <div>
diff --git a/src/pages/settings/tabs/GeneralSettings.tsx b/src/pages/settings/tabs/GeneralSettings.tsx
index 7dc0a4703..34cde95ea 100644
--- a/src/pages/settings/tabs/GeneralSettings.tsx
+++ b/src/pages/settings/tabs/GeneralSettings.tsx
@@ -82,6 +82,7 @@ function GeneralSettings() {
 
   return (
     <>
+      <title>Settings &gt; General | Shoko</title>
       <div className="flex flex-col gap-y-1">
         <div className="text-xl font-semibold">General</div>
         <div>
diff --git a/src/pages/settings/tabs/ImportSettings.tsx b/src/pages/settings/tabs/ImportSettings.tsx
index 71528744f..7b1a62210 100644
--- a/src/pages/settings/tabs/ImportSettings.tsx
+++ b/src/pages/settings/tabs/ImportSettings.tsx
@@ -34,6 +34,7 @@ function ImportSettings() {
 
   return (
     <>
+      <title>Settings &gt; Import | Shoko</title>
       <div className="flex flex-col gap-y-1">
         <div className="text-xl font-semibold">Import</div>
         <div>
diff --git a/src/pages/settings/tabs/IntegrationsSettings.tsx b/src/pages/settings/tabs/IntegrationsSettings.tsx
index 2e792bf21..45b56212c 100644
--- a/src/pages/settings/tabs/IntegrationsSettings.tsx
+++ b/src/pages/settings/tabs/IntegrationsSettings.tsx
@@ -5,6 +5,7 @@ import TraktSettings from '@/components/Settings/MetadataSitesSettings/TraktSett
 
 const IntegrationsSettings = () => (
   <>
+    <title>Settings &gt; Integrations | Shoko</title>
     <div className="flex flex-col gap-y-1">
       <div className="text-xl font-semibold">Integrations</div>
       <div>
diff --git a/src/pages/settings/tabs/MetadataSitesSettings.tsx b/src/pages/settings/tabs/MetadataSitesSettings.tsx
index fc61c768a..1a990fa42 100644
--- a/src/pages/settings/tabs/MetadataSitesSettings.tsx
+++ b/src/pages/settings/tabs/MetadataSitesSettings.tsx
@@ -9,6 +9,7 @@ function MetadataSitesSettings() {
 
   return (
     <>
+      <title>Settings &gt; Metadata Sites | Shoko</title>
       <div className="flex flex-col gap-y-1">
         <div className="text-xl font-semibold">Metadata Sites</div>
         <div>
diff --git a/src/pages/settings/tabs/UserManagementSettings.tsx b/src/pages/settings/tabs/UserManagementSettings.tsx
index 24a4515bc..b6a7d47d0 100644
--- a/src/pages/settings/tabs/UserManagementSettings.tsx
+++ b/src/pages/settings/tabs/UserManagementSettings.tsx
@@ -174,6 +174,7 @@ function UserManagementSettings() {
 
   return (
     <>
+      <title>Settings &gt; User Management | Shoko</title>
       <div className="flex flex-col gap-y-1">
         <div className="text-xl font-semibold">User Management</div>
         <div>
diff --git a/src/pages/utilities/FileSearch.tsx b/src/pages/utilities/FileSearch.tsx
index d38bbe0dc..0d9c6fb42 100644
--- a/src/pages/utilities/FileSearch.tsx
+++ b/src/pages/utilities/FileSearch.tsx
@@ -374,81 +374,84 @@ const FileSearch = () => {
   const selectedId = useMemo(() => fileSearchSelectedRows[viewIndex]?.ID, [fileSearchSelectedRows, viewIndex]);
 
   return (
-    <div className="flex grow flex-col gap-y-6">
-      <ShokoPanel title="File Search" options={<ItemCount count={fileCount} selected={selectedRows?.length} />}>
-        <div className="flex items-center gap-x-3">
-          <Input
-            type="text"
-            placeholder="Search..."
-            startIcon={mdiMagnify}
-            id="search"
-            onChange={setSearch}
-            value={search}
-            inputClassName="px-4 py-3"
-          />
-          <Menu
-            selectedRows={selectedRows}
-            setSelectedRows={setRowSelection}
-          />
-        </div>
-      </ShokoPanel>
-      <div className="flex grow justify-between gap-x-6 overflow-y-auto contain-strict">
-        <div className="flex w-full rounded-lg border border-panel-border bg-panel-background p-6 lg:max-w-[75%]">
-          {filesQuery.isPending && (
-            <div className="flex grow items-center justify-center text-panel-text-primary">
-              <Icon path={mdiLoading} size={4} spin />
-            </div>
-          )}
-
-          {!filesQuery.isPending && fileCount === 0 && (
-            <div className="flex grow items-center justify-center font-semibold">No search results!</div>
-          )}
-
-          {filesQuery.isSuccess && fileCount > 0 && (
-            <UtilitiesTable
-              count={fileCount}
-              fetchNextPage={filesQuery.fetchNextPage}
-              handleRowSelect={handleRowSelect}
-              columns={staticColumns}
-              isFetchingNextPage={filesQuery.isFetchingNextPage}
-              rows={files}
-              rowSelection={rowSelection}
+    <>
+      <title>Utilities &gt; File Search | Shoko</title>
+      <div className="flex grow flex-col gap-y-6">
+        <ShokoPanel title="File Search" options={<ItemCount count={fileCount} selected={selectedRows?.length} />}>
+          <div className="flex items-center gap-x-3">
+            <Input
+              type="text"
+              placeholder="Search..."
+              startIcon={mdiMagnify}
+              id="search"
+              onChange={setSearch}
+              value={search}
+              inputClassName="px-4 py-3"
+            />
+            <Menu
+              selectedRows={selectedRows}
               setSelectedRows={setRowSelection}
-              setSortCriteria={setSortCriteria}
-              sortCriteria={sortCriteria}
             />
-          )}
-        </div>
-        <div className="flex w-full flex-col lg:max-w-[25%]">
-          {selectedRows?.length > 0 && (
-            <div className="flex size-full flex-col overflow-y-auto overflow-x-hidden rounded-lg border border-panel-border bg-panel-background p-6">
-              <div className="flex w-full grow flex-col gap-y-6 overflow-y-auto pr-4">
-                <FilesSummary title="Selected Summary" items={selectedRows} />
-                <div className="flex w-full text-xl font-semibold">
-                  <div className="flex w-full justify-between">
-                    <span className="grow">Selected File</span>
-                    <div className={cx('flex', selectedRows.length <= 1 ? 'hidden' : '')}>
-                      <Button buttonType="secondary" onClick={onPrevView}>
-                        <Icon className="text-panel-icon-action" path={mdiChevronLeft} size={1} />
-                      </Button>
-                      <Button buttonType="secondary" onClick={onNextView}>
-                        <Icon className="text-panel-icon-action" path={mdiChevronRight} size={1} />
-                      </Button>
+          </div>
+        </ShokoPanel>
+        <div className="flex grow justify-between gap-x-6 overflow-y-auto contain-strict">
+          <div className="flex w-full rounded-lg border border-panel-border bg-panel-background p-6 lg:max-w-[75%]">
+            {filesQuery.isPending && (
+              <div className="flex grow items-center justify-center text-panel-text-primary">
+                <Icon path={mdiLoading} size={4} spin />
+              </div>
+            )}
+
+            {!filesQuery.isPending && fileCount === 0 && (
+              <div className="flex grow items-center justify-center font-semibold">No search results!</div>
+            )}
+
+            {filesQuery.isSuccess && fileCount > 0 && (
+              <UtilitiesTable
+                count={fileCount}
+                fetchNextPage={filesQuery.fetchNextPage}
+                handleRowSelect={handleRowSelect}
+                columns={staticColumns}
+                isFetchingNextPage={filesQuery.isFetchingNextPage}
+                rows={files}
+                rowSelection={rowSelection}
+                setSelectedRows={setRowSelection}
+                setSortCriteria={setSortCriteria}
+                sortCriteria={sortCriteria}
+              />
+            )}
+          </div>
+          <div className="flex w-full flex-col lg:max-w-[25%]">
+            {selectedRows?.length > 0 && (
+              <div className="flex size-full flex-col overflow-y-auto overflow-x-hidden rounded-lg border border-panel-border bg-panel-background p-6">
+                <div className="flex w-full grow flex-col gap-y-6 overflow-y-auto pr-4">
+                  <FilesSummary title="Selected Summary" items={selectedRows} />
+                  <div className="flex w-full text-xl font-semibold">
+                    <div className="flex w-full justify-between">
+                      <span className="grow">Selected File</span>
+                      <div className={cx('flex', selectedRows.length <= 1 ? 'hidden' : '')}>
+                        <Button buttonType="secondary" onClick={onPrevView}>
+                          <Icon className="text-panel-icon-action" path={mdiChevronLeft} size={1} />
+                        </Button>
+                        <Button buttonType="secondary" onClick={onNextView}>
+                          <Icon className="text-panel-icon-action" path={mdiChevronRight} size={1} />
+                        </Button>
+                      </div>
                     </div>
                   </div>
+                  <FileDetails fileId={selectedId} />
                 </div>
-                <FileDetails fileId={selectedId} />
               </div>
-            </div>
-          )}
-          {!selectedRows?.length && (
-            <div className="flex size-full flex-col rounded-lg border border-panel-border bg-panel-background p-6">
-              <div className="flex grow items-center justify-center font-semibold">Select File To Populate</div>
-            </div>
-          )}
+            )}
+            {!selectedRows?.length && (
+              <div className="flex size-full flex-col rounded-lg border border-panel-border bg-panel-background p-6">
+                <div className="flex grow items-center justify-center font-semibold">Select File To Populate</div>
+              </div>
+            )}
+          </div>
         </div>
       </div>
-    </div>
+    </>
   );
 };
 export default FileSearch;
diff --git a/src/pages/utilities/ReleaseManagement.tsx b/src/pages/utilities/ReleaseManagement.tsx
new file mode 100644
index 000000000..9e5b9c3f6
--- /dev/null
+++ b/src/pages/utilities/ReleaseManagement.tsx
@@ -0,0 +1,210 @@
+import React, { useEffect, useState } from 'react';
+import { mdiCloseCircleOutline, mdiFileDocumentMultipleOutline, mdiRefresh, mdiSelectMultiple } from '@mdi/js';
+import { Icon } from '@mdi/react';
+import cx from 'classnames';
+import { map, toNumber } from 'lodash';
+import { useToggle } from 'usehooks-ts';
+
+import Button from '@/components/Input/Button';
+import Checkbox from '@/components/Input/Checkbox';
+import ShokoPanel from '@/components/Panels/ShokoPanel';
+import toast from '@/components/Toast';
+import TransitionDiv from '@/components/TransitionDiv';
+import ItemCount from '@/components/Utilities/ItemCount';
+import Episode from '@/components/Utilities/ReleaseManagement/Episode';
+import QuickSelectModal from '@/components/Utilities/ReleaseManagement/QuickSelectModal';
+import SeriesList from '@/components/Utilities/ReleaseManagement/SeriesList';
+import Title from '@/components/Utilities/ReleaseManagement/Title';
+import MenuButton from '@/components/Utilities/Unrecognized/MenuButton';
+import {
+  useDeleteFileLocationMutation,
+  useDeleteFileMutation,
+  useMarkVariationMutation,
+} from '@/core/react-query/file/mutations';
+import { invalidateQueries, resetQueries } from '@/core/react-query/queryClient';
+import useEventCallback from '@/hooks/useEventCallback';
+
+import type { ReleaseManagementOptionsType } from '@/components/Utilities/constants';
+import type { EpisodeType } from '@/core/types/api/episode';
+import type { AxiosResponse } from 'axios';
+
+type Props = {
+  type: 'multiples' | 'duplicates';
+};
+
+const ReleaseManagement = ({ type }: Props) => {
+  const [ignoreVariations, toggleIgnoreVariations] = useToggle(true);
+  const [onlyFinishedSeries, toggleOnlyFinishedSeries] = useToggle(false);
+  const [seriesCount, setSeriesCount] = useState(0);
+  const [selectedSeries, setSelectedSeries] = useState(0);
+  const [selectedEpisode, setSelectedEpisode] = useState<EpisodeType>();
+  const [operationsPending, setOperationsPending] = useState(false);
+  const [fileOptions, setFileOptions] = useState<ReleaseManagementOptionsType>({});
+  const [showQuickSelectModal, toggleShowQuickSelectModal] = useToggle(false);
+
+  useEffect(() => () => {
+    setSelectedSeries(0);
+  }, []);
+
+  const { mutateAsync: deleteFile } = useDeleteFileMutation();
+  const { mutateAsync: markVariation } = useMarkVariationMutation();
+  const { mutateAsync: deleteFileLocation } = useDeleteFileLocationMutation();
+
+  const handleCheckboxChange = (checkboxType: 'variations' | 'series') => {
+    if (checkboxType === 'variations') toggleIgnoreVariations();
+    if (checkboxType === 'series') toggleOnlyFinishedSeries();
+  };
+
+  const confirmChanges = useEventCallback(() => {
+    setOperationsPending(true);
+
+    let operations: (Promise<AxiosResponse<unknown, unknown>> | null)[];
+
+    if (type === 'multiples') {
+      operations = map(fileOptions, (option, id) => {
+        if (!selectedEpisode) return null;
+
+        const file = selectedEpisode.Files!.find(item => item.ID === toNumber(id))!;
+        if (!file) return null;
+        if (option === 'delete') return deleteFile({ fileId: file.ID, removeFolder: false });
+        if (option === 'variation' && !file.IsVariation) return markVariation({ fileId: file.ID, variation: true });
+        if (option === 'keep' && file.IsVariation) return markVariation({ fileId: file.ID, variation: false });
+        return null;
+      });
+    } else {
+      operations = map(fileOptions, (option, id) => {
+        if (!selectedEpisode || option !== 'delete') return null;
+        return deleteFileLocation({ locationId: toNumber(id) });
+      });
+    }
+
+    Promise.all(operations)
+      .then(() => toast.success('Successful!'))
+      .catch(() => toast.error('One or more operations failed!'))
+      .finally(() => {
+        setOperationsPending(false);
+        resetQueries(['release-management']);
+        setSelectedEpisode(undefined);
+      });
+  });
+
+  return (
+    <>
+      <title>{`Utilities > ${type === 'multiples' ? 'Multiple Releases' : 'Duplicate Files'} | Shoko`}</title>
+      <div className="flex grow flex-col gap-y-6 overflow-y-auto">
+        <ShokoPanel title={<Title />} options={<ItemCount count={seriesCount} suffix="Series" />}>
+          <div className="flex items-center gap-x-3">
+            <div
+              className={cx(
+                'relative box-border flex grow items-center gap-x-4 rounded-md border border-panel-border bg-panel-background-alt px-4 py-2 transition-opacity',
+                selectedEpisode && 'pointer-events-none opacity-65',
+              )}
+            >
+              <MenuButton
+                onClick={() => invalidateQueries(['release-management', 'series'])}
+                icon={mdiRefresh}
+                name="Refresh"
+              />
+
+              {type === 'multiples' && (
+                <Checkbox
+                  id="ignore-variations"
+                  isChecked={ignoreVariations}
+                  onChange={() => handleCheckboxChange('variations')}
+                  label="Ignore Variations"
+                  labelRight
+                />
+              )}
+
+              <Checkbox
+                id="only-finished-series"
+                isChecked={onlyFinishedSeries}
+                onChange={() => handleCheckboxChange('series')}
+                label="Only Finished Series"
+                labelRight
+              />
+            </div>
+
+            {/* TODO: Add support for auto-delete */}
+            {/* {!selectedEpisode && ( */}
+            {/*   <Button */}
+            {/*     buttonType="primary" */}
+            {/*     className="flex gap-x-2.5 px-4 py-3 font-semibold" */}
+            {/*     disabled={seriesCount === 0} */}
+            {/*   > */}
+            {/*     <Icon path={mdiFileDocumentMultipleOutline} size={0.8333} /> */}
+            {/*     Auto-Delete Multiples */}
+            {/*   </Button> */}
+            {/* )} */}
+
+            {(type === 'multiples' && !selectedEpisode) && (
+              <Button
+                buttonType="secondary"
+                className="flex gap-x-2.5 px-4 py-3 font-semibold"
+                disabled={!selectedSeries}
+                onClick={toggleShowQuickSelectModal}
+              >
+                <Icon path={mdiSelectMultiple} size={0.8333} />
+                Quick Select
+              </Button>
+            )}
+
+            {selectedEpisode && (
+              <div className="flex items-center justify-end gap-x-3">
+                <Button
+                  buttonType="secondary"
+                  className="flex gap-x-2.5 px-4 py-3 font-semibold"
+                  onClick={() => setSelectedEpisode(undefined)}
+                >
+                  <Icon path={mdiCloseCircleOutline} size={0.8333} />
+                  Cancel
+                </Button>
+                <Button
+                  buttonType="primary"
+                  className="flex gap-x-2.5 px-4 py-3 font-semibold"
+                  onClick={confirmChanges}
+                  loading={operationsPending}
+                >
+                  <Icon path={mdiFileDocumentMultipleOutline} size={0.8333} />
+                  Confirm
+                </Button>
+              </div>
+            )}
+          </div>
+        </ShokoPanel>
+
+        <div className="relative flex grow">
+          <TransitionDiv show={!selectedEpisode} className="absolute flex size-full gap-x-3">
+            <SeriesList
+              type={type}
+              ignoreVariations={ignoreVariations}
+              onlyFinishedSeries={onlyFinishedSeries}
+              setSelectedEpisode={setSelectedEpisode}
+              setSelectedSeriesId={setSelectedSeries}
+              setSeriesCount={setSeriesCount}
+            />
+          </TransitionDiv>
+
+          <TransitionDiv
+            show={!!selectedEpisode}
+            className="absolute flex size-full flex-col gap-y-6 overflow-y-auto rounded-md border border-panel-border bg-panel-background p-6"
+          >
+            <Episode
+              type={type}
+              episode={selectedEpisode}
+              setFileOptions={setFileOptions}
+            />
+          </TransitionDiv>
+        </div>
+
+        <QuickSelectModal
+          show={showQuickSelectModal}
+          onClose={toggleShowQuickSelectModal}
+          seriesId={selectedSeries}
+        />
+      </div>
+    </>
+  );
+};
+
+export default ReleaseManagement;
diff --git a/src/pages/utilities/ReleaseManagementUtilityTabs/MultiplesUtil.tsx b/src/pages/utilities/ReleaseManagementUtilityTabs/MultiplesUtil.tsx
deleted file mode 100644
index 1ce9855e7..000000000
--- a/src/pages/utilities/ReleaseManagementUtilityTabs/MultiplesUtil.tsx
+++ /dev/null
@@ -1,180 +0,0 @@
-import React, { useState } from 'react';
-import { mdiCloseCircleOutline, mdiFileDocumentMultipleOutline, mdiRefresh, mdiSelectMultiple } from '@mdi/js';
-import { Icon } from '@mdi/react';
-import cx from 'classnames';
-import { map, toNumber } from 'lodash';
-import { useToggle } from 'usehooks-ts';
-
-import Button from '@/components/Input/Button';
-import Checkbox from '@/components/Input/Checkbox';
-import ShokoPanel from '@/components/Panels/ShokoPanel';
-import toast from '@/components/Toast';
-import TransitionDiv from '@/components/TransitionDiv';
-import ItemCount from '@/components/Utilities/ItemCount';
-import MultiplesUtilEpisode from '@/components/Utilities/ReleaseManagement/MultiplesUtilEpisode';
-import MultiplesUtilList from '@/components/Utilities/ReleaseManagement/MultiplesUtilList';
-import QuickSelectModal from '@/components/Utilities/ReleaseManagement/QuickSelectModal';
-import Title from '@/components/Utilities/ReleaseManagement/Title';
-import MenuButton from '@/components/Utilities/Unrecognized/MenuButton';
-import { useDeleteFileMutation, useMarkVariationMutation } from '@/core/react-query/file/mutations';
-import { invalidateQueries, resetQueries } from '@/core/react-query/queryClient';
-import useEventCallback from '@/hooks/useEventCallback';
-
-import type { MultipleFileOptionsType } from '@/components/Utilities/constants';
-import type { EpisodeType } from '@/core/types/api/episode';
-
-const MultiplesUtil = () => {
-  const [ignoreVariations, toggleIgnoreVariations] = useToggle(true);
-  const [onlyFinishedSeries, toggleOnlyFinishedSeries] = useToggle(false);
-  const [seriesCount, setSeriesCount] = useState(0);
-  const [selectedSeries, setSelectedSeries] = useState(0);
-  const [selectedEpisode, setSelectedEpisode] = useState<EpisodeType>();
-  const [operationsPending, setOperationsPending] = useState(false);
-  const [fileOptions, setFileOptions] = useState<MultipleFileOptionsType>({});
-  const [showQuickSelectModal, toggleShowQuickSelectModal] = useToggle(false);
-
-  const { mutateAsync: deleteFile } = useDeleteFileMutation();
-  const { mutateAsync: markVariation } = useMarkVariationMutation();
-
-  const handleCheckboxChange = (type: 'variations' | 'series') => {
-    if (type === 'variations') toggleIgnoreVariations();
-    if (type === 'series') toggleOnlyFinishedSeries();
-  };
-
-  const confirmChanges = useEventCallback(() => {
-    setOperationsPending(true);
-
-    const operations = map(fileOptions, (option, id) => {
-      if (!selectedEpisode) return null;
-
-      const file = selectedEpisode.Files!.find(item => item.ID === toNumber(id))!;
-      if (!file) return null;
-      if (option === 'delete') return deleteFile({ fileId: file.ID, removeFolder: false });
-      if (option === 'variation' && !file.IsVariation) return markVariation({ fileId: file.ID, variation: true });
-      if (option === 'keep' && file.IsVariation) return markVariation({ fileId: file.ID, variation: false });
-      return null;
-    });
-
-    Promise.all(operations)
-      .then(() => toast.success('Successful!'))
-      .catch(() => toast.error('One or more operations failed!'))
-      .finally(() => {
-        setOperationsPending(false);
-        resetQueries(['release-management']);
-        setSelectedEpisode(undefined);
-      });
-  });
-
-  return (
-    <div className="flex grow flex-col gap-y-6 overflow-y-auto">
-      <ShokoPanel title={<Title />} options={<ItemCount count={seriesCount} suffix="Series" />}>
-        <div className="flex items-center gap-x-3">
-          <div
-            className={cx(
-              'relative box-border flex grow items-center gap-x-4 rounded-md border border-panel-border bg-panel-background-alt px-4 py-2 transition-opacity',
-              selectedEpisode && 'pointer-events-none opacity-65',
-            )}
-          >
-            <MenuButton
-              onClick={() => invalidateQueries(['release-management', 'series'])}
-              icon={mdiRefresh}
-              name="Refresh"
-            />
-
-            <Checkbox
-              id="ignore-variations"
-              isChecked={ignoreVariations}
-              onChange={() => handleCheckboxChange('variations')}
-              label="Ignore Variations"
-              labelRight
-            />
-
-            <Checkbox
-              id="only-finished-series"
-              isChecked={onlyFinishedSeries}
-              onChange={() => handleCheckboxChange('series')}
-              label="Only Finished Series"
-              labelRight
-            />
-          </div>
-
-          {/* TODO: Add support for auto-delete */}
-          {/* {!selectedEpisode && ( */}
-          {/*   <Button */}
-          {/*     buttonType="primary" */}
-          {/*     className="flex gap-x-2.5 px-4 py-3 font-semibold" */}
-          {/*     disabled={seriesCount === 0} */}
-          {/*   > */}
-          {/*     <Icon path={mdiFileDocumentMultipleOutline} size={0.8333} /> */}
-          {/*     Auto-Delete Multiples */}
-          {/*   </Button> */}
-          {/* )} */}
-
-          {!selectedEpisode && (
-            <Button
-              buttonType="secondary"
-              className="flex gap-x-2.5 px-4 py-3 font-semibold"
-              disabled={!selectedSeries}
-              onClick={toggleShowQuickSelectModal}
-            >
-              <Icon path={mdiSelectMultiple} size={0.8333} />
-              Quick Select
-            </Button>
-          )}
-
-          {selectedEpisode && (
-            <div className="flex items-center justify-end gap-x-3">
-              <Button
-                buttonType="secondary"
-                className="flex gap-x-2.5 px-4 py-3 font-semibold"
-                onClick={() => setSelectedEpisode(undefined)}
-              >
-                <Icon path={mdiCloseCircleOutline} size={0.8333} />
-                Cancel
-              </Button>
-              <Button
-                buttonType="primary"
-                className="flex gap-x-2.5 px-4 py-3 font-semibold"
-                onClick={confirmChanges}
-                loading={operationsPending}
-              >
-                <Icon path={mdiFileDocumentMultipleOutline} size={0.8333} />
-                Confirm
-              </Button>
-            </div>
-          )}
-        </div>
-      </ShokoPanel>
-
-      <div className="relative flex grow">
-        <TransitionDiv show={!selectedEpisode} className="absolute flex size-full gap-x-3">
-          <MultiplesUtilList
-            ignoreVariations={ignoreVariations}
-            onlyFinishedSeries={onlyFinishedSeries}
-            setSelectedEpisode={setSelectedEpisode}
-            setSelectedSeriesId={setSelectedSeries}
-            setSeriesCount={setSeriesCount}
-          />
-        </TransitionDiv>
-
-        <TransitionDiv
-          show={!!selectedEpisode}
-          className="absolute flex size-full flex-col gap-y-6 overflow-y-auto rounded-md border border-panel-border bg-panel-background p-6"
-        >
-          <MultiplesUtilEpisode
-            episode={selectedEpisode}
-            setFileOptions={setFileOptions}
-          />
-        </TransitionDiv>
-      </div>
-
-      <QuickSelectModal
-        show={showQuickSelectModal}
-        onClose={toggleShowQuickSelectModal}
-        seriesId={selectedSeries}
-      />
-    </div>
-  );
-};
-
-export default MultiplesUtil;
diff --git a/src/pages/utilities/Renamer.tsx b/src/pages/utilities/Renamer.tsx
index 6cd6fa6d3..7b6f69269 100644
--- a/src/pages/utilities/Renamer.tsx
+++ b/src/pages/utilities/Renamer.tsx
@@ -497,184 +497,187 @@ const Renamer = () => {
   }, [addedFiles.length, configEdited, moveFiles, relocatePending, renameFiles]);
 
   return (
-    <div className="flex grow flex-col gap-y-3">
-      <ShokoPanel title="File Rename">
-        <div className="flex items-center gap-x-3">
-          <Menu
-            selectedRows={selectedRows}
-            moveFiles={moveFiles}
-            renameFiles={renameFiles}
-            toggleMoveFiles={toggleMoveFiles}
-            toggleRenameFiles={toggleRenameFiles}
-            disable={relocatePending}
-          />
-          <div className="flex gap-x-3">
-            <Button
-              buttonType="secondary"
-              buttonSize="normal"
-              className="flex h-13 items-center"
-              onClick={toggleSettings}
-              disabled={!renamerConfigsQuery.isSuccess}
-            >
-              <Icon path={mdiCogOutline} size={1} />
-            </Button>
-            <Button
-              buttonType="secondary"
-              buttonSize="normal"
-              className="flex h-13 items-center"
-              onClick={toggleAddFilesModal}
-              disabled={relocatePending}
-            >
-              Add Files
-            </Button>
-            <Button
-              buttonType="primary"
-              buttonSize="normal"
-              className="flex h-13 flex-wrap items-center gap-x-2"
-              onClick={handleRename}
-              loading={relocatePending}
-              disabled={renameDisabled}
-              tooltip={renameDisabledReason}
-            >
-              <Icon path={mdiFileDocumentEditOutline} size={1} />
-              Rename Files
-            </Button>
+    <>
+      <title>Utilities &gt; File Rename | Shoko</title>
+      <div className="flex grow flex-col gap-y-3">
+        <ShokoPanel title="File Rename">
+          <div className="flex items-center gap-x-3">
+            <Menu
+              selectedRows={selectedRows}
+              moveFiles={moveFiles}
+              renameFiles={renameFiles}
+              toggleMoveFiles={toggleMoveFiles}
+              toggleRenameFiles={toggleRenameFiles}
+              disable={relocatePending}
+            />
+            <div className="flex gap-x-3">
+              <Button
+                buttonType="secondary"
+                buttonSize="normal"
+                className="flex h-13 items-center"
+                onClick={toggleSettings}
+                disabled={!renamerConfigsQuery.isSuccess}
+              >
+                <Icon path={mdiCogOutline} size={1} />
+              </Button>
+              <Button
+                buttonType="secondary"
+                buttonSize="normal"
+                className="flex h-13 items-center"
+                onClick={toggleAddFilesModal}
+                disabled={relocatePending}
+              >
+                Add Files
+              </Button>
+              <Button
+                buttonType="primary"
+                buttonSize="normal"
+                className="flex h-13 flex-wrap items-center gap-x-2"
+                onClick={handleRename}
+                loading={relocatePending}
+                disabled={renameDisabled}
+                tooltip={renameDisabledReason}
+              >
+                <Icon path={mdiFileDocumentEditOutline} size={1} />
+                Rename Files
+              </Button>
+            </div>
+            <AddFilesModal show={showAddFilesModal} onClose={toggleAddFilesModal} />
           </div>
-          <AddFilesModal show={showAddFilesModal} onClose={toggleAddFilesModal} />
-        </div>
-      </ShokoPanel>
-
-      <AnimateHeight height={showSettings ? 'auto' : 0}>
-        <div className={cx('my-3 flex !h-[32rem] gap-x-6', relocatePending && 'opacity-65 pointer-events-none')}>
-          {renamerConfigsQuery.isSuccess && (
-            <>
-              <div className="flex w-1/3 flex-col gap-y-6">
-                <ShokoPanel title="Renamer Selection" contentClassName="gap-y-5" fullHeight={!renamerSettingsExist}>
-                  <Select
-                    label="Config"
-                    id="renamer-config"
-                    value={selectedConfig.Name}
-                    onChange={event => changeSelectedConfig(event.target.value)}
-                  >
-                    {renamerConfigsQuery.data.map(renamerConfig => (
-                      <ConfigOption config={renamerConfig} key={renamerConfig.Name} />
-                    ))}
-
-                    {renamerConfigsQuery.data.length === 0 && (
-                      <option key="na" value="na">
-                        No renamer found!
-                      </option>
-                    )}
-                  </Select>
-                  <div className="flex justify-end gap-x-3 font-semibold">
-                    <Button
-                      onClick={handleSetAsDefault}
-                      buttonType="secondary"
-                      buttonSize="normal"
-                      loading={settingsPatchPending}
-                      disabled={(selectedConfig.Name === settings.Plugins.Renamer.DefaultRenamer)
-                        || settingsPatchPending}
-                      tooltip={selectedConfig.Name === settings.Plugins.Renamer.DefaultRenamer
-                        ? 'Already set as default!'
-                        : ''}
-                    >
-                      Set as default
-                    </Button>
-                    <Button
-                      onClick={handleDeleteConfig}
-                      buttonType="danger"
-                      buttonSize="normal"
-                      loading={deletePending}
-                      disabled={(selectedConfig.Name === settings.Plugins.Renamer.DefaultRenamer) || deletePending}
-                      tooltip={(selectedConfig.Name === settings.Plugins.Renamer.DefaultRenamer)
-                        ? 'Cannot delete default config!'
-                        : ''}
-                    >
-                      Delete
-                    </Button>
-                    <Button
-                      onClick={() => openConfigModal(true)}
-                      buttonType="secondary"
-                      buttonSize="normal"
-                    >
-                      Rename
-                    </Button>
-                    <Button
-                      onClick={() => openConfigModal(false)}
-                      buttonType="secondary"
-                      buttonSize="normal"
+        </ShokoPanel>
+
+        <AnimateHeight height={showSettings ? 'auto' : 0}>
+          <div className={cx('my-3 flex !h-[32rem] gap-x-6', relocatePending && 'opacity-65 pointer-events-none')}>
+            {renamerConfigsQuery.isSuccess && (
+              <>
+                <div className="flex w-1/3 flex-col gap-y-6">
+                  <ShokoPanel title="Renamer Selection" contentClassName="gap-y-5" fullHeight={!renamerSettingsExist}>
+                    <Select
+                      label="Config"
+                      id="renamer-config"
+                      value={selectedConfig.Name}
+                      onChange={event => changeSelectedConfig(event.target.value)}
                     >
-                      New
-                    </Button>
-                    <Button
-                      onClick={handleSaveConfig}
-                      buttonType="primary"
-                      buttonSize="normal"
-                      loading={savePending}
-                      disabled={savePending || !renamer?.DefaultSettings}
-                      tooltip={!renamer?.DefaultSettings ? 'Renamer does not have any settings to save.' : ''}
-                    >
-                      Save
-                    </Button>
-                    <ConfigModal
-                      show={showConfigModal}
-                      onClose={toggleConfigModal}
-                      rename={configModelRename}
-                      config={selectedConfig}
-                      changeSelectedConfig={changeSelectedConfig}
+                      {renamerConfigsQuery.data.map(renamerConfig => (
+                        <ConfigOption config={renamerConfig} key={renamerConfig.Name} />
+                      ))}
+
+                      {renamerConfigsQuery.data.length === 0 && (
+                        <option key="na" value="na">
+                          No renamer found!
+                        </option>
+                      )}
+                    </Select>
+                    <div className="flex justify-end gap-x-3 font-semibold">
+                      <Button
+                        onClick={handleSetAsDefault}
+                        buttonType="secondary"
+                        buttonSize="normal"
+                        loading={settingsPatchPending}
+                        disabled={(selectedConfig.Name === settings.Plugins.Renamer.DefaultRenamer)
+                          || settingsPatchPending}
+                        tooltip={selectedConfig.Name === settings.Plugins.Renamer.DefaultRenamer
+                          ? 'Already set as default!'
+                          : ''}
+                      >
+                        Set as default
+                      </Button>
+                      <Button
+                        onClick={handleDeleteConfig}
+                        buttonType="danger"
+                        buttonSize="normal"
+                        loading={deletePending}
+                        disabled={(selectedConfig.Name === settings.Plugins.Renamer.DefaultRenamer) || deletePending}
+                        tooltip={(selectedConfig.Name === settings.Plugins.Renamer.DefaultRenamer)
+                          ? 'Cannot delete default config!'
+                          : ''}
+                      >
+                        Delete
+                      </Button>
+                      <Button
+                        onClick={() => openConfigModal(true)}
+                        buttonType="secondary"
+                        buttonSize="normal"
+                      >
+                        Rename
+                      </Button>
+                      <Button
+                        onClick={() => openConfigModal(false)}
+                        buttonType="secondary"
+                        buttonSize="normal"
+                      >
+                        New
+                      </Button>
+                      <Button
+                        onClick={handleSaveConfig}
+                        buttonType="primary"
+                        buttonSize="normal"
+                        loading={savePending}
+                        disabled={savePending || !renamer?.DefaultSettings}
+                        tooltip={!renamer?.DefaultSettings ? 'Renamer does not have any settings to save.' : ''}
+                      >
+                        Save
+                      </Button>
+                      <ConfigModal
+                        show={showConfigModal}
+                        onClose={toggleConfigModal}
+                        rename={configModelRename}
+                        config={selectedConfig}
+                        changeSelectedConfig={changeSelectedConfig}
+                      />
+                    </div>
+                  </ShokoPanel>
+
+                  {renamerSettingsExist && (
+                    <ShokoPanel title="Selected Renamer Config">
+                      {/* TODO: Maybe a todo... The transition div for checkbox is buggy when AnimateHeight is used. */}
+                      {/* It doesn't appear before a click event when height is changed. Adding showSetting force re-renders it. */}
+                      {newConfig && renamer?.Settings && showSettings && (
+                        <RenamerSettings
+                          newConfig={newConfig}
+                          setNewConfig={setNewConfig}
+                          settingsModel={renamer.Settings}
+                        />
+                      )}
+                    </ShokoPanel>
+                  )}
+                </div>
+
+                <ShokoPanel title="Selected Renamer Script" className="w-2/3" disableOverflow>
+                  {newConfig && renamer?.Settings && (
+                    <RenamerScript
+                      newConfig={newConfig}
+                      setNewConfig={setNewConfig}
+                      settingsModel={renamer.Settings}
                     />
-                  </div>
+                  )}
                 </ShokoPanel>
+              </>
+            )}
+          </div>
+        </AnimateHeight>
 
-                {renamerSettingsExist && (
-                  <ShokoPanel title="Selected Renamer Config">
-                    {/* TODO: Maybe a todo... The transition div for checkbox is buggy when AnimateHeight is used. */}
-                    {/* It doesn't appear before a click event when height is changed. Adding showSetting force re-renders it. */}
-                    {newConfig && renamer?.Settings && showSettings && (
-                      <RenamerSettings
-                        newConfig={newConfig}
-                        setNewConfig={setNewConfig}
-                        settingsModel={renamer.Settings}
-                      />
-                    )}
-                  </ShokoPanel>
-                )}
-              </div>
-
-              <ShokoPanel title="Selected Renamer Script" className="w-2/3" disableOverflow>
-                {newConfig && renamer?.Settings && (
-                  <RenamerScript
-                    newConfig={newConfig}
-                    setNewConfig={setNewConfig}
-                    settingsModel={renamer.Settings}
-                  />
-                )}
-              </ShokoPanel>
-            </>
+        <ShokoPanel title="Renamer Preview" className="min-h-[40rem] grow">
+          {addedFiles.length === 0 && (
+            <div className="flex grow items-center justify-center font-semibold">No files selected!</div>
           )}
-        </div>
-      </AnimateHeight>
-
-      <ShokoPanel title="Renamer Preview" className="min-h-[40rem] grow">
-        {addedFiles.length === 0 && (
-          <div className="flex grow items-center justify-center font-semibold">No files selected!</div>
-        )}
-
-        {addedFiles.length > 0 && (
-          <UtilitiesTable
-            columns={columns}
-            count={addedFiles.length}
-            handleRowSelect={handleRowSelect}
-            rows={addedFiles}
-            rowSelection={rowSelection}
-            setSelectedRows={setRowSelection}
-            fetchNextPreviewPage={fetchPreviewPage}
-            isFetchingNextPage={previewPending}
-            isRenamer
-          />
-        )}
-      </ShokoPanel>
-    </div>
+
+          {addedFiles.length > 0 && (
+            <UtilitiesTable
+              columns={columns}
+              count={addedFiles.length}
+              handleRowSelect={handleRowSelect}
+              rows={addedFiles}
+              rowSelection={rowSelection}
+              setSelectedRows={setRowSelection}
+              fetchNextPreviewPage={fetchPreviewPage}
+              isFetchingNextPage={previewPending}
+              isRenamer
+            />
+          )}
+        </ShokoPanel>
+      </div>
+    </>
   );
 };
 
diff --git a/src/pages/utilities/SeriesWithoutFilesUtility.tsx b/src/pages/utilities/SeriesWithoutFilesUtility.tsx
index 259c718de..69c262bf7 100644
--- a/src/pages/utilities/SeriesWithoutFilesUtility.tsx
+++ b/src/pages/utilities/SeriesWithoutFilesUtility.tsx
@@ -158,53 +158,56 @@ function SeriesWithoutFilesUtility() {
   } = useRowSelection<SeriesType>(series);
 
   return (
-    <div className="flex grow flex-col gap-y-6">
-      <div>
-        <ShokoPanel
-          title="Series Without Files"
-          options={<ItemCount count={seriesCount} selected={selectedRows?.length} suffix="Series" />}
-        >
-          <div className="flex items-center gap-x-3">
-            <Input
-              type="text"
-              placeholder="Search..."
-              startIcon={mdiMagnify}
-              id="search"
-              value={search}
-              onChange={event => setSearch(event.target.value)}
-              inputClassName="px-4 py-3"
+    <>
+      <title>Utilities &gt; Series Without Files | Shoko</title>
+      <div className="flex grow flex-col gap-y-6">
+        <div>
+          <ShokoPanel
+            title="Series Without Files"
+            options={<ItemCount count={seriesCount} selected={selectedRows?.length} suffix="Series" />}
+          >
+            <div className="flex items-center gap-x-3">
+              <Input
+                type="text"
+                placeholder="Search..."
+                startIcon={mdiMagnify}
+                id="search"
+                value={search}
+                onChange={event => setSearch(event.target.value)}
+                inputClassName="px-4 py-3"
+              />
+              <Menu selectedRows={selectedRows} setSelectedRows={setRowSelection} />
+            </div>
+          </ShokoPanel>
+        </div>
+
+        <div className="flex grow overflow-y-auto rounded-lg border border-panel-border bg-panel-background px-4 py-6">
+          {seriesQuery.isPending && (
+            <div className="flex grow items-center justify-center text-panel-text-primary">
+              <Icon path={mdiLoading} size={4} spin />
+            </div>
+          )}
+
+          {!seriesQuery.isPending && seriesCount === 0 && (
+            <div className="flex grow items-center justify-center font-semibold">No series without files!</div>
+          )}
+
+          {seriesQuery.isSuccess && seriesCount > 0 && (
+            <UtilitiesTable
+              columns={columns}
+              count={seriesCount}
+              fetchNextPage={seriesQuery.fetchNextPage}
+              handleRowSelect={handleRowSelect}
+              isFetchingNextPage={seriesQuery.isFetchingNextPage}
+              rows={series}
+              rowSelection={rowSelection}
+              setSelectedRows={setRowSelection}
+              skipSort
             />
-            <Menu selectedRows={selectedRows} setSelectedRows={setRowSelection} />
-          </div>
-        </ShokoPanel>
-      </div>
-
-      <div className="flex grow overflow-y-auto rounded-lg border border-panel-border bg-panel-background px-4 py-6">
-        {seriesQuery.isPending && (
-          <div className="flex grow items-center justify-center text-panel-text-primary">
-            <Icon path={mdiLoading} size={4} spin />
-          </div>
-        )}
-
-        {!seriesQuery.isPending && seriesCount === 0 && (
-          <div className="flex grow items-center justify-center font-semibold">No series without files!</div>
-        )}
-
-        {seriesQuery.isSuccess && seriesCount > 0 && (
-          <UtilitiesTable
-            columns={columns}
-            count={seriesCount}
-            fetchNextPage={seriesQuery.fetchNextPage}
-            handleRowSelect={handleRowSelect}
-            isFetchingNextPage={seriesQuery.isFetchingNextPage}
-            rows={series}
-            rowSelection={rowSelection}
-            setSelectedRows={setRowSelection}
-            skipSort
-          />
-        )}
+          )}
+        </div>
       </div>
-    </div>
+    </>
   );
 }
 
diff --git a/src/pages/utilities/UnrecognizedUtilityTabs/IgnoredFilesTab.tsx b/src/pages/utilities/UnrecognizedUtilityTabs/IgnoredFilesTab.tsx
index 19c7e850d..c76f97fab 100644
--- a/src/pages/utilities/UnrecognizedUtilityTabs/IgnoredFilesTab.tsx
+++ b/src/pages/utilities/UnrecognizedUtilityTabs/IgnoredFilesTab.tsx
@@ -132,54 +132,57 @@ function IgnoredFilesTab() {
   } = useRowSelection<FileType>(files);
 
   return (
-    <div className="flex grow flex-col gap-y-6">
-      <div>
-        <ShokoPanel title={<Title />} options={<ItemCount count={fileCount} selected={selectedRows?.length} />}>
-          <div className="flex items-center gap-x-3">
-            <Input
-              type="text"
-              placeholder="Search..."
-              startIcon={mdiMagnify}
-              id="search"
-              value={search}
-              onChange={setSearch}
-              inputClassName="px-4 py-3"
-            />
-            <Menu
-              selectedRows={selectedRows}
+    <>
+      <title>Utilities &gt; Ignored Files | Shoko</title>
+      <div className="flex grow flex-col gap-y-6">
+        <div>
+          <ShokoPanel title={<Title />} options={<ItemCount count={fileCount} selected={selectedRows?.length} />}>
+            <div className="flex items-center gap-x-3">
+              <Input
+                type="text"
+                placeholder="Search..."
+                startIcon={mdiMagnify}
+                id="search"
+                value={search}
+                onChange={setSearch}
+                inputClassName="px-4 py-3"
+              />
+              <Menu
+                selectedRows={selectedRows}
+                setSelectedRows={setRowSelection}
+              />
+            </div>
+          </ShokoPanel>
+        </div>
+
+        <TransitionDiv className="flex grow overflow-y-auto rounded-lg border border-panel-border bg-panel-background p-6">
+          {filesQuery.isPending && (
+            <div className="flex grow items-center justify-center text-panel-text-primary">
+              <Icon path={mdiLoading} size={4} spin />
+            </div>
+          )}
+
+          {!filesQuery.isPending && fileCount === 0 && (
+            <div className="flex grow items-center justify-center font-semibold">No ignored file(s)!</div>
+          )}
+
+          {filesQuery.isSuccess && fileCount > 0 && (
+            <UtilitiesTable
+              count={fileCount}
+              fetchNextPage={filesQuery.fetchNextPage}
+              handleRowSelect={handleRowSelect}
+              columns={columns}
+              isFetchingNextPage={filesQuery.isFetchingNextPage}
+              rows={files}
+              rowSelection={rowSelection}
               setSelectedRows={setRowSelection}
+              setSortCriteria={setSortCriteria}
+              sortCriteria={sortCriteria}
             />
-          </div>
-        </ShokoPanel>
+          )}
+        </TransitionDiv>
       </div>
-
-      <TransitionDiv className="flex grow overflow-y-auto rounded-lg border border-panel-border bg-panel-background p-6">
-        {filesQuery.isPending && (
-          <div className="flex grow items-center justify-center text-panel-text-primary">
-            <Icon path={mdiLoading} size={4} spin />
-          </div>
-        )}
-
-        {!filesQuery.isPending && fileCount === 0 && (
-          <div className="flex grow items-center justify-center font-semibold">No ignored file(s)!</div>
-        )}
-
-        {filesQuery.isSuccess && fileCount > 0 && (
-          <UtilitiesTable
-            count={fileCount}
-            fetchNextPage={filesQuery.fetchNextPage}
-            handleRowSelect={handleRowSelect}
-            columns={columns}
-            isFetchingNextPage={filesQuery.isFetchingNextPage}
-            rows={files}
-            rowSelection={rowSelection}
-            setSelectedRows={setRowSelection}
-            setSortCriteria={setSortCriteria}
-            sortCriteria={sortCriteria}
-          />
-        )}
-      </TransitionDiv>
-    </div>
+    </>
   );
 }
 
diff --git a/src/pages/utilities/UnrecognizedUtilityTabs/ManuallyLinkedTab.tsx b/src/pages/utilities/UnrecognizedUtilityTabs/ManuallyLinkedTab.tsx
index 11fe3f36b..4981b7f05 100644
--- a/src/pages/utilities/UnrecognizedUtilityTabs/ManuallyLinkedTab.tsx
+++ b/src/pages/utilities/UnrecognizedUtilityTabs/ManuallyLinkedTab.tsx
@@ -220,97 +220,100 @@ const ManuallyLinkedTab = () => {
   }, [seriesQuery.data]);
 
   return (
-    <TransitionDiv className="flex grow flex-col gap-y-6 overflow-y-auto">
-      <ShokoPanel
-        title={<Title />}
-        options={
-          <ItemCount
-            count={seriesCount}
-            selected={selectedFileIds.length}
-            suffix="Series"
-            selectedSuffix={selectedFileIds.length === 1 ? 'File' : 'Files'}
-          />
-        }
-      >
-        <div className="flex items-center gap-x-3">
-          <Input
-            type="text"
-            placeholder="Search..."
-            startIcon={mdiMagnify}
-            id="search"
-            value={search}
-            onChange={event => setSearch(event.target.value)}
-            inputClassName="px-4 py-3"
-          />
-          <Menu selectedFileIds={selectedFileIds} setSelectedRows={setRowSelection} />
-          <TransitionDiv show={selectedFileIds.length !== 0} className="flex gap-x-3">
-            <Button
-              buttonType="primary"
-              buttonSize="normal"
-              className="flex gap-x-2.5 px-4 py-3 font-semibold"
-              onClick={unlinkFiles}
-              loading={unlinkingInProgress}
-            >
-              <Icon path={mdiLinkOff} size={1} />
-              Unlink
-            </Button>
-          </TransitionDiv>
-        </div>
-      </ShokoPanel>
+    <>
+      <title>Utilities &gt; Manually Linked Files | Shoko</title>
+      <TransitionDiv className="flex grow flex-col gap-y-6 overflow-y-auto">
+        <ShokoPanel
+          title={<Title />}
+          options={
+            <ItemCount
+              count={seriesCount}
+              selected={selectedFileIds.length}
+              suffix="Series"
+              selectedSuffix={selectedFileIds.length === 1 ? 'File' : 'Files'}
+            />
+          }
+        >
+          <div className="flex items-center gap-x-3">
+            <Input
+              type="text"
+              placeholder="Search..."
+              startIcon={mdiMagnify}
+              id="search"
+              value={search}
+              onChange={event => setSearch(event.target.value)}
+              inputClassName="px-4 py-3"
+            />
+            <Menu selectedFileIds={selectedFileIds} setSelectedRows={setRowSelection} />
+            <TransitionDiv show={selectedFileIds.length !== 0} className="flex gap-x-3">
+              <Button
+                buttonType="primary"
+                buttonSize="normal"
+                className="flex gap-x-2.5 px-4 py-3 font-semibold"
+                onClick={unlinkFiles}
+                loading={unlinkingInProgress}
+              >
+                <Icon path={mdiLinkOff} size={1} />
+                Unlink
+              </Button>
+            </TransitionDiv>
+          </div>
+        </ShokoPanel>
 
-      <div className="flex grow gap-x-3">
-        <div className="flex w-1/2 overflow-y-auto rounded-md border border-panel-border bg-panel-background p-6">
-          {seriesQuery.isPending && (
-            <div className="flex grow items-center justify-center text-panel-text-primary">
-              <Icon path={mdiLoading} size={4} spin />
-            </div>
-          )}
+        <div className="flex grow gap-x-3">
+          <div className="flex w-1/2 overflow-y-auto rounded-md border border-panel-border bg-panel-background p-6">
+            {seriesQuery.isPending && (
+              <div className="flex grow items-center justify-center text-panel-text-primary">
+                <Icon path={mdiLoading} size={4} spin />
+              </div>
+            )}
 
-          {!seriesQuery.isPending && seriesCount === 0 && (
-            <div className="flex grow items-center justify-center text-lg font-semibold">
-              No series with manually linked files!
-            </div>
-          )}
+            {!seriesQuery.isPending && seriesCount === 0 && (
+              <div className="flex grow items-center justify-center text-lg font-semibold">
+                No series with manually linked files!
+              </div>
+            )}
 
-          {seriesQuery.isSuccess && seriesCount > 0 && (
-            <UtilitiesTable
-              columns={seriesColumns}
-              count={seriesCount}
-              fetchNextPage={seriesQuery.fetchNextPage}
-              isFetchingNextPage={seriesQuery.isFetchingNextPage}
-              rows={series}
-              skipSort
-              handleRowSelect={(id, _) => setSelectedSeries(id)}
-              rowSelection={{ [selectedSeries]: true }}
-            />
-          )}
-        </div>
+            {seriesQuery.isSuccess && seriesCount > 0 && (
+              <UtilitiesTable
+                columns={seriesColumns}
+                count={seriesCount}
+                fetchNextPage={seriesQuery.fetchNextPage}
+                isFetchingNextPage={seriesQuery.isFetchingNextPage}
+                rows={series}
+                skipSort
+                handleRowSelect={(id, _) => setSelectedSeries(id)}
+                rowSelection={{ [selectedSeries]: true }}
+              />
+            )}
+          </div>
 
-        <div className="flex w-1/2 overflow-y-auto rounded-md border border-panel-border bg-panel-background p-6">
-          {selectedSeries === 0 && <div className="m-auto text-lg font-semibold">Select Series to Populate</div>}
+          <div className="flex w-1/2 overflow-y-auto rounded-md border border-panel-border bg-panel-background p-6">
+            {selectedSeries === 0 && <div className="m-auto text-lg font-semibold">Select Series to Populate</div>}
 
-          {selectedSeries > 0 && episodesQuery.isPending && (
-            <div className="flex grow items-center justify-center text-panel-text-primary">
-              <Icon path={mdiLoading} size={4} spin />
-            </div>
-          )}
+            {selectedSeries > 0 && episodesQuery.isPending && (
+              <div className="flex grow items-center justify-center text-panel-text-primary">
+                <Icon path={mdiLoading} size={4} spin />
+              </div>
+            )}
 
-          {selectedSeries > 0 && episodesQuery.isSuccess && episodeCount > 0 && (
-            <UtilitiesTable
-              columns={episodeColumns}
-              count={episodeCount}
-              fetchNextPage={episodesQuery.fetchNextPage}
-              isFetchingNextPage={episodesQuery.isFetchingNextPage}
-              rows={episodes}
-              skipSort
-              handleRowSelect={handleRowSelect}
-              rowSelection={rowSelection}
-              setSelectedRows={setRowSelection}
-            />
-          )}
+            {selectedSeries > 0 && episodesQuery.isSuccess && episodeCount > 0 && (
+              <UtilitiesTable
+                columns={episodeColumns}
+                count={episodeCount}
+                fetchNextPage={episodesQuery.fetchNextPage}
+                isFetchingNextPage={episodesQuery.isFetchingNextPage}
+                rows={episodes}
+                skipSort
+                handleRowSelect={handleRowSelect}
+                rowSelection={rowSelection}
+                setSelectedRows={setRowSelection}
+              />
+            )}
+          </div>
         </div>
-      </div>
-    </TransitionDiv>
+      </TransitionDiv>
+    </>
   );
 };
 
diff --git a/src/pages/utilities/UnrecognizedUtilityTabs/UnrecognizedTab.tsx b/src/pages/utilities/UnrecognizedUtilityTabs/UnrecognizedTab.tsx
index 982c43543..5ce05dce5 100644
--- a/src/pages/utilities/UnrecognizedUtilityTabs/UnrecognizedTab.tsx
+++ b/src/pages/utilities/UnrecognizedUtilityTabs/UnrecognizedTab.tsx
@@ -373,6 +373,7 @@ function UnrecognizedTab() {
 
   return (
     <>
+      <title>Utilities &gt; Unrecognized Files | Shoko</title>
       <div className="flex grow flex-col gap-y-6" ref={tabContainerRef}>
         <div>
           <ShokoPanel title={<Title />} options={<ItemCount count={fileCount} selected={selectedRows?.length} />}>