mirror of
https://github.com/ankitects/anki.git
synced 2026-07-21 21:26:52 -04:00
main
## Linked issue (required) Closes #4754 ## Summary / motivation (required) Image occlusion masks could silently go missing from a note at add time. Two separate problems caused this, both timing dependent, which is why the bug was rare and hard to reproduce. **Problem 1: the editor reset races the add (desktop).** Clicking Add on an image occlusion note saved the note, queued the actual add, and reset the mask editor all at once. The reset clears the canvas, and clearing the canvas writes an empty value into the Occlusions field of the same note object the add was about to read. Depending on which message arrived first: the add worked normally (the common case), the note was added with no masks and no warning, or the add was wrongly rejected with "no occlusion created" while the masks were plainly visible. Fix: reset the mask editor only after the add succeeds, matching the ordering the non-legacy TS editor page already uses. This also means a rejected add no longer wipes the masks you drew. **Problem 2: one field's save cancels another's.** All fields committed through a single shared save timer, and scheduling a save cancelled the timer's previous pending action. Mask edits commit the Occlusions field through that timer with a 600 ms debounce, so typing into Header or Back Extra within 600 ms of the last mask edit threw away the pending occlusions write. It was never retried, because later saves saw the store already held the same value and skipped notifying. Every mask drawn since the last committed write was then missing from the added note. Fix: give each field its own timer. This also surfaced a second bug in `ChangeTimer` itself: an action scheduled while `fireImmediately()` was awaiting a previous action was silently discarded when that await finished; fixed by taking the action before running it and letting a concurrent flush wait for the run in flight. ## Steps to reproduce (required, use N/A if not applicable) Problem 2 (reliably reproducible): 1. Add an Image Occlusion note, draw a mask, wait for it to auto-commit (~1s). 2. Draw a second mask. 3. Immediately (within ~600 ms) switch to the fields view and type a character into Header. 4. Wait a second, click Add. 5. Before this fix: the added note is missing the second mask. No warning is shown. Problem 1 (event-loop timing dependent, not reliably reproducible by hand): 1. Add an Image Occlusion note, paste an image, draw one or more masks. 2. Click Add. 3. Before this fix: rarely, the note is added with an empty Occlusions field and no warning, or Add is rejected with "no occlusion created" while masks are visible on screen. ## How to test (required) ### Checklist (minimum) - [x] I ran `./ninja check` or an equivalent relevant check locally. - [x] I added or updated tests when the change is non-trivial or behavior changed. ### Details - `ts/tests/e2e/io-mask-save-race.spec.ts` reproduces problem 2 with real mouse drawing and real keystrokes at the real 600 ms timing. Fails on current `main` (payload contains 1 cloze instead of 2, consistently across repeated runs) and passes with this change. - `ts/lib/editable/change-timer.test.ts` covers the discarded-action case in `ChangeTimer` and fails without the fix. - Full Playwright e2e suite (`just test-e2e`) passes repeatedly across fresh profiles. - `just fmt`, `just lint`, and the vitest suite are clean. - Problem 1 has no reliable automated test: the outcome depends on how the Qt event loop interleaves two webChannel messages and a background task. I verified the mechanism with a scripted headless Anki that drives the real `AddCards` flow and delivers the racing write inside the vulnerable window, and confirmed by inspection that after this change the empty write can no longer be issued while an add is in flight. ## Before / after behavior (optional) Before: masks could be silently dropped from a newly added note (problem 2, common), or the note could be added completely empty of masks / spuriously rejected (problem 1, rare). After: neither race is possible; a rejected add also no longer clears the masks you've drawn, so you can fix the note and retry instead of redrawing. ## Risk / compatibility / migration (optional) Low risk. Both fixes are ordering/scoping changes with no behavior change outside the affected race windows. No schema, API, or migration changes. ## UI evidence (required for visual changes; otherwise N/A) N/A (no visual/UI changes). ## Scope - [x] This PR is focused on one change (no unrelated edits). This PR contains two fixes rather than one. I considered splitting them, but both are root causes of the same reported bug (#4754) discovered during the same investigation, they are already isolated into two separate commits for review, and neither is a complete fix for the issue on its own. Happy to split into two PRs if you'd prefer. --------- Co-authored-by: llama <gh@siid.sh>
Anki
This repo contains the source code for the computer version of Anki.
About
Anki is a spaced repetition program. Please see the website to learn more.
Getting Started
Contributing
Want to contribute to Anki? Check out the Contribution Guidelines.
For more information on building and developing, please see Development.
Contributors
The following people have contributed to Anki: CONTRIBUTORS
Anki Betas
If you'd like to try development builds of Anki but don't feel comfortable building the code, please see Anki betas.
License
Anki's license: LICENSE
Description
Languages
Rust
39.2%
Python
25.4%
MDX
13.6%
TypeScript
10.1%
Svelte
9.9%
Other
1.4%