Repository: IgnisDa/ryot (language: rust)

Issue:
Fitness tracking feature requests
From a user on Discord:

> Is there a way to pause the workout timer once it starts?
> Can I also have the workout timer not start automatically but wait for me to click it? I might want to set things up a bit beforehand or pause it because I get a call or something.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 59ebeba157..440d372acb 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -146,6 +146,7 @@ jobs:
         env:
           TARGET: ${{ matrix.platform.target }}
         run: |
+          echo "sha=${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
           echo "version=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT"
           echo "docker-arch=${{ startsWith(matrix.platform.target, 'x86_64') && 'amd64' || 'arm64' }}" >> "$GITHUB_OUTPUT"
       - name: Extract rust toolchain
@@ -176,11 +177,12 @@ jobs:
           locked: false
       - name: Build
         env:
+          UNKEY_API_ID: ${{ secrets.UNKEY_API_ID }}
+          APP_COMMIT_SHA: ${{ steps.build.outputs.sha }}
           APP_VERSION: ${{ steps.build.outputs.version }}
-          DEFAULT_TMDB_ACCESS_TOKEN: ${{ secrets.DEFAULT_TMDB_ACCESS_TOKEN }}
-          DEFAULT_MAL_CLIENT_ID: ${{ secrets.DEFAULT_MAL_CLIENT_ID }}
           TRAKT_CLIENT_ID: ${{ secrets.TRAKT_CLIENT_ID }}
-          UNKEY_API_ID: ${{ secrets.UNKEY_API_ID }}
+          DEFAULT_MAL_CLIENT_ID: ${{ secrets.DEFAULT_MAL_CLIENT_ID }}
+          DEFAULT_TMDB_ACCESS_TOKEN: ${{ secrets.DEFAULT_TMDB_ACCESS_TOKEN }}
         run: |
           ${{ matrix.platform.command }} build --locked --target ${{ matrix.platform.target }} --release
       - name: Upload build artifact
diff --git a/Cargo.lock b/Cargo.lock
index 3b599bcd84..1e6811a658 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1140,6 +1140,7 @@ dependencies = [
  "config",
  "database-models",
  "dependent-models",
+ "env-utils",
  "sea-orm",
  "sea-query",
  "serde",
diff --git a/Cross.toml b/Cross.toml
index 4e0386df00..49c2fea27d 100644
--- a/Cross.toml
+++ b/Cross.toml
@@ -1,10 +1,11 @@
 [build.env]
 passthrough = [
     "APP_VERSION",
-    "DEFAULT_TMDB_ACCESS_TOKEN",
-    "DEFAULT_MAL_CLIENT_ID",
-    "TRAKT_CLIENT_ID",
+    "APP_COMMIT_SHA",
     "UNKEY_API_ID",
+    "TRAKT_CLIENT_ID",
+    "DEFAULT_MAL_CLIENT_ID",
+    "DEFAULT_TMDB_ACCESS_TOKEN",
 ]
 
 [build]
diff --git a/apps/backend/src/common.rs b/apps/backend/src/common.rs
index cf9534b7d6..92392e5082 100644
--- a/apps/backend/src/common.rs
+++ b/apps/backend/src/common.rs
@@ -10,10 +10,7 @@ use axum::{
     routing::{get, post, Router},
     Extension,
 };
-use background_models::{
-    ApplicationJob, HpApplicationJob, LpApplicationJob,
-    MpApplicationJob,
-};
+use background_models::{ApplicationJob, HpApplicationJob, LpApplicationJob, MpApplicationJob};
 use cache_service::CacheService;
 use collection_resolver::{CollectionMutation, CollectionQuery};
 use collection_service::CollectionService;
@@ -50,7 +47,6 @@ use user_service::UserService;
 
 /// All the services that are used by the app
 pub struct AppServices {
-    pub app_router: Router,
     pub fitness_service: Arc<FitnessService>,
     pub importer_service: Arc<ImporterService>,
     pub exporter_service: Arc<ExporterService>,
@@ -68,7 +64,7 @@ pub async fn create_app_services(
     lp_application_job: &MemoryStorage<LpApplicationJob>,
     mp_application_job: &MemoryStorage<MpApplicationJob>,
     hp_application_job: &MemoryStorage<HpApplicationJob>,
-) -> AppServices {
+) -> (Router, Arc<AppServices>) {
     let oidc_client = create_oidc_client(&config).await;
     let file_storage_service = Arc::new(FileStorageService::new(
         s3_client,
@@ -159,15 +155,17 @@ pub async fn create_app_services(
         ))
         .layer(cors);
 
-    AppServices {
+    (
         app_router,
-        fitness_service,
-        importer_service,
-        exporter_service,
-        statistics_service,
-        integration_service,
-        miscellaneous_service,
-    }
+        Arc::new(AppServices {
+            fitness_service,
+            importer_service,
+            exporter_service,
+            statistics_service,
+            integration_service,
+            miscellaneous_service,
+        }),
+    )
 }
 
 async fn create_oidc_client(config: &config::AppConfig) -> Option<CoreClient> {
diff --git a/apps/backend/src/job.rs b/apps/backend/src/job.rs
index 660d83116b..0dba84e36f 100644
--- a/apps/backend/src/job.rs
+++ b/apps/backend/src/job.rs
@@ -3,33 +3,34 @@ use std::sync::Arc;
 use apalis::prelude::*;
 use background_models::{HpApplicationJob, LpApplicationJob, MpApplicationJob, ScheduledJob};
 use common_utils::ryot_log;
-use exporter_service::ExporterService;
-use fitness_service::FitnessService;
-use importer_service::ImporterService;
-use integration_service::IntegrationService;
-use miscellaneous_service::MiscellaneousService;
-use statistics_service::StatisticsService;
 use traits::TraceOk;
 
+use crate::common::AppServices;
+
 pub async fn run_background_jobs(
     information: ScheduledJob,
-    misc_service: Data<Arc<MiscellaneousService>>,
+    app_services: Data<Arc<AppServices>>,
 ) -> Result<(), Error> {
     ryot_log!(debug, "Running job at {:#?}", information.0);
-    misc_service.perform_background_jobs().await.trace_ok();
+    app_services
+        .miscellaneous_service
+        .perform_background_jobs()
+        .await
+        .trace_ok();
     Ok(())
 }
 
 pub async fn run_frequent_jobs(
     _information: ScheduledJob,
-    fitness_service: Data<Arc<FitnessService>>,
-    integration_service: Data<Arc<IntegrationService>>,
+    app_services: Data<Arc<AppServices>>,
 ) -> Result<(), Error> {
-    integration_service
+    app_services
+        .integration_service
         .yank_integrations_data()
         .await
         .trace_ok();
-    fitness_service
+    app_services
+        .fitness_service
         .process_users_scheduled_for_workout_revision()
         .await
         .trace_ok();
@@ -38,30 +39,36 @@ pub async fn run_frequent_jobs(
 
 pub async fn perform_hp_application_job(
     information: HpApplicationJob,
-    misc_service: Data<Arc<MiscellaneousService>>,
-    statistics_service: Data<Arc<StatisticsService>>,
-    integration_service: Data<Arc<IntegrationService>>,
+    app_services: Data<Arc<AppServices>>,
 ) -> Result<(), Error> {
     ryot_log!(trace, "Started job {:?}", information);
     let status = match information {
         HpApplicationJob::SyncUserIntegrationsData(user_id) => {
-            integration_service
-                .yank_integrations_data_for_user(&user_id)
+            app_services
+                .integration_service
+                .sync_integrations_data_for_user(&user_id)
                 .await
         }
         HpApplicationJob::RecalculateUserActivitiesAndSummary(
             user_id,
             calculate_from_beginning,
         ) => {
-            statistics_service
+            app_services
+                .statistics_service
                 .calculate_user_activities_and_summary(&user_id, calculate_from_beginning)
                 .await
         }
         HpApplicationJob::ReviewPosted(event) => {
-            misc_service.handle_review_posted_event(event).await
+            app_services
+                .miscellaneous_service
+                .handle_review_posted_event(event)
+                .await
         }
         HpApplicationJob::BulkProgressUpdate(user_id, input) => {
-            misc_service.bulk_progress_update(user_id, input).await
+            app_services
+                .miscellaneous_service
+                .bulk_progress_update(user_id, input)
+                .await
         }
     };
     status.map_err(|e| Error::Failed(Arc::new(e.message.into())))
@@ -69,49 +76,68 @@ pub async fn perform_hp_application_job(
 
 pub async fn perform_mp_application_job(
     information: MpApplicationJob,
-    misc_service: Data<Arc<MiscellaneousService>>,
-    integration_service: Data<Arc<IntegrationService>>,
-    importer_service: Data<Arc<ImporterService>>,
-    exporter_service: Data<Arc<ExporterService>>,
-    fitness_service: Data<Arc<FitnessService>>,
+    app_services: Data<Arc<AppServices>>,
 ) -> Result<(), Error> {
     ryot_log!(trace, "Started job {:?}", information);
     let status = match information {
         MpApplicationJob::ImportFromExternalSource(user_id, input) => {
-            importer_service.perform_import(user_id, input).await
+            app_services
+                .importer_service
+                .perform_import(user_id, input)
+                .await
         }
         MpApplicationJob::ReviseUserWorkouts(user_id) => {
-            fitness_service.revise_user_workouts(user_id).await
+            app_services
+                .fitness_service
+                .revise_user_workouts(user_id)
+                .await
         }
         MpApplicationJob::UpdateMetadata(metadata_id) => {
-            misc_service
+            app_services
+                .miscellaneous_service
                 .update_metadata_and_notify_users(&metadata_id)
                 .await
         }
         MpApplicationJob::UpdatePerson(person_id) => {
-            misc_service
+            app_services
+                .miscellaneous_service
                 .update_person_and_notify_users(&person_id)
                 .await
         }
         MpApplicationJob::UpdateMetadataGroup(metadata_group_id) => {
-            misc_service.update_metadata_group(&metadata_group_id).await
+            app_services
+                .miscellaneous_service
+                .update_metadata_group(&metadata_group_id)
+                .await
+        }
+        MpApplicationJob::UpdateGithubExercises => {
+            app_services.fitness_service.update_github_exercises().await
         }
-        MpApplicationJob::UpdateGithubExercises => fitness_service.update_github_exercises().await,
         MpApplicationJob::RecalculateCalendarEvents => {
-            misc_service.recalculate_calendar_events().await
+            app_services
+                .miscellaneous_service
+                .recalculate_calendar_events()
+                .await
+        }
+        MpApplicationJob::PerformBackgroundTasks => {
+            app_services
+                .miscellaneous_service
+                .perform_background_jobs()
+                .await
+        }
+        MpApplicationJob::PerformExport(user_id) => {
+            app_services.exporter_service.perform_export(user_id).await
         }
-        MpApplicationJob::PerformBackgroundTasks => misc_service.perform_background_jobs().await,
-        MpApplicationJob::PerformExport(user_id) => exporter_service.perform_export(user_id).await,
         MpApplicationJob::UpdateExerciseLibrary => {
-            fitness_service.deploy_update_exercise_library_job().await
+            app_services
+                .fitness_service
+                .deploy_update_exercise_library_job()
+                .await
         }
         MpApplicationJob::SyncIntegrationsData => {
-            integration_service
-                .yank_integrations_data()
-                .await
-                .trace_ok();
-            integration_service
-                .sync_integrations_data_to_owned_collection()
+            app_services
+                .integration_service
+                .sync_integrations_data()
                 .await
         }
     };
@@ -120,21 +146,33 @@ pub async fn perform_mp_application_job(
 
 pub async fn perform_lp_application_job(
     information: LpApplicationJob,
-    misc_service: Data<Arc<MiscellaneousService>>,
-    integration_service: Data<Arc<IntegrationService>>,
+    app_services: Data<Arc<AppServices>>,
 ) -> Result<(), Error> {
     ryot_log!(trace, "Started job {:?}", information);
     let status = match information {
         LpApplicationJob::HandleAfterMediaSeenTasks(seen) => {
-            misc_service.handle_after_media_seen_tasks(seen).await
+            app_services
+                .miscellaneous_service
+                .handle_after_media_seen_tasks(seen)
+                .await
         }
         LpApplicationJob::HandleEntityAddedToCollectionEvent(collection_to_entity_id) => {
-            integration_service
+            app_services
+                .integration_service
                 .handle_entity_added_to_collection_event(collection_to_entity_id)
                 .await
         }
         LpApplicationJob::HandleOnSeenComplete(id) => {
-            integration_service.handle_on_seen_complete(id).await
+            app_services
+                .integration_service
+                .handle_on_seen_complete(id)
+                .await
+        }
+        LpApplicationJob::DeleteAllApplicationCache => {
+            app_services
+                .miscellaneous_service
+                .delete_all_application_cache()
+                .await
         }
     };
     status.map_err(|e| Error::Failed(Arc::new(e.message.into())))
diff --git a/apps/backend/src/main.rs b/apps/backend/src/main.rs
index b7a508da25..bc524717ce 100644
--- a/apps/backend/src/main.rs
+++ b/apps/backend/src/main.rs
@@ -4,7 +4,6 @@ use std::{
     path::PathBuf,
     str::FromStr,
     sync::{Arc, Mutex},
-    time::Duration,
 };
 
 use anyhow::{bail, Result};
@@ -17,7 +16,6 @@ use aws_sdk_s3::config::Region;
 use common_utils::{ryot_log, PROJECT_NAME, TEMP_DIR};
 use dependent_models::CompleteExport;
 use env_utils::APP_VERSION;
-use job::perform_lp_application_job;
 use logs_wheel::LogFileInitializer;
 use migrations::Migrator;
 use schematic::schema::{SchemaGenerator, TypeScriptRenderer, YamlTemplateRenderer};
@@ -26,15 +24,15 @@ use sea_orm_migration::MigratorTrait;
 use tokio::{
     join,
     net::TcpListener,
-    time::{sleep, Duration as TokioDuration},
+    time::{sleep, Duration},
 };
 use tracing_subscriber::{fmt, layer::SubscriberExt};
 
 use crate::{
     common::create_app_services,
     job::{
-        perform_hp_application_job, perform_mp_application_job, run_background_jobs,
-        run_frequent_jobs,
+        perform_hp_application_job, perform_lp_application_job, perform_mp_application_job,
+        run_background_jobs, run_frequent_jobs,
     },
 };
 
@@ -42,19 +40,20 @@ mod common;
 mod job;
 
 static BASE_DIR: &str = env!("CARGO_MANIFEST_DIR");
+static LOGGING_ENV_VAR: &str = "RUST_LOG";
 
 #[tokio::main]
 async fn main() -> Result<()> {
     #[cfg(debug_assertions)]
     dotenvy::dotenv().ok();
 
-    match env::var("RUST_LOG").ok() {
+    match env::var(LOGGING_ENV_VAR).ok() {
         Some(v) => {
             if !v.contains("sea_orm") {
-                env::set_var("RUST_LOG", format!("{},sea_orm=info", v));
+                env::set_var(LOGGING_ENV_VAR, format!("{},sea_orm=info", v));
             }
         }
-        None => env::set_var("RUST_LOG", "ryot=info,sea_orm=info"),
+        None => env::set_var(LOGGING_ENV_VAR, "ryot=info,sea_orm=info"),
     }
     init_tracing()?;
 
@@ -62,7 +61,7 @@ async fn main() -> Result<()> {
 
     let config = Arc::new(config::load_app_config()?);
     if config.server.sleep_before_startup_seconds > 0 {
-        let duration = TokioDuration::from_secs(config.server.sleep_before_startup_seconds);
+        let duration = Duration::from_secs(config.server.sleep_before_startup_seconds);
         ryot_log!(warn, "Sleeping for {:?} before starting up...", duration);
         sleep(duration).await;
     }
@@ -116,7 +115,7 @@ async fn main() -> Result<()> {
         .unwrap_or_else(|_| chrono_tz::Etc::GMT);
     ryot_log!(info, "Timezone: {}", tz);
 
-    let app_services = create_app_services(
+    let (app_router, app_services) = create_app_services(
         db,
         tz,
         s3_client,
@@ -169,7 +168,7 @@ async fn main() -> Result<()> {
             WorkerBuilder::new("daily_background_jobs")
                 .enable_tracing()
                 .catch_panic()
-                .data(app_services.miscellaneous_service.clone())
+                .data(app_services.clone())
                 .backend(
                     // every day
                     CronStream::new_with_timezone(Schedule::from_str("0 0 0 * * *").unwrap(), tz),
@@ -180,8 +179,7 @@ async fn main() -> Result<()> {
             WorkerBuilder::new("frequent_jobs")
                 .enable_tracing()
                 .catch_panic()
-                .data(app_services.integration_service.clone())
-                .data(app_services.fitness_service.clone())
+                .data(app_services.clone())
                 .backend(CronStream::new_with_timezone(
                     Schedule::from_str(&format!("0 */{} * * * *", sync_every_minutes)).unwrap(),
                     tz,
@@ -193,9 +191,7 @@ async fn main() -> Result<()> {
             WorkerBuilder::new("perform_hp_application_job")
                 .catch_panic()
                 .enable_tracing()
-                .data(app_services.statistics_service.clone())
-                .data(app_services.integration_service.clone())
-                .data(app_services.miscellaneous_service.clone())
+                .data(app_services.clone())
                 .backend(hp_application_job_storage)
                 .build_fn(perform_hp_application_job),
         )
@@ -205,11 +201,7 @@ async fn main() -> Result<()> {
                 .enable_tracing()
                 .rate_limit(5, Duration::new(5, 0))
                 .retry(RetryPolicy::retries(3))
-                .data(app_services.fitness_service.clone())
-                .data(app_services.exporter_service.clone())
-                .data(app_services.importer_service.clone())
-                .data(app_services.integration_service.clone())
-                .data(app_services.miscellaneous_service.clone())
+                .data(app_services.clone())
                 .backend(mp_application_job_storage)
                 .build_fn(perform_mp_application_job),
         )
@@ -218,14 +210,13 @@ async fn main() -> Result<()> {
                 .catch_panic()
                 .enable_tracing()
                 .rate_limit(20, Duration::new(5, 0))
-                .data(app_services.integration_service)
-                .data(app_services.miscellaneous_service)
+                .data(app_services)
                 .backend(lp_application_job_storage)
                 .build_fn(perform_lp_application_job),
         )
         .run();
 
-    let http = axum::serve(listener, app_services.app_router.into_make_service());
+    let http = axum::serve(listener, app_router.into_make_service());
 
     if disable_background_jobs {
         let _ = join!(http);
diff --git a/apps/frontend/app/components/common.tsx b/apps/frontend/app/components/common.tsx
index 394a6c1ad2..bad73d5629 100644
--- a/apps/frontend/app/components/common.tsx
+++ b/apps/frontend/app/components/common.tsx
@@ -1,6 +1,5 @@
-import { Carousel } from "@mantine/carousel";
-import "@mantine/carousel/styles.css";
 import { useAutoAnimate } from "@formkit/auto-animate/react";
+import { Carousel } from "@mantine/carousel";
 import {
 	ActionIcon,
 	Alert,
diff --git a/apps/frontend/app/components/fitness.tsx b/apps/frontend/app/components/fitness.tsx
index 1c7ada6ddf..aa0fea0a2f 100644
--- a/apps/frontend/app/components/fitness.tsx
+++ b/apps/frontend/app/components/fitness.tsx
@@ -95,8 +95,8 @@ export const displayWeightWithUnit = (
 ) => {
 	return new Intl.NumberFormat("en-us", {
 		style: "unit",
-		unit: unit === UserUnitSystem.Metric ? "kilogram" : "pound",
 		notation: compactNotation ? "compact" : undefined,
+		unit: unit === UserUnitSystem.Metric ? "kilogram" : "pound",
 	}).format(Number((data || 0).toString()));
 };
 
@@ -106,9 +106,11 @@ export const displayWeightWithUnit = (
 export const displayDistanceWithUnit = (
 	unit: UserUnitSystem,
 	data: string | number | null | undefined,
+	compactNotation?: boolean,
 ) => {
 	return new Intl.NumberFormat("en-us", {
 		style: "unit",
+		notation: compactNotation ? "compact" : undefined,
 		unit: unit === UserUnitSystem.Metric ? "kilometer" : "mile",
 	}).format(Number((data || 0).toString()));
 };
diff --git a/apps/frontend/app/components/media.tsx b/apps/frontend/app/components/media.tsx
index e0b4b9e832..2e66aaa9fc 100644
--- a/apps/frontend/app/components/media.tsx
+++ b/apps/frontend/app/components/media.tsx
@@ -10,8 +10,6 @@ import {
 	ThemeIcon,
 	Tooltip,
 } from "@mantine/core";
-import { $path } from "remix-routes";
-import "@mantine/dates/styles.css";
 import { useInViewport } from "@mantine/hooks";
 import { Form, Link } from "@remix-run/react";
 import {
@@ -34,6 +32,7 @@ import {
 } from "@tabler/icons-react";
 import { useQuery } from "@tanstack/react-query";
 import type { ReactNode } from "react";
+import { $path } from "remix-routes";
 import { match } from "ts-pattern";
 import { withQuery } from "ufo";
 import {
diff --git a/apps/frontend/app/lib/state/fitness.ts b/apps/frontend/app/lib/state/fitness.ts
index ca41587805..0f51dfffbb 100644
--- a/apps/frontend/app/lib/state/fitness.ts
+++ b/apps/frontend/app/lib/state/fitness.ts
@@ -11,6 +11,7 @@ import {
 	type UserWorkoutDetailsQuery,
 	type UserWorkoutSetRecord,
 	UserWorkoutTemplateDetailsDocument,
+	type WorkoutDuration,
 	type WorkoutInformation,
 	type WorkoutSetStatistic,
 	type WorkoutSupersetsInformation,
@@ -39,8 +40,10 @@ export type ExerciseSet = {
 	lot: SetLot;
 	rpe?: number | null;
 	confirmedAt: string | null;
+	restTimerStartedAt?: string;
 	statistic: WorkoutSetStatistic;
 	note?: boolean | string | null;
+	displayRestTimeTrigger?: boolean;
 	restTimer?: { duration: number; hasElapsed?: boolean } | null;
 };
 
@@ -75,14 +78,15 @@ export type InProgressWorkout = {
 	endTime?: string;
 	startTime: string;
 	templateId?: string;
+	images: Array<Media>;
 	videos: Array<string>;
 	repeatedFrom?: string;
 	supersets: Superset[];
-	images: Array<Media>;
 	updateWorkoutId?: string;
 	exercises: Array<Exercise>;
 	replacingExerciseIdx?: number;
 	updateWorkoutTemplateId?: string;
+	durations: Array<WorkoutDuration>;
 	currentActionOrCompleted: FitnessAction;
 };
 
@@ -108,7 +112,7 @@ export const useGetSetAtIndex = (exerciseIdx: number, setIdx: number) => {
 export const getDefaultWorkout = (
 	fitnessEntity: FitnessAction,
 ): InProgressWorkout => {
-	const date = dayjsLib().add(7, "second");
+	const date = dayjsLib().add(4, "second");
 	return {
 		images: [],
 		videos: [],
@@ -116,6 +120,7 @@ export const getDefaultWorkout = (
 		exercises: [],
 		startTime: date.toISOString(),
 		currentActionOrCompleted: fitnessEntity,
+		durations: [{ from: date.toISOString() }],
 		name: `${getTimeOfDay(date.hour())} Workout`,
 	};
 };
@@ -203,6 +208,7 @@ export const currentWorkoutToCreateWorkoutInput = (
 			name: currentWorkout.name,
 			comment: currentWorkout.comment,
 			endTime: new Date().toISOString(),
+			durations: currentWorkout.durations,
 			templateId: currentWorkout.templateId,
 			repeatedFrom: currentWorkout.repeatedFrom,
 			updateWorkoutId: currentWorkout.updateWorkoutId,
@@ -226,6 +232,7 @@ export const currentWorkoutToCreateWorkoutInput = (
 					lot: set.lot,
 					statistic: set.statistic,
 					restTime: set.restTimer?.duration,
+					restTimerStartedAt: set.restTimerStartedAt,
 					confirmedAt: set.confirmedAt
 						? new Date(set.confirmedAt).toISOString()
 						: null,
@@ -249,8 +256,9 @@ export const currentWorkoutToCreateWorkoutInput = (
 };
 
 export type CurrentWorkoutTimer = {
-	endAt: string;
+	willEndAt: string;
 	totalTime: number;
+	wasPausedAt?: string;
 	triggeredBy?: { exerciseIdentifier: string; setIdx: number };
 };
 
@@ -277,8 +285,8 @@ export const duplicateOldWorkout = async (
 	coreDetails: ReturnType<typeof useCoreDetails>,
 	userFitnessPreferences: UserFitnessPreferences,
 	params: {
-		repeatedFromId?: string;
 		templateId?: string;
+		repeatedFromId?: string;
 		updateWorkoutId?: string;
 		updateWorkoutTemplateId?: string;
 	},
diff --git a/apps/frontend/app/root.tsx b/apps/frontend/app/root.tsx
index fa5f603cca..d6d855ae86 100644
--- a/apps/frontend/app/root.tsx
+++ b/apps/frontend/app/root.tsx
@@ -7,8 +7,11 @@ import {
 	MantineProvider,
 	createTheme,
 } from "@mantine/core";
-import "@mantine/charts/styles.css";
 import "@mantine/core/styles.css";
+import "@mantine/code-highlight/styles.css";
+import "@mantine/charts/styles.css";
+import "@mantine/carousel/styles.css";
+import "@mantine/dates/styles.css";
 import "@mantine/notifications/styles.css";
 import {
 	type LinksFunction,
@@ -40,13 +43,10 @@ import {
 const theme = createTheme({
 	fontFamily: "Poppins",
 	components: {
+		Alert: Alert.extend({ defaultProps: { p: "xs" } }),
 		ActionIcon: ActionIcon.extend({
-			defaultProps: {
-				variant: "subtle",
-				color: "gray",
-			},
+			defaultProps: { variant: "subtle", color: "gray" },
 		}),
-		Alert: Alert.extend({ defaultProps: { p: "xs" } }),
 	},
 	breakpoints: {
 		xs: "30em",
@@ -96,29 +96,14 @@ export const links: LinksFunction = () => {
 export const loader = async ({ request }: LoaderFunctionArgs) => {
 	const { toast, headers: toastHeaders } = await getToast(request);
 	const colorScheme = await colorSchemeCookie.parse(
-		request.headers.get("cookie") || "",
+		request.headers.get("cookie"),
 	);
 	const headers = new Headers();
 	const defaultColorScheme = colorScheme || "light";
 	if (toastHeaders) extendResponseHeaders(headers, toastHeaders);
-
 	return data({ toast, defaultColorScheme }, { headers });
 };
 
-const DefaultHeadTags = () => {
-	return (
-		<>
-			<meta charSet="utf-8" />
-			<meta
-				name="viewport"
-				content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
-			/>
-			<link rel="manifest" href="/manifest.json" />
-			<link rel="apple-touch-icon" href={"/icon-192x192.png"} />
-		</>
-	);
-};
-
 export default function App() {
 	const navigation = useNavigation();
 	const loaderData = useLoaderData<typeof loader>();
@@ -126,7 +111,13 @@ export default function App() {
 	return (
 		<html lang="en">
 			<head>
-				<DefaultHeadTags />
+				<meta charSet="utf-8" />
+				<meta
+					name="viewport"
+					content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
+				/>
+				<link rel="manifest" href="/manifest.json" />
+				<link rel="apple-touch-icon" href={"/icon-192x192.png"} />
 				<Meta />
 				<Links />
 				<ColorSchemeScript forceColorScheme={loaderData.defaultColorScheme} />
diff --git a/apps/frontend/app/routes/_dashboard.analytics.tsx b/apps/frontend/app/routes/_dashboard.analytics.tsx
index 8206838d5e..0dc370e158 100644
--- a/apps/frontend/app/routes/_dashboard.analytics.tsx
+++ b/apps/frontend/app/routes/_dashboard.analytics.tsx
@@ -14,6 +14,7 @@ import {
 	SimpleGrid,
 	Stack,
 	Text,
+	Tooltip,
 } from "@mantine/core";
 import { DatePicker } from "@mantine/dates";
 import { notifications } from "@mantine/notifications";
@@ -617,18 +618,21 @@ const TimeOfDayChart = () => {
 const StatItem = (props: {
 	text: string;
 	label: string;
+	tooltipLabel: string | number;
 	icon: ComponentType<IconProps>;
 }) => {
 	return (
-		<Stack align="center" justify="center" gap={0}>
-			<props.icon size={20} />
-			<Text size="sm" c="dimmed" ta="center">
-				{props.label}
-			</Text>
-			<Text size="xl" fw="bold" ta="center">
-				{props.text}
-			</Text>
-		</Stack>
+		<Tooltip label={props.tooltipLabel}>
+			<Stack align="center" justify="center" gap={0}>
+				<props.icon size={20} />
+				<Text size="sm" c="dimmed" ta="center">
+					{props.label}
+				</Text>
+				<Text size="xl" fw="bold" ta="center">
+					{props.text}
+				</Text>
+			</Stack>
+		</Tooltip>
 	);
 };
 
@@ -658,15 +662,21 @@ const StatisticsCard = () => {
 							label="Workouts"
 							icon={IconStretching}
 							text={`${fitness.workoutCount}`}
+							tooltipLabel={fitness.workoutCount}
 						/>
 						<StatItem
 							label="Reps"
 							icon={IconRepeat}
+							tooltipLabel={fitness.workoutReps}
 							text={formatQuantityWithCompactNotation(fitness.workoutReps)}
 						/>
 						<StatItem
 							label="Weight"
 							icon={IconWeight}
+							tooltipLabel={displayWeightWithUnit(
+								unitSystem,
+								fitness.workoutWeight,
+							)}
 							text={displayWeightWithUnit(
 								unitSystem,
 								fitness.workoutWeight,
@@ -676,35 +686,45 @@ const StatisticsCard = () => {
 						<StatItem
 							icon={IconRoad}
 							label="Distance"
+							tooltipLabel={displayDistanceWithUnit(
+								unitSystem,
+								fitness.workoutDistance,
+							)}
 							text={displayDistanceWithUnit(
 								unitSystem,
 								fitness.workoutDistance,
+								true,
 							)}
 						/>
 						<StatItem
 							label="Duration"
 							icon={IconClock}
+							tooltipLabel={`${fitness.workoutDuration}s`}
 							text={displayDuration(fitness.workoutDuration)}
 						/>
 						<StatItem
 							label="Exercises"
 							icon={IconBarbell}
 							text={`${fitness.workoutExercises.length}`}
+							tooltipLabel={fitness.workoutExercises.length}
 						/>
 						<StatItem
 							icon={IconTrophy}
 							label="Personal Bests"
 							text={`${fitness.workoutPersonalBests}`}
+							tooltipLabel={fitness.workoutPersonalBests}
 						/>
 						<StatItem
 							icon={IconZzz}
 							label="Rest Time"
+							tooltipLabel={`${fitness.workoutRestTime}s`}
 							text={displayDuration(fitness.workoutRestTime)}
 						/>
 						<StatItem
 							label="Measurements"
 							icon={IconRulerMeasure}
 							text={`${fitness.measurementCount}`}
+							tooltipLabel={fitness.measurementCount}
 						/>
 					</SimpleGrid>
 				),
diff --git a/apps/frontend/app/routes/_dashboard.fitness.$action.tsx b/apps/frontend/app/routes/_dashboard.fitness.$action.tsx
index d4627ca9bc..7bec5f7cbd 100644
--- a/apps/frontend/app/routes/_dashboard.fitness.$action.tsx
+++ b/apps/frontend/app/routes/_dashboard.fitness.$action.tsx
@@ -1,6 +1,3 @@
-// biome-ignore lint/style/useNodejsImportProtocol: this is a dependency
-import { Buffer } from "buffer";
-import "@mantine/carousel/styles.css";
 import { useAutoAnimate } from "@formkit/auto-animate/react";
 import { DragDropContext, Draggable, Droppable } from "@hello-pangea/dnd";
 import { Carousel } from "@mantine/carousel";
@@ -14,6 +11,7 @@ import {
 	Container,
 	Divider,
 	Drawer,
+	FileButton,
 	Flex,
 	Group,
 	Image,
@@ -42,7 +40,6 @@ import {
 	useDidUpdate,
 	useDisclosure,
 	useListState,
-	useToggle,
 } from "@mantine/hooks";
 import { notifications } from "@mantine/notifications";
 import type { LoaderFunctionArgs, MetaArgs } from "@remix-run/node";
@@ -69,7 +66,6 @@ import {
 } from "@ryot/ts-utils";
 import {
 	IconCamera,
-	IconCameraRotate,
 	IconCheck,
 	IconChevronUp,
 	IconClipboard,
@@ -81,9 +77,11 @@ import {
 	IconHeartSpark,
 	IconInfoCircle,
 	IconLayersIntersect,
+	IconLibraryPhoto,
 	IconPhoto,
 	IconReorder,
 	IconReplace,
+	IconStopwatch,
 	IconTrash,
 	IconZzz,
 } from "@tabler/icons-react";
@@ -92,7 +90,6 @@ import { Howl } from "howler";
 import { produce } from "immer";
 import { RESET } from "jotai/utils";
 import { useEffect, useMemo, useRef, useState } from "react";
-import Webcam from "react-webcam";
 import { $path } from "remix-routes";
 import { ClientOnly } from "remix-utils/client-only";
 import invariant from "tiny-invariant";
@@ -170,6 +167,91 @@ const deleteUploadedAsset = (key: string) => {
 	});
 };
 
+const getNextSetInWorkout = (
+	currentSetIdx: number,
+	currentExerciseIdx: number,
+	currentWorkout: InProgressWorkout,
+) => {
+	const currentExercise = currentWorkout.exercises[currentExerciseIdx];
+	const partOfSuperset = currentWorkout.supersets.find((superset) =>
+		superset.exercises.includes(currentExercise.identifier),
+	);
+	const areAllSetsConfirmed = currentExercise.sets.every((s) => s.confirmedAt);
+	if (partOfSuperset) {
+		const sortedExercises = sortBy(partOfSuperset.exercises, (s) =>
+			currentWorkout.exercises.findIndex((e) => e.identifier === s),
+		);
+		const nextExerciseWithIncompleteSets = currentWorkout.exercises.find(
+			(e) =>
+				e.identifier !== currentExercise.identifier &&
+				sortedExercises.includes(e.identifier) &&
+				e.sets.some((s) => !s.confirmedAt),
+		);
+		if (nextExerciseWithIncompleteSets) {
+			const exerciseIdx = currentWorkout.exercises.findIndex(
+				(e) => e.identifier === nextExerciseWithIncompleteSets.identifier,
+			);
+			const setIdx = nextExerciseWithIncompleteSets.sets.findIndex(
+				(s) => !s.confirmedAt,
+			);
+			return { exerciseIdx, setIdx: setIdx, wasLastSet: areAllSetsConfirmed };
+		}
+	}
+	if (areAllSetsConfirmed)
+		return {
+			exerciseIdx: currentExerciseIdx + 1,
+			setIdx: 0,
+			wasLastSet: true,
+		};
+	return {
+		exerciseIdx: currentExerciseIdx,
+		setIdx: currentSetIdx + 1,
+		wasLastSet: false,
+	};
+};
+
+type ExerciseDetails = ExerciseDetailsQuery["exerciseDetails"];
+type UserExerciseDetails = UserExerciseDetailsQuery["userExerciseDetails"];
+
+const usePerformTasksAfterSetConfirmed = () => {
+	const userPreferences = useUserPreferences();
+	const [currentWorkout, setCurrentWorkout] = useCurrentWorkout();
+
+	const performTask = async (setIdx: number, exerciseIdx: number) => {
+		const exerciseId = currentWorkout?.exercises[exerciseIdx].exerciseId;
+		if (!exerciseId) return;
+		const exerciseDetails = await queryClient.ensureQueryData(
+			getExerciseDetailsQuery(exerciseId),
+		);
+		const userExerciseDetails = await queryClient.ensureQueryData(
+			getUserExerciseDetailsQuery(exerciseId),
+		);
+		setCurrentWorkout((cw) =>
+			produce(cw, (draft) => {
+				if (!draft) return;
+				const currentExercise = draft.exercises[exerciseIdx];
+				const nextSet = getNextSetInWorkout(setIdx, exerciseIdx, draft);
+				focusOnExercise(nextSet.exerciseIdx);
+				if (nextSet.wasLastSet) {
+					currentExercise.isCollapsed = true;
+					currentExercise.isShowDetailsOpen = false;
+					const nextExercise = draft.exercises[nextSet.exerciseIdx];
+					const nextExerciseHasDetailsToShow =
+						nextExercise &&
+						exerciseHasDetailsToShow(exerciseDetails, userExerciseDetails);
+					if (nextExerciseHasDetailsToShow) {
+						nextExercise.isCollapsed = false;
+						if (userPreferences.fitness.logging.showDetailsWhileEditing)
+							nextExercise.isShowDetailsOpen = true;
+					}
+				}
+			}),
+		);
+	};
+
+	return performTask;
+};
+
 export default function Page() {
 	const loaderData = useLoaderData<typeof loader>();
 	const userPreferences = useUserPreferences();
@@ -179,33 +261,12 @@ export default function Page() {
 	const navigate = useNavigate();
 	const [isSaveBtnLoading, setIsSaveBtnLoading] = useState(false);
 	const [currentWorkout, setCurrentWorkout] = useCurrentWorkout();
-	const playCompleteTimerSound = () => {
-		const sound = new Howl({ src: ["/timer-completed.mp3"] });
-		if (!userPreferences.fitness.logging.muteSounds) sound.play();
-		if (document.visibilityState === "visible") return;
-		sendNotificationToServiceWorker(
-			"Timer completed",
-			"Let's get this done!",
-			"timer-completed",
-			{ event: "open-link", link: window.location.href },
-		);
-	};
-	useInterval(() => {
-		if (
-			loaderData.action === FitnessAction.LogWorkout &&
-			navigator.serviceWorker.controller &&
-			document.visibilityState === "visible"
-		)
-			postMessageToServiceWorker({
-				event: "remove-timer-completed-notification",
-			});
-	}, 5000);
 	const [
 		timerDrawerOpened,
 		{
-			close: timerDrawerClose,
-			toggle: timerDrawerToggle,
-			open: timerDrawerOpen,
+			open: openTimerDrawer,
+			close: closeTimerDrawer,
+			toggle: toggleTimerDrawer,
 		},
 	] = useDisclosure(false);
 	const [supersetWithExerciseIdentifier, setSupersetModalOpened] = useState<
@@ -220,22 +281,55 @@ export default function Page() {
 	const [assetsModalOpened, setAssetsModalOpened] = useState<
 		string | null | undefined
 	>(undefined);
+	const promptForRestTimer = userPreferences.fitness.logging.promptForRestTimer;
+	const performTasksAfterSetConfirmed = usePerformTasksAfterSetConfirmed();
+	const isWorkoutPaused = isString(currentWorkout?.durations.at(-1)?.to);
 
 	useInterval(() => {
-		const timeRemaining = dayjsLib(currentTimer?.endAt).diff(
+		if (
+			loaderData.action === FitnessAction.LogWorkout &&
+			navigator.serviceWorker.controller &&
+			document.visibilityState === "visible"
+		)
+			postMessageToServiceWorker({
+				event: "remove-timer-completed-notification",
+			});
+	}, 5000);
+	useInterval(() => {
+		const timeRemaining = dayjsLib(currentTimer?.willEndAt).diff(
 			dayjsLib(),
 			"second",
 		);
-		if (timeRemaining && timeRemaining <= 3) {
+		if (!currentTimer?.wasPausedAt && timeRemaining && timeRemaining <= 3) {
 			if (navigator.vibrate) navigator.vibrate(200);
 			if (timeRemaining <= 1) {
+				const triggeredBy = currentTimer?.triggeredBy;
+				if (promptForRestTimer && triggeredBy && currentWorkout) {
+					const exerciseIdx = currentWorkout?.exercises.findIndex(
+						(c) => c.identifier === triggeredBy.exerciseIdentifier,
+					);
+					if (exerciseIdx !== undefined && exerciseIdx !== -1) {
+						performTasksAfterSetConfirmed(triggeredBy.setIdx, exerciseIdx);
+					}
+				}
 				playCompleteTimerSound();
 				stopTimer();
-				setTimeout(() => timerDrawerClose(), 500);
+				setTimeout(() => closeTimerDrawer(), 500);
 			}
 		}
 	}, 1000);
 
+	const playCompleteTimerSound = () => {
+		const sound = new Howl({ src: ["/timer-completed.mp3"] });
+		if (!userPreferences.fitness.logging.muteSounds) sound.play();
+		if (document.visibilityState === "visible") return;
+		sendNotificationToServiceWorker(
+			"Timer completed",
+			"Let's get this done!",
+			"timer-completed",
+			{ event: "open-link", link: window.location.href },
+		);
+	};
 	const startTimer = (
 		duration: number,
 		triggeredBy?: { exerciseIdentifier: string; setIdx: number },
@@ -243,10 +337,22 @@ export default function Page() {
 		setCurrentTimer({
 			triggeredBy,
 			totalTime: duration,
-			endAt: dayjsLib().add(duration, "second").toISOString(),
+			willEndAt: dayjsLib().add(duration, "second").toISOString(),
 		});
 	};
-
+	const pauseOrResumeTimer = () => {
+		if (currentTimer)
+			setCurrentTimer(
+				produce(currentTimer, (draft) => {
+					draft.willEndAt = dayjsLib(draft.willEndAt)
+						.add(dayjsLib().diff(draft.wasPausedAt, "second"), "second")
+						.toISOString();
+					draft.wasPausedAt = draft.wasPausedAt
+						? undefined
+						: dayjsLib().toISOString();
+				}),
+			);
+	};
 	const stopTimer = () => {
 		const triggeredBy = currentTimer?.triggeredBy;
 		if (currentWorkout && triggeredBy) {
@@ -274,10 +380,11 @@ export default function Page() {
 								closeModal={() => setAssetsModalOpened(undefined)}
 							/>
 							<TimerDrawer
-								opened={timerDrawerOpened}
-								onClose={timerDrawerClose}
-								startTimer={startTimer}
 								stopTimer={stopTimer}
+								startTimer={startTimer}
+								opened={timerDrawerOpened}
+								onClose={closeTimerDrawer}
+								pauseOrResumeTimer={pauseOrResumeTimer}
 							/>
 							<ReorderDrawer
 								opened={reorderDrawerOpened}
@@ -293,7 +400,7 @@ export default function Page() {
 									openAssetsModal={() => setAssetsModalOpened(null)}
 								/>
 								<Group>
-									<WorkoutDurationTimer />
+									<WorkoutDurationTimer isWorkoutPaused={isWorkoutPaused} />
 									<StatDisplay
 										name="Exercises"
 										value={
@@ -352,7 +459,7 @@ export default function Page() {
 										color="orange"
 										variant="subtle"
 										size="compact-sm"
-										onClick={timerDrawerToggle}
+										onClick={toggleTimerDrawer}
 										style={
 											loaderData.isCreatingTemplate ||
 											loaderData.isUpdatingWorkout
@@ -383,6 +490,7 @@ export default function Page() {
 												variant="subtle"
 												size="compact-sm"
 												loading={isSaveBtnLoading}
+												disabled={isWorkoutPaused}
 												onClick={async () => {
 													if (!currentWorkout.name) {
 														notifications.show({
@@ -503,7 +611,8 @@ export default function Page() {
 										key={ex.identifier}
 										stopTimer={stopTimer}
 										startTimer={startTimer}
-										openTimerDrawer={timerDrawerOpen}
+										isWorkoutPaused={isWorkoutPaused}
+										openTimerDrawer={openTimerDrawer}
 										reorderDrawerToggle={reorderDrawerToggle}
 										openSupersetModal={(s) => setSupersetModalOpened(s)}
 										setOpenAssetsModal={() =>
@@ -609,10 +718,23 @@ const StatDisplay = (props: {
 	name: string;
 	value: string;
 	isHidden?: boolean;
+	onClick?: () => void;
+	highlightValue?: boolean;
 }) => {
 	return (
-		<Box mx="auto" style={props.isHidden ? { display: "none" } : undefined}>
-			<Text ta="center" fz={{ md: "xl" }}>
+		<Box
+			mx="auto"
+			onClick={props.onClick}
+			style={{
+				display: props.isHidden ? "none" : undefined,
+				cursor: props.onClick ? "pointer" : undefined,
+			}}
+		>
+			<Text
+				ta="center"
+				fz={{ md: "xl" }}
+				c={props.highlightValue ? "red" : undefined}
+			>
 				{props.value}
 			</Text>
 			<Text c="dimmed" size="sm" ta="center">
@@ -625,27 +747,34 @@ const StatDisplay = (props: {
 const formatTimerDuration = (duration: number) =>
 	dayjsLib.duration(duration).format("mm:ss");
 
-const offsetDate = (startTime?: string) => {
-	const now = dayjsLib();
-	return now.diff(dayjsLib(startTime), "seconds");
-};
-
 const RestTimer = () => {
 	forceUpdateEverySecond();
 	const [currentTimer] = useTimerAtom();
 
 	return currentTimer
-		? formatTimerDuration(dayjsLib(currentTimer.endAt).diff(dayjsLib()))
+		? formatTimerDuration(
+				dayjsLib(currentTimer.willEndAt).diff(currentTimer.wasPausedAt),
+			)
 		: "Timer";
 };
 
-const WorkoutDurationTimer = () => {
+const WorkoutDurationTimer = (props: { isWorkoutPaused: boolean }) => {
 	const { isCreatingTemplate, isUpdatingWorkout } =
 		useLoaderData<typeof loader>();
-	const [currentWorkout] = useCurrentWorkout();
-	const seconds = offsetDate(currentWorkout?.startTime);
+	const [value, setValue] = useState(0);
+	const [currentWorkout, setCurrentWorkout] = useCurrentWorkout();
 
-	forceUpdateEverySecond();
+	useInterval(() => setValue((v) => v + 1), 1000);
+
+	const seconds = useMemo(() => {
+		let total = 0;
+		for (const duration of currentWorkout?.durations || []) {
+			total += dayjsLib(duration.to).diff(duration.from) / 1000;
+		}
+		return total;
+	}, [value, currentWorkout]);
+
+	if (!currentWorkout) return null;
 
 	let format = "mm:ss";
 	if (seconds > 3600) format = `H:${format}`;
@@ -653,8 +782,22 @@ const WorkoutDurationTimer = () => {
 	return (
 		<StatDisplay
 			name="Duration"
-			value={dayjsLib.duration(seconds * 1000).format(format)}
+			highlightValue={props.isWorkoutPaused}
 			isHidden={isCreatingTemplate || isUpdatingWorkout}
+			value={dayjsLib.duration(seconds, "second").format(format)}
+			onClick={() => {
+				setCurrentWorkout(
+					produce(currentWorkout, (draft) => {
+						const currentDurations = draft.durations;
+						if (Object.keys(currentDurations.at(-1) || {}).length === 2) {
+							currentDurations.push({ from: dayjsLib().toISOString() });
+						} else {
+							currentDurations[currentDurations.length - 1].to =
+								dayjsLib().toISOString();
+						}
+					}),
+				);
+			}}
 		/>
 	);
 };
@@ -691,42 +834,40 @@ const StatInput = (props: {
 	return currentWorkout ? (
 		<Flex style={{ flex: 1 }} justify="center">
 			<NumberInput
+				size="xs"
 				required
-				value={
-					isString(set.statistic[props.stat])
-						? Number(set.statistic[props.stat])
-						: undefined
-				}
+				hideControls
+				step={props.inputStep}
 				onChange={(v) => setValue(v)}
 				onFocus={(e) => e.target.select()}
-				size="xs"
 				styles={{
 					input: { fontSize: 15, width: rem(72), textAlign: "center" },
 				}}
+				value={
+					isString(set.statistic[props.stat])
+						? Number(set.statistic[props.stat])
+						: undefined
+				}
 				decimalScale={
 					isNumber(props.inputStep)
 						? Math.log10(1 / props.inputStep)
 						: undefined
 				}
-				step={props.inputStep}
-				hideControls
 			/>
 		</Flex>
 	) : null;
 };
 
-const fileType = "image/jpeg";
-
 const ImageDisplay = (props: { imageSrc: string; removeImage: () => void }) => {
 	return (
 		<Box pos="relative">
 			<Avatar src={props.imageSrc} size="lg" />
 			<ActionIcon
-				pos="absolute"
 				top={0}
+				size="xs"
 				left={-12}
 				color="red"
-				size="xs"
+				pos="absolute"
 				onClick={async () => {
 					const yes = await confirmWrapper({
 						confirmation: "Are you sure you want to remove this image?",
@@ -954,8 +1095,8 @@ const focusOnExercise = (idx: number) => {
 };
 
 const exerciseHasDetailsToShow = (
-	details?: ExerciseDetailsQuery["exerciseDetails"],
-	userDetails?: UserExerciseDetailsQuery["userExerciseDetails"],
+	details?: ExerciseDetails,
+	userDetails?: UserExerciseDetails,
 ) =>
 	(details?.attributes.images.length || 0) > 0 ||
 	(userDetails?.history?.length || 0) > 0;
@@ -967,14 +1108,42 @@ const UploadAssetsModal = (props: {
 	const coreDetails = useCoreDetails();
 	const fileUploadAllowed = coreDetails.fileStorageEnabled;
 	const [currentWorkout, setCurrentWorkout] = useCurrentWorkout();
-	const [cameraFacing, toggleCameraFacing] = useToggle([
-		"environment",
-		"user",
-	] as const);
-	const webcamRef = useRef<Webcam>(null);
 
 	if (!currentWorkout) return null;
 
+	const afterFileSelected = async (file: File | null) => {
+		if (props.modalOpenedBy === null && !coreDetails.isServerKeyValidated) {
+			notifications.show({
+				color: "red",
+				message: PRO_REQUIRED_MESSAGE,
+			});
+			return;
+		}
+		if (!file) return;
+		const imageSrc = URL.createObjectURL(file);
+		const toSubmitForm = new FormData();
+		toSubmitForm.append("file", file, "image.jpg");
+		try {
+			const resp = await fetch(
+				$path("/actions", { intent: "uploadWorkoutAsset" }),
+				{ method: "POST", body: toSubmitForm },
+			);
+			const data = await resp.json();
+			setCurrentWorkout(
+				produce(currentWorkout, (draft) => {
+					const media = { imageSrc, key: data.key };
+					if (exercise) draft.exercises[exerciseIdx].images.push(media);
+					else draft.images.push(media);
+				}),
+			);
+		} catch {
+			notifications.show({
+				color: "red",
+				message: "Error while uploading image",
+			});
+		}
+	};
+
 	const exerciseIdx = currentWorkout.exercises.findIndex(
 		(e) => e.identifier === props.modalOpenedBy,
 	);
@@ -983,14 +1152,11 @@ const UploadAssetsModal = (props: {
 
 	return (
 		<Modal
-			withCloseButton={false}
 			onClose={() => props.closeModal()}
 			opened={props.modalOpenedBy !== undefined}
+			title={`Images for ${exercise ? exercise.name : "the workout"}`}
 		>
 			<Stack>
-				<Text size="lg">
-					Images for {exercise ? exercise.name : "the workout"}
-				</Text>
 				{fileUploadAllowed ? (
 					<>
 						{isString(props.modalOpenedBy) ? (
@@ -1034,78 +1200,38 @@ const UploadAssetsModal = (props: {
 								))}
 							</Avatar.Group>
 						) : null}
-						<Group justify="center" gap={4}>
-							<Paper radius="md" style={{ overflow: "hidden" }}>
-								<Webcam
-									ref={webcamRef}
-									height={180}
-									width={240}
-									videoConstraints={{ facingMode: cameraFacing }}
-									screenshotFormat={fileType}
-								/>
-							</Paper>
-							<Stack>
-								<ActionIcon size="xl" onClick={() => toggleCameraFacing()}>
-									<IconCameraRotate size={32} />
-								</ActionIcon>
-								<ActionIcon
-									size="xl"
-									onClick={async () => {
-										if (
-											props.modalOpenedBy === null &&
-											!coreDetails.isServerKeyValidated
-										) {
-											notifications.show({
-												color: "red",
-												message: PRO_REQUIRED_MESSAGE,
-											});
-											return;
-										}
-										const imageSrc = webcamRef.current?.getScreenshot();
-										if (imageSrc) {
-											const buffer = Buffer.from(
-												imageSrc.replace(/^data:image\/\w+;base64,/, ""),
-												"base64",
-											);
-											const fileObj = new File([buffer], "image.jpg", {
-												type: fileType,
-											});
-											const toSubmitForm = new FormData();
-											toSubmitForm.append("file", fileObj, "image.jpg");
-											try {
-												const resp = await fetch(
-													$path("/actions", { intent: "uploadWorkoutAsset" }),
-													{ method: "POST", body: toSubmitForm },
-												);
-												const data = await resp.json();
-												setCurrentWorkout(
-													produce(currentWorkout, (draft) => {
-														const media = { imageSrc, key: data.key };
-														if (exercise)
-															draft.exercises[exerciseIdx].images.push(media);
-														else draft.images.push(media);
-													}),
-												);
-											} catch {
-												notifications.show({
-													color: "red",
-													message: "Error while uploading image",
-												});
-											}
-										}
-									}}
-								>
-									<IconCamera size={32} />
-								</ActionIcon>
-							</Stack>
+						<Group justify="space-between">
+							<FileButton accept="image/*" onChange={afterFileSelected}>
+								{(props) => (
+									<Button
+										{...props}
+										flex={1}
+										color="cyan"
+										variant="outline"
+										leftSection={<IconLibraryPhoto />}
+									>
+										Select picture
+									</Button>
+								)}
+							</FileButton>
+							<FileButton
+								accept="image/*"
+								capture="environment"
+								onChange={afterFileSelected}
+							>
+								{(props) => (
+									<Button
+										{...props}
+										flex={1}
+										color="cyan"
+										variant="outline"
+										leftSection={<IconCamera />}
+									>
+										Take picture
+									</Button>
+								)}
+							</FileButton>
 						</Group>
-						<Button
-							fullWidth
-							variant="outline"
-							onClick={() => props.closeModal()}
-						>
-							Done
-						</Button>
 					</>
 				) : (
 					<Text c="red" size="sm">
@@ -1117,9 +1243,19 @@ const UploadAssetsModal = (props: {
 	);
 };
 
+const getProgressOfExercise = (cw: InProgressWorkout, index: number) => {
+	const isCompleted = cw.exercises[index].sets.every((s) => s.confirmedAt);
+	return isCompleted
+		? ("complete" as const)
+		: cw.exercises[index].sets.some((s) => s.confirmedAt)
+			? ("in-progress" as const)
+			: ("not-started" as const);
+};
+
 const ExerciseDisplay = (props: {
 	exerciseIdx: number;
 	stopTimer: () => void;
+	isWorkoutPaused: boolean;
 	startTimer: FuncStartTimer;
 	openTimerDrawer: () => void;
 	reorderDrawerToggle: () => void;
@@ -1185,17 +1321,17 @@ const ExerciseDisplay = (props: {
 	return (
 		<>
 			<Paper
+				pl="sm"
 				radius={0}
+				ml={{ base: "-md", md: 0 }}
+				id={props.exerciseIdx.toString()}
+				pr={{ base: 4, md: "xs", lg: "sm" }}
 				style={{
 					scrollMargin: exercise.scrollMarginRemoved ? "10px" : "60px",
 					borderLeft: partOfSuperset
 						? `3px solid ${theme.colors[partOfSuperset.color][6]}`
 						: undefined,
 				}}
-				pl="sm"
-				ml={{ base: "-md", md: 0 }}
-				id={props.exerciseIdx.toString()}
-				pr={{ base: 4, md: "xs", lg: "sm" }}
 			>
 				<Stack ref={parent}>
 					<Menu shadow="md" width={200} position="left-end">
@@ -1210,7 +1346,9 @@ const ExerciseDisplay = (props: {
 							</Anchor>
 							<Group wrap="nowrap" mr={-10}>
 								{didExerciseActivateTimer ? (
-									<DisplayLastExerciseSetRestTimer />
+									<DisplayExerciseSetRestTimer
+										openTimerDrawer={props.openTimerDrawer}
+									/>
 								) : null}
 								<ActionIcon
 									variant="transparent"
@@ -1304,8 +1442,8 @@ const ExerciseDisplay = (props: {
 								</Menu.Item>
 							) : null}
 							<Menu.Item
-								leftSection={<IconReorder size={14} />}
 								onClick={props.reorderDrawerToggle}
+								leftSection={<IconReorder size={14} />}
 							>
 								Reorder
 							</Menu.Item>
@@ -1348,10 +1486,10 @@ const ExerciseDisplay = (props: {
 						<Stack ref={parent}>
 							{exercise.notes.map((note, idx) => (
 								<NoteInput
-									key={`${exercise.identifier}-${idx}`}
-									exerciseIdx={props.exerciseIdx}
-									noteIdx={idx}
 									note={note}
+									noteIdx={idx}
+									exerciseIdx={props.exerciseIdx}
+									key={`${exercise.identifier}-${idx}`}
 								/>
 							))}
 							{exercise.isShowDetailsOpen ? (
@@ -1363,10 +1501,10 @@ const ExerciseDisplay = (props: {
 													{exerciseDetails?.attributes.images.map((i) => (
 														<Image
 															key={i}
-															radius="md"
 															src={i}
 															h={200}
 															w={350}
+															radius="md"
 														/>
 													))}
 												</Group>
@@ -1436,13 +1574,13 @@ const ExerciseDisplay = (props: {
 										.exhaustive()}
 									{(userExerciseDetails?.history?.length || 0) > 0 ? (
 										<ActionIcon
+											p={2}
+											size="sm"
 											right={10}
 											bottom={10}
-											variant="filled"
 											color="red"
-											size="sm"
 											pos="absolute"
-											p={2}
+											variant="filled"
 											onClick={() => {
 												if (!coreDetails.isServerKeyValidated) {
 													notifications.show({
@@ -1477,8 +1615,8 @@ const ExerciseDisplay = (props: {
 								</Text>
 								<Text
 									size="xs"
-									w={`${(isCreatingTemplate ? 95 : 85) / toBeDisplayedColumns}%`}
 									ta="center"
+									w={`${(isCreatingTemplate ? 95 : 85) / toBeDisplayedColumns}%`}
 								>
 									PREVIOUS
 								</Text>
@@ -1528,6 +1666,7 @@ const ExerciseDisplay = (props: {
 									startTimer={props.startTimer}
 									exerciseIdx={props.exerciseIdx}
 									key={`${exercise.identifier}-${idx}`}
+									isWorkoutPaused={props.isWorkoutPaused}
 									openTimerDrawer={props.openTimerDrawer}
 									toBeDisplayedColumns={toBeDisplayedColumns}
 								/>
@@ -1569,7 +1708,9 @@ const ExerciseDisplay = (props: {
 	);
 };
 
-const DisplayLastExerciseSetRestTimer = () => {
+const DisplayExerciseSetRestTimer = (props: {
+	openTimerDrawer: () => void;
+}) => {
 	const [currentTimer] = useTimerAtom();
 	forceUpdateEverySecond();
 
@@ -1577,70 +1718,35 @@ const DisplayLastExerciseSetRestTimer = () => {
 
 	return (
 		<RingProgress
-			roundCaps
 			size={30}
+			roundCaps
 			thickness={2}
 			style={{ cursor: "pointer" }}
+			onClick={props.openTimerDrawer}
 			sections={[
 				{
 					value:
-						(dayjsLib(currentTimer.endAt).diff(dayjsLib(), "seconds") * 100) /
+						(dayjsLib(currentTimer.willEndAt).diff(
+							currentTimer.wasPausedAt,
+							"seconds",
+						) *
+							100) /
 						currentTimer.totalTime,
 					color: "blue",
 				},
 			]}
 			label={
 				<Text ta="center" size="xs">
-					{Math.ceil(dayjsLib(currentTimer.endAt).diff(dayjsLib()) / 1000)}
+					{Math.floor(
+						dayjsLib(currentTimer.willEndAt).diff(currentTimer.wasPausedAt) /
+							1000,
+					)}
 				</Text>
 			}
 		/>
 	);
 };
 
-const getNextSetInWorkout = (
-	currentWorkout: InProgressWorkout,
-	currentExerciseIdx: number,
-	currentSetIdx: number,
-) => {
-	const currentExercise = currentWorkout.exercises[currentExerciseIdx];
-	const partOfSuperset = currentWorkout.supersets.find((superset) =>
-		superset.exercises.includes(currentExercise.identifier),
-	);
-	const areAllSetsConfirmed = currentExercise.sets.every((s) => s.confirmedAt);
-	if (partOfSuperset) {
-		const sortedExercises = sortBy(partOfSuperset.exercises, (s) =>
-			currentWorkout.exercises.findIndex((e) => e.identifier === s),
-		);
-		const nextExerciseWithIncompleteSets = currentWorkout.exercises.find(
-			(e) =>
-				e.identifier !== currentExercise.identifier &&
-				sortedExercises.includes(e.identifier) &&
-				e.sets.some((s) => !s.confirmedAt),
-		);
-		if (nextExerciseWithIncompleteSets) {
-			const exerciseIdx = currentWorkout.exercises.findIndex(
-				(e) => e.identifier === nextExerciseWithIncompleteSets.identifier,
-			);
-			const setIdx = nextExerciseWithIncompleteSets.sets.findIndex(
-				(s) => !s.confirmedAt,
-			);
-			return { exerciseIdx, setIdx: setIdx, wasLastSet: areAllSetsConfirmed };
-		}
-	}
-	if (areAllSetsConfirmed)
-		return {
-			exerciseIdx: currentExerciseIdx + 1,
-			setIdx: 0,
-			wasLastSet: true,
-		};
-	return {
-		exerciseIdx: currentExerciseIdx,
-		setIdx: currentSetIdx + 1,
-		wasLastSet: false,
-	};
-};
-
 const SetDisplay = (props: {
 	setIdx: number;
 	repsCol: boolean;
@@ -1649,6 +1755,7 @@ const SetDisplay = (props: {
 	durationCol: boolean;
 	distanceCol: boolean;
 	stopTimer: () => void;
+	isWorkoutPaused: boolean;
 	startTimer: FuncStartTimer;
 	openTimerDrawer: () => void;
 	toBeDisplayedColumns: number;
@@ -1666,18 +1773,15 @@ const SetDisplay = (props: {
 	const [isRpeModalOpen, setIsRpeModalOpen] = useState(false);
 	const [isRpeDetailsOpen, setIsRpeDetailsOpen] = useState(false);
 	const [value, setValue] = useDebouncedState(set?.note || "", 500);
-	const { data: exerciseDetails } = useQuery(
-		getExerciseDetailsQuery(exercise.exerciseId),
-	);
-	const { data: userExerciseDetails } = useQuery(
-		getUserExerciseDetailsQuery(exercise.exerciseId),
-	);
+	const performTasksAfterSetConfirmed = usePerformTasksAfterSetConfirmed();
 
 	const playCheckSound = () => {
 		const sound = new Howl({ src: ["/check.mp3"] });
 		if (!userPreferences.fitness.logging.muteSounds) sound.play();
 	};
 
+	const closeRpeModal = () => setIsRpeModalOpen(false);
+
 	useDidUpdate(() => {
 		if (currentWorkout && isString(value))
 			setCurrentWorkout(
@@ -1692,15 +1796,27 @@ const SetDisplay = (props: {
 	const didCurrentSetActivateTimer =
 		currentTimer?.triggeredBy?.exerciseIdentifier === exercise.identifier &&
 		currentTimer?.triggeredBy?.setIdx === props.setIdx;
-
 	const hasRestTimerOfThisSetElapsed = set.restTimer?.hasElapsed;
+	const promptForRestTimer = userPreferences.fitness.logging.promptForRestTimer;
 
 	return (
 		<>
 			<Modal
 				opened={isRpeModalOpen}
-				title="Rate of Perceived Exertion (RPE)"
-				onClose={() => setIsRpeModalOpen(false)}
+				withCloseButton={false}
+				onClose={closeRpeModal}
+				title={
+					<Group justify="space-between" gap="xl">
+						<Text>Rate of Perceived Exertion</Text>
+						<Button
+							variant="outline"
+							size="compact-xs"
+							onClick={() => setIsRpeDetailsOpen(!isRpeDetailsOpen)}
+						>
+							{isRpeDetailsOpen ? "Hide" : "Show"} instructions
+						</Button>
+					</Group>
+				}
 			>
 				<Stack>
 					<Group>
@@ -1720,13 +1836,10 @@ const SetDisplay = (props: {
 								);
 							}}
 						/>
-						<Button
-							variant="transparent"
-							onClick={() => setIsRpeDetailsOpen(!isRpeDetailsOpen)}
-						>
-							{isRpeDetailsOpen ? "Hide" : "Show"} instructions
-						</Button>
 					</Group>
+					<Button fullWidth variant="outline" onClick={closeRpeModal}>
+						Done
+					</Button>
 					<Collapse in={isRpeDetailsOpen}>
 						<Stack gap="xs">
 							<Text size="xs">
@@ -1975,90 +2088,107 @@ const SetDisplay = (props: {
 							duration={200}
 							timingFunction="ease-in-out"
 						>
-							{(style) => (
-								<ActionIcon
-									variant={set.confirmedAt ? "filled" : "outline"}
-									style={style}
-									disabled={
-										!match(exercise.lot)
-											.with(
-												ExerciseLot.DistanceAndDuration,
-												() =>
-													isString(set.statistic.distance) &&
-													isString(set.statistic.duration),
-											)
-											.with(ExerciseLot.Duration, () =>
-												isString(set.statistic.duration),
-											)
-											.with(ExerciseLot.Reps, () =>
-												isString(set.statistic.reps),
-											)
-											.with(
-												ExerciseLot.RepsAndWeight,
-												() =>
-													isString(set.statistic.reps) &&
-													isString(set.statistic.weight),
-											)
-											.exhaustive()
-									}
-									color="green"
-									onClick={() => {
-										playCheckSound();
-										const newConfirmed = !set.confirmedAt;
-										if (
-											!newConfirmed &&
-											currentTimer?.triggeredBy?.exerciseIdentifier ===
-												exercise.identifier &&
-											currentTimer?.triggeredBy?.setIdx === props.setIdx
-										)
-											props.stopTimer();
-										if (set.restTimer && newConfirmed)
+							{(style) =>
+								set.displayRestTimeTrigger ? (
+									<ActionIcon
+										color="blue"
+										style={style}
+										variant="outline"
+										onClick={() => {
+											invariant(set.restTimer);
 											props.startTimer(set.restTimer.duration, {
-												exerciseIdentifier: exercise.identifier,
 												setIdx: props.setIdx,
+												exerciseIdentifier: exercise.identifier,
 											});
-										setCurrentWorkout(
-											produce(currentWorkout, (draft) => {
-												const currentExercise =
-													draft.exercises[props.exerciseIdx];
-												currentExercise.sets[props.setIdx].confirmedAt =
-													newConfirmed ? dayjsLib().toISOString() : null;
-												currentExercise.scrollMarginRemoved = true;
-												if (newConfirmed) {
-													const nextSet = getNextSetInWorkout(
-														draft,
-														props.exerciseIdx,
-														props.setIdx,
-													);
-													focusOnExercise(nextSet.exerciseIdx);
-													if (nextSet.wasLastSet) {
-														currentExercise.isCollapsed = true;
-														currentExercise.isShowDetailsOpen = false;
-														const nextExercise =
-															draft.exercises[nextSet.exerciseIdx];
-														const nextExerciseHasDetailsToShow =
-															nextExercise &&
-															exerciseHasDetailsToShow(
-																exerciseDetails,
-																userExerciseDetails,
-															);
-														if (nextExerciseHasDetailsToShow) {
-															nextExercise.isCollapsed = false;
-															if (
-																userPreferences.fitness.logging
-																	.showDetailsWhileEditing
-															)
-																nextExercise.isShowDetailsOpen = true;
-														}
-													}
-												}
-											}),
-										);
-									}}
-								>
-									<IconCheck />
-								</ActionIcon>
-							)}
+											setCurrentWorkout(
+												produce(currentWorkout, (draft) => {
+													const currentExercise =
+														draft.exercises[props.exerciseIdx];
+													const currentSet = currentExercise.sets[props.setIdx];
+													currentSet.displayRestTimeTrigger = false;
+													currentSet.restTimerStartedAt =
+														dayjsLib().toISOString();
+												}),
+											);
+										}}
+									>
+										<IconStopwatch />
+									</ActionIcon>
+								) : (
+									<ActionIcon
+										color="green"
+										style={style}
+										variant={set.confirmedAt ? "filled" : "outline"}
+										disabled={
+											!match(exercise.lot)
+												.with(
+													ExerciseLot.DistanceAndDuration,
+													() =>
+														isString(set.statistic.distance) &&
+														isString(set.statistic.duration),
+												)
+												.with(ExerciseLot.Duration, () =>
+													isString(set.statistic.duration),
+												)
+												.with(ExerciseLot.Reps, () =>
+													isString(set.statistic.reps),
+												)
+												.with(
+													ExerciseLot.RepsAndWeight,
+													() =>
+														isString(set.statistic.reps) &&
+														isString(set.statistic.weight),
+												)
+												.exhaustive()
+										}
+										onClick={async () => {
+											playCheckSound();
+											const newConfirmed = !set.confirmedAt;
+											if (
+												!newConfirmed &&
+												currentTimer?.triggeredBy?.exerciseIdentifier ===
+													exercise.identifier &&
+												currentTimer?.triggeredBy?.setIdx === props.setIdx
+											)
+												props.stopTimer();
+											if (set.restTimer && newConfirmed && !promptForRestTimer)
+												props.startTimer(set.restTimer.duration, {
+													setIdx: props.setIdx,
+													exerciseIdentifier: exercise.identifier,
+												});
+											setCurrentWorkout(
+												produce(currentWorkout, (draft) => {
+													if (props.isWorkoutPaused)
+														draft.durations.push({
+															from: dayjsLib().toISOString(),
+														});
+													const currentExercise =
+														draft.exercises[props.exerciseIdx];
+													const currentSet = currentExercise.sets[props.setIdx];
+													currentSet.confirmedAt = newConfirmed
+														? dayjsLib().toISOString()
+														: null;
+													currentExercise.scrollMarginRemoved = true;
+													if (
+														newConfirmed &&
+														promptForRestTimer &&
+														set.restTimer
+													)
+														currentSet.displayRestTimeTrigger = true;
+												}),
+											);
+											if (newConfirmed && !promptForRestTimer) {
+												await performTasksAfterSetConfirmed(
+													props.setIdx,
+													props.exerciseIdx,
+												);
+											}
+										}}
+									>
+										<IconCheck />
+									</ActionIcon>
+								)
+							}
 						</Transition>
 					</Group>
 				</Flex>
@@ -2173,7 +2303,8 @@ const DisplaySetRestTimer = (props: {
 			transitionDuration={300}
 			style={{ cursor: "pointer" }}
 			value={
-				(dayjsLib(props.currentTimer.endAt).diff(dayjsLib(), "seconds") * 100) /
+				(dayjsLib(props.currentTimer.willEndAt).diff(dayjsLib(), "seconds") *
+					100) /
 				props.currentTimer.totalTime
 			}
 		/>
@@ -2193,11 +2324,13 @@ const TimerDrawer = (props: {
 	opened: boolean;
 	onClose: () => void;
 	stopTimer: () => void;
+	pauseOrResumeTimer: () => void;
 	startTimer: (duration: number) => void;
 }) => {
-	forceUpdateEverySecond();
 	const [currentTimer, setCurrentTimer] = useTimerAtom();
 
+	forceUpdateEverySecond();
+
 	return (
 		<Drawer
 			onClose={props.onClose}
@@ -2210,40 +2343,14 @@ const TimerDrawer = (props: {
 			<Stack align="center">
 				{currentTimer ? (
 					<>
-						<RingProgress
-							roundCaps
-							size={300}
-							thickness={8}
-							sections={[
-								{
-									value:
-										(dayjsLib(currentTimer.endAt).diff(dayjsLib(), "seconds") *
-											100) /
-										currentTimer.totalTime,
-									color: "orange",
-								},
-							]}
-							label={
-								<>
-									<Text ta="center" fz={64}>
-										{formatTimerDuration(
-											dayjsLib(currentTimer.endAt).diff(dayjsLib()),
-										)}
-									</Text>
-									<Text ta="center" c="dimmed" fz="lg" mt="-md">
-										{formatTimerDuration(currentTimer.totalTime * 1000)}
-									</Text>
-								</>
-							}
-						/>
-						<Button.Group>
+						<Group gap="xl">
 							<Button
 								color="orange"
 								onClick={() => {
 									setCurrentTimer(
 										produce(currentTimer, (draft) => {
 											if (draft) {
-												draft.endAt = dayjsLib(draft.endAt)
+												draft.willEndAt = dayjsLib(draft.willEndAt)
 													.subtract(30, "seconds")
 													.toISOString();
 												draft.totalTime -= 30;
@@ -2254,18 +2361,23 @@ const TimerDrawer = (props: {
 								size="compact-lg"
 								variant="outline"
 								disabled={
-									dayjsLib(currentTimer.endAt).diff(dayjsLib(), "seconds") <= 30
+									dayjsLib(currentTimer.willEndAt).diff(
+										currentTimer.wasPausedAt,
+										"seconds",
+									) <= 30
 								}
 							>
 								-30 sec
 							</Button>
 							<Button
 								color="orange"
+								size="compact-lg"
+								variant="outline"
 								onClick={() => {
 									setCurrentTimer(
 										produce(currentTimer, (draft) => {
 											if (draft) {
-												draft.endAt = dayjsLib(draft.endAt)
+												draft.willEndAt = dayjsLib(draft.willEndAt)
 													.add(30, "seconds")
 													.toISOString();
 												draft.totalTime += 30;
@@ -2273,52 +2385,94 @@ const TimerDrawer = (props: {
 										}),
 									);
 								}}
-								size="compact-lg"
-								variant="outline"
 							>
 								+30 sec
 							</Button>
+						</Group>
+						<RingProgress
+							roundCaps
+							size={300}
+							thickness={8}
+							sections={[
+								{
+									color: currentTimer.wasPausedAt ? "gray" : "orange",
+									value:
+										(dayjsLib(currentTimer.willEndAt).diff(
+											currentTimer.wasPausedAt,
+											"seconds",
+										) *
+											100) /
+										currentTimer.totalTime,
+								},
+							]}
+							label={
+								<>
+									<Text ta="center" fz={64}>
+										{formatTimerDuration(
+											dayjsLib(currentTimer.willEndAt).diff(
+												currentTimer.wasPausedAt,
+											),
+										)}
+									</Text>
+									<Text ta="center" c="dimmed" fz="lg" mt="-md">
+										{formatTimerDuration(currentTimer.totalTime * 1000)}
+									</Text>
+								</>
+							}
+						/>
+						<Group gap="xl">
+							<Button
+								color="orange"
+								variant="outline"
+								size="compact-lg"
+								onClick={() => {
+									props.pauseOrResumeTimer();
+								}}
+							>
+								{currentTimer.wasPausedAt ? "Resume" : "Pause"}
+							</Button>
 							<Button
 								color="orange"
+								variant="outline"
+								size="compact-lg"
 								onClick={() => {
 									props.onClose();
 									props.stopTimer();
 								}}
-								size="compact-lg"
 							>
 								Skip
 							</Button>
-						</Button.Group>
+						</Group>
 					</>
 				) : (
 					<>
 						<Button
-							size="compact-xl"
 							w={160}
+							size="compact-xl"
 							variant="outline"
 							onClick={() => props.startTimer(180)}
 						>
 							3 minutes
 						</Button>
 						<Button
-							size="compact-xl"
 							w={160}
+							size="compact-xl"
 							variant="outline"
 							onClick={() => props.startTimer(300)}
 						>
 							5 minutes
 						</Button>
 						<Button
-							size="compact-xl"
 							w={160}
+							size="compact-xl"
 							variant="outline"
 							onClick={() => props.startTimer(480)}
 						>
 							8 minutes
 						</Button>
 						<Button
-							size="compact-xl"
 							w={160}
+							size="compact-xl"
 							variant="outline"
 							onClick={() => {
 								const input = prompt("Enter duration in seconds");
@@ -2337,15 +2491,6 @@ const TimerDrawer = (props: {
 	);
 };
 
-const getProgressOfExercise = (cw: InProgressWorkout, index: number) => {
-	const isCompleted = cw.exercises[index].sets.every((s) => s.confirmedAt);
-	return isCompleted
-		? ("complete" as const)
-		: cw.exercises[index].sets.some((s) => s.confirmedAt)
-			? ("in-progress" as const)
-			: ("not-started" as const);
-};
-
 const ReorderDrawer = (props: { opened: boolean; onClose: () => void }) => {
 	const [currentWorkout, setCurrentWorkout] = useCurrentWorkout();
 	const [exerciseElements, exerciseElementsHandlers] = useListState(
@@ -2371,10 +2516,10 @@ const ReorderDrawer = (props: { opened: boolean; onClose: () => void }) => {
 
 	return currentWorkout ? (
 		<Drawer
-			onClose={props.onClose}
-			opened={props.opened}
 			size="sm"
 			styles={styles}
+			opened={props.opened}
+			onClose={props.onClose}
 		>
 			<DragDropContext
 				onDragEnd={({ destination, source }) => {
@@ -2436,9 +2581,9 @@ const ReorderDrawer = (props: { opened: boolean; onClose: () => void }) => {
 };
 
 const NoteInput = (props: {
-	exerciseIdx: number;
-	noteIdx: number;
 	note: string;
+	noteIdx: number;
+	exerciseIdx: number;
 }) => {
 	const [currentWorkout, setCurrentWorkout] = useCurrentWorkout();
 	const [value, setValue] = useDebouncedState(props.note, 500);
@@ -2455,12 +2600,12 @@ const NoteInput = (props: {
 	return (
 		<Flex align="center" gap="xs">
 			<Textarea
-				style={{ flexGrow: 1 }}
-				placeholder="Add a note"
+				autosize
 				size="xs"
 				minRows={1}
 				maxRows={4}
-				autosize
+				style={{ flexGrow: 1 }}
+				placeholder="Add a note"
 				defaultValue={props.note}
 				onChange={(e) => setValue(e.currentTarget.value)}
 			/>
diff --git a/apps/frontend/app/routes/_dashboard.fitness.$entity.$id._index.tsx b/apps/frontend/app/routes/_dashboard.fitness.$entity.$id._index.tsx
index a3dbf48045..7fd808a3eb 100644
--- a/apps/frontend/app/routes/_dashboard.fitness.$entity.$id._index.tsx
+++ b/apps/frontend/app/routes/_dashboard.fitness.$entity.$id._index.tsx
@@ -14,8 +14,6 @@ import {
 	Title,
 } from "@mantine/core";
 import { DateTimePicker } from "@mantine/dates";
-import { $path } from "remix-routes";
-import "@mantine/dates/styles.css";
 import { useDisclosure } from "@mantine/hooks";
 import { notifications } from "@mantine/notifications";
 import type {
@@ -55,6 +53,7 @@ import {
 	IconZzz,
 } from "@tabler/icons-react";
 import { type ReactNode, useState } from "react";
+import { $path } from "remix-routes";
 import { match } from "ts-pattern";
 import { withQuery } from "ufo";
 import { z } from "zod";
diff --git a/apps/frontend/app/routes/_dashboard.fitness.$entity.list.tsx b/apps/frontend/app/routes/_dashboard.fitness.$entity.list.tsx
index 2b9af7bdfe..131ef5f7b7 100644
--- a/apps/frontend/app/routes/_dashboard.fitness.$entity.list.tsx
+++ b/apps/frontend/app/routes/_dashboard.fitness.$entity.list.tsx
@@ -15,7 +15,7 @@ import {
 	Text,
 	Title,
 } from "@mantine/core";
-import { useDisclosure } from "@mantine/hooks";
+import { useDisclosure, useInViewport } from "@mantine/hooks";
 import { notifications } from "@mantine/notifications";
 import type { LoaderFunctionArgs, MetaArgs } from "@remix-run/node";
 import { Link, useLoaderData } from "@remix-run/react";
@@ -198,10 +198,12 @@ export default function Page() {
 const DisplayFitnessEntity = (props: { entityId: string; index: number }) => {
 	const loaderData = useLoaderData<typeof loader>();
 	const unitSystem = useUserUnitSystem();
+	const { ref, inViewport } = useInViewport();
 	const [parent] = useAutoAnimate();
 	const [showDetails, setShowDetails] = useDisclosure(false);
 
 	const { data: entityInformation } = useQuery({
+		enabled: inViewport,
 		queryKey: ["fitnessEntityDetails", props.entityId],
 		queryFn: () =>
 			match(loaderData.entity)
@@ -239,7 +241,7 @@ const DisplayFitnessEntity = (props: { entityId: string; index: number }) => {
 
 	if (!entityInformation)
 		return (
-			<Stack gap={4}>
+			<Stack gap={4} ref={ref}>
 				<Skeleton height={76} />
 				<Group wrap="nowrap" justify="space-between" gap={4}>
 					<Skeleton height={40} />
diff --git a/apps/frontend/app/routes/_dashboard.fitness.exercises.$action.tsx b/apps/frontend/app/routes/_dashboard.fitness.exercises.$action.tsx
index 07c1194d88..4242b20d59 100644
--- a/apps/frontend/app/routes/_dashboard.fitness.exercises.$action.tsx
+++ b/apps/frontend/app/routes/_dashboard.fitness.exercises.$action.tsx
@@ -110,12 +110,11 @@ export const action = async ({ request }: ActionFunctionArgs) => {
 		const intent = getActionIntent(request);
 		return await match(intent)
 			.with(Action.Create, async () => {
-				input.id = "dummy";
 				const { createCustomExercise } =
 					await serverGqlService.authenticatedRequest(
 						request,
 						CreateCustomExerciseDocument,
-						{ input },
+						{ input: { ...input, id: "dummy" } },
 					);
 				return redirect(getExerciseDetailsPath(createCustomExercise));
 			})
@@ -125,7 +124,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
 				await serverGqlService.authenticatedRequest(
 					request,
 					UpdateCustomExerciseDocument,
-					{ input },
+					{ input: { ...input, id } },
 				);
 				const redirectUrl = submission.shouldDelete
 					? $path("/fitness/exercises/list")
diff --git a/apps/frontend/app/routes/_dashboard.media.$action.$lot.tsx b/apps/frontend/app/routes/_dashboard.media.$action.$lot.tsx
index 15b46745bd..9f99ea829c 100644
--- a/apps/frontend/app/routes/_dashboard.media.$action.$lot.tsx
+++ b/apps/frontend/app/routes/_dashboard.media.$action.$lot.tsx
@@ -33,7 +33,6 @@ import {
 	MetadataListDocument,
 	MetadataSearchDocument,
 	type MetadataSearchQuery,
-	type UserReviewScale,
 } from "@ryot/generated/graphql/backend/graphql";
 import { changeCase, snakeCase, startCase } from "@ryot/ts-utils";
 import {
@@ -222,7 +221,6 @@ export const meta = ({ params }: MetaArgs<typeof loader>) => {
 
 export default function Page() {
 	const loaderData = useLoaderData<typeof loader>();
-	const userPreferences = useUserPreferences();
 	const [_, { setP }] = useAppSearchParam(loaderData.cookieName);
 	const [
 		filtersModalOpened,
@@ -238,6 +236,7 @@ export default function Page() {
 		loaderData.mediaList?.url.sortOrder !== defaultFilters.mineSortOrder ||
 		loaderData.mediaList?.url.sortBy !== defaultFilters.mineSortBy ||
 		loaderData.mediaList?.url.collections !== defaultFilters.mineCollection;
+	const mediaSearch = loaderData.mediaSearch;
 
 	return (
 		<Container>
@@ -360,56 +359,47 @@ export default function Page() {
 						) : null}
 					</>
 				) : null}
-				{loaderData.mediaSearch ? (
+				{mediaSearch ? (
 					<>
 						<Flex gap="xs">
 							<DebouncedSearchInput
+								initialValue={loaderData.query.query}
+								enhancedQueryParams={loaderData.cookieName}
 								placeholder={`Sift through your ${changeCase(
 									loaderData.lot.toLowerCase(),
 								).toLowerCase()}s`}
-								initialValue={loaderData.query.query}
-								enhancedQueryParams={loaderData.cookieName}
 							/>
-							{loaderData.mediaSearch.mediaSources.length > 1 ? (
+							{mediaSearch.mediaSources.length > 1 ? (
 								<Select
-									value={loaderData.mediaSearch.url.source}
-									data={loaderData.mediaSearch.mediaSources.map((o) => ({
-										value: o.toString(),
-										label: startCase(o.toLowerCase()),
-									}))}
+									value={mediaSearch.url.source}
 									onChange={(v) => {
 										if (v) setP("source", v);
 									}}
+									data={mediaSearch.mediaSources.map((o) => ({
+										value: o.toString(),
+										label: startCase(o.toLowerCase()),
+									}))}
 								/>
 							) : null}
 						</Flex>
-						{loaderData.mediaSearch.search === false ? (
+						{mediaSearch.search === false ? (
 							<Text>
 								Something is wrong. Please try with an alternate provider.
 							</Text>
-						) : loaderData.mediaSearch.search.details.total > 0 ? (
+						) : mediaSearch.search.details.total > 0 ? (
 							<>
 								<Box>
 									<Text display="inline" fw="bold">
-										{loaderData.mediaSearch.search.details.total}
+										{mediaSearch.search.details.total}
 									</Text>{" "}
 									items found
 								</Box>
 								<ApplicationGrid>
-									{loaderData.mediaSearch.search.items.map((b, idx) => (
+									{mediaSearch.search.items.map((b) => (
 										<MediaSearchItem
-											idx={idx}
-											action={Action.Search}
-											key={b.item.identifier}
 											item={b}
-											maybeItemId={b.databaseId ?? undefined}
-											hasInteracted={b.hasInteracted}
-											lot={loaderData.lot}
-											source={
-												loaderData.mediaSearch?.url.source ||
-												MediaSource.Anilist
-											}
-											reviewScale={userPreferences.general.reviewScale}
+											key={b.identifier}
+											source={mediaSearch.url.source}
 										/>
 									))}
 								</ApplicationGrid>
@@ -417,7 +407,7 @@ export default function Page() {
 						) : (
 							<Text>No media found matching your query</Text>
 						)}
-						{loaderData.mediaSearch.search ? (
+						{mediaSearch.search ? (
 							<Center>
 								<Pagination
 									size="sm"
@@ -435,16 +425,11 @@ export default function Page() {
 }
 
 const MediaSearchItem = (props: {
-	item: MetadataSearchQuery["metadataSearch"]["items"][number];
-	idx: number;
-	lot: MediaLot;
 	source: MediaSource;
-	action: "search" | "list";
-	hasInteracted: boolean;
-	reviewScale: UserReviewScale;
-	maybeItemId?: string;
+	item: MetadataSearchQuery["metadataSearch"]["items"][number];
 }) => {
 	const navigate = useNavigate();
+	const loaderData = useLoaderData<typeof loader>();
 	const userDetails = useUserDetails();
 	const userPreferences = useUserPreferences();
 	const gridPacking = userPreferences.general.gridPacking;
@@ -453,13 +438,16 @@ const MediaSearchItem = (props: {
 	const events = useApplicationEvents();
 	const [_, setMetadataToUpdate] = useMetadataProgressUpdate();
 	const [_a, setAddEntityToCollectionData] = useAddEntityToCollection();
+
+	const buttonSize =
+		gridPacking === GridPacking.Normal ? "compact-md" : "compact-xs";
+
 	const basicCommit = async () => {
-		if (props.maybeItemId) return props.maybeItemId;
 		setIsLoading(true);
 		const data = new FormData();
-		data.append("name", props.item.item.title);
-		data.append("identifier", props.item.item.identifier);
-		data.append("lot", props.lot);
+		data.append("name", props.item.title);
+		data.append("identifier", props.item.identifier);
+		data.append("lot", loaderData.lot);
 		data.append("source", props.source);
 		const resp = await fetch($path("/actions", { intent: "commitMedia" }), {
 			method: "POST",
@@ -471,33 +459,27 @@ const MediaSearchItem = (props: {
 		return response;
 	};
 
-	const buttonSize =
-		gridPacking === GridPacking.Normal ? "compact-md" : "compact-xs";
 	return (
 		<Box>
 			<BaseMediaDisplayItem
 				isLoading={false}
-				name={props.item.item.title}
-				onImageClickBehavior={async () => {
-					setIsLoading(true);
-					const id = await basicCommit();
-					setIsLoading(false);
-					navigate($path("/media/item/:id", { id }));
-				}}
+				name={props.item.title}
+				imageUrl={props.item.image}
 				labels={{
-					left: props.item.item.publishYear,
-					right: (
-						<Text c={props.hasInteracted ? "yellow" : undefined}>
-							{changeCase(snakeCase(props.lot))}
-						</Text>
-					),
+					left: props.item.publishYear,
+					right: <Text>{changeCase(snakeCase(loaderData.lot))}</Text>,
 				}}
-				imageUrl={props.item.item.image}
 				imageOverlay={{
 					topLeft: isLoading ? (
 						<Loader color="red" variant="bars" size="sm" m={2} />
 					) : null,
 				}}
+				onImageClickBehavior={async () => {
+					setIsLoading(true);
+					const id = await basicCommit();
+					setIsLoading(false);
+					navigate($path("/media/item/:id", { id }));
+				}}
 				nameRight={
 					<Menu shadow="md">
 						<Menu.Target>
@@ -532,7 +514,7 @@ const MediaSearchItem = (props: {
 						setMetadataToUpdate({ metadataId });
 					}}
 				>
-					Mark as {getVerb(Verb.Read, props.lot)}
+					Mark as {getVerb(Verb.Read, loaderData.lot)}
 				</Button>
 				<Button
 					w="100%"
diff --git a/apps/frontend/app/routes/_dashboard.settings.imports-and-exports._index.tsx b/apps/frontend/app/routes/_dashboard.settings.imports-and-exports._index.tsx
index a007c8740c..f7f4808353 100644
--- a/apps/frontend/app/routes/_dashboard.settings.imports-and-exports._index.tsx
+++ b/apps/frontend/app/routes/_dashboard.settings.imports-and-exports._index.tsx
@@ -1,9 +1,9 @@
+import { CodeHighlight } from "@mantine/code-highlight";
 import {
 	Accordion,
 	Anchor,
 	Box,
 	Button,
-	Code,
 	Container,
 	Divider,
 	FileInput,
@@ -453,25 +453,34 @@ export default function Page() {
 															/>
 														) : null}
 													</Accordion.Control>
-													<Accordion.Panel>
+													<Accordion.Panel
+														styles={{ content: { paddingTop: 0 } }}
+													>
 														{report.details ? (
-															<>
+															<Stack>
 																<Text>
-																	Total imported: {report.details.import.total}
-																</Text>
-																<Text>
-																	Failed: {report.details.failedItems.length}
+																	<Text span fw="bold" mr={4}>
+																		Total imported:
+																	</Text>
+																	{report.details.import.total},
+																	<Text span fw="bold" ml="md" mr={4}>
+																		Failed:
+																	</Text>
+																	{report.details.failedItems.length}
 																</Text>
 																{report.details.failedItems.length > 0 ? (
-																	<Code mah={400} block>
-																		{JSON.stringify(
+																	<CodeHighlight
+																		mah={400}
+																		language="json"
+																		style={{ overflow: "scroll" }}
+																		code={JSON.stringify(
 																			report.details.failedItems,
 																			null,
-																			4,
+																			2,
 																		)}
-																	</Code>
+																	/>
 																) : null}
-															</>
+															</Stack>
 														) : (
 															<Text>This import never finished</Text>
 														)}
diff --git a/apps/frontend/app/routes/_dashboard.settings.miscellaneous.tsx b/apps/frontend/app/routes/_dashboard.settings.miscellaneous.tsx
index 9b5de45f5c..d27d54f050 100644
--- a/apps/frontend/app/routes/_dashboard.settings.miscellaneous.tsx
+++ b/apps/frontend/app/routes/_dashboard.settings.miscellaneous.tsx
@@ -15,6 +15,7 @@ import {
 	UserLot,
 } from "@ryot/generated/graphql/backend/graphql";
 import { processSubmission } from "@ryot/ts-utils";
+import { match } from "ts-pattern";
 import { z } from "zod";
 import { useDashboardLayoutData, useUserDetails } from "~/lib/hooks";
 import { createToastHeaders, serverGqlService } from "~/lib/utilities.server";
@@ -44,10 +45,6 @@ const jobSchema = z.object({
 });
 
 export default function Page() {
-	const userDetails = useUserDetails();
-	const dashboardData = useDashboardLayoutData();
-	const isEditDisabled = dashboardData.isDemo;
-
 	return (
 		<Container size="lg">
 			<Form replace method="POST">
@@ -57,132 +54,9 @@ export default function Page() {
 						cols={{ base: 1, lg: 2 }}
 						spacing={{ base: "xl", md: "md" }}
 					>
-						{userDetails.lot === UserLot.Admin ? (
-							<>
-								<Stack>
-									<Box>
-										<Title order={4}>Update all metadata</Title>
-										<Text>
-											Fetch and update the metadata for all the media items that
-											are stored. The more media you have, the longer this will
-											take. This also updates people and group data from remote
-											providers.
-										</Text>
-									</Box>
-									<Button
-										disabled={isEditDisabled}
-										{...btnProps}
-										value={BackgroundJob.UpdateAllMetadata}
-									>
-										Update metadata
-									</Button>
-								</Stack>
-								<Stack>
-									<Box>
-										<Title order={4}>Update Calendar Events</Title>
-										<Text>
-											Create any pending calendar events, or delete ones that
-											have changed. Useful if you have added new media items or
-											publish dates have changed. This is run every 24 hours
-											automatically.
-										</Text>
-									</Box>
-									<Button
-										disabled={isEditDisabled}
-										{...btnProps}
-										value={BackgroundJob.RecalculateCalendarEvents}
-									>
-										Update calendar events
-									</Button>
-								</Stack>
-								<Stack>
-									<Box>
-										<Title order={4}>Update Exercises</Title>
-										<Text>
-											Update the exercise database. Exercise data is downloaded
-											on startup but they can be updated manually. Trigger this
-											job when there are new exercises available.
-										</Text>
-									</Box>
-									<Button
-										disabled={isEditDisabled}
-										{...btnProps}
-										value={BackgroundJob.UpdateAllExercises}
-									>
-										Update exercises
-									</Button>
-								</Stack>
-								<Stack>
-									<Box>
-										<Title order={4}>Perform all background tasks</Title>
-										<Text>
-											Update the user summaries, recalculate media associations
-											for all users, update all monitored entities and remove
-											useless data. The more users you have, the longer this
-											will take.
-										</Text>
-									</Box>
-									<Button
-										disabled={isEditDisabled}
-										{...btnProps}
-										value={BackgroundJob.PerformBackgroundTasks}
-									>
-										Perform background tasks
-									</Button>
-								</Stack>
-							</>
-						) : null}
-						<Stack>
-							<Box>
-								<Title order={4}>Regenerate Summaries</Title>
-								<Text>
-									Regenerate all pre-computed summaries from the beginning. This
-									may be useful if, for some reason, summaries are faulty or
-									preconditions have changed. This may take some time.
-								</Text>
-							</Box>
-							<Button
-								disabled={isEditDisabled}
-								{...btnProps}
-								value={BackgroundJob.CalculateUserActivitiesAndSummary}
-							>
-								Clean and regenerate
-							</Button>
-						</Stack>
-						<Stack>
-							<Box>
-								<Title order={4}>Revise workouts</Title>
-								<Text>
-									Revise all workouts. This may be useful if exercises done
-									during a workout have changed or workouts have been edited or
-									deleted.
-								</Text>
-							</Box>
-							<Button
-								disabled={isEditDisabled}
-								{...btnProps}
-								value={BackgroundJob.ReviseUserWorkouts}
-							>
-								Revise workouts
-							</Button>
-						</Stack>
-						<Stack>
-							<Box>
-								<Title order={4}>Synchronize integrations progress</Title>
-								<Text>
-									Get/push data for all configured integrations and update
-									progress if applicable. The more integrations you have
-									enabled, the longer this will take.
-								</Text>
-							</Box>
-							<Button
-								disabled={isEditDisabled}
-								{...btnProps}
-								value={BackgroundJob.SyncIntegrationsData}
-							>
-								Synchronize
-							</Button>
-						</Stack>
+						{Object.values(BackgroundJob).map((job) => (
+							<DisplayJobBtn key={job} job={job} />
+						))}
 					</SimpleGrid>
 				</Stack>
 			</Form>
@@ -190,8 +64,92 @@ export default function Page() {
 	);
 }
 
-const btnProps = {
-	variant: "light",
-	type: "submit" as const,
-	name: "jobName",
+const DisplayJobBtn = (props: { job: BackgroundJob }) => {
+	const userDetails = useUserDetails();
+	const dashboardData = useDashboardLayoutData();
+	const isEditDisabled = dashboardData.isDemo;
+
+	const [title, description, isAdminOnly] = match(props.job)
+		.with(
+			BackgroundJob.UpdateAllMetadata,
+			() =>
+				[
+					"Update all metadata",
+					"Fetch and update the metadata for all the media items that are stored. The more media you have, the longer this will take. This also updates people and group data from remote providers.",
+					true,
+				] as const,
+		)
+		.with(
+			BackgroundJob.UpdateAllExercises,
+			() =>
+				[
+					"Update all exercises",
+					"Update the exercise database. Exercise data is downloaded on startup but they can be updated manually. Trigger this job when there are new exercises available.",
+					true,
+				] as const,
+		)
+		.with(
+			BackgroundJob.PerformBackgroundTasks,
+			() =>
+				[
+					"Perform background tasks",
+					"Update the user summaries, recalculate media associations for all users, update all monitored entities and remove useless data. The more users you have, the longer this will take.",
+					true,
+				] as const,
+		)
+		.with(
+			BackgroundJob.DeleteAllApplicationCache,
+			() =>
+				[
+					"Delete all cache",
+					"Delete all application caches. Use this if you updated a critical configuration parameter and can not see the changes reflected in the UI.",
+					true,
+				] as const,
+		)
+		.with(
+			BackgroundJob.CalculateUserActivitiesAndSummary,
+			() =>
+				[
+					"Regenerate Summaries",
+					"Regenerate all pre-computed summaries from the beginning. This may be useful if, for some reason, summaries are faulty or preconditions have changed. This may take some time.",
+				] as const,
+		)
+		.with(
+			BackgroundJob.ReviseUserWorkouts,
+			() =>
+				[
+					"Revise workouts",
+					"Revise all workouts. This may be useful if exercises done during a workout have changed or workouts have been edited or deleted.",
+				] as const,
+		)
+		.with(
+			BackgroundJob.SyncIntegrationsData,
+			() =>
+				[
+					"Synchronize integrations progress",
+					"Get/push data for all configured integrations and update progress if applicable. The more integrations you have enabled, the longer this will take.",
+				] as const,
+		)
+		.exhaustive();
+
+	if (isAdminOnly && userDetails.lot !== UserLot.Admin) return null;
+
+	return (
+		<Stack>
+			<Box>
+				<Title order={4}>{title}</Title>
+				<Text>{description}</Text>
+			</Box>
+			<Button
+				mt="auto"
+				type="submit"
+				name="jobName"
+				variant="light"
+				value={props.job}
+				disabled={isEditDisabled}
+			>
+				{title}
+			</Button>
+		</Stack>
+	);
 };
diff --git a/apps/frontend/app/routes/_dashboard.settings.notifications.tsx b/apps/frontend/app/routes/_dashboard.settings.notifications.tsx
index 36f9b00f50..3b2d003706 100644
--- a/apps/frontend/app/routes/_dashboard.settings.notifications.tsx
+++ b/apps/frontend/app/routes/_dashboard.settings.notifications.tsx
@@ -22,7 +22,7 @@ import type {
 	LoaderFunctionArgs,
 	MetaArgs,
 } from "@remix-run/node";
-import { Form, useLoaderData } from "@remix-run/react";
+import { Form, Link, useLoaderData } from "@remix-run/react";
 import {
 	CreateUserNotificationPlatformDocument,
 	DeleteUserNotificationPlatformDocument,
@@ -39,6 +39,7 @@ import {
 	IconTrash,
 } from "@tabler/icons-react";
 import { useState } from "react";
+import { $path } from "remix-routes";
 import { match } from "ts-pattern";
 import { withQuery } from "ufo";
 import { z } from "zod";
@@ -156,20 +157,137 @@ export default function Page() {
 	] = useState<NotificationPlatformLot>();
 
 	return (
-		<Container size="xs">
-			<Stack>
-				<Title>Notification settings</Title>
-				{loaderData.userNotificationPlatforms.length > 0 ? (
-					loaderData.userNotificationPlatforms.map((notification) => (
-						<DisplayNotification
-							key={notification.id}
-							notification={notification}
+		<>
+			<Modal
+				opened={createUserNotificationPlatformModalOpened}
+				onClose={closeCreateUserNotificationPlatformModal}
+				centered
+				withCloseButton={false}
+			>
+				<Box
+					method="POST"
+					component={Form}
+					action={withQuery(".", { intent: "create" })}
+					onSubmit={() => {
+						closeCreateUserNotificationPlatformModal();
+						setCreateUserNotificationPlatformLot(undefined);
+					}}
+				>
+					<input hidden name="lot" value={createUserNotificationPlatformLot} />
+					<Stack>
+						<Select
+							required
+							searchable
+							label="Select a platform"
+							data={Object.values(NotificationPlatformLot).map((v) => ({
+								label: changeCase(v),
+								value: v,
+							}))}
+							onChange={(v) => {
+								if (v)
+									setCreateUserNotificationPlatformLot(
+										v as NotificationPlatformLot,
+									);
+							}}
 						/>
-					))
-				) : (
-					<Text>No notification platforms configured</Text>
-				)}
-				<Box>
+						{createUserNotificationPlatformLot
+							? match(createUserNotificationPlatformLot)
+									.with(NotificationPlatformLot.Apprise, () => (
+										<>
+											<TextInput label="Base Url" required name="baseUrl" />
+											<TextInput label="Key" required name="apiToken" />
+										</>
+									))
+									.with(NotificationPlatformLot.Discord, () => (
+										<>
+											<TextInput label="Webhook Url" required name="baseUrl" />
+										</>
+									))
+									.with(NotificationPlatformLot.Gotify, () => (
+										<>
+											<TextInput label="Server Url" required name="baseUrl" />
+											<TextInput label="Token" required name="apiToken" />
+											<NumberInput label="Priority" name="priority" />
+										</>
+									))
+									.with(NotificationPlatformLot.Ntfy, () => (
+										<>
+											<TextInput label="Topic" required name="apiToken" />
+											<TextInput label="Server Url" name="baseUrl" />
+											<TextInput
+												label="Access token"
+												description={
+													<>
+														If you want to publish to a{" "}
+														<Anchor
+															size="xs"
+															href="https://docs.ntfy.sh/publish/#access-tokens"
+															target="_blank"
+														>
+															protected topic
+														</Anchor>
+													</>
+												}
+												name="authHeader"
+											/>
+											<NumberInput label="Priority" name="priority" />
+										</>
+									))
+									.with(NotificationPlatformLot.PushBullet, () => (
+										<>
+											<TextInput label="Token" required name="apiToken" />
+										</>
+									))
+									.with(NotificationPlatformLot.PushOver, () => (
+										<>
+											<TextInput label="User Key" required name="apiToken" />
+											<TextInput label="App Key" name="authHeader" />
+										</>
+									))
+									.with(NotificationPlatformLot.PushSafer, () => (
+										<>
+											<TextInput label="Key" required name="apiToken" />
+										</>
+									))
+									.with(NotificationPlatformLot.Telegram, () => (
+										<>
+											<TextInput label="Bot Token" required name="apiToken" />
+											<TextInput label="Chat ID" required name="chatId" />
+										</>
+									))
+									.with(NotificationPlatformLot.Email, () => (
+										<>
+											<TextInput
+												type="email"
+												label="Email ID"
+												required
+												name="apiToken"
+												disabled={!coreDetails.smtpEnabled}
+												description={
+													coreDetails.smtpEnabled ? null : "SMTP is not enabled"
+												}
+											/>
+										</>
+									))
+									.exhaustive()
+							: null}
+						<Button type="submit">Submit</Button>
+					</Stack>
+				</Box>
+			</Modal>
+			<Container size="xs">
+				<Stack>
+					<Title>Notification settings</Title>
+					{loaderData.userNotificationPlatforms.length > 0 ? (
+						loaderData.userNotificationPlatforms.map((notification) => (
+							<DisplayNotification
+								key={notification.id}
+								notification={notification}
+							/>
+						))
+					) : (
+						<Text>No notification platforms configured</Text>
+					)}
 					<Flex justify="end">
 						<Group>
 							{loaderData.userNotificationPlatforms.length > 0 ? (
@@ -192,147 +310,21 @@ export default function Page() {
 							</Button>
 						</Group>
 					</Flex>
-					<Modal
-						opened={createUserNotificationPlatformModalOpened}
-						onClose={closeCreateUserNotificationPlatformModal}
-						centered
-						withCloseButton={false}
-					>
-						<Box
-							method="POST"
-							component={Form}
-							action={withQuery(".", { intent: "create" })}
-							onSubmit={() => {
-								closeCreateUserNotificationPlatformModal();
-								setCreateUserNotificationPlatformLot(undefined);
-							}}
+					<Text size="xs" ta="right">
+						For more settings, please visit the{" "}
+						<Anchor
+							component={Link}
+							to={$path("/settings/preferences", {
+								defaultTab: "notifications",
+							})}
 						>
-							<input
-								hidden
-								name="lot"
-								value={createUserNotificationPlatformLot}
-							/>
-							<Stack>
-								<Select
-									label="Select a platform"
-									required
-									data={Object.values(NotificationPlatformLot).map((v) => ({
-										label: changeCase(v),
-										value: v,
-									}))}
-									onChange={(v) => {
-										if (v)
-											setCreateUserNotificationPlatformLot(
-												v as NotificationPlatformLot,
-											);
-									}}
-								/>
-								{createUserNotificationPlatformLot
-									? match(createUserNotificationPlatformLot)
-											.with(NotificationPlatformLot.Apprise, () => (
-												<>
-													<TextInput label="Base Url" required name="baseUrl" />
-													<TextInput label="Key" required name="apiToken" />
-												</>
-											))
-											.with(NotificationPlatformLot.Discord, () => (
-												<>
-													<TextInput
-														label="Webhook Url"
-														required
-														name="baseUrl"
-													/>
-												</>
-											))
-											.with(NotificationPlatformLot.Gotify, () => (
-												<>
-													<TextInput
-														label="Server Url"
-														required
-														name="baseUrl"
-													/>
-													<TextInput label="Token" required name="apiToken" />
-													<NumberInput label="Priority" name="priority" />
-												</>
-											))
-											.with(NotificationPlatformLot.Ntfy, () => (
-												<>
-													<TextInput label="Topic" required name="apiToken" />
-													<TextInput label="Server Url" name="baseUrl" />
-													<TextInput
-														label="Access token"
-														description={
-															<>
-																If you want to publish to a{" "}
-																<Anchor
-																	size="xs"
-																	href="https://docs.ntfy.sh/publish/#access-tokens"
-																	target="_blank"
-																>
-																	protected topic
-																</Anchor>
-															</>
-														}
-														name="authHeader"
-													/>
-													<NumberInput label="Priority" name="priority" />
-												</>
-											))
-											.with(NotificationPlatformLot.PushBullet, () => (
-												<>
-													<TextInput label="Token" required name="apiToken" />
-												</>
-											))
-											.with(NotificationPlatformLot.PushOver, () => (
-												<>
-													<TextInput
-														label="User Key"
-														required
-														name="apiToken"
-													/>
-													<TextInput label="App Key" name="authHeader" />
-												</>
-											))
-											.with(NotificationPlatformLot.PushSafer, () => (
-												<>
-													<TextInput label="Key" required name="apiToken" />
-												</>
-											))
-											.with(NotificationPlatformLot.Telegram, () => (
-												<>
-													<TextInput
-														label="Bot Token"
-														required
-														name="apiToken"
-													/>
-													<TextInput label="Chat ID" required name="chatId" />
-												</>
-											))
-											.with(NotificationPlatformLot.Email, () => (
-												<>
-													<TextInput
-														type="email"
-														label="Email ID"
-														required
-														name="apiToken"
-														disabled={!coreDetails.smtpEnabled}
-														description={
-															coreDetails.smtpEnabled
-																? null
-																: "SMTP is not enabled"
-														}
-													/>
-												</>
-											))
-											.exhaustive()
-									: null}
-								<Button type="submit">Submit</Button>
-							</Stack>
-						</Box>
-					</Modal>
-				</Box>
-			</Stack>
-		</Container>
+							notification preferences
+						</Anchor>
+						.
+					</Text>
+				</Stack>
+			</Container>
+		</>
 	);
 }
 
diff --git a/apps/frontend/app/routes/_dashboard.settings.preferences.tsx b/apps/frontend/app/routes/_dashboard.settings.preferences.tsx
index 3b31d301d9..c4b973a2a6 100644
--- a/apps/frontend/app/routes/_dashboard.settings.preferences.tsx
+++ b/apps/frontend/app/routes/_dashboard.settings.preferences.tsx
@@ -59,9 +59,11 @@ import { match } from "ts-pattern";
 import { z } from "zod";
 import { zx } from "zodix";
 import { confirmWrapper } from "~/components/confirmation";
+import { PRO_REQUIRED_MESSAGE } from "~/lib/generals";
 import {
 	useComplexJsonUpdate,
 	useConfirmSubmit,
+	useCoreDetails,
 	useDashboardLayoutData,
 	useIsFitnessActionActive,
 	useUserPreferences,
@@ -118,6 +120,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
 
 export default function Page() {
 	const loaderData = useLoaderData<typeof loader>();
+	const coreDetails = useCoreDetails();
 	const userPreferences = useUserPreferences();
 	const submit = useConfirmSubmit();
 	const isFitnessActionActive = useIsFitnessActionActive();
@@ -433,7 +436,7 @@ export default function Page() {
 								The notifications you want to receive in your configured
 								providers.
 							</Text>
-							<SimpleGrid cols={2}>
+							<SimpleGrid cols={{ md: 2 }}>
 								{Object.values(MediaStateChanged).map((name) => (
 									<Switch
 										key={name}
@@ -579,33 +582,61 @@ export default function Page() {
 								</SimpleGrid>
 							</Input.Wrapper>
 							<Divider />
-							{(["muteSounds", "showDetailsWhileEditing"] as const).map(
-								(option) => (
+							{(
+								[
+									"muteSounds",
+									"promptForRestTimer",
+									"showDetailsWhileEditing",
+								] as const
+							).map((option) => {
+								const [label, isGatedBehindServerKeyValidation] = match(option)
+									.with(
+										"muteSounds",
+										() => ["Mute sounds while logging workouts"] as const,
+									)
+									.with(
+										"promptForRestTimer",
+										() =>
+											[
+												"Prompt for rest timer when confirming sets",
+												true,
+											] as const,
+									)
+									.with(
+										"showDetailsWhileEditing",
+										() =>
+											[
+												"Show details and history while editing workouts/templates",
+											] as const,
+									)
+									.exhaustive();
+
+								return (
 									<Switch
-										key={option}
-										label={match(option)
-											.with(
-												"muteSounds",
-												() => "Mute sounds while logging workouts",
-											)
-											.with(
-												"showDetailsWhileEditing",
-												() =>
-													"Show details and history while editing workouts/templates",
-											)
-											.exhaustive()}
 										size="xs"
+										key={option}
+										label={label}
 										disabled={!!isEditDisabled}
 										defaultChecked={userPreferences.fitness.logging[option]}
 										onChange={(ev) => {
+											if (
+												isGatedBehindServerKeyValidation &&
+												!coreDetails.isServerKeyValidated
+											) {
+												notifications.show({
+													color: "red",
+													message: PRO_REQUIRED_MESSAGE,
+												});
+												return;
+											}
 											appendPref(
 												`fitness.logging.${snakeCase(option)}`,
 												String(ev.currentTarget.checked),
 											);
 										}}
 									/>
-								),
-							)}
+								);
+							})}
 							<Divider />
 							<Input.Wrapper label="The default measurements you want to keep track of">
 								<SimpleGrid cols={2} mt="xs">
diff --git a/apps/frontend/app/routes/_dashboard.tsx b/apps/frontend/app/routes/_dashboard.tsx
index 8458335f8c..36642be3ec 100644
--- a/apps/frontend/app/routes/_dashboard.tsx
+++ b/apps/frontend/app/routes/_dashboard.tsx
@@ -47,6 +47,7 @@ import {
 	useDisclosure,
 	useLocalStorage,
 } from "@mantine/hooks";
+import type { LoaderFunctionArgs } from "@remix-run/node";
 import {
 	Form,
 	Link,
@@ -103,10 +104,13 @@ import {
 	IconStretching,
 	IconSun,
 } from "@tabler/icons-react";
+import { useQuery } from "@tanstack/react-query";
 import { produce } from "immer";
+import Cookies from "js-cookie";
 import { type FormEvent, useState } from "react";
 import { Fragment } from "react/jsx-runtime";
 import { $path } from "remix-routes";
+import { ClientOnly } from "remix-utils/client-only";
 import { match } from "ts-pattern";
 import { joinURL, withQuery } from "ufo";
 import {
@@ -132,6 +136,7 @@ import {
 	useUserMetadataDetails,
 	useUserPreferences,
 } from "~/lib/hooks";
+import { useBulkEditCollection } from "~/lib/state/collection";
 import { useMeasurementsDrawerOpen } from "~/lib/state/fitness";
 import {
 	type UpdateProgressData,
@@ -140,20 +145,14 @@ import {
 	useReviewEntity,
 } from "~/lib/state/media";
 import {
-	getUserCollectionsList,
 	getCookieValue,
 	getCoreDetails,
 	getDecodedJwt,
+	getUserCollectionsList,
 	getUserPreferences,
 	redirectIfNotAuthenticatedOrUpdated,
 } from "~/lib/utilities.server";
 import { colorSchemeCookie } from "~/lib/utilities.server";
-import "@mantine/dates/styles.css";
-import type { LoaderFunctionArgs } from "@remix-run/node";
-import { useQuery } from "@tanstack/react-query";
-import Cookies from "js-cookie";
-import { ClientOnly } from "remix-utils/client-only";
-import { useBulkEditCollection } from "~/lib/state/collection";
 import classes from "~/styles/dashboard.module.css";
 
 const discordLink = "https://discord.gg/D9XTg2a7R8";
diff --git a/apps/frontend/package.json b/apps/frontend/package.json
index 13bf70c04b..1755b5c0e1 100644
--- a/apps/frontend/package.json
+++ b/apps/frontend/package.json
@@ -14,6 +14,7 @@
 		"@lukemorales/query-key-factory": "1.3.4",
 		"@mantine/carousel": "7.14.3",
 		"@mantine/charts": "7.14.3",
+		"@mantine/code-highlight": "7.14.3",
 		"@mantine/core": "7.14.3",
 		"@mantine/dates": "7.14.3",
 		"@mantine/form": "7.14.3",
@@ -30,7 +31,6 @@
 		"@tabler/icons-react": "3.17.0",
 		"@tanstack/react-query": "5.62.2",
 		"@tanstack/react-query-devtools": "5.62.2",
-		"buffer": "6.0.3",
 		"clsx": "2.1.1",
 		"cookie": "1.0.2",
 		"crypto-js": "4.2.0",
@@ -54,7 +54,6 @@
 		"react-confirm": "0.3.0-7",
 		"react-dom": "18.3.1",
 		"react-virtuoso": "4.12.3",
-		"react-webcam": "7.2.0",
 		"recharts": "2.14.1",
 		"remix-routes": "1.7.7",
 		"remix-utils": "7.7.0",
diff --git a/apps/website/app/root.tsx b/apps/website/app/root.tsx
index ab70b78ebe..600574f1d4 100644
--- a/apps/website/app/root.tsx
+++ b/apps/website/app/root.tsx
@@ -22,6 +22,16 @@ import { Toaster } from "./lib/components/ui/sonner";
 import { getCustomerFromCookie, honeypot } from "./lib/config.server";
 import { logoUrl, startUrl } from "./lib/utils";
 
+export const meta: MetaFunction = () => {
+	return [
+		{ title: "Ryot" },
+		{
+			name: "description",
+			content: "The only self hosted tracker you will ever need.",
+		},
+	];
+};
+
 export const links: LinksFunction = () => {
 	return [
 		{
@@ -51,10 +61,6 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
 	};
 };
 
-export const meta: MetaFunction = () => {
-	return [{ title: "Ryot" }];
-};
-
 export default function App() {
 	const loaderData = useLoaderData<typeof loader>();
 
diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs
index 281693bb80..57d83159d4 100644
--- a/crates/migrations/src/lib.rs
+++ b/crates/migrations/src/lib.rs
@@ -54,6 +54,7 @@ mod m20241126_changes_for_issue_1113;
 mod m20241129_changes_for_issue_1114;
 mod m20241214_create_user_notification;
 mod m20241215_changes_for_issue_1131;
+mod m20241220_changes_for_issue_1141;
 mod m20241220_changes_for_issue_49;
 
 pub use m20230410_create_metadata::Metadata as AliasedMetadata;
@@ -131,6 +132,7 @@ impl MigratorTrait for Migrator {
             Box::new(m20241214_create_user_notification::Migration),
             Box::new(m20241215_changes_for_issue_1131::Migration),
             Box::new(m20241220_changes_for_issue_49::Migration),
+            Box::new(m20241220_changes_for_issue_1141::Migration),
         ]
     }
 }
diff --git a/crates/migrations/src/m20230819_create_workout.rs b/crates/migrations/src/m20230819_create_workout.rs
index b770f849ba..fe159ba1b2 100644
--- a/crates/migrations/src/m20230819_create_workout.rs
+++ b/crates/migrations/src/m20230819_create_workout.rs
@@ -44,12 +44,7 @@ impl MigrationTrait for Migration {
                             .timestamp_with_time_zone()
                             .not_null(),
                     )
-                    .col(
-                        ColumnDef::new(Workout::Duration)
-                            .integer()
-                            .not_null()
-                            .extra("GENERATED ALWAYS AS (EXTRACT(EPOCH FROM (end_time - start_time))) STORED")
-                    )
+                    .col(ColumnDef::new(Workout::Duration).integer().not_null())
                     .col(ColumnDef::new(Workout::Summary).json_binary().not_null())
                     .col(
                         ColumnDef::new(Workout::Information)
diff --git a/crates/migrations/src/m20240827_create_daily_user_activity.rs b/crates/migrations/src/m20240827_create_daily_user_activity.rs
index 5a511e4040..5882005025 100644
--- a/crates/migrations/src/m20240827_create_daily_user_activity.rs
+++ b/crates/migrations/src/m20240827_create_daily_user_activity.rs
@@ -158,6 +158,7 @@ pub async fn create_daily_user_activity_table(manager: &SchemaManager<'_>) -> Re
             Index::create()
                 .name(DAILY_USER_ACTIVITY_COMPOSITE_UNIQUE_KEY)
                 .unique()
+                .nulls_not_distinct()
                 .table(DailyUserActivity::Table)
                 .col(DailyUserActivity::UserId)
                 .col(DailyUserActivity::Date)
diff --git a/crates/migrations/src/m20241004_create_application_cache.rs b/crates/migrations/src/m20241004_create_application_cache.rs
index 718c823d98..f6cf24f6f3 100644
--- a/crates/migrations/src/m20241004_create_application_cache.rs
+++ b/crates/migrations/src/m20241004_create_application_cache.rs
@@ -9,6 +9,7 @@ pub enum ApplicationCache {
     Id,
     Key,
     Value,
+    Version,
     ExpiresAt,
     CreatedAt,
 }
@@ -45,6 +46,7 @@ impl MigrationTrait for Migration {
                             .json_binary()
                             .not_null(),
                     )
+                    .col(ColumnDef::new(ApplicationCache::Version).text().not_null())
                     .to_owned(),
             )
             .await?;
diff --git a/crates/migrations/src/m20241220_changes_for_issue_1141.rs b/crates/migrations/src/m20241220_changes_for_issue_1141.rs
new file mode 100644
index 0000000000..2da9abf3fe
--- /dev/null
+++ b/crates/migrations/src/m20241220_changes_for_issue_1141.rs
@@ -0,0 +1,47 @@
+use sea_orm_migration::prelude::*;
+
+use crate::m20240827_create_daily_user_activity::DAILY_USER_ACTIVITY_COMPOSITE_UNIQUE_KEY;
+
+#[derive(DeriveMigrationName)]
+pub struct Migration;
+
+#[async_trait::async_trait]
+impl MigrationTrait for Migration {
+    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
+        let db = manager.get_connection();
+        db.execute_unprepared(&format!(
+            r#"
+UPDATE "user"
+SET "preferences" = jsonb_set(
+    "preferences",
+    '{{fitness,logging,prompt_for_rest_timer}}',
+    'false'
+);
+
+--
+
+ALTER TABLE "workout" DROP COLUMN "duration";
+ALTER TABLE "workout" ADD COLUMN "duration" INTEGER;
+UPDATE "workout" SET "duration" = extract(epoch FROM "end_time" - "start_time");
+ALTER TABLE "workout" ALTER COLUMN "duration" SET NOT NULL;
+
+--
+
+DELETE FROM "daily_user_activity";
+DROP INDEX "{dua_idx}";
+CREATE UNIQUE INDEX "{dua_idx}" ON "daily_user_activity"("user_id", "date") NULLS NOT DISTINCT;
+--
+
+DELETE FROM "application_cache";
+ALTER TABLE "application_cache" ADD COLUMN IF NOT EXISTS "version" TEXT NOT NULL;
+        "#,
+            dua_idx = DAILY_USER_ACTIVITY_COMPOSITE_UNIQUE_KEY
+        ))
+        .await?;
+        Ok(())
+    }
+
+    async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
+        Ok(())
+    }
+}
diff --git a/crates/models/background/src/lib.rs b/crates/models/background/src/lib.rs
index 353a13e632..efa0329849 100644
--- a/crates/models/background/src/lib.rs
+++ b/crates/models/background/src/lib.rs
@@ -31,9 +31,10 @@ pub enum MpApplicationJob {
 
 #[derive(Debug, Deserialize, Serialize, Display, Clone)]
 pub enum LpApplicationJob {
+    DeleteAllApplicationCache,
     HandleOnSeenComplete(String),
-    HandleAfterMediaSeenTasks(seen::Model),
     HandleEntityAddedToCollectionEvent(Uuid),
+    HandleAfterMediaSeenTasks(Box<seen::Model>),
 }
 
 #[derive(Debug, Deserialize, Serialize, Display, Clone)]
diff --git a/crates/models/common/src/lib.rs b/crates/models/common/src/lib.rs
index 1ca49752d3..c893fa7b39 100644
--- a/crates/models/common/src/lib.rs
+++ b/crates/models/common/src/lib.rs
@@ -47,39 +47,41 @@ pub enum StoredUrl {
 
 #[derive(Debug, InputObject)]
 pub struct UpdateComplexJsonInput {
+    pub value: String,
     /// Dot delimited path to the property that needs to be changed.
     pub property: String,
-    pub value: String,
 }
 
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Enum, ConfigEnum)]
+#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Enum, ConfigEnum)]
 pub enum CollectionExtraInformationLot {
-    String,
-    Number,
     Date,
+    Number,
+    #[default]
+    String,
     DateTime,
     StringArray,
 }
 
 #[derive(
-    Debug,
-    PartialEq,
     Eq,
     Clone,
+    Debug,
+    Default,
+    PartialEq,
     Serialize,
+    Schematic,
     Deserialize,
     SimpleObject,
-    FromJsonQueryResult,
     InputObject,
-    Schematic,
+    FromJsonQueryResult,
 )]
 #[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>,
+    pub default_value: Option<String>,
+    pub lot: CollectionExtraInformationLot,
 }
 
 #[derive(Display, EnumIter)]
@@ -105,10 +107,9 @@ meta! {
         vec![
             CollectionExtraInformation {
                 name: "Owned on".to_string(),
-                description: "When did you get this media?".to_string(),
                 lot: CollectionExtraInformationLot::Date,
-                default_value: None,
-                required: None,
+                description: "When did you get this media?".to_string(),
+                ..Default::default()
             }
         ]
     ), "Items that I have in my inventory.");
@@ -116,17 +117,17 @@ meta! {
         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),
+                lot: CollectionExtraInformationLot::Date,
+                description: "When do you want to be reminded?".to_string(),
+                ..Default::default()
             },
             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),
+                lot: CollectionExtraInformationLot::String,
+                description: "What do you want to be reminded about?".to_string(),
+                ..Default::default()
             }
         ]
     ), "Items that I want to be reminded about.");
@@ -139,7 +140,7 @@ pub enum BackgroundJob {
     UpdateAllExercises,
     SyncIntegrationsData,
     PerformBackgroundTasks,
-    RecalculateCalendarEvents,
+    DeleteAllApplicationCache,
     CalculateUserActivitiesAndSummary,
 }
 
@@ -184,16 +185,16 @@ pub struct ChangeCollectionToEntityInput {
 
 #[derive(Enum, Eq, PartialEq, Copy, Clone, Debug, Serialize, Deserialize, Display, EnumIter)]
 pub enum MediaStateChanged {
+    ReviewPosted,
     MetadataPublished,
+    PersonMediaAssociated,
     MetadataStatusChanged,
-    MetadataReleaseDateChanged,
-    MetadataNumberOfSeasonsChanged,
     MetadataEpisodeReleased,
+    MetadataReleaseDateChanged,
     MetadataEpisodeNameChanged,
-    MetadataChaptersOrEpisodesChanged,
     MetadataEpisodeImagesChanged,
-    PersonMediaAssociated,
-    ReviewPosted,
+    MetadataNumberOfSeasonsChanged,
+    MetadataChaptersOrEpisodesChanged,
 }
 
 #[derive(Debug, Serialize, Deserialize, SimpleObject, Clone)]
@@ -239,8 +240,8 @@ pub struct ApplicationDateRange {
 #[strum(serialize_all = "snake_case")]
 pub enum DailyUserActivitiesResponseGroupedBy {
     Day,
-    Month,
     Year,
+    Month,
     Millennium,
 }
 
@@ -260,18 +261,18 @@ pub struct MetadataGroupSearchInput {
 
 #[skip_serializing_none]
 #[derive(
+    Eq,
+    Hash,
+    Clone,
     Debug,
+    Default,
+    Schematic,
+    PartialEq,
     Serialize,
     Deserialize,
     InputObject,
-    Clone,
     SimpleObject,
     FromJsonQueryResult,
-    Eq,
-    PartialEq,
-    Hash,
-    Default,
-    Schematic,
 )]
 #[graphql(input_name = "PersonSourceSpecificsInput")]
 #[serde(rename_all = "snake_case")]
@@ -289,8 +290,8 @@ pub struct PeopleSearchInput {
 
 #[derive(Clone, Debug, PartialEq, InputObject, FromJsonQueryResult, Eq, Serialize, Deserialize)]
 pub struct MetadataSearchInput {
-    pub search: SearchInput,
     pub lot: MediaLot,
+    pub search: SearchInput,
     pub source: MediaSource,
 }
 
diff --git a/crates/models/database/src/application_cache.rs b/crates/models/database/src/application_cache.rs
index aa66c5fb5f..f186210d4d 100644
--- a/crates/models/database/src/application_cache.rs
+++ b/crates/models/database/src/application_cache.rs
@@ -8,6 +8,7 @@ use sea_orm::entity::prelude::*;
 pub struct Model {
     #[sea_orm(primary_key)]
     pub id: Uuid,
+    pub version: String,
     pub created_at: DateTimeUtc,
     #[sea_orm(column_type = "Json")]
     pub key: ApplicationCacheKey,
diff --git a/crates/models/database/src/metadata_group.rs b/crates/models/database/src/metadata_group.rs
index e747a3bcb7..66f3b60bf2 100644
--- a/crates/models/database/src/metadata_group.rs
+++ b/crates/models/database/src/metadata_group.rs
@@ -13,25 +13,25 @@ use serde::{Deserialize, Serialize};
 #[sea_orm(table_name = "metadata_group")]
 #[graphql(name = "MetadataGroup")]
 #[boilermates("MetadataGroupWithoutId")]
-#[boilermates(attr_for("MetadataGroupWithoutId", "#[derive(Clone, Debug)]"))]
+#[boilermates(attr_for("MetadataGroupWithoutId", "#[derive(Clone, Default, Debug)]"))]
 pub struct Model {
     #[boilermates(not_in("MetadataGroupWithoutId"))]
     #[sea_orm(primary_key, auto_increment = false)]
     pub id: String,
-    pub identifier: String,
-    pub lot: MediaLot,
-    pub source: MediaSource,
     pub parts: i32,
+    pub lot: MediaLot,
     pub title: String,
-    #[sea_orm(column_type = "Json")]
-    #[graphql(skip)]
-    pub images: Option<Vec<MetadataImage>>,
-    #[sea_orm(ignore)]
-    pub display_images: Vec<String>,
+    pub identifier: String,
+    pub source: MediaSource,
     #[boilermates(not_in("MetadataGroupWithoutId"))]
     pub is_partial: Option<bool>,
-    pub description: Option<String>,
     pub source_url: Option<String>,
+    #[sea_orm(ignore)]
+    pub display_images: Vec<String>,
+    pub description: Option<String>,
+    #[sea_orm(column_type = "Json")]
+    #[graphql(skip)]
+    pub images: Option<Vec<MetadataImage>>,
 }
 
 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
diff --git a/crates/models/database/src/user_measurement.rs b/crates/models/database/src/user_measurement.rs
index e2173d099e..5f7f9272e5 100644
--- a/crates/models/database/src/user_measurement.rs
+++ b/crates/models/database/src/user_measurement.rs
@@ -10,16 +10,17 @@ use serde_with::skip_serializing_none;
 /// An export of a measurement taken at a point in time.
 #[skip_serializing_none]
 #[derive(
+    Eq,
     Clone,
     Debug,
+    Default,
     PartialEq,
-    DeriveEntityModel,
-    Eq,
+    Schematic,
     Serialize,
     Deserialize,
-    SimpleObject,
     InputObject,
-    Schematic,
+    SimpleObject,
+    DeriveEntityModel,
 )]
 #[graphql(name = "UserMeasurement", input_name = "UserMeasurementInput")]
 #[schematic(rename = "UserMeasurement", rename_all = "snake_case")]
diff --git a/crates/models/dependent/src/lib.rs b/crates/models/dependent/src/lib.rs
index ad58488c81..0f0fd48078 100644
--- a/crates/models/dependent/src/lib.rs
+++ b/crates/models/dependent/src/lib.rs
@@ -21,9 +21,9 @@ use media_models::{
     CollectionItem, CreateOrUpdateCollectionInput, EntityWithLot, GenreListItem,
     GraphqlMediaAssets, ImportOrExportExerciseItem, ImportOrExportMetadataGroupItem,
     ImportOrExportMetadataItem, ImportOrExportPersonItem, MetadataCreatorGroupedByRole,
-    MetadataGroupSearchItem, MetadataSearchItemResponse, PeopleSearchItem,
-    PersonDetailsGroupedByRole, ReviewItem, UserDetailsError, UserMediaNextEntry,
-    UserMetadataDetailsEpisodeProgress, UserMetadataDetailsShowSeasonProgress,
+    MetadataGroupSearchItem, MetadataSearchItem, PeopleSearchItem, PersonDetailsGroupedByRole,
+    ReviewItem, UserDetailsError, UserMediaNextEntry, UserMetadataDetailsEpisodeProgress,
+    UserMetadataDetailsShowSeasonProgress,
 };
 use rust_decimal::Decimal;
 use schematic::Schematic;
@@ -32,7 +32,7 @@ use serde::{Deserialize, Serialize};
 use serde_with::skip_serializing_none;
 use strum::Display;
 
-#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, SimpleObject, Clone)]
+#[derive(PartialEq, Eq, Default, Serialize, Deserialize, Debug, SimpleObject, Clone)]
 #[graphql(concrete(name = "ExerciseListResults", params(fitness_models::ExerciseListItem)))]
 #[graphql(concrete(
     name = "MediaCollectionContentsResults",
@@ -40,7 +40,7 @@ use strum::Display;
 ))]
 #[graphql(concrete(
     name = "MetadataSearchResults",
-    params(media_models::MetadataSearchItemResponse)
+    params(media_models::MetadataSearchItem)
 ))]
 #[graphql(concrete(name = "PeopleSearchResults", params(media_models::PeopleSearchItem)))]
 #[graphql(concrete(
@@ -100,12 +100,12 @@ pub struct UserWorkoutDetails {
     pub collections: Vec<collection::Model>,
 }
 
-#[derive(Debug, Serialize, Deserialize, SimpleObject, Clone)]
+#[derive(Debug, Default, Serialize, Deserialize, SimpleObject, Clone)]
 pub struct UserExerciseDetails {
+    pub reviews: Vec<ReviewItem>,
+    pub collections: Vec<collection::Model>,
     pub details: Option<user_to_entity::Model>,
     pub history: Option<Vec<UserToExerciseHistoryExtraInformation>>,
-    pub collections: Vec<collection::Model>,
-    pub reviews: Vec<ReviewItem>,
 }
 
 #[derive(Clone, Debug, Deserialize, Serialize, InputObject)]
@@ -416,17 +416,17 @@ pub struct EmptyCacheValue {
 
 pub type UserCollectionsListResponse = Vec<CollectionItem>;
 pub type PeopleSearchResponse = SearchResults<PeopleSearchItem>;
-pub type MetadataSearchResponse = SearchResults<MetadataSearchItemResponse>;
+pub type MetadataSearchResponse = SearchResults<MetadataSearchItem>;
 pub type MetadataGroupSearchResponse = SearchResults<MetadataGroupSearchItem>;
 
 #[derive(Clone, Debug, PartialEq, FromJsonQueryResult, Serialize, Deserialize, Eq)]
 #[serde(untagged)]
 pub enum ApplicationCacheValue {
     Empty(EmptyCacheValue),
-    CoreDetails(CoreDetails),
     TmdbSettings(TmdbSettings),
     IgdbSettings(IgdbSettings),
     UserAnalytics(UserAnalytics),
+    CoreDetails(Box<CoreDetails>),
     PeopleSearch(PeopleSearchResponse),
     MetadataSearch(MetadataSearchResponse),
     ListennotesSettings(ListennotesSettings),
diff --git a/crates/models/fitness/src/lib.rs b/crates/models/fitness/src/lib.rs
index 7a568449b5..e4c4b16fa1 100644
--- a/crates/models/fitness/src/lib.rs
+++ b/crates/models/fitness/src/lib.rs
@@ -138,19 +138,19 @@ pub struct ExerciseListItem {
 
 /// The totals of a workout and the different bests achieved.
 #[derive(
+    Eq,
+    Sum,
+    Add,
     Debug,
-    FromJsonQueryResult,
     Clone,
+    Default,
     Serialize,
-    Deserialize,
-    Eq,
     PartialEq,
-    SimpleObject,
-    Default,
-    Sum,
-    Add,
     AddAssign,
     Schematic,
+    Deserialize,
+    SimpleObject,
+    FromJsonQueryResult,
 )]
 #[serde(rename_all = "snake_case")]
 pub struct WorkoutOrExerciseTotals {
@@ -179,17 +179,17 @@ pub struct UserToExerciseHistoryExtraInformation {
 /// Details about the statistics of the set performed.
 #[skip_serializing_none]
 #[derive(
+    Eq,
     Clone,
     Debug,
-    Deserialize,
+    Default,
     Serialize,
-    FromJsonQueryResult,
-    Eq,
+    Schematic,
     PartialEq,
-    SimpleObject,
+    Deserialize,
     InputObject,
-    Schematic,
-    Default,
+    SimpleObject,
+    FromJsonQueryResult,
 )]
 #[graphql(input_name = "SetStatisticInput")]
 #[serde(rename_all = "snake_case")]
@@ -205,10 +205,21 @@ pub struct WorkoutSetStatistic {
 
 /// The types of set (mostly characterized by exertion level).
 #[derive(
-    Clone, Debug, Deserialize, Serialize, FromJsonQueryResult, Eq, PartialEq, Enum, Copy, ConfigEnum,
+    Eq,
+    Enum,
+    Copy,
+    Clone,
+    Debug,
+    Default,
+    Serialize,
+    PartialEq,
+    ConfigEnum,
+    Deserialize,
+    FromJsonQueryResult,
 )]
 #[serde(rename_all = "snake_case")]
 pub enum SetLot {
+    #[default]
     Normal,
     WarmUp,
     Drop,
@@ -236,15 +247,16 @@ pub struct WorkoutSetTotals {
 /// Details about the set performed.
 #[skip_serializing_none]
 #[derive(
+    Eq,
     Clone,
     Debug,
-    Deserialize,
+    Default,
     Serialize,
-    FromJsonQueryResult,
-    Eq,
+    Schematic,
     PartialEq,
+    Deserialize,
     SimpleObject,
-    Schematic,
+    FromJsonQueryResult,
 )]
 #[serde(rename_all = "snake_case")]
 pub struct WorkoutSetRecord {
@@ -255,6 +267,7 @@ pub struct WorkoutSetRecord {
     pub statistic: WorkoutSetStatistic,
     pub totals: Option<WorkoutSetTotals>,
     pub confirmed_at: Option<DateTimeUtc>,
+    pub rest_timer_started_at: Option<DateTimeUtc>,
     pub personal_bests: Option<Vec<WorkoutSetPersonalBest>>,
 }
 
@@ -308,17 +321,17 @@ pub struct UserToExerciseExtraInformation {
 
 /// The assets that were uploaded for an entity.
 #[derive(
+    Eq,
     Clone,
     Debug,
-    Deserialize,
-    Serialize,
-    FromJsonQueryResult,
-    Eq,
-    PartialEq,
-    SimpleObject,
-    InputObject,
     Default,
     Schematic,
+    PartialEq,
+    Serialize,
+    InputObject,
+    Deserialize,
+    SimpleObject,
+    FromJsonQueryResult,
 )]
 #[graphql(input_name = "EntityAssetsInput")]
 #[serde(rename_all = "snake_case")]
@@ -332,15 +345,16 @@ pub struct EntityAssets {
 /// An exercise that has been processed and committed to the database.
 #[skip_serializing_none]
 #[derive(
+    Eq,
     Clone,
     Debug,
-    Deserialize,
+    Default,
+    Schematic,
     Serialize,
-    FromJsonQueryResult,
-    Eq,
     PartialEq,
+    Deserialize,
     SimpleObject,
-    Schematic,
+    FromJsonQueryResult,
 )]
 #[serde(rename_all = "snake_case")]
 pub struct ProcessedExercise {
@@ -397,36 +411,60 @@ pub struct WorkoutSupersetsInformation {
 /// Information about a workout done.
 #[skip_serializing_none]
 #[derive(
+    Eq,
     Clone,
     Debug,
-    Deserialize,
+    Default,
+    PartialEq,
+    Schematic,
     Serialize,
+    Deserialize,
+    InputObject,
+    SimpleObject,
     FromJsonQueryResult,
+)]
+#[serde(rename_all = "snake_case")]
+#[graphql(input_name = "WorkoutDurationInput")]
+pub struct WorkoutDuration {
+    pub from: DateTimeUtc,
+    pub to: Option<DateTimeUtc>,
+}
+
+/// Information about a workout done.
+#[skip_serializing_none]
+#[derive(
     Eq,
+    Clone,
+    Debug,
+    Default,
     PartialEq,
-    SimpleObject,
     Schematic,
-    Default,
+    Serialize,
+    Deserialize,
+    SimpleObject,
+    FromJsonQueryResult,
 )]
 #[serde(rename_all = "snake_case")]
 pub struct WorkoutInformation {
     pub comment: Option<String>,
     pub assets: Option<EntityAssets>,
     pub exercises: Vec<ProcessedExercise>,
+    pub durations: Option<Vec<WorkoutDuration>>,
     pub supersets: Vec<WorkoutSupersetsInformation>,
 }
 
 /// The summary about an exercise done in a workout.
 #[derive(
+    Eq,
     Clone,
     Debug,
-    Deserialize,
+    Default,
     Serialize,
-    FromJsonQueryResult,
-    Eq,
+    Schematic,
     PartialEq,
+    Deserialize,
     SimpleObject,
-    Schematic,
+    FromJsonQueryResult,
 )]
 #[serde(rename_all = "snake_case")]
 pub struct WorkoutSummaryExercise {
@@ -562,13 +600,13 @@ pub struct WorkoutSummary {
     pub exercises: Vec<WorkoutSummaryExercise>,
 }
 
-#[derive(Debug, Serialize, Deserialize, InputObject, Clone)]
+#[derive(Debug, Default, Serialize, Deserialize, InputObject, Clone)]
 pub struct UserMeasurementsListInput {
     pub start_time: Option<DateTimeUtc>,
     pub end_time: Option<DateTimeUtc>,
 }
 
-#[derive(Clone, Debug, Deserialize, Serialize, InputObject)]
+#[derive(Clone, Debug, Deserialize, Serialize, InputObject, Default)]
 pub struct UserWorkoutSetRecord {
     pub lot: SetLot,
     pub rpe: Option<u8>,
@@ -576,9 +614,10 @@ pub struct UserWorkoutSetRecord {
     pub rest_time: Option<u16>,
     pub statistic: WorkoutSetStatistic,
     pub confirmed_at: Option<DateTimeUtc>,
+    pub rest_timer_started_at: Option<DateTimeUtc>,
 }
 
-#[derive(Clone, Debug, Deserialize, Serialize, InputObject)]
+#[derive(Clone, Debug, Deserialize, Serialize, InputObject, Default)]
 pub struct UserExerciseInput {
     pub notes: Vec<String>,
     pub exercise_id: String,
@@ -586,7 +625,7 @@ pub struct UserExerciseInput {
     pub sets: Vec<UserWorkoutSetRecord>,
 }
 
-#[derive(Clone, Debug, Deserialize, Serialize, InputObject)]
+#[derive(Clone, Default, Debug, Deserialize, Serialize, InputObject)]
 pub struct UserWorkoutInput {
     pub name: String,
     pub end_time: DateTimeUtc,
@@ -600,6 +639,7 @@ pub struct UserWorkoutInput {
     pub create_workout_id: Option<String>,
     pub exercises: Vec<UserExerciseInput>,
     pub update_workout_id: Option<String>,
+    pub durations: Option<Vec<WorkoutDuration>>,
     pub update_workout_template_id: Option<String>,
     pub supersets: Vec<WorkoutSupersetsInformation>,
 }
diff --git a/crates/models/importer/src/lib.rs b/crates/models/importer/src/lib.rs
index a239c971e7..fa3c05bb33 100644
--- a/crates/models/importer/src/lib.rs
+++ b/crates/models/importer/src/lib.rs
@@ -4,9 +4,10 @@ use sea_orm::FromJsonQueryResult;
 use serde::{Deserialize, Serialize};
 
 /// The various steps in which media importing can fail
-#[derive(Debug, Enum, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
+#[derive(Debug, Default, Enum, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
 pub enum ImportFailStep {
     /// Failed to get details from the source itself (for eg: MediaTracker, Goodreads etc.)
+    #[default]
     ItemDetailsFromSource,
     /// Failed to get metadata from the provider (for eg: Openlibrary, IGDB etc.)
     MediaDetailsFromProvider,
@@ -19,12 +20,12 @@ pub enum ImportFailStep {
 }
 
 #[derive(
-    Debug, SimpleObject, FromJsonQueryResult, Serialize, Deserialize, Eq, PartialEq, Clone,
+    Debug, Default, SimpleObject, FromJsonQueryResult, Serialize, Deserialize, Eq, PartialEq, Clone,
 )]
 pub struct ImportFailedItem {
-    pub lot: Option<MediaLot>,
-    pub step: ImportFailStep,
     pub identifier: String,
+    pub step: ImportFailStep,
+    pub lot: Option<MediaLot>,
     pub error: Option<String>,
 }
 
diff --git a/crates/models/media/src/lib.rs b/crates/models/media/src/lib.rs
index c415233a0e..a2be48811a 100644
--- a/crates/models/media/src/lib.rs
+++ b/crates/models/media/src/lib.rs
@@ -18,20 +18,12 @@ use sea_orm::{prelude::DateTimeUtc, EnumIter, FromJsonQueryResult, FromQueryResu
 use serde::{Deserialize, Serialize};
 use serde_with::skip_serializing_none;
 
-#[derive(Debug, SimpleObject, Serialize, Deserialize, Clone)]
+#[derive(Debug, Default, SimpleObject, Serialize, Deserialize, Clone)]
 pub struct EntityWithLot {
     pub entity_id: String,
     pub entity_lot: EntityLot,
 }
 
-#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, SimpleObject, Clone)]
-pub struct MetadataSearchItemResponse {
-    /// Whether the user has interacted with this media item.
-    pub has_interacted: bool,
-    pub item: MetadataSearchItem,
-    pub database_id: Option<String>,
-}
-
 #[derive(Debug, InputObject, Default, Clone, Serialize)]
 pub struct CreateOrUpdateCollectionInput {
     pub name: String,
@@ -335,7 +327,7 @@ pub struct MangaSpecifics {
     pub url: Option<String>,
 }
 
-#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, SimpleObject, Clone)]
+#[derive(PartialEq, Default, Eq, Debug, Serialize, Deserialize, SimpleObject, Clone)]
 pub struct MetadataSearchItem {
     pub title: String,
     pub identifier: String,
@@ -343,7 +335,7 @@ pub struct MetadataSearchItem {
     pub publish_year: Option<i32>,
 }
 
-#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, SimpleObject, Clone)]
+#[derive(PartialEq, Default, Eq, Debug, Serialize, Deserialize, SimpleObject, Clone)]
 pub struct PeopleSearchItem {
     pub name: String,
     pub identifier: String,
@@ -370,7 +362,7 @@ pub struct CreateOrUpdateReviewInput {
     pub manga_volume_number: Option<i32>,
 }
 
-#[derive(Debug, Serialize, Deserialize, InputObject, Clone)]
+#[derive(Debug, Default, Serialize, Deserialize, InputObject, Clone)]
 pub struct ProgressUpdateInput {
     pub metadata_id: String,
     pub date: Option<NaiveDate>,
@@ -405,7 +397,7 @@ pub enum ProgressUpdateResultUnion {
     Error(ProgressUpdateError),
 }
 
-#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, SimpleObject, Hash)]
+#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq, SimpleObject, Hash)]
 pub struct PartialMetadataPerson {
     pub name: String,
     pub role: String,
@@ -416,14 +408,14 @@ pub struct PartialMetadataPerson {
     pub source_specifics: Option<PersonSourceSpecifics>,
 }
 
-#[derive(Debug, Serialize, Deserialize, Clone, Hash)]
+#[derive(Debug, Default, Serialize, Deserialize, Clone, Hash)]
 pub struct MetadataPersonRelated {
     pub role: String,
     pub character: Option<String>,
     pub metadata: PartialMetadataWithoutId,
 }
 
-#[derive(Debug, Serialize, Deserialize, Clone, Hash)]
+#[derive(Debug, Default, Serialize, Deserialize, Clone, Hash)]
 pub struct MetadataPerson {
     pub name: String,
     pub identifier: String,
@@ -697,16 +689,16 @@ pub struct ImportOrExportItemReviewComment {
 }
 
 #[derive(
+    Eq,
+    Hash,
     Clone,
     Debug,
+    Default,
     PartialEq,
-    FromJsonQueryResult,
-    Eq,
     Serialize,
     Deserialize,
     SimpleObject,
-    Default,
-    Hash,
+    FromJsonQueryResult,
 )]
 pub struct MetadataFreeCreator {
     pub name: String,
@@ -771,17 +763,17 @@ pub struct ReviewPostedEvent {
 #[boilermates("PartialMetadataWithoutId")]
 #[boilermates(attr_for(
     "PartialMetadataWithoutId",
-    "#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize, Hash)]"
+    "#[derive(Clone, Default, Eq, PartialEq, Debug, Serialize, Deserialize, Hash)]"
 ))]
 #[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize, Hash)]
 pub struct PartialMetadata {
     #[boilermates(not_in("PartialMetadataWithoutId"))]
     pub id: String,
-    pub identifier: String,
-    pub title: String,
-    pub image: Option<String>,
     pub lot: MediaLot,
+    pub title: String,
+    pub identifier: String,
     pub source: MediaSource,
+    pub image: Option<String>,
     pub is_recommendation: Option<bool>,
 }
 
@@ -800,12 +792,12 @@ pub struct MetadataPartialDetails {
 #[derive(Debug, InputObject)]
 pub struct CommitPersonInput {
     pub name: String,
-    pub source: MediaSource,
     pub identifier: String,
+    pub source: MediaSource,
     pub source_specifics: Option<PersonSourceSpecifics>,
 }
 
-#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, SimpleObject, Clone)]
+#[derive(PartialEq, Default, Eq, Debug, Serialize, Deserialize, SimpleObject, Clone)]
 pub struct MetadataGroupSearchItem {
     pub name: String,
     pub identifier: String,
@@ -1188,7 +1180,7 @@ pub struct CollectionItem {
     pub information_template: Option<Vec<CollectionExtraInformation>>,
 }
 
-#[derive(Debug, Serialize, Deserialize, SimpleObject, Clone)]
+#[derive(Debug, Default, Serialize, Deserialize, SimpleObject, Clone)]
 pub struct MetadataCreator {
     pub name: String,
     pub id: Option<String>,
diff --git a/crates/models/user/src/lib.rs b/crates/models/user/src/lib.rs
index 55688386c6..c7787e3018 100644
--- a/crates/models/user/src/lib.rs
+++ b/crates/models/user/src/lib.rs
@@ -96,9 +96,10 @@ pub struct UserFitnessFeaturesEnabledPreferences {
 )]
 #[educe(Default)]
 pub struct UserFitnessLoggingPreferences {
+    pub mute_sounds: bool,
+    pub prompt_for_rest_timer: bool,
     #[educe(Default = true)]
     pub show_details_while_editing: bool,
-    pub mute_sounds: bool,
 }
 
 #[derive(
diff --git a/crates/providers/src/anilist/mod.rs b/crates/providers/src/anilist/mod.rs
index 4fd89c2c94..9b1177e7de 100644
--- a/crates/providers/src/anilist/mod.rs
+++ b/crates/providers/src/anilist/mod.rs
@@ -170,10 +170,9 @@ impl MediaProvider for NonMediaAnilistService {
                 .map(|s| {
                     let data = s.unwrap();
                     PeopleSearchItem {
-                        identifier: data.id.to_string(),
                         name: data.name,
-                        image: None,
-                        birth_year: None,
+                        identifier: data.id.to_string(),
+                        ..Default::default()
                     }
                 })
                 .collect();
@@ -271,37 +270,31 @@ impl MediaProvider for NonMediaAnilistService {
                 .map(|r| {
                     let data = r.unwrap().node.unwrap();
                     MetadataPersonRelated {
-                        character: None,
                         role: STUDIO_ROLE.to_owned(),
                         metadata: PartialMetadataWithoutId {
-                            title: data.title.unwrap().native.unwrap(),
-                            identifier: data.id.to_string(),
                             source: MediaSource::Anilist,
+                            identifier: data.id.to_string(),
+                            title: data.title.unwrap().native.unwrap(),
+                            image: data.cover_image.unwrap().extra_large,
                             lot: match data.type_.unwrap() {
                                 studio_query::MediaType::ANIME => MediaLot::Anime,
                                 studio_query::MediaType::MANGA => MediaLot::Manga,
                                 studio_query::MediaType::Other(_) => unreachable!(),
                             },
-                            image: data.cover_image.unwrap().extra_large,
-                            is_recommendation: None,
+                            ..Default::default()
                         },
+                        ..Default::default()
                     }
                 })
                 .collect();
             MetadataPerson {
-                identifier: details.id.to_string(),
-                source: MediaSource::Anilist,
-                name: details.name,
                 related,
-                source_specifics: source_specifics.to_owned(),
+                name: details.name,
                 website: details.site_url,
-                description: None,
-                gender: None,
-                source_url: None,
-                place: None,
-                images: None,
-                death_date: None,
-                birth_date: None,
+                source: MediaSource::Anilist,
+                identifier: details.id.to_string(),
+                source_specifics: source_specifics.to_owned(),
+                ..Default::default()
             }
         } else {
             let variables = staff_query::Variables {
@@ -371,15 +364,15 @@ impl MediaProvider for NonMediaAnilistService {
                                 role: "Voicing".to_owned(),
                                 metadata: PartialMetadataWithoutId {
                                     title: title.clone(),
-                                    is_recommendation: None,
-                                    identifier: data.id.to_string(),
                                     source: MediaSource::Anilist,
+                                    identifier: data.id.to_string(),
+                                    image: data.cover_image.clone().unwrap().extra_large,
                                     lot: match data.type_.clone().unwrap() {
                                         staff_query::MediaType::ANIME => MediaLot::Anime,
                                         staff_query::MediaType::MANGA => MediaLot::Manga,
                                         staff_query::MediaType::Other(_) => unreachable!(),
                                     },
-                                    image: data.cover_image.clone().unwrap().extra_large,
+                                    ..Default::default()
                                 },
                             })
                         }
@@ -403,37 +396,36 @@ impl MediaProvider for NonMediaAnilistService {
                             &self.base.preferred_language,
                         );
                         MetadataPersonRelated {
-                            character: None,
                             role: edge.staff_role.unwrap_or_else(|| "Production".to_owned()),
                             metadata: PartialMetadataWithoutId {
                                 title,
-                                identifier: data.id.to_string(),
                                 source: MediaSource::Anilist,
+                                identifier: data.id.to_string(),
+                                image: data.cover_image.unwrap().extra_large,
                                 lot: match data.type_.unwrap() {
                                     staff_query::MediaType::ANIME => MediaLot::Anime,
                                     staff_query::MediaType::MANGA => MediaLot::Manga,
                                     staff_query::MediaType::Other(_) => unreachable!(),
                                 },
-                                image: data.cover_image.unwrap().extra_large,
-                                is_recommendation: None,
+                                ..Default::default()
                             },
+                            ..Default::default()
                         }
                     }),
             );
             MetadataPerson {
-                identifier: details.id.to_string(),
-                source: MediaSource::Anilist,
-                name: details.name.unwrap().full.unwrap(),
-                description: details.description,
-                gender: details.gender,
-                place: details.home_town,
-                images: Some(images),
+                related,
                 death_date,
                 birth_date,
-                related,
+                images: Some(images),
+                gender: details.gender,
+                place: details.home_town,
+                source: MediaSource::Anilist,
+                description: details.description,
+                identifier: details.id.to_string(),
+                name: details.name.unwrap().full.unwrap(),
                 source_specifics: source_specifics.to_owned(),
-                website: None,
-                source_url: None,
+                ..Default::default()
             }
         };
         Ok(data)
@@ -580,12 +572,11 @@ async fn media_details(
         .map(|s| {
             let node = s.node.unwrap();
             PartialMetadataPerson {
-                name: node.name.unwrap().full.unwrap(),
-                identifier: node.id.to_string(),
-                source: MediaSource::Anilist,
                 role: s.role.unwrap(),
-                character: None,
-                source_specifics: None,
+                source: MediaSource::Anilist,
+                identifier: node.id.to_string(),
+                name: node.name.unwrap().full.unwrap(),
+                ..Default::default()
             }
         })
         .collect_vec();
@@ -598,14 +589,14 @@ async fn media_details(
                 let node = s.node.unwrap();
                 PartialMetadataPerson {
                     name: node.name,
-                    identifier: node.id.to_string(),
                     source: MediaSource::Anilist,
                     role: STUDIO_ROLE.to_owned(),
-                    character: None,
+                    identifier: node.id.to_string(),
                     source_specifics: Some(PersonSourceSpecifics {
                         is_anilist_studio: Some(true),
                         ..Default::default()
                     }),
+                    ..Default::default()
                 }
             }),
     );
@@ -639,7 +630,7 @@ async fn media_details(
             Some(MangaSpecifics {
                 chapters: media.chapters.map(Decimal::from),
                 volumes: media.volumes.and_then(|v| v.try_into().ok()),
-                url: None,
+                ..Default::default()
             }),
         ),
         media_details_query::MediaType::Other(_) => unreachable!(),
@@ -666,15 +657,15 @@ async fn media_details(
                 );
                 PartialMetadataWithoutId {
                     title,
-                    identifier: data.id.to_string(),
                     source: MediaSource::Anilist,
+                    identifier: data.id.to_string(),
+                    image: data.cover_image.unwrap().extra_large,
                     lot: match data.type_.unwrap() {
                         media_details_query::MediaType::ANIME => MediaLot::Anime,
                         media_details_query::MediaType::MANGA => MediaLot::Manga,
                         media_details_query::MediaType::Other(_) => unreachable!(),
                     },
-                    image: data.cover_image.unwrap().extra_large,
-                    is_recommendation: None,
+                    ..Default::default()
                 }
             })
         })
@@ -697,31 +688,26 @@ async fn media_details(
     );
     let identifier = media.id.to_string();
     Ok(MetadataDetails {
+        lot,
+        people,
+        videos,
+        suggestions,
+        anime_specifics,
+        manga_specifics,
+        url_images: images,
+        publish_year: year,
         title: title.clone(),
-        identifier: identifier.clone(),
+        provider_rating: score,
         is_nsfw: media.is_adult,
         source: MediaSource::Anilist,
+        identifier: identifier.clone(),
         description: media.description,
-        lot,
-        people,
-        creators: vec![],
-        url_images: images,
+        genres: genres.into_iter().unique().collect(),
+        production_status: media_status_string(media.status),
         source_url: Some(format!(
             "https://anilist.co/{}/{}/{}",
             lot, identifier, title
         )),
-        videos,
-        genres: genres.into_iter().unique().collect(),
-        publish_year: year,
-        publish_date: None,
-        anime_specifics,
-        manga_specifics,
-        suggestions,
-        provider_rating: score,
-        groups: vec![],
-        s3_images: vec![],
-        production_status: media_status_string(media.status),
-        original_language: None,
         ..Default::default()
     })
 }
diff --git a/crates/providers/src/audible.rs b/crates/providers/src/audible.rs
index 1995e471a8..c4571b3355 100644
--- a/crates/providers/src/audible.rs
+++ b/crates/providers/src/audible.rs
@@ -188,8 +188,7 @@ impl MediaProvider for AudibleService {
             .map(|a| PeopleSearchItem {
                 identifier: a.asin.unwrap_or_default(),
                 name: a.name,
-                image: None,
-                birth_year: None,
+                ..Default::default()
             })
             .collect_vec();
         let total_items = data.len();
@@ -227,15 +226,8 @@ impl MediaProvider for AudibleService {
             .map_err(|e| anyhow!(e))?;
         let name = data.name;
         Ok(MetadataPerson {
-            place: None,
-            gender: None,
-            website: None,
-            related: vec![],
-            death_date: None,
-            birth_date: None,
             name: name.clone(),
             identifier: data.asin,
-            source_specifics: None,
             source: MediaSource::Audible,
             description: data.description,
             images: Some(Vec::from_iter(data.image)),
@@ -243,6 +235,7 @@ impl MediaProvider for AudibleService {
                 "https://www.audible.com/author/{}/{}",
                 name, identity
             )),
+            ..Default::default()
         })
     }
 
@@ -279,21 +272,18 @@ impl MediaProvider for AudibleService {
                 .map_err(|e| anyhow!(e))?;
             let data: AudibleItemResponse = rsp.json().await.map_err(|e| anyhow!(e))?;
             collection_contents.push(PartialMetadataWithoutId {
-                title: data.product.title,
-                image: data.product.product_images.and_then(|i| i.image_2400),
                 identifier: i,
-                source: MediaSource::Audible,
                 lot: MediaLot::AudioBook,
-                is_recommendation: None,
+                title: data.product.title,
+                source: MediaSource::Audible,
+                image: data.product.product_images.and_then(|i| i.image_2400),
+                ..Default::default()
             })
         }
         let title = data.product.title;
         Ok((
             MetadataGroupWithoutId {
-                images: None,
-                description: None,
                 title: title.clone(),
-                display_images: vec![],
                 lot: MediaLot::AudioBook,
                 source: MediaSource::Audible,
                 identifier: identifier.to_owned(),
@@ -302,6 +292,7 @@ impl MediaProvider for AudibleService {
                     "https://www.audible.com/series/{}/{}",
                     identifier, title
                 )),
+                ..Default::default()
             },
             collection_contents,
         ))
@@ -346,11 +337,11 @@ impl MediaProvider for AudibleService {
             for sim in data.similar_products.into_iter() {
                 suggestions.push(PartialMetadataWithoutId {
                     title: sim.title,
-                    image: sim.product_images.and_then(|i| i.image_500),
                     identifier: sim.asin,
-                    source: MediaSource::Audible,
                     lot: MediaLot::AudioBook,
-                    is_recommendation: None,
+                    source: MediaSource::Audible,
+                    image: sim.product_images.and_then(|i| i.image_500),
+                    ..Default::default()
                 });
             }
         }
@@ -428,12 +419,11 @@ impl AudibleService {
             .into_iter()
             .filter_map(|a| {
                 a.asin.map(|au| PartialMetadataPerson {
+                    name: a.name,
                     identifier: au,
-                    source: MediaSource::Audible,
                     role: "Author".to_owned(),
-                    name: a.name,
-                    character: None,
-                    source_specifics: None,
+                    source: MediaSource::Audible,
+                    ..Default::default()
                 })
             })
             .collect_vec();
@@ -444,18 +434,13 @@ impl AudibleService {
             .map(|a| MetadataFreeCreator {
                 name: a.name,
                 role: "Narrator".to_owned(),
-                image: None,
+                ..Default::default()
             })
             .collect_vec();
         let description = item.publisher_summary.or(item.merchandising_summary);
-        let rating = if let Some(r) = item.rating {
-            if r.num_reviews > 0 {
-                r.overall_distribution.display_average_rating
-            } else {
-                None
-            }
-        } else {
-            None
+        let rating = match item.rating {
+            Some(r) if r.num_reviews > 0 => r.overall_distribution.display_average_rating,
+            _ => None,
         };
         MetadataDetails {
             people,
diff --git a/crates/providers/src/google_books.rs b/crates/providers/src/google_books.rs
index a5a3ee1c8c..66be3a2bcf 100644
--- a/crates/providers/src/google_books.rs
+++ b/crates/providers/src/google_books.rs
@@ -17,7 +17,7 @@ use reqwest::{
 };
 use rust_decimal::Decimal;
 use serde::{Deserialize, Serialize};
-use traits::{MediaProvider, };
+use traits::MediaProvider;
 
 static URL: &str = "https://www.googleapis.com/books/v1/volumes";
 
@@ -191,14 +191,14 @@ impl GoogleBooksService {
             .map(|a| MetadataFreeCreator {
                 name: a,
                 role: "Author".to_owned(),
-                image: None,
+                ..Default::default()
             })
             .collect_vec();
         if let Some(p) = item.publisher {
             creators.push(MetadataFreeCreator {
                 name: p,
                 role: "Publisher".to_owned(),
-                image: None,
+                ..Default::default()
             });
         }
         let mut genres = item
@@ -216,15 +216,14 @@ impl GoogleBooksService {
             title: item.title.clone(),
             description: item.description,
             source: MediaSource::GoogleBooks,
-            creators: creators.into_iter().unique().collect(),
+            provider_rating: item.average_rating,
+            url_images: images.unique().collect(),
             genres: genres.into_iter().unique().collect(),
+            creators: creators.into_iter().unique().collect(),
             publish_year: item.published_date.and_then(|d| convert_date_to_year(&d)),
-            publish_date: None,
             book_specifics: Some(BookSpecifics {
                 pages: item.page_count,
             }),
-            url_images: images.unique().collect(),
-            provider_rating: item.average_rating,
             source_url: Some(format!(
                 "https://www.google.co.in/books/edition/{}/{}",
                 item.title, id
diff --git a/crates/providers/src/igdb.rs b/crates/providers/src/igdb.rs
index 8aaa92ffff..f25717141f 100644
--- a/crates/providers/src/igdb.rs
+++ b/crates/providers/src/igdb.rs
@@ -255,12 +255,12 @@ where id = {id};
                     None
                 } else {
                     Some(PartialMetadataWithoutId {
-                        identifier: g.id.to_string(),
                         title: g.name.unwrap(),
-                        image: g.cover.map(|c| self.get_cover_image_url(c.image_id)),
-                        source: MediaSource::Igdb,
                         lot: MediaLot::VideoGame,
-                        is_recommendation: None,
+                        source: MediaSource::Igdb,
+                        identifier: g.id.to_string(),
+                        image: g.cover.map(|c| self.get_cover_image_url(c.image_id)),
+                        ..Default::default()
                     })
                 }
             })
@@ -268,15 +268,13 @@ where id = {id};
         let title = details.name.unwrap_or_default();
         Ok((
             MetadataGroupWithoutId {
-                images: None,
-                description: None,
                 title: title.clone(),
-                display_images: vec![],
                 lot: MediaLot::VideoGame,
                 source: MediaSource::Igdb,
                 identifier: details.id.to_string(),
                 parts: items.len().try_into().unwrap(),
                 source_url: Some(format!("https://www.igdb.com/collection/{}", title)),
+                ..Default::default()
             },
             items,
         ))
@@ -314,10 +312,10 @@ offset: {offset};
             .map(|ic| {
                 let image = ic.logo.map(|a| self.get_cover_image_url(a.image_id));
                 PeopleSearchItem {
-                    identifier: ic.id.to_string(),
-                    name: ic.name,
                     image,
-                    birth_year: None,
+                    name: ic.name,
+                    identifier: ic.id.to_string(),
+                    ..Default::default()
                 }
             })
             .collect_vec();
@@ -362,42 +360,38 @@ where id = {id};
             .map(|r| {
                 let image = r.cover.map(|a| self.get_cover_image_url(a.image_id));
                 MetadataPersonRelated {
-                    character: None,
                     role: "Publishing".to_owned(),
                     metadata: PartialMetadataWithoutId {
                         image,
                         title: r.name.unwrap(),
-                        is_recommendation: None,
                         lot: MediaLot::VideoGame,
                         source: MediaSource::Igdb,
                         identifier: r.id.to_string(),
+                        ..Default::default()
                     },
+                    ..Default::default()
                 }
             })
             .collect_vec();
         related.extend(detail.developed.unwrap_or_default().into_iter().map(|r| {
             let image = r.cover.map(|a| self.get_cover_image_url(a.image_id));
             MetadataPersonRelated {
-                character: None,
                 role: "Development".to_owned(),
                 metadata: PartialMetadataWithoutId {
                     image,
                     title: r.name.unwrap(),
-                    is_recommendation: None,
                     lot: MediaLot::VideoGame,
                     source: MediaSource::Igdb,
                     identifier: r.id.to_string(),
+                    ..Default::default()
                 },
+                ..Default::default()
             }
         }));
         let name = detail.name;
         Ok(MetadataPerson {
             related,
-            gender: None,
-            birth_date: None,
-            death_date: None,
             name: name.clone(),
-            source_specifics: None,
             source: MediaSource::Igdb,
             description: detail.description,
             identifier: detail.id.to_string(),
@@ -414,6 +408,7 @@ where id = {id};
                 .unwrap_or_default()
                 .first()
                 .map(|i| i.url.clone()),
+            ..Default::default()
         })
     }
 
@@ -598,12 +593,11 @@ impl IgdbService {
                     "Unknown"
                 };
                 PartialMetadataPerson {
-                    identifier: ic.id.to_string(),
                     name: ic.company.name,
-                    source: MediaSource::Igdb,
                     role: role.to_owned(),
-                    character: None,
-                    source_specifics: None,
+                    source: MediaSource::Igdb,
+                    identifier: ic.id.to_string(),
+                    ..Default::default()
                 }
             })
             .unique()
@@ -651,11 +645,11 @@ impl IgdbService {
                 .into_iter()
                 .map(|g| PartialMetadataWithoutId {
                     title: g.name.unwrap(),
-                    image: g.cover.map(|c| self.get_cover_image_url(c.image_id)),
-                    identifier: g.id.to_string(),
                     lot: MediaLot::VideoGame,
                     source: MediaSource::Igdb,
-                    is_recommendation: None,
+                    identifier: g.id.to_string(),
+                    image: g.cover.map(|c| self.get_cover_image_url(c.image_id)),
+                    ..Default::default()
                 })
                 .collect(),
             provider_rating: item.rating,
diff --git a/crates/providers/src/itunes.rs b/crates/providers/src/itunes.rs
index 68399991fb..555448e4b6 100644
--- a/crates/providers/src/itunes.rs
+++ b/crates/providers/src/itunes.rs
@@ -14,7 +14,7 @@ use media_models::{
 use reqwest::Client;
 use sea_orm::prelude::ChronoDateTimeUtc;
 use serde::{Deserialize, Serialize};
-use traits::{MediaProvider, };
+use traits::MediaProvider;
 
 static URL: &str = "https://itunes.apple.com";
 
@@ -89,7 +89,7 @@ impl MediaProvider for ITunesService {
             .map(|a| MetadataFreeCreator {
                 name: a,
                 role: "Artist".to_owned(),
-                image: None,
+                ..Default::default()
             })
             .collect();
         let genres = ht
diff --git a/crates/providers/src/listennotes.rs b/crates/providers/src/listennotes.rs
index 3a8206d104..72bab1bd6f 100644
--- a/crates/providers/src/listennotes.rs
+++ b/crates/providers/src/listennotes.rs
@@ -88,7 +88,7 @@ impl MediaProvider for ListennotesService {
                 identifier: r.id,
                 lot: MediaLot::Podcast,
                 source: MediaSource::Listennotes,
-                is_recommendation: None,
+                ..Default::default()
             })
             .collect();
 
@@ -268,7 +268,7 @@ impl ListennotesService {
             creators: Vec::from_iter(podcast_data.publisher.map(|p| MetadataFreeCreator {
                 name: p,
                 role: "Publishing".to_owned(),
-                image: None,
+                ..Default::default()
             })),
             genres: podcast_data
                 .genre_ids
diff --git a/crates/providers/src/mal.rs b/crates/providers/src/mal.rs
index 0c373109e2..a6b4f248ad 100644
--- a/crates/providers/src/mal.rs
+++ b/crates/providers/src/mal.rs
@@ -18,7 +18,7 @@ use reqwest::{
 use rust_decimal::Decimal;
 use serde::{Deserialize, Serialize};
 use serde_json::json;
-use traits::{MediaProvider, };
+use traits::MediaProvider;
 
 static URL: &str = "https://api.myanimelist.net/v2";
 
@@ -205,43 +205,43 @@ async fn details(client: &Client, media_type: &str, id: &str) -> Result<Metadata
             .num_volumes
             .zip(details.num_chapters)
             .map(|(v, c)| MangaSpecifics {
-                chapters: Some(Decimal::from(c)),
                 volumes: Some(v),
-                url: None,
+                chapters: Some(Decimal::from(c)),
+                ..Default::default()
             });
     let anime_specifics = details.num_episodes.map(|e| AnimeSpecifics {
         episodes: Some(e),
-        airing_schedule: None,
+        ..Default::default()
     });
     let mut suggestions = vec![];
     for rel in details.related_anime.unwrap_or_default().into_iter() {
         suggestions.push(PartialMetadataWithoutId {
-            identifier: rel.node.id.to_string(),
+            lot: MediaLot::Anime,
             title: rel.node.title,
-            image: Some(rel.node.main_picture.large),
             source: MediaSource::Mal,
-            lot: MediaLot::Anime,
-            is_recommendation: None,
+            identifier: rel.node.id.to_string(),
+            image: Some(rel.node.main_picture.large),
+            ..Default::default()
         });
     }
     for rel in details.related_manga.unwrap_or_default().into_iter() {
         suggestions.push(PartialMetadataWithoutId {
-            identifier: rel.node.id.to_string(),
+            lot: MediaLot::Manga,
             title: rel.node.title,
-            image: Some(rel.node.main_picture.large),
             source: MediaSource::Mal,
-            lot: MediaLot::Manga,
-            is_recommendation: None,
+            identifier: rel.node.id.to_string(),
+            image: Some(rel.node.main_picture.large),
+            ..Default::default()
         });
     }
     for rel in details.recommendations.unwrap_or_default().into_iter() {
         suggestions.push(PartialMetadataWithoutId {
-            identifier: rel.node.id.to_string(),
+            lot,
             title: rel.node.title,
-            image: Some(rel.node.main_picture.large),
             source: MediaSource::Mal,
-            lot,
-            is_recommendation: None,
+            identifier: rel.node.id.to_string(),
+            image: Some(rel.node.main_picture.large),
+            ..Default::default()
         });
     }
     suggestions.shuffle(&mut rng());
diff --git a/crates/providers/src/manga_updates.rs b/crates/providers/src/manga_updates.rs
index 0568977d71..72b5e70173 100644
--- a/crates/providers/src/manga_updates.rs
+++ b/crates/providers/src/manga_updates.rs
@@ -15,7 +15,7 @@ use media_models::{
 use reqwest::Client;
 use rust_decimal::Decimal;
 use serde::{Deserialize, Serialize};
-use traits::{MediaProvider, };
+use traits::MediaProvider;
 
 static URL: &str = "https://api.mangaupdates.com/v1";
 
@@ -181,10 +181,9 @@ impl MediaProvider for MangaUpdatesService {
             .results
             .into_iter()
             .map(|s| PeopleSearchItem {
-                identifier: s.record.id.to_string(),
                 name: s.hit_name,
-                image: None,
-                birth_year: None,
+                identifier: s.record.id.to_string(),
+                ..Default::default()
             })
             .collect();
         Ok(SearchResults {
@@ -228,27 +227,21 @@ impl MediaProvider for MangaUpdatesService {
             .series_list
             .into_iter()
             .map(|r| MetadataPersonRelated {
-                character: None,
                 role: "Author".to_owned(),
                 metadata: PartialMetadataWithoutId {
-                    image: None,
                     title: r.title,
                     lot: MediaLot::Manga,
-                    is_recommendation: None,
                     source: MediaSource::MangaUpdates,
                     identifier: r.series_id.to_string(),
+                    ..Default::default()
                 },
+                ..Default::default()
             })
             .collect_vec();
         let resp = MetadataPerson {
             related,
-            website: None,
-            death_date: None,
-            source_url: None,
-            description: None,
             gender: data.gender,
             place: data.birthplace,
-            source_specifics: None,
             name: data.name.unwrap(),
             identifier: identity.to_owned(),
             source: MediaSource::MangaUpdates,
@@ -260,6 +253,7 @@ impl MediaProvider for MangaUpdatesService {
                     None
                 }
             }),
+            ..Default::default()
         };
         Ok(resp)
     }
@@ -280,12 +274,11 @@ impl MediaProvider for MangaUpdatesService {
             .into_iter()
             .flat_map(|a| {
                 a.author_id.map(|ai| PartialMetadataPerson {
+                    role: a.lot.unwrap(),
                     identifier: ai.to_string(),
                     name: a.name.unwrap_or_default(),
-                    role: a.lot.unwrap(),
                     source: MediaSource::MangaUpdates,
-                    character: None,
-                    source_specifics: None,
+                    ..Default::default()
                 })
             })
             .collect_vec();
@@ -312,12 +305,12 @@ impl MediaProvider for MangaUpdatesService {
                 .await
             {
                 suggestions.push(PartialMetadataWithoutId {
+                    lot: MediaLot::Manga,
                     title: data.title.unwrap(),
+                    source: MediaSource::MangaUpdates,
                     image: data.image.unwrap().url.original,
                     identifier: data.series_id.unwrap().to_string(),
-                    source: MediaSource::MangaUpdates,
-                    lot: MediaLot::Manga,
-                    is_recommendation: None,
+                    ..Default::default()
                 });
             }
         }
diff --git a/crates/providers/src/openlibrary.rs b/crates/providers/src/openlibrary.rs
index 18a785c6ac..1d4ef93d47 100644
--- a/crates/providers/src/openlibrary.rs
+++ b/crates/providers/src/openlibrary.rs
@@ -17,7 +17,7 @@ use reqwest::Client;
 use scraper::{Html, Selector};
 use serde::{Deserialize, Serialize};
 use serde_json::json;
-use traits::{MediaProvider, };
+use traits::MediaProvider;
 
 static URL: &str = "https://openlibrary.org";
 static IMAGE_BASE_URL: &str = "https://covers.openlibrary.org";
@@ -53,17 +53,17 @@ struct BookSearchResults {
     items: Vec<BookSearchItem>,
 }
 
-#[derive(Debug, Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone, Default)]
 struct BookSearchItem {
-    identifier: String,
     title: String,
-    description: Option<String>,
-    author_names: Vec<String>,
+    identifier: String,
     genres: Vec<String>,
     images: Vec<String>,
     publish_year: Option<i32>,
-    publish_date: Option<NaiveDate>,
+    author_names: Vec<String>,
+    description: Option<String>,
     book_specifics: BookSpecifics,
+    publish_date: Option<NaiveDate>,
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
@@ -183,10 +183,10 @@ impl MediaProvider for OpenlibraryService {
             .docs
             .into_iter()
             .map(|d| PeopleSearchItem {
-                identifier: get_key(&d.key),
                 name: d.name,
-                image: None,
+                identifier: get_key(&d.key),
                 birth_year: d.birth_date.and_then(|b| parse_date(&b)).map(|d| d.year()),
+                ..Default::default()
             })
             .collect_vec();
         let data = SearchResults {
@@ -251,16 +251,16 @@ impl MediaProvider for OpenlibraryService {
                     .first()
                     .cloned();
                 related.push(MetadataPersonRelated {
-                    character: None,
                     role: "Author".to_owned(),
                     metadata: PartialMetadataWithoutId {
                         title,
                         image,
                         lot: MediaLot::Book,
-                        is_recommendation: None,
                         identifier: get_key(&entry.key),
                         source: MediaSource::Openlibrary,
+                        ..Default::default()
                     },
+                    ..Default::default()
                 })
             }
         }
@@ -268,12 +268,9 @@ impl MediaProvider for OpenlibraryService {
         let name = data.name;
         Ok(MetadataPerson {
             related,
-            place: None,
             description,
-            gender: None,
             name: name.clone(),
             images: Some(images),
-            source_specifics: None,
             identifier: identifier.clone(),
             source: MediaSource::Openlibrary,
             birth_date: data.birth_date.and_then(|b| parse_date(&b)),
@@ -285,6 +282,7 @@ impl MediaProvider for OpenlibraryService {
             website: data
                 .links
                 .and_then(|l| l.first().and_then(|a| a.url.clone())),
+            ..Default::default()
         })
     }
 
@@ -339,12 +337,10 @@ impl MediaProvider for OpenlibraryService {
                 ),
             };
             people.push(PartialMetadataPerson {
-                identifier: get_key(&key),
-                name: "".to_owned(),
                 role,
+                identifier: get_key(&key),
                 source: MediaSource::Openlibrary,
-                character: None,
-                source_specifics: None,
+                ..Default::default()
             });
         }
         let description = data.description.map(|d| match d {
@@ -433,12 +429,12 @@ impl MediaProvider for OpenlibraryService {
                     .and_then(|img| img.value().attr("src"))
                     .map(|src| src.to_string());
                 suggestions.push(PartialMetadataWithoutId {
-                    title: name,
                     image,
                     identifier,
+                    title: name,
                     lot: MediaLot::Book,
                     source: MediaSource::Openlibrary,
-                    is_recommendation: None,
+                    ..Default::default()
                 });
             }
         }
@@ -500,17 +496,15 @@ impl MediaProvider for OpenlibraryService {
             .map(|d| {
                 let images = Vec::from_iter(d.cover_i.map(|f| self.get_book_cover_image_url(f)));
                 BookSearchItem {
-                    identifier: get_key(&d.key),
+                    images,
                     title: d.title,
-                    description: None,
-                    author_names: d.author_name.unwrap_or_default(),
-                    genres: vec![],
+                    identifier: get_key(&d.key),
                     publish_year: d.first_publish_year,
-                    publish_date: None,
+                    author_names: d.author_name.unwrap_or_default(),
                     book_specifics: BookSpecifics {
                         pages: d.number_of_pages_median,
                     },
-                    images,
+                    ..Default::default()
                 }
             })
             .collect_vec();
diff --git a/crates/providers/src/tmdb.rs b/crates/providers/src/tmdb.rs
index 3e8f7d9e20..217b2e04ae 100644
--- a/crates/providers/src/tmdb.rs
+++ b/crates/providers/src/tmdb.rs
@@ -272,10 +272,10 @@ impl TmdbService {
                 suggestions.push(PartialMetadataWithoutId {
                     lot,
                     title: name,
-                    is_recommendation: None,
                     source: MediaSource::Tmdb,
                     identifier: entry.id.to_string(),
                     image: entry.poster_path.map(|p| self.get_image_url(p)),
+                    ..Default::default()
                 });
             }
             if new_recs.page >= new_recs.total_pages {
@@ -414,10 +414,10 @@ impl MediaProvider for NonMediaTmdbService {
             .results
             .into_iter()
             .map(|d| PeopleSearchItem {
-                identifier: d.id.to_string(),
                 name: d.title.unwrap(),
+                identifier: d.id.to_string(),
                 image: d.poster_path.map(|p| self.base.get_image_url(p)),
-                birth_year: None,
+                ..Default::default()
             })
             .collect_vec();
         let next_page = if page < search.total_pages {
@@ -483,7 +483,6 @@ impl MediaProvider for NonMediaTmdbService {
             for media in cred_det.crew.into_iter().chain(cred_det.cast.into_iter()) {
                 let role = media.job.unwrap_or_else(|| "Actor".to_owned());
                 let metadata = PartialMetadataWithoutId {
-                    is_recommendation: None,
                     source: MediaSource::Tmdb,
                     identifier: media.id.unwrap().to_string(),
                     title: media.title.or(media.name).unwrap_or_default(),
@@ -493,6 +492,7 @@ impl MediaProvider for NonMediaTmdbService {
                         "tv" => MediaLot::Show,
                         _ => continue,
                     },
+                    ..Default::default()
                 };
                 related.push(MetadataPersonRelated {
                     role,
@@ -516,10 +516,8 @@ impl MediaProvider for NonMediaTmdbService {
                         .await
                         .map_err(|e| anyhow!(e))?;
                     related.extend(cred_det.results.into_iter().map(|m| MetadataPersonRelated {
-                        character: None,
                         role: "Production Company".to_owned(),
                         metadata: PartialMetadataWithoutId {
-                            is_recommendation: None,
                             source: MediaSource::Tmdb,
                             identifier: m.id.to_string(),
                             title: m.title.unwrap_or_default(),
@@ -529,7 +527,9 @@ impl MediaProvider for NonMediaTmdbService {
                                 "tv" => MediaLot::Show,
                                 _ => unreachable!(),
                             },
+                            ..Default::default()
                         },
+                        ..Default::default()
                     }));
                     if cred_det.page == cred_det.total_pages {
                         break;
@@ -692,12 +692,12 @@ impl MediaProvider for TmdbMovieService {
                 .flat_map(|g| {
                     g.id.and_then(|id| {
                         g.known_for_department.map(|r| PartialMetadataPerson {
-                            identifier: id.to_string(),
-                            name: g.name.unwrap_or_default(),
                             role: r,
-                            source: MediaSource::Tmdb,
                             character: g.character,
-                            source_specifics: None,
+                            source: MediaSource::Tmdb,
+                            identifier: id.to_string(),
+                            name: g.name.unwrap_or_default(),
+                            ..Default::default()
                         })
                     })
                 })
@@ -712,12 +712,12 @@ impl MediaProvider for TmdbMovieService {
                 .flat_map(|g| {
                     g.id.and_then(|id| {
                         g.known_for_department.map(|r| PartialMetadataPerson {
-                            identifier: id.to_string(),
-                            name: g.name.unwrap_or_default(),
                             role: r,
-                            source: MediaSource::Tmdb,
                             character: g.character,
-                            source_specifics: None,
+                            source: MediaSource::Tmdb,
+                            identifier: id.to_string(),
+                            name: g.name.unwrap_or_default(),
+                            ..Default::default()
                         })
                     })
                 })
@@ -729,15 +729,15 @@ impl MediaProvider for TmdbMovieService {
                 .unwrap_or_default()
                 .into_iter()
                 .map(|p| PartialMetadataPerson {
-                    identifier: p.id.to_string(),
                     name: p.name,
-                    role: "Production Company".to_owned(),
                     source: MediaSource::Tmdb,
-                    character: None,
+                    identifier: p.id.to_string(),
+                    role: "Production Company".to_owned(),
                     source_specifics: Some(PersonSourceSpecifics {
                         is_tmdb_company: Some(true),
                         ..Default::default()
                     }),
+                    ..Default::default()
                 })
                 .collect_vec(),
         );
@@ -844,10 +844,10 @@ impl MediaProvider for TmdbMovieService {
             .results
             .into_iter()
             .map(|d| MetadataGroupSearchItem {
-                identifier: d.id.to_string(),
                 name: d.title.unwrap(),
+                identifier: d.id.to_string(),
                 image: d.poster_path.map(|p| self.base.get_image_url(p)),
-                parts: None,
+                ..Default::default()
             })
             .collect_vec();
         let next_page = if page < search.total_pages {
@@ -902,12 +902,12 @@ impl MediaProvider for TmdbMovieService {
             .parts
             .into_iter()
             .map(|p| PartialMetadataWithoutId {
+                lot: MediaLot::Movie,
                 title: p.title.unwrap(),
-                identifier: p.id.to_string(),
                 source: MediaSource::Tmdb,
-                lot: MediaLot::Movie,
+                identifier: p.id.to_string(),
                 image: p.poster_path.map(|p| self.base.get_image_url(p)),
-                is_recommendation: None,
+                ..Default::default()
             })
             .collect_vec();
         let title = replace_from_end(data.name, " Collection", "");
@@ -915,7 +915,6 @@ impl MediaProvider for TmdbMovieService {
             MetadataGroupWithoutId {
                 lot: MediaLot::Movie,
                 title: title.clone(),
-                display_images: vec![],
                 source: MediaSource::Tmdb,
                 description: data.overview,
                 identifier: identifier.to_owned(),
@@ -933,6 +932,7 @@ impl MediaProvider for TmdbMovieService {
                         })
                         .collect(),
                 ),
+                ..Default::default()
             },
             parts,
         ))
@@ -1059,12 +1059,12 @@ impl MediaProvider for TmdbShowService {
                             .flat_map(|g| {
                                 g.id.and_then(|id| {
                                     g.known_for_department.map(|r| PartialMetadataPerson {
-                                        identifier: id.to_string(),
-                                        name: g.name.unwrap_or_default(),
                                         role: r,
-                                        source: MediaSource::Tmdb,
                                         character: g.character,
-                                        source_specifics: None,
+                                        source: MediaSource::Tmdb,
+                                        identifier: id.to_string(),
+                                        name: g.name.unwrap_or_default(),
+                                        ..Default::default()
                                     })
                                 })
                             })
@@ -1079,15 +1079,15 @@ impl MediaProvider for TmdbShowService {
                 .unwrap_or_default()
                 .into_iter()
                 .map(|p| PartialMetadataPerson {
-                    identifier: p.id.to_string(),
                     name: p.name,
-                    role: "Production Company".to_owned(),
                     source: MediaSource::Tmdb,
-                    character: None,
+                    identifier: p.id.to_string(),
+                    role: "Production Company".to_owned(),
                     source_specifics: Some(PersonSourceSpecifics {
                         is_tmdb_company: Some(true),
                         ..Default::default()
                     }),
+                    ..Default::default()
                 })
                 .collect_vec(),
         );
diff --git a/crates/providers/src/vndb.rs b/crates/providers/src/vndb.rs
index fb9e58469d..02e1a2a666 100644
--- a/crates/providers/src/vndb.rs
+++ b/crates/providers/src/vndb.rs
@@ -101,8 +101,7 @@ impl MediaProvider for VndbService {
             .map(|b| PeopleSearchItem {
                 identifier: b.id,
                 name: b.title.unwrap(),
-                image: None,
-                birth_year: None,
+                ..Default::default()
             })
             .collect();
         let next_page = if data.more {
@@ -138,19 +137,11 @@ impl MediaProvider for VndbService {
         let data: SearchResponse = rsp.json().await.map_err(|e| anyhow!(e))?;
         let item = data.results.unwrap_or_default().pop().unwrap();
         Ok(MetadataPerson {
-            place: None,
-            gender: None,
-            images: None,
-            website: None,
-            related: vec![],
-            death_date: None,
-            birth_date: None,
-            source_url: None,
             identifier: item.id,
-            source_specifics: None,
             name: item.title.unwrap(),
             source: MediaSource::Vndb,
             description: item.description,
+            ..Default::default()
         })
     }
 
@@ -242,12 +233,11 @@ impl VndbService {
             .unwrap_or_default()
             .into_iter()
             .map(|a| PartialMetadataPerson {
-                identifier: a.id,
                 name: a.name,
-                role: "Developer".to_owned(),
+                identifier: a.id,
                 source: MediaSource::Vndb,
-                character: None,
-                source_specifics: None,
+                role: "Developer".to_owned(),
+                ..Default::default()
             })
             .collect_vec();
         let genres = item
diff --git a/crates/providers/src/youtube_music.rs b/crates/providers/src/youtube_music.rs
index 0b258b3703..2d4f355bea 100644
--- a/crates/providers/src/youtube_music.rs
+++ b/crates/providers/src/youtube_music.rs
@@ -59,9 +59,9 @@ impl MediaProvider for YoutubeMusicService {
                     title: t.name,
                     identifier: t.id,
                     lot: MediaLot::Music,
-                    is_recommendation: None,
                     source: MediaSource::YoutubeMusic,
                     image: self.largest_image(&t.cover).map(|c| c.url.to_owned()),
+                    ..Default::default()
                 })
                 .collect()
         } else {
@@ -106,10 +106,9 @@ impl MediaProvider for YoutubeMusicService {
                     a.id.map(|id| PartialMetadataPerson {
                         name: a.name,
                         identifier: id,
-                        character: None,
-                        source_specifics: None,
                         role: "Artist".to_string(),
                         source: MediaSource::YoutubeMusic,
+                        ..Default::default()
                     })
                 })
                 .collect(),
@@ -127,7 +126,7 @@ impl MediaProvider for YoutubeMusicService {
         let data = SearchResults {
             details: SearchDetails {
                 total: 1,
-                next_page: None,
+                ..Default::default()
             },
             items: results
                 .items
@@ -136,8 +135,8 @@ impl MediaProvider for YoutubeMusicService {
                 .map(|i| MetadataSearchItem {
                     title: i.name,
                     identifier: i.id,
-                    publish_year: None,
                     image: self.largest_image(&i.cover).map(|t| t.url.to_owned()),
+                    ..Default::default()
                 })
                 .collect(),
         };
@@ -155,7 +154,6 @@ impl MediaProvider for YoutubeMusicService {
                 title: title.clone(),
                 lot: MediaLot::Music,
                 identifier: album.id,
-                display_images: vec![],
                 source: MediaSource::YoutubeMusic,
                 parts: album.tracks.len().try_into().unwrap(),
                 description: album.description.map(|d| d.to_html()),
@@ -170,6 +168,7 @@ impl MediaProvider for YoutubeMusicService {
                         })
                         .collect(),
                 ),
+                ..Default::default()
             },
             album
                 .tracks
@@ -178,9 +177,9 @@ impl MediaProvider for YoutubeMusicService {
                     title: t.name,
                     identifier: t.id,
                     lot: MediaLot::Music,
-                    is_recommendation: None,
                     source: MediaSource::YoutubeMusic,
                     image: self.largest_image(&t.cover).map(|t| t.url.to_owned()),
+                    ..Default::default()
                 })
                 .collect(),
         ))
@@ -196,17 +195,17 @@ impl MediaProvider for YoutubeMusicService {
         Ok(SearchResults {
             details: SearchDetails {
                 total: 1,
-                next_page: None,
+                ..Default::default()
             },
             items: data
                 .items
                 .items
                 .into_iter()
                 .map(|t| MetadataGroupSearchItem {
-                    parts: None,
                     name: t.name,
                     identifier: t.id,
                     image: self.largest_image(&t.cover).map(|t| t.url.to_owned()),
+                    ..Default::default()
                 })
                 .collect(),
         })
@@ -225,16 +224,16 @@ impl MediaProvider for YoutubeMusicService {
                 .items
                 .into_iter()
                 .map(|t| MetadataPersonRelated {
-                    character: None,
                     role: "Artist".to_string(),
                     metadata: PartialMetadataWithoutId {
                         title: t.name,
                         identifier: t.id,
                         lot: MediaLot::Music,
-                        is_recommendation: None,
                         source: MediaSource::YoutubeMusic,
                         image: self.largest_image(&t.cover).map(|t| t.url.to_owned()),
+                        ..Default::default()
                     },
+                    ..Default::default()
                 })
                 .collect()
         } else {
@@ -243,13 +242,7 @@ impl MediaProvider for YoutubeMusicService {
         let identifier = data.id;
         Ok(MetadataPerson {
             related,
-            place: None,
-            gender: None,
-            website: None,
             name: data.name,
-            birth_date: None,
-            death_date: None,
-            source_specifics: None,
             description: data.description,
             identifier: identifier.clone(),
             source: MediaSource::YoutubeMusic,
@@ -260,6 +253,7 @@ impl MediaProvider for YoutubeMusicService {
                     .map(|t| t.url.to_owned())
                     .collect(),
             ),
+            ..Default::default()
         })
     }
 
@@ -274,7 +268,7 @@ impl MediaProvider for YoutubeMusicService {
         Ok(SearchResults {
             details: SearchDetails {
                 total: 1,
-                next_page: None,
+                ..Default::default()
             },
             items: data
                 .items
@@ -283,8 +277,8 @@ impl MediaProvider for YoutubeMusicService {
                 .map(|t| PeopleSearchItem {
                     name: t.name,
                     identifier: t.id,
-                    birth_year: None,
                     image: self.largest_image(&t.avatar).map(|t| t.url.to_owned()),
+                    ..Default::default()
                 })
                 .collect(),
         })
diff --git a/crates/services/cache/Cargo.toml b/crates/services/cache/Cargo.toml
index 34aa620ac3..3aefddbcec 100644
--- a/crates/services/cache/Cargo.toml
+++ b/crates/services/cache/Cargo.toml
@@ -11,6 +11,7 @@ common-utils = { path = "../../utils/common" }
 config = { path = "../../config" }
 database-models = { path = "../../models/database" }
 dependent-models = { path = "../../models/dependent" }
+env-utils = { path = "../../utils/env" }
 sea-orm = { workspace = true }
 sea-query = { workspace = true }
 serde = { workspace = true }
diff --git a/crates/services/cache/src/lib.rs b/crates/services/cache/src/lib.rs
index 16de70bd00..c1d34275d7 100644
--- a/crates/services/cache/src/lib.rs
+++ b/crates/services/cache/src/lib.rs
@@ -6,6 +6,7 @@ use common_models::ApplicationCacheKey;
 use common_utils::ryot_log;
 use database_models::{application_cache, prelude::ApplicationCache};
 use dependent_models::ApplicationCacheValue;
+use env_utils::APP_COMMIT_SHA;
 use sea_orm::{ActiveValue, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
 use sea_query::OnConflict;
 use serde::de::DeserializeOwned;
@@ -26,25 +27,25 @@ impl CacheService {
 }
 
 impl CacheService {
-    fn get_expiry_for_key(&self, key: &ApplicationCacheKey) -> Option<i64> {
+    fn get_expiry_for_key(&self, key: &ApplicationCacheKey) -> i64 {
         match key {
             ApplicationCacheKey::IgdbSettings
             | ApplicationCacheKey::ListennotesSettings
-            | ApplicationCacheKey::TmdbSettings => None,
+            | ApplicationCacheKey::TmdbSettings => 120,
 
             ApplicationCacheKey::CoreDetails
             | ApplicationCacheKey::PeopleSearch { .. }
             | ApplicationCacheKey::MetadataSearch { .. }
             | ApplicationCacheKey::MetadataGroupSearch { .. }
-            | ApplicationCacheKey::MetadataRecentlyConsumed { .. } => Some(1),
+            | ApplicationCacheKey::MetadataRecentlyConsumed { .. } => 1,
 
-            ApplicationCacheKey::UserAnalytics { .. } => Some(2),
+            ApplicationCacheKey::UserAnalytics { .. } => 2,
 
             ApplicationCacheKey::UserCollectionsList { .. }
-            | ApplicationCacheKey::UserAnalyticsParameters { .. } => Some(8),
+            | ApplicationCacheKey::UserAnalyticsParameters { .. } => 8,
 
             ApplicationCacheKey::ProgressUpdateCache { .. } => {
-                Some(self.config.server.progress_update_threshold)
+                self.config.server.progress_update_threshold
             }
         }
     }
@@ -59,8 +60,9 @@ impl CacheService {
         let to_insert = application_cache::ActiveModel {
             key: ActiveValue::Set(key),
             created_at: ActiveValue::Set(now),
+            version: ActiveValue::Set(APP_COMMIT_SHA.to_owned()),
             value: ActiveValue::Set(serde_json::to_value(value)?),
-            expires_at: ActiveValue::Set(expiry_hours.map(|hours| now + Duration::hours(hours))),
+            expires_at: ActiveValue::Set(Some(now + Duration::hours(expiry_hours))),
             ..Default::default()
         };
         let inserted = ApplicationCache::insert(to_insert)
@@ -68,6 +70,7 @@ impl CacheService {
                 OnConflict::column(application_cache::Column::Key)
                     .update_columns([
                         application_cache::Column::Value,
+                        application_cache::Column::Version,
                         application_cache::Column::ExpiresAt,
                         application_cache::Column::CreatedAt,
                     ])
@@ -91,6 +94,7 @@ impl CacheService {
                 cache
                     .expires_at
                     .map_or(true, |expires_at| expires_at > Utc::now())
+                    && cache.version == APP_COMMIT_SHA
             })
             .map(|m| m.value)?;
         serde_json::from_value::<T>(db_value).ok()
diff --git a/crates/services/collection/src/lib.rs b/crates/services/collection/src/lib.rs
index 7f90a7a064..1316dcf3d7 100644
--- a/crates/services/collection/src/lib.rs
+++ b/crates/services/collection/src/lib.rs
@@ -269,10 +269,7 @@ impl CollectionService {
                 items,
             }
         } else {
-            SearchResults {
-                details: SearchDetails::default(),
-                items: vec![],
-            }
+            SearchResults::default()
         };
         let user = collection
             .find_related(User)
diff --git a/crates/services/exporter/src/lib.rs b/crates/services/exporter/src/lib.rs
index ed464ff4cb..46763b99f0 100644
--- a/crates/services/exporter/src/lib.rs
+++ b/crates/services/exporter/src/lib.rs
@@ -56,9 +56,7 @@ pub struct ExporterService(pub Arc<SupportingService>);
 impl ExporterService {
     pub async fn deploy_export_job(&self, user_id: String) -> Result<bool> {
         self.0
-            .perform_application_job(ApplicationJob::Mp(
-                MpApplicationJob::PerformExport(user_id),
-            ))
+            .perform_application_job(ApplicationJob::Mp(MpApplicationJob::PerformExport(user_id)))
             .await?;
         Ok(true)
     }
@@ -356,15 +354,9 @@ impl ExporterService {
         user_id: &String,
         writer: &mut JsonStreamWriter<StdFile>,
     ) -> Result<()> {
-        let measurements = user_measurements_list(
-            &self.0.db,
-            user_id,
-            UserMeasurementsListInput {
-                start_time: None,
-                end_time: None,
-            },
-        )
-        .await?;
+        let measurements =
+            user_measurements_list(&self.0.db, user_id, UserMeasurementsListInput::default())
+                .await?;
         for measurement in measurements {
             writer.serialize_value(&measurement).unwrap();
         }
diff --git a/crates/services/fitness/src/lib.rs b/crates/services/fitness/src/lib.rs
index 89fb11155d..9b295a0726 100644
--- a/crates/services/fitness/src/lib.rs
+++ b/crates/services/fitness/src/lib.rs
@@ -102,23 +102,21 @@ impl FitnessService {
         server_key_validation_guard(self.0.is_server_key_validated().await?).await?;
         let mut summary = WorkoutSummary::default();
         let mut information = WorkoutInformation {
-            assets: None,
-            exercises: vec![],
             comment: input.comment,
             supersets: input.supersets,
+            ..Default::default()
         };
         for exercise in input.exercises {
             let db_ex = self.exercise_details(exercise.exercise_id.clone()).await?;
             summary.exercises.push(WorkoutSummaryExercise {
-                id: exercise.exercise_id.clone(),
-                best_set: None,
-                lot: None,
                 num_sets: exercise.sets.len(),
+                id: exercise.exercise_id.clone(),
+                ..Default::default()
             });
             information.exercises.push(ProcessedExercise {
-                total: None,
-                assets: None,
                 lot: db_ex.lot,
+                notes: exercise.notes,
+                id: exercise.exercise_id,
                 sets: exercise
                     .sets
                     .into_iter()
@@ -126,15 +124,13 @@ impl FitnessService {
                         lot: s.lot,
                         rpe: s.rpe,
                         note: s.note,
-                        totals: None,
-                        confirmed_at: None,
-                        personal_bests: None,
                         rest_time: s.rest_time,
                         statistic: s.statistic,
+                        rest_timer_started_at: s.rest_timer_started_at,
+                        ..Default::default()
                     })
                     .collect(),
-                notes: exercise.notes,
-                id: exercise.exercise_id,
+                ..Default::default()
             });
         }
         let processed_exercises = information.exercises.clone();
@@ -236,10 +232,9 @@ impl FitnessService {
         let reviews =
             item_reviews(&user_id, &exercise_id, EntityLot::Exercise, true, &self.0).await?;
         let mut resp = UserExerciseDetails {
-            details: None,
-            history: None,
             collections,
             reviews,
+            ..Default::default()
         };
         if let Some(association) = UserToEntity::find()
             .filter(user_to_entity::Column::UserId.eq(user_id))
@@ -420,9 +415,7 @@ impl FitnessService {
             "Instance does not have exercises data. Deploying job to download them..."
         );
         self.0
-            .perform_application_job(ApplicationJob::Mp(
-                MpApplicationJob::UpdateGithubExercises,
-            ))
+            .perform_application_job(ApplicationJob::Mp(MpApplicationJob::UpdateGithubExercises))
             .await?;
         Ok(())
     }
@@ -444,7 +437,7 @@ impl FitnessService {
                 .into_iter()
                 .map(StoredUrl::Url)
                 .collect(),
-            images: vec![],
+            ..Default::default()
         };
         let mut muscles = ex.attributes.primary_muscles;
         muscles.extend(ex.attributes.secondary_muscles);
diff --git a/crates/services/importer/src/audiobookshelf.rs b/crates/services/importer/src/audiobookshelf.rs
index 4226f776e2..3433c72cff 100644
--- a/crates/services/importer/src/audiobookshelf.rs
+++ b/crates/services/importer/src/audiobookshelf.rs
@@ -84,20 +84,20 @@ where
                         Some((identifier, source)) => (identifier, MediaLot::Book, source, None),
                         _ => {
                             failed.push(ImportFailedItem {
-                                error: Some("No Google Books ID found".to_string()),
                                 identifier: title,
-                                lot: None,
                                 step: ImportFailStep::InputTransformation,
+                                error: Some("No Google Books ID found".to_string()),
+                                ..Default::default()
                             });
                             continue;
                         }
                     },
                     _ => {
                         failed.push(ImportFailedItem {
-                            error: Some("No ISBN found".to_string()),
                             identifier: title,
-                            lot: None,
+                            error: Some("No ISBN found".to_string()),
                             step: ImportFailStep::InputTransformation,
+                            ..Default::default()
                         });
                         continue;
                     }
diff --git a/crates/services/importer/src/goodreads.rs b/crates/services/importer/src/goodreads.rs
index 6f4b342a72..6ec20625cd 100644
--- a/crates/services/importer/src/goodreads.rs
+++ b/crates/services/importer/src/goodreads.rs
@@ -93,8 +93,6 @@ pub async fn import(
         };
         let mut seen_history = vec![
             ImportOrExportMetadataItemSeen {
-                started_on: None,
-                ended_on: None,
                 provider_watched_on: Some(ImportSource::Goodreads.to_string()),
                 ..Default::default()
             };
@@ -124,10 +122,9 @@ pub async fn import(
         let mut review = None;
         if !record.review.is_empty() {
             review = Some(ImportOrExportItemReview {
-                date: None,
                 spoiler: Some(false),
                 text: Some(record.review),
-                visibility: None,
+                ..Default::default()
             });
         }
         completed.push(ImportCompletedItem::Metadata(ImportOrExportMetadataItem {
diff --git a/crates/services/importer/src/hevy.rs b/crates/services/importer/src/hevy.rs
index eb6d2ffc60..9bdbb2bcfe 100644
--- a/crates/services/importer/src/hevy.rs
+++ b/crates/services/importer/src/hevy.rs
@@ -91,7 +91,6 @@ pub async fn import(
                 ExerciseLot::Reps
             } else {
                 failed.push(ImportFailedItem {
-                    lot: None,
                     step: ImportFailStep::InputTransformation,
                     identifier: format!(
                         "Workout #{:#?}, Set #{}",
@@ -101,6 +100,7 @@ pub async fn import(
                         "Could not determine exercise lot: {}",
                         serde_json::to_string(&valid_ex).unwrap()
                     )),
+                    ..Default::default()
                 });
                 continue;
             };
@@ -122,11 +122,8 @@ pub async fn import(
                     _ => SetLot::Normal,
                 };
                 collected_sets.push(UserWorkoutSetRecord {
-                    note: None,
                     lot: set_lot,
                     rpe: set.rpe,
-                    rest_time: None,
-                    confirmed_at: None,
                     statistic: WorkoutSetStatistic {
                         weight,
                         reps: set.reps,
@@ -134,34 +131,29 @@ pub async fn import(
                         distance: set.distance.and_then(|d| d.checked_div(dec!(1000))),
                         ..Default::default()
                     },
+                    ..Default::default()
                 });
             }
             collected_exercises.push(UserExerciseInput {
                 exercise_id,
-                assets: None,
                 sets: collected_sets,
                 notes: first_exercise
                     .exercise_notes
                     .clone()
                     .map(|n| vec![n])
                     .unwrap_or_default(),
+                ..Default::default()
             });
         }
         let start_time = parse_date_string(&first_exercise.start_time);
         let end_time = parse_date_string(&first_exercise.end_time);
         completed.push(ImportCompletedItem::Workout(UserWorkoutInput {
-            assets: None,
-            supersets: vec![],
-            template_id: None,
-            repeated_from: None,
-            create_workout_id: None,
-            update_workout_id: None,
             exercises: collected_exercises,
-            update_workout_template_id: None,
             name: first_exercise.title.clone(),
             comment: first_exercise.description.clone(),
             end_time: utils::get_date_time_with_offset(end_time, &ss.timezone),
             start_time: utils::get_date_time_with_offset(start_time, &ss.timezone),
+            ..Default::default()
         }));
     }
     completed.extend(
diff --git a/crates/services/importer/src/imdb.rs b/crates/services/importer/src/imdb.rs
index 58beb46ef7..463d0b452a 100644
--- a/crates/services/importer/src/imdb.rs
+++ b/crates/services/importer/src/imdb.rs
@@ -37,10 +37,10 @@ pub async fn import(
             Ok(r) => r,
             Err(e) => {
                 failed.push(ImportFailedItem {
-                    lot: None,
-                    step: ImportFailStep::InputTransformation,
-                    identifier: idx.to_string(),
                     error: Some(e.to_string()),
+                    identifier: idx.to_string(),
+                    step: ImportFailStep::InputTransformation,
+                    ..Default::default()
                 });
                 continue;
             }
@@ -50,10 +50,10 @@ pub async fn import(
             "TV Series" | "TV Mini Series" | "tvSeries" | "tvMiniSeries" => MediaLot::Show,
             tt => {
                 failed.push(ImportFailedItem {
-                    lot: None,
-                    step: ImportFailStep::InputTransformation,
                     identifier: record.id.clone(),
+                    step: ImportFailStep::InputTransformation,
                     error: Some(format!("Unknown title type: {tt}")),
+                    ..Default::default()
                 });
                 continue;
             }
diff --git a/crates/services/importer/src/jellyfin.rs b/crates/services/importer/src/jellyfin.rs
index a86a16820b..d1bbe4a1e6 100644
--- a/crates/services/importer/src/jellyfin.rs
+++ b/crates/services/importer/src/jellyfin.rs
@@ -66,10 +66,10 @@ pub async fn import(input: DeployUrlAndKeyAndUsernameImportInput) -> Result<Impo
             }
             _ => {
                 failed.push(ImportFailedItem {
-                    step: ImportFailStep::ItemDetailsFromSource,
                     identifier: item.name,
+                    step: ImportFailStep::ItemDetailsFromSource,
                     error: Some(format!("Unknown media type: {:?}", type_)),
-                    lot: None,
+                    ..Default::default()
                 });
                 continue;
             }
@@ -97,10 +97,10 @@ pub async fn import(input: DeployUrlAndKeyAndUsernameImportInput) -> Result<Impo
             });
         } else {
             failed.push(ImportFailedItem {
-                step: ImportFailStep::ItemDetailsFromSource,
                 identifier: item.name,
                 error: Some("No tmdb id found".to_string()),
-                lot: None,
+                step: ImportFailStep::ItemDetailsFromSource,
+                ..Default::default()
             });
         }
     }
diff --git a/crates/services/importer/src/mediatracker.rs b/crates/services/importer/src/mediatracker.rs
index 78c1ea87a3..b7e834c220 100644
--- a/crates/services/importer/src/mediatracker.rs
+++ b/crates/services/importer/src/mediatracker.rs
@@ -186,10 +186,10 @@ pub async fn import(input: DeployUrlAndKeyImportInput) -> Result<ImportResult> {
     for (idx, d) in data.into_iter().enumerate() {
         let Some(media_type) = d.media_type else {
             failed.push(ImportFailedItem {
-                lot: None,
-                step: ImportFailStep::ItemDetailsFromSource,
                 identifier: d.id.to_string(),
                 error: Some("No media type".to_string()),
+                step: ImportFailStep::ItemDetailsFromSource,
+                ..Default::default()
             });
             continue;
         };
@@ -263,9 +263,9 @@ pub async fn import(input: DeployUrlAndKeyImportInput) -> Result<ImportResult> {
                 let review = if let Some(_s) = r.clone().review {
                     Some(ImportOrExportItemReview {
                         date: r.date,
-                        spoiler: Some(false),
                         text: r.review,
-                        visibility: None,
+                        spoiler: Some(false),
+                        ..Default::default()
                     })
                 } else {
                     None
diff --git a/crates/services/importer/src/movary.rs b/crates/services/importer/src/movary.rs
index fb147567a7..33f354d28d 100644
--- a/crates/services/importer/src/movary.rs
+++ b/crates/services/importer/src/movary.rs
@@ -110,16 +110,15 @@ pub async fn import(input: DeployMovaryImportInput) -> Result<ImportResult> {
         };
         let watched_at = Some(convert_naive_to_utc(record.watched_at));
         let seen_item = ImportOrExportMetadataItemSeen {
-            started_on: None,
             ended_on: watched_at.map(|d| d.date_naive()),
             provider_watched_on: Some(ImportSource::Movary.to_string()),
             ..Default::default()
         };
         let review = record.comment.map(|c| ImportOrExportItemReview {
-            spoiler: Some(false),
             text: Some(c),
             date: watched_at,
-            visibility: None,
+            spoiler: Some(false),
+            ..Default::default()
         });
         if let Some(media) = media
             .iter_mut()
diff --git a/crates/services/importer/src/myanimelist.rs b/crates/services/importer/src/myanimelist.rs
index d05fe8fdaf..cdfd24fa23 100644
--- a/crates/services/importer/src/myanimelist.rs
+++ b/crates/services/importer/src/myanimelist.rs
@@ -82,7 +82,6 @@ fn convert_to_format(item: Item, lot: MediaLot) -> ImportOrExportMetadataItem {
         })
         .collect_vec();
     let review_item = ImportOrExportItemRating {
-        review: None,
         rating: if item.my_score == 0 {
             None
         } else {
diff --git a/crates/services/importer/src/open_scale.rs b/crates/services/importer/src/open_scale.rs
index 8b8a6e939b..5cd30a8d0c 100644
--- a/crates/services/importer/src/open_scale.rs
+++ b/crates/services/importer/src/open_scale.rs
@@ -52,10 +52,10 @@ pub async fn import(
             Ok(r) => r,
             Err(e) => {
                 failed.push(ImportFailedItem {
-                    lot: None,
-                    step: ImportFailStep::InputTransformation,
-                    identifier: idx.to_string(),
                     error: Some(e.to_string()),
+                    identifier: idx.to_string(),
+                    step: ImportFailStep::InputTransformation,
+                    ..Default::default()
                 });
                 continue;
             }
@@ -65,8 +65,6 @@ pub async fn import(
         let timestamp = utils::get_date_time_with_offset(ndt, timezone);
         completed.push(ImportCompletedItem::Measurement(user_measurement::Model {
             timestamp,
-            user_id: "".to_string(),
-            name: None,
             comment: record.comment,
             stats: UserMeasurementStats {
                 weight: record.weight,
@@ -88,6 +86,7 @@ pub async fn import(
                 total_body_water: record.water,
                 ..Default::default()
             },
+            ..Default::default()
         }));
     }
     Ok(ImportResult { failed, completed })
diff --git a/crates/services/importer/src/plex.rs b/crates/services/importer/src/plex.rs
index b7c1c36b44..6878610fb8 100644
--- a/crates/services/importer/src/plex.rs
+++ b/crates/services/importer/src/plex.rs
@@ -73,9 +73,7 @@ pub async fn import(input: DeployUrlAndKeyImportInput) -> Result<ImportResult> {
                 MediaLot::Movie => {
                     success_items.push(ImportCompletedItem::Metadata(ImportOrExportMetadataItem {
                         lot,
-                        reviews: vec![],
                         source_id: item.key,
-                        collections: vec![],
                         source: MediaSource::Tmdb,
                         identifier: tmdb_id.to_string(),
                         seen_history: vec![ImportOrExportMetadataItemSeen {
@@ -83,6 +81,7 @@ pub async fn import(input: DeployUrlAndKeyImportInput) -> Result<ImportResult> {
                             provider_watched_on: Some(ImportSource::Plex.to_string()),
                             ..Default::default()
                         }],
+                        ..Default::default()
                     }));
                 }
                 MediaLot::Show => {
@@ -98,12 +97,10 @@ pub async fn import(input: DeployUrlAndKeyImportInput) -> Result<ImportResult> {
                         .await?;
                     let mut item = ImportOrExportMetadataItem {
                         lot,
-                        reviews: vec![],
-                        collections: vec![],
-                        seen_history: vec![],
                         source: MediaSource::Tmdb,
                         source_id: item.key.clone(),
                         identifier: tmdb_id.to_string(),
+                        ..Default::default()
                     };
                     for leaf in leaves.media_container.metadata {
                         if leaf.last_viewed_at.is_some() {
diff --git a/crates/services/importer/src/storygraph.rs b/crates/services/importer/src/storygraph.rs
index 7474f51153..092ea89789 100644
--- a/crates/services/importer/src/storygraph.rs
+++ b/crates/services/importer/src/storygraph.rs
@@ -108,8 +108,6 @@ pub async fn import(
         );
         let mut seen_history = vec![
             ImportOrExportMetadataItemSeen {
-                started_on: None,
-                ended_on: None,
                 provider_watched_on: Some(ImportSource::Storygraph.to_string()),
                 ..Default::default()
             };
@@ -129,25 +127,24 @@ pub async fn import(
             collections.extend(t.split(", ").map(|d| d.to_case(Case::Title)))
         }
         media.push(ImportOrExportMetadataItem {
-            source_id: record.title.clone(),
             lot,
             source,
             identifier,
+            collections,
             seen_history,
+            source_id: record.title.clone(),
             reviews: vec![ImportOrExportItemRating {
                 rating: record
                     .rating
                     // DEV: Rates items out of 10
                     .map(|d| d.saturating_mul(dec!(10))),
                 review: record.review.map(|r| ImportOrExportItemReview {
-                    date: None,
-                    spoiler: Some(false),
                     text: Some(r),
-                    visibility: None,
+                    spoiler: Some(false),
+                    ..Default::default()
                 }),
                 ..Default::default()
             }],
-            collections,
         })
     }
     Ok(ImportResult {
diff --git a/crates/services/importer/src/strong_app.rs b/crates/services/importer/src/strong_app.rs
index 4da294153c..fc4a51d27b 100644
--- a/crates/services/importer/src/strong_app.rs
+++ b/crates/services/importer/src/strong_app.rs
@@ -129,7 +129,6 @@ async fn import_exercises(
                 ExerciseLot::Reps
             } else {
                 failed.push(ImportFailedItem {
-                    lot: None,
                     step: ImportFailStep::InputTransformation,
                     identifier: format!(
                         "Workout #{}, Set #{}",
@@ -139,6 +138,7 @@ async fn import_exercises(
                         "Could not determine exercise lot: {}",
                         serde_json::to_string(&valid_ex).unwrap()
                     )),
+                    ..Default::default()
                 });
                 continue;
             };
@@ -163,11 +163,7 @@ async fn import_exercises(
                     _ => SetLot::Normal,
                 };
                 collected_sets.push(UserWorkoutSetRecord {
-                    rpe: None,
-                    note: None,
                     lot: set_lot,
-                    rest_time: None,
-                    confirmed_at: None,
                     statistic: WorkoutSetStatistic {
                         weight,
                         reps: set.reps,
@@ -175,28 +171,23 @@ async fn import_exercises(
                         distance: set.distance.and_then(|d| d.checked_div(dec!(1000))),
                         ..Default::default()
                     },
+                    ..Default::default()
                 });
             }
             collected_exercises.push(UserExerciseInput {
                 notes,
                 exercise_id,
-                assets: None,
                 sets: collected_sets,
+                ..Default::default()
             });
         }
         completed.push(ImportCompletedItem::Workout(UserWorkoutInput {
-            assets: None,
             start_time: ndt,
-            supersets: vec![],
-            template_id: None,
-            repeated_from: None,
-            create_workout_id: None,
-            update_workout_id: None,
             exercises: collected_exercises,
             end_time: ndt + workout_duration,
-            update_workout_template_id: None,
             name: first_exercise.workout_name.clone(),
             comment: first_exercise.workout_notes.clone(),
+            ..Default::default()
         }));
     }
     completed.extend(
diff --git a/crates/services/importer/src/trakt.rs b/crates/services/importer/src/trakt.rs
index 1389159a9d..8286477887 100644
--- a/crates/services/importer/src/trakt.rs
+++ b/crates/services/importer/src/trakt.rs
@@ -21,7 +21,7 @@ use super::{ImportFailStep, ImportFailedItem, ImportOrExportMetadataItem};
 const API_URL: &str = "https://api.trakt.tv";
 const API_VERSION: &str = "2";
 
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Debug, Default, Serialize, Deserialize)]
 struct Id {
     trakt: u64,
     tmdb: Option<u64>,
@@ -45,11 +45,11 @@ struct ListItemResponse {
     rating: Option<Decimal>,
 }
 
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize, Default)]
 struct ListResponse {
+    ids: Id,
     name: String,
     description: Option<String>,
-    ids: Id,
     #[serde(default)]
     items: Vec<ListItemResponse>,
 }
@@ -90,13 +90,9 @@ pub async fn import(input: DeployTraktImportInput) -> Result<ImportResult> {
             .unwrap();
         let items: Vec<ListItemResponse> = rsp.json().await.unwrap();
         lists.push(ListResponse {
-            name: list.to_owned(),
-            description: None,
-            ids: Id {
-                trakt: 0,
-                tmdb: None,
-            },
             items,
+            name: list.to_owned(),
+            ..Default::default()
         });
     }
 
@@ -128,10 +124,9 @@ pub async fn import(input: DeployTraktImportInput) -> Result<ImportResult> {
                             // DEV: Rates items out of 10
                             .map(|e| e * dec!(10)),
                         review: Some(ImportOrExportItemReview {
-                            spoiler: Some(false),
-                            text: None,
                             date: item.rated_at,
-                            visibility: None,
+                            spoiler: Some(false),
+                            ..Default::default()
                         }),
                         ..Default::default()
                     });
@@ -240,10 +235,9 @@ fn process_item(i: &ListItemResponse) -> Result<ImportOrExportMetadataItem, Impo
         (d.ids.trakt, d.ids.tmdb, MediaLot::Show)
     } else {
         return Err(ImportFailedItem {
-            lot: None,
             step: ImportFailStep::ItemDetailsFromSource,
-            identifier: "".to_owned(),
             error: Some("Item is neither a movie or a show".to_owned()),
+            ..Default::default()
         });
     };
     match identifier {
@@ -255,10 +249,9 @@ fn process_item(i: &ListItemResponse) -> Result<ImportOrExportMetadataItem, Impo
             ..Default::default()
         }),
         None => Err(ImportFailedItem {
-            lot: None,
-            identifier: "".to_owned(),
             step: ImportFailStep::ItemDetailsFromSource,
             error: Some("Item does not have an associated TMDB id".to_owned()),
+            ..Default::default()
         }),
     }
 }
diff --git a/crates/services/integration/src/lib.rs b/crates/services/integration/src/lib.rs
index 968d3034e1..0c9844f4f0 100644
--- a/crates/services/integration/src/lib.rs
+++ b/crates/services/integration/src/lib.rs
@@ -1,6 +1,6 @@
 use std::{collections::HashSet, sync::Arc};
 
-use async_graphql::{Error, Result as GqlResult};
+use async_graphql::{Error, Result};
 use chrono::Utc;
 use common_utils::ryot_log;
 use database_models::{
@@ -32,7 +32,7 @@ impl IntegrationService {
         &self,
         integration: integration::Model,
         updates: ImportResult,
-    ) -> GqlResult<()> {
+    ) -> Result<()> {
         let mut import = updates;
         import.completed.iter_mut().for_each(|item| {
             if let ImportCompletedItem::Metadata(metadata) = item {
@@ -89,7 +89,7 @@ impl IntegrationService {
         &self,
         integration_slug: String,
         payload: String,
-    ) -> GqlResult<String> {
+    ) -> Result<String> {
         ryot_log!(
             debug,
             "Processing integration webhook for slug: {}",
@@ -128,7 +128,7 @@ impl IntegrationService {
     pub async fn handle_entity_added_to_collection_event(
         &self,
         collection_to_entity_id: Uuid,
-    ) -> GqlResult<()> {
+    ) -> Result<()> {
         let cte = CollectionToEntity::find_by_id(collection_to_entity_id)
             .one(&self.0.db)
             .await?
@@ -210,7 +210,7 @@ impl IntegrationService {
         Ok(())
     }
 
-    pub async fn handle_on_seen_complete(&self, id: String) -> GqlResult<()> {
+    pub async fn handle_on_seen_complete(&self, id: String) -> Result<()> {
         let (seen, show_extra_information, metadata_title, metadata_lot) = Seen::find_by_id(id)
             .left_join(Metadata)
             .select_only()
@@ -246,7 +246,7 @@ impl IntegrationService {
         Ok(())
     }
 
-    pub async fn yank_integrations_data_for_user(&self, user_id: &String) -> GqlResult<()> {
+    async fn yank_integrations_data_for_user(&self, user_id: &String) -> Result<()> {
         let preferences = user_by_id(user_id, &self.0).await?.preferences;
         if preferences.general.disable_integrations {
             return Ok(());
@@ -307,7 +307,7 @@ impl IntegrationService {
         Ok(())
     }
 
-    pub async fn yank_integrations_data(&self) -> GqlResult<()> {
+    pub async fn yank_integrations_data(&self) -> Result<()> {
         let users_with_integrations = Integration::find()
             .filter(integration::Column::Lot.eq(IntegrationLot::Yank))
             .select_only()
@@ -324,10 +324,10 @@ impl IntegrationService {
         Ok(())
     }
 
-    pub async fn sync_integrations_data_to_owned_collection_for_user(
+    async fn sync_integrations_data_to_owned_collection_for_user(
         &self,
         user_id: &String,
-    ) -> GqlResult<bool> {
+    ) -> Result<bool> {
         let preferences = user_by_id(user_id, &self.0).await?.preferences;
         if preferences.general.disable_integrations {
             return Ok(false);
@@ -383,7 +383,7 @@ impl IntegrationService {
         Ok(true)
     }
 
-    pub async fn sync_integrations_data_to_owned_collection(&self) -> GqlResult<()> {
+    async fn sync_integrations_data_to_owned_collection(&self) -> Result<()> {
         let users_with_integrations = Integration::find()
             .filter(integration::Column::SyncToOwnedCollection.eq(true))
             .select_only()
@@ -404,4 +404,17 @@ impl IntegrationService {
         }
         Ok(())
     }
+
+    pub async fn sync_integrations_data_for_user(&self, user_id: &String) -> Result<()> {
+        self.sync_integrations_data_to_owned_collection_for_user(user_id)
+            .await?;
+        self.yank_integrations_data_for_user(user_id).await?;
+        Ok(())
+    }
+
+    pub async fn sync_integrations_data(&self) -> Result<()> {
+        self.yank_integrations_data().await?;
+        self.sync_integrations_data_to_owned_collection().await?;
+        Ok(())
+    }
 }
diff --git a/crates/services/integration/src/yank/audiobookshelf.rs b/crates/services/integration/src/yank/audiobookshelf.rs
index 203e013d87..bfdfa4177a 100644
--- a/crates/services/integration/src/yank/audiobookshelf.rs
+++ b/crates/services/integration/src/yank/audiobookshelf.rs
@@ -2,7 +2,6 @@ use std::{future::Future, sync::Arc};
 
 use anyhow::{anyhow, Result};
 use application_utils::{get_base_http_client, get_podcast_episode_number_by_name};
-use async_graphql::Result as GqlResult;
 use common_models::{DefaultCollection, StringIdObject};
 use common_utils::ryot_log;
 use dependent_models::{ImportCompletedItem, ImportResult};
@@ -37,7 +36,7 @@ pub async fn yank_progress<F>(
     commit_metadata: impl Fn(UniqueMediaIdentifier) -> F,
 ) -> Result<ImportResult>
 where
-    F: Future<Output = GqlResult<StringIdObject>>,
+    F: Future<Output = async_graphql::Result<StringIdObject>>,
 {
     let url = format!("{}/api", base_url);
     let client = get_http_client(&access_token);
diff --git a/crates/services/miscellaneous/src/lib.rs b/crates/services/miscellaneous/src/lib.rs
index c46a55dfc7..3728bd2a43 100644
--- a/crates/services/miscellaneous/src/lib.rs
+++ b/crates/services/miscellaneous/src/lib.rs
@@ -69,8 +69,8 @@ use media_models::{
     GroupedCalendarEvent, ImportOrExportItemReviewComment, MediaAssociatedPersonStateChanges,
     MediaGeneralFilter, MediaSortBy, MetadataCreator, MetadataCreatorGroupedByRole,
     MetadataFreeCreator, MetadataGroupsListInput, MetadataImage, MetadataListInput,
-    MetadataPartialDetails, MetadataSearchItemResponse, MetadataVideo, MetadataVideoSource,
-    PartialMetadata, PartialMetadataWithoutId, PeopleListInput, PersonAndMetadataGroupsSortBy,
+    MetadataPartialDetails, MetadataVideo, MetadataVideoSource, PartialMetadata,
+    PartialMetadataWithoutId, PeopleListInput, PersonAndMetadataGroupsSortBy,
     PersonDetailsGroupedByRole, PersonDetailsItemWithCharacter, PodcastSpecifics,
     ProgressUpdateInput, ReviewPostedEvent, SeenAnimeExtraInformation, SeenPodcastExtraInformation,
     SeenShowExtraInformation, ShowSpecifics, UniqueMediaIdentifier, UpdateSeenItemInput,
@@ -307,10 +307,9 @@ ORDER BY RANDOM() LIMIT 10;
         if let Some(free_creators) = &meta.free_creators {
             for cr in free_creators.clone() {
                 let creator = MetadataCreator {
-                    id: None,
                     name: cr.name,
                     image: cr.image,
-                    character: None,
+                    ..Default::default()
                 };
                 creators
                     .entry(cr.role)
@@ -1050,9 +1049,9 @@ ORDER BY RANDOM() LIMIT 10;
         input: Vec<ProgressUpdateInput>,
     ) -> Result<bool> {
         self.0
-            .perform_application_job(ApplicationJob::Hp(
-                HpApplicationJob::BulkProgressUpdate(user_id, input),
-            ))
+            .perform_application_job(ApplicationJob::Hp(HpApplicationJob::BulkProgressUpdate(
+                user_id, input,
+            )))
             .await?;
         Ok(true)
     }
@@ -1277,9 +1276,9 @@ ORDER BY RANDOM() LIMIT 10;
             .unwrap()
             .unwrap();
         self.0
-            .perform_application_job(ApplicationJob::Mp(
-                MpApplicationJob::UpdatePerson(person.id),
-            ))
+            .perform_application_job(ApplicationJob::Mp(MpApplicationJob::UpdatePerson(
+                person.id,
+            )))
             .await?;
         Ok(true)
     }
@@ -1294,9 +1293,9 @@ ORDER BY RANDOM() LIMIT 10;
             .unwrap()
             .unwrap();
         self.0
-            .perform_application_job(ApplicationJob::Mp(
-                MpApplicationJob::UpdateMetadataGroup(metadata_group.id),
-            ))
+            .perform_application_job(ApplicationJob::Mp(MpApplicationJob::UpdateMetadataGroup(
+                metadata_group.id,
+            )))
             .await?;
         Ok(true)
     }
@@ -1461,72 +1460,13 @@ ORDER BY RANDOM() LIMIT 10;
         }
         let query = input.search.query.unwrap_or_default();
         if query.is_empty() {
-            return Ok(SearchResults {
-                details: SearchDetails {
-                    total: 0,
-                    next_page: None,
-                },
-                items: vec![],
-            });
+            return Ok(SearchResults::default());
         }
-        let cloned_user_id = user_id.to_owned();
         let preferences = user_by_id(user_id, &self.0).await?.preferences;
         let provider = get_metadata_provider(input.lot, input.source, &self.0).await?;
         let results = provider
             .metadata_search(&query, input.search.page, preferences.general.display_nsfw)
             .await?;
-        let all_identifiers = results
-            .items
-            .iter()
-            .map(|i| i.identifier.to_owned())
-            .collect_vec();
-        let interactions = Metadata::find()
-            .join(
-                JoinType::LeftJoin,
-                metadata::Relation::UserToEntity
-                    .def()
-                    .on_condition(move |_left, right| {
-                        Condition::all().add(
-                            Expr::col((right, user_to_entity::Column::UserId))
-                                .eq(cloned_user_id.clone()),
-                        )
-                    }),
-            )
-            .select_only()
-            .column(metadata::Column::Identifier)
-            .column_as(
-                Expr::col((Alias::new("metadata"), metadata::Column::Id)),
-                "database_id",
-            )
-            .column_as(
-                Expr::col((Alias::new("user_to_entity"), user_to_entity::Column::Id)).is_not_null(),
-                "has_interacted",
-            )
-            .filter(metadata::Column::Lot.eq(input.lot))
-            .filter(metadata::Column::Source.eq(input.source))
-            .filter(metadata::Column::Identifier.is_in(&all_identifiers))
-            .into_tuple::<(String, String, bool)>()
-            .all(&self.0.db)
-            .await?
-            .into_iter()
-            .map(|(key, value1, value2)| (key, (value1, value2)));
-        let interactions = HashMap::<_, _>::from_iter(interactions.into_iter());
-        let data = results
-            .items
-            .into_iter()
-            .map(|i| {
-                let interaction = interactions.get(&i.identifier).cloned();
-                MetadataSearchItemResponse {
-                    has_interacted: interaction.clone().unwrap_or_default().1,
-                    database_id: interaction.map(|i| i.0),
-                    item: i,
-                }
-            })
-            .collect();
-        let results = SearchResults {
-            details: results.details,
-            items: data,
-        };
         cc.set_key(
             cache_key,
             ApplicationCacheValue::MetadataSearch(results.clone()),
@@ -1550,13 +1490,7 @@ ORDER BY RANDOM() LIMIT 10;
         }
         let query = input.search.query.unwrap_or_default();
         if query.is_empty() {
-            return Ok(SearchResults {
-                details: SearchDetails {
-                    total: 0,
-                    next_page: None,
-                },
-                items: vec![],
-            });
+            return Ok(SearchResults::default());
         }
         let preferences = user_by_id(user_id, &self.0).await?.preferences;
         let provider = self.get_non_metadata_provider(input.source).await?;
@@ -1591,13 +1525,7 @@ ORDER BY RANDOM() LIMIT 10;
         }
         let query = input.search.query.unwrap_or_default();
         if query.is_empty() {
-            return Ok(SearchResults {
-                details: SearchDetails {
-                    total: 0,
-                    next_page: None,
-                },
-                items: vec![],
-            });
+            return Ok(SearchResults::default());
         }
         let preferences = user_by_id(user_id, &self.0).await?.preferences;
         let provider = get_metadata_provider(input.lot, input.source, &self.0).await?;
@@ -1675,8 +1603,8 @@ ORDER BY RANDOM() LIMIT 10;
         }
     }
 
-    pub async fn handle_after_media_seen_tasks(&self, seen: seen::Model) -> Result<()> {
-        handle_after_media_seen_tasks(seen, &self.0).await
+    pub async fn handle_after_media_seen_tasks(&self, seen: Box<seen::Model>) -> Result<()> {
+        handle_after_media_seen_tasks(*seen, &self.0).await
     }
 
     pub async fn delete_seen_item(
@@ -1767,7 +1695,7 @@ ORDER BY RANDOM() LIMIT 10;
             .map(|c| MetadataFreeCreator {
                 name: c,
                 role: "Creator".to_string(),
-                image: None,
+                ..Default::default()
             })
             .collect_vec();
         let is_partial = match input.lot {
@@ -2573,6 +2501,11 @@ ORDER BY RANDOM() LIMIT 10;
         Ok(notifications)
     }
 
+    pub async fn delete_all_application_cache(&self) -> Result<()> {
+        ApplicationCache::delete_many().exec(&self.0.db).await?;
+        Ok(())
+    }
+
     pub async fn update_metadata_and_notify_users(&self, metadata_id: &String) -> Result<()> {
         update_metadata_and_notify_users(metadata_id, &self.0).await
     }
@@ -2918,9 +2851,7 @@ ORDER BY RANDOM() LIMIT 10;
 
     pub async fn sync_integrations_data_to_owned_collection(&self) -> Result<()> {
         self.0
-            .perform_application_job(ApplicationJob::Mp(
-                MpApplicationJob::SyncIntegrationsData,
-            ))
+            .perform_application_job(ApplicationJob::Mp(MpApplicationJob::SyncIntegrationsData))
             .await?;
         Ok(())
     }
diff --git a/crates/services/statistics/src/lib.rs b/crates/services/statistics/src/lib.rs
index 76a234d54d..490abe6a58 100644
--- a/crates/services/statistics/src/lib.rs
+++ b/crates/services/statistics/src/lib.rs
@@ -123,15 +123,21 @@ impl StatisticsService {
         };
         let day_alias = Expr::col(Alias::new("day"));
         let date_type = Alias::new("DATE");
+        let default_date = Expr::val("2001-01-01");
         let items = precondition
             .column_as(
                 Expr::expr(Func::cast_as(
-                    Func::cust(DateTrunc)
-                        .arg(Expr::val(grouped_by.to_string()))
-                        .arg(Func::coalesce([
-                            Expr::col(daily_user_activity::Column::Date).into(),
-                            Func::cast_as(Expr::val("2001-01-01"), date_type.clone()).into(),
-                        ])),
+                    match grouped_by {
+                        DailyUserActivitiesResponseGroupedBy::Millennium => default_date,
+                        _ => Expr::expr(
+                            Func::cust(DateTrunc)
+                                .arg(Expr::val(grouped_by.to_string()))
+                                .arg(Func::coalesce([
+                                    Expr::col(daily_user_activity::Column::Date).into(),
+                                    Func::cast_as(default_date, date_type.clone()).into(),
+                                ])),
+                        ),
+                    },
                     date_type,
                 )),
                 "day",
diff --git a/crates/services/supporting/src/lib.rs b/crates/services/supporting/src/lib.rs
index 889564f0c6..2ac2263985 100644
--- a/crates/services/supporting/src/lib.rs
+++ b/crates/services/supporting/src/lib.rs
@@ -2,10 +2,7 @@ use std::sync::Arc;
 
 use apalis::prelude::{MemoryStorage, MessageQueue};
 use async_graphql::Result;
-use background_models::{
-    ApplicationJob, HpApplicationJob, LpApplicationJob,
-    MpApplicationJob,
-};
+use background_models::{ApplicationJob, HpApplicationJob, LpApplicationJob, MpApplicationJob};
 use cache_service::CacheService;
 use chrono::{NaiveDate, TimeZone, Utc};
 use common_models::{ApplicationCacheKey, BackendError};
@@ -222,7 +219,7 @@ impl SupportingService {
         };
         cc.set_key(
             ApplicationCacheKey::CoreDetails,
-            ApplicationCacheValue::CoreDetails(core_details.clone()),
+            ApplicationCacheValue::CoreDetails(Box::new(core_details.clone())),
         )
         .await?;
         Ok(core_details)
diff --git a/crates/services/user/src/lib.rs b/crates/services/user/src/lib.rs
index b37d722cea..df2de4e238 100644
--- a/crates/services/user/src/lib.rs
+++ b/crates/services/user/src/lib.rs
@@ -589,13 +589,17 @@ impl UserService {
                                 },
                             },
                             "logging" => match right {
+                                "mute_sounds" => {
+                                    preferences.fitness.logging.mute_sounds = value_bool.unwrap();
+                                }
+                                "prompt_for_rest_timer" => {
+                                    preferences.fitness.logging.prompt_for_rest_timer =
+                                        value_bool.unwrap();
+                                }
                                 "show_details_while_editing" => {
                                     preferences.fitness.logging.show_details_while_editing =
                                         value_bool.unwrap();
                                 }
-                                "mute_sounds" => {
-                                    preferences.fitness.logging.mute_sounds = value_bool.unwrap();
-                                }
                                 _ => return Err(err()),
                             },
                             _ => return Err(err()),
diff --git a/crates/utils/database/src/lib.rs b/crates/utils/database/src/lib.rs
index e16ba14695..5fd4115c4d 100644
--- a/crates/utils/database/src/lib.rs
+++ b/crates/utils/database/src/lib.rs
@@ -668,14 +668,14 @@ pub async fn calculate_user_activities_and_summary(
     }
 
     for (_, activity) in activities.iter_mut() {
-        let deleted = DailyUserActivity::delete_many()
-            .filter(daily_user_activity::Column::Date.eq(activity.date))
+        DailyUserActivity::delete_many()
             .filter(daily_user_activity::Column::UserId.eq(user_id))
+            .filter(match activity.date {
+                None => daily_user_activity::Column::Date.is_null(),
+                Some(date) => daily_user_activity::Column::Date.eq(date),
+            })
             .exec(db)
             .await?;
-        if deleted.rows_affected > 0 {
-            ryot_log!(debug, "Deleted existing activity = {:?}", activity.date);
-        }
         ryot_log!(debug, "Inserting activity = {:?}", activity.date);
         let total_review_count = activity.metadata_review_count
             + activity.collection_review_count
diff --git a/crates/utils/dependent/src/lib.rs b/crates/utils/dependent/src/lib.rs
index bda8ee2073..0fd23524f2 100644
--- a/crates/utils/dependent/src/lib.rs
+++ b/crates/utils/dependent/src/lib.rs
@@ -39,7 +39,7 @@ use file_storage_service::FileStorageService;
 use fitness_models::{
     ExerciseBestSetRecord, ProcessedExercise, UserExerciseInput,
     UserToExerciseBestSetExtraInformation, UserToExerciseExtraInformation,
-    UserToExerciseHistoryExtraInformation, UserWorkoutInput, UserWorkoutSetRecord,
+    UserToExerciseHistoryExtraInformation, UserWorkoutInput, UserWorkoutSetRecord, WorkoutDuration,
     WorkoutEquipmentFocusedSummary, WorkoutFocusedSummary, WorkoutForceFocusedSummary,
     WorkoutInformation, WorkoutLevelFocusedSummary, WorkoutLotFocusedSummary,
     WorkoutMuscleFocusedSummary, WorkoutOrExerciseTotals, WorkoutSetRecord, WorkoutSetStatistic,
@@ -823,7 +823,7 @@ pub async fn deploy_background_job(
     match job_name {
         BackgroundJob::UpdateAllMetadata
         | BackgroundJob::UpdateAllExercises
-        | BackgroundJob::RecalculateCalendarEvents
+        | BackgroundJob::DeleteAllApplicationCache
         | BackgroundJob::PerformBackgroundTasks => {
             admin_account_guard(user_id, ss).await?;
         }
@@ -843,16 +843,16 @@ pub async fn deploy_background_job(
                 deploy_update_metadata_job(&metadata_id, ss).await?;
             }
         }
+        BackgroundJob::DeleteAllApplicationCache => {
+            ss.perform_application_job(ApplicationJob::Lp(
+                LpApplicationJob::DeleteAllApplicationCache,
+            ))
+            .await?;
+        }
         BackgroundJob::UpdateAllExercises => {
             ss.perform_application_job(ApplicationJob::Mp(MpApplicationJob::UpdateExerciseLibrary))
                 .await?;
         }
-        BackgroundJob::RecalculateCalendarEvents => {
-            ss.perform_application_job(ApplicationJob::Mp(
-                MpApplicationJob::RecalculateCalendarEvents,
-            ))
-            .await?;
-        }
         BackgroundJob::PerformBackgroundTasks => {
             ss.perform_application_job(ApplicationJob::Mp(
                 MpApplicationJob::PerformBackgroundTasks,
@@ -1110,7 +1110,7 @@ pub async fn deploy_after_handle_media_seen_tasks(
     ss: &Arc<SupportingService>,
 ) -> Result<()> {
     ss.perform_application_job(ApplicationJob::Lp(
-        LpApplicationJob::HandleAfterMediaSeenTasks(seen),
+        LpApplicationJob::HandleAfterMediaSeenTasks(Box::new(seen)),
     ))
     .await
 }
@@ -1692,6 +1692,36 @@ pub async fn create_or_update_user_workout(
     ss: &Arc<SupportingService>,
 ) -> Result<String> {
     let end_time = input.end_time;
+    let (duration, durations) = match input.durations.clone() {
+        Some(durations) => {
+            if durations.is_empty() {
+                return Err(Error::new("Durations cannot be empty"));
+            }
+            if durations.last().and_then(|d| d.to).is_some() {
+                return Err(Error::new(
+                    "The workout was never resumed after being paused",
+                ));
+            }
+            let mut total = 0;
+            for duration in durations.iter() {
+                total += duration
+                    .to
+                    .unwrap_or(end_time)
+                    .signed_duration_since(duration.from)
+                    .num_seconds();
+            }
+            (total, durations)
+        }
+        None => (
+            end_time
+                .signed_duration_since(input.start_time)
+                .num_seconds(),
+            vec![WorkoutDuration {
+                from: input.start_time,
+                ..Default::default()
+            }],
+        ),
+    };
     let mut input = input;
     let (new_workout_id, to_update_workout) = match &input.update_workout_id {
         Some(id) => (
@@ -1741,10 +1771,10 @@ pub async fn create_or_update_user_workout(
             .ok()
             .flatten();
         let history_item = UserToExerciseHistoryExtraInformation {
-            best_set: None,
             idx: exercise_idx,
             workout_end_on: end_time,
             workout_id: new_workout_id.clone(),
+            ..Default::default()
         };
         let asc = match association {
             Some(e) => e,
@@ -1808,6 +1838,7 @@ pub async fn create_or_update_user_workout(
                 personal_bests: Some(vec![]),
                 confirmed_at: set.confirmed_at,
                 statistic: set.statistic.clone(),
+                rest_timer_started_at: set.rest_timer_started_at,
             };
             value.statistic.one_rm = calculate_one_rm(&value);
             value.statistic.pace = calculate_pace(&value);
@@ -1859,9 +1890,9 @@ pub async fn create_or_update_user_workout(
             if let Some(set_personal_bests) = &set.personal_bests {
                 for best in set_personal_bests.iter() {
                     let to_insert_record = ExerciseBestSetRecord {
-                        workout_id: new_workout_id.clone(),
-                        exercise_idx,
                         set_idx,
+                        exercise_idx,
+                        workout_id: new_workout_id.clone(),
                     };
                     if let Some(record) = personal_bests.iter_mut().find(|pb| pb.lot == *best) {
                         let mut data = record.sets.clone();
@@ -1893,8 +1924,8 @@ pub async fn create_or_update_user_workout(
             db_ex.lot,
             ProcessedExercise {
                 sets,
-                lot: db_ex.lot,
                 id: db_ex.id,
+                lot: db_ex.lot,
                 total: Some(totals),
                 notes: ex.notes.clone(),
                 assets: ex.assets.clone(),
@@ -1920,7 +1951,9 @@ pub async fn create_or_update_user_workout(
         user_id: user_id.clone(),
         id: new_workout_id.clone(),
         start_time: input.start_time,
+        template_id: input.template_id,
         repeated_from: input.repeated_from,
+        duration: duration.try_into().unwrap(),
         summary: WorkoutSummary {
             focused,
             total: Some(summary_total),
@@ -1939,13 +1972,11 @@ pub async fn create_or_update_user_workout(
             assets: input.assets,
             comment: input.comment,
             supersets: input.supersets,
+            durations: Some(durations),
             exercises: processed_exercises,
         },
-        template_id: input.template_id,
-        duration: 0,
     };
     let mut insert: workout::ActiveModel = model.into();
-    insert.duration = ActiveValue::NotSet;
     if let Some(old_workout) = to_update_workout.clone() {
         insert.end_time = ActiveValue::Set(old_workout.end_time);
         insert.start_time = ActiveValue::Set(old_workout.start_time);
@@ -2124,7 +2155,6 @@ where
                         respect_cache,
                         ProgressUpdateInput {
                             progress,
-                            change_state: None,
                             date: seen.ended_on,
                             start_date: seen.started_on,
                             metadata_id: db_metadata.id.clone(),
@@ -2135,6 +2165,7 @@ where
                             manga_chapter_number: seen.manga_chapter_number,
                             podcast_episode_number: seen.podcast_episode_number,
                             provider_watched_on: seen.provider_watched_on.clone(),
+                            ..Default::default()
                         },
                         ss,
                     )
@@ -2251,10 +2282,10 @@ where
                     Ok(p) => p,
                     Err(e) => {
                         import.failed.push(ImportFailedItem {
-                            lot: None,
                             error: Some(e.message),
                             identifier: person.identifier.clone(),
                             step: ImportFailStep::MediaDetailsFromProvider,
+                            ..Default::default()
                         });
                         continue;
                     }
@@ -2269,10 +2300,10 @@ where
                     ) {
                         if let Err(e) = post_review(user_id, input, ss).await {
                             import.failed.push(ImportFailedItem {
-                                lot: None,
-                                step: ImportFailStep::ReviewConversion,
-                                identifier: person.name.to_owned(),
                                 error: Some(e.message),
+                                identifier: person.name.to_owned(),
+                                step: ImportFailStep::ReviewConversion,
+                                ..Default::default()
                             });
                         };
                     }
@@ -2292,10 +2323,10 @@ where
                 need_to_schedule_user_for_workout_revision = true;
                 if let Err(err) = create_or_update_user_workout(workout, user_id, ss).await {
                     import.failed.push(ImportFailedItem {
-                        lot: None,
                         error: Some(err.message),
                         identifier: "Exercise".to_string(),
                         step: ImportFailStep::InputTransformation,
+                        ..Default::default()
                     });
                 }
             }
@@ -2304,10 +2335,10 @@ where
                 match create_or_update_user_workout(workout_input, user_id, ss).await {
                     Err(err) => {
                         import.failed.push(ImportFailedItem {
-                            lot: None,
                             error: Some(err.message),
                             identifier: "Exercise".to_string(),
                             step: ImportFailStep::InputTransformation,
+                            ..Default::default()
                         });
                     }
                     Ok(workout_id) => {
@@ -2327,10 +2358,10 @@ where
             ImportCompletedItem::Measurement(measurement) => {
                 if let Err(err) = create_user_measurement(user_id, measurement, &ss.db).await {
                     import.failed.push(ImportFailedItem {
-                        lot: None,
                         error: Some(err.message),
                         identifier: "Measurement".to_string(),
                         step: ImportFailStep::InputTransformation,
+                        ..Default::default()
                     });
                 }
             }
@@ -2374,9 +2405,7 @@ where
 pub fn db_workout_to_workout_input(user_workout: workout::Model) -> UserWorkoutInput {
     UserWorkoutInput {
         name: user_workout.name,
-        update_workout_id: None,
         end_time: user_workout.end_time,
-        update_workout_template_id: None,
         start_time: user_workout.start_time,
         template_id: user_workout.template_id,
         assets: user_workout.information.assets,
@@ -2384,6 +2413,7 @@ pub fn db_workout_to_workout_input(user_workout: workout::Model) -> UserWorkoutI
         repeated_from: user_workout.repeated_from,
         comment: user_workout.information.comment,
         supersets: user_workout.information.supersets,
+        durations: user_workout.information.durations,
         exercises: user_workout
             .information
             .exercises
@@ -2400,12 +2430,14 @@ pub fn db_workout_to_workout_input(user_workout: workout::Model) -> UserWorkoutI
                         rest_time: s.rest_time,
                         statistic: s.statistic,
                         confirmed_at: s.confirmed_at,
+                        rest_timer_started_at: s.rest_timer_started_at,
                     })
                     .collect(),
                 notes: e.notes,
                 assets: e.assets,
             })
             .collect(),
+        ..Default::default()
     }
 }
 
diff --git a/crates/utils/env/src/lib.rs b/crates/utils/env/src/lib.rs
index 972f1a57e0..7ab09b2425 100644
--- a/crates/utils/env/src/lib.rs
+++ b/crates/utils/env/src/lib.rs
@@ -1,5 +1,6 @@
+pub const APP_COMMIT_SHA: &str = env!("APP_COMMIT_SHA");
 pub const APP_VERSION: &str = env!("APP_VERSION");
 pub const UNKEY_API_ID: &str = env!("UNKEY_API_ID");
-pub const DEFAULT_TMDB_ACCESS_TOKEN: &str = env!("DEFAULT_TMDB_ACCESS_TOKEN");
-pub const DEFAULT_MAL_CLIENT_ID: &str = env!("DEFAULT_MAL_CLIENT_ID");
 pub const TRAKT_CLIENT_ID: &str = env!("TRAKT_CLIENT_ID");
+pub const DEFAULT_MAL_CLIENT_ID: &str = env!("DEFAULT_MAL_CLIENT_ID");
+pub const DEFAULT_TMDB_ACCESS_TOKEN: &str = env!("DEFAULT_TMDB_ACCESS_TOKEN");
diff --git a/docs/content/configuration.md b/docs/content/configuration.md
index 782f682674..911a2389c2 100644
--- a/docs/content/configuration.md
+++ b/docs/content/configuration.md
@@ -17,6 +17,11 @@ Ryot serves the final configuration loaded at the `/backend/config` endpoint as
 | `VIDEO_GAMES_TWITCH_CLIENT_ID`     | The client ID issued by Twitch. **Required** to enable video games tracking. [More information](guides/video-games.md) |
 | `VIDEO_GAMES_TWITCH_CLIENT_SECRET` | The client secret issued by Twitch. **Required** to enable video games tracking.                                       |
 
+## Delete all cache
+
+Configuration is cached in database. If you need to clear it, login as the admin user and
+go to the "Miscellaneous" settings and deploy the job to "Delete all cache".
+
 ## Health endpoint
 
 The `/health` endpoint can be used for checking service healthiness. More information
diff --git a/docs/content/contributing.md b/docs/content/contributing.md
index 1b5718845c..d2576991e8 100644
--- a/docs/content/contributing.md
+++ b/docs/content/contributing.md
@@ -8,6 +8,7 @@
 
   ```bash title=".env"
   DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres
+  APP_COMMIT_SHA=45937eded96319941d381eef9074922702b05f73
   DEFAULT_TMDB_ACCESS_TOKEN=your-tmdb-access-token
   DEFAULT_MAL_CLIENT_ID=your-mal-client-id
   TRAKT_CLIENT_ID=your-trakt-client-id
diff --git a/docs/content/guides/authentication.md b/docs/content/guides/authentication.md
index 5e5e39a74e..8a93f1ef0a 100644
--- a/docs/content/guides/authentication.md
+++ b/docs/content/guides/authentication.md
@@ -32,3 +32,23 @@ username set to their email address. This can be changed later in the profile se
 
 You can set `USERS_DISABLE_LOCAL_AUTH=true` to disable local authentication and only allow
 users to authenticate using OIDC.
+
+### Converting a local user to an OIDC user
+
+- Setup OpenID on your instance using the the above guide.
+- Make a backup of your database using this
+  [guide](./exporting.md#exporting-the-entire-database).
+- Delete all cache using this [guide](../configuration.md#delete-all-cache).
+- Logout of your original account and then click on "Continue with OpenID Connect".
+  Continue with user you want to select, after which a new account will be created.
+- Let's say that I want `IgnisDa` below to be able to login using OIDC (of
+  `ignisda2001@gmail.com`): ![image](../images/authentication_original-state.png)
+- Drop into your database (`docker exec -u postgres -it ryot-db psql`) and copy the
+  `oidc_issuer_id` (`104798859970005336426` here) of the new user and then delete it using
+  `DELETE FROM "user" WHERE id = 'usr_v5aGOC9UzrId';`
+- Update details of the old user using
+  `UPDATE "user" SET oidc_issuer_id = '104798859970005336426', password = NULL WHERE id = 'usr_ujrD0pCeKc1Y';`.
+  After this, it should look like this:
+  ![image](../images/authentication_new-state.png)
+
+You should now be able login using OIDC. The same procedure needs to be followed for all users that want their provider changed to OIDC.
diff --git a/docs/content/guides/exporting.md b/docs/content/guides/exporting.md
index 5c6be1005d..6678b9acaf 100644
--- a/docs/content/guides/exporting.md
+++ b/docs/content/guides/exporting.md
@@ -20,8 +20,9 @@ S3 instance offered by [Minio](https://play.min.io).
     The Minio team resets this instance every 24 hours, hence this method is not suitable
     if you want to store the data for a long time.
 
-- Go to the [Minio playground](https://play.min.io). The username is `minioadmin` and
-  password is `minioadmin`.
+- Go to the [Minio playground](https://play.min.io). The login credentials are changed
+  everyday and you can find them
+  [here](https://min.io/docs/minio/linux/administration/minio-console.html#logging-in).
 - Click on "Buckets" under the "Administrator" section and then on "Create Bucket".
 - Set a name and click on "Create Bucket".
 - Click on "Access Keys" under the "User" section and then on "Create access key".
diff --git a/docs/content/images/authentication_new-state.png b/docs/content/images/authentication_new-state.png
new file mode 100644
index 0000000000..d3dce852bd
Binary files /dev/null and b/docs/content/images/authentication_new-state.png differ
diff --git a/docs/content/images/authentication_original-state.png b/docs/content/images/authentication_original-state.png
new file mode 100644
index 0000000000..9afa71aba2
Binary files /dev/null and b/docs/content/images/authentication_original-state.png differ
diff --git a/docs/content/index.md b/docs/content/index.md
index 57db10f7e7..a0eb5f58cd 100644
--- a/docs/content/index.md
+++ b/docs/content/index.md
@@ -39,13 +39,13 @@ the features of the pro version, check the [features page]({{extra.main_website_
 To upgrade to the pro version, you need to provide a `SERVER_PRO_KEY` environment variable.
 You can get a key by purchasing it from the [website]({{extra.main_website_url}}).
 
-Once you have the key, you can set it in the `docker-compose.yml` file:
-
-```diff
-  ryot:
-    environment:
-+      - SERVER_PRO_KEY=<pro_key_issued_to_you>
-```
+- Once you have the key, you can set it in the `docker-compose.yml` file:
+  ```diff
+    ryot:
+      environment:
+  +      - SERVER_PRO_KEY=<pro_key_issued_to_you>
+  ```
+- Remove cached configuration using this [guide](./configuration.md#delete-all-cache).
 
 If the key is invalid or your subscription has expired, the server will automatically start
 with the community version. Since the two versions are compatible, you can switch between
diff --git a/docs/includes/export-schema.ts b/docs/includes/export-schema.ts
index 5b3ecda542..900a918ec9 100644
--- a/docs/includes/export-schema.ts
+++ b/docs/includes/export-schema.ts
@@ -225,6 +225,12 @@ export interface EntityAssets {
 	videos: string[];
 }
 
+/** Information about a workout done. */
+export interface WorkoutDuration {
+	from: string;
+	to: string | null;
+}
+
 /** The different types of exercises that can be done. */
 export type ExerciseLot = 'duration' | 'distance_and_duration' | 'reps' | 'reps_and_weight';
 
@@ -252,11 +258,15 @@ export interface WorkoutSetTotals {
 /** Details about the set performed. */
 export interface WorkoutSetRecord {
 	confirmed_at: string | null;
-	/** @type {'normal' | 'warm_up' | 'drop' | 'failure'} */
+	/**
+	 * @default 'normal'
+	 * @type {'normal' | 'warm_up' | 'drop' | 'failure'}
+	 */
 	lot: SetLot;
 	note: string | null;
 	personal_bests: WorkoutSetPersonalBest[] | null;
 	rest_time: number | null;
+	rest_timer_started_at: string | null;
 	rpe: number | null;
 	statistic: WorkoutSetStatistic;
 	totals: WorkoutSetTotals | null;
@@ -299,6 +309,7 @@ export interface WorkoutSupersetsInformation {
 export interface WorkoutInformation {
 	assets: EntityAssets | null;
 	comment: string | null;
+	durations: WorkoutDuration[] | null;
 	exercises: ProcessedExercise[];
 	supersets: WorkoutSupersetsInformation[];
 }
diff --git a/libs/generated/src/graphql/backend/gql.ts b/libs/generated/src/graphql/backend/gql.ts
index 4381a12bdf..93ce94a14d 100644
--- a/libs/generated/src/graphql/backend/gql.ts
+++ b/libs/generated/src/graphql/backend/gql.ts
@@ -18,11 +18,11 @@ const documents = {
     "query MetadataDetails($metadataId: String!) {\n  metadataDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    source\n    isNsfw\n    isPartial\n    sourceUrl\n    identifier\n    description\n    suggestions\n    publishYear\n    publishDate\n    providerRating\n    productionStatus\n    originalLanguage\n    genres {\n      id\n      name\n    }\n    group {\n      id\n      name\n      part\n    }\n    assets {\n      images\n      videos {\n        videoId\n        source\n      }\n    }\n    creators {\n      name\n      items {\n        id\n        name\n        image\n        character\n      }\n    }\n    watchProviders {\n      name\n      image\n      languages\n    }\n    animeSpecifics {\n      episodes\n    }\n    audioBookSpecifics {\n      runtime\n    }\n    bookSpecifics {\n      pages\n    }\n    movieSpecifics {\n      runtime\n    }\n    mangaSpecifics {\n      volumes\n      chapters\n    }\n    podcastSpecifics {\n      episodes {\n        id\n        title\n        overview\n        thumbnail\n        number\n        runtime\n        publishDate\n      }\n      totalEpisodes\n    }\n    showSpecifics {\n      totalSeasons\n      totalEpisodes\n      runtime\n      seasons {\n        id\n        seasonNumber\n        name\n        overview\n        backdropImages\n        posterImages\n        episodes {\n          id\n          name\n          posterImages\n          episodeNumber\n          publishDate\n          name\n          overview\n          runtime\n        }\n      }\n    }\n    visualNovelSpecifics {\n      length\n    }\n    videoGameSpecifics {\n      platforms\n    }\n    musicSpecifics {\n      duration\n      viewCount\n      byVariousArtists\n    }\n  }\n}": types.MetadataDetailsDocument,
     "query PersonDetails($personId: String!) {\n  personDetails(personId: $personId) {\n    details {\n      id\n      name\n      source\n      identifier\n      isPartial\n      description\n      birthDate\n      deathDate\n      place\n      website\n      gender\n      displayImages\n      sourceUrl\n    }\n    contents {\n      name\n      count\n      items {\n        character\n        metadataId\n      }\n    }\n  }\n}": types.PersonDetailsDocument,
     "query UserAnalytics($input: UserAnalyticsInput!) {\n  userAnalytics(input: $input) {\n    hours {\n      hour\n      entities {\n        entityId\n        entityLot\n        metadataLot\n      }\n    }\n    activities {\n      groupedBy\n      totalCount\n      totalDuration\n      items {\n        day\n        bookCount\n        showCount\n        totalCount\n        movieCount\n        animeCount\n        mangaCount\n        workoutCount\n        podcastCount\n        totalDuration\n        totalBookPages\n        audioBookCount\n        videoGameCount\n        totalReviewCount\n        visualNovelCount\n        totalWorkoutReps\n        totalShowDuration\n        totalMovieDuration\n        totalMetadataCount\n        totalWorkoutWeight\n        totalPodcastDuration\n        totalWorkoutDistance\n        totalWorkoutRestTime\n        totalWorkoutDuration\n        userMeasurementCount\n        totalVideoGameDuration\n        totalAudioBookDuration\n        totalPersonReviewCount\n        totalVisualNovelDuration\n        totalMetadataReviewCount\n        totalWorkoutPersonalBests\n        totalCollectionReviewCount\n        totalMetadataGroupReviewCount\n      }\n    }\n    fitness {\n      workoutReps\n      workoutCount\n      workoutWeight\n      workoutDistance\n      workoutDuration\n      workoutRestTime\n      measurementCount\n      workoutPersonalBests\n      workoutExercises {\n        count\n        exercise\n      }\n      workoutMuscles {\n        count\n        muscle\n      }\n      workoutEquipments {\n        count\n        equipment\n      }\n    }\n  }\n}": types.UserAnalyticsDocument,
-    "query UserDetails {\n  userDetails {\n    __typename\n    ... on User {\n      id\n      lot\n      name\n      isDisabled\n      oidcIssuerId\n      preferences {\n        general {\n          reviewScale\n          gridPacking\n          displayNsfw\n          disableVideos\n          persistQueries\n          disableReviews\n          disableIntegrations\n          disableWatchProviders\n          disableNavigationAnimation\n          dashboard {\n            hidden\n            section\n            numElements\n            deduplicateMedia\n          }\n          watchProviders {\n            lot\n            values\n          }\n        }\n        fitness {\n          logging {\n            muteSounds\n            showDetailsWhileEditing\n          }\n          exercises {\n            unitSystem\n            setRestTimers {\n              ...SetRestTimersPart\n            }\n          }\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        }\n        notifications {\n          toSend\n          enabled\n        }\n        featuresEnabled {\n          analytics {\n            enabled\n          }\n          others {\n            calendar\n            collections\n          }\n          fitness {\n            enabled\n            workouts\n            templates\n            measurements\n          }\n          media {\n            enabled\n            show\n            book\n            anime\n            manga\n            music\n            movie\n            groups\n            people\n            genres\n            podcast\n            audioBook\n            videoGame\n            visualNovel\n          }\n        }\n      }\n    }\n  }\n}": types.UserDetailsDocument,
+    "query UserDetails {\n  userDetails {\n    __typename\n    ... on User {\n      id\n      lot\n      name\n      isDisabled\n      oidcIssuerId\n      preferences {\n        general {\n          reviewScale\n          gridPacking\n          displayNsfw\n          disableVideos\n          persistQueries\n          disableReviews\n          disableIntegrations\n          disableWatchProviders\n          disableNavigationAnimation\n          dashboard {\n            hidden\n            section\n            numElements\n            deduplicateMedia\n          }\n          watchProviders {\n            lot\n            values\n          }\n        }\n        fitness {\n          logging {\n            muteSounds\n            promptForRestTimer\n            showDetailsWhileEditing\n          }\n          exercises {\n            unitSystem\n            setRestTimers {\n              ...SetRestTimersPart\n            }\n          }\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        }\n        notifications {\n          toSend\n          enabled\n        }\n        featuresEnabled {\n          analytics {\n            enabled\n          }\n          others {\n            calendar\n            collections\n          }\n          fitness {\n            enabled\n            workouts\n            templates\n            measurements\n          }\n          media {\n            enabled\n            show\n            book\n            anime\n            manga\n            music\n            movie\n            groups\n            people\n            genres\n            podcast\n            audioBook\n            videoGame\n            visualNovel\n          }\n        }\n      }\n    }\n  }\n}": types.UserDetailsDocument,
     "query UserExerciseDetails($exerciseId: String!) {\n  userExerciseDetails(exerciseId: $exerciseId) {\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    history {\n      idx\n      workoutId\n      workoutEndOn\n      bestSet {\n        ...WorkoutSetRecordPart\n      }\n    }\n    details {\n      exerciseId\n      createdOn\n      lastUpdatedOn\n      exerciseNumTimesInteracted\n      exerciseExtraInformation {\n        settings {\n          excludeFromAnalytics\n          setRestTimers {\n            ...SetRestTimersPart\n          }\n        }\n        lifetimeStats {\n          weight\n          reps\n          distance\n          duration\n          personalBestsAchieved\n        }\n        personalBests {\n          lot\n          sets {\n            setIdx\n            workoutId\n            exerciseIdx\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    averageRating\n    seenByAllCount\n    seenByUserCount\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    history {\n      ...SeenPart\n    }\n    nextEntry {\n      season\n      volume\n      episode\n      chapter\n    }\n    inProgress {\n      ...SeenPart\n    }\n    collections {\n      ...CollectionPart\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 GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n  collectionContents(input: $input) {\n    user {\n      id\n      name\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    results {\n      details {\n        total\n        nextPage\n      }\n      items {\n        entityId\n        entityLot\n      }\n    }\n    details {\n      name\n      description\n      createdOn\n    }\n  }\n}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\n    exerciseParameters {\n      downloadRequired\n      filters {\n        type\n        level\n        force\n        mechanic\n        equipment\n        muscle\n      }\n      lotMapping {\n        lot\n        bests\n      }\n    }\n  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      databaseId\n      hasInteracted\n      item {\n        identifier\n        title\n        image\n        publishYear\n      }\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      visibility\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}": types.GetOidcRedirectUrlDocument,
+    "query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n  collectionContents(input: $input) {\n    user {\n      id\n      name\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    results {\n      details {\n        total\n        nextPage\n      }\n      items {\n        entityId\n        entityLot\n      }\n    }\n    details {\n      name\n      description\n      createdOn\n    }\n  }\n}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\n    exerciseParameters {\n      downloadRequired\n      filters {\n        type\n        level\n        force\n        mechanic\n        equipment\n        muscle\n      }\n      lotMapping {\n        lot\n        bests\n      }\n    }\n  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      title\n      image\n      publishYear\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      visibility\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}": types.GetOidcRedirectUrlDocument,
     "fragment SeenPodcastExtraInformationPart on SeenPodcastExtraInformation {\n  episode\n}\n\nfragment SeenShowExtraInformationPart on SeenShowExtraInformation {\n  episode\n  season\n}\n\nfragment SeenAnimeExtraInformationPart on SeenAnimeExtraInformation {\n  episode\n}\n\nfragment SeenMangaExtraInformationPart on SeenMangaExtraInformation {\n  volume\n  chapter\n}\n\nfragment CalendarEventPart on GraphqlCalendarEvent {\n  date\n  metadataId\n  metadataLot\n  episodeName\n  metadataTitle\n  metadataImage\n  calendarEventId\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n}\n\nfragment SeenPart on Seen {\n  id\n  state\n  progress\n  reviewId\n  startedOn\n  finishedOn\n  lastUpdatedOn\n  manualTimeSpent\n  numTimesUpdated\n  providerWatchedOn\n  showExtraInformation {\n    ...SeenShowExtraInformationPart\n  }\n  podcastExtraInformation {\n    ...SeenPodcastExtraInformationPart\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}\n\nfragment MetadataSearchItemPart on MetadataSearchItem {\n  title\n  image\n  identifier\n  publishYear\n}\n\nfragment WorkoutOrExerciseTotalsPart on WorkoutOrExerciseTotals {\n  reps\n  weight\n  distance\n  duration\n  restTime\n  personalBestsAchieved\n}\n\nfragment EntityAssetsPart on EntityAssets {\n  images\n  videos\n}\n\nfragment WorkoutSetStatisticPart on WorkoutSetStatistic {\n  reps\n  pace\n  oneRm\n  weight\n  volume\n  duration\n  distance\n}\n\nfragment WorkoutSetRecordPart on WorkoutSetRecord {\n  lot\n  personalBests\n  statistic {\n    ...WorkoutSetStatisticPart\n  }\n}\n\nfragment WorkoutSummaryPart on WorkoutSummary {\n  total {\n    ...WorkoutOrExerciseTotalsPart\n  }\n  exercises {\n    id\n    lot\n    numSets\n    bestSet {\n      ...WorkoutSetRecordPart\n    }\n  }\n  focused {\n    lots {\n      lot\n      exercises\n    }\n    levels {\n      level\n      exercises\n    }\n    forces {\n      force\n      exercises\n    }\n    muscles {\n      muscle\n      exercises\n    }\n    equipments {\n      equipment\n      exercises\n    }\n  }\n}\n\nfragment CollectionPart on Collection {\n  id\n  name\n  userId\n}\n\nfragment ReviewItemPart on ReviewItem {\n  id\n  rating\n  postedOn\n  isSpoiler\n  visibility\n  textOriginal\n  textRendered\n  seenItemsAssociatedWith\n  postedBy {\n    id\n    name\n  }\n  comments {\n    id\n    text\n    likedBy\n    createdOn\n    user {\n      id\n      name\n    }\n  }\n  showExtraInformation {\n    season\n    episode\n  }\n  podcastExtraInformation {\n    episode\n  }\n  animeExtraInformation {\n    ...SeenAnimeExtraInformationPart\n  }\n  mangaExtraInformation {\n    ...SeenMangaExtraInformationPart\n  }\n}\n\nfragment WorkoutInformationPart on WorkoutInformation {\n  comment\n  assets {\n    ...EntityAssetsPart\n  }\n  supersets {\n    color\n    exercises\n  }\n  exercises {\n    id\n    lot\n    notes\n    total {\n      ...WorkoutOrExerciseTotalsPart\n    }\n    assets {\n      ...EntityAssetsPart\n    }\n    sets {\n      lot\n      rpe\n      note\n      restTime\n      confirmedAt\n      personalBests\n      statistic {\n        ...WorkoutSetStatisticPart\n      }\n    }\n  }\n}\n\nfragment SetRestTimersPart on SetRestTimersSettings {\n  drop\n  warmup\n  normal\n  failure\n}": types.SeenPodcastExtraInformationPartFragmentDoc,
 };
 
@@ -59,7 +59,7 @@ export function graphql(source: "query UserAnalytics($input: UserAnalyticsInput!
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
-export function graphql(source: "query UserDetails {\n  userDetails {\n    __typename\n    ... on User {\n      id\n      lot\n      name\n      isDisabled\n      oidcIssuerId\n      preferences {\n        general {\n          reviewScale\n          gridPacking\n          displayNsfw\n          disableVideos\n          persistQueries\n          disableReviews\n          disableIntegrations\n          disableWatchProviders\n          disableNavigationAnimation\n          dashboard {\n            hidden\n            section\n            numElements\n            deduplicateMedia\n          }\n          watchProviders {\n            lot\n            values\n          }\n        }\n        fitness {\n          logging {\n            muteSounds\n            showDetailsWhileEditing\n          }\n          exercises {\n            unitSystem\n            setRestTimers {\n              ...SetRestTimersPart\n            }\n          }\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        }\n        notifications {\n          toSend\n          enabled\n        }\n        featuresEnabled {\n          analytics {\n            enabled\n          }\n          others {\n            calendar\n            collections\n          }\n          fitness {\n            enabled\n            workouts\n            templates\n            measurements\n          }\n          media {\n            enabled\n            show\n            book\n            anime\n            manga\n            music\n            movie\n            groups\n            people\n            genres\n            podcast\n            audioBook\n            videoGame\n            visualNovel\n          }\n        }\n      }\n    }\n  }\n}"): (typeof documents)["query UserDetails {\n  userDetails {\n    __typename\n    ... on User {\n      id\n      lot\n      name\n      isDisabled\n      oidcIssuerId\n      preferences {\n        general {\n          reviewScale\n          gridPacking\n          displayNsfw\n          disableVideos\n          persistQueries\n          disableReviews\n          disableIntegrations\n          disableWatchProviders\n          disableNavigationAnimation\n          dashboard {\n            hidden\n            section\n            numElements\n            deduplicateMedia\n          }\n          watchProviders {\n            lot\n            values\n          }\n        }\n        fitness {\n          logging {\n            muteSounds\n            showDetailsWhileEditing\n          }\n          exercises {\n            unitSystem\n            setRestTimers {\n              ...SetRestTimersPart\n            }\n          }\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        }\n        notifications {\n          toSend\n          enabled\n        }\n        featuresEnabled {\n          analytics {\n            enabled\n          }\n          others {\n            calendar\n            collections\n          }\n          fitness {\n            enabled\n            workouts\n            templates\n            measurements\n          }\n          media {\n            enabled\n            show\n            book\n            anime\n            manga\n            music\n            movie\n            groups\n            people\n            genres\n            podcast\n            audioBook\n            videoGame\n            visualNovel\n          }\n        }\n      }\n    }\n  }\n}"];
+export function graphql(source: "query UserDetails {\n  userDetails {\n    __typename\n    ... on User {\n      id\n      lot\n      name\n      isDisabled\n      oidcIssuerId\n      preferences {\n        general {\n          reviewScale\n          gridPacking\n          displayNsfw\n          disableVideos\n          persistQueries\n          disableReviews\n          disableIntegrations\n          disableWatchProviders\n          disableNavigationAnimation\n          dashboard {\n            hidden\n            section\n            numElements\n            deduplicateMedia\n          }\n          watchProviders {\n            lot\n            values\n          }\n        }\n        fitness {\n          logging {\n            muteSounds\n            promptForRestTimer\n            showDetailsWhileEditing\n          }\n          exercises {\n            unitSystem\n            setRestTimers {\n              ...SetRestTimersPart\n            }\n          }\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        }\n        notifications {\n          toSend\n          enabled\n        }\n        featuresEnabled {\n          analytics {\n            enabled\n          }\n          others {\n            calendar\n            collections\n          }\n          fitness {\n            enabled\n            workouts\n            templates\n            measurements\n          }\n          media {\n            enabled\n            show\n            book\n            anime\n            manga\n            music\n            movie\n            groups\n            people\n            genres\n            podcast\n            audioBook\n            videoGame\n            visualNovel\n          }\n        }\n      }\n    }\n  }\n}"): (typeof documents)["query UserDetails {\n  userDetails {\n    __typename\n    ... on User {\n      id\n      lot\n      name\n      isDisabled\n      oidcIssuerId\n      preferences {\n        general {\n          reviewScale\n          gridPacking\n          displayNsfw\n          disableVideos\n          persistQueries\n          disableReviews\n          disableIntegrations\n          disableWatchProviders\n          disableNavigationAnimation\n          dashboard {\n            hidden\n            section\n            numElements\n            deduplicateMedia\n          }\n          watchProviders {\n            lot\n            values\n          }\n        }\n        fitness {\n          logging {\n            muteSounds\n            promptForRestTimer\n            showDetailsWhileEditing\n          }\n          exercises {\n            unitSystem\n            setRestTimers {\n              ...SetRestTimersPart\n            }\n          }\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        }\n        notifications {\n          toSend\n          enabled\n        }\n        featuresEnabled {\n          analytics {\n            enabled\n          }\n          others {\n            calendar\n            collections\n          }\n          fitness {\n            enabled\n            workouts\n            templates\n            measurements\n          }\n          media {\n            enabled\n            show\n            book\n            anime\n            manga\n            music\n            movie\n            groups\n            people\n            genres\n            podcast\n            audioBook\n            videoGame\n            visualNovel\n          }\n        }\n      }\n    }\n  }\n}"];
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
@@ -75,7 +75,7 @@ export function graphql(source: "query UserMetadataDetails($metadataId: String!)
 /**
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
  */
-export function graphql(source: "query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n  collectionContents(input: $input) {\n    user {\n      id\n      name\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    results {\n      details {\n        total\n        nextPage\n      }\n      items {\n        entityId\n        entityLot\n      }\n    }\n    details {\n      name\n      description\n      createdOn\n    }\n  }\n}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\n    exerciseParameters {\n      downloadRequired\n      filters {\n        type\n        level\n        force\n        mechanic\n        equipment\n        muscle\n      }\n      lotMapping {\n        lot\n        bests\n      }\n    }\n  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      databaseId\n      hasInteracted\n      item {\n        identifier\n        title\n        image\n        publishYear\n      }\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      visibility\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}"): (typeof documents)["query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n  collectionContents(input: $input) {\n    user {\n      id\n      name\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    results {\n      details {\n        total\n        nextPage\n      }\n      items {\n        entityId\n        entityLot\n      }\n    }\n    details {\n      name\n      description\n      createdOn\n    }\n  }\n}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\n    exerciseParameters {\n      downloadRequired\n      filters {\n        type\n        level\n        force\n        mechanic\n        equipment\n        muscle\n      }\n      lotMapping {\n        lot\n        bests\n      }\n    }\n  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      databaseId\n      hasInteracted\n      item {\n        identifier\n        title\n        image\n        publishYear\n      }\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      visibility\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}"];
+export function graphql(source: "query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n  collectionContents(input: $input) {\n    user {\n      id\n      name\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    results {\n      details {\n        total\n        nextPage\n      }\n      items {\n        entityId\n        entityLot\n      }\n    }\n    details {\n      name\n      description\n      createdOn\n    }\n  }\n}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\n    exerciseParameters {\n      downloadRequired\n      filters {\n        type\n        level\n        force\n        mechanic\n        equipment\n        muscle\n      }\n      lotMapping {\n        lot\n        bests\n      }\n    }\n  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      title\n      image\n      publishYear\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      visibility\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\n  }\n}"): (typeof documents)["query GetOidcRedirectUrl {\n  getOidcRedirectUrl\n}\n\nquery UserByOidcIssuerId($oidcIssuerId: String!) {\n  userByOidcIssuerId(oidcIssuerId: $oidcIssuerId)\n}\n\nquery GetOidcToken($code: String!) {\n  getOidcToken(code: $code) {\n    subject\n    email\n  }\n}\n\nquery GetPresignedS3Url($key: String!) {\n  getPresignedS3Url(key: $key)\n}\n\nquery UserExports {\n  userExports {\n    url\n    size\n    endedAt\n    startedAt\n  }\n}\n\nquery UserCollectionsList($name: String) {\n  userCollectionsList(name: $name) {\n    id\n    name\n    count\n    isDefault\n    description\n    creator {\n      id\n      name\n    }\n    collaborators {\n      id\n      name\n    }\n    informationTemplate {\n      lot\n      name\n      required\n      description\n      defaultValue\n    }\n  }\n}\n\nquery UserIntegrations {\n  userIntegrations {\n    id\n    lot\n    provider\n    createdOn\n    isDisabled\n    maximumProgress\n    minimumProgress\n    lastTriggeredOn\n    syncToOwnedCollection\n  }\n}\n\nquery UserNotificationPlatforms {\n  userNotificationPlatforms {\n    id\n    lot\n    createdOn\n    isDisabled\n    description\n  }\n}\n\nquery UsersList($query: String) {\n  usersList(query: $query) {\n    id\n    lot\n    name\n    isDisabled\n  }\n}\n\nquery UserRecommendations {\n  userRecommendations\n}\n\nquery UserUpcomingCalendarEvents($input: UserUpcomingCalendarEventInput!) {\n  userUpcomingCalendarEvents(input: $input) {\n    ...CalendarEventPart\n  }\n}\n\nquery UserCalendarEvents($input: UserCalendarEventInput!) {\n  userCalendarEvents(input: $input) {\n    date\n    events {\n      ...CalendarEventPart\n    }\n  }\n}\n\nquery MetadataPartialDetails($metadataId: String!) {\n  metadataPartialDetails(metadataId: $metadataId) {\n    id\n    lot\n    title\n    image\n    publishYear\n  }\n}\n\nquery MetadataGroupsList($input: MetadataGroupsListInput!) {\n  metadataGroupsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery PeopleList($input: PeopleListInput!) {\n  peopleList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAccessLinks {\n  userAccessLinks {\n    id\n    name\n    isDemo\n    createdOn\n    expiresOn\n    timesUsed\n    isRevoked\n    maximumUses\n    isAccountDefault\n    isMutationAllowed\n  }\n}\n\nquery ExerciseDetails($exerciseId: String!) {\n  exerciseDetails(exerciseId: $exerciseId) {\n    id\n    lot\n    name\n    level\n    force\n    source\n    muscles\n    mechanic\n    equipment\n    createdByUserId\n    attributes {\n      instructions\n      images\n    }\n  }\n}\n\nquery ExercisesList($input: ExercisesListInput!) {\n  exercisesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      lot\n      name\n      image\n      muscle\n      lastUpdatedOn\n      numTimesInteracted\n    }\n  }\n}\n\nquery ImportReports {\n  importReports {\n    id\n    source\n    progress\n    startedOn\n    finishedOn\n    wasSuccess\n    details {\n      import {\n        total\n      }\n      failedItems {\n        lot\n        step\n        error\n        identifier\n      }\n    }\n  }\n}\n\nquery GenresList($input: SearchInput!) {\n  genresList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      id\n      name\n      numItems\n    }\n  }\n}\n\nquery GenreDetails($input: GenreDetailsInput!) {\n  genreDetails(input: $input) {\n    details {\n      id\n      name\n      numItems\n    }\n    contents {\n      details {\n        total\n        nextPage\n      }\n      items\n    }\n  }\n}\n\nquery CollectionContents($input: CollectionContentsInput!) {\n  collectionContents(input: $input) {\n    user {\n      id\n      name\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n    results {\n      details {\n        total\n        nextPage\n      }\n      items {\n        entityId\n        entityLot\n      }\n    }\n    details {\n      name\n      description\n      createdOn\n    }\n  }\n}\n\nquery CoreDetails {\n  coreDetails {\n    version\n    docsLink\n    pageSize\n    websiteUrl\n    smtpEnabled\n    oidcEnabled\n    signupAllowed\n    repositoryLink\n    disableTelemetry\n    tokenValidForDays\n    localAuthDisabled\n    fileStorageEnabled\n    isServerKeyValidated\n    metadataLotSourceMappings {\n      lot\n      sources\n    }\n    metadataProviderLanguages {\n      source\n      default\n      supported\n    }\n    frontend {\n      url\n      oidcButtonLabel\n      dashboardMessage\n      umami {\n        domains\n        scriptUrl\n        websiteId\n      }\n    }\n    exerciseParameters {\n      downloadRequired\n      filters {\n        type\n        level\n        force\n        mechanic\n        equipment\n        muscle\n      }\n      lotMapping {\n        lot\n        bests\n      }\n    }\n  }\n}\n\nquery MetadataGroupDetails($metadataGroupId: String!) {\n  metadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    contents\n    details {\n      id\n      lot\n      title\n      parts\n      source\n      isPartial\n      sourceUrl\n      identifier\n      description\n      displayImages\n    }\n  }\n}\n\nquery MetadataGroupSearch($input: MetadataGroupSearchInput!) {\n  metadataGroupSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      parts\n    }\n  }\n}\n\nquery MetadataList($input: MetadataListInput!) {\n  metadataList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery MetadataSearch($input: MetadataSearchInput!) {\n  metadataSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      title\n      image\n      publishYear\n    }\n  }\n}\n\nquery PeopleSearch($input: PeopleSearchInput!) {\n  peopleSearch(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items {\n      identifier\n      name\n      image\n      birthYear\n    }\n  }\n}\n\nquery UserMetadataGroupDetails($metadataGroupId: String!) {\n  userMetadataGroupDetails(metadataGroupId: $metadataGroupId) {\n    recentlyConsumed\n    reviews {\n      ...ReviewItemPart\n    }\n    collections {\n      ...CollectionPart\n    }\n  }\n}\n\nquery UserPersonDetails($personId: String!) {\n  userPersonDetails(personId: $personId) {\n    recentlyConsumed\n    collections {\n      ...CollectionPart\n    }\n    reviews {\n      ...ReviewItemPart\n    }\n  }\n}\n\nquery UserWorkoutDetails($workoutId: String!) {\n  userWorkoutDetails(workoutId: $workoutId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      endTime\n      duration\n      startTime\n      templateId\n      repeatedFrom\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutsList($input: SearchInput!) {\n  userWorkoutsList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserWorkoutTemplateDetails($workoutTemplateId: String!) {\n  userWorkoutTemplateDetails(workoutTemplateId: $workoutTemplateId) {\n    collections {\n      ...CollectionPart\n    }\n    details {\n      id\n      name\n      createdOn\n      visibility\n      summary {\n        ...WorkoutSummaryPart\n      }\n      information {\n        ...WorkoutInformationPart\n      }\n    }\n  }\n}\n\nquery UserWorkoutTemplatesList($input: SearchInput!) {\n  userWorkoutTemplatesList(input: $input) {\n    details {\n      total\n      nextPage\n    }\n    items\n  }\n}\n\nquery UserAnalyticsParameters {\n  userAnalyticsParameters {\n    endDate\n    startDate\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 573f5f9e07..f7c52392e3 100644
--- a/libs/generated/src/graphql/backend/graphql.ts
+++ b/libs/generated/src/graphql/backend/graphql.ts
@@ -114,8 +114,8 @@ export enum BackendError {
 
 export enum BackgroundJob {
   CalculateUserActivitiesAndSummary = 'CALCULATE_USER_ACTIVITIES_AND_SUMMARY',
+  DeleteAllApplicationCache = 'DELETE_ALL_APPLICATION_CACHE',
   PerformBackgroundTasks = 'PERFORM_BACKGROUND_TASKS',
-  RecalculateCalendarEvents = 'RECALCULATE_CALENDAR_EVENTS',
   ReviseUserWorkouts = 'REVISE_USER_WORKOUTS',
   SyncIntegrationsData = 'SYNC_INTEGRATIONS_DATA',
   UpdateAllExercises = 'UPDATE_ALL_EXERCISES',
@@ -556,6 +556,7 @@ export type ExerciseInput = {
   attributes: ExerciseAttributesInput;
   equipment?: InputMaybe<ExerciseEquipment>;
   force?: InputMaybe<ExerciseForce>;
+  id: Scalars['String']['input'];
   level: ExerciseLevel;
   lot: ExerciseLot;
   mechanic?: InputMaybe<ExerciseMechanic>;
@@ -1133,16 +1134,9 @@ export type MetadataSearchItem = {
   title: Scalars['String']['output'];
 };
 
-export type MetadataSearchItemResponse = {
-  databaseId?: Maybe<Scalars['String']['output']>;
-  /** Whether the user has interacted with this media item. */
-  hasInteracted: Scalars['Boolean']['output'];
-  item: MetadataSearchItem;
-};
-
 export type MetadataSearchResults = {
   details: SearchDetails;
-  items: Array<MetadataSearchItemResponse>;
+  items: Array<MetadataSearchItem>;
 };
 
 export enum MetadataVideoSource {
@@ -2188,6 +2182,7 @@ export type UpdateCustomExerciseInput = {
   attributes: ExerciseAttributesInput;
   equipment?: InputMaybe<ExerciseEquipment>;
   force?: InputMaybe<ExerciseForce>;
+  id: Scalars['String']['input'];
   level: ExerciseLevel;
   lot: ExerciseLot;
   mechanic?: InputMaybe<ExerciseMechanic>;
@@ -2336,6 +2331,7 @@ export type UserFitnessFeaturesEnabledPreferences = {
 
 export type UserFitnessLoggingPreferences = {
   muteSounds: Scalars['Boolean']['output'];
+  promptForRestTimer: Scalars['Boolean']['output'];
   showDetailsWhileEditing: Scalars['Boolean']['output'];
 };
 
@@ -2659,6 +2655,7 @@ export type UserWorkoutDetails = {
 export type UserWorkoutInput = {
   assets?: InputMaybe<EntityAssetsInput>;
   comment?: InputMaybe<Scalars['String']['input']>;
+  durations?: InputMaybe<Array<WorkoutDurationInput>>;
   endTime: Scalars['DateTime']['input'];
   exercises: Array<UserExerciseInput>;
   name: Scalars['String']['input'];
@@ -2675,6 +2672,7 @@ export type UserWorkoutSetRecord = {
   lot: SetLot;
   note?: InputMaybe<Scalars['String']['input']>;
   restTime?: InputMaybe<Scalars['Int']['input']>;
+  restTimerStartedAt?: InputMaybe<Scalars['DateTime']['input']>;
   rpe?: InputMaybe<Scalars['Int']['input']>;
   statistic: SetStatisticInput;
 };
@@ -2724,6 +2722,18 @@ export type Workout = {
   templateId?: Maybe<Scalars['String']['output']>;
 };
 
+/** Information about a workout done. */
+export type WorkoutDuration = {
+  from: Scalars['DateTime']['output'];
+  to?: Maybe<Scalars['DateTime']['output']>;
+};
+
+/** Information about a workout done. */
+export type WorkoutDurationInput = {
+  from: Scalars['DateTime']['input'];
+  to?: InputMaybe<Scalars['DateTime']['input']>;
+};
+
 export type WorkoutEquipmentFocusedSummary = {
   equipment: ExerciseEquipment;
   exercises: Array<Scalars['Int']['output']>;
@@ -2746,6 +2756,7 @@ export type WorkoutForceFocusedSummary = {
 export type WorkoutInformation = {
   assets?: Maybe<EntityAssets>;
   comment?: Maybe<Scalars['String']['output']>;
+  durations?: Maybe<Array<WorkoutDuration>>;
   exercises: Array<ProcessedExercise>;
   supersets: Array<WorkoutSupersetsInformation>;
 };
@@ -2789,12 +2800,12 @@ export enum WorkoutSetPersonalBest {
 
 /** Details about the set performed. */
 export type WorkoutSetRecord = {
-  actualRestTime?: Maybe<Scalars['Int']['output']>;
   confirmedAt?: Maybe<Scalars['DateTime']['output']>;
   lot: SetLot;
   note?: Maybe<Scalars['String']['output']>;
   personalBests?: Maybe<Array<WorkoutSetPersonalBest>>;
   restTime?: Maybe<Scalars['Int']['output']>;
+  restTimerStartedAt?: Maybe<Scalars['DateTime']['output']>;
   rpe?: Maybe<Scalars['Int']['output']>;
   statistic: WorkoutSetStatistic;
   totals?: Maybe<WorkoutSetTotals>;
@@ -3229,7 +3240,7 @@ export type UserAnalyticsQuery = { userAnalytics: { hours: Array<{ hour: number,
 export type UserDetailsQueryVariables = Exact<{ [key: string]: never; }>;
 
 
-export type UserDetailsQuery = { userDetails: { __typename: 'User', id: string, lot: UserLot, name: string, isDisabled?: boolean | null, oidcIssuerId?: string | null, preferences: { general: { reviewScale: UserReviewScale, gridPacking: GridPacking, displayNsfw: boolean, disableVideos: boolean, persistQueries: boolean, disableReviews: boolean, disableIntegrations: boolean, disableWatchProviders: boolean, disableNavigationAnimation: boolean, dashboard: Array<{ hidden: boolean, section: DashboardElementLot, numElements?: number | null, deduplicateMedia?: boolean | null }>, watchProviders: Array<{ lot: MediaLot, values: Array<string> }> }, fitness: { logging: { muteSounds: boolean, showDetailsWhileEditing: boolean }, exercises: { unitSystem: UserUnitSystem, setRestTimers: { drop?: number | null, warmup?: number | null, normal?: number | null, failure?: number | null } }, measurements: { custom: Array<{ name: string, dataType: UserCustomMeasurementDataType }>, inbuilt: { weight: boolean, bodyMassIndex: boolean, totalBodyWater: boolean, muscle: boolean, leanBodyMass: boolean, bodyFat: boolean, boneMass: boolean, visceralFat: boolean, waistCircumference: boolean, waistToHeightRatio: boolean, hipCircumference: boolean, waistToHipRatio: boolean, chestCircumference: boolean, thighCircumference: boolean, bicepsCircumference: boolean, neckCircumference: boolean, bodyFatCaliper: boolean, chestSkinfold: boolean, abdominalSkinfold: boolean, thighSkinfold: boolean, basalMetabolicRate: boolean, totalDailyEnergyExpenditure: boolean, calories: boolean } } }, notifications: { toSend: Array<MediaStateChanged>, enabled: boolean }, featuresEnabled: { analytics: { enabled: boolean }, others: { calendar: boolean, collections: boolean }, fitness: { enabled: boolean, workouts: boolean, templates: boolean, measurements: boolean }, media: { enabled: boolean, show: boolean, book: boolean, anime: boolean, manga: boolean, music: boolean, movie: boolean, groups: boolean, people: boolean, genres: boolean, podcast: boolean, audioBook: boolean, videoGame: boolean, visualNovel: boolean } } } } | { __typename: 'UserDetailsError' } };
+export type UserDetailsQuery = { userDetails: { __typename: 'User', id: string, lot: UserLot, name: string, isDisabled?: boolean | null, oidcIssuerId?: string | null, preferences: { general: { reviewScale: UserReviewScale, gridPacking: GridPacking, displayNsfw: boolean, disableVideos: boolean, persistQueries: boolean, disableReviews: boolean, disableIntegrations: boolean, disableWatchProviders: boolean, disableNavigationAnimation: boolean, dashboard: Array<{ hidden: boolean, section: DashboardElementLot, numElements?: number | null, deduplicateMedia?: boolean | null }>, watchProviders: Array<{ lot: MediaLot, values: Array<string> }> }, fitness: { logging: { muteSounds: boolean, promptForRestTimer: boolean, showDetailsWhileEditing: boolean }, exercises: { unitSystem: UserUnitSystem, setRestTimers: { drop?: number | null, warmup?: number | null, normal?: number | null, failure?: number | null } }, measurements: { custom: Array<{ name: string, dataType: UserCustomMeasurementDataType }>, inbuilt: { weight: boolean, bodyMassIndex: boolean, totalBodyWater: boolean, muscle: boolean, leanBodyMass: boolean, bodyFat: boolean, boneMass: boolean, visceralFat: boolean, waistCircumference: boolean, waistToHeightRatio: boolean, hipCircumference: boolean, waistToHipRatio: boolean, chestCircumference: boolean, thighCircumference: boolean, bicepsCircumference: boolean, neckCircumference: boolean, bodyFatCaliper: boolean, chestSkinfold: boolean, abdominalSkinfold: boolean, thighSkinfold: boolean, basalMetabolicRate: boolean, totalDailyEnergyExpenditure: boolean, calories: boolean } } }, notifications: { toSend: Array<MediaStateChanged>, enabled: boolean }, featuresEnabled: { analytics: { enabled: boolean }, others: { calendar: boolean, collections: boolean }, fitness: { enabled: boolean, workouts: boolean, templates: boolean, measurements: boolean }, media: { enabled: boolean, show: boolean, book: boolean, anime: boolean, manga: boolean, music: boolean, movie: boolean, groups: boolean, people: boolean, genres: boolean, podcast: boolean, audioBook: boolean, videoGame: boolean, visualNovel: boolean } } } } | { __typename: 'UserDetailsError' } };
 
 export type UserExerciseDetailsQueryVariables = Exact<{
   exerciseId: Scalars['String']['input'];
@@ -3423,7 +3434,7 @@ export type MetadataSearchQueryVariables = Exact<{
 }>;
 
 
-export type MetadataSearchQuery = { metadataSearch: { details: { total: number, nextPage?: number | null }, items: Array<{ databaseId?: string | null, hasInteracted: boolean, item: { identifier: string, title: string, image?: string | null, publishYear?: number | null } }> } };
+export type MetadataSearchQuery = { metadataSearch: { details: { total: number, nextPage?: number | null }, items: Array<{ identifier: string, title: string, image?: string | null, publishYear?: number | null }> } };
 
 export type PeopleSearchQueryVariables = Exact<{
   input: PeopleSearchInput;
@@ -3581,7 +3592,7 @@ export const MergeExerciseDocument = {"kind":"Document","definitions":[{"kind":"
 export const MetadataDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataDetails"},"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":"metadataDetails"},"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":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"isNsfw"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"suggestions"}},{"kind":"Field","name":{"kind":"Name","value":"publishYear"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}},{"kind":"Field","name":{"kind":"Name","value":"providerRating"}},{"kind":"Field","name":{"kind":"Name","value":"productionStatus"}},{"kind":"Field","name":{"kind":"Name","value":"originalLanguage"}},{"kind":"Field","name":{"kind":"Name","value":"genres"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"part"}}]}},{"kind":"Field","name":{"kind":"Name","value":"assets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"images"}},{"kind":"Field","name":{"kind":"Name","value":"videos"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"videoId"}},{"kind":"Field","name":{"kind":"Name","value":"source"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"creators"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"character"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"languages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodes"}}]}},{"kind":"Field","name":{"kind":"Name","value":"audioBookSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}},{"kind":"Field","name":{"kind":"Name","value":"bookSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"pages"}}]}},{"kind":"Field","name":{"kind":"Name","value":"movieSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"volumes"}},{"kind":"Field","name":{"kind":"Name","value":"chapters"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"thumbnail"}},{"kind":"Field","name":{"kind":"Name","value":"number"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalEpisodes"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalSeasons"}},{"kind":"Field","name":{"kind":"Name","value":"totalEpisodes"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"seasons"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"seasonNumber"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"backdropImages"}},{"kind":"Field","name":{"kind":"Name","value":"posterImages"}},{"kind":"Field","name":{"kind":"Name","value":"episodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"posterImages"}},{"kind":"Field","name":{"kind":"Name","value":"episodeNumber"}},{"kind":"Field","name":{"kind":"Name","value":"publishDate"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"overview"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"visualNovelSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"length"}}]}},{"kind":"Field","name":{"kind":"Name","value":"videoGameSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"platforms"}}]}},{"kind":"Field","name":{"kind":"Name","value":"musicSpecifics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"viewCount"}},{"kind":"Field","name":{"kind":"Name","value":"byVariousArtists"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataDetailsQuery, MetadataDetailsQueryVariables>;
 export const PersonDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PersonDetails"},"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":"personDetails"},"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":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"birthDate"}},{"kind":"Field","name":{"kind":"Name","value":"deathDate"}},{"kind":"Field","name":{"kind":"Name","value":"place"}},{"kind":"Field","name":{"kind":"Name","value":"website"}},{"kind":"Field","name":{"kind":"Name","value":"gender"}},{"kind":"Field","name":{"kind":"Name","value":"displayImages"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"contents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"character"}},{"kind":"Field","name":{"kind":"Name","value":"metadataId"}}]}}]}}]}}]}}]} as unknown as DocumentNode<PersonDetailsQuery, PersonDetailsQueryVariables>;
 export const UserAnalyticsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserAnalytics"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UserAnalyticsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userAnalytics"},"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":"hours"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hour"}},{"kind":"Field","name":{"kind":"Name","value":"entities"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"entityId"}},{"kind":"Field","name":{"kind":"Name","value":"entityLot"}},{"kind":"Field","name":{"kind":"Name","value":"metadataLot"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"activities"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"groupedBy"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalDuration"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"day"}},{"kind":"Field","name":{"kind":"Name","value":"bookCount"}},{"kind":"Field","name":{"kind":"Name","value":"showCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"movieCount"}},{"kind":"Field","name":{"kind":"Name","value":"animeCount"}},{"kind":"Field","name":{"kind":"Name","value":"mangaCount"}},{"kind":"Field","name":{"kind":"Name","value":"workoutCount"}},{"kind":"Field","name":{"kind":"Name","value":"podcastCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalBookPages"}},{"kind":"Field","name":{"kind":"Name","value":"audioBookCount"}},{"kind":"Field","name":{"kind":"Name","value":"videoGameCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"visualNovelCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutReps"}},{"kind":"Field","name":{"kind":"Name","value":"totalShowDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalMovieDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalMetadataCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutWeight"}},{"kind":"Field","name":{"kind":"Name","value":"totalPodcastDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutDistance"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutRestTime"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutDuration"}},{"kind":"Field","name":{"kind":"Name","value":"userMeasurementCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalVideoGameDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalAudioBookDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalPersonReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalVisualNovelDuration"}},{"kind":"Field","name":{"kind":"Name","value":"totalMetadataReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalWorkoutPersonalBests"}},{"kind":"Field","name":{"kind":"Name","value":"totalCollectionReviewCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalMetadataGroupReviewCount"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workoutReps"}},{"kind":"Field","name":{"kind":"Name","value":"workoutCount"}},{"kind":"Field","name":{"kind":"Name","value":"workoutWeight"}},{"kind":"Field","name":{"kind":"Name","value":"workoutDistance"}},{"kind":"Field","name":{"kind":"Name","value":"workoutDuration"}},{"kind":"Field","name":{"kind":"Name","value":"workoutRestTime"}},{"kind":"Field","name":{"kind":"Name","value":"measurementCount"}},{"kind":"Field","name":{"kind":"Name","value":"workoutPersonalBests"}},{"kind":"Field","name":{"kind":"Name","value":"workoutExercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"exercise"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workoutMuscles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workoutEquipments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"equipment"}}]}}]}}]}}]}}]} as unknown as DocumentNode<UserAnalyticsQuery, UserAnalyticsQueryVariables>;
-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":"isDisabled"}},{"kind":"Field","name":{"kind":"Name","value":"oidcIssuerId"}},{"kind":"Field","name":{"kind":"Name","value":"preferences"},"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":"gridPacking"}},{"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":"disableReviews"}},{"kind":"Field","name":{"kind":"Name","value":"disableIntegrations"}},{"kind":"Field","name":{"kind":"Name","value":"disableWatchProviders"}},{"kind":"Field","name":{"kind":"Name","value":"disableNavigationAnimation"}},{"kind":"Field","name":{"kind":"Name","value":"dashboard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hidden"}},{"kind":"Field","name":{"kind":"Name","value":"section"}},{"kind":"Field","name":{"kind":"Name","value":"numElements"}},{"kind":"Field","name":{"kind":"Name","value":"deduplicateMedia"}}]}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"values"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"logging"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"muteSounds"}},{"kind":"Field","name":{"kind":"Name","value":"showDetailsWhileEditing"}}]}},{"kind":"Field","name":{"kind":"Name","value":"exercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unitSystem"}},{"kind":"Field","name":{"kind":"Name","value":"setRestTimers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SetRestTimersPart"}}]}}]}},{"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":"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":"analytics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}},{"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":"templates"}},{"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":"show"}},{"kind":"Field","name":{"kind":"Name","value":"book"}},{"kind":"Field","name":{"kind":"Name","value":"anime"}},{"kind":"Field","name":{"kind":"Name","value":"manga"}},{"kind":"Field","name":{"kind":"Name","value":"music"}},{"kind":"Field","name":{"kind":"Name","value":"movie"}},{"kind":"Field","name":{"kind":"Name","value":"groups"}},{"kind":"Field","name":{"kind":"Name","value":"people"}},{"kind":"Field","name":{"kind":"Name","value":"genres"}},{"kind":"Field","name":{"kind":"Name","value":"podcast"}},{"kind":"Field","name":{"kind":"Name","value":"audioBook"}},{"kind":"Field","name":{"kind":"Name","value":"videoGame"}},{"kind":"Field","name":{"kind":"Name","value":"visualNovel"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SetRestTimersPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SetRestTimersSettings"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"drop"}},{"kind":"Field","name":{"kind":"Name","value":"warmup"}},{"kind":"Field","name":{"kind":"Name","value":"normal"}},{"kind":"Field","name":{"kind":"Name","value":"failure"}}]}}]} as unknown as DocumentNode<UserDetailsQuery, UserDetailsQueryVariables>;
+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":"isDisabled"}},{"kind":"Field","name":{"kind":"Name","value":"oidcIssuerId"}},{"kind":"Field","name":{"kind":"Name","value":"preferences"},"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":"gridPacking"}},{"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":"disableReviews"}},{"kind":"Field","name":{"kind":"Name","value":"disableIntegrations"}},{"kind":"Field","name":{"kind":"Name","value":"disableWatchProviders"}},{"kind":"Field","name":{"kind":"Name","value":"disableNavigationAnimation"}},{"kind":"Field","name":{"kind":"Name","value":"dashboard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hidden"}},{"kind":"Field","name":{"kind":"Name","value":"section"}},{"kind":"Field","name":{"kind":"Name","value":"numElements"}},{"kind":"Field","name":{"kind":"Name","value":"deduplicateMedia"}}]}},{"kind":"Field","name":{"kind":"Name","value":"watchProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"values"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"fitness"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"logging"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"muteSounds"}},{"kind":"Field","name":{"kind":"Name","value":"promptForRestTimer"}},{"kind":"Field","name":{"kind":"Name","value":"showDetailsWhileEditing"}}]}},{"kind":"Field","name":{"kind":"Name","value":"exercises"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unitSystem"}},{"kind":"Field","name":{"kind":"Name","value":"setRestTimers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SetRestTimersPart"}}]}}]}},{"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":"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":"analytics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}},{"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":"templates"}},{"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":"show"}},{"kind":"Field","name":{"kind":"Name","value":"book"}},{"kind":"Field","name":{"kind":"Name","value":"anime"}},{"kind":"Field","name":{"kind":"Name","value":"manga"}},{"kind":"Field","name":{"kind":"Name","value":"music"}},{"kind":"Field","name":{"kind":"Name","value":"movie"}},{"kind":"Field","name":{"kind":"Name","value":"groups"}},{"kind":"Field","name":{"kind":"Name","value":"people"}},{"kind":"Field","name":{"kind":"Name","value":"genres"}},{"kind":"Field","name":{"kind":"Name","value":"podcast"}},{"kind":"Field","name":{"kind":"Name","value":"audioBook"}},{"kind":"Field","name":{"kind":"Name","value":"videoGame"}},{"kind":"Field","name":{"kind":"Name","value":"visualNovel"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SetRestTimersPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SetRestTimersSettings"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"drop"}},{"kind":"Field","name":{"kind":"Name","value":"warmup"}},{"kind":"Field","name":{"kind":"Name","value":"normal"}},{"kind":"Field","name":{"kind":"Name","value":"failure"}}]}}]} 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":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"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":"workoutEndOn"}},{"kind":"Field","name":{"kind":"Name","value":"bestSet"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutSetRecordPart"}}]}}]}},{"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":"settings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"excludeFromAnalytics"}},{"kind":"Field","name":{"kind":"Name","value":"setRestTimers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SetRestTimersPart"}}]}}]}},{"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":"setIdx"}},{"kind":"Field","name":{"kind":"Name","value":"workoutId"}},{"kind":"Field","name":{"kind":"Name","value":"exerciseIdx"}}]}}]}}]}}]}}]}}]}},{"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":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkoutSetStatisticPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutSetStatistic"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"reps"}},{"kind":"Field","name":{"kind":"Name","value":"pace"}},{"kind":"Field","name":{"kind":"Name","value":"oneRm"}},{"kind":"Field","name":{"kind":"Name","value":"weight"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"distance"}}]}},{"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":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"seenItemsAssociatedWith"}},{"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":"likedBy"}},{"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":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"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":"WorkoutSetRecordPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkoutSetRecord"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"personalBests"}},{"kind":"Field","name":{"kind":"Name","value":"statistic"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkoutSetStatisticPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SetRestTimersPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SetRestTimersSettings"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"drop"}},{"kind":"Field","name":{"kind":"Name","value":"warmup"}},{"kind":"Field","name":{"kind":"Name","value":"normal"}},{"kind":"Field","name":{"kind":"Name","value":"failure"}}]}}]} 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":"averageRating"}},{"kind":"Field","name":{"kind":"Name","value":"seenByAllCount"}},{"kind":"Field","name":{"kind":"Name","value":"seenByUserCount"}},{"kind":"Field","name":{"kind":"Name","value":"recentlyConsumed"}},{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"history"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPart"}}]}},{"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":"inProgress"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"collections"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CollectionPart"}}]}},{"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":"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":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"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":"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":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"seenItemsAssociatedWith"}},{"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":"likedBy"}},{"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":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"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":"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":"state"}},{"kind":"Field","name":{"kind":"Name","value":"progress"}},{"kind":"Field","name":{"kind":"Name","value":"reviewId"}},{"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":"manualTimeSpent"}},{"kind":"Field","name":{"kind":"Name","value":"numTimesUpdated"}},{"kind":"Field","name":{"kind":"Name","value":"providerWatchedOn"}},{"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<UserMetadataDetailsQuery, UserMetadataDetailsQueryVariables>;
@@ -3611,7 +3622,7 @@ export const CoreDetailsDocument = {"kind":"Document","definitions":[{"kind":"Op
 export const MetadataGroupDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataGroupDetails"},"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":"metadataGroupDetails"},"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":"contents"}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"parts"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"isPartial"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"displayImages"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataGroupDetailsQuery, MetadataGroupDetailsQueryVariables>;
 export const MetadataGroupSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataGroupSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MetadataGroupSearchInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataGroupSearch"},"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":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"parts"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataGroupSearchQuery, MetadataGroupSearchQueryVariables>;
 export const MetadataListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MetadataListInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataList"},"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":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"}}]}}]}}]} as unknown as DocumentNode<MetadataListQuery, MetadataListQueryVariables>;
-export const MetadataSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MetadataSearchInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataSearch"},"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":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"databaseId"}},{"kind":"Field","name":{"kind":"Name","value":"hasInteracted"}},{"kind":"Field","name":{"kind":"Name","value":"item"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"publishYear"}}]}}]}}]}}]}}]} as unknown as DocumentNode<MetadataSearchQuery, MetadataSearchQueryVariables>;
+export const MetadataSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MetadataSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MetadataSearchInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataSearch"},"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":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"publishYear"}}]}}]}}]}}]} as unknown as DocumentNode<MetadataSearchQuery, MetadataSearchQueryVariables>;
 export const PeopleSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PeopleSearch"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PeopleSearchInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"peopleSearch"},"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":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"birthYear"}}]}}]}}]}}]} as unknown as DocumentNode<PeopleSearchQuery, PeopleSearchQueryVariables>;
 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":"recentlyConsumed"}},{"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":"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":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"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":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"seenItemsAssociatedWith"}},{"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":"likedBy"}},{"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":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"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":"recentlyConsumed"}},{"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":"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":"volume"}},{"kind":"Field","name":{"kind":"Name","value":"chapter"}}]}},{"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":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"seenItemsAssociatedWith"}},{"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":"likedBy"}},{"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":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"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>;
diff --git a/libs/generated/src/graphql/backend/types.generated.ts b/libs/generated/src/graphql/backend/types.generated.ts
index 239d246a11..b6c8baceca 100644
--- a/libs/generated/src/graphql/backend/types.generated.ts
+++ b/libs/generated/src/graphql/backend/types.generated.ts
@@ -94,8 +94,8 @@ export enum BackendError {
 
 export enum BackgroundJob {
   CalculateUserActivitiesAndSummary = 'CALCULATE_USER_ACTIVITIES_AND_SUMMARY',
+  DeleteAllApplicationCache = 'DELETE_ALL_APPLICATION_CACHE',
   PerformBackgroundTasks = 'PERFORM_BACKGROUND_TASKS',
-  RecalculateCalendarEvents = 'RECALCULATE_CALENDAR_EVENTS',
   ReviseUserWorkouts = 'REVISE_USER_WORKOUTS',
   SyncIntegrationsData = 'SYNC_INTEGRATIONS_DATA',
   UpdateAllExercises = 'UPDATE_ALL_EXERCISES',
@@ -552,6 +552,7 @@ export type ExerciseInput = {
   attributes: ExerciseAttributesInput;
   equipment?: InputMaybe<ExerciseEquipment>;
   force?: InputMaybe<ExerciseForce>;
+  id: Scalars['String']['input'];
   level: ExerciseLevel;
   lot: ExerciseLot;
   mechanic?: InputMaybe<ExerciseMechanic>;
@@ -1169,18 +1170,10 @@ export type MetadataSearchItem = {
   title: Scalars['String']['output'];
 };
 
-export type MetadataSearchItemResponse = {
-  __typename?: 'MetadataSearchItemResponse';
-  databaseId?: Maybe<Scalars['String']['output']>;
-  /** Whether the user has interacted with this media item. */
-  hasInteracted: Scalars['Boolean']['output'];
-  item: MetadataSearchItem;
-};
-
 export type MetadataSearchResults = {
   __typename?: 'MetadataSearchResults';
   details: SearchDetails;
-  items: Array<MetadataSearchItemResponse>;
+  items: Array<MetadataSearchItem>;
 };
 
 export enum MetadataVideoSource {
@@ -2260,6 +2253,7 @@ export type UpdateCustomExerciseInput = {
   attributes: ExerciseAttributesInput;
   equipment?: InputMaybe<ExerciseEquipment>;
   force?: InputMaybe<ExerciseForce>;
+  id: Scalars['String']['input'];
   level: ExerciseLevel;
   lot: ExerciseLot;
   mechanic?: InputMaybe<ExerciseMechanic>;
@@ -2419,6 +2413,7 @@ export type UserFitnessFeaturesEnabledPreferences = {
 export type UserFitnessLoggingPreferences = {
   __typename?: 'UserFitnessLoggingPreferences';
   muteSounds: Scalars['Boolean']['output'];
+  promptForRestTimer: Scalars['Boolean']['output'];
   showDetailsWhileEditing: Scalars['Boolean']['output'];
 };
 
@@ -2766,6 +2761,7 @@ export type UserWorkoutDetails = {
 export type UserWorkoutInput = {
   assets?: InputMaybe<EntityAssetsInput>;
   comment?: InputMaybe<Scalars['String']['input']>;
+  durations?: InputMaybe<Array<WorkoutDurationInput>>;
   endTime: Scalars['DateTime']['input'];
   exercises: Array<UserExerciseInput>;
   name: Scalars['String']['input'];
@@ -2782,6 +2778,7 @@ export type UserWorkoutSetRecord = {
   lot: SetLot;
   note?: InputMaybe<Scalars['String']['input']>;
   restTime?: InputMaybe<Scalars['Int']['input']>;
+  restTimerStartedAt?: InputMaybe<Scalars['DateTime']['input']>;
   rpe?: InputMaybe<Scalars['Int']['input']>;
   statistic: SetStatisticInput;
 };
@@ -2836,6 +2833,19 @@ export type Workout = {
   templateId?: Maybe<Scalars['String']['output']>;
 };
 
+/** Information about a workout done. */
+export type WorkoutDuration = {
+  __typename?: 'WorkoutDuration';
+  from: Scalars['DateTime']['output'];
+  to?: Maybe<Scalars['DateTime']['output']>;
+};
+
+/** Information about a workout done. */
+export type WorkoutDurationInput = {
+  from: Scalars['DateTime']['input'];
+  to?: InputMaybe<Scalars['DateTime']['input']>;
+};
+
 export type WorkoutEquipmentFocusedSummary = {
   __typename?: 'WorkoutEquipmentFocusedSummary';
   equipment: ExerciseEquipment;
@@ -2862,6 +2872,7 @@ export type WorkoutInformation = {
   __typename?: 'WorkoutInformation';
   assets?: Maybe<EntityAssets>;
   comment?: Maybe<Scalars['String']['output']>;
+  durations?: Maybe<Array<WorkoutDuration>>;
   exercises: Array<ProcessedExercise>;
   supersets: Array<WorkoutSupersetsInformation>;
 };
@@ -2910,12 +2921,12 @@ export enum WorkoutSetPersonalBest {
 /** Details about the set performed. */
 export type WorkoutSetRecord = {
   __typename?: 'WorkoutSetRecord';
-  actualRestTime?: Maybe<Scalars['Int']['output']>;
   confirmedAt?: Maybe<Scalars['DateTime']['output']>;
   lot: SetLot;
   note?: Maybe<Scalars['String']['output']>;
   personalBests?: Maybe<Array<WorkoutSetPersonalBest>>;
   restTime?: Maybe<Scalars['Int']['output']>;
+  restTimerStartedAt?: Maybe<Scalars['DateTime']['output']>;
   rpe?: Maybe<Scalars['Int']['output']>;
   statistic: WorkoutSetStatistic;
   totals?: Maybe<WorkoutSetTotals>;
diff --git a/libs/graphql/src/backend/queries/UserAnalytics.gql b/libs/graphql/src/backend/queries/UserAnalytics.gql
index 2f998a7e54..dc1ffd2275 100644
--- a/libs/graphql/src/backend/queries/UserAnalytics.gql
+++ b/libs/graphql/src/backend/queries/UserAnalytics.gql
@@ -1,4 +1,3 @@
-
 query UserAnalytics($input: UserAnalyticsInput!) {
 	userAnalytics(input: $input) {
 		hours {
diff --git a/libs/graphql/src/backend/queries/UserDetails.gql b/libs/graphql/src/backend/queries/UserDetails.gql
index dcc283e628..7bd27b3b60 100644
--- a/libs/graphql/src/backend/queries/UserDetails.gql
+++ b/libs/graphql/src/backend/queries/UserDetails.gql
@@ -32,6 +32,7 @@ query UserDetails {
 				fitness {
 					logging {
 						muteSounds
+						promptForRestTimer
 						showDetailsWhileEditing
 					}
 					exercises {
diff --git a/libs/graphql/src/backend/queries/combined.gql b/libs/graphql/src/backend/queries/combined.gql
index 083eb64f55..707cec1b47 100644
--- a/libs/graphql/src/backend/queries/combined.gql
+++ b/libs/graphql/src/backend/queries/combined.gql
@@ -367,14 +367,10 @@ query MetadataSearch($input: MetadataSearchInput!) {
 			nextPage
 		}
 		items {
-			databaseId
-			hasInteracted
-			item {
-				identifier
-				title
-				image
-				publishYear
-			}
+			identifier
+			title
+			image
+			publishYear
 		}
 	}
 }
diff --git a/yarn.lock b/yarn.lock
index 873a5c948b..dc1b6fb921 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3788,6 +3788,21 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@mantine/code-highlight@npm:7.14.3":
+  version: 7.14.3
+  resolution: "@mantine/code-highlight@npm:7.14.3"
+  dependencies:
+    clsx: "npm:^2.1.1"
+    highlight.js: "npm:^11.10.0"
+  peerDependencies:
+    "@mantine/core": 7.14.3
+    "@mantine/hooks": 7.14.3
+    react: ^18.x || ^19.x
+    react-dom: ^18.x || ^19.x
+  checksum: 10/b8da438b6e026723d9b1f79409d91166d475c57707c62ccc4c35bac791eab917f42a794c45256095ea83fa8fb0f44f1f51957d1c6a5c8b8c64b4db4e36bf4e95
+  languageName: node
+  linkType: hard
+
 "@mantine/core@npm:7.14.3":
   version: 7.14.3
   resolution: "@mantine/core@npm:7.14.3"
@@ -5724,6 +5739,7 @@ __metadata:
     "@lukemorales/query-key-factory": "npm:1.3.4"
     "@mantine/carousel": "npm:7.14.3"
     "@mantine/charts": "npm:7.14.3"
+    "@mantine/code-highlight": "npm:7.14.3"
     "@mantine/core": "npm:7.14.3"
     "@mantine/dates": "npm:7.14.3"
     "@mantine/form": "npm:7.14.3"
@@ -5749,7 +5765,6 @@ __metadata:
     "@types/react": "npm:18.3.12"
     "@types/react-dom": "npm:18.3.1"
     "@types/uuid": "npm:10.0.0"
-    buffer: "npm:6.0.3"
     clsx: "npm:2.1.1"
     cookie: "npm:1.0.2"
     crypto-js: "npm:4.2.0"
@@ -5776,7 +5791,6 @@ __metadata:
     react-confirm: "npm:0.3.0-7"
     react-dom: "npm:18.3.1"
     react-virtuoso: "npm:4.12.3"
-    react-webcam: "npm:7.2.0"
     recharts: "npm:2.14.1"
     remix-development-tools: "npm:4.7.7"
     remix-routes: "npm:1.7.7"
@@ -7119,16 +7133,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"buffer@npm:6.0.3":
-  version: 6.0.3
-  resolution: "buffer@npm:6.0.3"
-  dependencies:
-    base64-js: "npm:^1.3.1"
-    ieee754: "npm:^1.2.1"
-  checksum: 10/b6bc68237ebf29bdacae48ce60e5e28fc53ae886301f2ad9496618efac49427ed79096750033e7eab1897a4f26ae374ace49106a5758f38fb70c78c9fda2c3b1
-  languageName: node
-  linkType: hard
-
 "buffer@npm:^5.5.0":
   version: 5.7.1
   resolution: "buffer@npm:5.7.1"
@@ -10435,6 +10439,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"highlight.js@npm:^11.10.0":
+  version: 11.11.0
+  resolution: "highlight.js@npm:11.11.0"
+  checksum: 10/6da3028d9649da2c011ef6ae42a1330173c1d1aeb1c6dbac4cedcd83baf49ac1ba0cbe076b4ea2970fd5d143e0b1bda516ab4e9d972ec17021e25620551c775c
+  languageName: node
+  linkType: hard
+
 "hosted-git-info@npm:^2.1.4":
   version: 2.8.9
   resolution: "hosted-git-info@npm:2.8.9"
@@ -10631,7 +10642,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1":
+"ieee754@npm:^1.1.13":
   version: 1.2.1
   resolution: "ieee754@npm:1.2.1"
   checksum: 10/d9f2557a59036f16c282aaeb107832dc957a93d73397d89bbad4eb1130560560eb695060145e8e6b3b498b15ab95510226649a0b8f52ae06583575419fe10fc4
@@ -14342,16 +14353,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"react-webcam@npm:7.2.0":
-  version: 7.2.0
-  resolution: "react-webcam@npm:7.2.0"
-  peerDependencies:
-    react: ">=16.2.0"
-    react-dom: ">=16.2.0"
-  checksum: 10/fe16756b9b71d4d2dcc40f337bd47fce977197a9ce37a453d9e691db870d0738fa4fe81db45fd8d3d81a06fd3b2f2fbafeacf9d5865c97568fa351969e4be70c
-  languageName: node
-  linkType: hard
-
 "react@npm:18.3.1":
   version: 18.3.1
   resolution: "react@npm:18.3.1"


Test patch: