From 228fdfad1ebeed95e423898aad2628d88967374f Mon Sep 17 00:00:00 2001
From: Fuwn <contact@fuwn.net>
Date: Thu, 21 May 2026 13:30:34 +0000
Subject: fix(lists): optimistic increment and snapshot to remove refresh flash

Click + on a Due item now bumps progress locally and drops the row
if the bump catches the user up, with no upfront refresh. The mutation
fires in the background and triggers a single revalidate on response.
Also snapshot the resolved media to previousAnimeList on every render,
so refreshes from any path (countdown, interval, manual, post-increment)
show the prior list during pending instead of a placeholder.
---
 src/lib/List/Anime/CleanAnimeList.svelte | 46 ++++++++++++++++----------------
 1 file changed, 23 insertions(+), 23 deletions(-)

(limited to 'src/lib/List/Anime/CleanAnimeList.svelte')

diff --git a/src/lib/List/Anime/CleanAnimeList.svelte b/src/lib/List/Anime/CleanAnimeList.svelte
index 23d87a97..3f397c25 100644
--- a/src/lib/List/Anime/CleanAnimeList.svelte
+++ b/src/lib/List/Anime/CleanAnimeList.svelte
@@ -9,6 +9,7 @@ import type { AniListAuthorisation } from "$lib/Data/AniList/identity";
 import ListTitle from "../ListTitle.svelte";
 import { onDestroy, onMount } from "svelte";
 import AiringTime from "$lib/Media/Anime/Airing/AiringTime.svelte";
+import { hasDueEpisodes } from "$lib/Media/Anime/Airing/classify";
 import { browser } from "$app/environment";
 import identity from "$stores/identity";
 import "../covers.css";
@@ -109,6 +110,9 @@ $: filteredMedia =
 		? media
 		: media.filter((m) => m.mediaListEntry?.customLists?.[selectedList]);
 
+$: if (browser && !dummy && media && previousAnimeList !== media)
+	previousAnimeList = media;
+
 const updateSelectedList = (event: Event) => {
 	const nextSelectedList = (event.currentTarget as HTMLSelectElement).value;
 
@@ -206,28 +210,24 @@ $: if (browser && !dummy) {
 onDestroy(() => clearAiringRefreshTimeout());
 
 const increment = (anime: Media, progress: number) => {
-	if (!dummy && pendingUpdate !== anime.id) {
+	if (dummy || pendingUpdate === anime.id) return;
+
+	pendingUpdate = anime.id;
+	lastUpdatedMedia = anime.id;
+
+	const target = media.find((m) => m.id === anime.id);
+
+	if (target?.mediaListEntry) target.mediaListEntry.progress = progress + 1;
+
+	media =
+		!completed && target && !hasDueEpisodes(target)
+			? media.filter((m) => m.id !== anime.id)
+			: media;
+
+	incrementMediaProgress(anime.id, anime.mediaListEntry?.progress, user, () => {
+		pendingUpdate = null;
 		$revalidateAnime = $revalidateAnime + 1;
-		lastUpdatedMedia = anime.id;
-		pendingUpdate = anime.id;
-
-		incrementMediaProgress(
-			anime.id,
-			anime.mediaListEntry?.progress,
-			user,
-			() => {
-				const mediaListEntry = media.find(
-					(m) => m.id === anime.id,
-				)?.mediaListEntry;
-
-				if (mediaListEntry) mediaListEntry.progress = progress + 1;
-
-				previousAnimeList = media;
-				animeLists = cleanCache(user, $identity);
-				pendingUpdate = null;
-			},
-		);
-	}
+	});
 };
 </script>
 
@@ -279,7 +279,7 @@ const increment = (anime: Media, progress: number) => {
   >
     <div slot="title" let:title={anime} let:progress>
       {#if !upcoming && !notYetReleased}
-        {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)}
+        {progress}{@html totalEpisodes(anime)}
         <button
           class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`}
           style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''}
@@ -312,7 +312,7 @@ const increment = (anime: Media, progress: number) => {
       {/if}
       {#if !upcoming || notYetReleased}
         <!-- {anime.mediaListEntry?.progress || 0}{@html totalEpisodes(anime)} -->
-        {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)}
+        {progress}{@html totalEpisodes(anime)}
         <button
           class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`}
           style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''}
-- 
cgit v1.2.3

