Repository: IgnisDa/ryot (language: rust)

Issue:
[FEATURE REQUEST] - Better manga tracking
The current tracking of manga is "lacking", as storing % read isn't the correct/best way to track manga.

For example, suppose I'm reading a manga which is still releasing with (as of right now) 10 chapters, and so far I've read 2 chapters. Right now, I'd manually have to store 2/10 = 20%. But now imagine a new chapter is released tomorrow, my actual percentage would now be 2/11, so the stored % isn't correct anymore.

For manga which is not releasing anymore (there won't be more chapters), you can input an amount of chapters and it will automatically convert this to a percentage. However, it would be way better to track you progress in terms of chapters than percentage.

What I propose is something similar to shows. You would track when (at which date), you've read chapter 1. Then when you've read a new chapter you would add this again to Ryot, similar to shows.
However, with shows there is metadata available about the actual episode. This might? not be the case for manga, but imo it won't be a problem. You can just store the number of the chapter(s) as a number (just like percentage) and a date with it.

Also, I'm not sure if you it's done on purpose but whenever I set a new percentage it overwrites the old one. I'd like to see the history at which date I read which chapter (just like with shows), and not that it just keeps track of how many chapters I've read in total.


Discussion / hints:
@Robin-Sch I have decided to go with tracking like shows. Each chapter would be one entry. I have a couple of questions:

1. would the same happen for anime, ie, each episode be one seen entry?
2. what about previous entries? ideally if there are seen items for a manga, that one should get deleted and then a new entries be added for each chapter. Does this sound right?
1. For anime it's exactly the same as a show, so yes.
2. You mean after you've re-read a chapter? Uhm... I'm not so sure of this actually. People might read a chapter (or a compete) manga twice for whatever reason and they want to track both. But I'm not so sure. How does this happen with shows, can you view one episode multiple times?

Also I just realised but for some manga they like to publish some "extra's" in between chapters (like a "behind the scence" ish). Most of the times they get a chapter number with one decimal, for example 5.5. (Meaning after chapter 5, but before chapter 6). These are counted as completely new chapters, but their numbering scene messing things up. (For example a manga with 3 chapters can have chapters 1, 1.5 and 2).
> can you view one episode multiple times

Yes

> some manga they like to publish some "extra's"

So it would work if I allowed the chapter number to be a decimal?
So then it'd be the best if it would be similar/the same as shows, that you can read a chapter multiple times.
And allowing a chapter number to be a decimal should work
@Robin-Sch 

> what about previous entries? ideally if there are seen items for a manga, that one should get deleted and then a new entries be added for each chapter. Does this sound right?

What about previously marked as read anime/manga that do not have num chapters/episodes information associated with them? How would entries be created for them? (Eg: https://ryot.fly.dev/media/item/12424)

EDIT: one possible solution would be to allow the `chapter` field of the seen entry be nullable. And if it is null, then it will be assumed that the entire manga has been read. If not, then it means that only one specific chapter has been read.
Yeah that could work, or maybe -1 ?
Ehhh, -1 does not convey any meaning on the database level. I will just stick with `null`.

Thanks for the help.
@Robin-Sch This was released with `v4.2.0`. Please try it out and let me know if the UI feels intutive enough.
I have just taken a look at it, and it seems like I can only mark the whole manga is finished/read, but not individual chapters.
Which manga?
All of them, both releasing and finished. For example: Jujutsu Kaisen, One Piece, etc.
I click on "update progress", then enter a date, and after that it doesn't ask for a chapter number, but rather marks it as completely read.
Also editing doesn't prompt for chapter number, only start/end date.
![Screenshot_20240207-234732_Firefox](https://github.com/IgnisDa/ryot/assets/60938164/f3bb719a-24f8-4c48-aa34-c71e037a21a1)

Here is what updating One Piece looks like. Does this not work for your use case?
It does not work for me like this.

actions -> update progress -> add to read history:
![image](https://github.com/IgnisDa/ryot/assets/46354497/e59efa0e-d877-4894-ada7-bc26a1f681a4)

history -> edit:
![image](https://github.com/IgnisDa/ryot/assets/46354497/9f1ad8db-73eb-4ae2-b877-ff4ce3ce64cd)

Version: v4.2.3
This is under the manga section, right?
yes it is under manga, not anime or something else
Try updating metadata from the "More action" menu.
Even after updating the metadata (and waiting some time) it does not work
Okay I will push a change with https://github.com/IgnisDa/ryot/pull/634 which might fix it.
@Robin-Sch Please upgrade to the latest version and see if the issue still persists.
Tested on Ryot 4.2.4 with two manga from Anilist—a finished manga and an ongoing manga.

I have clicked "Update metadata" for all scenarios.

Attempting to set progress on a manga with existing progress from before 4.x only allows me to set the progress to a percentage.

After clearing a manga's history, I no longer have any options under "Update progress" besides "Add to read history", even when it's in the In Progress collection.

Clicking "Add to read history", I can specify a chapter. It will mark that chapter as "Completed" in the history and remove the manga from the In Progress category entirely despite the chapter not being the final chapter in a complete series.

Briefly trying a MAL-sourced manga, I got similar results.

Additional comments:

When I read "Add to read history", I imagine it's going to mark the entire manga as read. The "Set Progress" label was more clear to me. I imagine it's meant to have all of the options from before, anyways.

It would be useful to be able to "Mark all *unread* chapters before this as watched". Also might make more sense to use the word "read" instead of "watched".
I just updated my instance and agree with @Alzarath 's points above. Especially with the last part, I think "Mark all chapters before this as ~~watched~~ read" should be omitted completely, and replaced with "Mark all unread chapters before this one". This could work in a way that it just skips duplicated entries, and should be good.
I won't be able to provide a "Mark all chapters before this as read" provide button due to how the action handler is written.
So if I read chapter 1-5 on the 1st of January, 6-10 on the 2nd and so on until chapter 100. Adding this manga to my history would require 100 individual steps... Now multiply this by multiple manga or manga with even more chapters and have fun adding all of it I guess.

Also, doesn't the same thing hold for shows? Suppose u binge watched 10 episodes on day 1, another 10 on day 2 and another 10 on day 3. Good luck doing 30 actions to add this to your history.

Maybe it's possible to (partially) re-write the action handler to include this use case, as it would save a lot of time.
> Also, doesn't the same thing hold for shows?

Yes it does.

The concerns you raise are totally valid. I just don't have a lot of motivation to work on fixing this 😅.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 2b30f7b181..b5f790f786 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -48,7 +48,7 @@ dependencies = [
  "getrandom",
  "once_cell",
  "version_check",
- "zerocopy 0.7.34",
+ "zerocopy 0.7.35",
 ]
 
 [[package]]
@@ -265,9 +265,9 @@ checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
 
 [[package]]
 name = "async-graphql"
-version = "7.0.6"
+version = "7.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf338d20ba5bab309f55ce8df95d65ee19446f7737f06f4a64593ab2c6b546ad"
+checksum = "2b76aba2f176af685c2229633881a3adeae51f87ae1811781e73910b7001c93e"
 dependencies = [
  "async-graphql-derive",
  "async-graphql-parser",
@@ -303,9 +303,9 @@ dependencies = [
 
 [[package]]
 name = "async-graphql-axum"
-version = "7.0.6"
+version = "7.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28f874ad4bc10519f3fa500e36814452033a5ce9ea681ab0a2e0d3b1f18bae44"
+checksum = "686e48ce7820a1cf404b5c8e9b90ae24d03c867a408d8d651183945c7a554982"
 dependencies = [
  "async-graphql",
  "async-trait",
@@ -321,9 +321,9 @@ dependencies = [
 
 [[package]]
 name = "async-graphql-derive"
-version = "7.0.6"
+version = "7.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc51fd6b7102acda72bc94e8ae1543844d5688ff394a6cf7c21f2a07fe2d64e4"
+checksum = "72e2e26a6b44bc61df3ca8546402cf9204c28e30c06084cc8e75cd5e34d4f150"
 dependencies = [
  "Inflector",
  "async-graphql-parser",
@@ -332,15 +332,15 @@ dependencies = [
  "proc-macro2",
  "quote",
  "strum",
- "syn 2.0.68",
+ "syn 2.0.72",
  "thiserror",
 ]
 
 [[package]]
 name = "async-graphql-parser"
-version = "7.0.6"
+version = "7.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75361eefd64e39f89bead4cb45fddbaf60ddb0e7b15fb7c852b6088bcd63071f"
+checksum = "f801451484b4977d6fe67b29030f81353cabdcbb754e5a064f39493582dac0cf"
 dependencies = [
  "async-graphql-value",
  "pest",
@@ -350,9 +350,9 @@ dependencies = [
 
 [[package]]
 name = "async-graphql-value"
-version = "7.0.6"
+version = "7.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1f665d2d52b41c4ed1f01c43f3ef27a2fe0af2452ed5c8bc7ac9b1a8719afaa"
+checksum = "69117c43c01d81a69890a9f5dd6235f2f027ca8d1ec62d6d3c5e01ca0edb4f2b"
 dependencies = [
  "bytes",
  "indexmap 2.2.6",
@@ -388,7 +388,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -399,7 +399,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -455,9 +455,9 @@ dependencies = [
 
 [[package]]
 name = "aws-sdk-s3"
-version = "1.40.0"
+version = "1.42.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8367c403fdf27690684b926a46ed9524099a69dd5dfcef62028bf4096b5b809f"
+checksum = "558bbcec8db82a1a8af1610afcb3b10d00652d25ad366a0558eecdff2400a1d1"
 dependencies = [
  "ahash 0.8.11",
  "aws-credential-types",
@@ -605,9 +605,9 @@ dependencies = [
  "h2",
  "http 0.2.12",
  "http-body 0.4.6",
- "http-body 1.0.0",
+ "http-body 1.0.1",
  "httparse",
- "hyper 0.14.29",
+ "hyper 0.14.30",
  "hyper-rustls",
  "once_cell",
  "pin-project-lite",
@@ -647,7 +647,7 @@ dependencies = [
  "http 0.2.12",
  "http 1.1.0",
  "http-body 0.4.6",
- "http-body 1.0.0",
+ "http-body 1.0.1",
  "http-body-util",
  "itoa",
  "num-integer",
@@ -696,9 +696,9 @@ dependencies = [
  "bytes",
  "futures-util",
  "http 1.1.0",
- "http-body 1.0.0",
+ "http-body 1.0.1",
  "http-body-util",
- "hyper 1.3.1",
+ "hyper 1.4.1",
  "hyper-util",
  "itoa",
  "matchit",
@@ -732,7 +732,7 @@ dependencies = [
  "bytes",
  "futures-util",
  "http 1.1.0",
- "http-body 1.0.0",
+ "http-body 1.0.1",
  "http-body-util",
  "mime",
  "pin-project-lite",
@@ -752,7 +752,7 @@ dependencies = [
  "heck 0.4.1",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -901,7 +901,7 @@ dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
  "syn_derive",
 ]
 
@@ -941,9 +941,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "bytes"
-version = "1.6.0"
+version = "1.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
+checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
 dependencies = [
  "serde",
 ]
@@ -960,33 +960,33 @@ dependencies = [
 
 [[package]]
 name = "cached"
-version = "0.52.0"
+version = "0.53.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8466736fe5dbcaf8b8ee24f9bbefe43c884dc3e9ff7178da70f55bffca1133c"
+checksum = "b4d73155ae6b28cf5de4cfc29aeb02b8a1c6dab883cb015d15cd514e42766846"
 dependencies = [
  "ahash 0.8.11",
  "cached_proc_macro",
  "cached_proc_macro_types",
  "directories",
  "hashbrown 0.14.5",
- "instant",
  "once_cell",
  "rmp-serde",
  "serde",
  "sled",
  "thiserror",
+ "web-time",
 ]
 
 [[package]]
 name = "cached_proc_macro"
-version = "0.22.0"
+version = "0.23.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "575f32e012222055211b70f5b0601f951f84523410a0e65c81f2744a6042450d"
+checksum = "2f42a145ed2d10dce2191e1dcf30cfccfea9026660e143662ba5eec4017d5daa"
 dependencies = [
  "darling",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -997,18 +997,18 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
 
 [[package]]
 name = "castaway"
-version = "0.2.2"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc"
+checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
 dependencies = [
  "rustversion",
 ]
 
 [[package]]
 name = "cc"
-version = "1.0.102"
+version = "1.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "779e6b7d17797c0b42023d417228c02889300190e700cb074c3438d9c541d332"
+checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f"
 
 [[package]]
 name = "cfg-if"
@@ -1034,7 +1034,7 @@ dependencies = [
  "num-traits",
  "serde",
  "wasm-bindgen",
- "windows-targets 0.52.5",
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -1071,9 +1071,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.8"
+version = "4.5.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d"
+checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -1081,9 +1081,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.5.8"
+version = "4.5.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708"
+checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942"
 dependencies = [
  "anstream",
  "anstyle",
@@ -1100,7 +1100,7 @@ dependencies = [
  "heck 0.5.0",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -1357,7 +1357,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
 dependencies = [
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -1405,14 +1405,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
 name = "darling"
-version = "0.20.9"
+version = "0.20.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1"
+checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
 dependencies = [
  "darling_core",
  "darling_macro",
@@ -1420,27 +1420,27 @@ dependencies = [
 
 [[package]]
 name = "darling_core"
-version = "0.20.9"
+version = "0.20.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120"
+checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
 dependencies = [
  "fnv",
  "ident_case",
  "proc-macro2",
  "quote",
  "strsim",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
 name = "darling_macro"
-version = "0.20.9"
+version = "0.20.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
+checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
 dependencies = [
  "darling_core",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -1504,7 +1504,7 @@ checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -1524,7 +1524,7 @@ checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -1715,7 +1715,7 @@ dependencies = [
  "enum-ordinalize",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -1727,7 +1727,7 @@ dependencies = [
  "enum-ordinalize",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -1798,9 +1798,9 @@ dependencies = [
 
 [[package]]
 name = "email_address"
-version = "0.2.4"
+version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112"
+checksum = "46b7a0ac6570e31bfe2c6cf575a576a55af9893d1a6b30b4444e6e90b216bb84"
 
 [[package]]
 name = "encode_unicode"
@@ -1834,7 +1834,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -2059,7 +2059,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -2118,9 +2118,9 @@ dependencies = [
 
 [[package]]
 name = "garde"
-version = "0.18.0"
+version = "0.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5fa8fb3ffe035745c6194540b2064b2fe275f32367fbb4eb026024b7921e2e5"
+checksum = "0a3233677ea1554a48235d81bb59d2a41654969a8e29a1316c48105fd1701693"
 dependencies = [
  "compact_str",
  "garde_derive",
@@ -2131,14 +2131,14 @@ dependencies = [
 
 [[package]]
 name = "garde_derive"
-version = "0.18.0"
+version = "0.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cf62650515830c41553b72bd49ec20fb120226f9277c7f2847f071cf998325b"
+checksum = "8796f322e43105351a7ec35148807b32b5b6058a539656dafe4a5b456d5ca41f"
 dependencies = [
  "proc-macro2",
  "quote",
  "regex",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -2389,16 +2389,16 @@ dependencies = [
 
 [[package]]
 name = "html5ever"
-version = "0.26.0"
+version = "0.27.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
+checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4"
 dependencies = [
  "log",
  "mac",
  "markup5ever",
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -2436,9 +2436,9 @@ dependencies = [
 
 [[package]]
 name = "http-body"
-version = "1.0.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
 dependencies = [
  "bytes",
  "http 1.1.0",
@@ -2453,15 +2453,15 @@ dependencies = [
  "bytes",
  "futures-util",
  "http 1.1.0",
- "http-body 1.0.0",
+ "http-body 1.0.1",
  "pin-project-lite",
 ]
 
 [[package]]
 name = "httparse"
-version = "1.8.0"
+version = "1.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
 
 [[package]]
 name = "httpdate"
@@ -2480,9 +2480,9 @@ dependencies = [
 
 [[package]]
 name = "hyper"
-version = "0.14.29"
+version = "0.14.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33"
+checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -2504,15 +2504,15 @@ dependencies = [
 
 [[package]]
 name = "hyper"
-version = "1.3.1"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d"
+checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
 dependencies = [
  "bytes",
  "futures-channel",
  "futures-util",
  "http 1.1.0",
- "http-body 1.0.0",
+ "http-body 1.0.1",
  "httparse",
  "httpdate",
  "itoa",
@@ -2530,7 +2530,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
 dependencies = [
  "futures-util",
  "http 0.2.12",
- "hyper 0.14.29",
+ "hyper 0.14.30",
  "log",
  "rustls 0.21.12",
  "rustls-native-certs",
@@ -2540,16 +2540,16 @@ dependencies = [
 
 [[package]]
 name = "hyper-util"
-version = "0.1.5"
+version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56"
+checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956"
 dependencies = [
  "bytes",
  "futures-channel",
  "futures-util",
  "http 1.1.0",
- "http-body 1.0.0",
- "hyper 1.3.1",
+ "http-body 1.0.1",
+ "hyper 1.4.1",
  "pin-project-lite",
  "socket2",
  "tokio",
@@ -2633,7 +2633,7 @@ checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -2747,7 +2747,7 @@ dependencies = [
  "convert_case",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -2776,7 +2776,7 @@ dependencies = [
  "nom",
  "percent-encoding",
  "quoted_printable",
- "rustls 0.23.10",
+ "rustls 0.23.11",
  "rustls-pemfile 2.1.2",
  "socket2",
  "tokio",
@@ -2875,13 +2875,13 @@ dependencies = [
 
 [[package]]
 name = "markup5ever"
-version = "0.11.0"
+version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
+checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45"
 dependencies = [
  "log",
- "phf 0.10.1",
- "phf_codegen 0.10.0",
+ "phf 0.11.2",
+ "phf_codegen 0.11.2",
  "string_cache",
  "string_cache_codegen",
  "tendril",
@@ -2938,7 +2938,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -3216,7 +3216,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -3327,9 +3327,9 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
 dependencies = [
  "cfg-if",
  "libc",
- "redox_syscall 0.5.2",
+ "redox_syscall 0.5.3",
  "smallvec",
- "windows-targets 0.52.5",
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -3375,9 +3375,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
 name = "pest"
-version = "2.7.10"
+version = "2.7.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8"
+checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95"
 dependencies = [
  "memchr",
  "thiserror",
@@ -3386,9 +3386,9 @@ dependencies = [
 
 [[package]]
 name = "pest_derive"
-version = "2.7.10"
+version = "2.7.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459"
+checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a"
 dependencies = [
  "pest",
  "pest_generator",
@@ -3396,22 +3396,22 @@ dependencies = [
 
 [[package]]
 name = "pest_generator"
-version = "2.7.10"
+version = "2.7.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687"
+checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183"
 dependencies = [
  "pest",
  "pest_meta",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
 name = "pest_meta"
-version = "2.7.10"
+version = "2.7.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd"
+checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f"
 dependencies = [
  "once_cell",
  "pest",
@@ -3487,7 +3487,7 @@ dependencies = [
  "phf_shared 0.11.2",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -3525,7 +3525,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -3700,9 +3700,9 @@ dependencies = [
 
 [[package]]
 name = "quoted_printable"
-version = "0.5.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79ec282e887b434b68c18fe5c121d38e72a5cf35119b59e54ec5b992ea9c8eb0"
+checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73"
 
 [[package]]
 name = "radium"
@@ -3814,9 +3814,9 @@ dependencies = [
 
 [[package]]
 name = "redox_syscall"
-version = "0.5.2"
+version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd"
+checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
 dependencies = [
  "bitflags 2.6.0",
 ]
@@ -3904,7 +3904,7 @@ dependencies = [
  "h2",
  "http 0.2.12",
  "http-body 0.4.6",
- "hyper 0.14.29",
+ "hyper 0.14.30",
  "hyper-rustls",
  "ipnet",
  "js-sys",
@@ -3947,7 +3947,7 @@ dependencies = [
  "h2",
  "http 0.2.12",
  "http-body 0.4.6",
- "hyper 0.14.29",
+ "hyper 0.14.30",
  "hyper-rustls",
  "ipnet",
  "js-sys",
@@ -3986,9 +3986,9 @@ dependencies = [
  "futures-core",
  "futures-util",
  "http 1.1.0",
- "http-body 1.0.0",
+ "http-body 1.0.1",
  "http-body-util",
- "hyper 1.3.1",
+ "hyper 1.4.1",
  "hyper-util",
  "ipnet",
  "js-sys",
@@ -4142,9 +4142,9 @@ dependencies = [
 
 [[package]]
 name = "rust_decimal_macros"
-version = "1.34.2"
+version = "1.35.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e418701588729bef95e7a655f2b483ad64bb97c46e8e79fde83efd92aaab6d82"
+checksum = "a05bf7103af0797dbce0667c471946b29b9eaea34652eff67324f360fec027de"
 dependencies = [
  "quote",
  "rust_decimal",
@@ -4204,15 +4204,15 @@ dependencies = [
 
 [[package]]
 name = "rustls"
-version = "0.23.10"
+version = "0.23.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402"
+checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0"
 dependencies = [
  "log",
  "once_cell",
  "ring",
  "rustls-pki-types",
- "rustls-webpki 0.102.4",
+ "rustls-webpki 0.102.6",
  "subtle",
  "zeroize",
 ]
@@ -4266,9 +4266,9 @@ dependencies = [
 
 [[package]]
 name = "rustls-webpki"
-version = "0.102.4"
+version = "0.102.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e"
+checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
 dependencies = [
  "ring",
  "rustls-pki-types",
@@ -4370,9 +4370,9 @@ dependencies = [
 
 [[package]]
 name = "schematic"
-version = "0.16.4"
+version = "0.16.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2a97356d9a387d6963940ab05de6ac9dbee0c3c97090247954c3d4c2ddc5b8d"
+checksum = "39662af6f427584874872ecc0e42888bccd0c42510d32d519c4ac9319f4c52f3"
 dependencies = [
  "garde",
  "indexmap 2.2.6",
@@ -4392,22 +4392,22 @@ dependencies = [
 
 [[package]]
 name = "schematic_macros"
-version = "0.16.5"
+version = "0.16.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbba5ab97b490c225145d5beba9d565c49049a4c3798227d9ef1dba23f6fcb0f"
+checksum = "9747db30b2549f7a3d5c5c61e00a4da45d6fe7f4a413594ffc5e598f5ced1330"
 dependencies = [
  "convert_case",
  "darling",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
 name = "schematic_types"
-version = "0.8.3"
+version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e23da8bf72241430f0a710266256139667125e51d11283e60147df0015da4758"
+checksum = "6196a0b23e5b4ee02e3894a937606fc49ccc9211e1650a9bef759062cfff3d58"
 dependencies = [
  "chrono",
  "indexmap 2.2.6",
@@ -4422,9 +4422,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "scraper"
-version = "0.19.0"
+version = "0.19.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b80b33679ff7a0ea53d37f3b39de77ea0c75b12c5805ac43ec0c33b3051af1b"
+checksum = "761fb705fdf625482d2ed91d3f0559dcfeab2798fe2771c69560a774865d0802"
 dependencies = [
  "ahash 0.8.11",
  "cssparser",
@@ -4456,7 +4456,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -4513,7 +4513,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "sea-bae",
- "syn 2.0.68",
+ "syn 2.0.72",
  "unicode-ident",
 ]
 
@@ -4536,9 +4536,9 @@ dependencies = [
 
 [[package]]
 name = "sea-query"
-version = "0.31.0-rc.8"
+version = "0.31.0-rc.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f62d5f33113186ad8b0f47c3522e40f9c31cdbc3056a8d7ad402e7498fc29789"
+checksum = "2ad45af4aedefa04c296d623ffb7e409a24d4bfc0b5599b43f5726d88cc7ff2a"
 dependencies = [
  "chrono",
  "educe 0.5.11",
@@ -4573,7 +4573,7 @@ dependencies = [
  "heck 0.4.1",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
  "thiserror",
 ]
 
@@ -4636,9 +4636,9 @@ dependencies = [
 
 [[package]]
 name = "security-framework"
-version = "2.11.0"
+version = "2.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
 dependencies = [
  "bitflags 2.6.0",
  "core-foundation",
@@ -4649,9 +4649,9 @@ dependencies = [
 
 [[package]]
 name = "security-framework-sys"
-version = "2.11.0"
+version = "2.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7"
+checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -4684,9 +4684,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
 
 [[package]]
 name = "serde"
-version = "1.0.202"
+version = "1.0.204"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
+checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
 dependencies = [
  "serde_derive",
 ]
@@ -4715,20 +4715,20 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.202"
+version = "1.0.204"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
+checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.117"
+version = "1.0.120"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
+checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
 dependencies = [
  "indexmap 2.2.6",
  "itoa",
@@ -4778,9 +4778,9 @@ dependencies = [
 
 [[package]]
 name = "serde_with"
-version = "3.8.3"
+version = "3.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e73139bc5ec2d45e6c5fd85be5a46949c1c39a4c18e56915f5eb4c12f975e377"
+checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857"
 dependencies = [
  "base64 0.22.1",
  "chrono",
@@ -4796,14 +4796,14 @@ dependencies = [
 
 [[package]]
 name = "serde_with_macros"
-version = "3.8.3"
+version = "3.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b80d3d6b56b64335c0180e5ffde23b3c5e08c14c585b51a15bd0e95393f46703"
+checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350"
 dependencies = [
  "darling",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -4841,9 +4841,9 @@ dependencies = [
 
 [[package]]
 name = "sha1_smol"
-version = "1.0.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
+checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
 
 [[package]]
 name = "sha2"
@@ -5308,7 +5308,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustversion",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -5351,9 +5351,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.68"
+version = "2.0.72"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
+checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -5369,7 +5369,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -5447,22 +5447,22 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.61"
+version = "1.0.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
+checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.61"
+version = "1.0.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
+checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -5508,9 +5508,9 @@ dependencies = [
 
 [[package]]
 name = "tinyvec"
-version = "1.6.1"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82"
+checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
 dependencies = [
  "tinyvec_macros",
 ]
@@ -5523,9 +5523,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.38.0"
+version = "1.38.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
+checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df"
 dependencies = [
  "backtrace",
  "bytes",
@@ -5548,7 +5548,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -5611,14 +5611,14 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.8.14"
+version = "0.8.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
+checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28"
 dependencies = [
  "serde",
  "serde_spanned",
  "toml_datetime",
- "toml_edit 0.22.14",
+ "toml_edit 0.22.16",
 ]
 
 [[package]]
@@ -5643,15 +5643,15 @@ dependencies = [
 
 [[package]]
 name = "toml_edit"
-version = "0.22.14"
+version = "0.22.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
+checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788"
 dependencies = [
  "indexmap 2.2.6",
  "serde",
  "serde_spanned",
  "toml_datetime",
- "winnow 0.6.13",
+ "winnow 0.6.15",
 ]
 
 [[package]]
@@ -5681,7 +5681,7 @@ dependencies = [
  "bytes",
  "futures-util",
  "http 1.1.0",
- "http-body 1.0.0",
+ "http-body 1.0.1",
  "http-body-util",
  "pin-project-lite",
  "tower-layer",
@@ -5721,7 +5721,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -5814,9 +5814,9 @@ checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
 
 [[package]]
 name = "ulid"
-version = "1.1.2"
+version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34778c17965aa2a08913b57e1f34db9b4a63f5de31768b55bf20d2795f921259"
+checksum = "04f903f293d11f31c0c29e4148f6dc0d033a7f80cebc0282bea147611667d289"
 dependencies = [
  "getrandom",
  "rand 0.8.5",
@@ -6016,7 +6016,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
  "wasm-bindgen-shared",
 ]
 
@@ -6050,7 +6050,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -6147,7 +6147,7 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
 dependencies = [
- "windows-targets 0.52.5",
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -6165,7 +6165,7 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
- "windows-targets 0.52.5",
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -6185,18 +6185,18 @@ dependencies = [
 
 [[package]]
 name = "windows-targets"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
 dependencies = [
- "windows_aarch64_gnullvm 0.52.5",
- "windows_aarch64_msvc 0.52.5",
- "windows_i686_gnu 0.52.5",
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
  "windows_i686_gnullvm",
- "windows_i686_msvc 0.52.5",
- "windows_x86_64_gnu 0.52.5",
- "windows_x86_64_gnullvm 0.52.5",
- "windows_x86_64_msvc 0.52.5",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
 ]
 
 [[package]]
@@ -6207,9 +6207,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
 
 [[package]]
 name = "windows_aarch64_msvc"
@@ -6219,9 +6219,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
 
 [[package]]
 name = "windows_i686_gnu"
@@ -6231,15 +6231,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
 
 [[package]]
 name = "windows_i686_gnullvm"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
 
 [[package]]
 name = "windows_i686_msvc"
@@ -6249,9 +6249,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
 
 [[package]]
 name = "windows_x86_64_gnu"
@@ -6261,9 +6261,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
@@ -6273,9 +6273,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
 
 [[package]]
 name = "windows_x86_64_msvc"
@@ -6285,9 +6285,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
 name = "winnow"
@@ -6300,9 +6300,9 @@ dependencies = [
 
 [[package]]
 name = "winnow"
-version = "0.6.13"
+version = "0.6.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
+checksum = "557404e450152cd6795bb558bca69e43c585055f4606e3bcae5894fc6dac9ba0"
 dependencies = [
  "memchr",
 ]
@@ -6350,11 +6350,11 @@ checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
 
 [[package]]
 name = "zerocopy"
-version = "0.7.34"
+version = "0.7.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
 dependencies = [
- "zerocopy-derive 0.7.34",
+ "zerocopy-derive 0.7.35",
 ]
 
 [[package]]
@@ -6368,13 +6368,13 @@ dependencies = [
 
 [[package]]
 name = "zerocopy-derive"
-version = "0.7.34"
+version = "0.7.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -6385,7 +6385,7 @@ checksum = "201585ea96d37ee69f2ac769925ca57160cef31acb137c16f38b02b76f4c1e62"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.72",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index f23d0dd5e9..e4f8f828e4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,7 @@ resolver = "2"
 
 [workspace.dependencies]
 anyhow = "=1.0.82"
-async-graphql = { version = "=7.0.6", features = [
+async-graphql = { version = "=7.0.7", features = [
     "chrono",
     "decimal",
     "log",
@@ -13,7 +13,7 @@ async-graphql = { version = "=7.0.6", features = [
 chrono = "=0.4.38"
 dotenvy_macro = "=0.15.7"
 nanoid = "=0.4.0"
-schematic = { version = "=0.16.4", features = [
+schematic = { version = "=0.16.6", features = [
     "config",
     "json",
     "schema",
@@ -37,7 +37,7 @@ sea-orm = { version = "=1.0.0-rc.5", features = [
     "with-uuid",
 ], default-features = false }
 sea-orm-migration = "=1.0.0-rc.5"
-serde = { version = "=1.0.202", features = ["derive"] }
-serde_json = "=1.0.117"
+serde = { version = "=1.0.204", features = ["derive"] }
+serde_json = "=1.0.120"
 strum = { version = "=0.26.2", features = ["derive"] }
 tracing = { version = "=0.1.40", features = ["attributes"] }
diff --git a/apps/backend/Cargo.toml b/apps/backend/Cargo.toml
index 5e62084d9b..81fc6cdde2 100644
--- a/apps/backend/Cargo.toml
+++ b/apps/backend/Cargo.toml
@@ -10,12 +10,12 @@ anyhow = { workspace = true }
 apalis = { version = "=0.5.3", features = ["cron", "limit"] }
 argon2 = "=0.6.0-pre.0"
 async-graphql = { workspace = true }
-async-graphql-axum = "=7.0.6"
+async-graphql-axum = "=7.0.7"
 async-trait = "=0.1.81"
-aws-sdk-s3 = { version = "=1.40.0", features = ["behavior-version-latest"] }
+aws-sdk-s3 = { version = "=1.42.0", features = ["behavior-version-latest"] }
 axum = { version = "=0.7.5", features = ["macros", "multipart"] }
 boilermates = "=0.3.0"
-cached = { version = "=0.52.0", features = ["disk_store"] }
+cached = { version = "=0.53.1", features = ["disk_store"] }
 chrono = { workspace = true }
 chrono-tz = "=0.9.0"
 compile-time = "=0.2.0"
@@ -63,21 +63,21 @@ reqwest = { git = "https://github.com/thomasqueirozb/reqwest", branch = "base_ur
 ], default-features = false }
 rs-utils = { path = "../../libs/rs-utils" }
 rust_decimal = "=1.35.0"
-rust_decimal_macros = "=1.34.2"
+rust_decimal_macros = "=1.35.0"
 rust_iso3166 = "=0.1.13"
 schematic = { workspace = true }
-scraper = "=0.19.0"
+scraper = "=0.19.1"
 sea-orm = { workspace = true }
 sea-orm-migration = { workspace = true }
-sea-query = "=0.31.0-rc.8"
+sea-query = "=0.31.0-rc.9"
 serde = { workspace = true }
 serde_json = { workspace = true }
-serde_with = { version = "=3.8.3", features = ["chrono_0_4"] }
+serde_with = { version = "=3.9.0", features = ["chrono_0_4"] }
 serde-xml-rs = "=0.6.0"
 slug = "=0.1.5"
 strum = { workspace = true }
 struson = { version = "=0.5.0", features = ["serde"] }
-tokio = { version = "=1.38.0", features = ["full"] }
+tokio = { version = "=1.38.1", features = ["full"] }
 tokio-util = { version = "=0.7.11", features = ["codec"] }
 tower = { version = "=0.4.13", features = ["buffer"] }
 tower-http = { version = "=0.5.2", features = ["catch-panic", "cors", "trace"] }
diff --git a/apps/backend/src/background.rs b/apps/backend/src/background.rs
index 0fd131756e..3fe3618483 100644
--- a/apps/backend/src/background.rs
+++ b/apps/backend/src/background.rs
@@ -11,7 +11,7 @@ use crate::{
     exporter::ExporterService,
     fitness::resolver::ExerciseService,
     importer::{DeployImportJobInput, ImporterService},
-    miscellaneous::resolver::MiscellaneousService,
+    miscellaneous::MiscellaneousService,
     models::{
         fitness::Exercise,
         media::{CommitMediaInput, ProgressUpdateInput, ReviewPostedEvent},
diff --git a/apps/backend/src/entities/collection.rs b/apps/backend/src/entities/collection.rs
index dc8a1c4e19..f13276014a 100644
--- a/apps/backend/src/entities/collection.rs
+++ b/apps/backend/src/entities/collection.rs
@@ -7,7 +7,7 @@ use sea_orm::entity::prelude::*;
 use sea_orm::ActiveValue;
 use serde::{Deserialize, Serialize};
 
-use crate::miscellaneous::CollectionExtraInformation;
+use crate::models::CollectionExtraInformation;
 
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize, SimpleObject)]
 #[sea_orm(table_name = "collection")]
diff --git a/apps/backend/src/entities/exercise.rs b/apps/backend/src/entities/exercise.rs
index cda898b8f2..36f15ac681 100644
--- a/apps/backend/src/entities/exercise.rs
+++ b/apps/backend/src/entities/exercise.rs
@@ -8,12 +8,12 @@ use database::{
     ExerciseEquipment, ExerciseForce, ExerciseLevel, ExerciseLot, ExerciseMechanic, ExerciseMuscle,
     ExerciseSource,
 };
-use sea_orm::{entity::prelude::*, FromQueryResult};
+use sea_orm::entity::prelude::*;
 use serde::{Deserialize, Serialize};
 
 use crate::{
     file_storage::FileStorageService, models::fitness::ExerciseAttributes,
-    traits::GraphqlRepresentation, utils::get_stored_asset,
+    traits::GraphqlRepresentation,
 };
 
 #[derive(
@@ -58,43 +58,13 @@ impl GraphqlRepresentation for Model {
         let mut converted_exercise = self.clone();
         let mut images = vec![];
         for image in self.attributes.internal_images.iter() {
-            images.push(get_stored_asset(image.clone(), file_storage_service).await);
+            images.push(file_storage_service.get_stored_asset(image.clone()).await);
         }
         converted_exercise.attributes.images = images;
         Ok(converted_exercise)
     }
 }
 
-#[derive(Clone, Debug, Deserialize, SimpleObject, FromQueryResult)]
-pub struct ExerciseListItem {
-    pub lot: ExerciseLot,
-    pub id: String,
-    #[graphql(skip)]
-    pub attributes: ExerciseAttributes,
-    pub num_times_interacted: Option<i32>,
-    pub last_updated_on: Option<DateTimeUtc>,
-    pub muscle: Option<ExerciseMuscle>,
-    pub image: Option<String>,
-    #[graphql(skip)]
-    pub muscles: Vec<ExerciseMuscle>,
-}
-
-#[async_trait]
-impl GraphqlRepresentation for ExerciseListItem {
-    async fn graphql_representation(
-        self,
-        file_storage_service: &Arc<FileStorageService>,
-    ) -> Result<Self> {
-        let mut converted_exercise = self.clone();
-        if let Some(img) = self.attributes.internal_images.first() {
-            converted_exercise.image =
-                Some(get_stored_asset(img.clone(), file_storage_service).await);
-        }
-        converted_exercise.muscle = self.muscles.first().cloned();
-        Ok(converted_exercise)
-    }
-}
-
 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
 pub enum Relation {
     #[sea_orm(has_many = "super::collection_to_entity::Entity")]
diff --git a/apps/backend/src/exporter.rs b/apps/backend/src/exporter.rs
index 413f6213b8..897e7a1a19 100644
--- a/apps/backend/src/exporter.rs
+++ b/apps/backend/src/exporter.rs
@@ -17,8 +17,8 @@ use tokio_util::codec::{BytesCodec, FramedRead};
 
 use crate::{
     background::ApplicationJob, file_storage::FileStorageService,
-    fitness::resolver::ExerciseService, miscellaneous::resolver::MiscellaneousService,
-    models::ExportItem, traits::AuthProvider, utils::TEMP_DIR,
+    fitness::resolver::ExerciseService, miscellaneous::MiscellaneousService, models::ExportItem,
+    traits::AuthProvider, utils::TEMP_DIR,
 };
 
 #[derive(Debug, Serialize, Deserialize, SimpleObject, Clone)]
diff --git a/apps/backend/src/file_storage.rs b/apps/backend/src/file_storage.rs
index 7a56ee8c7f..eb5271de4e 100644
--- a/apps/backend/src/file_storage.rs
+++ b/apps/backend/src/file_storage.rs
@@ -4,6 +4,8 @@ use aws_sdk_s3::presigning::PresigningConfig;
 use chrono::Duration;
 use nanoid::nanoid;
 
+use crate::models::StoredUrl;
+
 #[derive(Debug)]
 pub struct FileStorageService {
     s3_client: aws_sdk_s3::Client,
@@ -104,4 +106,11 @@ impl FileStorageService {
             .metadata
             .unwrap()
     }
+
+    pub async fn get_stored_asset(&self, url: StoredUrl) -> String {
+        match url {
+            StoredUrl::Url(u) => u,
+            StoredUrl::S3(u) => self.get_presigned_url(u).await,
+        }
+    }
 }
diff --git a/apps/backend/src/fitness/resolver.rs b/apps/backend/src/fitness/resolver.rs
index 1d70f7a74d..c664aa8322 100644
--- a/apps/backend/src/fitness/resolver.rs
+++ b/apps/backend/src/fitness/resolver.rs
@@ -20,20 +20,19 @@ use struson::writer::{JsonStreamWriter, JsonWriter};
 use crate::{
     background::ApplicationJob,
     entities::{
-        collection, collection_to_entity,
-        exercise::{self, ExerciseListItem},
+        collection, collection_to_entity, exercise,
         prelude::{CollectionToEntity, Exercise, UserMeasurement, UserToEntity, Workout},
         user_measurement, user_to_entity, workout,
     },
     file_storage::FileStorageService,
-    miscellaneous::DefaultCollection,
     models::{
         fitness::{
-            Exercise as GithubExercise, ExerciseAttributes, ExerciseCategory,
+            Exercise as GithubExercise, ExerciseAttributes, ExerciseCategory, ExerciseListItem,
             GithubExerciseAttributes, UserExerciseInput, UserToExerciseHistoryExtraInformation,
-            UserWorkoutInput, UserWorkoutSetRecord, WorkoutListItem,
+            UserWorkoutInput, UserWorkoutSetRecord,
         },
-        ChangeCollectionToEntityInput, SearchDetails, SearchInput, SearchResults, StoredUrl,
+        ChangeCollectionToEntityInput, DefaultCollection, SearchDetails, SearchInput,
+        SearchResults, StoredUrl,
     },
     traits::{AuthProvider, GraphqlRepresentation},
     utils::{add_entity_to_collection, entity_in_collections, ilike_sql, user_by_id},
@@ -145,7 +144,7 @@ impl ExerciseQuery {
         &self,
         gql_ctx: &Context<'_>,
         input: SearchInput,
-    ) -> Result<SearchResults<WorkoutListItem>> {
+    ) -> Result<SearchResults<workout::Model>> {
         let service = gql_ctx.data_unchecked::<Arc<ExerciseService>>();
         let user_id = self.user_id_from_ctx(gql_ctx).await?;
         service.user_workouts_list(user_id, input).await
@@ -407,7 +406,7 @@ impl ExerciseService {
         &self,
         user_id: String,
         input: SearchInput,
-    ) -> Result<SearchResults<WorkoutListItem>> {
+    ) -> Result<SearchResults<workout::Model>> {
         let page = input.page.unwrap_or(1);
         let query = Workout::find()
             .filter(workout::Column::UserId.eq(user_id))
@@ -417,9 +416,7 @@ impl ExerciseService {
             .order_by_desc(workout::Column::EndTime);
         let total = query.clone().count(&self.db).await?;
         let total: i32 = total.try_into().unwrap();
-        let data = query
-            .into_partial_model::<WorkoutListItem>()
-            .paginate(&self.db, self.config.frontend.page_size.try_into().unwrap());
+        let data = query.paginate(&self.db, self.config.frontend.page_size.try_into().unwrap());
         let items = data.fetch_page((page - 1).try_into().unwrap()).await?;
         let next_page = if total - (page * self.config.frontend.page_size) > 0 {
             Some(page + 1)
diff --git a/apps/backend/src/graphql.rs b/apps/backend/src/graphql.rs
index 4e4e83e11b..35fdc0a089 100644
--- a/apps/backend/src/graphql.rs
+++ b/apps/backend/src/graphql.rs
@@ -4,7 +4,7 @@ use crate::{
     exporter::{ExporterMutation, ExporterQuery},
     fitness::resolver::{ExerciseMutation, ExerciseQuery},
     importer::{ImporterMutation, ImporterQuery},
-    miscellaneous::resolver::{MiscellaneousMutation, MiscellaneousQuery},
+    miscellaneous::{MiscellaneousMutation, MiscellaneousQuery},
     utils::AppServices,
 };
 
diff --git a/apps/backend/src/importer/audiobookshelf.rs b/apps/backend/src/importer/audiobookshelf.rs
index 6e3fdb1218..b21112e066 100644
--- a/apps/backend/src/importer/audiobookshelf.rs
+++ b/apps/backend/src/importer/audiobookshelf.rs
@@ -13,8 +13,10 @@ use serde_json::json;
 use crate::{
     entities::metadata,
     importer::{ImportFailStep, ImportFailedItem, ImportResult},
-    miscellaneous::{audiobookshelf_models, itunes_podcast_episode_by_name},
-    models::media::{CommitMediaInput, ImportOrExportMediaItem, ImportOrExportMediaItemSeen},
+    models::{
+        audiobookshelf_models,
+        media::{CommitMediaInput, ImportOrExportMediaItem, ImportOrExportMediaItemSeen},
+    },
     providers::google_books::GoogleBooksService,
     utils::get_base_http_client,
 };
@@ -115,8 +117,9 @@ where
                                         ..Default::default()
                                     })
                                     .await?;
-                                    if let Some(pe) =
-                                        itunes_podcast_episode_by_name(&episode.title, podcast)
+                                    if let Some(pe) = podcast
+                                        .podcast_specifics
+                                        .and_then(|p| p.episode_by_name(&episode.title))
                                     {
                                         to_return.push(pe);
                                     }
diff --git a/apps/backend/src/importer/igdb.rs b/apps/backend/src/importer/igdb.rs
index 1a488f9bf1..c19eefae7d 100644
--- a/apps/backend/src/importer/igdb.rs
+++ b/apps/backend/src/importer/igdb.rs
@@ -5,9 +5,9 @@ use itertools::Itertools;
 use rust_decimal_macros::dec;
 use serde::Deserialize;
 
-use crate::{
-    miscellaneous::DefaultCollection,
-    models::media::{ImportOrExportMediaItem, ImportOrExportMediaItemSeen},
+use crate::models::{
+    media::{ImportOrExportMediaItem, ImportOrExportMediaItemSeen},
+    DefaultCollection,
 };
 
 use super::{DeployIgdbImportInput, ImportFailStep, ImportFailedItem, ImportResult};
diff --git a/apps/backend/src/importer/imdb.rs b/apps/backend/src/importer/imdb.rs
index 7cc2d530ff..cf6bcbfb53 100644
--- a/apps/backend/src/importer/imdb.rs
+++ b/apps/backend/src/importer/imdb.rs
@@ -6,8 +6,7 @@ use serde::Deserialize;
 
 use crate::{
     importer::{DeployGenericCsvImportInput, ImportFailStep, ImportFailedItem, ImportResult},
-    miscellaneous::DefaultCollection,
-    models::media::ImportOrExportMediaItem,
+    models::{media::ImportOrExportMediaItem, DefaultCollection},
     providers::tmdb::NonMediaTmdbService,
 };
 
diff --git a/apps/backend/src/importer/mod.rs b/apps/backend/src/importer/mod.rs
index 180a9dac28..fdee65099e 100644
--- a/apps/backend/src/importer/mod.rs
+++ b/apps/backend/src/importer/mod.rs
@@ -16,7 +16,7 @@ use crate::{
     background::ApplicationJob,
     entities::{import_report, prelude::ImportReport, user_measurement},
     fitness::resolver::ExerciseService,
-    miscellaneous::resolver::MiscellaneousService,
+    miscellaneous::MiscellaneousService,
     models::{
         fitness::UserWorkoutInput,
         media::{
diff --git a/apps/backend/src/importer/movary.rs b/apps/backend/src/importer/movary.rs
index b8c0df0f44..a160904b8b 100644
--- a/apps/backend/src/importer/movary.rs
+++ b/apps/backend/src/importer/movary.rs
@@ -12,9 +12,9 @@ use crate::{
         DeployMovaryImportInput, ImportFailStep, ImportFailedItem, ImportOrExportMediaItem,
         ImportResult,
     },
-    miscellaneous::DefaultCollection,
-    models::media::{
-        ImportOrExportItemRating, ImportOrExportItemReview, ImportOrExportMediaItemSeen,
+    models::{
+        media::{ImportOrExportItemRating, ImportOrExportItemReview, ImportOrExportMediaItemSeen},
+        DefaultCollection,
     },
 };
 
diff --git a/apps/backend/src/integrations.rs b/apps/backend/src/integrations.rs
index 5c86147ced..d2ae0a2cb2 100644
--- a/apps/backend/src/integrations.rs
+++ b/apps/backend/src/integrations.rs
@@ -13,8 +13,7 @@ use serde::{Deserialize, Serialize};
 
 use crate::{
     entities::{metadata, prelude::Metadata},
-    miscellaneous::{audiobookshelf_models, itunes_podcast_episode_by_name},
-    models::media::CommitMediaInput,
+    models::{audiobookshelf_models, media::CommitMediaInput},
     providers::google_books::GoogleBooksService,
     utils::{get_base_http_client, ilike_sql},
 };
@@ -339,7 +338,10 @@ impl IntegrationService {
                             })
                             .await
                             .unwrap();
-                            match itunes_podcast_episode_by_name(&pe.title, podcast) {
+                            match podcast
+                                .podcast_specifics
+                                .and_then(|p| p.episode_by_name(&pe.title))
+                            {
                                 Some(episode) => (
                                     format!("{}/{}", item.id, pe.id),
                                     itunes_id,
diff --git a/apps/backend/src/miscellaneous/resolver.rs b/apps/backend/src/miscellaneous.rs
similarity index 98%
rename from apps/backend/src/miscellaneous/resolver.rs
rename to apps/backend/src/miscellaneous.rs
index bdb4f60a3a..91d4fb8d5b 100644
--- a/apps/backend/src/miscellaneous/resolver.rs
+++ b/apps/backend/src/miscellaneous.rs
@@ -70,7 +70,6 @@ use crate::{
     fitness::resolver::ExerciseService,
     integrations::{IntegrationMediaSeen, IntegrationService},
     jwt,
-    miscellaneous::{CustomService, DefaultCollection},
     models::{
         fitness::UserUnitSystem,
         media::{
@@ -89,9 +88,9 @@ use crate::{
             SeenMangaExtraInformation, SeenPodcastExtraInformation, SeenShowExtraInformation,
             ShowSpecifics, VideoGameSpecifics, VisualNovelSpecifics, WatchProvider,
         },
-        BackendError, BackgroundJob, ChangeCollectionToEntityInput, EntityLot, IdAndNamedObject,
-        MediaStateChanged, SearchDetails, SearchInput, SearchResults, StoredUrl, StringIdObject,
-        UserSummaryData,
+        BackendError, BackgroundJob, ChangeCollectionToEntityInput, CollectionExtraInformation,
+        DefaultCollection, EntityLot, IdAndNamedObject, MediaStateChanged, SearchDetails,
+        SearchInput, SearchResults, StoredUrl, StringIdObject, UserSummaryData,
     },
     providers::{
         anilist::{
@@ -118,13 +117,11 @@ use crate::{
     },
     utils::{
         add_entity_to_collection, associate_user_with_entity, entity_in_collections,
-        get_current_date, get_stored_asset, get_user_to_entity_association, ilike_sql, user_by_id,
+        get_current_date, get_user_to_entity_association, ilike_sql, user_by_id,
         user_id_from_token, AUTHOR, SHOW_SPECIAL_SEASON_NAMES, TEMP_DIR, VERSION,
     },
 };
 
-use super::CollectionExtraInformation;
-
 type Provider = Box<(dyn MediaProvider + Send + Sync)>;
 
 #[derive(Debug, Serialize, Deserialize, InputObject, Clone)]
@@ -643,11 +640,12 @@ struct UserMetadataDetails {
     has_interacted: bool,
 }
 
-#[derive(SimpleObject, Debug, Clone)]
+#[derive(SimpleObject, Debug, Clone, Default)]
 struct UserMediaNextEntry {
     season: Option<i32>,
-    episode: Option<i32>,
+    volume: Option<i32>,
     chapter: Option<i32>,
+    episode: Option<i32>,
 }
 
 #[derive(Debug, Serialize, Deserialize, InputObject, Clone)]
@@ -777,6 +775,19 @@ fn empty_nonce_verifier(_nonce: Option<&Nonce>) -> Result<(), String> {
     Ok(())
 }
 
+#[derive(Debug, Clone)]
+struct CustomService {}
+
+impl MediaProviderLanguages for CustomService {
+    fn supported_languages() -> Vec<String> {
+        ["us"].into_iter().map(String::from).collect()
+    }
+
+    fn default_language() -> String {
+        "us".to_owned()
+    }
+}
+
 #[derive(Default)]
 pub struct MiscellaneousQuery;
 
@@ -1528,7 +1539,10 @@ impl MiscellaneousService {
         let mut videos = vec![];
         if let Some(vids) = &meta.videos {
             for v in vids.clone() {
-                let url = get_stored_asset(v.identifier, &self.file_storage_service).await;
+                let url = self
+                    .file_storage_service
+                    .get_stored_asset(v.identifier)
+                    .await;
                 videos.push(GraphqlVideoAsset {
                     source: v.source,
                     video_id: url,
@@ -1807,76 +1821,70 @@ impl MiscellaneousService {
         let reviews = self
             .item_reviews(&user_id, Some(metadata_id.clone()), None, None, None)
             .await?;
-        let (is_finished, history) = self
+        let (_, history) = self
             .is_metadata_finished_by_user(&user_id, &media_details)
             .await?;
         let in_progress = history
             .iter()
             .find(|h| h.state == SeenState::InProgress || h.state == SeenState::OnAHold)
             .cloned();
-        let next_entry = if is_finished {
-            None
-        } else {
-            history.first().and_then(|h| {
-                if let Some(s) = &media_details.model.show_specifics {
-                    let all_episodes = s
-                        .seasons
-                        .iter()
-                        .map(|s| (s.season_number, &s.episodes))
-                        .collect_vec()
-                        .into_iter()
-                        .flat_map(|(s, e)| {
-                            e.iter().map(move |e| UserMediaNextEntry {
-                                season: Some(s),
-                                episode: Some(e.episode_number),
-                                chapter: None,
-                            })
-                        })
-                        .collect_vec();
-                    let next = all_episodes.iter().position(|e| {
-                        e.season == Some(h.show_extra_information.as_ref().unwrap().season)
-                            && e.episode == Some(h.show_extra_information.as_ref().unwrap().episode)
-                    });
-                    Some(all_episodes.get(next? + 1)?.clone())
-                } else if let Some(p) = &media_details.model.podcast_specifics {
-                    let all_episodes = p
-                        .episodes
-                        .iter()
-                        .map(|e| UserMediaNextEntry {
-                            season: None,
-                            episode: Some(e.number),
-                            chapter: None,
-                        })
-                        .collect_vec();
-                    let next = all_episodes.iter().position(|e| {
-                        e.episode == Some(h.podcast_extra_information.as_ref().unwrap().episode)
-                    });
-                    Some(all_episodes.get(next? + 1)?.clone())
-                } else if let Some(anime_spec) = &media_details.model.anime_specifics {
-                    anime_spec.episodes.and_then(|_| {
-                        h.anime_extra_information.as_ref().and_then(|hist| {
-                            hist.episode.map(|e| UserMediaNextEntry {
-                                season: None,
-                                episode: Some(e + 1),
-                                chapter: None,
-                            })
+        let next_entry = history.first().and_then(|h| {
+            if let Some(s) = &media_details.model.show_specifics {
+                let all_episodes = s
+                    .seasons
+                    .iter()
+                    .map(|s| (s.season_number, &s.episodes))
+                    .collect_vec()
+                    .into_iter()
+                    .flat_map(|(s, e)| {
+                        e.iter().map(move |e| UserMediaNextEntry {
+                            season: Some(s),
+                            episode: Some(e.episode_number),
+                            ..Default::default()
                         })
                     })
-                } else if let Some(manga_spec) = &media_details.model.manga_specifics {
-                    manga_spec.chapters.and_then(|_| {
-                        h.manga_extra_information.as_ref().and_then(|hist| {
-                            hist.chapter.map(|e| UserMediaNextEntry {
-                                season: None,
-                                episode: None,
-                                chapter: Some(e + 1),
-                            })
-                        })
+                    .collect_vec();
+                let next = all_episodes.iter().position(|e| {
+                    e.season == Some(h.show_extra_information.as_ref().unwrap().season)
+                        && e.episode == Some(h.show_extra_information.as_ref().unwrap().episode)
+                });
+                Some(all_episodes.get(next? + 1)?.clone())
+            } else if let Some(p) = &media_details.model.podcast_specifics {
+                let all_episodes = p
+                    .episodes
+                    .iter()
+                    .map(|e| UserMediaNextEntry {
+                        episode: Some(e.number),
+                        ..Default::default()
                     })
-                } else {
-                    None
-                }
-            })
-        };
+                    .collect_vec();
+                let next = all_episodes.iter().position(|e| {
+                    e.episode == Some(h.podcast_extra_information.as_ref().unwrap().episode)
+                });
+                Some(all_episodes.get(next? + 1)?.clone())
+            } else if let Some(_anime_spec) = &media_details.model.anime_specifics {
+                h.anime_extra_information.as_ref().and_then(|hist| {
+                    hist.episode.map(|e| UserMediaNextEntry {
+                        episode: Some(e + 1),
+                        ..Default::default()
+                    })
+                })
+            } else if let Some(_manga_spec) = &media_details.model.manga_specifics {
+                h.manga_extra_information.as_ref().and_then(|hist| {
+                    hist.chapter
+                        .map(|e| UserMediaNextEntry {
+                            chapter: Some(e + 1),
+                            ..Default::default()
+                        })
+                        .or(hist.volume.map(|e| UserMediaNextEntry {
+                            volume: Some(e + 1),
+                            ..Default::default()
+                        }))
+                })
+            } else {
+                None
+            }
+        });
         let metadata_alias = Alias::new("m");
         let seen_alias = Alias::new("s");
         let seen_select = Query::select()
@@ -2129,7 +2137,7 @@ impl MiscellaneousService {
                 calc.show_extra_information = Some(s);
             } else if let Some(p) = evt.metadata_podcast_extra_information {
                 if let Some(po) = evt.m_podcast_specifics {
-                    if let Some(ep) = po.get_episode(p.episode) {
+                    if let Some(ep) = po.episode_by_number(p.episode) {
                         image = ep.thumbnail.clone();
                         title = Some(ep.title.clone());
                     }
@@ -2406,16 +2414,18 @@ impl MiscellaneousService {
             ProgressUpdateAction::Update => {
                 let prev_seen = all_prev_seen[0].clone();
                 let progress = input.progress.unwrap();
-                if prev_seen.progress == progress {
+                let watched_on = prev_seen.provider_watched_on.clone();
+                if prev_seen.progress == progress && watched_on == input.provider_watched_on {
                     return Ok(ProgressUpdateResultUnion::Error(ProgressUpdateError {
                         error: ProgressUpdateErrorVariant::UpdateWithoutProgressUpdate,
                     }));
                 }
-                let watched_on = prev_seen.provider_watched_on.clone();
                 let mut updated_at = prev_seen.updated_at.clone();
                 let now = Utc::now();
-                updated_at.push(now);
-                let mut last_seen: seen::ActiveModel = prev_seen.clone().into();
+                if prev_seen.progress != progress {
+                    updated_at.push(now);
+                }
+                let mut last_seen: seen::ActiveModel = prev_seen.into();
                 last_seen.state = ActiveValue::Set(SeenState::InProgress);
                 last_seen.progress = ActiveValue::Set(progress);
                 last_seen.updated_at = ActiveValue::Set(updated_at);
@@ -4782,7 +4792,7 @@ impl MiscellaneousService {
             } else if let Some(item) = meta.podcast_specifics {
                 ls.unique_items.podcasts.insert(meta.id.clone());
                 if let Some(s) = seen.podcast_extra_information.to_owned() {
-                    if let Some(episode) = item.get_episode(s.episode) {
+                    if let Some(episode) = item.episode_by_number(s.episode) {
                         if let Some(r) = episode.runtime {
                             ls.media.podcasts.runtime += r;
                             units_consumed = Some(r);
@@ -6422,7 +6432,11 @@ impl MiscellaneousService {
             .unwrap();
         let mut images = vec![];
         for image in group.images.iter() {
-            images.push(get_stored_asset(image.url.clone(), &self.file_storage_service).await);
+            images.push(
+                self.file_storage_service
+                    .get_stored_asset(image.url.clone())
+                    .await,
+            );
         }
         group.display_images = images;
         let slug = slug::slugify(&group.title);
@@ -6773,7 +6787,7 @@ impl MiscellaneousService {
                     }
                 } else if let Some(podcast) = cal_event.metadata_podcast_extra_information {
                     if let Some(podcast_info) = &meta.podcast_specifics {
-                        if let Some(ep) = podcast_info.get_episode(podcast.episode) {
+                        if let Some(ep) = podcast_info.episode_by_number(podcast.episode) {
                             if ep.publish_date == cal_event.date {
                                 need_to_delete = false;
                             }
diff --git a/apps/backend/src/miscellaneous/mod.rs b/apps/backend/src/miscellaneous/mod.rs
deleted file mode 100644
index 9f61b80b65..0000000000
--- a/apps/backend/src/miscellaneous/mod.rs
+++ /dev/null
@@ -1,180 +0,0 @@
-use anyhow::Result;
-use async_graphql::{Enum, InputObject, SimpleObject};
-use enum_meta::{meta, Meta};
-use rust_decimal::Decimal;
-use sea_orm::FromJsonQueryResult;
-use serde::{Deserialize, Serialize};
-use strum::{Display, EnumIter};
-
-use crate::{entities::metadata, traits::MediaProviderLanguages};
-
-pub mod resolver;
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Enum)]
-pub enum CollectionExtraInformationLot {
-    String,
-    Number,
-    Date,
-    DateTime,
-    StringArray,
-}
-
-#[derive(
-    Debug,
-    PartialEq,
-    Eq,
-    Clone,
-    Serialize,
-    Deserialize,
-    SimpleObject,
-    FromJsonQueryResult,
-    InputObject,
-)]
-#[graphql(input_name = "CollectionExtraInformationInput")]
-pub struct CollectionExtraInformation {
-    pub name: String,
-    pub description: String,
-    pub lot: CollectionExtraInformationLot,
-    pub default_value: Option<String>,
-    pub required: Option<bool>,
-}
-
-#[derive(Display, EnumIter)]
-pub enum DefaultCollection {
-    Watchlist,
-    #[strum(serialize = "In Progress")]
-    InProgress,
-    Completed,
-    Monitoring,
-    Custom,
-    Owned,
-    Reminders,
-}
-
-meta! {
-    DefaultCollection, (Option<Vec<CollectionExtraInformation>>, &'static str);
-    Watchlist, (None, "Things I want to watch in the future.");
-    InProgress, (None, "Media items that I am currently watching.");
-    Completed, (None, "Media items that I have completed.");
-    Custom, (None, "Items that I have created manually.");
-    Monitoring, (None, "Items that I am keeping an eye on.");
-    Owned, (Some(
-        vec![
-            CollectionExtraInformation {
-                name: "Owned on".to_string(),
-                description: "When did you get this media?".to_string(),
-                lot: CollectionExtraInformationLot::Date,
-                default_value: None,
-                required: None,
-            }
-        ]
-    ), "Items that I have in my inventory.");
-    Reminders, (Some(
-        vec![
-            CollectionExtraInformation {
-                name: "Reminder".to_string(),
-                description: "When do you want to be reminded?".to_string(),
-                lot: CollectionExtraInformationLot::Date,
-                default_value: None,
-                required: Some(true),
-            },
-            CollectionExtraInformation {
-                name: "Text".to_string(),
-                description: "What do you want to be reminded about?".to_string(),
-                lot: CollectionExtraInformationLot::String,
-                default_value: None,
-                required: Some(true),
-            }
-        ]
-    ), "Items that I want to be reminded about.");
-}
-
-#[derive(Debug, Clone)]
-pub struct CustomService {}
-
-impl MediaProviderLanguages for CustomService {
-    fn supported_languages() -> Vec<String> {
-        ["us"].into_iter().map(String::from).collect()
-    }
-
-    fn default_language() -> String {
-        "us".to_owned()
-    }
-}
-
-pub mod audiobookshelf_models {
-    use super::*;
-
-    #[derive(Debug, Serialize, Deserialize, Clone, Display)]
-    #[serde(rename_all = "snake_case")]
-    pub enum MediaType {
-        Book,
-        Podcast,
-    }
-    #[derive(Debug, Serialize, Deserialize)]
-    #[serde(rename_all = "camelCase")]
-    pub struct ItemProgress {
-        pub progress: Decimal,
-        pub is_finished: bool,
-        pub ebook_progress: Option<Decimal>,
-    }
-    #[derive(Debug, Serialize, Deserialize, Clone)]
-    #[serde(rename_all = "camelCase")]
-    pub struct ItemMetadata {
-        pub title: String,
-        pub id: Option<String>,
-        pub asin: Option<String>,
-        pub isbn: Option<String>,
-        pub itunes_id: Option<String>,
-    }
-    #[derive(Debug, Serialize, Deserialize, Clone)]
-    #[serde(rename_all = "camelCase")]
-    pub struct ItemMedia {
-        pub metadata: ItemMetadata,
-        pub ebook_format: Option<String>,
-        pub episodes: Option<Vec<ItemMetadata>>,
-    }
-    #[derive(Debug, Serialize, Deserialize)]
-    #[serde(rename_all = "camelCase")]
-    pub struct RecentEpisode {
-        pub id: String,
-        pub title: String,
-        pub season: Option<String>,
-        pub episode: Option<String>,
-    }
-    #[derive(Debug, Serialize, Deserialize)]
-    #[serde(rename_all = "camelCase")]
-    pub struct Item {
-        pub id: String,
-        pub name: Option<String>,
-        pub media: Option<ItemMedia>,
-        pub media_type: Option<MediaType>,
-        pub recent_episode: Option<RecentEpisode>,
-        pub user_media_progress: Option<ItemProgress>,
-    }
-    #[derive(Debug, Serialize, Deserialize)]
-    #[serde(rename_all = "camelCase")]
-    pub struct Response {
-        pub library_items: Vec<Item>,
-    }
-
-    #[derive(Debug, Serialize, Deserialize)]
-    pub struct LibrariesListResponse {
-        pub libraries: Vec<Item>,
-    }
-
-    #[derive(Debug, Serialize, Deserialize)]
-    pub struct ListResponse {
-        pub results: Vec<Item>,
-    }
-}
-
-pub fn itunes_podcast_episode_by_name(name: &str, podcast: metadata::Model) -> Option<i32> {
-    podcast.podcast_specifics.and_then(|specifics| {
-        specifics
-            .episodes
-            .iter()
-            .find(|e| e.title == name)
-            .map(|e| e.number)
-    })
-}
diff --git a/apps/backend/src/models.rs b/apps/backend/src/models.rs
index ec5c62b481..c706c3a8f8 100644
--- a/apps/backend/src/models.rs
+++ b/apps/backend/src/models.rs
@@ -4,7 +4,7 @@ use std::{
     sync::Arc,
 };
 
-use async_graphql::{Enum, InputObject, OutputType, SimpleObject, Union};
+use async_graphql::{Enum, InputObject, OutputType, Result as GraphqlResult, SimpleObject, Union};
 use async_trait::async_trait;
 use boilermates::boilermates;
 use chrono::{DateTime, NaiveDate};
@@ -13,25 +13,101 @@ use database::{
     MediaLot, MediaSource, SeenState, Visibility,
 };
 use derive_more::{Add, AddAssign, Sum};
+use enum_meta::{meta, Meta};
 use rust_decimal::Decimal;
 use rust_decimal_macros::dec;
 use schematic::ConfigEnum;
 use schematic::Schematic;
-use sea_orm::{
-    prelude::DateTimeUtc, DerivePartialModel, EnumIter, FromJsonQueryResult, FromQueryResult,
-};
+use sea_orm::{prelude::DateTimeUtc, EnumIter, FromJsonQueryResult, FromQueryResult};
 use serde::{de, Deserialize, Serialize};
 use serde_with::skip_serializing_none;
 use strum::Display;
 
 use crate::{
-    entities::{exercise::ExerciseListItem, prelude::Workout, user_measurement, workout},
+    entities::{user_measurement, workout},
     file_storage::FileStorageService,
-    miscellaneous::CollectionExtraInformation,
-    traits::{DatabaseAssetsAsSingleUrl, DatabaseAssetsAsUrls},
-    utils::get_stored_asset,
+    traits::{DatabaseAssetsAsSingleUrl, DatabaseAssetsAsUrls, GraphqlRepresentation},
 };
 
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Enum)]
+pub enum CollectionExtraInformationLot {
+    String,
+    Number,
+    Date,
+    DateTime,
+    StringArray,
+}
+
+#[derive(
+    Debug,
+    PartialEq,
+    Eq,
+    Clone,
+    Serialize,
+    Deserialize,
+    SimpleObject,
+    FromJsonQueryResult,
+    InputObject,
+)]
+#[graphql(input_name = "CollectionExtraInformationInput")]
+pub struct CollectionExtraInformation {
+    pub name: String,
+    pub description: String,
+    pub lot: CollectionExtraInformationLot,
+    pub default_value: Option<String>,
+    pub required: Option<bool>,
+}
+
+#[derive(Display, EnumIter)]
+pub enum DefaultCollection {
+    Watchlist,
+    #[strum(serialize = "In Progress")]
+    InProgress,
+    Completed,
+    Monitoring,
+    Custom,
+    Owned,
+    Reminders,
+}
+
+meta! {
+    DefaultCollection, (Option<Vec<CollectionExtraInformation>>, &'static str);
+    Watchlist, (None, "Things I want to watch in the future.");
+    InProgress, (None, "Media items that I am currently watching.");
+    Completed, (None, "Media items that I have completed.");
+    Custom, (None, "Items that I have created manually.");
+    Monitoring, (None, "Items that I am keeping an eye on.");
+    Owned, (Some(
+        vec![
+            CollectionExtraInformation {
+                name: "Owned on".to_string(),
+                description: "When did you get this media?".to_string(),
+                lot: CollectionExtraInformationLot::Date,
+                default_value: None,
+                required: None,
+            }
+        ]
+    ), "Items that I have in my inventory.");
+    Reminders, (Some(
+        vec![
+            CollectionExtraInformation {
+                name: "Reminder".to_string(),
+                description: "When do you want to be reminded?".to_string(),
+                lot: CollectionExtraInformationLot::Date,
+                default_value: None,
+                required: Some(true),
+            },
+            CollectionExtraInformation {
+                name: "Text".to_string(),
+                description: "What do you want to be reminded about?".to_string(),
+                lot: CollectionExtraInformationLot::String,
+                default_value: None,
+                required: Some(true),
+            }
+        ]
+    ), "Items that I want to be reminded about.");
+}
+
 #[derive(Enum, Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq)]
 pub enum BackgroundJob {
     CalculateSummary,
@@ -94,7 +170,7 @@ pub struct SearchDetails {
 }
 
 #[derive(Serialize, Deserialize, Debug, SimpleObject, Clone)]
-#[graphql(concrete(name = "ExerciseListResults", params(ExerciseListItem)))]
+#[graphql(concrete(name = "ExerciseListResults", params(fitness::ExerciseListItem)))]
 #[graphql(concrete(name = "MediaCollectionContentsResults", params(media::EntityWithLot)))]
 #[graphql(concrete(
     name = "MetadataSearchResults",
@@ -106,7 +182,7 @@ pub struct SearchDetails {
     params(media::MetadataGroupSearchItem)
 ))]
 #[graphql(concrete(name = "GenreListResults", params(media::GenreListItem)))]
-#[graphql(concrete(name = "WorkoutListResults", params(fitness::WorkoutListItem)))]
+#[graphql(concrete(name = "WorkoutListResults", params(workout::Model)))]
 #[graphql(concrete(name = "IdResults", params(String)))]
 pub struct SearchResults<T: OutputType> {
     pub details: SearchDetails,
@@ -442,9 +518,16 @@ pub mod media {
     }
 
     impl PodcastSpecifics {
-        pub fn get_episode(&self, episode_number: i32) -> Option<&PodcastEpisode> {
+        pub fn episode_by_number(&self, episode_number: i32) -> Option<&PodcastEpisode> {
             self.episodes.iter().find(|e| e.number == episode_number)
         }
+
+        pub fn episode_by_name(&self, name: &str) -> Option<i32> {
+            self.episodes
+                .iter()
+                .find(|e| e.title == name)
+                .map(|e| e.number)
+        }
     }
 
     #[derive(
@@ -1007,7 +1090,7 @@ pub mod media {
         ) -> Option<String> {
             if let Some(images) = self {
                 if let Some(i) = images.first().cloned() {
-                    Some(get_stored_asset(i.url, file_storage_service).await)
+                    Some(file_storage_service.get_stored_asset(i.url).await)
                 } else {
                     None
                 }
@@ -1023,7 +1106,7 @@ pub mod media {
             let mut images = vec![];
             if let Some(imgs) = self {
                 for i in imgs.clone() {
-                    images.push(get_stored_asset(i.url, file_storage_service).await);
+                    images.push(file_storage_service.get_stored_asset(i.url).await);
                 }
             }
             images
@@ -1330,6 +1413,36 @@ pub mod fitness {
         pub custom: Option<HashMap<String, Decimal>>,
     }
 
+    #[derive(Clone, Debug, Deserialize, SimpleObject, FromQueryResult)]
+    pub struct ExerciseListItem {
+        pub lot: ExerciseLot,
+        pub id: String,
+        #[graphql(skip)]
+        pub attributes: ExerciseAttributes,
+        pub num_times_interacted: Option<i32>,
+        pub last_updated_on: Option<DateTimeUtc>,
+        pub muscle: Option<ExerciseMuscle>,
+        pub image: Option<String>,
+        #[graphql(skip)]
+        pub muscles: Vec<ExerciseMuscle>,
+    }
+
+    #[async_trait]
+    impl GraphqlRepresentation for ExerciseListItem {
+        async fn graphql_representation(
+            self,
+            file_storage_service: &Arc<FileStorageService>,
+        ) -> GraphqlResult<Self> {
+            let mut converted_exercise = self.clone();
+            if let Some(img) = self.attributes.internal_images.first() {
+                converted_exercise.image =
+                    Some(file_storage_service.get_stored_asset(img.clone()).await)
+            }
+            converted_exercise.muscle = self.muscles.first().cloned();
+            Ok(converted_exercise)
+        }
+    }
+
     /// The totals of a workout and the different bests achieved.
     #[derive(
         Debug,
@@ -1693,26 +1806,6 @@ pub mod fitness {
         pub exercises: Vec<WorkoutSummaryExercise>,
     }
 
-    #[derive(
-        Clone,
-        Debug,
-        PartialEq,
-        Eq,
-        Serialize,
-        Deserialize,
-        FromQueryResult,
-        DerivePartialModel,
-        SimpleObject,
-    )]
-    #[sea_orm(entity = "Workout")]
-    pub struct WorkoutListItem {
-        pub id: String,
-        pub start_time: DateTimeUtc,
-        pub end_time: DateTimeUtc,
-        pub summary: WorkoutSummary,
-        pub name: String,
-    }
-
     #[derive(Clone, Debug, Deserialize, Serialize, InputObject)]
     pub struct UserWorkoutSetRecord {
         pub lot: SetLot,
@@ -1745,3 +1838,76 @@ pub mod fitness {
         pub exercises: Vec<UserExerciseInput>,
     }
 }
+
+pub mod audiobookshelf_models {
+    use super::*;
+
+    #[derive(Debug, Serialize, Deserialize, Clone, Display)]
+    #[serde(rename_all = "snake_case")]
+    pub enum MediaType {
+        Book,
+        Podcast,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    #[serde(rename_all = "camelCase")]
+    pub struct ItemProgress {
+        pub progress: Decimal,
+        pub is_finished: bool,
+        pub ebook_progress: Option<Decimal>,
+    }
+
+    #[derive(Debug, Serialize, Deserialize, Clone)]
+    #[serde(rename_all = "camelCase")]
+    pub struct ItemMetadata {
+        pub title: String,
+        pub id: Option<String>,
+        pub asin: Option<String>,
+        pub isbn: Option<String>,
+        pub itunes_id: Option<String>,
+    }
+
+    #[derive(Debug, Serialize, Deserialize, Clone)]
+    #[serde(rename_all = "camelCase")]
+    pub struct ItemMedia {
+        pub metadata: ItemMetadata,
+        pub ebook_format: Option<String>,
+        pub episodes: Option<Vec<ItemMetadata>>,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    #[serde(rename_all = "camelCase")]
+    pub struct RecentEpisode {
+        pub id: String,
+        pub title: String,
+        pub season: Option<String>,
+        pub episode: Option<String>,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    #[serde(rename_all = "camelCase")]
+    pub struct Item {
+        pub id: String,
+        pub name: Option<String>,
+        pub media: Option<ItemMedia>,
+        pub media_type: Option<MediaType>,
+        pub recent_episode: Option<RecentEpisode>,
+        pub user_media_progress: Option<ItemProgress>,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    #[serde(rename_all = "camelCase")]
+    pub struct Response {
+        pub library_items: Vec<Item>,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    pub struct LibrariesListResponse {
+        pub libraries: Vec<Item>,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    pub struct ListResponse {
+        pub results: Vec<Item>,
+    }
+}
diff --git a/apps/backend/src/providers/anilist/mod.rs b/apps/backend/src/providers/anilist/mod.rs
index 3777d2108e..f9149d4775 100644
--- a/apps/backend/src/providers/anilist/mod.rs
+++ b/apps/backend/src/providers/anilist/mod.rs
@@ -773,5 +773,5 @@ fn get_in_preferred_language(
         AnilistPreferredLanguage::English => english.clone(),
         AnilistPreferredLanguage::Romaji => romaji.clone(),
     };
-    title.or(native).unwrap()
+    title.or(native).or(english).or(romaji).unwrap()
 }
diff --git a/apps/backend/src/routes.rs b/apps/backend/src/routes.rs
index 7ca2fbdb52..ab691b0dba 100644
--- a/apps/backend/src/routes.rs
+++ b/apps/backend/src/routes.rs
@@ -14,7 +14,7 @@ use serde_json::json;
 
 use crate::{
     graphql::GraphqlSchema,
-    miscellaneous::resolver::MiscellaneousService,
+    miscellaneous::MiscellaneousService,
     utils::{AuthContext, TEMP_DIR},
 };
 
diff --git a/apps/backend/src/utils.rs b/apps/backend/src/utils.rs
index dddbfa314f..432dc6d580 100644
--- a/apps/backend/src/utils.rs
+++ b/apps/backend/src/utils.rs
@@ -37,8 +37,8 @@ use crate::{
     fitness::resolver::ExerciseService,
     importer::ImporterService,
     jwt,
-    miscellaneous::resolver::MiscellaneousService,
-    models::{ChangeCollectionToEntityInput, StoredUrl},
+    miscellaneous::MiscellaneousService,
+    models::ChangeCollectionToEntityInput,
     traits::TraceOk,
 };
 
@@ -263,16 +263,6 @@ pub fn get_base_http_client(
         .unwrap()
 }
 
-pub async fn get_stored_asset(
-    url: StoredUrl,
-    file_storage_service: &Arc<FileStorageService>,
-) -> String {
-    match url {
-        StoredUrl::Url(u) => u,
-        StoredUrl::S3(u) => file_storage_service.get_presigned_url(u).await,
-    }
-}
-
 type CteCol = collection_to_entity::Column;
 
 pub async fn entity_in_collections(
diff --git a/apps/frontend/app/lib/state/media.ts b/apps/frontend/app/lib/state/media.ts
index 4dbce72207..11d3e946b9 100644
--- a/apps/frontend/app/lib/state/media.ts
+++ b/apps/frontend/app/lib/state/media.ts
@@ -15,14 +15,13 @@ import {
 
 export type UpdateProgressData = {
 	metadataId: string;
-	onlySeason?: boolean;
-	completeShow?: boolean;
-	completePodcast?: boolean;
+	showAllEpisodesBefore?: boolean;
 	showSeasonNumber?: number | null;
 	showEpisodeNumber?: number | null;
 	podcastEpisodeNumber?: number | null;
 	animeEpisodeNumber?: number | null;
 	mangaChapterNumber?: number | null;
+	mangaVolumeNumber?: number | null;
 };
 
 const metadataProgressUpdateAtom = atom<UpdateProgressData | null>(null);
diff --git a/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx b/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx
index 12100ddfaf..457aa88f64 100644
--- a/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx
+++ b/apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx
@@ -240,6 +240,7 @@ export default function Page() {
 	const [_m, setMetadataToUpdate] = useMetadataProgressUpdate();
 	const [_r, setEntityToReview] = useReviewEntity();
 	const [_a, setAddEntityToCollectionData] = useAddEntityToCollection();
+	const nextEntry = loaderData.userMetadataDetails.nextEntry;
 
 	const PutOnHoldBtn = () => {
 		return (
@@ -648,30 +649,20 @@ export default function Page() {
 										{loaderData.metadataDetails.lot === MediaLot.Show ? (
 											<>
 												<Menu.Label>Shows</Menu.Label>
-												{loaderData.userMetadataDetails.nextEntry ? (
+												{nextEntry ? (
 													<>
 														<Menu.Item
 															onClick={() => {
 																setMetadataToUpdate({
 																	metadataId: loaderData.metadataId,
-																	showSeasonNumber:
-																		loaderData.metadataDetails.lot ===
-																		MediaLot.Show
-																			? loaderData.userMetadataDetails.nextEntry
-																					?.season
-																			: undefined,
-																	showEpisodeNumber:
-																		loaderData.metadataDetails.lot ===
-																		MediaLot.Show
-																			? loaderData.userMetadataDetails.nextEntry
-																					?.episode
-																			: undefined,
+																	showSeasonNumber: nextEntry.season,
+																	showEpisodeNumber: nextEntry.episode,
 																});
 															}}
 														>
 															Mark{" "}
-															{`S${loaderData.userMetadataDetails.nextEntry?.season}-E${loaderData.userMetadataDetails.nextEntry?.episode}`}{" "}
-															as seen
+															{`S${nextEntry.season}-E${nextEntry.episode}`} as
+															seen
 														</Menu.Item>
 														<PutOnHoldBtn />
 													</>
@@ -685,30 +676,63 @@ export default function Page() {
 												)}
 											</>
 										) : null}
+										{loaderData.metadataDetails.lot === MediaLot.Anime &&
+										nextEntry ? (
+											<>
+												<Menu.Label>Anime</Menu.Label>
+												<>
+													<Menu.Item
+														onClick={() => {
+															setMetadataToUpdate({
+																metadataId: loaderData.metadataId,
+																animeEpisodeNumber: nextEntry.episode,
+															});
+														}}
+													>
+														Mark EP-
+														{nextEntry.episode} as listened
+													</Menu.Item>
+												</>
+											</>
+										) : null}
+										{loaderData.metadataDetails.lot === MediaLot.Manga &&
+										nextEntry ? (
+											<>
+												<Menu.Label>Manga</Menu.Label>
+												<>
+													<Menu.Item
+														onClick={() => {
+															setMetadataToUpdate({
+																metadataId: loaderData.metadataId,
+																mangaChapterNumber: nextEntry.chapter,
+																mangaVolumeNumber: nextEntry.volume,
+															});
+														}}
+													>
+														Mark{" "}
+														{nextEntry.chapter
+															? `CH-${nextEntry.chapter}`
+															: `VOL-${nextEntry.volume}`}{" "}
+														as read
+													</Menu.Item>
+												</>
+											</>
+										) : null}
 										{loaderData.metadataDetails.lot === MediaLot.Podcast ? (
 											<>
 												<Menu.Label>Podcasts</Menu.Label>
-												{loaderData.userMetadataDetails.nextEntry ? (
+												{nextEntry ? (
 													<>
 														<Menu.Item
 															onClick={() => {
 																setMetadataToUpdate({
 																	metadataId: loaderData.metadataId,
-																	podcastEpisodeNumber:
-																		loaderData.metadataDetails.lot ===
-																		MediaLot.Podcast
-																			? loaderData.userMetadataDetails.nextEntry
-																					?.episode
-																			: undefined,
+																	podcastEpisodeNumber: nextEntry.episode,
 																});
 															}}
 														>
 															Mark EP-
-															{
-																loaderData.userMetadataDetails.nextEntry
-																	?.episode
-															}{" "}
-															as listened
+															{nextEntry.episode} as listened
 														</Menu.Item>
 														<PutOnHoldBtn />
 													</>
@@ -1588,7 +1612,8 @@ const DisplayShowSeason = (props: {
 							setMetadataToUpdate({
 								metadataId: loaderData.metadataId,
 								showSeasonNumber: props.season.seasonNumber,
-								onlySeason: true,
+								showEpisodeNumber: props.season.episodes.at(-1)?.episodeNumber,
+								showAllEpisodesBefore: true,
 							});
 						}}
 					>
diff --git a/apps/frontend/app/routes/_dashboard.settings.notifications.tsx b/apps/frontend/app/routes/_dashboard.settings.notifications.tsx
index 39bdfe4891..4dba7406e1 100644
--- a/apps/frontend/app/routes/_dashboard.settings.notifications.tsx
+++ b/apps/frontend/app/routes/_dashboard.settings.notifications.tsx
@@ -369,7 +369,7 @@ const DisplayNotification = (props: {
 				</Box>
 				<Group>
 					<Form method="POST" action={withQuery("", { intent: "update" })}>
-						<ActionIcon color="indigo" variant="outline" type="submit">
+						<ActionIcon color="indigo" variant="subtle" type="submit">
 							{props.notification.isDisabled ? (
 								<IconPlayerPlay />
 							) : (
@@ -398,7 +398,7 @@ const DisplayNotification = (props: {
 							<ActionIcon
 								type="submit"
 								color="red"
-								variant="outline"
+								variant="subtle"
 								onClick={async (e) => {
 									const form = e.currentTarget.form;
 									e.preventDefault();
@@ -409,7 +409,7 @@ const DisplayNotification = (props: {
 									if (conf && form) submit(form);
 								}}
 							>
-								<IconTrash size={16} />
+								<IconTrash />
 							</ActionIcon>
 						</Form>
 					</Tooltip>
diff --git a/apps/frontend/app/routes/_dashboard.settings.users.tsx b/apps/frontend/app/routes/_dashboard.settings.users.tsx
index b8dd29f829..2754b9384d 100644
--- a/apps/frontend/app/routes/_dashboard.settings.users.tsx
+++ b/apps/frontend/app/routes/_dashboard.settings.users.tsx
@@ -285,21 +285,17 @@ const UserDisplay = (props: { index: number }) => {
 				<Group>
 					<ActionIcon
 						color="indigo"
-						variant="outline"
+						variant="subtle"
 						onClick={() => setUpdateUserData(user)}
 					>
 						<IconPencil />
 					</ActionIcon>
-					<Form
-						method="POST"
-						action={withQuery("", { intent: "delete" })}
-						style={{ flex: "none" }}
-					>
+					<Form method="POST" action={withQuery("", { intent: "delete" })}>
 						<input hidden name="toDeleteUserId" defaultValue={user.id} />
 						<ActionIcon
 							color="red"
 							type="submit"
-							variant="outline"
+							variant="subtle"
 							onClick={async (e) => {
 								const form = e.currentTarget.form;
 								e.preventDefault();
@@ -309,7 +305,7 @@ const UserDisplay = (props: { index: number }) => {
 								if (conf && form) submit(form);
 							}}
 						>
-							<IconTrash size={16} />
+							<IconTrash />
 						</ActionIcon>
 					</Form>
 				</Group>
diff --git a/apps/frontend/app/routes/_dashboard.tsx b/apps/frontend/app/routes/_dashboard.tsx
index b54cf77713..ef4ee24365 100644
--- a/apps/frontend/app/routes/_dashboard.tsx
+++ b/apps/frontend/app/routes/_dashboard.tsx
@@ -3,7 +3,6 @@ import { $path } from "@ignisda/remix-routes";
 import {
 	ActionIcon,
 	Affix,
-	Alert,
 	Anchor,
 	AppShell,
 	Box,
@@ -79,7 +78,6 @@ import {
 	snakeCase,
 } from "@ryot/ts-utils";
 import {
-	IconAlertCircle,
 	IconArchive,
 	IconBook,
 	IconBrandPagekit,
@@ -942,15 +940,6 @@ const NewProgressUpdateForm = ({
 	);
 	const [watchTime, setWatchTime] =
 		useState<(typeof WATCH_TIMES)[number]>("Just Right Now");
-	const [animeEpisodeNumber, setAnimeEpisodeNumber] = useState<
-		string | undefined
-	>(undefined);
-	const [mangaChapterNumber, setMangaChapterNumber] = useState<
-		string | undefined
-	>(undefined);
-	const [mangaVolumeNumber, setMangaVolumeNumber] = useState<
-		string | undefined
-	>(undefined);
 
 	return (
 		<Form
@@ -973,172 +962,147 @@ const NewProgressUpdateForm = ({
 					<>
 						<NumberInput
 							label="Episode"
-							name="animeEpisodeNumber"
-							description="Leaving this empty will mark the whole anime as watched"
+							required
 							hideControls
-							value={animeEpisodeNumber}
-							onChange={(e) => setAnimeEpisodeNumber(e.toString())}
+							value={metadataToUpdate.animeEpisodeNumber?.toString()}
+							onChange={(e) => {
+								setMetadataToUpdate(
+									produce(metadataToUpdate, (draft) => {
+										draft.animeEpisodeNumber = Number(e);
+									}),
+								);
+							}}
+						/>
+						<Checkbox
+							label="Mark all unseen episodes before this as watched"
+							name="animeAllEpisodesBefore"
 						/>
-						{animeEpisodeNumber ? (
-							<Checkbox
-								label="Mark all episodes before this as watched"
-								name="animeAllEpisodesBefore"
-							/>
-						) : null}
 					</>
 				) : null}
 				{metadataDetails.lot === MediaLot.Manga ? (
 					<>
-						<Box>
-							<Text c="dimmed" size="sm">
-								Leaving the following empty will mark the whole manga as watched
-							</Text>
+						<Input.Wrapper
+							required
+							label="Enter either the chapter number or the volume number"
+						>
 							<Group wrap="nowrap">
 								<NumberInput
-									label="Chapter"
-									name="mangaChapterNumber"
+									description="Chapter"
 									hideControls
-									value={mangaChapterNumber}
-									onChange={(e) => setMangaChapterNumber(e.toString())}
+									value={metadataToUpdate.mangaChapterNumber?.toString()}
+									onChange={(e) => {
+										setMetadataToUpdate(
+											produce(metadataToUpdate, (draft) => {
+												draft.mangaChapterNumber = Number(e);
+											}),
+										);
+									}}
 								/>
 								<Text ta="center" fw="bold" mt="sm">
 									OR
 								</Text>
 								<NumberInput
-									label="Volume"
-									name="mangaVolumeNumber"
+									description="Volume"
 									hideControls
-									value={mangaVolumeNumber}
-									onChange={(e) => setMangaVolumeNumber(e.toString())}
+									value={metadataToUpdate.mangaVolumeNumber?.toString()}
+									onChange={(e) => {
+										setMetadataToUpdate(
+											produce(metadataToUpdate, (draft) => {
+												draft.mangaVolumeNumber = Number(e);
+											}),
+										);
+									}}
 								/>
 							</Group>
-						</Box>
-						{mangaChapterNumber ? (
-							<Checkbox
-								label="Mark all chapters before this as watched"
-								name="mangaAllChaptersBefore"
-							/>
-						) : null}
+						</Input.Wrapper>
+						<Checkbox
+							label="Mark all unread volumes/chapters before this as watched"
+							name="mangaAllChaptersOrVolumesBefore"
+						/>
 					</>
 				) : null}
 				{metadataDetails.lot === MediaLot.Show ? (
 					<>
-						<input
-							hidden
-							name="showSpecifics"
-							defaultValue={JSON.stringify(
-								metadataDetails.showSpecifics?.seasons.map((s) => ({
-									seasonNumber: s.seasonNumber,
-									episodes: s.episodes.map((e) => e.episodeNumber),
-								})),
-							)}
+						<Select
+							label="Season"
+							required
+							data={metadataDetails.showSpecifics?.seasons.map((s) => ({
+								label: `${s.seasonNumber}. ${s.name.toString()}`,
+								value: s.seasonNumber.toString(),
+							}))}
+							value={metadataToUpdate.showSeasonNumber?.toString()}
+							onChange={(v) => {
+								setMetadataToUpdate(
+									produce(metadataToUpdate, (draft) => {
+										draft.showSeasonNumber = Number(v);
+									}),
+								);
+							}}
+							searchable
+							limit={50}
+						/>
+						<Select
+							label="Episode"
+							required
+							data={
+								metadataDetails.showSpecifics?.seasons
+									.find(
+										(s) => s.seasonNumber === metadataToUpdate.showSeasonNumber,
+									)
+									?.episodes.map((e) => ({
+										label: `${e.episodeNumber}. ${e.name.toString()}`,
+										value: e.episodeNumber.toString(),
+									})) || []
+							}
+							value={metadataToUpdate.showEpisodeNumber?.toString()}
+							onChange={(v) => {
+								setMetadataToUpdate(
+									produce(metadataToUpdate, (draft) => {
+										draft.showEpisodeNumber = Number(v);
+									}),
+								);
+							}}
+							searchable
+							limit={50}
+						/>
+						<Checkbox
+							label="Mark all unseen episodes before this as seen"
+							defaultChecked={metadataToUpdate.showAllEpisodesBefore}
+							onChange={(e) => {
+								setMetadataToUpdate(
+									produce(metadataToUpdate, (draft) => {
+										draft.showAllEpisodesBefore = e.target.checked;
+									}),
+								);
+							}}
 						/>
-						{metadataToUpdate.onlySeason || metadataToUpdate.completeShow ? (
-							<Alert color="yellow" icon={<IconAlertCircle />}>
-								{metadataToUpdate.onlySeason
-									? `This will mark all episodes of season ${metadataToUpdate.showSeasonNumber} as seen`
-									: metadataToUpdate.completeShow
-										? "This will mark all episodes for this show as seen"
-										: null}
-							</Alert>
-						) : null}
-						{!metadataToUpdate.completeShow ? (
-							<Select
-								label="Season"
-								required
-								data={metadataDetails.showSpecifics?.seasons.map((s) => ({
-									label: `${s.seasonNumber}. ${s.name.toString()}`,
-									value: s.seasonNumber.toString(),
-								}))}
-								value={metadataToUpdate.showSeasonNumber?.toString()}
-								onChange={(v) => {
-									setMetadataToUpdate(
-										produce(metadataToUpdate, (draft) => {
-											draft.showSeasonNumber = Number(v);
-										}),
-									);
-								}}
-								searchable
-								limit={50}
-							/>
-						) : null}
-						{metadataToUpdate?.onlySeason ? (
-							<Checkbox
-								label="Mark all seasons before this as seen"
-								name="showAllSeasonsBefore"
-							/>
-						) : null}
-						{!metadataToUpdate.onlySeason &&
-						typeof metadataToUpdate.showSeasonNumber !== "undefined" ? (
-							<Select
-								label="Episode"
-								required
-								data={
-									metadataDetails.showSpecifics?.seasons
-										.find(
-											(s) =>
-												s.seasonNumber ===
-												Number(metadataToUpdate.showSeasonNumber),
-										)
-										?.episodes.map((e) => ({
-											label: `${e.episodeNumber}. ${e.name.toString()}`,
-											value: e.episodeNumber.toString(),
-										})) || []
-								}
-								value={metadataToUpdate.showEpisodeNumber?.toString()}
-								onChange={(v) => {
-									setMetadataToUpdate(
-										produce(metadataToUpdate, (draft) => {
-											draft.showEpisodeNumber = Number(v);
-										}),
-									);
-								}}
-								searchable
-								limit={50}
-							/>
-						) : null}
 					</>
 				) : null}
 				{metadataDetails.lot === MediaLot.Podcast ? (
 					<>
-						<input
-							hidden
-							name="podcastSpecifics"
-							defaultValue={JSON.stringify(
-								metadataDetails.podcastSpecifics?.episodes.map((e) => ({
-									episodeNumber: e.number,
-								})),
-							)}
+						<Text fw="bold">Select episode</Text>
+						<Select
+							required
+							label="Episode"
+							data={metadataDetails.podcastSpecifics?.episodes.map((se) => ({
+								label: se.title.toString(),
+								value: se.number.toString(),
+							}))}
+							value={metadataToUpdate.podcastEpisodeNumber?.toString()}
+							onChange={(v) => {
+								setMetadataToUpdate(
+									produce(metadataToUpdate, (draft) => {
+										draft.podcastEpisodeNumber = Number(v);
+									}),
+								);
+							}}
+							searchable
+							limit={50}
+						/>
+						<Checkbox
+							label="Mark all unseen episodes before this as seen"
+							name="podcastAllEpisodesBefore"
 						/>
-						{metadataToUpdate.completePodcast ? (
-							<Alert color="yellow" icon={<IconAlertCircle />}>
-								This will mark all episodes for this podcast as seen
-							</Alert>
-						) : (
-							<>
-								<Text fw="bold">Select episode</Text>
-								<Select
-									required
-									label="Episode"
-									data={metadataDetails.podcastSpecifics?.episodes.map(
-										(se) => ({
-											label: se.title.toString(),
-											value: se.number.toString(),
-										}),
-									)}
-									value={metadataToUpdate.podcastEpisodeNumber?.toString()}
-									onChange={(v) => {
-										setMetadataToUpdate(
-											produce(metadataToUpdate, (draft) => {
-												draft.podcastEpisodeNumber = Number(v);
-											}),
-										);
-									}}
-									searchable
-									limit={50}
-								/>
-							</>
-						)}
 					</>
 				) : null}
 				<Select
diff --git a/apps/frontend/app/routes/actions.tsx b/apps/frontend/app/routes/actions.tsx
index 79692f7086..6417da3a16 100644
--- a/apps/frontend/app/routes/actions.tsx
+++ b/apps/frontend/app/routes/actions.tsx
@@ -17,12 +17,14 @@ import {
 	EntityLot,
 	MediaLot,
 	MediaSource,
+	MetadataDetailsDocument,
 	PostReviewDocument,
 	RemoveEntityFromCollectionDocument,
 	SeenState,
+	UserMetadataDetailsDocument,
 	Visibility,
 } from "@ryot/generated/graphql/backend/graphql";
-import { isEmpty, omitBy, set } from "@ryot/ts-utils";
+import { isEmpty, isNumber, omitBy, set } from "@ryot/ts-utils";
 import invariant from "tiny-invariant";
 import { match } from "ts-pattern";
 import { z } from "zod";
@@ -222,6 +224,18 @@ export const action = unstable_defineAction(async ({ request, response }) => {
 		})
 		.with("progressUpdate", async () => {
 			const submission = processSubmission(formData, progressUpdateSchema);
+			const metadataId = submission.metadataId;
+			const { metadataDetails } = await serverGqlService.request(
+				MetadataDetailsDocument,
+				{ metadataId },
+			);
+			const { userMetadataDetails } =
+				await serverGqlService.authenticatedRequest(
+					request,
+					UserMetadataDetailsDocument,
+					{ metadataId },
+				);
+			const latestHistoryItem = userMetadataDetails?.history?.[0];
 			const variables = {
 				metadataId: submission.metadataId,
 				progress: "100",
@@ -234,92 +248,115 @@ export const action = unstable_defineAction(async ({ request, response }) => {
 				mangaVolumeNumber: submission.mangaVolumeNumber,
 				providerWatchedOn: submission.providerWatchedOn,
 			};
-			let needsFinalUpdate = true;
 			const updates = [];
-			const showSpecifics = showSpecificsSchema.parse(
-				JSON.parse(submission.showSpecifics || "[]"),
-			);
-			const podcastSpecifics = podcastSpecificsSchema.parse(
-				JSON.parse(submission.podcastSpecifics || "[]"),
-			);
-			if (submission.metadataLot === MediaLot.Anime) {
-				if (submission.animeEpisodeNumber) {
-					if (submission.animeAllEpisodesBefore) {
-						for (let i = 1; i <= submission.animeEpisodeNumber; i++) {
-							updates.push({
-								...variables,
-								animeEpisodeNumber: i,
-							});
-						}
-						needsFinalUpdate = false;
-					}
+			const showSpecifics = metadataDetails.showSpecifics?.seasons || [];
+			const podcastSpecifics = metadataDetails.podcastSpecifics?.episodes || [];
+			if (
+				submission.metadataLot === MediaLot.Anime &&
+				submission.animeAllEpisodesBefore &&
+				submission.animeEpisodeNumber
+			) {
+				const lastSeenEpisode =
+					latestHistoryItem?.animeExtraInformation?.episode || 0;
+				for (
+					let i = lastSeenEpisode + 1;
+					i < submission.animeEpisodeNumber;
+					i++
+				) {
+					updates.push({ ...variables, animeEpisodeNumber: i });
 				}
 			}
 			if (submission.metadataLot === MediaLot.Manga) {
-				if (submission.mangaChapterNumber) {
-					if (submission.mangaAllChaptersBefore) {
-						for (let i = 1; i <= submission.mangaChapterNumber; i++) {
-							updates.push({
-								...variables,
-								mangaChapterNumber: i,
-							});
+				if (
+					(isNumber(submission.mangaChapterNumber) &&
+						isNumber(submission.mangaVolumeNumber)) ||
+					(!isNumber(submission.mangaChapterNumber) &&
+						!isNumber(submission.mangaVolumeNumber))
+				)
+					throw Response.json({
+						message:
+							"Exactly one of mangaChapterNumber or mangaVolumeNumber must be provided",
+					});
+				if (submission.mangaAllChaptersOrVolumesBefore) {
+					if (submission.mangaVolumeNumber) {
+						const lastSeenVolume =
+							latestHistoryItem?.mangaExtraInformation?.volume || 0;
+						for (
+							let i = lastSeenVolume + 1;
+							i < submission.mangaVolumeNumber;
+							i++
+						) {
+							updates.push({ ...variables, mangaVolumeNumber: i });
 						}
-						needsFinalUpdate = false;
 					}
-				}
-			}
-			if (submission.metadataLot === MediaLot.Show) {
-				if (submission.completeShow) {
-					for (const season of showSpecifics) {
-						for (const episode of season.episodes) {
-							updates.push({
-								...variables,
-								showSeasonNumber: season.seasonNumber,
-								showEpisodeNumber: episode,
-							});
-						}
-					}
-					needsFinalUpdate = false;
-				}
-				if (submission.onlySeason) {
-					const selectedSeason = showSpecifics.find(
-						(s) => s.seasonNumber === submission.showSeasonNumber,
-					);
-					invariant(selectedSeason);
-					needsFinalUpdate = false;
-					if (submission.showAllSeasonsBefore) {
-						for (const season of showSpecifics) {
-							if (season.seasonNumber > selectedSeason.seasonNumber) break;
-							for (const episode of season.episodes || []) {
-								updates.push({
-									...variables,
-									showSeasonNumber: season.seasonNumber,
-									showEpisodeNumber: episode,
-								});
-							}
-						}
-					} else {
-						for (const episode of selectedSeason.episodes || []) {
-							updates.push({
-								...variables,
-								showEpisodeNumber: episode,
-							});
+					if (submission.mangaChapterNumber) {
+						const lastSeenChapter =
+							latestHistoryItem?.mangaExtraInformation?.chapter || 0;
+						for (
+							let i = lastSeenChapter + 1;
+							i < submission.mangaChapterNumber;
+							i++
+						) {
+							updates.push({ ...variables, mangaChapterNumber: i });
 						}
 					}
 				}
 			}
-			if (submission.metadataLot === MediaLot.Podcast) {
-				if (submission.completePodcast) {
-					for (const episode of podcastSpecifics) {
+			if (
+				submission.metadataLot === MediaLot.Show &&
+				submission.showAllEpisodesBefore
+			) {
+				const allEpisodesInShow = showSpecifics.flatMap((s) =>
+					s.episodes.map((e) => ({ seasonNumber: s.seasonNumber, ...e })),
+				);
+				const selectedEpisodeIndex = allEpisodesInShow.findIndex(
+					(e) =>
+						e.seasonNumber === submission.showSeasonNumber &&
+						e.episodeNumber === submission.showEpisodeNumber,
+				);
+				invariant(selectedEpisodeIndex !== -1);
+				const firstEpisodeOfShow = allEpisodesInShow[0];
+				const lastSeenEpisode = latestHistoryItem?.showExtraInformation || {
+					episode: firstEpisodeOfShow.episodeNumber,
+					season: firstEpisodeOfShow.seasonNumber,
+				};
+				const lastSeenEpisodeIndex = allEpisodesInShow.findIndex(
+					(e) =>
+						e.seasonNumber === lastSeenEpisode.season &&
+						e.episodeNumber === lastSeenEpisode.episode,
+				);
+				invariant(lastSeenEpisodeIndex !== -1);
+				const firstEpisodeIndexToMark = lastSeenEpisodeIndex + 1;
+				if (selectedEpisodeIndex > firstEpisodeIndexToMark) {
+					for (let i = firstEpisodeIndexToMark; i < selectedEpisodeIndex; i++) {
+						const episode = allEpisodesInShow[i];
 						updates.push({
 							...variables,
-							podcastEpisodeNumber: episode.episodeNumber,
+							showSeasonNumber: episode.seasonNumber,
+							showEpisodeNumber: episode.episodeNumber,
 						});
 					}
-					needsFinalUpdate = false;
 				}
 			}
-			if (needsFinalUpdate) updates.push(variables);
+			if (
+				submission.metadataLot === MediaLot.Podcast &&
+				submission.podcastAllEpisodesBefore
+			) {
+				const selectedEpisode = podcastSpecifics.find(
+					(e) => e.number === submission.podcastEpisodeNumber,
+				);
+				invariant(selectedEpisode);
+				const lastSeenEpisode =
+					latestHistoryItem?.podcastExtraInformation?.episode || 0;
+				const allUnseenEpisodesBefore = podcastSpecifics
+					.filter(
+						(e) =>
+							e.number < selectedEpisode.number && e.number > lastSeenEpisode,
+					)
+					.map((e) => ({ ...variables, podcastEpisodeNumber: e.number }));
+				updates.push(...allUnseenEpisodesBefore);
+			}
+			updates.push(variables);
 			const { deployBulkProgressUpdate } =
 				await serverGqlService.authenticatedRequest(
 					request,
@@ -446,25 +483,15 @@ const progressUpdateSchema = z
 		metadataLot: z.nativeEnum(MediaLot),
 		date: z.string().optional(),
 		[redirectToQueryParam]: z.string().optional(),
-		showSpecifics: z.string().optional(),
-		showAllSeasonsBefore: zx.CheckboxAsString.optional(),
-		podcastSpecifics: z.string().optional(),
-		onlySeason: zx.BoolAsString.optional(),
-		completeShow: zx.BoolAsString.optional(),
-		completePodcast: zx.BoolAsString.optional(),
+		showAllEpisodesBefore: zx.BoolAsString.optional(),
+		podcastAllEpisodesBefore: zx.CheckboxAsString.optional(),
 		animeAllEpisodesBefore: zx.CheckboxAsString.optional(),
-		mangaAllChaptersBefore: zx.CheckboxAsString.optional(),
+		mangaAllChaptersOrVolumesBefore: zx.CheckboxAsString.optional(),
 		providerWatchedOn: z.string().optional(),
 	})
 	.merge(MetadataIdSchema)
 	.merge(MetadataSpecificsSchema);
 
-const showSpecificsSchema = z.array(
-	z.object({ seasonNumber: z.number(), episodes: z.array(z.number()) }),
-);
-
-const podcastSpecificsSchema = z.array(z.object({ episodeNumber: z.number() }));
-
 const bulkUpdateSchema = z
 	.object({
 		progress: z.string().optional(),
diff --git a/apps/frontend/package.json b/apps/frontend/package.json
index ef9108657e..a6c7283b03 100644
--- a/apps/frontend/package.json
+++ b/apps/frontend/package.json
@@ -13,26 +13,26 @@
 		"@hello-pangea/dnd": "16.6.0",
 		"@ignisda/remix-routes": "3.3.0",
 		"@lukemorales/query-key-factory": "1.3.4",
-		"@mantine/carousel": "7.11.1",
-		"@mantine/charts": "7.11.1",
-		"@mantine/core": "7.11.1",
-		"@mantine/dates": "7.11.1",
-		"@mantine/form": "7.11.1",
-		"@mantine/hooks": "7.11.1",
-		"@mantine/notifications": "7.11.1",
-		"@remix-run/node": "2.10.2",
-		"@remix-run/react": "2.10.2",
-		"@remix-run/serve": "2.10.2",
+		"@mantine/carousel": "7.11.2",
+		"@mantine/charts": "7.11.2",
+		"@mantine/core": "7.11.2",
+		"@mantine/dates": "7.11.2",
+		"@mantine/form": "7.11.2",
+		"@mantine/hooks": "7.11.2",
+		"@mantine/notifications": "7.11.2",
+		"@remix-run/node": "2.10.3",
+		"@remix-run/react": "2.10.3",
+		"@remix-run/serve": "2.10.3",
 		"@ryot/generated": "workspace:*",
 		"@ryot/graphql": "workspace:*",
 		"@ryot/ts-utils": "workspace:*",
-		"@tabler/icons-react": "3.10.0",
-		"@tanstack/react-query": "5.50.1",
-		"@tanstack/react-query-devtools": "5.50.1",
+		"@tabler/icons-react": "3.11.0",
+		"@tanstack/react-query": "5.51.11",
+		"@tanstack/react-query-devtools": "5.51.11",
 		"buffer": "6.0.3",
 		"clsx": "2.1.1",
 		"cookie": "0.6.0",
-		"dayjs": "1.11.11",
+		"dayjs": "1.11.12",
 		"dotenv": "16.4.5",
 		"embla-carousel-react": "7.1.0",
 		"graphql": "16.9.0",
@@ -40,27 +40,27 @@
 		"howler": "2.2.4",
 		"humanize-duration-ts": "2.1.1",
 		"immer": "10.1.1",
-		"isbot": "5.1.12",
-		"jotai": "2.9.0",
+		"isbot": "5.1.13",
+		"jotai": "2.9.1",
 		"js-cookie": "3.0.5",
-		"mantine-datatable": "7.11.1",
+		"mantine-datatable": "7.11.2",
 		"react": "18.3.1",
 		"react-confirm": "0.3.0-7",
 		"react-dom": "18.3.1",
-		"react-virtuoso": "4.7.11",
+		"react-virtuoso": "4.7.13",
 		"react-webcam": "7.2.0",
 		"recharts": "2.12.7",
 		"remix-utils": "7.6.0",
 		"tiny-invariant": "1.3.3",
 		"ts-pattern": "5.2.0",
-		"ufo": "1.5.3",
+		"ufo": "1.5.4",
 		"uuid": "10.0.0",
 		"zod": "3.23.8",
 		"zodix": "0.4.4"
 	},
 	"devDependencies": {
 		"@bitprojects/umami-logger-typescript": "1.0.10",
-		"@remix-run/dev": "2.10.2",
+		"@remix-run/dev": "2.10.3",
 		"@types/cookie": "0.6.0",
 		"@types/crypto-js": "4.2.2",
 		"@types/howler": "2.2.11",
@@ -69,11 +69,11 @@
 		"@types/react-dom": "18.3.0",
 		"@types/uuid": "10.0.0",
 		"postcss": "8.4.39",
-		"postcss-preset-mantine": "1.15.0",
+		"postcss-preset-mantine": "1.17.0",
 		"postcss-simple-vars": "7.0.1",
 		"ts-essentials": "10.0.1",
 		"typescript-plugin-css-modules": "5.1.0",
-		"vite": "5.3.3",
+		"vite": "5.3.4",
 		"vite-tsconfig-paths": "4.3.2"
 	},
 	"engines": {
diff --git a/docs/content/index.md b/docs/content/index.md
index 3adf59cbdd..d6d1c5966a 100644
--- a/docs/content/index.md
+++ b/docs/content/index.md
@@ -15,12 +15,14 @@ services:
       - POSTGRES_PASSWORD=postgres
       - POSTGRES_USER=postgres
       - POSTGRES_DB=postgres
+      - TZ=Europe/Amsterdam
     container_name: ryot-db
 
   ryot:
     image: ignisda/ryot:latest # or ghcr.io/ignisda/ryot:latest
     environment:
       - DATABASE_URL=postgres://postgres:postgres@ryot-db:5432/postgres
+      - TZ=Europe/Amsterdam
     ports:
       - "8000:8000"
     pull_policy: always
diff --git a/docs/includes/export-schema.ts b/docs/includes/export-schema.ts
index 9da642e52f..53758d1a5f 100644
--- a/docs/includes/export-schema.ts
+++ b/docs/includes/export-schema.ts
@@ -135,6 +135,7 @@ export interface ImportOrExportMediaItem {
 	 * The type of media.
 	 *
 	 * @default 'book'
+	 * @type {'audio_book' | 'anime' | 'book' | 'podcast' | 'manga' | 'movie' | 'show' | 'video_game' | 'visual_novel'}
 	 */
 	lot: MediaLot;
 	/** The review history for the user. */
@@ -145,6 +146,7 @@ export interface ImportOrExportMediaItem {
 	 * The source of media.
 	 *
 	 * @default 'audible'
+	 * @type {'anilist' | 'audible' | 'custom' | 'google_books' | 'igdb' | 'itunes' | 'listennotes' | 'manga_updates' | 'mal' | 'openlibrary' | 'tmdb' | 'vndb'}
 	 */
 	source: MediaSource;
 	/** An string to help identify it in the original source. */
@@ -161,6 +163,7 @@ export interface ImportOrExportMediaGroupItem {
 	 * The type of media.
 	 *
 	 * @default 'book'
+	 * @type {'audio_book' | 'anime' | 'book' | 'podcast' | 'manga' | 'movie' | 'show' | 'video_game' | 'visual_novel'}
 	 */
 	lot: MediaLot;
 	/** The review history for the user. */
@@ -169,6 +172,7 @@ export interface ImportOrExportMediaGroupItem {
 	 * The source of media.
 	 *
 	 * @default 'audible'
+	 * @type {'anilist' | 'audible' | 'custom' | 'google_books' | 'igdb' | 'itunes' | 'listennotes' | 'manga_updates' | 'mal' | 'openlibrary' | 'tmdb' | 'vndb'}
 	 */
 	source: MediaSource;
 	/** Name of the group. */
@@ -194,6 +198,7 @@ export interface ImportOrExportPersonItem {
 	 * The source of data.
 	 *
 	 * @default 'audible'
+	 * @type {'anilist' | 'audible' | 'custom' | 'google_books' | 'igdb' | 'itunes' | 'listennotes' | 'manga_updates' | 'mal' | 'openlibrary' | 'tmdb' | 'vndb'}
 	 */
 	source: MediaSource;
 	/** The source specific data. */
@@ -236,6 +241,7 @@ export interface WorkoutSetTotals {
 export interface WorkoutSetRecord {
 	actual_rest_time: number | null;
 	confirmed_at: string | null;
+	/** @type {'normal' | 'warm_up' | 'drop' | 'failure'} */
 	lot: SetLot;
 	note: string | null;
 	personal_bests: WorkoutSetPersonalBest[] | null;
@@ -258,6 +264,7 @@ export interface WorkoutOrExerciseTotals {
 /** An exercise that has been processed and committed to the database. */
 export interface ProcessedExercise {
 	assets: EntityAssets | null;
+	/** @type {'duration' | 'distance_and_duration' | 'reps' | 'reps_and_weight'} */
 	lot: ExerciseLot;
 	name: string;
 	notes: string[];
diff --git a/libs/database/src/migrations/m20230417_create_user.rs b/libs/database/src/migrations/m20230417_create_user.rs
index 014e9366d0..e41ff588c3 100644
--- a/libs/database/src/migrations/m20230417_create_user.rs
+++ b/libs/database/src/migrations/m20230417_create_user.rs
@@ -12,10 +12,6 @@ pub enum User {
     Password,
     Lot,
     Preferences,
-    // This field can be `NULL` if the user has not enabled any yank integration
-    YankIntegrations,
-    // This field can be `NULL` if the user has not enabled any sink integration
-    SinkIntegrations,
     OidcIssuerId,
     ExtraInformation,
     IsDisabled,
@@ -33,8 +29,6 @@ impl MigrationTrait for Migration {
                     .col(ColumnDef::new(User::Password).text())
                     .col(ColumnDef::new(User::Lot).text().not_null())
                     .col(ColumnDef::new(User::Preferences).json_binary().not_null())
-                    .col(ColumnDef::new(User::YankIntegrations).json_binary())
-                    .col(ColumnDef::new(User::SinkIntegrations).json_binary())
                     .col(ColumnDef::new(User::OidcIssuerId).text())
                     .col(
                         ColumnDef::new(User::CreatedOn)
diff --git a/libs/database/src/migrations/m20240723_remove_integration_columns_from_user_table.rs b/libs/database/src/migrations/m20240723_remove_integration_columns_from_user_table.rs
new file mode 100644
index 0000000000..930aba3c84
--- /dev/null
+++ b/libs/database/src/migrations/m20240723_remove_integration_columns_from_user_table.rs
@@ -0,0 +1,25 @@
+use sea_orm_migration::prelude::*;
+
+#[derive(DeriveMigrationName)]
+pub struct Migration;
+
+#[async_trait::async_trait]
+impl MigrationTrait for Migration {
+    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
+        if manager.has_column("user", "yank_integrations").await? {
+            let db = manager.get_connection();
+            db.execute_unprepared(r#"ALTER TABLE "user" DROP COLUMN "yank_integrations""#)
+                .await?;
+        }
+        if manager.has_column("user", "sink_integrations").await? {
+            let db = manager.get_connection();
+            db.execute_unprepared(r#"ALTER TABLE "user" DROP COLUMN "sink_integrations""#)
+                .await?;
+        }
+        Ok(())
+    }
+
+    async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
+        Ok(())
+    }
+}
diff --git a/libs/database/src/migrations/mod.rs b/libs/database/src/migrations/mod.rs
index faa996bf8e..b9aa90a035 100644
--- a/libs/database/src/migrations/mod.rs
+++ b/libs/database/src/migrations/mod.rs
@@ -39,6 +39,7 @@ mod m20240717_add_columns_to_integration_and_notification_tables;
 mod m20240717_zz_add_columns_to_user_table;
 mod m20240719_remove_comment_from_top_level;
 mod m20240722_remove_columns_from_user_table;
+mod m20240723_remove_integration_columns_from_user_table;
 
 pub use m20230410_create_metadata::Metadata as AliasedMetadata;
 pub use m20230413_create_person::Person as AliasedPerson;
@@ -99,6 +100,7 @@ impl MigratorTrait for Migrator {
             Box::new(m20240717_zz_add_columns_to_user_table::Migration),
             Box::new(m20240719_remove_comment_from_top_level::Migration),
             Box::new(m20240722_remove_columns_from_user_table::Migration),
+            Box::new(m20240723_remove_integration_columns_from_user_table::Migration),
         ]
     }
 }
diff --git a/libs/generated/src/graphql/backend/gql.ts b/libs/generated/src/graphql/backend/gql.ts
index 08e835d012..d779a85831 100644
--- a/libs/generated/src/graphql/backend/gql.ts
+++ b/libs/generated/src/graphql/backend/gql.ts
@@ -33,7 +33,7 @@ const documents = {
     "query UserDetails {\n  userDetails {\n    __typename\n    ... on User {\n      id\n      lot\n      name\n      oidcIssuerId\n    }\n  }\n}": types.UserDetailsDocument,
     "query UserExerciseDetails($exerciseId: String!) {\n  userExerciseDetails(exerciseId: $exerciseId) {\n    collections {\n      ...CollectionPart\n    }\n    history {\n      idx\n      workoutId\n    }\n    details {\n      exerciseId\n      createdOn\n      lastUpdatedOn\n      exerciseNumTimesInteracted\n      exerciseExtraInformation {\n        lifetimeStats {\n          weight\n          reps\n          distance\n          duration\n          personalBestsAchieved\n        }\n        personalBests {\n          lot\n          sets {\n            workoutId\n            exerciseIdx\n            setIdx\n          }\n        }\n      }\n    }\n  }\n}": types.UserExerciseDetailsDocument,
     "query UserMeasurementsList($input: UserMeasurementsListInput!) {\n  userMeasurementsList(input: $input) {\n    timestamp\n    name\n    comment\n    stats {\n      weight\n      bodyMassIndex\n      totalBodyWater\n      muscle\n      leanBodyMass\n      bodyFat\n      boneMass\n      visceralFat\n      waistCircumference\n      waistToHeightRatio\n      hipCircumference\n      waistToHipRatio\n      chestCircumference\n      thighCircumference\n      bicepsCircumference\n      neckCircumference\n      bodyFatCaliper\n      chestSkinfold\n      abdominalSkinfold\n      thighSkinfold\n      basalMetabolicRate\n      totalDailyEnergyExpenditure\n      calories\n      custom\n    }\n  }\n}": types.UserMeasurementsListDocument,
-    "query UserMetadataDetails($metadataId: String!) {\n  userMetadataDetails(metadataId: $metadataId) {\n    mediaReason\n    hasInteracted\n    collections {\n      ...CollectionPart\n    }\n    inProgress {\n      ...SeenPart\n    }\n    history {\n      ...SeenPart\n    }\n    averageRating\n    unitsConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    seenByAllCount\n    seenByUserCount\n    nextEntry {\n      season\n      episode\n      chapter\n    }\n    showProgress {\n      timesSeen\n      seasonNumber\n      episodes {\n        episodeNumber\n        timesSeen\n      }\n    }\n    podcastProgress {\n      episodeNumber\n      timesSeen\n    }\n  }\n}": types.UserMetadataDetailsDocument,
+    "query UserMetadataDetails($metadataId: String!) {\n  userMetadataDetails(metadataId: $metadataId) {\n    mediaReason\n    hasInteracted\n    collections {\n      ...CollectionPart\n    }\n    inProgress {\n      ...SeenPart\n    }\n    history {\n      ...SeenPart\n    }\n    averageRating\n    unitsConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    seenByAllCount\n    seenByUserCount\n    nextEntry {\n      season\n      volume\n      episode\n      chapter\n    }\n    showProgress {\n      timesSeen\n      seasonNumber\n      episodes {\n        episodeNumber\n        timesSeen\n      }\n    }\n    podcastProgress {\n      episodeNumber\n      timesSeen\n    }\n  }\n}": types.UserMetadataDetailsDocument,
     "query UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}": types.UserMetadataGroupDetailsDocument,
     "query UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}": types.UserPersonDetailsDocument,
     "query UserPreferences {\n  userPreferences {\n    general {\n      reviewScale\n      displayNsfw\n      disableVideos\n      persistQueries\n      watchProviders\n      disableReviews\n      disableWatchProviders\n      disableYankIntegrations\n      disableNavigationAnimation\n      dashboard {\n        section\n        hidden\n        numElements\n      }\n    }\n    fitness {\n      measurements {\n        custom {\n          name\n          dataType\n        }\n        inbuilt {\n          weight\n          bodyMassIndex\n          totalBodyWater\n          muscle\n          leanBodyMass\n          bodyFat\n          boneMass\n          visceralFat\n          waistCircumference\n          waistToHeightRatio\n          hipCircumference\n          waistToHipRatio\n          chestCircumference\n          thighCircumference\n          bicepsCircumference\n          neckCircumference\n          bodyFatCaliper\n          chestSkinfold\n          abdominalSkinfold\n          thighSkinfold\n          basalMetabolicRate\n          totalDailyEnergyExpenditure\n          calories\n        }\n      }\n      exercises {\n        saveHistory\n        unitSystem\n      }\n    }\n    notifications {\n      toSend\n      enabled\n    }\n    featuresEnabled {\n      others {\n        calendar\n        collections\n      }\n      fitness {\n        enabled\n        workouts\n        measurements\n      }\n      media {\n        enabled\n        anime\n        audioBook\n        book\n        manga\n        movie\n        podcast\n        show\n        videoGame\n        visualNovel\n        people\n        groups\n        genres\n      }\n    }\n  }\n}": types.UserPreferencesDocument,
@@ -140,7 +140,7 @@ export function graphql(source: "query UserMeasurementsList($input: UserMeasurem
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
-export function graphql(source: "query UserMetadataDetails($metadataId: String!) {\n  userMetadataDetails(metadataId: $metadataId) {\n    mediaReason\n    hasInteracted\n    collections {\n      ...CollectionPart\n    }\n    inProgress {\n      ...SeenPart\n    }\n    history {\n      ...SeenPart\n    }\n    averageRating\n    unitsConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    seenByAllCount\n    seenByUserCount\n    nextEntry {\n      season\n      episode\n      chapter\n    }\n    showProgress {\n      timesSeen\n      seasonNumber\n      episodes {\n        episodeNumber\n        timesSeen\n      }\n    }\n    podcastProgress {\n      episodeNumber\n      timesSeen\n    }\n  }\n}"): (typeof documents)["query UserMetadataDetails($metadataId: String!) {\n  userMetadataDetails(metadataId: $metadataId) {\n    mediaReason\n    hasInteracted\n    collections {\n      ...CollectionPart\n    }\n    inProgress {\n      ...SeenPart\n    }\n    history {\n      ...SeenPart\n    }\n    averageRating\n    unitsConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    seenByAllCount\n    seenByUserCount\n    nextEntry {\n      season\n      episode\n      chapter\n    }\n    showProgress {\n      timesSeen\n      seasonNumber\n      episodes {\n        episodeNumber\n        timesSeen\n      }\n    }\n    podcastProgress {\n      episodeNumber\n      timesSeen\n    }\n  }\n}"];
+export function graphql(source: "query UserMetadataDetails($metadataId: String!) {\n  userMetadataDetails(metadataId: $metadataId) {\n    mediaReason\n    hasInteracted\n    collections {\n      ...CollectionPart\n    }\n    inProgress {\n      ...SeenPart\n    }\n    history {\n      ...SeenPart\n    }\n    averageRating\n    unitsConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    seenByAllCount\n    seenByUserCount\n    nextEntry {\n      season\n      volume\n      episode\n      chapter\n    }\n    showProgress {\n      timesSeen\n      seasonNumber\n      episodes {\n        episodeNumber\n        timesSeen\n      }\n    }\n    podcastProgress {\n      episodeNumber\n      timesSeen\n    }\n  }\n}"): (typeof documents)["query UserMetadataDetails($metadataId: String!) {\n  userMetadataDetails(metadataId: $metadataId) {\n    mediaReason\n    hasInteracted\n    collections {\n      ...CollectionPart\n    }\n    inProgress {\n      ...SeenPart\n    }\n    history {\n      ...SeenPart\n    }\n    averageRating\n    unitsConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    seenByAllCount\n    seenByUserCount\n    nextEntry {\n      season\n      volume\n      episode\n      chapter\n    }\n    showProgress {\n      timesSeen\n      seasonNumber\n      episodes {\n        episodeNumber\n        timesSeen\n      }\n    }\n    podcastProgress {\n      episodeNumber\n      timesSeen\n    }\n  }\n}"];
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
diff --git a/libs/generated/src/graphql/backend/graphql.ts b/libs/generated/src/graphql/backend/graphql.ts
index d36ce347f2..d08ba00c52 100644
--- a/libs/generated/src/graphql/backend/graphql.ts
+++ b/libs/generated/src/graphql/backend/graphql.ts
@@ -2153,6 +2153,7 @@ export type UserMediaNextEntry = {
   chapter?: Maybe<Scalars['Int']['output']>;
   episode?: Maybe<Scalars['Int']['output']>;
   season?: Maybe<Scalars['Int']['output']>;
+  volume?: Maybe<Scalars['Int']['output']>;
 };
 
 export type UserMediaSummary = {
@@ -2372,17 +2373,9 @@ export type WorkoutInformation = {
   exercises: Array<ProcessedExercise>;
 };
 
-export type WorkoutListItem = {
-  endTime: Scalars['DateTime']['output'];
-  id: Scalars['String']['output'];
-  name: Scalars['String']['output'];
-  startTime: Scalars['DateTime']['output'];
-  summary: WorkoutSummary;
-};
-
 export type WorkoutListResults = {
   details: SearchDetails;
-  items: Array<WorkoutListItem>;
+  items: Array<Workout>;
 };
 
 /** The totals of a workout and the different bests achieved. */
@@ -2865,7 +2858,7 @@ export type UserMetadataDetailsQueryVariables = Exact<{
 }>;
 
 
-export type UserMetadataDetailsQuery = { userMetadataDetails: { mediaReason?: Array<UserToMediaReason> | null, hasInteracted: boolean, averageRating?: string | null, unitsConsumed?: number | null, seenByAllCount: number, seenByUserCount: number, collections: Array<{ id: string, name: string, userId: string }>, inProgress?: { id: string, progress: string, providerWatchedOn?: string | null, state: SeenState, startedOn?: string | null, finishedOn?: string | null, lastUpdatedOn: string, numTimesUpdated: number, showExtraInformation?: { episode: number, season: number } | null, podcastExtraInformation?: { episode: number } | null, animeExtraInformation?: { episode?: number | null } | null, mangaExtraInformation?: { chapter?: number | null, volume?: number | null } | null } | null, history: Array<{ id: string, progress: string, providerWatchedOn?: string | null, state: SeenState, startedOn?: string | null, finishedOn?: string | null, lastUpdatedOn: string, numTimesUpdated: number, showExtraInformation?: { episode: number, season: number } | null, podcastExtraInformation?: { episode: number } | null, animeExtraInformation?: { episode?: number | null } | null, mangaExtraInformation?: { chapter?: number | null, volume?: number | null } | null }>, reviews: Array<{ id: string, rating?: string | null, textOriginal?: string | null, textRendered?: string | null, isSpoiler: boolean, visibility: Visibility, postedOn: string, postedBy: { id: string, name: string }, comments: Array<{ id: string, text: string, createdOn: string, likedBy: Array<string>, user: { id: string, name: string } }>, showExtraInformation?: { episode: number, season: number } | null, podcastExtraInformation?: { episode: number } | null, animeExtraInformation?: { episode?: number | null } | null, mangaExtraInformation?: { chapter?: number | null, volume?: number | null } | null }>, nextEntry?: { season?: number | null, episode?: number | null, chapter?: number | null } | null, showProgress?: Array<{ timesSeen: number, seasonNumber: number, episodes: Array<{ episodeNumber: number, timesSeen: number }> }> | null, podcastProgress?: Array<{ episodeNumber: number, timesSeen: number }> | null } };
+export type UserMetadataDetailsQuery = { userMetadataDetails: { mediaReason?: Array<UserToMediaReason> | null, hasInteracted: boolean, averageRating?: string | null, unitsConsumed?: number | null, seenByAllCount: number, seenByUserCount: number, collections: Array<{ id: string, name: string, userId: string }>, inProgress?: { id: string, progress: string, providerWatchedOn?: string | null, state: SeenState, startedOn?: string | null, finishedOn?: string | null, lastUpdatedOn: string, numTimesUpdated: number, showExtraInformation?: { episode: number, season: number } | null, podcastExtraInformation?: { episode: number } | null, animeExtraInformation?: { episode?: number | null } | null, mangaExtraInformation?: { chapter?: number | null, volume?: number | null } | null } | null, history: Array<{ id: string, progress: string, providerWatchedOn?: string | null, state: SeenState, startedOn?: string | null, finishedOn?: string | null, lastUpdatedOn: string, numTimesUpdated: number, showExtraInformation?: { episode: number, season: number } | null, podcastExtraInformation?: { episode: number } | null, animeExtraInformation?: { episode?: number | null } | null, mangaExtraInformation?: { chapter?: number | null, volume?: number | null } | null }>, reviews: Array<{ id: string, rating?: string | null, textOriginal?: string | null, textRendered?: string | null, isSpoiler: boolean, visibility: Visibility, postedOn: string, postedBy: { id: string, name: string }, comments: Array<{ id: string, text: string, createdOn: string, likedBy: Array<string>, user: { id: string, name: string } }>, showExtraInformation?: { episode: number, season: number } | null, podcastExtraInformation?: { episode: number } | null, animeExtraInformation?: { episode?: number | null } | null, mangaExtraInformation?: { chapter?: number | null, volume?: number | null } | null }>, nextEntry?: { season?: number | null, volume?: number | null, episode?: number | null, chapter?: number | null } | null, showProgress?: Array<{ timesSeen: number, seasonNumber: number, episodes: Array<{ episodeNumber: number, timesSeen: number }> }> | null, podcastProgress?: Array<{ episodeNumber: number, timesSeen: number }> | null } };
 
 export type UserMetadataGroupDetailsQueryVariables = Exact<{
   metadataGroupId: Scalars['String']['input'];
@@ -3091,7 +3084,7 @@ export const PersonDetailsDocument = {"kind":"Document","definitions":[{"kind":"
 export const UserDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oidcIssuerId"}}]}}]}}]}}]} as unknown as DocumentNode<UserDetailsQuery, UserDetailsQueryVariables>;
 export const UserExerciseDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserExerciseDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"exerciseId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userExerciseDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"exerciseId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"exerciseId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"history"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"idx"}},{"kind":"Field","name":{"kind":"Name","value":"workoutId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"exerciseId"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedOn"}},{"kind":"Field","name":{"kind":"Name","value":"exerciseNumTimesInteracted"}},{"kind":"Field","name":{"kind":"Name","value":"exerciseExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lifetimeStats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"reps"}},{"kind":"Field","name":{"kind":"Name","value":"distance"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"personalBestsAchieved"}}]}},{"kind":"Field","name":{"kind":"Name","value":"personalBests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"sets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workoutId"}},{"kind":"Field","name":{"kind":"Name","value":"exerciseIdx"}},{"kind":"Field","name":{"kind":"Name","value":"setIdx"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Collection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}}]} as unknown as DocumentNode<UserExerciseDetailsQuery, UserExerciseDetailsQueryVariables>;
 export const UserMeasurementsListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserMeasurementsList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserMeasurementsListInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userMeasurementsList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"timestamp"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"comment"}},{"kind":"Field","name":{"kind":"Name","value":"stats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"bodyMassIndex"}},{"kind":"Field","name":{"kind":"Name","value":"totalBodyWater"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}},{"kind":"Field","name":{"kind":"Name","value":"leanBodyMass"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFat"}},{"kind":"Field","name":{"kind":"Name","value":"boneMass"}},{"kind":"Field","name":{"kind":"Name","value":"visceralFat"}},{"kind":"Field","name":{"kind":"Name","value":"waistCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHeightRatio"}},{"kind":"Field","name":{"kind":"Name","value":"hipCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHipRatio"}},{"kind":"Field","name":{"kind":"Name","value":"chestCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"thighCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bicepsCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"neckCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFatCaliper"}},{"kind":"Field","name":{"kind":"Name","value":"chestSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"abdominalSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"thighSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"basalMetabolicRate"}},{"kind":"Field","name":{"kind":"Name","value":"totalDailyEnergyExpenditure"}},{"kind":"Field","name":{"kind":"Name","value":"calories"}},{"kind":"Field","name":{"kind":"Name","value":"custom"}}]}}]}}]}}]} as unknown as DocumentNode<UserMeasurementsListQuery, UserMeasurementsListQueryVariables>;
-export const UserMetadataDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserMetadataDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userMetadataDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"metadataId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mediaReason"}},{"kind":"Field","name":{"kind":"Name","value":"hasInteracted"}},{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inProgress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"history"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"averageRating"}},{"kind":"Field","name":{"kind":"Name","value":"unitsConsumed"}},{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"seenByAllCount"}},{"kind":"Field","name":{"kind":"Name","value":"seenByUserCount"}},{"kind":"Field","name":{"kind":"Name","value":"nextEntry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showProgress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"timesSeen"}},{"kind":"Field","name":{"kind":"Name","value":"seasonNumber"}},{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodeNumber"}},{"kind":"Field","name":{"kind":"Name","value":"timesSeen"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastProgress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodeNumber"}},{"kind":"Field","name":{"kind":"Name","value":"timesSeen"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenAnimeExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenMangaExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"chapter"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Collection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Seen"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"progress"}},{"kind":"Field","name":{"kind":"Name","value":"providerWatchedOn"}},{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"startedOn"}},{"kind":"Field","name":{"kind":"Name","value":"finishedOn"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedOn"}},{"kind":"Field","name":{"kind":"Name","value":"numTimesUpdated"}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ReviewItemPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ReviewItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rating"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"postedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"likedBy"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}}]} as unknown as DocumentNode<UserMetadataDetailsQuery, UserMetadataDetailsQueryVariables>;
+export const UserMetadataDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserMetadataDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userMetadataDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"metadataId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"metadataId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mediaReason"}},{"kind":"Field","name":{"kind":"Name","value":"hasInteracted"}},{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inProgress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"history"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"averageRating"}},{"kind":"Field","name":{"kind":"Name","value":"unitsConsumed"}},{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"seenByAllCount"}},{"kind":"Field","name":{"kind":"Name","value":"seenByUserCount"}},{"kind":"Field","name":{"kind":"Name","value":"nextEntry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showProgress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"timesSeen"}},{"kind":"Field","name":{"kind":"Name","value":"seasonNumber"}},{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodeNumber"}},{"kind":"Field","name":{"kind":"Name","value":"timesSeen"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastProgress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodeNumber"}},{"kind":"Field","name":{"kind":"Name","value":"timesSeen"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenAnimeExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenMangaExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"chapter"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Collection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Seen"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"progress"}},{"kind":"Field","name":{"kind":"Name","value":"providerWatchedOn"}},{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"startedOn"}},{"kind":"Field","name":{"kind":"Name","value":"finishedOn"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedOn"}},{"kind":"Field","name":{"kind":"Name","value":"numTimesUpdated"}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ReviewItemPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ReviewItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rating"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"postedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"likedBy"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}}]} as unknown as DocumentNode<UserMetadataDetailsQuery, UserMetadataDetailsQueryVariables>;
 export const UserMetadataGroupDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserMetadataGroupDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"metadataGroupId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userMetadataGroupDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"metadataGroupId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"metadataGroupId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionPart"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenAnimeExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenMangaExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"chapter"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ReviewItemPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ReviewItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rating"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"postedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"likedBy"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Collection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}}]} as unknown as DocumentNode<UserMetadataGroupDetailsQuery, UserMetadataGroupDetailsQueryVariables>;
 export const UserPersonDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserPersonDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"personId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userPersonDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"personId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"personId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenAnimeExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenMangaExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"chapter"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CollectionPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Collection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"userId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ReviewItemPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ReviewItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rating"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"postedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"likedBy"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}}]} as unknown as DocumentNode<UserPersonDetailsQuery, UserPersonDetailsQueryVariables>;
 export const UserPreferencesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserPreferences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userPreferences"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"general"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reviewScale"}},{"kind":"Field","name":{"kind":"Name","value":"displayNsfw"}},{"kind":"Field","name":{"kind":"Name","value":"disableVideos"}},{"kind":"Field","name":{"kind":"Name","value":"persistQueries"}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"}},{"kind":"Field","name":{"kind":"Name","value":"disableReviews"}},{"kind":"Field","name":{"kind":"Name","value":"disableWatchProviders"}},{"kind":"Field","name":{"kind":"Name","value":"disableYankIntegrations"}},{"kind":"Field","name":{"kind":"Name","value":"disableNavigationAnimation"}},{"kind":"Field","name":{"kind":"Name","value":"dashboard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"section"}},{"kind":"Field","name":{"kind":"Name","value":"hidden"}},{"kind":"Field","name":{"kind":"Name","value":"numElements"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"measurements"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"custom"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"dataType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inbuilt"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"bodyMassIndex"}},{"kind":"Field","name":{"kind":"Name","value":"totalBodyWater"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}},{"kind":"Field","name":{"kind":"Name","value":"leanBodyMass"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFat"}},{"kind":"Field","name":{"kind":"Name","value":"boneMass"}},{"kind":"Field","name":{"kind":"Name","value":"visceralFat"}},{"kind":"Field","name":{"kind":"Name","value":"waistCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHeightRatio"}},{"kind":"Field","name":{"kind":"Name","value":"hipCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"waistToHipRatio"}},{"kind":"Field","name":{"kind":"Name","value":"chestCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"thighCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bicepsCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"neckCircumference"}},{"kind":"Field","name":{"kind":"Name","value":"bodyFatCaliper"}},{"kind":"Field","name":{"kind":"Name","value":"chestSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"abdominalSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"thighSkinfold"}},{"kind":"Field","name":{"kind":"Name","value":"basalMetabolicRate"}},{"kind":"Field","name":{"kind":"Name","value":"totalDailyEnergyExpenditure"}},{"kind":"Field","name":{"kind":"Name","value":"calories"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"exercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"saveHistory"}},{"kind":"Field","name":{"kind":"Name","value":"unitSystem"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"notifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toSend"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}},{"kind":"Field","name":{"kind":"Name","value":"featuresEnabled"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"others"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"calendar"}},{"kind":"Field","name":{"kind":"Name","value":"collections"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"workouts"}},{"kind":"Field","name":{"kind":"Name","value":"measurements"}}]}},{"kind":"Field","name":{"kind":"Name","value":"media"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"anime"}},{"kind":"Field","name":{"kind":"Name","value":"audioBook"}},{"kind":"Field","name":{"kind":"Name","value":"book"}},{"kind":"Field","name":{"kind":"Name","value":"manga"}},{"kind":"Field","name":{"kind":"Name","value":"movie"}},{"kind":"Field","name":{"kind":"Name","value":"podcast"}},{"kind":"Field","name":{"kind":"Name","value":"show"}},{"kind":"Field","name":{"kind":"Name","value":"videoGame"}},{"kind":"Field","name":{"kind":"Name","value":"visualNovel"}},{"kind":"Field","name":{"kind":"Name","value":"people"}},{"kind":"Field","name":{"kind":"Name","value":"groups"}},{"kind":"Field","name":{"kind":"Name","value":"genres"}}]}}]}}]}}]}}]} as unknown as DocumentNode<UserPreferencesQuery, UserPreferencesQueryVariables>;
diff --git a/libs/graphql/src/backend/queries/UserMetadataDetails.gql b/libs/graphql/src/backend/queries/UserMetadataDetails.gql
index 11b3311daa..d0b98ff424 100644
--- a/libs/graphql/src/backend/queries/UserMetadataDetails.gql
+++ b/libs/graphql/src/backend/queries/UserMetadataDetails.gql
@@ -20,6 +20,7 @@ query UserMetadataDetails($metadataId: String!) {
     seenByUserCount
     nextEntry {
       season
+      volume
       episode
       chapter
     }
diff --git a/libs/ts-utils/package.json b/libs/ts-utils/package.json
index 8d3760b8b6..52ab481f47 100644
--- a/libs/ts-utils/package.json
+++ b/libs/ts-utils/package.json
@@ -3,11 +3,11 @@
 	"main": "src/index.ts",
 	"dependencies": {
 		"@ryot/generated": "workspace:*",
-		"dayjs": "1.11.11",
+		"dayjs": "1.11.12",
 		"humanize-duration-ts": "2.1.1",
 		"lodash": "4.17.21"
 	},
 	"devDependencies": {
-		"@types/lodash": "4.17.6"
+		"@types/lodash": "4.17.7"
 	}
 }
diff --git a/package.json b/package.json
index 5a59f7b0a9..3d0b5cc42a 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,6 @@
 	"devDependencies": {
 		"@biomejs/biome": "1.8.3",
 		"tsconfig-moon": "1.3.0",
-		"typescript": "5.5.3"
+		"typescript": "5.5.4"
 	}
 }
diff --git a/yarn.lock b/yarn.lock
index cd4ce4a715..232dc9b69e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2851,35 +2851,35 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@mantine/carousel@npm:7.11.1":
-  version: 7.11.1
-  resolution: "@mantine/carousel@npm:7.11.1"
+"@mantine/carousel@npm:7.11.2":
+  version: 7.11.2
+  resolution: "@mantine/carousel@npm:7.11.2"
   peerDependencies:
-    "@mantine/core": 7.11.1
-    "@mantine/hooks": 7.11.1
+    "@mantine/core": 7.11.2
+    "@mantine/hooks": 7.11.2
     embla-carousel-react: ">=7.0.0"
     react: ^18.2.0
     react-dom: ^18.2.0
-  checksum: 10/ae31b4b8e7c62b89e0a58fbd39aaa1c329f43e469b3dd6b30ae0163b9aebe602aa55787b9d164a7d79c6306fb832f2f9117a9b6ef29a7e5d1dc614718510802b
+  checksum: 10/45f041958ca734a8ffffe5cc42ae92264b4ab8a532494d13c2c7fd3b279bcce4fc991e75344dd6f0a77773e1554d1a48feafb3178cb24bed1c00d90f89c65441
   languageName: node
   linkType: hard
 
-"@mantine/charts@npm:7.11.1":
-  version: 7.11.1
-  resolution: "@mantine/charts@npm:7.11.1"
+"@mantine/charts@npm:7.11.2":
+  version: 7.11.2
+  resolution: "@mantine/charts@npm:7.11.2"
   peerDependencies:
-    "@mantine/core": 7.11.1
-    "@mantine/hooks": 7.11.1
+    "@mantine/core": 7.11.2
+    "@mantine/hooks": 7.11.2
     react: ^18.2.0
     react-dom: ^18.2.0
     recharts: ^2.10.3
-  checksum: 10/b9b81d7061c89f5f035d3abaa9b618bcd82f76096f9c16942ca752af7d4ad71801b91ffd754d30fc9139e1825e48c1669df9b4cebeb26d29ede7095fbb475fce
+  checksum: 10/199e187ae3ca9299b2aea3893f4c99e94f016e9fdca2dd7d6611e23d7e26de605bcfda826d6966983864934f97488ad983473d36d2e63a4ce87b21e5cef221ad
   languageName: node
   linkType: hard
 
-"@mantine/core@npm:7.11.1":
-  version: 7.11.1
-  resolution: "@mantine/core@npm:7.11.1"
+"@mantine/core@npm:7.11.2":
+  version: 7.11.2
+  resolution: "@mantine/core@npm:7.11.2"
   dependencies:
     "@floating-ui/react": "npm:^0.26.9"
     clsx: "npm:^2.1.1"
@@ -2888,70 +2888,70 @@ __metadata:
     react-textarea-autosize: "npm:8.5.3"
     type-fest: "npm:^4.12.0"
   peerDependencies:
-    "@mantine/hooks": 7.11.1
+    "@mantine/hooks": 7.11.2
     react: ^18.2.0
     react-dom: ^18.2.0
-  checksum: 10/6eec3f859d4954250ed691a6b41ef50941236339e613e831b4e6e3a30d05994ee1492f77a849c7cd0084e7160eb9239f8a03ee1d7326a06350747d288b7319bf
+  checksum: 10/b8706cff0426b8274f4f984c00954e4e9054fa26bc65305c499822a8f5e6b20aced8a46a266161a7eec8c3539e540d306114392611dcb4d682cca8bd60f8362a
   languageName: node
   linkType: hard
 
-"@mantine/dates@npm:7.11.1":
-  version: 7.11.1
-  resolution: "@mantine/dates@npm:7.11.1"
+"@mantine/dates@npm:7.11.2":
+  version: 7.11.2
+  resolution: "@mantine/dates@npm:7.11.2"
   dependencies:
     clsx: "npm:^2.1.1"
   peerDependencies:
-    "@mantine/core": 7.11.1
-    "@mantine/hooks": 7.11.1
+    "@mantine/core": 7.11.2
+    "@mantine/hooks": 7.11.2
     dayjs: ">=1.0.0"
     react: ^18.2.0
     react-dom: ^18.2.0
-  checksum: 10/a4a0fd032e77350bbe50ff041b1e4c9a02a07360dadb577f96e1a6e69ef524e70bdbc2324ffaf84b45929247fc163e9f56ce3462d5067ed3cc4a8d03a9a62514
+  checksum: 10/039cd3e6187ff9aef71978bba2acc5948c0b1c760a28e25630c4d8c64b20367b75dd292ec26498957ce95bc76e713bb372287f7534425a73d9f582418a0e1a15
   languageName: node
   linkType: hard
 
-"@mantine/form@npm:7.11.1":
-  version: 7.11.1
-  resolution: "@mantine/form@npm:7.11.1"
+"@mantine/form@npm:7.11.2":
+  version: 7.11.2
+  resolution: "@mantine/form@npm:7.11.2"
   dependencies:
     fast-deep-equal: "npm:^3.1.3"
     klona: "npm:^2.0.6"
   peerDependencies:
     react: ^18.2.0
-  checksum: 10/196bf3d9b5a699536b431e57fb01548d090e441546eebbe2910862c158a0646c592adcbaedeb352be136caa813b58ba2c471f25613812219ed1ffcdcad6fa5d7
+  checksum: 10/045f3ca1c7985a23ce7701a4cb8cb74521bd2e6fc3de8cce0c418c2a2991a8ff4d0208b07a38146cb815099af7d2d735759d790391529ec8da7c03ff0250c3a8
   languageName: node
   linkType: hard
 
-"@mantine/hooks@npm:7.11.1":
-  version: 7.11.1
-  resolution: "@mantine/hooks@npm:7.11.1"
+"@mantine/hooks@npm:7.11.2":
+  version: 7.11.2
+  resolution: "@mantine/hooks@npm:7.11.2"
   peerDependencies:
     react: ^18.2.0
-  checksum: 10/7e26298996720d12d118bc34091334b77475d4b2db1989f24896bcb0efe87304e3fc58d85bc73cfb08ab5dd947046d133d7fc7e6aca6dc104d4ec1b1c4f206fd
+  checksum: 10/3746b98fc9108cf06c5007b96bdcccbe66fad0845781d1cd9d4d2fbbbdda8221ad0736952f5a7c1fe47fe1a9131ed2a5ec49e224bce7cde61a662551c23ee391
   languageName: node
   linkType: hard
 
-"@mantine/notifications@npm:7.11.1":
-  version: 7.11.1
-  resolution: "@mantine/notifications@npm:7.11.1"
+"@mantine/notifications@npm:7.11.2":
+  version: 7.11.2
+  resolution: "@mantine/notifications@npm:7.11.2"
   dependencies:
-    "@mantine/store": "npm:7.11.1"
+    "@mantine/store": "npm:7.11.2"
     react-transition-group: "npm:4.4.5"
   peerDependencies:
-    "@mantine/core": 7.11.1
-    "@mantine/hooks": 7.11.1
+    "@mantine/core": 7.11.2
+    "@mantine/hooks": 7.11.2
     react: ^18.2.0
     react-dom: ^18.2.0
-  checksum: 10/72d012ad90134b655c99ae907de451d564ce0d516037c0643ed0c73d6fa7224a35d2710d5c71eda95793bbb58d75701dd19b73800ebe9ca6be204bc7593a8d2a
+  checksum: 10/1125b6a2323c271f77221b7a2ece70db33b700013b1fd5ec4808d9eeef45e68cef0d4386c0284f5938b731ab18184d6eb9109c331b2b0b3a8824e69306725125
   languageName: node
   linkType: hard
 
-"@mantine/store@npm:7.11.1":
-  version: 7.11.1
-  resolution: "@mantine/store@npm:7.11.1"
+"@mantine/store@npm:7.11.2":
+  version: 7.11.2
+  resolution: "@mantine/store@npm:7.11.2"
   peerDependencies:
     react: ^18.2.0
-  checksum: 10/b362d03f0335f64e136001915eda7b2f2bca5635e597de403ab5e589a2098f0a999ef57830167fc6d3e06447637efea212b2d2b5a085b7254feae5d3b7fc6838
+  checksum: 10/e0a07078cd79fd61e79bdb9a6d4ac81ad0446048749b299ed7c1dc17beecb21f0c8c5dc67b316fbfd3679d0b9ee1d71b8cddb3d377dd12b9d30116e5a8751c02
   languageName: node
   linkType: hard
 
@@ -3144,9 +3144,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@remix-run/dev@npm:2.10.2":
-  version: 2.10.2
-  resolution: "@remix-run/dev@npm:2.10.2"
+"@remix-run/dev@npm:2.10.3":
+  version: 2.10.3
+  resolution: "@remix-run/dev@npm:2.10.3"
   dependencies:
     "@babel/core": "npm:^7.21.8"
     "@babel/generator": "npm:^7.21.5"
@@ -3158,9 +3158,9 @@ __metadata:
     "@babel/types": "npm:^7.22.5"
     "@mdx-js/mdx": "npm:^2.3.0"
     "@npmcli/package-json": "npm:^4.0.1"
-    "@remix-run/node": "npm:2.10.2"
-    "@remix-run/router": "npm:1.17.1"
-    "@remix-run/server-runtime": "npm:2.10.2"
+    "@remix-run/node": "npm:2.10.3"
+    "@remix-run/router": "npm:1.18.0"
+    "@remix-run/server-runtime": "npm:2.10.3"
     "@types/mdx": "npm:^2.0.5"
     "@vanilla-extract/integration": "npm:^6.2.0"
     arg: "npm:^5.0.1"
@@ -3202,8 +3202,8 @@ __metadata:
     tsconfig-paths: "npm:^4.0.0"
     ws: "npm:^7.4.5"
   peerDependencies:
-    "@remix-run/react": ^2.10.2
-    "@remix-run/serve": ^2.10.2
+    "@remix-run/react": ^2.10.3
+    "@remix-run/serve": ^2.10.3
     typescript: ^5.1.0
     vite: ^5.1.0
     wrangler: ^3.28.2
@@ -3218,30 +3218,30 @@ __metadata:
       optional: true
   bin:
     remix: dist/cli.js
-  checksum: 10/774def480c572f914ae9189b8339903afe31c317a531c2e2ef3a6885463d780b57c9a65a940348a23560dbe39e6c9eeb61943dd8b3e582d9519d3190dc3eed39
+  checksum: 10/44e5429ff2efd35a882d4050b4485b4b28b9bd2a74b394b7d2442ba2df164e2b2539af3db4501e628bba2888c08d39324bed600f5117e5d3184650faaf9a53a1
   languageName: node
   linkType: hard
 
-"@remix-run/express@npm:2.10.2":
-  version: 2.10.2
-  resolution: "@remix-run/express@npm:2.10.2"
+"@remix-run/express@npm:2.10.3":
+  version: 2.10.3
+  resolution: "@remix-run/express@npm:2.10.3"
   dependencies:
-    "@remix-run/node": "npm:2.10.2"
+    "@remix-run/node": "npm:2.10.3"
   peerDependencies:
     express: ^4.19.2
     typescript: ^5.1.0
   peerDependenciesMeta:
     typescript:
       optional: true
-  checksum: 10/9ca184d9825a6b780c59ddf356a6e60e312a787098e462867cd8dee9cff6b9a86ebe74e8a29b52ecd4147439c99490a8acd16316daf268bb04c5ae3a63a90f2a
+  checksum: 10/b635fffa1beee32cad4603427766962728106a9daca081caf8538f1831c670a83bbfdfe91b314655c762e88d6ab6987f8d9e544dbe2189922f41b235aa4828c7
   languageName: node
   linkType: hard
 
-"@remix-run/node@npm:2.10.2":
-  version: 2.10.2
-  resolution: "@remix-run/node@npm:2.10.2"
+"@remix-run/node@npm:2.10.3":
+  version: 2.10.3
+  resolution: "@remix-run/node@npm:2.10.3"
   dependencies:
-    "@remix-run/server-runtime": "npm:2.10.2"
+    "@remix-run/server-runtime": "npm:2.10.3"
     "@remix-run/web-fetch": "npm:^4.4.2"
     "@web3-storage/multipart-parser": "npm:^1.0.0"
     cookie-signature: "npm:^1.1.0"
@@ -3253,18 +3253,18 @@ __metadata:
   peerDependenciesMeta:
     typescript:
       optional: true
-  checksum: 10/8a092a6d7e0bfd9a2245882cf916a2097fbb3fa20848b30f038d435d0334f238bf39afc0684be4b48afe4f18d11ac6601d8e7bc54afa8057122444b896473425
+  checksum: 10/70fa59c6ee56f64f89c93a844a18b8781386262d2f49edcd34c3e14f2ec6aeae90070fa8f35213133267e73ec91719efc35be38310b97ae0a0e5c738ac4718f7
   languageName: node
   linkType: hard
 
-"@remix-run/react@npm:2.10.2":
-  version: 2.10.2
-  resolution: "@remix-run/react@npm:2.10.2"
+"@remix-run/react@npm:2.10.3":
+  version: 2.10.3
+  resolution: "@remix-run/react@npm:2.10.3"
   dependencies:
-    "@remix-run/router": "npm:1.17.1"
-    "@remix-run/server-runtime": "npm:2.10.2"
-    react-router: "npm:6.24.1"
-    react-router-dom: "npm:6.24.1"
+    "@remix-run/router": "npm:1.18.0"
+    "@remix-run/server-runtime": "npm:2.10.3"
+    react-router: "npm:6.25.0"
+    react-router-dom: "npm:6.25.0"
     turbo-stream: "npm:2.2.0"
   peerDependencies:
     react: ^18.0.0
@@ -3273,23 +3273,23 @@ __metadata:
   peerDependenciesMeta:
     typescript:
       optional: true
-  checksum: 10/9d3ecf6b53ff5fc4be9a65c374893f1e9e327f2249da5fd3d93d598b39154e066c7fbb4656243f923d76f1cdc5aaaf92095e91a615f37db6c8dd124de90dfcf8
+  checksum: 10/0766017c3b9497f8d6ee51ab643a9fe85cc65dd50e6afa8c4601bfa4d72fc4dc5ca42711e46aff9f069ca002bd1a583a86d1278007d70144c16552568f5e7394
   languageName: node
   linkType: hard
 
-"@remix-run/router@npm:1.17.1":
-  version: 1.17.1
-  resolution: "@remix-run/router@npm:1.17.1"
-  checksum: 10/5efc598626cd81688ac26e0abd08204b980831ead8cd2c4b8a27e0c169ee4777fc609fa289c093b93efc3a1e335304698c6961276c2309348444ec7209836c83
+"@remix-run/router@npm:1.18.0":
+  version: 1.18.0
+  resolution: "@remix-run/router@npm:1.18.0"
+  checksum: 10/f878cf246b94368f431a51363f1d33dc35ad11cb910d930476d988825b024a152de87a7f74f0891c3e7182228f892c7f64f94409aae27084c320338dee82caa1
   languageName: node
   linkType: hard
 
-"@remix-run/serve@npm:2.10.2":
-  version: 2.10.2
-  resolution: "@remix-run/serve@npm:2.10.2"
+"@remix-run/serve@npm:2.10.3":
+  version: 2.10.3
+  resolution: "@remix-run/serve@npm:2.10.3"
   dependencies:
-    "@remix-run/express": "npm:2.10.2"
-    "@remix-run/node": "npm:2.10.2"
+    "@remix-run/express": "npm:2.10.3"
+    "@remix-run/node": "npm:2.10.3"
     chokidar: "npm:^3.5.3"
     compression: "npm:^1.7.4"
     express: "npm:^4.19.2"
@@ -3298,15 +3298,15 @@ __metadata:
     source-map-support: "npm:^0.5.21"
   bin:
     remix-serve: dist/cli.js
-  checksum: 10/fe099b30eb567bdbf6cf99886b8265c30d43db0bfb4862db805c565203e479b8a13ad3442eaa7bdc6ab1acafe6b0cc7765b056a9c5f204a309314286295f92a1
+  checksum: 10/05c179518f4c1fe853077d50759e9441434bab824e7b63a010d236bb411eba2f06aa50430938c91d2ab2270828b29023333e3b6083b038c08df44c334d3b51cf
   languageName: node
   linkType: hard
 
-"@remix-run/server-runtime@npm:2.10.2":
-  version: 2.10.2
-  resolution: "@remix-run/server-runtime@npm:2.10.2"
+"@remix-run/server-runtime@npm:2.10.3":
+  version: 2.10.3
+  resolution: "@remix-run/server-runtime@npm:2.10.3"
   dependencies:
-    "@remix-run/router": "npm:1.17.1"
+    "@remix-run/router": "npm:1.18.0"
     "@types/cookie": "npm:^0.6.0"
     "@web3-storage/multipart-parser": "npm:^1.0.0"
     cookie: "npm:^0.6.0"
@@ -3318,7 +3318,7 @@ __metadata:
   peerDependenciesMeta:
     typescript:
       optional: true
-  checksum: 10/a9906dd479f173676c7b72b7b019c8b13a67c38e80790ea4280b4748587e26d7ffaf1423b61c98888e88c087a139e8023e191ac0e21ae5e3165d990015ddc9ed
+  checksum: 10/7abcc23cb0585c591e2ed2e19fa87c18a0a572ba8cf1103268c4209beda047b574c22f4a76ec55c90af8522af1c7037b1588e33ba0538f3f136f547e399b6aa7
   languageName: node
   linkType: hard
 
@@ -3484,23 +3484,23 @@ __metadata:
     "@hello-pangea/dnd": "npm:16.6.0"
     "@ignisda/remix-routes": "npm:3.3.0"
     "@lukemorales/query-key-factory": "npm:1.3.4"
-    "@mantine/carousel": "npm:7.11.1"
-    "@mantine/charts": "npm:7.11.1"
-    "@mantine/core": "npm:7.11.1"
-    "@mantine/dates": "npm:7.11.1"
-    "@mantine/form": "npm:7.11.1"
-    "@mantine/hooks": "npm:7.11.1"
-    "@mantine/notifications": "npm:7.11.1"
-    "@remix-run/dev": "npm:2.10.2"
-    "@remix-run/node": "npm:2.10.2"
-    "@remix-run/react": "npm:2.10.2"
-    "@remix-run/serve": "npm:2.10.2"
+    "@mantine/carousel": "npm:7.11.2"
+    "@mantine/charts": "npm:7.11.2"
+    "@mantine/core": "npm:7.11.2"
+    "@mantine/dates": "npm:7.11.2"
+    "@mantine/form": "npm:7.11.2"
+    "@mantine/hooks": "npm:7.11.2"
+    "@mantine/notifications": "npm:7.11.2"
+    "@remix-run/dev": "npm:2.10.3"
+    "@remix-run/node": "npm:2.10.3"
+    "@remix-run/react": "npm:2.10.3"
+    "@remix-run/serve": "npm:2.10.3"
     "@ryot/generated": "workspace:*"
     "@ryot/graphql": "workspace:*"
     "@ryot/ts-utils": "workspace:*"
-    "@tabler/icons-react": "npm:3.10.0"
-    "@tanstack/react-query": "npm:5.50.1"
-    "@tanstack/react-query-devtools": "npm:5.50.1"
+    "@tabler/icons-react": "npm:3.11.0"
+    "@tanstack/react-query": "npm:5.51.11"
+    "@tanstack/react-query-devtools": "npm:5.51.11"
     "@types/cookie": "npm:0.6.0"
     "@types/crypto-js": "npm:4.2.2"
     "@types/howler": "npm:2.2.11"
@@ -3511,7 +3511,7 @@ __metadata:
     buffer: "npm:6.0.3"
     clsx: "npm:2.1.1"
     cookie: "npm:0.6.0"
-    dayjs: "npm:1.11.11"
+    dayjs: "npm:1.11.12"
     dotenv: "npm:16.4.5"
     embla-carousel-react: "npm:7.1.0"
     graphql: "npm:16.9.0"
@@ -3519,17 +3519,17 @@ __metadata:
     howler: "npm:2.2.4"
     humanize-duration-ts: "npm:2.1.1"
     immer: "npm:10.1.1"
-    isbot: "npm:5.1.12"
-    jotai: "npm:2.9.0"
+    isbot: "npm:5.1.13"
+    jotai: "npm:2.9.1"
     js-cookie: "npm:3.0.5"
-    mantine-datatable: "npm:7.11.1"
+    mantine-datatable: "npm:7.11.2"
     postcss: "npm:8.4.39"
-    postcss-preset-mantine: "npm:1.15.0"
+    postcss-preset-mantine: "npm:1.17.0"
     postcss-simple-vars: "npm:7.0.1"
     react: "npm:18.3.1"
     react-confirm: "npm:0.3.0-7"
     react-dom: "npm:18.3.1"
-    react-virtuoso: "npm:4.7.11"
+    react-virtuoso: "npm:4.7.13"
     react-webcam: "npm:7.2.0"
     recharts: "npm:2.12.7"
     remix-utils: "npm:7.6.0"
@@ -3537,9 +3537,9 @@ __metadata:
     ts-essentials: "npm:10.0.1"
     ts-pattern: "npm:5.2.0"
     typescript-plugin-css-modules: "npm:5.1.0"
-    ufo: "npm:1.5.3"
+    ufo: "npm:1.5.4"
     uuid: "npm:10.0.0"
-    vite: "npm:5.3.3"
+    vite: "npm:5.3.4"
     vite-tsconfig-paths: "npm:4.3.2"
     zod: "npm:3.23.8"
     zodix: "npm:0.4.4"
@@ -3572,7 +3572,7 @@ __metadata:
   dependencies:
     "@biomejs/biome": "npm:1.8.3"
     tsconfig-moon: "npm:1.3.0"
-    typescript: "npm:5.5.3"
+    typescript: "npm:5.5.4"
   languageName: unknown
   linkType: soft
 
@@ -3581,65 +3581,65 @@ __metadata:
   resolution: "@ryot/ts-utils@workspace:libs/ts-utils"
   dependencies:
     "@ryot/generated": "workspace:*"
-    "@types/lodash": "npm:4.17.6"
-    dayjs: "npm:1.11.11"
+    "@types/lodash": "npm:4.17.7"
+    dayjs: "npm:1.11.12"
     humanize-duration-ts: "npm:2.1.1"
     lodash: "npm:4.17.21"
   languageName: unknown
   linkType: soft
 
-"@tabler/icons-react@npm:3.10.0":
-  version: 3.10.0
-  resolution: "@tabler/icons-react@npm:3.10.0"
+"@tabler/icons-react@npm:3.11.0":
+  version: 3.11.0
+  resolution: "@tabler/icons-react@npm:3.11.0"
   dependencies:
-    "@tabler/icons": "npm:3.10.0"
+    "@tabler/icons": "npm:3.11.0"
   peerDependencies:
     react: ">= 16"
-  checksum: 10/f21f00791b2fe63bbb6da6e9f4a74064ef88f47e3896d83b311ef3d3fbaa9b5e7890c710796311670ecaeaecf27a57a3afe66f6b2c7a6f51793dcef3d7c02780
+  checksum: 10/1011b55f0a1efe327a863c6fdaf6522685d6492b3cddef0e8dfc2fa8326a88c661e13417f3b07a5710b4bbbde6c6830cb3c2309db81a7b20c0b2242f3e89a982
   languageName: node
   linkType: hard
 
-"@tabler/icons@npm:3.10.0":
-  version: 3.10.0
-  resolution: "@tabler/icons@npm:3.10.0"
-  checksum: 10/45a246f8d3bf84409b88a240b6c4da1283f909ddd3e637eb2125078f5cda4421af1938ec6ee6eb0d588d93a66a3567e2e8f8a94cbdbb088d540b7973fb156d73
+"@tabler/icons@npm:3.11.0":
+  version: 3.11.0
+  resolution: "@tabler/icons@npm:3.11.0"
+  checksum: 10/1815b5ba0b1016db4902248f5fbc79dff217b9f41ece950bfa0222cfd92d75740bc9d08ab227d7b9fee93e972df57c0c4d8da8b9769a92e065067aa7d8370a13
   languageName: node
   linkType: hard
 
-"@tanstack/query-core@npm:5.50.1":
-  version: 5.50.1
-  resolution: "@tanstack/query-core@npm:5.50.1"
-  checksum: 10/f89f74bb6381738b04223534b9c4df435de418a242abb86af4f6db3010c96f2addd38ee658db3f500ac42070e6b15cdd0177636d3b046312d03a3220b13cf277
+"@tanstack/query-core@npm:5.51.9":
+  version: 5.51.9
+  resolution: "@tanstack/query-core@npm:5.51.9"
+  checksum: 10/263f64a2e9448cc412fa9f29b0b77af1a5eeb66ecba6965548d4be97b7e99cca0dd194992b686426745030240764846d9d7e39e43d3ed64b97afb47f3a9661d7
   languageName: node
   linkType: hard
 
-"@tanstack/query-devtools@npm:5.50.1":
-  version: 5.50.1
-  resolution: "@tanstack/query-devtools@npm:5.50.1"
-  checksum: 10/56e4909e981650d25b149662587b4d7ae91201823572425c1c35830e578cd52c1361d93e15eba43d5a9f6d5000b42e22a6edced2e8b2484ebdcf96b5f98bd128
+"@tanstack/query-devtools@npm:5.51.9":
+  version: 5.51.9
+  resolution: "@tanstack/query-devtools@npm:5.51.9"
+  checksum: 10/ffc19f1a9b16edbb99dc8ef4fb00f6cbc6ac39d0fbdb7976d5b849005d8f1eb80081449bfe379fd1eb8a0aa1a0357b014b822a8f32a9d57644fbb14fb039d8fd
   languageName: node
   linkType: hard
 
-"@tanstack/react-query-devtools@npm:5.50.1":
-  version: 5.50.1
-  resolution: "@tanstack/react-query-devtools@npm:5.50.1"
+"@tanstack/react-query-devtools@npm:5.51.11":
+  version: 5.51.11
+  resolution: "@tanstack/react-query-devtools@npm:5.51.11"
   dependencies:
-    "@tanstack/query-devtools": "npm:5.50.1"
+    "@tanstack/query-devtools": "npm:5.51.9"
   peerDependencies:
-    "@tanstack/react-query": ^5.50.1
+    "@tanstack/react-query": ^5.51.11
     react: ^18 || ^19
-  checksum: 10/395049e2186516d063029eeddf49f79b86eb6c66c1cf926d0e705e90907dfeee3933701eda009cb848e9c4c77a285280db30ab891485baf796892c43f6fea85b
+  checksum: 10/2a868370db70ef37ce9185c8d1d01f837ffb15bb3c304ef3eab9261e7de07c67e8a5b09264ffbbd2c650bd33a790f80c3d0be67d528d66cab31718d31081b2d7
   languageName: node
   linkType: hard
 
-"@tanstack/react-query@npm:5.50.1":
-  version: 5.50.1
-  resolution: "@tanstack/react-query@npm:5.50.1"
+"@tanstack/react-query@npm:5.51.11":
+  version: 5.51.11
+  resolution: "@tanstack/react-query@npm:5.51.11"
   dependencies:
-    "@tanstack/query-core": "npm:5.50.1"
+    "@tanstack/query-core": "npm:5.51.9"
   peerDependencies:
     react: ^18.0.0
-  checksum: 10/ab70f213ae77f9d59172cb571c0832d3d443b85c6cfb6540d4a15235373d26af18dd5214cd1f62275cfbc54164f64f6f2ef5a85e7cb442639b411a41fb65aae6
+  checksum: 10/e840eb5f90fb6ec1693f98be651c5fa55a2885930015a2bf1f8699def596293eac79df8b3335e31b87af0a189d40597d35986379c930faf3496fc5061861d988
   languageName: node
   linkType: hard
 
@@ -3821,10 +3821,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/lodash@npm:4.17.6":
-  version: 4.17.6
-  resolution: "@types/lodash@npm:4.17.6"
-  checksum: 10/6d3a68b3e795381f4aaf946855134d24eeb348ad5d66e9a44461d30026da82b215d55b92b70486d811ca45d54d4ab956aa2dced37fd04e19d49afe160ae3da2e
+"@types/lodash@npm:4.17.7":
+  version: 4.17.7
+  resolution: "@types/lodash@npm:4.17.7"
+  checksum: 10/b8177f19cf962414a66989837481b13f546afc2e98e8d465bec59e6ac03a59c584eb7053ce511cde3a09c5f3096d22a5ae22cfb56b23f3b0da75b0743b6b1a44
   languageName: node
   linkType: hard
 
@@ -5359,10 +5359,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"dayjs@npm:1.11.11":
-  version: 1.11.11
-  resolution: "dayjs@npm:1.11.11"
-  checksum: 10/f03948b172fbeed229837965988d1d5bac99c72a31c28731a457303259439f2f36289186489ae140adbeb10f591a926908c8de5d81eb449a2edbf5cbd6e9e30c
+"dayjs@npm:1.11.12":
+  version: 1.11.12
+  resolution: "dayjs@npm:1.11.12"
+  checksum: 10/8ee7c1e14961fd08d40b788d0c0e930dc6288b3d32911bb911b2fb31bb703c262788164fbe678ee9e50e2a35268d667b8c8ba43fd1806771c1f404c300a2b428
   languageName: node
   linkType: hard
 
@@ -7511,10 +7511,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"isbot@npm:5.1.12":
-  version: 5.1.12
-  resolution: "isbot@npm:5.1.12"
-  checksum: 10/9f8f56a28e75a9628113b1c6da6cd2776728e215d1f384c8ea82a7e52005a1429c37ad61de0758514879272c6e74ba19fcfa80f7b09faef785edfb945824892a
+"isbot@npm:5.1.13":
+  version: 5.1.13
+  resolution: "isbot@npm:5.1.13"
+  checksum: 10/520989ff4b3ca0df4436cc79cb93aacd6043284cb8d2516c17a3753f725dcb063dc5e58d84e48da6dc71ac45b35fb33d7db1fb2d084faa7b87cb662a07e82de0
   languageName: node
   linkType: hard
 
@@ -7593,9 +7593,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"jotai@npm:2.9.0":
-  version: 2.9.0
-  resolution: "jotai@npm:2.9.0"
+"jotai@npm:2.9.1":
+  version: 2.9.1
+  resolution: "jotai@npm:2.9.1"
   peerDependencies:
     "@types/react": ">=17.0.0"
     react: ">=17.0.0"
@@ -7604,7 +7604,7 @@ __metadata:
       optional: true
     react:
       optional: true
-  checksum: 10/376b4b0ab927189b402cd4c832a85dfdc9de6d610b81d366d5aab065ea387b3e38f1a932942425bc600b1b33ae340f0fd1bad67dcd5078ae212aa28cd732ef4f
+  checksum: 10/6cbf4cbebe5fd2630cf31aadb1f51dfac02cf1461daf3542ea09d4f8b9229d387c8790e26ae12fd63a42541c68ca29248fa776e5fe38326ffac6c33dbd65e1c5
   languageName: node
   linkType: hard
 
@@ -8009,15 +8009,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"mantine-datatable@npm:7.11.1":
-  version: 7.11.1
-  resolution: "mantine-datatable@npm:7.11.1"
+"mantine-datatable@npm:7.11.2":
+  version: 7.11.2
+  resolution: "mantine-datatable@npm:7.11.2"
   peerDependencies:
     "@mantine/core": ">=7.8"
     "@mantine/hooks": ">=7.8"
     clsx: ">=2"
     react: ">=18.2"
-  checksum: 10/d9308f5f894da079e233c1317702e7ab4d57eb0be9b2c7de7ba808e33f737495136a902e083ac1272dd50a752b71aed9d1fd7329aba13de58630d0fe21639b43
+  checksum: 10/8a7f788b6e64fb86aa303c3a371f356bd71a139a4999f11ea97c15072718e6f36fa6ee121e135483c6fd5cae1e36c3a3e189a803039f603ffd700a03f09a508e
   languageName: node
   linkType: hard
 
@@ -9716,15 +9716,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"postcss-preset-mantine@npm:1.15.0":
-  version: 1.15.0
-  resolution: "postcss-preset-mantine@npm:1.15.0"
+"postcss-preset-mantine@npm:1.17.0":
+  version: 1.17.0
+  resolution: "postcss-preset-mantine@npm:1.17.0"
   dependencies:
     postcss-mixins: "npm:^9.0.4"
     postcss-nested: "npm:^6.0.1"
   peerDependencies:
     postcss: ">=8.0.0"
-  checksum: 10/a2539013970cd05965a62ba3a49ea1545914c67005f444e6d620c5abb90939158e47df5012e73d7d588ccf1c49ba71c91be7c1a891555eacb3e23c42ab7448b5
+  checksum: 10/1fd27b9e90f24765b2c99675387fba0831fc0a318aafd2fbbc41c5ab0cc65edef5ec06037bfb284c2e0300043f51d9eabd6f6d17f9a5afb8a434a36466cd6818
   languageName: node
   linkType: hard
 
@@ -10112,27 +10112,27 @@ __metadata:
   languageName: node
   linkType: hard
 
-"react-router-dom@npm:6.24.1":
-  version: 6.24.1
-  resolution: "react-router-dom@npm:6.24.1"
+"react-router-dom@npm:6.25.0":
+  version: 6.25.0
+  resolution: "react-router-dom@npm:6.25.0"
   dependencies:
-    "@remix-run/router": "npm:1.17.1"
-    react-router: "npm:6.24.1"
+    "@remix-run/router": "npm:1.18.0"
+    react-router: "npm:6.25.0"
   peerDependencies:
     react: ">=16.8"
     react-dom: ">=16.8"
-  checksum: 10/98eeeec3d36695b3d6d8000d8373ba3cefa9afc49ee44f646f3b721e8b47a80f5ce3737ad1f752cccf19caf01e370918750a4bc86a06a99e491731b454d5ec55
+  checksum: 10/4f74050c64980a5acd37b62dad2e877b0e757a641b310cb233e8772277e45d60c2e44a75d14572079fee60d5469002b98b46c569761a0b9b347d083804cb3b7b
   languageName: node
   linkType: hard
 
-"react-router@npm:6.24.1":
-  version: 6.24.1
-  resolution: "react-router@npm:6.24.1"
+"react-router@npm:6.25.0":
+  version: 6.25.0
+  resolution: "react-router@npm:6.25.0"
   dependencies:
-    "@remix-run/router": "npm:1.17.1"
+    "@remix-run/router": "npm:1.18.0"
   peerDependencies:
     react: ">=16.8"
-  checksum: 10/18ac968171dee286a2f067dc8568faf73c759f833e88e09f1b34ff6e9376d1fd5eade8697a86be83093225956b256b398d935ce2f681c1bf711fb3c058c19839
+  checksum: 10/7885b90e3abb574b2843b3efefe481ed87c1dba2a17ad301dc0df5e6e93c08c56ccbafc24ee2fc43650e11c1d7d0304762e7c98d43f35e756af76f2587b95b80
   languageName: node
   linkType: hard
 
@@ -10195,13 +10195,13 @@ __metadata:
   languageName: node
   linkType: hard
 
-"react-virtuoso@npm:4.7.11":
-  version: 4.7.11
-  resolution: "react-virtuoso@npm:4.7.11"
+"react-virtuoso@npm:4.7.13":
+  version: 4.7.13
+  resolution: "react-virtuoso@npm:4.7.13"
   peerDependencies:
     react: ">=16 || >=17 || >= 18"
     react-dom: ">=16 || >=17 || >= 18"
-  checksum: 10/5f6188063a280f3f60d2586d69ade83ed45ac2afc929964c11574257c4f6a8732a4812657f976e1b5a7298ea3faf37edffd50e10b933f8891e955b2dd168643c
+  checksum: 10/0cf91b50baaaecfd2ca117b284b3298f45fa847dfba047f2da67ba4a49120e1fd55770c34b37dca4177505e8d3b11e86f5cbe7492444e87fa81d00fb7cf74a69
   languageName: node
   linkType: hard
 
@@ -11664,23 +11664,23 @@ __metadata:
   languageName: node
   linkType: hard
 
-"typescript@npm:5.5.3":
-  version: 5.5.3
-  resolution: "typescript@npm:5.5.3"
+"typescript@npm:5.5.4":
+  version: 5.5.4
+  resolution: "typescript@npm:5.5.4"
   bin:
     tsc: bin/tsc
     tsserver: bin/tsserver
-  checksum: 10/11a867312419ed497929aafd2f1d28b2cd41810a5eb6c6e9e169559112e9ea073d681c121a29102e67cd4478d0a4ae37a306a5800f3717f59c4337e6a9bd5e8d
+  checksum: 10/1689ccafef894825481fc3d856b4834ba3cc185a9c2878f3c76a9a1ef81af04194849840f3c69e7961e2312771471bb3b460ca92561e1d87599b26c37d0ffb6f
   languageName: node
   linkType: hard
 
-"typescript@patch:typescript@npm%3A5.5.3#optional!builtin<compat/typescript>":
-  version: 5.5.3
-  resolution: "typescript@patch:typescript@npm%3A5.5.3#optional!builtin<compat/typescript>::version=5.5.3&hash=5adc0c"
+"typescript@patch:typescript@npm%3A5.5.4#optional!builtin<compat/typescript>":
+  version: 5.5.4
+  resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin<compat/typescript>::version=5.5.4&hash=5adc0c"
   bin:
     tsc: bin/tsc
     tsserver: bin/tsserver
-  checksum: 10/b61b8bb4b4d6a8a00f9d5f931f8c67070eed6ad11feabf4c41744a326987080bfc806a621596c70fbf2e5974eca3ed65bafeeeb22a078071bdfb51d8abd7c013
+  checksum: 10/2c065f0ef81855eac25c9b658a3c9da65ffc005260c12854c2286f40f3667e1b1ecf8bdbdd37b59aa0397920378ce7900bff8cb32e0f1c7af6fd86efc676718c
   languageName: node
   linkType: hard
 
@@ -11691,10 +11691,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"ufo@npm:1.5.3":
-  version: 1.5.3
-  resolution: "ufo@npm:1.5.3"
-  checksum: 10/2b30dddd873c643efecdb58cfe457183cd4d95937ccdacca6942c697b87a2c578232c25a5149fda85436696bf0fdbc213bf2b220874712bc3e58c0fb00a2c950
+"ufo@npm:1.5.4":
+  version: 1.5.4
+  resolution: "ufo@npm:1.5.4"
+  checksum: 10/a885ed421e656aea6ca64e9727b8118a9488715460b6f1a0f0427118adfe2f2830fe7c1d5bd9c5c754a332e6807516551cd663ea67ce9ed6a4e3edc739916335
   languageName: node
   linkType: hard
 
@@ -12174,9 +12174,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"vite@npm:5.3.3":
-  version: 5.3.3
-  resolution: "vite@npm:5.3.3"
+"vite@npm:5.3.4":
+  version: 5.3.4
+  resolution: "vite@npm:5.3.4"
   dependencies:
     esbuild: "npm:^0.21.3"
     fsevents: "npm:~2.3.3"
@@ -12210,7 +12210,7 @@ __metadata:
       optional: true
   bin:
     vite: bin/vite.js
-  checksum: 10/e7a094cefedad9e204b715588502118e07d1b9c00c617f55b810169181907f55144f0a82f650995d6a74f12e3695fca65afc348b475b91a81dcbd0274d30a088
+  checksum: 10/9eadb261be1f5f6335a67cb44a8803febd8b190202909385fd2ae7849991095ee13c5af914da53da48a421ee0901a8fedbb7519dc7d1b8ade3ea6e42bd9b2b39
   languageName: node
   linkType: hard
 


Test patch: