Hello Anki team and community,
I have encountered and successfully investigated two independent but related theme-rendering bugs in Anki Desktop on macOS. Below are the detailed reports, including symptoms, root causes, what I tried, and my proposed solutions (which I have verified locally and am ready to submit as a PR).
Issue 1: ‘Follow System’ night mode detection fails on certain macOS environments
1. What is the problem?
When the theme preference is set to Follow System, Anki fails to switch to night mode even when macOS system appearance is set to Dark. It gets stuck in light mode.
2. Steps to reproduce:
- Open Anki and set the theme preference to Follow System (System Default).
- Set macOS system appearance to Dark in System Settings.
- Restart Anki or trigger an appearance update. Anki remains rendering in Light mode.
3. Root Cause Analysis & What I tried:
I added print debug statements in ThemeManager._determine_night_mode() to check why macOS dark mode resolved to False.
The problem lies in get_macos_dark_mode() inside aqt/theme.py:
def get_macos_dark_mode() -> bool:
from aqt._macos_helper import macos_helper
if not macos_helper:
return False
return macos_helper.system_is_dark()
On some macOS systems, macos_helper.system_is_dark() returns False even when the system is actually in Dark mode. Because Anki immediately returns this value, the existing robust fallback detection commands (using shell defaults read -g AppleInterfaceStyle or AppleScript query via osascript) never run.
Additionally, even if Night Mode is forced, the native macOS widget styling sometimes remains visually light unless custom Fusion styling is forced to render the dark palette properly.
4. Proposed Solution:
Update get_macos_dark_mode() to fall back to the command-line/AppleScript methods if macos_helper.system_is_dark() returns False.
Also, force the Fusion style on macOS when night_mode is active, avoiding visual styling mismatch.
Issue 2: Theme updates cause RuntimeError on destroyed temporary webviews
1. What is the problem?
If a temporary webview (such as FindDupesWebView, StatsWebView, or EmptyCardsWebView) is opened, closed, and then a theme change occurs (e.g. system toggles appearance or preference updates), Anki crashes with a RuntimeError.
2. Steps to reproduce:
- Open a dialog that initializes a temporary webview (for example, go to Tools → Find Duplicates).
- Close the Find Duplicates dialog.
- Toggle the system appearance or update the theme preference in Anki.
- Anki raises a traceback or crashes.
3. Exact Error Message:
RuntimeError: wrapped C/C++ object of type FindDupesWebView has been deleted
4. Root Cause Analysis & What I tried:
When a dialog containing AnkiWebView is closed and destroyed, the underlying Qt C++ object is deleted. However:
- The Python object may still receive theme-change callbacks from the
gui_hooks.theme_did_changelist if hook cleanup has not fully settled. - When
on_theme_did_change()runs, it attempts to callself.page()and access page settings, which immediately raises aRuntimeErrorsince the C++ object is dead. - Hook removal in
cleanup()can also trigger aValueErrorif the callback was already removed.
5. Proposed Solution:
- Add
if sip.isdeleted(self): returnguard at the beginning ofon_theme_did_change(). - Wrap
self.page()and page settings accesses intry/except RuntimeErrorblocks to safely ignore deleted Qt objects. - Use tolerant try-except blocks during hook removal in
cleanup()to prevent failures.
I have prepared these changes locally in the desktop repository, verified their behavior, and they resolve both issues cleanly without introducing regressions. I am happy to open a Pull Request for this!
Thank you for your time and all your volunteer work on Anki!
Best regards,
Allen