anki icon indicating copy to clipboard operation
anki copied to clipboard

Feat/Svelte port for reviewer

Open Luc-Mcgrady opened this issue 4 months ago • 55 comments

[!NOTE]
While testing this, make sure to enable "Use new reviewer" in the preferences: image

image

I'm not sure if I have the right idea here. If I don't please tell me so I can give up and leave it to the people who probably should be left to do this.

  • [x] It doesn't appear until you enter and exit back out, I'm assuming because the height is unknown. This didn't happen when I initially tried it with the ts page rather than the sveltekit one (7e92c4016938be6964d3d85e89bdf8f4c19b873b last good commit).
  • [x] The due dates aren't shown yet (although "due" is passed to the element so it should be quick)
  • [x] The down arrow for the more button is a placeholder
  • [x] Buttons look weird, I don't think width is set correctly

I'm not sure how faithful to the original html I need to be. If I should use the old table methods, for addon support reasons perhaps, let me know.

Reviewer

  • [ ] Main window API access
  • [X] Mathjax
    • [x] MathJax Font's
  • [x] Typed answers
  • [X] Audio buttons
    • [x] Autoplay
  • [x] Flags display
  • [x] "More" menu
    • [x] Flags don't un-set
    • [x] Shortcuts
    • [x] Audio control buttons
    • [x] Add vertical scroll to menu when it overflows the screen(?)
  • [x] Timer
  • [x] Undo
  • [x] Image occlusion is broken for ./tools/runopt
  • [x] Congratulations screen
  • [x] Spacebar rates cards option
  • [x] Changing flag resets timer
  • [ ] Auto Advance Wait for audio
  • [x] Preload resources
  • [ ] "Processing" equivalent for when next_card_data takes a long time.
  • [ ] Hooks
  • [x] Clicking the toolbar causes enter to do the toolbar action rather than progress the card.
  • [x] Holding enter rushes through the cards
  • [ ] Clicking the edit or more button, then hitting enter, both reviews the card and triggers the button

[!NOTE]
I'll repeatedly edit this comment with any queries I have about how things should be implemented. For the time being.

  • https://github.com/ankitects/anki/pull/4289#issuecomment-3518166035

Luc-Mcgrady avatar Aug 26 '25 01:08 Luc-Mcgrady

Hi Luc,

My vision for the reviewer migration is to have the answer buttons handled as part of the reviewer page, rather than a separate webview. While we could potentially migrate the bottom bar separately first and potentially reuse some of it when we later merge the two pages, I imagine it would be more efficient to tackle both at once. WDYT?

Due to it being a rather radical change, we're probably going to maintain two separate paths, with users being able to switch between the old and new modes.

dae avatar Sep 02 '25 07:09 dae

I'll try and make the reviewer svelte as well then. Hopefully inheritance can make the toggle-able option easy to implement. No promises on the timescale for this :sweat_smile:. I thought I was biting off more than I could chew with just the bottom bar.

Luc-Mcgrady avatar Sep 03 '25 17:09 Luc-Mcgrady

Do you think there's a need to isolate the bottom bar from the card's css? If so what would be a good way to do it?

Luc-Mcgrady avatar Sep 03 '25 22:09 Luc-Mcgrady

I'm hoping we can use an iframe to sandbox user-provided content and ensure it doesn't alter Anki's UI at all, or have access to our internal API. That is the place I would start: a basic prototype that demonstrates it's technologically feasible (it should be with message passing between frames). Once that is confirmed, I'd suggest we write up a short plan of how we will implement it, and get agreement on the basic architecture before proceeding with implementation.

dae avatar Sep 17 '25 02:09 dae

I've done a quick proof of concept. It seems like this should work.

(it should be with message passing between frames)

Should this be with some sort of "reviewer-inner" endpoint for the iframe, or should it all be set from the reviewer as it is currently (in the proof of concept)? Currently, the code copies the head element directly from the parent page and pastes it into the iframe but this is obviously quite a finicky solution so I'm assuming in an actual attempt it would be better with the endpoint just to set the css and whatever java-script api we want the card to have access to?

or have access to our internal API

I'm assuming this means that allowing direct access to the bridge command is a bad idea?

Luc-Mcgrady avatar Sep 22 '25 00:09 Luc-Mcgrady

That's promising!

Should this be with some sort of "reviewer-inner" endpoint for the iframe, or should it all be set from the reviewer as it is currently (in the proof of concept)?

That's a good question, and we have some tradeoffs to consider. Some thoughts:

  • If we declare the outer frame responsible for all network I/O and state, it makes things easier to reason about - it applies state changes, with the inner iframe just responsible for rendering the new state.
  • But naively postMessage()ing the card content to the iframe is not efficient, since it has to make another copy of the data. I gather it is possible to pass some objects like blobs across without a copy , so the outer frame could potentially pass the binary data from a network request to the inner frame without (much) overhead. This could work for the card content, but seems tricky for things like embedded images. So at the least, we probably want to allow the iframe access to an endpoint that can fetch arbitrary files from the media folder. Extending that to allow fetching of the card's question/answer as well would save us having to worry about slowdowns with notes with lots of HTML being passed in from the outer frame.
  • Aside from those two, I think any communication that inner frame wants to do, should probably be via sending a message to the parent frame, so that the outer frame can decide whether it needs to change its internal state and/or communicate with the backend. Currently I can only think of the type answer code needing to signal enter being pressed, and perhaps the handling of shortcut keys, depending on how focus/bubbling interacts with the iframe.

whatever java-script api we want the card to have access to

I'd like to keep it as minimal as possible, because we're exposing it to untrusted content from pastes and shared decks. Aside from the endpoints mentioned above, what would we need?

I'm assuming this means that allowing direct access to the bridge command is a bad idea?

Yep. In fact, I think we should be removing all bridgeCommand use as part of this refactor.

dae avatar Sep 24 '25 13:09 dae

Oh, and one thing I forgot to mention: you've used a writable for the POC, but for the real implementation, I think we'll probably want to directly manipulate the DOM after receiving the card data from the network. The asynchronous postMessage() from the outer frame already adds at least a tick, and Svelte's reactivity would presumably add a few more.

But perhaps I'm being biased by the problems I ran into when using reactivity on AnkiWeb. :-)

dae avatar Sep 24 '25 13:09 dae

Something I've overlooked; as the iframe and bottom bar are still part of the same page, the zoom will now affect the bottom bar. Is this something that needs to be fixed? image

Yep. In fact, I think we should be removing all bridgeCommand use as part of this refactor.

How should the existing pycmd commands be handled? I have taken the inner-reviewer endpoint approach, where I try to keep as much state in the outer frame as possible, so should the commands be relayed to the outer frame after being checked that they one of a set number of "allowed" pycmd commands (like in https://github.com/ankitects/anki/pull/4289/commits/b6e07f57800faee93636da109a2fcde0ab03227c).

So at the least, we probably want to allow the iframe access to an endpoint that can fetch arbitrary files from the media folder

Yep, I think that is a must because otherwise cards which use images stored on webpages would be broken.

Extending that to allow fetching of the card's question/answer as well would save us having to worry about slowdowns with notes with lots of HTML being passed in from the outer frame.

So the flow to this would look something like:

showQuestion(cid) -> postmessage("question-cid") -> fetch("_api/question/{cid}")
showAnswer(cid) -> ... -> fetch("_api/answer/{cid}")

Or would it be best to send both the question and the answer from a single "_api/card/{cid}" endpoint and have it display the answer when triggered, without an extra request?

I'd like to keep it as minimal as possible, because we're exposing it to un-trusted content from pastes and shared decks. Aside from the endpoints mentioned above, what would we need?

I don't think there's anything.

Just checking but is sandbox="allow-scripts" a sufficient level of sandboxed for our purposes? I'm assuming no harm can be done without allow-same-origin?

If you have any other notes regarding the direction this is headed let me know :).

Luc-Mcgrady avatar Oct 03 '25 01:10 Luc-Mcgrady

Something I've overlooked; as the iframe and bottom bar are still part of the same page, the zoom will now affect the bottom bar. Is this something that needs to be fixed?

Not at first, but we might want to at one point. I think we could probably work around it by intercepting the zoom events and sending a message to the iframe to scale its content, which would leave the surrounding interface alone.

How should the existing pycmd commands be handled?

Apologies if the following is partially redundant; I haven't had a chance to read through all your changes yet.

bottomReady is a legacy artifact. In our Svelte pages, when we need to ensure commands are run after the page is fully initialized, we either call a protobuf method, and/or export a promise on the page, both of which can be seen here:

ts/routes/deck-options/[deckId]/+page.svelte

But I wonder if we even need it in this case? Part of our Svelte migration is shifting the controlling logic from Python to TypeScript, so decisions like "we should show the next question" should be made at the TypeScript level, not by Python. We should be able to load the Svelte page in a browser, and have it function properly without any Python code running (aside from mediasrv.py).

Thoughts on the other ones currently in use:

  • edit/more should be turned into protobuf methods for now (eventually we'll be able to navigate directly to the editing page/display the context menu directly)
  • ans presumably can be handled all in the TS end
  • ease[1-4] would use a protobuf method.
  • play: could be turned into another protobuf endpoint. We could tweak the av_refs_to_play_icons() logic to store file index in an attribute like data-play-index, and use onclick="<some predefined handler like anki.reviewer.playHandler>" or similar, perhaps?

Or would it be best to send both the question and the answer from a single "_api/card/{cid}" endpoint and have it display the answer when triggered, without an extra request?

Sending both at once would allow us to preload the answer-side media, making subsequent reveal faster, and it avoids extra work (when templates are rendered, we output both front+back at once, since the back side usually includes the front).

I'm reconsidering my earlier suggestion of having the inner iframe fetch the data directly. It's more efficient for large templates, but it's a more complicated system than having the outer frame handle all data fetching except for media files. With the fetching done by the outer frame, we could combine the answering and next card fetching into one for example:

nextCardData(answerForCurrentCard: answer | null) → data

when the user taps on an answer button, we'd do something like nextCardData(answer).then((data) => showNextCard(data))

Just checking but is sandbox="allow-scripts" a sufficient level of sandboxed for our purposes? I'm assuming no harm can be done without allow-same-origin?

There are templates out there that use remote scripts, which we'd break if we enforced same-origin. In the future we may well want to use CSP or similar to enforce only first-party scripts by default (a lot of the use cases embedded scripts currently serve could perhaps be handled better by #3833, and keeping them enabled allows for exfiltration of card/media data), but that's something that would need to be optional for legacy reasons, and not something we need to fix right now.

I'm not sure I'm doing a great job of explaining everything :-) Please feel free to ask as many follow-up questions as you need; it's better we clarify things now than after code is written. And don't be afraid to argue back if you feel something is wrong - it's quite possible I've overlooked something that would become more apparent when it comes time to implementing things.

dae avatar Oct 03 '25 07:10 dae

Just checking but is sandbox="allow-scripts" a sufficient level of sandboxed for our purposes? I'm assuming no harm can be done without allow-same-origin?

There are templates out there that use remote scripts, which we'd break if we enforced same-origin. In the future we may

I think I worded this poorly. Currently the

When I attempted

(earlier comment) we probably want to allow the iframe access to an endpoint that can fetch arbitrary files from the media folder.

I noticed that <img> element's loaded and assumed the image endpoint didn't have CORS set properly as the iframe pretends to be from a different origin. After further googling, <img> elements don't respect CORS which means there is no need for any blobs or fancy programing. I had assumed, since you expected a problem here, something must have been wrong.

edit/more should be turned into protobuf methods for now (eventually we'll be able to navigate directly to the editing page/display the context menu directly)

I feel like "more" will be particularly difficult here. I should add a context menu via svelte correct? Are there any extra protobuf methods I need to make this work?

(eventually we'll be able to navigate directly to the editing page/display the context menu directly)

Should clicking "edit" in a web browser open the QT edit menu for now? Maybe it's best to leave the bridge commands as they are if a future solution will overwrite any work I do here. (Unless the direct navigation work is also intended to be part of this PR?)

nextCardData(answerForCurrentCard: answer | null) → data

~~Will this include converting all the card template rendering into rust? e.g. the aforementioned av_refs_to_play_icons is python code.~~ (I think the answer will have to be yes. I'm presuming that this will be difficult with most of the sound/mpv code being part of the python code?)

I have no problem understanding the rest, though may have more questions later. I can only hope I explain so well :).

Luc-Mcgrady avatar Oct 04 '25 01:10 Luc-Mcgrady

I've implemented a nextCardData function. The reviewer's basic functionality is usable in the browser now:

image

I hope this is what you had in mind?

I was wondering how it would be best to handle external changes to the queue/refreshing the displayed card when needed.

Luc-Mcgrady avatar Oct 04 '25 20:10 Luc-Mcgrady

Hi! I'm new to this repository and was trying to make sense of what's going on here. This is just a suggestion so please by all means feel free to ignore if you don't think it fits with the philosophy of the project — just offering my 2c.

Also, hello Dae, from Australia! Fellow Aussie here. Thanks for your hard work on Anki, and the rest of the team. It's an invaluable part of my university toolkit and it's a wonderful and unique tool.

From what I can tell, the system that you're gunning for is one where a renderer communicates with the core Anki Python backend via protobuf, but you want to gradually move more and more of the rendering and UI work to the web engine. Ultimately this transition is a bit annoying because of protobuf so you want to limit the amount of IPC possible.

Given a case like "changes to the queue" or "request was sent", it feels like a hassle to implement a bunch of cross-process calls to fetch the data of the card. Is it possible to serve the card question and answer through mediasrv.py over HTTP, say, the endpoint http://<base_url>/cards/front/<card_id>? i.e.

def fetch_card_html(card_id: anki.cards.CardID, side: Literal["front", "back"]):
    cid = anki.cards.CardId(int(get_query_param("cid")))
    side = get_query_param("side")

    if side not in ["question", "answer"]:
        raise ValueError("Invalid `side` parameter. Must be 'question' or 'answer'.")

    card_template = mw.col.get_card(cid).render_output()

    if side == "front":
        content = card_template.question_text
    elif side == "back":
        content = card_template.answer_text

    return f'<style>{card_template.css}</style><div class="card" style="margin: 0; padding: 12px;">{content}</div>'

You could even produce both sides, with the reverse card opacity: 0 style applied for answer prerendering. The renderer could then hide the front and show the back when the answer is clicked. But that can be done no matter what architecture is used.

Potential benefits:

  • Embeddable in the iframe with no dramas
  • No extra calls or cruft needed
  • Increased modularisation
  • If I understand correctly the POC I've just given is like... 50% of the code required to implement this
  • The reviewer needs to keep track of queued cards but that's about it

olliecheng avatar Oct 07 '25 12:10 olliecheng

From what I can tell, the system that you're gunning for is one where a renderer communicates with the core Anki Python backend via protobuf,

Hello. Thanks for the input. The system that this PR should use is one which replaces the python aspects of the reviewer with Rust and Svelte.

I have already implemented a function akin to the one you have described (Although it is in rust): https://github.com/ankitects/anki/pull/4289/files#diff-cc0065b0b5676731b2612086bdd05e2fd81d0225a43d39a07a48cb46dffa1e5aR391-R431

My issue with refreshing I can't think of a way outside of either polling or web-sockets to notify the front-end of when it needs to fetch the new card data. (without resorting to web.evals/python code).

Functionality I need to re-implement:

https://github.com/ankitects/anki/blob/9550b127d9e290ba191d4c1f8430d574f4a898c1/qt/aqt/reviewer.py#L221-L235

Luc-Mcgrady avatar Oct 07 '25 14:10 Luc-Mcgrady

I guess my point was more that I can't seem to get a clear understanding of the separation between the frontend and backend at the moment, so changes become difficult to make. My interpretation of dae's comments above...

Part of our Svelte migration is shifting the controlling logic from Python to TypeScript, so decisions like "we should show the next question" should be made at the TypeScript level, not by Python

...is that this transition involves pushing logic to the front-end and changing the backend to act primarily to:

  • Render the front-end initially and provide the overarching Qt window
  • Send controls to the front-end, if needed
  • Relay commands from externally, such as addon functionality (i.e. Contanki)
  • Handle rescheduling
  • Store and provide access to the collection

Maybe an example of my interpretation of this principle is in your next_card_data function, which reads the queue as a side-effect. Instead, could next_card_data take in an arbitrary card ID and load in the corresponding card? Then, queue management can be done on the front-end: the front-end can request the queue from the backend as necessary, but has full control over the queue logic. I guess my proposal was to take this to the extreme and classify 'card HTML' as media; just move "card HTML rendering" to mediasrv.py, which already serves the assets required to render the card. But I certainly don't think it's necessary.

To that end, it feels like a solid set of IPC guidelines for how to communicate from backend to frontend is a necessary step before moving forwards, otherwise it's easy to accrue technical debt. After all, the frontend to backend communication is well defined through just a HTTP POST request. No reason we can't do anything similar the other way around. I think one option is to use the existing event listeners API in the browser. This way, we only need one server that acts as a relay to listen to incoming protobuf payloads, decoding, and then sending out an event. This enables addEventListener() to receive messages, which feels natural to me. A few ways to implement the relay server:

  • Like you suggest, establishing a WebSocket/HTTP POST server
  • Creating a globally available relay function which can be called using web.eval (this might make it hard to see call return values though)
  • base64 encoding over QWebChannel (which ?only supports strings)

What do you think?

olliecheng avatar Oct 07 '25 15:10 olliecheng

@Luc-Mcgrady

I feel like "more" will be particularly difficult here. I should add a context menu via svelte correct? Are there any extra protobuf methods I need to make this work?

Should clicking "edit" in a web browser open the QT edit menu for now? Maybe it's best to leave the bridge commands as they are if a future solution will overwrite any work I do here. (Unless the direct navigation work is also intended to be part of this PR?)

I guess the point of turning edit/more into protobuf methods here is that bridgeCommand() is not available on mobile. Implementation will not change for now (we can rewrite the context menu in Svelte later, maybe using the ContextMenu component I added for the editor). See deck_options_ready/deckOptionsReady or the editor rework for examples.

abdnh avatar Oct 13 '25 08:10 abdnh

This PR breaks the flexible grading add-on.

Before After
screenshot-2025-10-13-20-54-32
Buttons are properly hidden. The bottom bar is short.
screenshot-2025-10-13-20-53-53
Buttons are visible even though the add-on removes them. The number of reps is hidden even though it should be visible. The bar is tall. There are three new buttons at the bottom.

PS: This rewrite doesn't achieve anything useful, but it will frustrate the users because it introduces breakage. Realistically, there's no need to use Svelte in Anki. As many people have pointed out, Svelte is an inferior technology that makes interfaces more bloated and difficult to use. So I think your time is better spent elsewhere 🙏.

tatsumoto-ren avatar Oct 13 '25 21:10 tatsumoto-ren

This PR breaks the flexible grading add-on.

Addons will likely have to be updated if the code is ported to another language. That's expected. Once this PR is part of the beta, addon authors will have a chance to update their addon code. Though some addons might not be updated and stay broken; I get why that frustrates you.

Realistically, there's no need to use Svelte in Anki.

Svelte is increasingly used because it works cross platform. That way, developement efforts between e.g. anki and ankidroid can be uniformed. That will also make the behavior of those components much more predictable to users, e.g. regarding templates.

As many people have pointed out, Svelte is an inferior technology that makes interfaces more bloated and difficult to use.

I know you dislike Ankis goal of moving away from python and towards svelte. But I honestly don't remember "many people" pointing out what you just said. Aside from the advantages I mentioned above, we should also consider that performance (if that is your concern) shouldn't be too much impacted by this port. Both, svelte and python don't run as a binary format (like e.g. C, which is compiled) and I believe dae mentioned that we currently have multiple webviews, which can be unified by ports like this one (and thus improving performance).

So the only real downsides I see are possible breakages of addons and that someone has to do the work (which Luc is already doing). The advantages I mentioned outnumber the disadvantages, especially regarding development efforts between different anki clients.

GithubAnon0000 avatar Oct 13 '25 22:10 GithubAnon0000

Addons will likely have to be updated if the code is ported to another language.

@Luc-Mcgrady Since you're responsible for this change, could you please upgrade the affected Flexible Grading add‑on? Or, ideally, modify this PR to avoid breaking add‑ons that can't be easily updated (for example, if the authors are no longer active). Fixing the breakage is the most important thing here.

Svelte is increasingly used because it works cross platform. That way, development efforts between e.g. anki and ankidroid can be uniformed.

Creating 4 buttons is not a major development effort. It makes more sense to unify the backend, not the GUI, which will perform horribly on all platforms if it's native to none.

we should also consider that performance (if that is your concern) shouldn't be too much impacted by this port. Both, svelte and python don't run as a binary format

I'm not a fan of Python. Qt is written in C++, and Qt handles widget drawing, which is why it's fast. See https://github.com/ankitects/anki/pull/4029#issuecomment-3010001449

If you compare webview windows with pure-Qt windows in Anki, you'll notice that Qt windows appear instantly, while webviews can take nearly half a second, or even close to a full second, to load. The worst example is the "deck options" dialog, which used to perform much better in the past and had a more minimalist design.

tatsumoto-ren avatar Oct 13 '25 23:10 tatsumoto-ren

Could you estimate, which other addons will be non-compatible with anki after this?

ganqqwerty avatar Oct 13 '25 23:10 ganqqwerty

Agreed on the point about addons and backwards compatibility. I believe dae has his reasons for the change, whether those reasons are an acceptable price to pay to break backwards compatibility is yet to see. It's entirely possible that the two systems will coexist for a long time just so older addons still work in newer versions.

Svelte is an inferior technology that makes interfaces more bloated and difficult to use

I would argue interface bloat is a function of UI design, not interface technology. If you're referring to accessibility, the web platform has excellent accessibility support (I'm not sure about QTWebEngine, but it's Chromium based).

If you compare webview windows with pure-Qt windows in Anki, you'll notice that Qt windows appear instantly, while webviews can take nearly half a second, or even close to a full second, to load. The worst example is the "deck options" dialog, which used to perform much better in the past and had a more minimalist design.

Agreed that the "deck options" dialog takes much longer to open. This is not because of Svelte, it's because Qt needs to initialise a browser engine to display the dialog box. However, this cost is already paid when the user opens the reviewer, since under the hood card rendering is already handled by a full fledged web engine. In this case, the proposal is not to replace a pure Qt implementation with a pure web engine implementation - it's to replace the current renderer, a mix of Qt widgets and QtWebEngine, with just a single QtWebEngine where the web renderer handles everything.

Your point about performance is important, though, because it's easy to screw up performance constantly creating web renderers and writing unoptimised frontend code. Obsidian is commonly used as an example of how web technology needs not be slow or laggy. I would imagine everybody is on the same page that any noticeable regression in reviewer performance is unacceptable.

olliecheng avatar Oct 13 '25 23:10 olliecheng

@Luc-Mcgrady Since you're responsible for this change, could you please upgrade the affected Flexible Grading add‑on?

That's the job of the addon author.

Creating 4 buttons is not a major development effort.

This isn't just about creating 4 buttons.

It makes more sense to unify the backend, not the GUI, which will perform horribly on all platforms if it's native to none.

Fair enough. Though it probably isn't even close as bad as you think it is and it is a major advantage to have the same backend and the same (or at least similar) gui code base.

I'm not a fan of Python. Qt is written in C++, and Qt handles widget drawing, which is why it's fast.

Anki uses pyqt though. I was under the impression that pyqt is separate from the "normal" qt (which uses C++). But I guess it makes sense that pyqt basically calls the qt stuff that had been build with C++, making it fast.


I just remembered another good reason to move away from qt though, which is the situation with their licensing and company changes. I cannot find where it was mentioned, but I'm pretty sure dae mentioned it as one of the many reasons.

That being said: This is starting to become off-topic. Maybe we can discuss more about this on the forums if you desire (though I have no saying in those decisions either).


Could you estimate, which other addons will be non-compatible with anki after this?

All addons that modify the review screen in any way. How big the breakage is and how easy / fast it is to solve it, depends on the specific addon.

GithubAnon0000 avatar Oct 14 '25 00:10 GithubAnon0000

That's the job of the addon author.

In the past, this has often been the case. However, it's an inhumane practice. Add-on authors should never be the ones to clean up the mess after Anki devs break things. And add-on authors may not know how to fix breakages, since that requires meticulously studying the new codebase which is never documented. Sometimes add-ons can't be easily updated. For example, if the author is inactive, or if a fix would require substantial effort. Fortunately, there have been instances where Anki adapted properly to existing add-ons without breaking them.

This isn't just about creating 4 buttons.

Yeah, 6 buttons. I forgot "Edit" and "More".

it is a major advantage to have the same backend and the same (or at least similar) gui code base.

Yes, but there are many disadvantages too.

pyqt basically calls the qt stuff that had been build with C++

Right.


I would argue interface bloat is a function of UI design, not interface technology. If you're referring to accessibility, the web platform has excellent accessibility support (I'm not sure about QTWebEngine, but it's Chromium based).

I mean bloat as in software consuming too many system resources and lagging when it can't consume enough. I imagine Svelte is appropriate to use somewhere on a website that only works inside a web browser, but not necessarily for a native app.

Agreed that the "deck options" dialog takes much longer to open. This is not because of Svelte, it's because Qt needs to initialise a browser engine to display the dialog box.

Yep, and there's a suggestion to move all windows and dialogs to Qt to avoid initializing webviews.

tatsumoto-ren avatar Oct 14 '25 01:10 tatsumoto-ren

My issue with refreshing I can't think of a way outside of either polling or web-sockets to notify the front-end of when it needs to fetch the new card data. (without resorting to web.evals/python code).

I used web.eval() in the editor for now. Here's the implementation for reference: https://github.com/ankitects/anki/pull/4029/files/616556723837d2023592d4368b71a1f8584b4ff5..9278da2c85ce33d7a48b68cc23717555e57a20e8

abdnh avatar Oct 14 '25 22:10 abdnh

Can we please not break add-ons. They work and are needed. Same applies for bloating without measurable gain.

SKRVTN avatar Oct 15 '25 04:10 SKRVTN

Can we please not break add-ons

Addons aren't broken on purpose. It's just a byproduct of change.

Same applies for bloating without measurable gain.

There is no bloating without measureable gain. As I described above, moving away from qt and towards web implementatons like svelte has a lot of major advatages.

GithubAnon0000 avatar Oct 15 '25 09:10 GithubAnon0000

One way to minimize the damage would be to include a comprehensive manual of how the addon developers can make their addons compatible. Preferably, comprehensive enough to just give it to an AI agent so that it rewrites the code. Of course, this won't help the unsupported (but perfectly working and popular) addons.

It's important to keep in mind that the value of Anki comes from the combination of Anki itself and the addons.

ganqqwerty avatar Oct 15 '25 11:10 ganqqwerty

Sorry to keep you waiting Luc. I’ve struggled to find a big block of time where I could sit down and concentrate on this, and have been a bit overwhelmed with everything on my plate at the moment.

I'm not sure how faithful to the original html I need to be. If I should use the old table methods, for addon support reasons perhaps, let me know.

We're breaking add-on compatibility in this new path anyway, so I think we should pick whatever makes sense here. When the tables were originally introduced, CSS was considerably less advanced than it is now.

Currently the

I’d recommend we stick with just allow-scripts for now, as it’s much easier to start small and optionally allow more in the future (e.g. forms, modals), than it is to provide them and then later try to take them away.

Sandboxing is just one part of the puzzle - we may want to use CSP as well, like we do for the editor. It should allow us to ensure network requests made by cards can’t connect to our API endpoints, even without credentials due to opaque origin. It may even be worth thinking about a mode/option where remote images/scripts/etc are disallowed, forcing everything to go via the local media server. We couldn’t offer that mode exclusively without breaking a lot, but it might be a sensible default at some point in the future to reduce the chances of data exfiltration. Not something we need to solve right now anyway.

The opaque origin is going to means cookies/localStorage/etc all don’t work. I imagine we have two options here:

  • Expose a persistence API over postMessage, and optionally monkey-patch things like localStorage for some degree of backwards compatibility. This would present a unified interface across platforms, and could store the data in the collection config so that it gets synced around.
  • Serving up the card content on a different port and using allow-same-origin. I’m no web security expert, but my understanding is the separate port should be sufficient to prevent access to our API port (provided CORS/CSP/etc set up correctly). It would mean cookies set by the outer page are still sent, so we’d need to be sure we don’t do that.

(earlier comment) we probably want to allow the iframe access to an endpoint that can fetch arbitrary files from the media folder.

Sorry, what I meant by that is we expose, say, http://localhost/media/[filename], so that the inner frame can read images and files like CSS directly, instead of us feeding them through postMessage. We’d set the base URL on the document, so references to things like “foo.jpg” would map to that URL prefix - just like the current reviewer.

Will this include converting all the card template rendering into rust? e.g. the aforementioned av_refs_to_play_icons is python code. (I think the answer will have to be yes. I'm presuming that this will be difficult with most of the sound/mpv code being part of the python code?)

The current reviewer’s rendering code renders what it can in Rust, but when it encounters unknown filter references, it leaves them alone and lets Python handle that. We should be able to maintain compatibility with that for now by keeping Python as an intermediate step: eg nextCardData() speaks to the Python web server, which calls into Rust to get the (partially) rendered template, applies add-on-defined filters, and then passes it back to the frontend. The Rust API, instead of returning plain strings, would return data like RenderExistingCard does.

I've implemented a nextCardData function. The reviewer's basic functionality is usable in the browser now:

I haven’t had a chance to fully read through the code yet, but that looks like what I was thinking of!

I was wondering how it would be best to handle external changes to the queue/refreshing the displayed card when needed.

The Python/Kotlin/Swift layers currently take care of tracking changes, so the simplest/most pragmatic solution for now is probably to have the Python code inject messages into the outer frame when changes are received (e.g. web.eval(“anki.changeReceived(…)”), like you were thinking of. We’ll presumably want to build a similar change manager into the TypeScript layer, which could potentially use addEventListener. In the future, it should be possible to switch from the JS injection into polling or web socket push, without having to change how TS consumers process the change messages.

Also, hello Dae, from Australia! Fellow Aussie here

Hi! 👋 Thank you for contributing to the discussion :-)

Realistically, there's no need to use Svelte in Anki.  I'm not a fan of Python. Qt is written in C++, and Qt handles widget drawing, which is why it's fast.

This dead horse again. 🙄 We’ve discussed this multiple times, and you’re not adding anything new. Anki is open source. If you want a Python-only or C++ only client that only focuses on the FOSS ecosystem, there’s nothing stopping you from building it yourself. Please direct your enthusiasm into productive avenues like that, instead of derailing technical discussions repeating the same comments over and over.

One way to minimize the damage would be to include a comprehensive manual of how the addon developers can make their addons compatible.

We’re far from that stage yet. The plan is to maintain the old codepath and allow switching between them, and we’ll want to see the new path’s issues ironed out before we even consider changing the default / retiring the old path at some point down the road.

dae avatar Oct 16 '25 11:10 dae

@dae Hi! 👋

It's been a while since I posted. Most of the time I'm fine because everything works, but there's a small subset of changes that inevitably keeps resurrecting the dead horse. And for some reason they often involve Svelte. In my experience, when changes break essential add‑ons, 99% of the time it's due to GUI changes. So why not leave the GUI alone and focus on the backend instead?

You know my position and it's unfortunate you haven't changed your mind. Right now the reviewer works fine and doesn't need to be changed. Putting that aside, what do you say to people whose setups will be broken?

My main concern is the Flexible Grading add‑on. I use it daily and can't live without it. If you or Luc can open a pull request to fix Flexible Grading, there's no problem. But if this is left to others, there's a real chance no one will figure out the fix, and that's a serious problem. I hope we can reach a resolution before this PR is merged. The same applies to #4029, most likely.

tatsumoto-ren avatar Oct 16 '25 14:10 tatsumoto-ren

let the things just WORK if they work, please. anki have a number of not-so-bad but problems in various backend stuff. but each time some cOoL GuI cHaNgEs break addons, user experience, optimization and create a plethora of other problems and all the shit.

please.

asakura42 avatar Oct 16 '25 15:10 asakura42

I'd wager that this will break @glutanimate's Speed Focus Mode addon as well, since that one also adds a button to the reviewer. With over 300 ratings, this would not be insignificant.

drehak avatar Oct 16 '25 15:10 drehak