info_panel: suppress flat-tag flash when category fetch is pending

When a category fetch is about to fire (Rule34/Safebooru.org/
Moebooru on first click), the info panel was rendering the full
flat tag list, then ~200ms later re-rendering with categorized
tags. The re-layout from flat→categorized looked like a visual
hitch.

Fix: new _categories_pending flag on InfoPanel. When True, the
flat-tag fallback branch is skipped — the tags area stays empty
until categories arrive and render in one clean pass.

  _ensure_post_categories_async sets _categories_pending = True
    before scheduling the fetch (or False if no fetcher = Danbooru)
  _on_categories_updated clears _categories_pending = False

Visual result:
  Danbooru/e621:        instant (inline, no flag)
  Gelbooru with auth:   instant (background prefetch beat the click)
  Rule34/SB.org/Moebooru: empty ~200ms → categories appear cleanly
                          (no flat→categorized re-layout)
This commit is contained in:
pax 2026-04-09 20:05:38 -05:00
parent 57a19f87ba
commit a86941decf
2 changed files with 12 additions and 6 deletions

View File

@ -79,6 +79,7 @@ class InfoPanel(QWidget):
def __init__(self, parent: QWidget | None = None) -> None: def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent) super().__init__(parent)
self._categories_pending = False
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setContentsMargins(6, 6, 6, 6) layout.setContentsMargins(6, 6, 6, 6)
@ -164,8 +165,11 @@ class InfoPanel(QWidget):
btn.setStyleSheet(style) btn.setStyleSheet(style)
btn.clicked.connect(lambda checked, t=tag: self.tag_clicked.emit(t)) btn.clicked.connect(lambda checked, t=tag: self.tag_clicked.emit(t))
self._tags_flow.addWidget(btn) self._tags_flow.addWidget(btn)
else: elif not self._categories_pending:
# Fallback: flat tag list (Gelbooru, Moebooru) # Flat tag fallback — only when no category fetch is
# in-flight. When a fetch IS pending, leaving the tags
# area empty avoids the flat→categorized re-layout hitch
# (categories arrive ~200ms later and render in one pass).
for tag in post.tag_list[:100]: for tag in post.tag_list[:100]:
btn = QPushButton(tag) btn = QPushButton(tag)
btn.setFlat(True) btn.setFlat(True)

View File

@ -166,14 +166,15 @@ class BooruApp(QMainWindow):
No-op if the active client doesn't have a CategoryFetcher No-op if the active client doesn't have a CategoryFetcher
(Danbooru/e621 categorize inline, no fetcher needed). (Danbooru/e621 categorize inline, no fetcher needed).
Does NOT check post.tag_categories partial cache composes Sets _categories_pending on the info panel so it skips the
from the background prefetch can leave the post at e.g. flat-tag fallback render (avoids the flatcategorized
5/40 coverage. ensure_categories checks 100% cache coverage re-layout hitch). The flag clears when categories arrive.
internally and fetches the remainder if needed.
""" """
client = self._make_client() client = self._make_client()
if client is None or client.category_fetcher is None: if client is None or client.category_fetcher is None:
self._info_panel._categories_pending = False
return return
self._info_panel._categories_pending = True
fetcher = client.category_fetcher fetcher = client.category_fetcher
signals = self._signals signals = self._signals
@ -195,6 +196,7 @@ class BooruApp(QMainWindow):
place by the CategoryFetcher, so we just call the panel's place by the CategoryFetcher, so we just call the panel's
set_post / set_post_tags again to pick up the new dict. set_post / set_post_tags again to pick up the new dict.
""" """
self._info_panel._categories_pending = False
if not post or not post.tag_categories: if not post or not post.tag_categories:
return return
idx = self._grid.selected_index idx = self._grid.selected_index