Skip to content

[ACTV-448] Improve Step Deck tutorial responsiveness - #1372

Merged
cjasm merged 1 commit into
mainfrom
actv-448
Aug 17, 2026
Merged

[ACTV-448] Improve Step Deck tutorial responsiveness#1372
cjasm merged 1 commit into
mainfrom
actv-448

Conversation

@cjasm

@cjasm cjasm commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Related issues

Proposed changes

This PR improves the Step Deck Tutorial transition from Step 1 (“Click Browse”) to Step 2 by reducing the startup debounce in the browser initialization flow from 1000ms to 800ms.

The goal is to make onboarding feel more responsive while preserving the existing protection against duplicate step advancement during Browser/sidebar startup events.

Users currently experience a noticeable pause (~3s perceived in some environments) before Step 2 appears.
That delay comes from Browser startup latency plus the debounce window used to wait for stable sidebar state. Reducing the debounce by 200ms improves perceived speed with low regression risk.

How to reproduce

  • Run Step Deck tutorial and verify Step 1 → Step 2 transition feels faster.
  • Validate behavior on:
    • normal profile
    • heavy profile (large deck/tag/sidebar state)
  • Confirm no regressions:
    • no duplicate step transitions
    • Step 2 highlight/target always resolves correctly
    • downstream tutorial steps continue to work normally

Expected Outcome

  • Faster, smoother transition into Browser-based tutorial steps.
  • Improved perceived responsiveness during onboarding without sacrificing stability.

@cjasm
cjasm requested review from abdnh and r-lelis August 14, 2026 19:59
@abdnh

abdnh commented Aug 17, 2026

Copy link
Copy Markdown
Member

Can you give this a test? It looks like we can eliminate the timer now (probably after the recent change to _pending_browser_startup_completion):

diff --git a/ankihub/gui/tutorial.py b/ankihub/gui/tutorial.py
index edc4ad3f..08c2f08b 100644
--- a/ankihub/gui/tutorial.py
+++ b/ankihub/gui/tutorial.py
@@ -3,7 +3,7 @@ import json
 from asyncio.futures import Future
 from dataclasses import dataclass
 from functools import cached_property, partial
-from typing import Any, Callable, Dict, List, Optional, Tuple, TypedDict, Union, cast
+from typing import Any, Callable, Dict, List, Optional, TypedDict, Union, cast
 
 import aqt
 from anki.cards import CardId
@@ -24,10 +24,8 @@ from aqt.overview import Overview, OverviewBottomBar
 from aqt.qt import (
     QAbstractItemView,
     QCloseEvent,
-    QObject,
     QPoint,
     Qt,
-    QTimer,
     QToolButton,
     QVBoxLayout,
     QWidget,
@@ -160,47 +158,6 @@ def wrap_method(klass: Any, method_name: str, new: Any, pos: str = "after") -> C
     return revert_wrapping
 
 
-class DebouncedDelayedCall:
-    """Queues delayed callbacks. Only the most recent one runs; earlier ones exit when superseded."""
-
-    def __init__(
-        self,
-        callback: Callable[..., None],
-        delay_ms: int,
-    ) -> None:
-        self._callback = callback
-        self._delay_ms = delay_ms
-        self._pending_calls: List[Tuple[int, Tuple[Any], Dict[str, Any]]] = []
-        self._next_sequence = 0
-        self._timer: Optional[QTimer] = None
-
-    def schedule(self, parent: QObject, *args: Any, **kwargs: Any) -> None:
-        self._next_sequence += 1
-        my_seq = self._next_sequence
-        self._pending_calls.append((my_seq, args, kwargs))
-        timer: Optional[QTimer] = None
-        pending_call = (my_seq, args, kwargs)
-
-        def _cleanup_timer() -> None:
-            if self._timer is timer:
-                self._timer = None
-            if timer and not sip.isdeleted(timer):
-                timer.deleteLater()
-
-        def run() -> None:
-            if any(seq > my_seq for seq, _, _ in self._pending_calls):
-                self._pending_calls.remove(pending_call)
-                _cleanup_timer()
-                return
-
-            self._pending_calls.remove(pending_call)
-            self._callback(*args, **kwargs)
-            _cleanup_timer()
-
-        timer = aqt.mw.progress.timer(self._delay_ms, run, repeat=False, parent=parent)
-        self._timer = timer
-
-
 class TutorialOverlayDialog(OverlayDialog):
     def __init__(self, parent: QWidget, target: Optional[OverlayTarget], target_outline: bool = True) -> None:
         self.target_outline = target_outline
@@ -1351,8 +1308,6 @@ class StepDeckTutorial(DeckBrowserOverviewBackdropMixin, Tutorial):
 
             model = browser.sidebar.model()
             if not model:
-                is_startup = False
-                self._pending_browser_startup_completion = False
                 LOGGER.debug("Skipping tutorial browser startup callback as sidebar model is unavailable")
                 return
 
@@ -1374,15 +1329,11 @@ class StepDeckTutorial(DeckBrowserOverviewBackdropMixin, Tutorial):
             is_startup = False
             self._pending_browser_startup_completion = False
 
-        # There can be multiple sidebar refresh events at browser startup,
-        # so we need to ensure we only call .next() once
-        debouncer = DebouncedDelayedCall(wrapped_on_done, delay_ms=800)
-
         def _build_deck_tree(*args: Any, **kwargs: Any) -> None:
             _old: Callable[..., None] = kwargs.pop("_old")
             args, kwargs, root = extract_argument(func=_old, args=args, kwargs=kwargs, arg_name="root")
             _old(*args, **kwargs, root=root)
-            aqt.mw.taskman.run_on_main(lambda: debouncer.schedule(self._get_live_browser() or aqt.mw, root))
+            aqt.mw.taskman.run_on_main(lambda: wrapped_on_done(root))
 
         def before_setup_table(browser: Browser) -> None:
             aqt.mw.col.set_config_bool(Config.Bool.BROWSER_TABLE_SHOW_NOTES_MODE, False)

@cjasm
cjasm requested a review from abdnh August 17, 2026 15:20
@abdnh

abdnh commented Aug 17, 2026

Copy link
Copy Markdown
Member

We can test that later. This is ready to go if we want to make a release today.

@cjasm
cjasm merged commit 5edfafb into main Aug 17, 2026
10 checks passed
@cjasm
cjasm deleted the actv-448 branch August 17, 2026 19:13
@cjasm

cjasm commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

@abdnh I tested it!

I didn't break when the browser was opened, but it break something with the deck search because it finds everything that contains "anking".

I'll try to fix this and open another PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants