## 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>
<!--
Title (for the Pull Request title field at the top):
Use a short prefix so the change type is obvious. You do not need to
repeat it in the body below.
Examples:
- fix: — bugfix
- feat: — feature
- refactor: — internal change without user-facing feature
- docs: — documentation only
- chore: — tooling, CI, deps, build housekeeping
- test: — tests only
-->
## Linked issue (required)
<!-- Fixes#123 / Closes#123 / Refs #123 -->
closes#4871
## How to test (required)
Find "Labs" as a new option in the preferences
### Checklist (minimum)
- [X] I ran `./ninja check` or an equivalent relevant check locally.
- [ ] I added or updated tests when the change is non-trivial or
behavior changed.
### Details
<!-- Commands, manual steps, edge cases, and what you observed -->
I've implemented the "Labs" feature in a web-view as I presume we are
moving away from QT and
Currently these options have no function. Before we can add the
functionality we should try and come up with the pattern that we should
use to register new flags.
## UI evidence (required for visual changes; otherwise N/A)
<img width="746" height="736" alt="image"
src="https://github.com/user-attachments/assets/28c4fd28-a593-4599-b43e-c2c23881251e"
/>
<img width="746" height="736" alt="image"
src="https://github.com/user-attachments/assets/c1cd69f9-3d8d-4866-a784-030b4274b4e7"
/>
## Scope
- [X] This PR is focused on one change (no unrelated edits).
## Linked issue
Closes#5126
## Summary
This prevents translation strings from the
https://github.com/ankitects/anki-desktop-ftl to be used in Rust/TS code
as AnkiMobile does not pull this module.
## Steps to reproduce (before)
Qt strings such as `addons-no-updates-available` were allowed to be used
in Rust: #5122
## How to test (after)
Revert #5122 and confirm you get a compile error with this PR.
## Summary
Closes#4954.
`_functions.scss` already had `@use "sass:map"` at the top but was still
calling the deprecated global functions `map-get` and `map-merge`.
Replaced all occurrences with their namespaced equivalents `map.get` and
`map.merge`.
This eliminates the `[global-builtin]` deprecation warnings visible in
every Check (Linux) CI run, which will become hard errors in Dart Sass
3.0.
## Test plan
- [x] `just lint` passes
- [x] `just test-ts` passes with no deprecation warnings
## Before / after behavior (optional)
### at main branch
<img width="1325" height="906" alt="Image"
src="https://github.com/user-attachments/assets/6a35ac2d-418a-4ded-baf6-f959b06b8d48"
/>
### at this branch
<img width="1156" height="921" alt="image"
src="https://github.com/user-attachments/assets/276a15ce-b319-4ee5-9ae2-122cd4d4d45e"
/>
<!--
Title (for the Pull Request title field at the top):
Use a short prefix so the change type is obvious. You do not need to
repeat it in the body below.
Examples:
- fix: — bugfix
- feat: — feature
- refactor: — internal change without user-facing feature
- docs: — documentation only
- chore: — tooling, CI, deps, build housekeeping
- test: — tests only
-->
## Linked issue
Closes#4951
## Summary / motivation
Removes two dead `@import` statements from `_button-mixins.scss`.
Bootstrap's functions and variables were imported but never used, since
all mixins rely exclusively on CSS custom properties (`var(--)`) and
local mixin parameters.
This eliminates ~35 Dart Sass deprecation warnings (`[import]`,
`[color-functions]`) that appeared during `just test-ts --coverage`.
## Test plan
- [x] `just lint` passes
- [x] `just test-ts` passes with no deprecation warnings
## Before / after behavior (optional)
### current behavior (at main branch)
<img width="1424" height="979" alt="image"
src="https://github.com/user-attachments/assets/89fafb5e-e811-48ca-b7e0-dbae36f63d7a"
/>
### Expected behavior (at this branch)
<img width="1489" height="870" alt="image"
src="https://github.com/user-attachments/assets/3eca3296-d31b-4e02-ad5f-e9287e59ee3d"
/>
Closes#4490
The italian and Catalan translations for the
importing-note-updated-as-file-had-newer string contain a variable
reference (probably from an older version of the string). We can avoid
throwing an exception in such cases by passing the `errors` argument to
`FluentBundle.formatpattern()`:
https://projectfluent.org/fluent.js/classes/_fluent_bundle.FluentBundle.html#formatpattern
* add fill tool
* add fill tool logic
* open colour picker on fill tool activation
* refactor/add fill attr to io clozes
* fill masks in editor
* fill text and inactive masks in reviewer
* fix lint
* remove debug option
* Update TrueRetention.svelte adding description
* Update statistics.ftl to add additional info
* Swap TR with DR
* Change string to 'Is expected to'
* Add help modal to TR table
* Add tooltip slot to Graph.svelte (thanks @Luc-Mcgrady)
* Fix lint warning and failing test
* Remove unused code
* removedd on:mount to make eslint happy
* ADD back on:mount
* ADD back code needed for on:mount
* REMOVE openHelpModal() as I couldn't figure out how to make the title clickable
* attempt to ADD clickable title (BROKEN\!)
* Update ts/lib/components/TitledContainer.svelte
Co-authored-by: Luc Mcgrady <lucmcgrady@gmail.com>
* Update ts/routes/graphs/Graph.svelte
Co-authored-by: Luc Mcgrady <lucmcgrady@gmail.com>
* Update ts/routes/graphs/TrueRetention.svelte
Co-authored-by: Luc Mcgrady <lucmcgrady@gmail.com>
* ADD exported onTitleClick as @Luc-Mcgrady suggested
* REMOVE vite.config.ts file
---------
Co-authored-by: Luc Mcgrady <lucmcgrady@gmail.com>
* Revert "Sanitize field content in editor"
This reverts commit 1c156905f8.
* Use CSP to block inline JS content in editor
This blocks inline scripts, scripts in the media folder, and
handlers like onclick in the editor. This is nicer than the previous
solution - it doesn't make any permanent changes, and leaves other
content like SVGs alone. Thanks to Nil Admirari for the suggestion.
* Sanitize field content in editor
The editor already strips script tags from fields, but was allowing
through Javascript in things like onclick handlers. We block this now,
as the editor context has access to internal APIs that we don't want to
expose to untrusted third-party code.
* Require an auth token for API access
We were previously inspecting the referrer, but that is spoofable,
and doesn't guard against other processes on the machine.
To accomplish this, we use a request interceptor to automatically
add an auth token to webviews with the right context. Some related
changes were required:
- We avoid storing _page, which was leading to leaks & warning on exit
- At webview creation (or set_kind() invocation), we assign either
an authenticated or unauthenticated web profile.
- Some of our screens initialize the AnkiWebView when calling, e.g.,
aqt.forms.stats.Ui_Dialog(). They then immediately call .set_kind().
This reveals a race condition in our DOM handling code: the webview
initialization creates an empty page with the injected script, which
causes a domDone signal to be sent back. This signal arrives after
we've created another page with .set_kind(), causing our code to think
the DOM is ready when it's not. Then when we try to inject the dynamic
styling, we get an error, as the DOM is not ready yet. In the absence
of better solutions, I've added a hack to set_kind() to deal with this
for now.
* Provide AnkiWebPage init defaults for existing add-on callers
* Inject bridge script when profile set-up skipped
Some add-ons fully override AnkiWebPage.__init__ and thus depend on _setupBridge injecting the JS bridge script.
With this change we account for these cases, while giving add-ons the opportunity to look for solutions that do not require overriding AnkiWebPage.__init__ completely.
* Add some missed pages/endpoints (thanks to iamllama)
* Avoid sending API key for remote resources
Thanks to Abdo for the report
---------
Co-authored-by: Aristotelis P <201596065+aps-amboss@users.noreply.github.com>
* Improve natural unit conversion for a time b/w 360 to 365 days
Previously, 363 days would be converted to 12.1 months, which is quite confusing because
- a user would think that if the value is more than 12 months, why it isn't displayed in years
- the value is actually less than a year, which is counterintuitive as 12.1 m suggests a value more than a year.
* precise
* Update time.ts to match timespan.rs
* Add another test
* Use average duration of a month instead
* Update time.ts
* Update test_schedv3.py
* Update time.test.ts
* Add percentage to FSRS spinner
This commit add a percentage option in SpinBox and SpinBoxFloatRow, set to False
by default.
If it's true, a percent symbol is added at the end of the line before
the increase/decrease button.
While the value is represented as a percentage without decimal places,
the internal representation is not changed. Which mean that a
multiplier must used to compute the string value, indicate to the
input field the min, max and step, and when updating the result.
* Remove unsightly percentage sign, and update historical retention too
https://github.com/ankitects/anki/pull/3679#issuecomment-2579636981
---------
Co-authored-by: Damien Elmes <gpg@ankiweb.net>
* Tweak how the True Retention stats table displays numbers
- Always show fractional parts of numbers even if they are 0 (91.0% not 91%).
- Show "N/A" for percentages instead of 0% when there are 0 total reviews.
* Localise percentages correctly
* Update to stable Svelte 5 release
This causes a bunch of warnings to be omitted:
ts/routes/graphs/RangeBox.svelte:52:18
Warn: Properties of objects and arrays are not reactive unless in runes mode. Changes to this property will not cause the reactive statement to update (svelte)
break;
case RevlogRange.All:
$days = 0;
They are triggered on enum references, and it appears to be a bug.
May need to report it to Svelte?
* Deps update
* Silence spurious errors + fix a real one
https://github.com/sveltejs/svelte/issues/13811
* Fix temporary disappearance of a tag
https://forums.ankiweb.net/t/anki-24-10-beta/49989/47
* Fix incorrect behavior after tag deletion
* Fix tag addition/selection buttons becoming invisible
Once tags were selected, subsequent deselection or selection did not
mount 'TagAddButton' or 'TagsSelectedButton' components.
* Update to Svelte 5
* Fix `<tr> is invalid inside <table>`
* Update sveltekit-svg to match svelte version
Fixes deck options failing to load, and a bunch of warnings with
./yarn dev
* Fix graph tooltips
* Fix editor loading
* Fix MathJax editor not loading
* Formatting
* Fix new formatting errors
* Merge remote-tracking branch 'origin/main' into svelte5
* Remove slot inside EditorToolbar
I think this is just stray code left over from a refactor, but I'm
not 100% sure.
Fixes
Error: Object literal may only specify known properties, and 'children' does not exist in type '{ size: number; wrap: boolean; api?: Partial<EditorToolbarAPI> | undefined; }'. (ts)
<div class="note-editor">
<EditorToolbar {size} {wrap} api={toolbar}>
<slot slot="notetypeButtons" name="notetypeButtons" />
* Fix component typing error
* Comment out svelte/internal exports, so editor loads
* Fix image occlusions in editor
* Revert "Remove slot inside EditorToolbar"
This reverts commit b3095e07ac,
which prevented the Preview button from showing in the browser.
This will break our tests again.
* Update vite
* Disable routes/tmp for now
* Fix references issue in routes/tmp
* Update amd64 docker container to Debian 11
This bumps the minimum required glibc to 2.29, which is 2019
Ubuntu/Fedora, and 2021 Debian.
Also remove the unused download of ninja
* Update to latest dprint
Unblocked by the glibc upgrade