This is super cool! Would love to see how you hooked up Ruff and ty.
Just curious, why not use Pygame?
Scratch abstracts away a ton of stuff to allow the student to focus on logical building blocks that mirror the mental model one might have when writing a real program. I'm wondering if keeping a lot of those abstractions when transitioning to text programming is educationally useful?
For example, it might not be clear that @on_forever is really just a loop, etc. One thing I've noticed when teaching beginners is that when you introduce a library/framework at the same time as a language, they start to form a model of the language that often wrongly includes parts of the library.
This is why I think Pygame is so useful for education, it sits at just the right level of abstraction for learning. In Pygame, your game loop is just a loop, handling input is just conditions in your loop, etc.
Regarding rewriting the AST to avoid async/await, do you have some experience or evidence to suggest that these should be abstracted out? I can see an argument for both sides, so just wondering how exactly you arrived at that decision.
Also, I tried a program with an infinite loop and the UI became unresponsive and I had to close the page. This indicates to me it's running on the main browser thread. Kids (and sometimes senior engineers) write infinite loops occasionally, so I highly recommend executing the user's code in a worker to prevent the harsh experience of losing your work suddenly.
Pygame would be the perfect use case for this. It also supports running in the browser via https://pypi.org/project/pygbag/