A request for help from the community here: three issues with an add-on I develop

I wrote my first add-on for Anki :slightly_smiling_face::tada:. It’s called Incubator, and its goal is to delay new cards until there is no mutual interference between them and their siblings. See the readme for more information.

I have three issues, which are described in the known issues section:

  • Sometimes the review window flickers or hiccups: cards which should be buried by the add-on are shown for a very short while or their audio is briefly heard before they are buried and the next card is drawn from the queue. I use gui_hooks.card_will_show as a hook for my function. Is it something wrong with my code, or is it a bug in Anki? I tested this with this version, installed with aur/anki-bin on Arch Linux: Version <U+2068>25.09.2 (3890e12c)<U+2069> Python 3.13.5 Qt 6.9.1 Chromium 130.
  • If you have many cards which are buried in succession, a minor yet noticeable delay might occur. Is there a way to improve the performance?
  • Hitting undo after a card has been buried by the add-on undoes the burying but then the add-on kicks in and buries it again immediately. This is not the desired behaviour, of course. How can this be fixed so undo does not operate on the last atomic bury but returns the state to the last reviewed card?

I prefer to wait with publishing the add-on on AnkiWeb until these issues are solved.

Thank you! :blossom:

2 Likes

Monkey patching to the rescue!

Patching Reviewer._showQuestion works more reliably according to my tests. Here’s an example:

import random
from typing import Callable

from anki.hooks import wrap
from aqt import mw
from aqt.operations import CollectionOp
from aqt.reviewer import Reviewer
from aqt.utils import tooltip


def patched_show_question(self: Reviewer, _old: Callable) -> None:
    if self.card.type != 0:
        return _old(self)

    blocking_siblings = random.choice([0, 1])
    if blocking_siblings:
        cid = self.card.id
        CollectionOp(mw, lambda col: col.sched.bury_cards([cid])).success(
            lambda _: tooltip(f"Buried card {cid}")
        ).run_in_background()
    else:
        _old(self)


Reviewer._showQuestion = wrap(Reviewer._showQuestion, patched_show_question, "around")  # type: ignore

Note: If you use CollectionOp, you don’t have to worry about telling the reviewer to go to the next card.

2 Likes

Brilliant! This seems to solve the first problem completely :slightly_smiling_face::folded_hands:

Any tips as to how to tackle the undo issue?

I don’t think there’s a reliable way to modify Anki’s undo state here. Maybe instead you can keep track of the last card buried by the add-on and skip it if it’s encountered again immediately in your patched _showQuestion?

1 Like

I remembered I’ve read something about that, and indeed I was not mistaken… Searching the documentation I found a link to this thread, which describes the desired functionality. See also this part of pylib/anki/collection.py.

Now I just need to see how to use this exactly, change the implementation accordingly, test it and that’s it!

The undo queue is maintained by the Rust code though, so you can’t modify it in add-ons.

I’m not sure we mean the same thing. I mean something like this, which is done in an add-on with a goal similar-yet-different to mine and is written in Python.

This adds a custom undo entry. Does it solve the issue of the add-on burying the same card after undo somehow? Curious to see how it’s implemented in your add-on.

Let’s define A and B as cards which should not be buried and X, Y and Z as cards which should, and let’s consider a AXYZB sequence.

The current situation is that if we undo the last step (burying Z and proceeding to B, i.e. going back to AXYZ), Z is buried again and we return to the same state of B showed to the user for review (AXYZB). We want to skip XYZ altogether, going back to A. What I propose (and what I intend to implement when I have time to sit and work on it…) is to have burying XYZ merged into a single undo step, going back from A[XYZB] to the state before burying XYZ (i.e. A) when hitting undo when B is shown.

The point is not having an undo entry with a custom name but merging several atomic entries into composite ones.

1 Like