Commit Graph

2366 Commits

Author SHA1 Message Date
Abdo
abd531b080 Ensure editor field's focus events are handled in order 2026-07-27 23:12:56 +03:00
llama
e10ce1518e fix(io): text label escaping (#5206)
## Linked issue (required)

Fixes #5204

## Summary / motivation (required)

This pr fixes incorrect IO text label escaping logic

## Steps to reproduce (required, use N/A if not applicable)

1. In the IO editor, make a text label with `🔤`
2. Reload the note and observe that its gone

## How to test (required)

`./check` and IO notes with text labels like `🔤`, `\:a\b\:`, `::\::`
etc.

### 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

Please see commit messages

## Scope

- [x] This PR is focused on one change (no unrelated edits).
2026-07-25 15:25:58 +03:00
Brayan Oliveira
00dcfc3e8b feat: lazy-load MathJax on Reviewer (#5171)
## Linked issue (required)

* Closes #5172

## Summary / motivation (required)

Loading MathJax makes the reviewer initialization slower, which is
relevant for people who don't use MathJax and slower devices (such as
phones)

## Steps to reproduce (required, use N/A if not applicable)

N/A

## How to test (required)

1. Opened a deck with a card with no MathJax
2. In developer tools' console,, check whether `MathJax` is not defined
3. Answer some cards until a card with MathJax appears -> Check if it is
correctly loaded

### 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 -->

## Before / after behavior (optional)

<!-- For bugfixes: behavior before vs after. For other types: N/A or a
short note. -->

## Risk / compatibility / migration (optional)

<!-- Breaking changes, rollout notes, or N/A for small / low-risk PRs
-->

## UI evidence (required for visual changes; otherwise N/A)

N/A

## Scope

- [X] This PR is focused on one change (no unrelated edits).
2026-07-24 15:23:45 +08:00
Timothy (Tim) Lee
e8d368ca23 fix: image occlusion masks lost when adding notes (#5155)
## 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>
2026-07-21 12:32:50 +08:00
llama
8e273f9af4 fix: broken tags editor in legacy editor (#5151)
Closes #5152

> it seems that tags are not saved?
> https://forums.ankiweb.net/t/anki-26-08-beta-1/70398/2?u=llama

This pr restores the codepath for saving tags that was mistakenly
removed in the new editor pr
2026-07-15 15:32:31 +03:00
Luc Mcgrady
db98bb675a feat: Experimental options in preferences (#5057)
<!--
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).
2026-07-13 16:16:42 +03:00
Pierre
1c6175daa4 fix: card info not updating on repeat (#5115)
## Linked issue (required)

Fixes #5114 

## Summary / motivation (required)

This fixes an issue I've noticed where if a card repeats, the card info
does not update. This was not optimal, because card info should provide
the most up-to-date info about a card possible, and the previous logic
was not evident to users. This issue could be triggered by answering
"Again" at the end of a study session, at which point a card would
repeat, but the card info would remain the same. Other methods of
triggering included, but were not limited to learning only 1 card at a
time.

## Steps to reproduce (required, use N/A if not applicable)

1. Open card info 
2. Answering "Again" at the end of a study session, at which point a
card would repeat
3. The card info would remain the same, contrary to desire

## How to test (required)

Manual tests

### 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. (N/A)

## Before / after behavior (optional)

Before: On direct card repeats, card info pane would not update.
After: On direct card repeats, card info does repeat

## UI evidence (required for visual changes; otherwise N/A)


https://github.com/user-attachments/assets/bf1409cb-fb27-4c50-9ca2-68c0cc61cece


https://github.com/user-attachments/assets/810dfa53-cee8-4d6c-b22f-3b6daf025cda

## Scope

- [X] This PR is focused on one change (no unrelated edits).
2026-07-13 06:04:46 +08:00
Yuuki
a053f6a3ca Fix note editor jumps html focus (#4687)
## Linked issue (required)
Closes #4234 
<!-- Fixes #123 / Closes #123 / Refs #123 -->

## Summary / motivation (required)
When the HTML editor (</>) is open and focused in the browser's note
editor, switching to a different note and then scrolling and then
clicking (anywhere) causes the view to jump back to the HTML editor's
position. This happens because the `refocus()` triggers both the
browser's native scroll to focused element and CodeMirror's internal
scroll to cursor behavior.

The fix prevents scrolling during refocus in two places:

`PlainTextInput.svelte`: use focus({ preventScroll: true }) instead of
editor.focus() in refocus() to prevent the browser from scrolling the
focused element into view
`code-mirror.ts`: pass { scroll: false } to setSelections() in the focus
handler to prevent CodeMirror from internally scrolling to the restored
cursor position.
<!-- What this PR does and why. For larger changes, add enough context
for reviewers. -->

## Steps to reproduce (required, use N/A if not applicable)

<!-- Steps to reproduce: how to trigger the bug in the broken state (the
"before").
 - Mainly for bugfixes;
    - For bugs: numbered steps before the fix. For non-bugs: write N/A.
 - use N/A for features, refactors, docs, chore, etc.
-->
(Following the original issue instructions)
In the browser:

1. Open HTML editor for any field
2. Click inside, putting focus in the HTML editor input
3. Select a different note
4. Scroll to a different part in the note editor and click anywhere
The view jumps to the position of the HTML editor where the focus was
previously

## How to test (required)
Follow the original issue related to this pull request. There is a video
showing the problem.

<!--- How to test: how you verified the change (checks, unit tests,
manual steps, edge cases — the "after" or general validation). --->

### Checklist (minimum)

- [x] I ran `./ninja check` or an equivalent relevant check locally.

### Details

<!-- Commands, manual steps, edge cases, and what you observed -->


<!-- For bugfixes: behavior before vs after. For other types: N/A or a
short note. -->

## Risk / compatibility / migration (optional)
Changes are scoped to two lines in two files. Only the refocus() code
path is affected. User's focus behavior is unchanged.
<!-- Breaking changes, rollout notes, or N/A for small / low-risk PRs
-->

## Scope

- [x] This PR is focused on one change (no unrelated edits).
2026-07-10 08:42:45 +08:00
Abdo
62efee43f0 fix: Disallow Qt translation strings in Rust/TS code (#5127)
## 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.
2026-07-08 21:21:33 +03:00
Abdo
643187a050 Shift editor control to TypeScript (#4029)
Closes https://github.com/ankitects/anki/issues/3830

---------

Co-authored-by: Fernando Lins <fernandolins@users.noreply.github.com>
2026-07-03 21:56:43 +03:00
Luc Mcgrady
3650ad8396 fix: Dont close deck config screen when optimizing all presets (#4981)
<!--
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 -->

## Summary / motivation (required)
closes https://github.com/ankitects/anki/issues/4978

<!-- What this PR does and why. For larger changes, add enough context
for reviewers. -->

## Steps to reproduce (required, use N/A if not applicable)

<!-- Steps to reproduce: how to trigger the bug in the broken state (the
"before").
 - Mainly for bugfixes;
    - For bugs: numbered steps before the fix. For non-bugs: write N/A.
 - use N/A for features, refactors, docs, chore, etc.
-->

1. press "optimize all presets"
2. the window will save the current settings and cloze after it is done.

## How to test (required)

<!--- How to test: how you verified the change (checks, unit tests,
manual steps, edge cases — the "after" or general validation). --->
after pressing optimize all presets, you should be prompted if you want
to save. After confirming you want to save, the window will remain open
when the process completes.

### 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 -->

## Before / after behavior (optional)

<!-- For bugfixes: behavior before vs after. For other types: N/A or a
short note. -->

## Risk / compatibility / migration (optional)

<!-- Breaking changes, rollout notes, or N/A for small / low-risk PRs
-->

## UI evidence (required for visual changes; otherwise N/A)

<!-- Screenshot or short video -->

## Scope

- [X] This PR is focused on one change (no unrelated edits).
2026-06-25 16:42:39 +03:00
Luc Mcgrady
9a63b95230 Fix: text input refocues when list item shortcuts are pressed (#5007)
<!--
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 -->
fixes #5006

## Steps to reproduce (required, use N/A if not applicable)

<!-- Steps to reproduce: how to trigger the bug in the broken state (the
"before").
 - Mainly for bugfixes;
    - For bugs: numbered steps before the fix. For non-bugs: write N/A.
 - use N/A for features, refactors, docs, chore, etc.
-->

> I. Undo duplication
> 
> (1) Write into the editor “line”.
> 
> (2) In the non html editor, select this line, then press the
unordered/ordered list button in the tool bar (or use keyword shortcut);
the html field of the editor becomes
> 
> ```
> <ol><li>line</li></ol>
> 
> ```
> 
> (3) Press the undo shortcut (command + Z on Mac)
> 
> Result: the list is still there, but its content is duplicated after
the list; the html field reads:
> 
> ```
> <ol><li>line</li></ol>line
> 
> ```
> 
> Remark: the same bug occurs when we begin with multiple lines instead
of just one.

## How to test (required)

<!--- How to test: how you verified the change (checks, unit tests,
manual steps, edge cases — the "after" or general validation). --->
Follow the reproduction steps. Undo should remove the text in the order
that it appeared.

### 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.

## Risk / compatibility / migration (optional)

<!-- Breaking changes, rollout notes, or N/A for small / low-risk PRs
-->

Undoes some of the behaviour from #3483. I couldn't reproduce the bug he
mentioned with this patch but I'm not entirely sure this doesn't re
introduce https://github.com/ankitects/anki/issues/3338 as a bug.

## Scope

- [X] This PR is focused on one change (no unrelated edits).
2026-06-22 18:30:13 +03:00
Arthur Milchior
b33f1d72e6 Fix: Correct "1 tags" to "1 tag" (#4967)
The string was either "Tags" if there is no tags, or "n Tags" if there
were a positive number of tags.

This ensure that for 1 tag, this does uses singular.

You can see the difference by opening the "add note" or the browser and
changing the number of tags.

Fixes: #4975.

---------

Co-authored-by: Abdo <abdo@abdnh.net>
2026-06-19 17:42:23 +03:00
Lee Doughty
d76bd4033b test: cover attribute and tag stripping in HTML filter (#4965)
## Linked issue

Closes #4929

## Summary / motivation

`ts/html-filter/element.ts` is responsible for sanitizing HTML on paste
in the note editor, so it's security-sensitive code. About 8% of it
wasn't covered by tests, including the branches that strip attributes
and tags. This PR adds tests to get it to full coverage (statements,
branches, functions, and lines) so we don't accidentally break the
filtering later. There are no changes to the actual code, just tests.

## Steps to reproduce

N/A - tests only.

## How to test

### 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
behaviour changed.

### Details

Ran `just check` (the full lint and test build) and everything passed.

For coverage specifically, I scoped vitest to the target file:

`npx vitest run html-filter/index.test.ts --coverage
--coverage.include='html-filter/element.ts'`

That reports element.ts at 100% for statements, branches, functions, and
lines.

New tests cover:
- event handler attributes (`onclick`/`onerror`) stripped from allowed
elements
- `<script>` removal, including empty and nested cases
- unknown/disallowed tags dropped (empty) or unwrapped (with content)
- `<span>` styling honouring night mode (the previously uncovered
branch)

## Before / after behavior

N/A - no behaviour change, tests document existing behaviour.

## UI evidence

N/A

## Scope

- [x] This PR is focused on one change (no unrelated edits).

**Note for reviewers:** the originating issue listed "verify `<p>` →
`<div>` conversion" as a criterion, but `element.ts` treats `<p>` as an
allowed tag (`P: allowNone`) and preserves it, so there is no `p`→`div`
conversion in this component. The tests document the actual behaviour
(`<p>` kept, attributes stripped). Happy to adjust if a conversion was
intended elsewhere.

---------

Co-authored-by: Fernando Lins <1887601+fernandolins@users.noreply.github.com>
2026-06-08 14:08:49 -03:00
Abdo
76c0407ccc fix: Repaint UI after answer reveal (#4970)
## Linked issue

Closes #4969

## Summary

Add a [QWidget.update()](https://doc.qt.io/qt-6/qwidget.html#update)
call for the main webview after the answer is revealed and images are
loaded. This should reduce UI glitches, especially on cards with images.

## How to test

It's hard to test and reproduce this, because its frequency varies
between operating systems and graphics drivers.
I've seen improvements on my collection on Windows with the default
video driver (Direct3D) while doing my daily reviews today with this
change and I'll continue testing over the week.
2026-06-08 17:51:15 +03:00
Fernando Lins
110cb8f3b1 fix(sass): replace global map-get/map-merge with sass:map namespace in _functions.scss (#4955)
## 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"
/>
2026-06-05 09:25:14 -03:00
Fernando Lins
067d04f4ab chore(sass): remove unused Bootstrap imports from _button-mixins.scss (#4953)
<!--
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"
/>
2026-06-05 09:24:36 -03:00
Fernando Lins
da64f03307 chore(deps): consolidated security updates (Dependabot batch) (#4934)
## Summary

Consolidates 8 open Dependabot PRs into a single security-focused batch.

### Addressed advisories

- GHSA: tar (Cargo)
[#296](https://github.com/ankitects/anki/security/dependabot/296)
- @tootallnate/once
[#295](https://github.com/ankitects/anki/security/dependabot/295)
- ws [#293](https://github.com/ankitects/anki/security/dependabot/293)
- openssl
[#292](https://github.com/ankitects/anki/security/dependabot/292)
- idna [#291](https://github.com/ankitects/anki/security/dependabot/291)
- devalue
[#287](https://github.com/ankitects/anki/security/dependabot/287)/[#239](https://github.com/ankitects/anki/security/dependabot/239)/[#217](https://github.com/ankitects/anki/security/dependabot/217)/[#216](https://github.com/ankitects/anki/security/dependabot/216)
- postcss
[#275](https://github.com/ankitects/anki/security/dependabot/275)
- svelte
[#286](https://github.com/ankitects/anki/security/dependabot/286)/[#288](https://github.com/ankitects/anki/security/dependabot/288)/[#289](https://github.com/ankitects/anki/security/dependabot/289)
- lodash-es
[#258](https://github.com/ankitects/anki/security/dependabot/258)/[#259](https://github.com/ankitects/anki/security/dependabot/259)
- @sveltejs/kit
[#294](https://github.com/ankitects/anki/security/dependabot/294)

### Sources

Merged from PRs: #4914, #4887, #4867, #4866, #4865, #4846, #4744, #4892.

### Not addressed — rand (Cargo)
[#268](https://github.com/ankitects/anki/security/dependabot/268)

PR #4741 (rand 0.9.4 → 0.10.1) was excluded because `fsrs 5.2.0` still
depends on `rand 0.9.4`. The rand 0.10 API changes (`Rng` → `RngExt`)
cause a compile error at the `PostSchedulingFn` boundary. This will be
unblocked when fsrs is upgraded.

### Not addressed (transitive — follow-up)

- urllib3
[#284](https://github.com/ankitects/anki/security/dependabot/284)/[#283](https://github.com/ankitects/anki/security/dependabot/283)
- GitPython
[#282](https://github.com/ankitects/anki/security/dependabot/282)
- ip-address
[#276](https://github.com/ankitects/anki/security/dependabot/276)
- pytest
[#266](https://github.com/ankitects/anki/security/dependabot/266)
- Pygments
[#256](https://github.com/ankitects/anki/security/dependabot/256)
- brace-expansion
[#255](https://github.com/ankitects/anki/security/dependabot/255)/[#158](https://github.com/ankitects/anki/security/dependabot/158)
- picomatch
[#253](https://github.com/ankitects/anki/security/dependabot/253)/[#252](https://github.com/ankitects/anki/security/dependabot/252)
- tar (npm)
[#238](https://github.com/ankitects/anki/security/dependabot/238)/[#235](https://github.com/ankitects/anki/security/dependabot/235)/[#209](https://github.com/ankitects/anki/security/dependabot/209)
- immutable
[#231](https://github.com/ankitects/anki/security/dependabot/231)
- minimatch
[#227](https://github.com/ankitects/anki/security/dependabot/227)/[#226](https://github.com/ankitects/anki/security/dependabot/226)/[#221](https://github.com/ankitects/anki/security/dependabot/221)
- fabric
[#211](https://github.com/ankitects/anki/security/dependabot/211)

These need manual `yarn.lock` resolutions / `uv.lock` overrides.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 15:10:39 -03:00
Fernando Lins
a140d39329 chore(e2e): add Playwright end-to-end test infrastructure (#4864)
## Linked issue

Closes #4863

## Summary / motivation

Adds Playwright as the e2e test framework so contributors can write
browser-based tests against a real headless Anki instance. There was no
automated way to exercise mediasrv pages, SvelteKit routes, or the
`/_anki/` RPC surface from a browser, this PR establishes that harness.

Key pieces:
- `qt/tests/launch_anki_for_e2e.py` — spawns a throwaway Anki instance
(temp `ANKI_BASE`, `QT_QPA_PLATFORM=offscreen`). Pre-seeds `prefs21.db`
so Anki skips the language picker and profile chooser and goes straight
  to serving mediasrv.
- `playwright.config.ts` — points `webServer` at the launcher; polls
  `/favicon.ico` as the readiness probe.
- `ts/tests/e2e/` — `fixtures.ts` base and a sanity spec that verifies
  mediasrv is reachable and a SvelteKit page hydrates.
- `justfile` — `just test-e2e` recipe; Chromium installed to
  `out/playwright-browsers/`.
- CI — e2e step in `check-linux`; failed-run artifacts uploaded for 7
days.
- `docs/e2e-testing.md` — contributor guide covering setup, managed vs
  reuse-server modes, and writing new tests.

## How to test

Build the project once, then run the e2e suite in managed mode (no
separate `./run` needed — the launcher is started automatically):

```shell
just build
just test-e2e
```

## Before / after behavior (optional)
Before: no browser-level test harness existed.
After: `just test-e2e` drives a real headless Anki instance via
Playwright.

## Risk / compatibility / migration
No production code changed. New dev-only files and CI step only.
Chromium is installed to `out/playwright-browsers/` (gitignored) and
does not affect the regular build.

---------

Co-authored-by: Abdo <abdo@abdnh.net>
2026-05-22 15:59:42 -03:00
Fernando Lins
f9a5b36c82 feat: add TS test coverage (#4843)
## Linked issue

Closes #4840

## Summary / motivation

Adds Vitest V8 coverage for TypeScript/Svelte tests via
`@vitest/coverage-v8`.
Introduces `just test-ts --coverage` and `just test-ts --coverage
--html`,
and wires TypeScript into the `just test --coverage` umbrella —
completing
coverage support across all three stacks (Python, Rust, TypeScript).

The threshold is set to 5% — intentionally low because the Vitest test
count is small relative to the TypeScript/Svelte source surface. It is
meant to be raised as more tests are added.

## How to test

```sh
# Existing behavior unchanged
just test-ts

# Terminal summary + enforces 5% line coverage threshold
just test-ts --coverage

# Terminal summary + HTML report under out/coverage/typescript/
just test-ts --coverage --html

# Full umbrella — all three stacks
just test --coverage
just test --coverage --html
```

### Checklist

- [x] I ran `./ninja check` or an equivalent relevant check locally.

### Details

- `@vitest/coverage-v8` pinned at `3.2.4` in `package.json`.
- Reports are written to `out/coverage/typescript/` via
`--coverage.reportsDirectory=../out/coverage/typescript` (relative to
the `ts/` working directory where vitest runs).
- V8 provider is preferred over Istanbul: faster and requires no Babel
transform for TypeScript projects.
- Coverage measures only code reachable through Vitest's module graph —
Svelte component rendering is not covered.
- The `yarn` justfile variable is added for platform-aware yarn
invocation (Windows vs Unix).

## Before / after behavior

Before: no `just test-ts`, no TypeScript coverage.
After: `just test-ts` runs Vitest via ninja; `just test-ts --coverage`
runs with V8 instrumentation.
`just test --coverage` now spans all three stacks.

---------

Co-authored-by: Abdo <abdo@abdnh.net>
2026-05-18 11:54:16 -03:00
Luc Mcgrady
0900370fc4 fix: add fallback value for startMemorized (#4796)
<!--
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 -->
fixes https://github.com/ankitects/anki/issues/4795

## Summary / motivation (required)

An error appeared in the console of the deck options. with this change
it doesn't.
<!-- What this PR does and why. For larger changes, add enough context
for reviewers. -->

## Steps to reproduce (required, use N/A if not applicable)

<!-- Steps to reproduce: how to trigger the bug in the broken state (the
"before").
 - Mainly for bugfixes;
    - For bugs: numbered steps before the fix. For non-bugs: write N/A.
 - use N/A for features, refactors, docs, chore, etc.
-->

1. open the deck options
2. check the console either by using the Anki command line or by using
[this addon](https://ankiweb.net/shared/info/31746032)

## How to test (required)

<!--- How to test: how you verified the change (checks, unit tests,
manual steps, edge cases — the "after" or general validation). --->
The console should be clear on the deck options page

### 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 -->

## Before / after behavior (optional)

<!-- For bugfixes: behavior before vs after. For other types: N/A or a
short note. -->

## Risk / compatibility / migration (optional)

<!-- Breaking changes, rollout notes, or N/A for small / low-risk PRs
-->

## UI evidence (required for visual changes; otherwise N/A)

<!-- Screenshot or short video -->

## Scope

- [X] This PR is focused on one change (no unrelated edits).
2026-05-12 13:51:47 +03:00
Luc Mcgrady
d036c2ade4 feat: expose setParameterUnlockClickTimeoutMs (#4810)
<!--
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)

closes https://github.com/ankitects/anki/issues/4809
<!-- Fixes #123 / Closes #123 / Refs #123 -->

## Summary / motivation (required)

<!-- What this PR does and why. For larger changes, add enough context
for reviewers. -->
This exposes a JS api to set the speed at which you click to unlock the
FSRS parameters (See #4372)

## Steps to reproduce (required, use N/A if not applicable)

<!-- Steps to reproduce: how to trigger the bug in the broken state (the
"before").
 - Mainly for bugfixes;
    - For bugs: numbered steps before the fix. For non-bugs: write N/A.
 - use N/A for features, refactors, docs, chore, etc.
-->

N/A

## How to test (required)

<!--- How to test: how you verified the change (checks, unit tests,
manual steps, edge cases — the "after" or general validation). --->
Open the console in the deck options and run
`anki.setParameterUnlockClickTimeoutMs(0)`. If you set it to 0 then it
will be impossible to unlock. if you set it to something very high it
will be very easy.

### 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 -->

## Before / after behavior (optional)

<!-- For bugfixes: behavior before vs after. For other types: N/A or a
short note. -->

## Risk / compatibility / migration (optional)

<!-- Breaking changes, rollout notes, or N/A for small / low-risk PRs
-->

## UI evidence (required for visual changes; otherwise N/A)

<!-- Screenshot or short video -->

## Scope

- [X] This PR is focused on one change (no unrelated edits).
2026-05-12 13:18:42 +03:00
Fernando Lins
7d8dc01722 chore: add release-age controls for uv and Yarn dependencies (#4761)
## Linked issue

Issue related #4747 

## Summary/motivation 

Add `[tool.uv]` `exclude-newer` + `required-version`, regenerate
`uv.lock`; bump bundled uv binaries in `python.rs`; bump **Yarn** and
`npmMinimalAgeGate`; remove --no-config from pyenv uv sync --locked so
it matches the lockfile.

## How to test 

- [ ] ./ninja check
- [ ] yarn install
2026-04-30 13:38:42 -03:00
barisayyildiz
cfc6dc015e Fix FSRS desired retention not updating on deck preset change (#4469) (#4610)
## Fix FSRS desired retention not updating on deck preset change (#4469)
### Description
This PR fixes issue #4469, where the "Desired Retention" (DR) setting in
the deck options UI failed to update and could be accidentally
overwritten when switching between configuration presets.

**The Cause:**
Previously, an `onPresetChange` method was being bound in
`DeckOptionsPage.svelte` and passed down to `FsrsOptionsOuter.svelte`.
However, this method was not actually implemented within
`FsrsOptionsOuter.svelte`

**Changes Made:**
* Removed the unimplemented `onPresetChange` prop binding from
`FsrsOptionsOuter.svelte`.
* Implemented the `onPresetChange` logic directly inside
`FsrsOptions.svelte` so that the component internally handles updating
its own FSRS values (including Desired Retention) whenever a new preset
is selected.

**Testing:**
* Additionally, I have tested this change in the simulator component and
didn't observe any side effects.

I would like to get your feedback on this.

Fixes #4469
2026-04-29 17:56:01 +03:00
Brandon (Carl) Song
39ef88b396 Deck Options screen change (#4558)
I'm opening this pr to undo my changes from #4501 and setting the word
break property as well as looking for suggestions on the Easy Days
section, and any other additional sections that may need improvement
(such as placement of buttons or text).


https://github.com/user-attachments/assets/38e65f48-2399-4bd3-9800-375ef4947625

I've tried out adjusting the font size but discovered that a potential
issue could be due to the content's width of the box model being small
which causes the words to be divided. However, if I set the font size's
viewport width to 1vw, the headers fit but at a smaller font.
<img width="200" height="auto" alt="image"
src="https://github.com/user-attachments/assets/303206f2-9dea-4d94-9962-3a70f6f93071"
/>

Perhaps this pr can be closed if setting word break ultimately isn't a
viable option.
2026-04-21 14:17:15 +03:00
Luc Mcgrady
922953acf4 Feat/Different method for ratio graph in Help Me Decide. (#4452)
Forum link:

-
https://forums.ankiweb.net/t/replace-cmrr-with-workload-vs-dr-graph-more/63234/27?u=a_blokee

@snorpdorp notified me that the logic behind the ratio graph is flawed
as it doesn't properly account for the number of cards that are
memorized to begin with.

I have implemented his (and @1DWalker's) proposed solution

<img width="1267" height="657" alt="image"
src="https://github.com/user-attachments/assets/41eded79-8f76-41dd-b98f-0bf1a968ab9b"
/>

Now the graph is inverted so that the higher the value, the more
efficient it is, in @snorpdorp's own words:

> The previous equation was flawed in that it displayed the total
knowledge at the end of the simulation, not the relative gain in
knowledge from the amount of studying done during the simulation
timeframe. The new (correct) equation is (relative gain in knowledge as
a result of studying during the simulation) / (time spent studying
during simulation) -
https://discord.com/channels/368267295601983490/1443273721878937650/1444447278344437833
2026-04-03 19:59:18 +03:00
Moamen Abdelsattar
2bfb120758 Fix issue of Arabic image occlusion editor (#4594)
Fix issue #4182

Tested using AnkiWebView Inspector addon.

[inset-inline-start
compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/inset-inline-start#browser_compatibility)
[margin-inline-start
compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/margin-inline-start#browser_compatibility)

These CSS properties depend on logical direction of the document. No
need to make separate rules for RTL languages.

I will delay adding myself to CONTRIBUTORS until I get a notification
that the maintainers are willing to merge (I already have 2 open PRs,
delayed merging leads to conflicts in CONTRIBUTORS).
2026-04-03 16:59:20 +03:00
Yuuki
e412ea76d0 fix html editor collapses after toggling sticky fields (#4566)
Problem:
When the HTML editor is active on a field in the Add Cards dialog,
toggling the pin/sticky on any field and then adding a note (Ctrl+Enter)
causes all active HTML editor fields to collapse unexpectedly.
The editor saves the UI state inside a session object. When the sticky
field is toggled, `update_notetype_legacy()` is called and the
notetype's modTime is changed. When we add the new card we call the
`loadNote()` function that does the following:
1. `saveSession()` saves the current state with the modTime.
2. `setNotetypeMeta() `sees the new modTime and detect a mismatch,
deleting the entire session.
3. `setPlainTexts()` finds no saved sessions and returns the defaults.

Solution:
My solution revolves around changing how we check if the session is
changed or not.
I removed the modTime comparison in `setNotetypeMeta()`. The check not
only causes the fallback of the html after toggling the sticky but also
when changing fields names or font changes.
I added a field count check inside `setPlainTexts()` so now the saved
session is discarded only if the number of fields changes.

So now the modTime is used for the syncing in the backend but not for
the frontend UI.
I also thought about maybe a solution while still using the modTime as
updating the saved modTime in `saveSession()` instead of removing the
check, but this would cause a call from the backend instead of using the
cached value, adding complexity to the code.

Fix: #4468
2026-04-02 21:10:11 +03:00
Abdo
e2886017bd Ignore Fluent message errors in TypeScript (#4494)
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
2026-03-31 17:41:28 +03:00
Junia Mannervik
bcfcdfee3d Fixed overlapping ranges when all history is selected (#4430)
* Fixed overlapping ranges when all history is selected and days calcolation to prevent overlapping labels

* fix statistics calendar and review grapgh range labeling

* update graph so that  the tooltip day label uses integer day boundaries

* fix(import): support variable field count with notetype column (#4421)

* update graph so that  the tooltip day label uses integer day boundaries
and removed earlier fix which didnt work

* Fixed overlapping ranges when all history is selected and days calcolation to prevent overlapping labels

* fix statistics calendar and review graph range labeling and removed earlier changes

* reverted changes in time.ts

* reverted changes in calendar.ts

* oldest bin width modified to match other bins in reviews.ts

* modified youngest bar in reviews to include today aswell

* Fix reviews graph: correct Year range to 365 days

* fixed formating for ninja testing

* bin labels and totals correct for year, review

* fixed structure for ninja checks

* reverted changes made to unrelated files

* reviewed code and simplified functions in review.ts

* changed xMax, so today is included in month and 3months

* removed a whitespace

* reverted unneccessary changes to xMax

* simplified code in time.ts and review.rs and pass bin boundaries directly to time.ts

* fixed labels for bins, and made sure year is 365 days

* fixed even bin sizes (0-4 days and 360-364 days) for year review graph

* ninja check fix, let -> const and removed empty line

* added comments to review.ts

* bin merging logic removed, since totals are correctly calculated anyways

* corrections from ninja check

* Update ts/routes/graphs/reviews.ts

Co-authored-by: user1823 <92206575+user1823@users.noreply.github.com>

* Update ts/routes/graphs/reviews.ts

Co-authored-by: user1823 <92206575+user1823@users.noreply.github.com>

* adjusted last bin labeling with xmin

* suggested changes to minimize difference from main

* small review changes

---------

Co-authored-by: junlu592 <junlu592@liu.student.se>
Co-authored-by: user1823 <92206575+user1823@users.noreply.github.com>
2026-02-11 16:23:18 +03:00
user1823
db811351e5 Fix/Make descending R a pure R sort (#4400)
* Feat/Add pure R-based review sorting orders

https://forums.ankiweb.net/t/follow-up-make-descending-retrievability-a-pure-r-sort/67082

* cleanup

* fix

* Update test
2026-02-11 16:54:37 +07:00
Luc Mcgrady
c12fe7c507 Feat/Quantiles for FSRS percentage stats (#4425)
* Added: Difficulty zoom

* Fix: quantiles, Extract: PercentageRange component
(I accidentally staged the second commit before comiting the first)

* Add to retrievability graph

* Fix: Typo

* Fix: Half the percentiles

* Remove 50% option

* Fix: Add minimum bin width

* Revert "Remove 50% option"

This reverts commit 372d15578f.

* Remove magic number

* Fix: tickSize not updated

* unfix sqlite.rs

* remove console.log
2026-02-11 16:37:55 +07:00
Luc Mcgrady
bc646fadb4 Feat/Disable parameter editing until right clicked 3 times (#4372)
* Feat/Disable parameter editing until right clicked 3 times

* wording

* use left click

* Change placeholder to text

* use diabled agaain

* Add warning box

* FSRS

* Added: Timeout ms

* a11y

* Fix: typo

* Fix: Text selection issue
2026-02-11 15:12:30 +07:00
Damien Elmes
6fd0e68265 Bump devalue for latest CVE 2026-02-11 14:30:22 +07:00
Damien Elmes
ef76aad3e7 Do JS license output from top-level script 2026-02-11 14:26:53 +07:00
Luc Mcgrady
70c8ce4cb3 Fix rounded corners on FSRS Simulator modal (#4535)
Co-authored-by: Brett Schwartz <brettschwartz871@gmail.com>
2026-02-09 21:10:50 +03:00
SelfishPig
101908ca90 Swallow decoding errors when preloading (#4534)
* Swallow decoding errors when preloading

* Noop fix
2026-02-09 21:09:23 +03:00
Brandon (Carl) Song
b8884bac72 Changed overflow-x from hidden to auto for AnkiDroid (#4501)
* Enabled horizontal scrolling in deck options

* Modified CONTRIBUTORS file
2026-01-19 18:04:13 +03:00
Lee Doughty
0e1b62afd6 fix: apply nightMode class to editor in dark mode (#4473) 2026-01-17 16:21:48 +03:00
SelfishPig
8f2144534b Start decoding images immediately (#4471)
* Start decoding images immediately

* Start decoding images immediately (revised)

* New contributor
2025-12-20 20:27:13 +03:00
llama
26751f2207 fix(io): remove incorrect error toast shown when saving twice (#4458) 2025-12-15 19:09:51 +03:00
Lee Doughty
2d4de33cf3 Ensure trailing spaces are placed outside cloze deletions (#4446) 2025-12-01 21:54:46 +03:00
Lee Doughty
321e23acb2 Replace custom UUID function with crypto.randomUUID (#4384) 2025-10-13 09:37:30 +03:00
Dan
0986af4f81 fix(Occlusion): remember toggle translucency setting (#4366)
* remember translucency

Adding myself to contributors

Added braces surrounding if statement to comply with ninja checks

* ninja fix format
2025-09-30 20:18:26 +03:00
Hanni614
ee664b8fbb Stats - Retention rate help box links to Deck Options manual page #4198 (#4329)
* stats: link retention help box to "Desired retention" section

* docs: use HTTPS for fsrs and desiredRetention links

* Add Hanna Nilsén to CONTRIBUTORS

* Apply suggestion from @user1823

Co-authored-by: user1823 <92206575+user1823@users.noreply.github.com>

---------

Co-authored-by: Hanna Nilsén <hannanilsen@macbookair.students.netlogon.liu.se>
Co-authored-by: user1823 <92206575+user1823@users.noreply.github.com>
2025-09-27 14:46:11 +10:00
llama
c56e6e55ec feat: show saved custom colours as options in fill tool colour picker on mobile (#4348)
* add GetCustomColours rpc method

* save colours as rgb instead of argb

* show saved custom colours as possible options in colour picker

this is primarily for mobile clients, as qt currently ignores this

* save custom colours on colour picker change (for desktop)
2025-09-25 06:34:27 +03:00
llama
80cff16250 fix: persist colour picker's custom palette in profile (#4326)
* add SaveCustomColours rpc method

* restore custom colour palette on editor init

* save custom colour palette on colour picker open and input

there doesn't seem to be an event fired when the picker is
cancelled/closed, so it's still possible for work to be lost

* save colours on `change` instead of `input`

`input` is supposed to be fired on every adjustment to the picker
whereas `change` is only fired when the picker is accepted, but qt
seems to treat both as the latter, so this is currently a no-op

* Store colors in the collection

One minor tweak to the logic while I was there: an invalid color no
longer invalidates all the rest.

---------

Co-authored-by: Damien Elmes <gpg@ankiweb.net>
2025-09-17 16:46:53 +10:00
Abdo
75d9026be5 Decode images asynchronously (#4320) 2025-09-17 09:06:42 +03:00
Luc Mcgrady
dda730dfa2 Fix/Invalid memory states in simulator after parameters changed (#4317)
* Fix/Invalid memory states after optimization for simulator

* Update ts/routes/deck-options/FsrsOptions.svelte

* typo

* ./check
2025-09-04 14:35:00 +10:00
GithubAnon0000
c93e11f343 FIX gap above bury (#4298) 2025-09-01 15:09:42 +10:00