## 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"
/>
## 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>
## 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>
<!--
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).
<!--
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).
## 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
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.
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
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
* 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>
* 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
* 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)
* 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>