diff --git a/booru_viewer/core/api/category_fetcher.py b/booru_viewer/core/api/category_fetcher.py index 48700a1..a2013eb 100644 --- a/booru_viewer/core/api/category_fetcher.py +++ b/booru_viewer/core/api/category_fetcher.py @@ -289,18 +289,20 @@ class CategoryFetcher: # ----- dispatch: ensure (single post) ----- async def ensure_categories(self, post: "Post") -> None: - """Idempotent. Guarantee ``post.tag_categories`` is populated. + """Guarantee ``post.tag_categories`` is fully populated. Dispatch: - 1. Already populated → return. - 2. Cache compose → return if complete. - 3. Batch tag API (if available + probe passed) → return. - 4. Per-post HTML scrape → return. + 1. Cache compose with 100% coverage → return. + 2. Batch tag API (if available + probe passed) → return. + 3. Per-post HTML scrape → return. + + Does NOT short-circuit on non-empty ``post.tag_categories`` + because partial cache composes can leave the post at e.g. + 5/40 coverage. Only the 100%-coverage return from + ``try_compose_from_cache`` is trusted as "done." Coalesces concurrent calls for the same ``post.id``. """ - if post.tag_categories: - return if self.try_compose_from_cache(post): return diff --git a/booru_viewer/gui/main_window.py b/booru_viewer/gui/main_window.py index e2ce488..d15f278 100644 --- a/booru_viewer/gui/main_window.py +++ b/booru_viewer/gui/main_window.py @@ -161,13 +161,16 @@ class BooruApp(QMainWindow): return client.category_fetcher if client else None def _ensure_post_categories_async(self, post) -> None: - """Schedule an async ensure_categories if the post needs it. + """Schedule an async ensure_categories for the post. - No-op if the post already has categories, or if the active - client doesn't have a CategoryFetcher attached. + No-op if the active client doesn't have a CategoryFetcher + (Danbooru/e621 categorize inline, no fetcher needed). + + Does NOT check post.tag_categories — partial cache composes + from the background prefetch can leave the post at e.g. + 5/40 coverage. ensure_categories checks 100% cache coverage + internally and fetches the remainder if needed. """ - if post.tag_categories: - return client = self._make_client() if client is None or client.category_fetcher is None: return