Commit Graph
12546 Commits
Author SHA1 Message Date
Luc Mcgrady 29709c081d remove manager parameter from GetAddons initialiser 2026-09-30 18:14:35 +01:00
Luc Mcgrady 90697b3b17 fix: missing backwards compatibility 2026-09-30 17:15:35 +01:00
Luc Mcgrady 00011c56e8 open GetAddons seperately of AddonsDialog 2026-09-30 17:06:36 +01:00
Luc Mcgrady 531396368c _onAddons -> _on_addons 2026-09-28 21:31:04 +01:00
Luc Mcgrady b6f16c6255 reorder 2026-09-24 14:18:53 +01:00
Luc Mcgrady 15a7b0e360 reorder 2026-09-22 20:30:06 +01:00
Luc Mcgrady 9d8834fae8 feat: add "Get add-ons" button 2026-09-22 20:13:16 +01:00
wofiporiaandAbdo e6fefb2962 docs: improve manual translation instructions (#5519)
## Linked issue (required)

Fixes #5510

## Summary / motivation (required)

Expands the manual translation instructions as requested in #5510:

- `docs-site/translators/anki/manual.mdx` now contains detailed
instructions for translating the documentation: forking the repository,
working in a language subdirectory (existing or new), keeping Markdown
formatting intact while translating the MDX pages, registering
translated pages in `docs.json` (with an example following the shape of
the existing language entries), and submitting a PR. It also notes that
partial translations are welcome, that documentation changes are
licensed under CC BY-SA 4.0, and where to ask questions.
- The Translations section of `docs-site/README.md` now links to that
page instead of holding the details itself, reversing the previous
direction per the issue.
- The Translations sections of `docs-site/developers/contributing.mdx`
and the cog-generated `docs/contributing.md` briefly mention
documentation translations, which were previously only covered for
interface strings.

Also adds myself to the CONTRIBUTORS file in a separate commit, as
requested for first pull requests.

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

N/A

## How to test (required)

Documentation-only change. Verified that:

- All paths referenced in the new instructions exist in the repository
(e.g. `docs-site/ru/manual/`, `docs-site/manual/`, and the other
language subdirectories).
- The `docs.json` example matches the structure of the existing language
entries under `navigation.languages`.
- Both internal links resolve to pages registered in
`docs-site/docs.json` (`/translators/anki/manual`,
`/translators/anki/developers`).
- No MDX-special characters (`{`, `<`) appear outside fenced code blocks
in the new content, so `mintlify validate` should pass.
- `docs/contributing.md` stays in sync with
`docs-site/developers/contributing.mdx` under the transformation in
`docs/cogdocs.py`.

### Checklist (minimum)

- [ ] I ran `./ninja check` or an equivalent relevant check locally.
*Not applicable for a documentation-only change; the relevant check is
the Docs Site workflow (`mintlify validate` / `mintlify a11y`), which
runs in CI.*
- [ ] I added or updated tests when the change is non-trivial or
behavior changed.
*N/A - documentation-only changes are exempt from tests per
`contributing.md`.*

## Before / after behavior (optional)

N/A

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

N/A

## Scope

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

---------

Co-authored-by: Abdo <abdo@abdnh.net>
2026-09-22 12:06:56 +03:00
Fernando Lins f4c684a96e fix(ci): fetch PR base branch so SonarCloud diffs only changed files (#5659)
## Linked issue

Fixes #5657

## Summary / motivation

SonarCloud PR analysis derives the changed-file set from git and
requires the target branch to be present in the local repository. The
scan checks out only the fork's head history (`fetch-depth: 0` on the
head ref), so the base branch was never fetched. Without a base to diff
against, the scanner attributed every analysed file to the PR as new
code, e.g. 977 files on the single-file change in #5641
(`qt/aqt/preferences.py`), and recurring noise in `tools/`.

This adds a step that fetches the target branch from the trusted
upstream repository (`github.repository`, never the fork) and pins it
locally under the name passed as `sonar.pullrequest.base`, so new code
is measured against the real PR diff. It does not disable any rule or
exclude any file.

The isolation boundary is preserved: the upstream branch is read as data
only, nothing is built or executed, the trusted config and
no-fork-code-execution constraints are unchanged, and the Quality Gate
stays informative.

## Steps to reproduce

1. Open a fork PR that changes a single file (e.g. #5641, base `main`).
2. Let CI complete and the SonarCloud workflow run.
3. Observe SonarCloud reporting new-code issues across hundreds of
unchanged files (~977) instead of only the changed file.

## How to test
### Details

Reproduced the scanner's git-based new-code computation locally against
the exact commits from #5641:

- head = `1e7914a`
- base branch `main` fetched from upstream → `merge-base` = `bdeec0877`,
which matches the PR's `baseRefOid` reported by the GitHub API.
- `git diff --name-only <merge-base> <head>` → **only**
`qt/aqt/preferences.py`.

The new step also logs the analysed commit, base repo/branch, base
commit and merge-base, and fails closed if the base reference cannot be
resolved (no silent fallback to a missing base). YAML parse and the
cog-generated `docs/sonarcloud.md` sync were verified.

## Before / after behavior

- Before: fork/PR scan treats all indexed files as new code (977 files
for a
  one-file PR).
- After: only the files actually changed vs the upstream base are
considered
  new code.
2026-09-22 11:45:03 +03:00
Luc Mcgrady 1516af9e4f fix(installer): add version to root folder (#5662)
closes #5621 

This will make the root folders name the format

`anki-26.9.2-linux-x86_64.tar` instead of just `anki-linux`
2026-09-21 21:46:02 +01:00
Luc Mcgrady 029c80aed1 refactor: use col.fsrs_enabled in more places (#5651)
closes #5652

`col.fsrs_enabled` should be usable in any place where we have access to
the collection (And that doesn't involve building the queue or modifying
the config).

c.c. @user1823
2026-09-21 15:17:09 +01:00
Abdo f5a8f54a40 fix: Do not skip scope dialog in MSI installer (#5658)
Unfortunately the change in #5626 to skip the scope dialog in the MSI
installer depending on the detected installation does not work as
expected in some cases. To reproduce:
- Install 26.09 on all-users scope.
- Install 26.09.2 on all-users scope.
- Bump .version (26.09.3) and build a new installer with #5626
- Install 26.09.3 and notice the default installation path is
`C:\Users\%USERNAME%\AppData\Local\Programs\Anki`, despite there being
two all-users installations.

Reverting for now. We can revisit it for the next major release.
2026-09-21 16:57:52 +03:00
Luc Mcgrady 754ce3a25f fix: Card modified by stats screen (#5648)
fixes #5635

We could fix specifically the SM2 part of this bug with just the
"fsrs_enabled" part; but then if we encounter a card with the memory
state actually missing while reviewing I think we would get the same
error. So now any calculated memory state is only shown in the card info
and not writ to the database after being calculated.
2026-09-18 18:35:53 +03:00
Fernando Lins 4acb5329e6 fix(ci): track releases in the SonarCloud project version (#5634)
## What was happening

When I set up the Sonar PoC, I pinned `sonar.projectVersion` to `0.1`.
The New Code period is "previous version", so with the version never
changing, the baseline stayed frozen at that first analysis. Everything
merged since then kept accumulating as "new code" instead of rolling off
at each release, which is why the Quality Gate on `main` has been red
since August (e.g., coverage for "new code" is at 62%, below the 80%
minimum).

## The fix

Now the Sonar version follows the project version instead of being
pinned, so every release bump gives us a fresh baseline. Pulled from the
trusted default-branch checkout so a fork can't tamper with it.

## How it works from now on

PRs won't change: Sonar already checks only your diff, so the 80%
coverage gate applies just to the lines you touched, not the old debt.

For `main`: the baseline now resets on each release. Just note it moves
on the next version bump, not on this merge, so `main` stays red until
then, and after that "new code" is only what changed since the last
release.
2026-09-17 17:20:11 -03:00
Luc Mcgrady 910c92e90f fix: improve "Please restart to save changes" (#5641)
closes #5640 closes #5639
2026-09-17 19:53:36 +01:00
Luc Mcgrady c8b9417300 Force software rendering in safe mode (#5637)
Closes #5629

I decided to do this by moving safe mode to "app" rather than by moving
the rendering code to the main window
(2db70434ea1412b4513479236cd74464de986b6a)

If 2db70434ea1412b4513479236cd74464de986b6a is preferred then I'll
switch back to it
2026-09-17 20:04:10 +03:00
Abdo bdeec08770 docs: Fix misplaced/missing pages (#5607)
Quick fixes for some doc issues I noticed.
2026-09-17 17:48:28 +03:00
3678260e96 test: add tests for config.py and stdmodels.py (#5513)
## Linked issue

Closes #5389

## Summary

Adds `pylib/tests/test_config.py` and `pylib/tests/test_stdmodels.py`.

`test_config.py` covers get/set/remove, default values, bool/list/dict
round-trips, and the legacy `col.conf` dict interface (including
`WrappedList`/`WrappedDict`). `test_stdmodels.py` covers each stock
notetype (Basic, Basic+reversed, optional reversed, type-in, Cloze)
asserting correct field and template names, plus the legacy add
functions.

Coverage results: `config.py` 100%, `stdmodels.py` 100% (target ≥ 80%
each).

## How to test

```
just test-py --coverage
```

---------

Co-authored-by: Fernando Lins <1887601+fernandolins@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 06:44:31 -03:00
Abdo 6f7a3175d2 chore: Update Rust dependencies (#5608)
- Update chacha20 from a yanked version.
- Update rustls for https://rustsec.org/advisories/RUSTSEC-2026-0285
2026-09-16 23:59:14 +03:00
Abdo 360ea49897 chore: Update Python dependencies to match bundled dependencies in 26.05+ (#5625)
This updates locked Python dependencies to match the versions bundled in
26.05+ (due to https://github.com/ankitects/anki/issues/5592) to reduce
compatibility issues.
2026-09-16 23:56:43 +03:00
Abdo e485a591fe Fix detection of all-users MSI installations (#5626)
## Linked issue

Closes #5037

## Summary

The MSI installer fails to detect existing installations and results in
multiple versions being installed. This was fixed in #5038 but
apparently later template changes reintroduced the issue (Windows
Installer is full of footguns and is very fussy about ordering...).

## Steps to reproduce (before)

- Install 26.09 in all-users scope.
- Install 26.09.2 in all-users scope and confirm it results in two
entries in _Settings > Apps > Installed Apps_.

## How to test (after)

Repeat the same process. All-users scope should now by selected by
default if an existing installation is detected and replaces the
existing one correctly.
2026-09-16 23:55:50 +03:00
Abdo 83723722c0 Bump version to 26.09.2 (#5624) 2026-09-16 23:54:26 +03:00
Abdo 08b1e64fe2 fix: Prevent external URLs being displayable in internal webviews (#5615)
AnkiWebPage.acceptNavigationRequest() did not check the full host and
port before accepting navigation requests, which made it possible for
external URLs to be opened in a trusted webview with access to pycmd().

This also adds a port check to AuthInterceptor so the API key is not
sent to a localhost server on a different port.

_This fix is already included in 26.09.1_.
2026-09-16 14:19:42 +03:00
argrig666 b3c23522d1 Document Linux TTS setup (#5600)
Closes
2026-09-15 20:03:46 +03:00
Fernando Lins 07a833d323 test: cover tags/service.rs (#5606)
## Linked issue

Closes #5387

## Summary / motivation

Add tests for the `TagsService` protobuf layer, covering all service
endpoints, request conversions, persisted changes, and representative
error paths.

This completes the remaining tags service coverage requested in #5387.
The media service portion was previously covered by #5588.

## How to test
### Details

Ran:

- `just test-rust`
- `just test-rust --coverage --html`

All Rust tests pass. Production code in `rslib/src/tags/service.rs` has
80/80 lines covered (100%).

<img width="1143" height="24" alt="image"
src="https://github.com/user-attachments/assets/fc8dd1d7-4903-4528-a92d-f985d7fafe7f"
/>


## Before / after behavior (optional)

Before: `rslib/src/tags/service.rs` had no direct test coverage.

After: all `TagsService` endpoints are covered, including persisted
mutations, protobuf request handling, empty-ID behavior, tag-name
canonification, and explicit error variants.
2026-09-15 14:01:05 -03:00
Fernando Lins 91abbb9d73 test: add unit coverage for card_rendering service (#5587)
## Linked issue

Closes #5384

## Summary / motivation

`rslib/src/card_rendering/service.rs` had 0% test coverage, whereas the
issue asks for at least 70%.
This PR adds golden unit tests that drive the `CardRenderingService`
trait implementation directly through qualified trait syntax, so that
the service layer itself gets exercised (its proto conversion,
validation and mode/flag dispatch) rather than the inherent `Collection`
render methods it delegates to.

Coverage, grouped by the issue's areas:
- **Question/answer rendering**: `render_uncommitted_card` /
`render_existing_card` produce the expected nodes, css and `is_empty`.
- **Template filters**: a known filter is applied; an unknown filter is
ignored when `partial_render` is false and emitted as a replacement node
when true, including when preceded by literal text.
- **Edge cases**: empty fields report `is_empty`; cloze rendering; the
empty-card report for both a note kept alive and a note marked for
deletion.
- **Error handling**: missing template/note and unknown card id return
the concrete `InvalidInput` variant and message; invalid legacy template
bytes return `JsonError`, so that none of these paths panic.
- **Delegating methods**: av-tag/latex extraction, markdown
(with/without sanitize), IRI path round-trip, strip-html modes,
html-to-text, compare-answer, cloze-for-typing, and the legacy render
path.

No production code was changed, since this PR adds tests only.

## How to test

### Details

- `cargo test -p anki card_rendering::service` → 27 passed.
- `just test-rust` → full Rust suite green; `cargo fmt --check` and
`clippy` clean on the changed file.
- `just test-rust --coverage` → `service.rs` now at 100% function and
~99% line coverage; the only uncovered lines are test-only guard arms
(`_ => None` fallbacks and `other => panic!(...)` in error matches),
which execute only on test failure and are intentionally left uncovered
rather than padded.
- Note: the repository-wide `just check` also runs the web/Python
stacks; this change is Rust-test-only, so the relevant Rust checks above
are the ones that apply.
<img width="994" height="28" alt="image"
src="https://github.com/user-attachments/assets/67479d89-66bc-4a2e-bdbd-fe16a0da3114"
/>
2026-09-15 14:00:32 -03:00
Luc Mcgrady 7447845b1a fix: Add redirects for mdbook pages (#5601)
closes #5598

I tried to use a glob but it didn't like redirecting based on it ending
in .html 😢

```json
    {
      "source": "/:glob.html",
      "destination": "/manual/:glob"
    }
```

```json
    {
      "source": "/:glob*.html",
      "destination": "/manual/:glob*"
    }
```

Does not work. So I brute forced it.
2026-09-15 16:22:39 +01:00
llama 2f4e86aeae test: cover media/service.rs (#5588)
## Linked issue (required)

Ref #5387

## Summary / motivation (required)

Add tests to cover the MediaService protobuf layer:
<img width="792" height="17" alt="image"
src="https://github.com/user-attachments/assets/b2599c95-1f85-4518-b3c8-69c1e14517c4"
/>
2026-09-15 11:08:24 -03:00
Abdo 0cd9a4d04f Fix bundled Windows app trying to load lib subfolder (#5605)
## Linked issue

Closes #5603

## Summary

Briefcase recently started to adding the "lib" subfolder to PYTHONPATH
(https://github.com/beeware/briefcase-windows-VisualStudio-template/pull/105).
This unfortunately caused unexpected issues with users trying to upgrade
to 26.09 who happened to have leftover lib/anki/_rsbridge.pyd libraries
(probably resulting from failed uninstalls) from PyOxidizer-based
pre-25.07 builds.

The fix suggested here is to revert the upstream change. This involves
switching from the pre-built
[briefcase-windows-app-template](https://github.com/ankitects/briefcase-windows-app-template)
template to
[briefcase-windows-VisualStudio-template](https://github.com/ankitects/briefcase-windows-VisualStudio-template).
I made sure to port all Anki-specific changes we made to the Wix
template (such as uninstall handling for the old NSIS-based installer).

## Steps to reproduce (before)

- Install 26.09
- Download and extract a lib folder from a PyOxidizer-build such as
[25.02.7](https://github.com/ankitects/anki/releases/tag/25.02.7) (You
can unzip the .exe using [7-zip](https://www.7-zip.org/)).
- Move the lib folder to the installation folder of 26.09
- Run Anki and confirm you get an error.

## How to test (after)

Repeat the same process but with this PR (`./tools/ninja installer`) and
confirm the lib subfolder has no effect.
2026-09-15 17:03:36 +03:00
Abdo b4e19f8883 fix: mention replacements in deprecation warnings for legacy import/export API (#5591)
The deprecation warnings added in #5547 incorrectly mention
_LegacyAnkiPackageExporter/_LegacyAnkiPackageImporter as the replacement
APIs instead of
Collection.export_anki_package/Collection.import_anki_package.

_This fix is already included in 26.09_
2026-09-14 23:10:33 +03:00
Abdo 55417c37ef fix: Lock bundled dependencies (#5593)
## Linked issue

Closes #5592

## Summary

Our Briefcase build didn't respect locked dependencies and dependency
cooldowns and used to resolve newer versions of some packages such as
`click` and `protobuf`.
This PR fixes the issue by exporting a list of pinned dependencies to a
file and passing it to uv using the `--constraints` option.

## Steps to reproduce (before)

- Build the bundle: `./ninja installer:build`.
- Inspect `out/installer/build/anki/*/app_packages` and notice
`protobuf-7.36.1.dist-info` is included despite it being locked to
6.33.5 in uv.lock

## How to test (after)

Build the bundle again and confirm there's no mismatch between locked
and installed dependency versions.
2026-09-14 23:04:59 +03:00
Abdo 19f7236927 Bump version (#5597)
Update translations and bump version to 26.09
2026-09-14 22:47:51 +03:00
Abdo 6eb89c0648 fix: Limit 'Open Image' action to image types (#5595)
Refer to
https://github.com/ankitects/anki/security/advisories/GHSA-932p-crhj-v43p
for explanation and reproduction steps.

_This fix is already included in 26.09_
2026-09-14 22:47:00 +03:00
Abdo 71bd36ff07 fix: Treat image-occlusion route as untrusted (#5594)
_Note: This fix is already included in 26.09_

This is a follow-up to
https://github.com/ankitects/anki/commit/b81c3b2cc497c32cba0f029afc8421d288a6630b
to prevent script execution in a top-level image-occlusion page.

See
https://github.com/ankitects/anki/security/advisories/GHSA-jw6j-j4mf-8jgm
2026-09-14 22:45:43 +03:00
Luc Mcgrady 697e909719 refactor: add get_scheduling_states_inner (#5145)
closes  #5144


https://github.com/ankitects/anki/blob/fd65f20fcd277da9ed156ed72b534a549742096d/rslib/src/scheduler/answering/mod.rs#L231

This call to the db is made even though the card is already fetched from
the db by the function caller.
2026-09-14 20:12:06 +01:00
Fernando Lins 587d6ec796 fix: run static SonarCloud analysis on fork PRs (#5596)
## Linked issue

Follow-up to #5498, which was closed in favor of a smaller and more
maintainable implementation.

## Summary / motivation

Run CI-based SonarCloud analysis for internal and fork pull requests
while keeping the Quality Gate informational.

The analysis matrix is:

| Event | Static analysis | Coverage in CI | Coverage in SonarCloud |
| --- | --- | --- | --- |
| Internal pull request | Yes | Yes | Yes |
| Push to `main` or `release/**` | Yes | Yes | Yes |
| Fork pull request | Yes | Yes | No |

Fork pull requests continue to run the full test coverage and
coverage-regression checks in the unprivileged CI workflow. Their
contributor-produced coverage reports are not passed to the privileged
SonarCloud workflow.

The fork scan reports static bugs, vulnerabilities, security hotspots,
and code smells. It:

- validates the PR state, repositories, branches, and tested SHA against
the GitHub API;
- checks out the exact revision tested by CI;
- replaces `sonar-project.properties` with the trusted default-branch
version;
- provides `SONAR_TOKEN` only to the scanner step;
- never builds, installs, or executes contributor code;
- keeps scanner-side Clippy and SCA disabled;
- clears coverage report paths and excludes coverage calculation for
forks.

Automatic Analysis remains disabled because it cannot be combined with
CI-based analysis and does not provide the required Rust and coverage
support.

This also configures Vitest to produce repository-relative LCOV paths
such as `SF:ts/lib/...`, allowing SonarCloud to resolve TypeScript files
when reports are downloaded into a separate checkout.

## How to test

### Checklist

- [x] I ran `just check` or an equivalent relevant check locally.
- [ ] I added or updated tests when the change is non-trivial or
behavior changed. The remaining behavior is specific to GitHub's
`workflow_run` environment and can only be exercised end-to-end after
the workflow is available on the default branch.

### Details

The following checks passed locally:

- `just test-ts --coverage`
- `just test-ts --coverage --html`
- `just fmt`
- `just check`
- YAML and JSON parsing
- `git diff --check`

Both TypeScript coverage runs passed all 67 tests. The generated LCOV
report was also checked to confirm that source paths are
repository-relative and begin with `SF:ts/`.

After this lands, end-to-end validation should cover:

1. A push to `main`, confirming that Python, TypeScript, and Rust
coverage reports are imported.
2. An internal PR, confirming static analysis and coverage.
3. A fork PR, confirming static analysis while the coverage download is
skipped and no coverage condition is evaluated.

## Before / after behavior

Before:

- fork PR analysis fails at the protected checkout;
- the `actions/checkout` version comments are outdated;
- TypeScript LCOV paths are relative to `ts/` and may not resolve in the
SonarCloud checkout.

After:

- internal PRs and trusted branch pushes receive static analysis and
coverage;
- fork PRs receive static analysis without importing
contributor-produced coverage;
- fork tests and coverage-regression checks continue to run in regular
CI;
- TypeScript LCOV paths resolve from the repository root;
- technical scanner failures remain visible without making the Sonar
Quality Gate a required merge check.

## Risk / compatibility / migration

The fork analysis runs in a privileged `workflow_run` and necessarily
asks the SonarScanner to parse untrusted source while it has access to a
project-scoped token.

The workflow limits this exposure by using GitHub-hosted runners,
read-only permissions, trusted scanner configuration, an exact tested
SHA, API-validated PR metadata, and no builds, dependency installation,
local Actions, caches, or fork-produced coverage artifacts.

This is intentionally a smaller trust boundary than the data-only
snapshot and custom coverage parser proposed in #5498. It prevents
direct execution of contributor code but does not eliminate potential
vulnerabilities in GitHub Actions or Sonar analyzers.

Automatic Analysis must remain disabled, `SONAR_TOKEN` should remain
restricted to this project, and the SonarCloud check must remain
non-required.
2026-09-14 15:30:42 -03:00
Abdo b81c3b2cc4 Merge commit from fork 2026-09-14 14:21:25 +03:00
Andreas 4383a7075c fix: prefer packaged add-on name for AnkiWeb installs (#5362)
## Linked issue (required)

Fixes #5342

## Summary / motivation (required)

When installing an add-on from AnkiWeb, Anki currently derives the
displayed
name from the AnkiWeb download filename and passes it as overriding
manifest
metadata. This can alter names when the filename has been sanitized by
AnkiWeb.

This change prefers a non-empty `name` from a valid packaged
`manifest.json`
when available, while retaining the existing AnkiWeb-derived name as a
fallback.

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

1. Package an add-on with a `manifest.json` whose `name` contains a
character
that is removed from the AnkiWeb download filename, such as `&` or `𝕾`.
2. Upload the add-on to AnkiWeb and install it from there.
3. Open the Add-ons dialog and observe that the displayed name is
derived from
   the sanitized AnkiWeb filename rather than the packaged `name`.

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

Added tests verifying that:

- a non-empty `name` from a valid packaged manifest is preferred for
AnkiWeb
  installs;
- the existing filename-derived name remains the fallback when no usable
  packaged name is available.

`./ninja check` passes locally.

Also manually verified with a local build by installing an AnkiWeb
add-on whose
packaged name differed from its AnkiWeb title; the Add-ons dialog used
the packaged name.

## Before / after behavior (optional)

Before: AnkiWeb installs always use the filename-derived name,
overriding a
`name` supplied in the packaged manifest.

After: A non-empty packaged `name` is preferred when available;
otherwise,
the existing AnkiWeb-derived name is retained.

## Risk / compatibility / migration (optional)

Low. The change is limited to AnkiWeb add-on installation, and the
existing
fallback behavior is preserved for add-ons without a usable packaged
name.

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

N/A

## Scope

- [x] This PR is focused on one change (no unrelated edits).
2026-09-13 19:04:57 +03:00
praxishandAbdo 2fae55543c fix: preserve pasted editor images and respect their format preference (#5568)
## Linked issue (required)

Fixes #5567

## Summary / motivation (required)

Pasting a base64 image in the new editor currently corrupts its bytes,
and the HTML paste path can put image markup into an image's `src`.
Decode base64 directly into bytes and return the saved filename from the
inline-image helper. Read the clipboard PNG preference's boolean value
so a disabled preference selects JPEG.

Add regressions through the public clipboard APIs for plain-text and
HTML data URLs and both image-format settings. Include editor route
sources and tests in the Vitest build inputs so edits to these
regressions reliably rerun the tests.

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

Using a disposable profile on the base revision:

1. Open the new editor and disable "Paste without shift key strips
formatting" to enable extended paste.
2. Paste the base64 PNG data URL from the linked issue as plain text,
then as the source of an image in clipboard HTML. Inspect the stored
media bytes and inserted image source: the bytes change and the HTML
source is invalid.
3. Disable "Paste clipboard images as PNG" and paste a bitmap image
through the clipboard path. PNG is still selected.
4. After a successful `just test-ts`, edit only an editor route test and
rerun it. The build incorrectly reuses the prior Vitest result.

## How to test

- [x] `RELEASE=1 just check` passed on this focused upstream-based
branch (`ee16fc015`), using Apple Silicon macOS and the repository's
pinned toolchains.
- [x] Added or updated regression coverage for the changed behavior.

Clipboard regressions check exact decoded bytes, a local media filename
in the inserted image, and both PNG/JPEG preference values. Confirmed
route test edits invalidate the Vitest target. Clipboard/backend APIs
are mocked; no user collection is accessed.
## Risk / compatibility / migration (optional)

No database migration or new dependency. The legacy paste branch is
unchanged. The build input change only causes relevant source/test edits
to rerun Vitest. Complete local validation was on macOS; other platforms
depend on CI.

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

N/A: no visual design changes. Regression tests inspect the inserted
image markup.

## Scope

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

---------

Co-authored-by: Abdo <abdo@abdnh.net>
2026-09-12 11:57:37 +03:00
praxishandAbdo f5a0b60806 docs: clarify Windows x64 and ARM64 installation requirements (#5582)
## Linked issue

Fixes #5336

## Summary / motivation

The Windows installation guide lists only a generic 64-bit Windows
requirement, even though Anki provides separate x64 and ARM64 installers
with different Windows requirements. Document both configurations and
explain how to match the processor type shown in Windows Settings to the
download label.

## Steps to reproduce

N/A: documentation only.

## How to test

- [x] `RELEASE=1 just check` passed on the focused branch on macOS Apple
Silicon.
- [x] No unit tests added for a documentation-only change.
- Confirmed the requirements and download labels against [Anki's
download page](https://apps.ankiweb.net/): Windows 10+ (x64) and Windows
11 (ARM).
- Confirmed that release 26.08.1 includes separate `win-x64.msi` and
`win-arm64.msi` installers.
- Checked the Settings path against [Microsoft's system-type
instructions](https://support.microsoft.com/en-us/windows/32-bit-and-64-bit-windows-frequently-asked-questions-c6ca9541-8dce-4d48-0415-94a3faa2e13d)
and [Windows ARM64
guidance](https://www.microsoft.com/en-us/software-download/windows11arm64).
- Mintlify 4.2.884: build validation and accessibility checks passed.
The site-wide accessibility scan reports 32 pre-existing parse warnings
on other pages; the edited Windows page is checked successfully.

## Risk / compatibility / migration

Documentation only. Existing guidance for older Windows releases and
32-bit systems is retained. No installer or platform support changes.

## UI evidence

N/A: documentation only.

## Scope

- [x] Focused on Windows requirements and choosing the matching
installer.

---------

Co-authored-by: Abdo <abdo@abdnh.net>
2026-09-11 20:08:37 +03:00
Luc Mcgrady 7941f546fc docs(fix): Revert "add redirect to english for missing translations" (#5559)
fixes #5558

We need to find a separate solution to this.
2026-09-11 16:45:49 +01:00
llama 93bd382884 fix: flaky mathjax loading test (#5560)
## Linked issue (required)

Closes #5561

## Summary / motivation (required)

Importing `./index` was taking a long time which timed out several
tests, and the scripts mock wasn't reset between tests. I added a dummy
import beforehand to warm the cache but test timeouts might still need
increasing
2026-09-10 18:43:00 +03:00
dea2a1f93a feat: Docs converter (#5483)
closes #5064 

Should let you quickly import to mintlify from the repos that use the
existing format.

a small problem that `DOCS_RELATIVE_LINK_REPLACEMENTS` needs to be set
manually for links between related translations but other than that
should be good.

I've run it on `en/addons` `en/faqs` `en/manual` and on `ar/manual`
`ar/faqs`. We might need to be careful to not overwrite any changes that
have been merged in this repo if there are any.

I've set it to copy only files where it can find them in the existing
mintlify docs because this allows us to more easily keep the site
structure. It does mean that there are some pages which aren't copied
and I'm going to have to look into that at some point.

But it should be good enough to look through and to import any remaining
changes left in the existing repos.

Example usage:
```
python tools/docs-converter.py [Source repo] [Target tab] [Target language code]
```

```
python tools/docs-converter.py ../anki-manual manual en
python tools/docs-converter.py ../anki-faqs faqs ar
```

---------

Co-authored-by: Abdo <abdo@abdnh.net>
Co-authored-by: Fernando Lins <1887601+fernandolins@users.noreply.github.com>
2026-09-10 15:10:22 +01:00
llama 8463287b80 test: mathjax loading (#5533)
## Linked issue (required)

Closes #5417

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

## Scope

- [x] This PR is focused on one change (no unrelated edits).
2026-09-10 16:17:10 +03:00
Abdo 9753998c5b fix: Update to Qt 6.11.2 (#5539)
## Linked issue

Closes #5538

## Summary

This updates to Qt 6.11.2 patch release for security/crash fixes.

## How to test

Run `just test` and `just test-e2e`.
2026-09-10 11:09:23 +03:00
Fernando Lins 392fdecbb3 docs: add unit testing guidelines (#5522)
## Linked issue

Closes #5521

## Summary / motivation

Adds an Anki-focused unit testing guide for maintainers, contributors,
and coding assistants.

The guide:

- Defines principles for focused, deterministic, and maintainable tests.
- Covers happy paths, error paths, boundaries, regressions, and
test-first development.
- Provides guidance for Python, Python/Qt, Rust, and
Svelte/TypeScript/JavaScript.
- Clarifies the appropriate scope for UI tests and discourages testing
styling or volatile presentation details.
- Links the guide from the shared `CLAUDE.md`/`AGENTS.md` instructions.
2026-09-09 17:03:26 -03:00
Caleb_Meadows 0842216571 fix: preserve FSRS unlock timeout across mounts (#5536)
## Linked issue (required)

Fixes #5516

## Summary / motivation (required)

Make the existing FSRS parameter unlock timing API available before the
parameter editor is mounted, and retain the host's chosen timeout when
FSRS is disabled and enabled again. Register the API and store its
timeout in the component's module script; keep click tracking local to
each editor instance.

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

1. Open the deck-options page with FSRS disabled.
2. In that page's JavaScript console, run
`anki.setParameterUnlockClickTimeoutMs(1000)`.
3. Before this fix, the call throws `TypeError:
anki.setParameterUnlockClickTimeoutMs is not a function`.

## How to test (required)

### Checklist (minimum)

- [x] I ran `just check` locally.
- [x] I added a browser regression for the changed behavior.

### Details

- The regression failed on the original implementation at the pre-mount
setter call and passes with the fix.
- It checks configuration before the first mount, retention across
remounts, updates while absent and present, the three-click gate, the
default timeout, and reset after page reload. Playwright's clock
controls the click intervals.
- `just test-ts`: 61 tests passed.
- `just test-e2e`: 27 tests passed, using the repository's disposable
Anki harness.
- `just check`: passed, including 572 Rust tests and the Python,
formatting, lint, and type checks.
- The Qt checks emitted an audio-thread warning (`mw` was `None` in
`qt/aqt/sound.py`); all 93 Qt tests passed and `just check` exited
successfully.

## Before / after behavior (optional)

Before: the API exists only after the editor mounts, and mounting a new
editor resets the timeout to 500 ms.

After: the API exists when the deck-options JavaScript loads, and the
selected timeout lasts for the page's lifetime.

## Risk / compatibility / migration (optional)

Existing API names, the 500 ms default, and the three-click requirement
are preserved. Click counts and pending timers remain local to each
editor. No stored data or scheduling behavior changes.

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

N/A.

## Scope

- [x] This PR is focused on one change.

Developed with assistance from Codex.
2026-09-08 21:42:14 +01:00
Fernando Lins bfad9474d4 test: cover the decks service layer (#5514)
## Linked issue

Closes #5383

## Summary / motivation

`rslib/src/decks/service.rs`: the Protobuf entry point for all deck
operations, it had 0% test coverage. This PR adds a golden
`#[cfg(test)]` suite that exercises the happy path, error paths,
boundary cases, and key invariants of the `DecksService` layer and its
proto↔domain conversions.

No production code was changed: the module is a thin routing/conversion
layer with no logic that warranted refactoring. Testing at this boundary
is additive rather than duplicative: `reparent_decks`,
`remove_decks_and_child_decks` and the proto↔domain conversions had no
direct unit tests before.

Coverage of `decks/service.rs` goes from **0% → ~99.7% of lines**

## How to test
### Details

- `just check` passes (fmt, clippy, rust_test, and the rest of the
suite).
- Run just this module: `cargo test -p anki decks::service::tests`.
- Coverage inspected with `just test-rust --coverage --html`
- `decks/service.rs` reports ~99.7% line coverage.

Scenarios covered:
- proto↔domain conversions: full-field `Deck` round trip for both kinds,
`missing kind → InvalidInput`, `FilteredDeckForUpdate` field
preservation and `config: None → default`.
- create/retrieve, update (common + kind-specific persistence), rename
with descendant cascade, delete + card removal, reparent (incl.
`new_parent == 0`).
- name/tree listing: `include_filtered`, empty vs non-empty default
skipping, child-name filtering, `deck_tree` counts by `now`, legacy tree
JSON.
- current deck, collapsed-scope isolation, legacy JSON paths (incl.
`preserve_usn_and_mtime`), and filtered-deck build.
- Error paths assert the specific `AnkiError` variant (`NotFound` /
`InvalidInput`), not just `is_err()`.
2026-09-08 17:10:11 -03:00
Abdo 39a36fc47c test: Improve coverage of notes service (#5509)
## Linked issue

Closes #5386

## Summary

This increases test coverage of `rslib/src/notes/service.rs` and some
related modules (`rslib/src/adding.rs` and `rslib/src/notes/mod.rs`).

## How to test

Run `just test-rust --coverage --html` and view coverage reports.
2026-09-08 22:15:17 +03:00
Abdo b758186f25 fix: Add compatibility shims for AnkiPackageImporter/AnkiPackageExporter (#5547)
## Linked issue

Closes #5541

## Summary

Add compatibility wrappers for AnkiPackageImporter/AnkiPackageExporter
to redirect the calls to the new APIs. This is intended as a temporary
compatibility workaround for the
[AnkiConnect](https://ankiweb.net/shared/info/2055492159) add-on, not as
a general wrapper for all removed APIs.

## Steps to reproduce (before)

Install AnkiConnect and confirm it fails at startup due to the removed
`anki.importing` and `anki.exporting` modules.

## How to test (after)

Export a sample apkg and run this script in the same directory to test
AnkiConnect's importPackage/exportPackage APIs.

```python
import json
import urllib.request
from pathlib import Path


def request(action, **params):
    return {'action': action, 'params': params, 'version': 6}

def invoke(action, **params):
    payload = json.dumps(request(action, **params)).encode('utf-8')
    response = json.load(urllib.request.urlopen(urllib.request.Request('http://127.0.0.1:8765', payload)))
    if len(response) != 2:
        raise Exception('response has an unexpected number of fields')
    if 'error' not in response:
        raise Exception('response is missing required error field')
    if 'result' not in response:
        raise Exception('response is missing required result field')
    if response['error'] is not None:
        raise Exception(response['error'])
    return response['result']

result = invoke('importPackage', path=str(Path(__file__).parent / 'sample.apkg'))
assert result is True

decks = invoke('deckNamesAndIds')
deck_to_export = next(name for name in decks if name != 'Default')

result = invoke('exportPackage', deck=deck_to_export, path=str(Path(__file__).parent / 'exported.apkg'), includeSched=True)
assert result is True

```
2026-09-08 21:30:34 +03:00