diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ece6ab..13d2be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.32.4 - 2026-05-17 + +- Highlighted fully ready finished watchlist entries with a thin green card border when both subbed and dubbed episodes are complete, making download-complete anime easier to scan quickly. + ## 0.32.3 - 2026-05-17 - Fixed watchlist download sync so a successful download now reuses the existing tracked entry when the resolved `show_id` changes, preserving its category and marking it downloaded instead of creating a duplicate finished-only row. diff --git a/README.md b/README.md index d5404e2..6506d50 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,7 @@ The app currently includes: - Per-show watchlist refresh queueing now also deduplicates in-flight refreshes, so the same show is not fetched twice back-to-back while one refresh is already running. - The watchlist page now silently refreshes itself while queued per-show refresh work is still being processed, so queued entries settle into their updated state without a manual reload. - The watchlist page now also avoids re-triggering the same unresolved thumbnail warm-up request on every silent poll cycle while a show's thumbnail state is unchanged. +- Finished watchlist cards with both sub and dub fully ready now get a thin green border for quicker visual scanning. - Background `Refresh All` execution with status polling, no inline thumbnail downloads during the bulk job, and a configurable delay between shows to reduce upstream request bursts. - Optional scheduled background watchlist refreshes with Config page controls for enable/disable and refresh period. - Scheduled background refresh runs now log queue, start, and completion status to stdout so container logs show whether they finished successfully, with errors, or failed. diff --git a/VERSION b/VERSION index d721c76..eb77136 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.32.3 +0.32.4 diff --git a/app_support.py b/app_support.py index 07c84ac..f56c807 100644 --- a/app_support.py +++ b/app_support.py @@ -16,7 +16,7 @@ from uuid import uuid4 PROJECT_ROOT = Path(__file__).resolve().parent ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli" APP_NAME = "ani-cli-web" -VERSION = "0.32.3" +VERSION = "0.32.4" ALLANIME_BASE = "allanime.day" ALLANIME_API = f"https://api.{ALLANIME_BASE}" ALLANIME_REFERER = "https://allmanga.to" diff --git a/test_app.py b/test_app.py index 4ae73c6..673cedb 100644 --- a/test_app.py +++ b/test_app.py @@ -1481,6 +1481,11 @@ class TemplateHelperTests(unittest.TestCase): self.assertIn("else if (state.queuedCount > 0)", APP.WATCHLIST_HTML) self.assertIn("fetchWatchlist(state.watchlistPage, { silent: true })", APP.WATCHLIST_HTML) + def test_watchlist_page_highlights_fully_ready_finished_cards(self): + self.assertIn(".anime-card.fully-ready", APP.WATCHLIST_HTML) + self.assertIn('item.category === "finished" && item.sub_complete && item.dub_complete', APP.WATCHLIST_HTML) + self.assertIn("card.className = watchlistCardClassName(item);", APP.WATCHLIST_HTML) + def test_watchlist_thumbnail_warmup_skips_repeated_attempts(self): self.assertIn("thumbnailWarmupPending: new Set()", APP.WATCHLIST_HTML) self.assertIn("function thumbnailWarmupToken(item)", APP.WATCHLIST_HTML) diff --git a/watchlist_page.py b/watchlist_page.py index 9e27880..ac7810e 100644 --- a/watchlist_page.py +++ b/watchlist_page.py @@ -303,10 +303,19 @@ WATCHLIST_HTML = r""" box-shadow: 0 18px 38px rgba(5, 2, 15, 0.32); transition: transform 0.18s ease, border-color 0.18s ease; } + .anime-card.fully-ready { + border-color: rgba(149, 228, 186, 0.72); + box-shadow: + inset 0 0 0 1px rgba(149, 228, 186, 0.18), + 0 18px 38px rgba(5, 2, 15, 0.32); + } .anime-card:hover { border-color: var(--line-strong); transform: translateY(-2px); } + .anime-card.fully-ready:hover { + border-color: rgba(149, 228, 186, 0.88); + } .anime-card.error { border-color: rgba(255, 144, 164, 0.3); } @@ -699,6 +708,11 @@ WATCHLIST_HTML = r""" return parts.map((part) => part[0]).join("").slice(0, 2); } + function watchlistCardClassName(item) { + const isFullyReady = item.category === "finished" && item.sub_complete && item.dub_complete; + return `anime-card${item.status === "error" ? " error" : ""}${isFullyReady ? " fully-ready" : ""}`; + } + function formatModeProgress(currentCount, expectedCount) { const current = Number(currentCount || 0); const expected = Number(expectedCount || 0); @@ -965,7 +979,7 @@ WATCHLIST_HTML = r""" for (const item of items) { const card = document.createElement("article"); const checked = item.last_checked ? new Date(item.last_checked).toLocaleString() : "Never"; - card.className = `anime-card${item.status === "error" ? " error" : ""}`; + card.className = watchlistCardClassName(item); card.dataset.showId = item.show_id; card.title = item.thumbnail_debug || "Thumbnail source: unresolved"; card.innerHTML = `