mirror of
https://github.com/ankitects/anki.git
synced 2026-10-02 05:24:38 +08:00
main
358
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7a4db0038e |
chore: enable strict_optional for aqt modules needing no changes (#5645)
Closes #5644. Part of #3317. 22 `aqt` modules already type-check cleanly under `strict_optional`, so this opts them in. Config only — no code changes. Verified clean with `mypy --platform` for `darwin`, `linux` and `win32`. `aqt.mpv` is left out: it sets `ignore_errors = True`, so a stanza there would be a no-op. |
||
|
|
cd096774bc |
fix: apply video driver settings before the Qt app is created (#5701)
## Linked issue (required) Fixes #5700 ## Summary / motivation (required) Since #5637, `setupGL()` runs after `AnkiApp` is constructed. Some of what it sets is only read by Qt at startup (`AA_UseSoftwareOpenGL` on macOS, `QT_OPENGL` on Windows), so safe mode and the Software driver are no longer fully applied. This calls `setupGL()` before creating the app again, where it was before #5637. Safe mode is decided at that point, from `--safemode` or from Shift. Since Qt can only detect Shift once the app exists, `shift_held_before_app()` checks it with platform APIs: `CGEventSourceFlagsState()` on macOS and `GetAsyncKeyState(VK_SHIFT)` on Windows. If Qt still detects Shift after the app is created but the check above didn't (e.g. on Linux, where it isn't implemented), `setupGL()` is called again with the Software driver. On Linux the settings it makes still take effect at that point, so that path keeps working as it does today. Happy to drop that fallback if you'd prefer. ## Steps to reproduce (required, use N/A if not applicable) 1. On macOS, run `/Applications/Anki.app/Contents/MacOS/anki --safemode` from a terminal 2. The console shows `Qt warning: Attribute Qt::AA_UseSoftwareOpenGL must be set before QCoreApplication is created.` ## How to test (required) ### Checklist (minimum) - [ ] 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 - `qt/tests/test_startup_video_driver.py` runs `_run()` with a stand-in for `AnkiApp` and checks that `setupGL()` is called with the expected driver (profile driver, or Software with `--safemode` or Shift held) before the app is constructed. Fails on main, passes with this change. - Launched the source tree on macOS with a temporary base folder, with `--safemode`: the warning above appears on main and is gone with this change. With Shift held, `shift_held_before_app()` returns True and the warning doesn't appear either. - On Windows 11, checked the `GetAsyncKeyState(VK_SHIFT)` call used here: it returns False before Shift is pressed and True while it's held. I haven't tested a full Anki launch on Windows. - ruff check/format pass on the changed files. I haven't been able to run the full `just check` locally yet, so I'm relying on CI for the rest. ## Before / after behavior (optional) Before: `--safemode` and the Software driver don't set `AA_UseSoftwareOpenGL` (macOS) / `QT_OPENGL` (Windows) in time. After: they're applied before the app is created. ## Risk / compatibility / migration (optional) This touches the same startup block as #5686, so whichever lands second will need a small rebase. I'll take care of it if this one goes second. ## 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> |
||
|
|
d1d484c824 |
fix: stream full sync to disk instead of buffering in memory (#5717)
## Linked issue (required) Fixes #5716 ## Summary / motivation (required) During a full download, Anki loads the whole collection into memory before saving it to disk. With a large collection, iOS can kill the app partway through the sync because of OOM. See #5716 for details. This PR streams the download straight into the temporary file instead, so memory usage no longer grows with the collection size. ## 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. Set up a self-hosted `anki-sync-server`, and put a large enough collection database(My collection is ~3GiB) 2. On AnkiMobile or Amgi, with an empty local collection, point the client to the self-hosted server 3. Start full sync, and then it get killed because of OOM ## How to test (required) <!--- How to test: how you verified the change (checks, unit tests, manual steps, edge cases — the "after" or general validation). ---> - Pointed [Amgi](https://github.com/antigluten/amgi)'s `anki-upstream` submodule at this branch, then ran a full download of a ~3 GiB collection from a self-hosted sync server on iOS. The app no longer gets killed for running out of memory, and the sync completes. AnkiMobile is close-source so I can not test it. - Built and ran Anki desktop from this branch. It starts and works normally. - Ran the `cargo test -p anki --lib sync::` ### 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. ## Before / after behavior (optional) Before: A full download loads the whole collection into memory before saving it, so on iOS, syncing a large collection (e.g. ~3 GiB) can get the app killed for running out of memory. After: The collection is saved to disk as it downloads Measured peak memory while downloading a 512 MiB collection from a LAN server: it dropped from about 534 MB to about 22 MB with Amgi. ## Scope - [X] This PR is focused on one change (no unrelated edits). |
||
|
|
41d7464c9b |
fix: apply the UI scale before creating the Qt app (#5686)
## Linked issue (required) Fixes #5676 ## Summary / motivation (required) #5637 moved the `QT_SCALE_FACTOR` assignment to after `AnkiApp` is created, but Qt only reads it when the app is constructed, so the "User interface size" setting stopped doing anything. this sets it before the app is created again; `setupGL()` stays where it is (smaller version suggested in review) ## Steps to reproduce (required, use N/A if not applicable) set "User interface size" to 200% in preferences, restart, the UI is still the default size ## How to test (required) ### Checklist (minimum) - [ ] 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 qt/tests/test_startup.py runs `_run()` with a stand-in for AnkiApp and checks QT_SCALE_FACTOR is already set when it's constructed, fails on main and passes with this. also checked with the 26.9.3 wheels on Windows at 150%: device pixel ratio 1.5 before, 3.0 after ## UI evidence (required for visual changes; otherwise N/A) N/A ## Scope - [x] This PR is focused on one change (no unrelated edits). |
||
|
|
bd69a45305 |
docs: minimum Python for the pylib/aqt packages is 3.10 (#5678)
## Linked issue (required) Closes #5679 ## Summary / motivation (required) The "pre-built Python wheels" section in both `docs/development.md` and `docs-site/developers/development.mdx` says you need Python 3.9 or later and recommends 3.9 because 3.10+ had minimal testing. The packages now declare `requires-python = ">=3.10"` (`pylib/pyproject.toml`, `qt/pyproject.toml`), so pip on 3.9 won't install them at all. This updates both copies to 3.10 or later and drops the outdated 3.9 recommendation. This is my first PR, so it also adds my name to `CONTRIBUTORS`. ## Steps to reproduce (required, use N/A if not applicable) N/A (docs). ## How to test (required) ### Checklist (minimum) - [ ] 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 Docs-only; compared against `requires-python` in every `pyproject.toml` in the repo (the root dev environment is `>=3.12`, the published packages are `>=3.10`). ## Scope - [x] This PR is focused on one change (no unrelated edits). AI assistance: found and prepared with Claude Code. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a5a0e44467 |
Fix premature closure of cloze when inside MathJax (#4612)
The cloze tokenizer in `rslib/src/cloze.rs` previously treated any
occurrence of `}}` as a cloze terminator, even when it appeared inside
MathJax expressions.
Example:
{{c1:: \( \frac{1}{ \sqrt{ \pi }} \) }}
The `}}` from `\sqrt{\pi}}` would cause the cloze to terminate early,
resulting in incorrect parsing and rendering.
<img width="725" height="257" alt="Screenshot 2026-03-14 at 11 17 21 AM"
src="https://github.com/user-attachments/assets/76e475ed-b3ab-4bb5-9538-2e65d3a136fe"
/>
<img width="610" height="253" alt="Screenshot 2026-03-14 at 11 17 46 AM"
src="https://github.com/user-attachments/assets/ecefe3ad-960d-4fd8-a2d9-501edef11679"
/>
`normal_text()` now tracks when the scanner is inside a MathJax region
(`\(...\)` or `\[...\]`). While inside MathJax, `}}` is ignored as a
cloze terminator.
Cloze openings are still recognized inside MathJax.
---------
Co-authored-by: Abdo <abdo@abdnh.net>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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. |
||
|
|
9e32ad8849 |
fix: refresh reviewer toolbar background after theme changes (#5241)
## Linked issue (required) Fixes #5240 ## Summary / motivation (required) During review, the top toolbar copies the main reviewer webview's computed background so that the card background continues behind the toolbar. When the theme changes, the webviews update their theme classes, but the toolbar's copied inline `background` remains unchanged. This leaves the top of the reviewer using the previous theme's background. This PR clears the stale inline background when the theme changes and schedules it to be copied again after the webviews have queued their theme-class updates. The additional behavior is limited to the review screen. Tests cover refreshing the background during review and ensuring that the background-copying behavior is not triggered on other screens. This is narrower than the general live-theme issue addressed by #1471 and #1497. The reviewer toolbar's computed-background behavior was introduced later as part of #2262. ## Steps to reproduce (required, use N/A if not applicable) 1. Start Anki and open **Preferences**. 2. Set **Theme** to **Light**. 3. Ensure **Minimalist mode** and **Hide top bar during review** are disabled. 4. Close Preferences and start reviewing a deck. 5. While the question side of a card is displayed, reopen Preferences. 6. Change **Theme** from **Light** to **Dark**. 7. Close Preferences without showing the answer or moving to another card. 8. Observe that the reviewer changes to the dark theme, but the background behind the top toolbar remains light. The issue can also be reproduced in the opposite direction, and when Anki follows the system theme while the operating system changes between light and dark appearance. ## How to test (required) ### Checklist (minimum) - [x] I ran `just check` locally. - [x] I added or updated tests when the change is non-trivial or behavior changed. ### Details Automated testing: - `just fmt` - `just test-py` - `just check` Manual testing: 1. Launched the fixed build with `just run`. 2. Repeated the reproduction steps above. 3. Confirmed that the top toolbar background immediately matches the rest of the reviewer after switching from light to dark. 4. Repeated the test from dark to light. 5. Confirmed that changing themes outside the reviewer continues to behave as before. ## Before / after behavior (optional) Before: the reviewer updates to the new theme, but the background behind the top toolbar retains the previous theme's color. After: the top toolbar background is recalculated after the theme changes and matches the rest of the reviewer. ## Risk / compatibility / migration (optional) Low risk. The change only adds theme-change handling to `TopWebView` while Anki is in the review state. It calls the existing base theme-change handler and reuses the existing background-copying method. ## UI evidence (required for visual changes; otherwise N/A) ### Light to dark **Before** <img width="400" alt="image" src="https://github.com/user-attachments/assets/a04bb77b-5a34-459e-896c-d00a66601b83" /> **After** <img width="400" alt="Screenshot 2026-07-30 at 13 52 49" src="https://github.com/user-attachments/assets/481871b5-a4bb-43bc-8cbc-8400c447fae3" /> ### Dark to light **Before** <img width="400" alt="image" src="https://github.com/user-attachments/assets/9a94f926-f4fc-440c-8f63-7a94feae9e71" /> **After** <img width="400" alt="Screenshot 2026-07-30 at 13 52 39" src="https://github.com/user-attachments/assets/c48a32d3-da0c-4593-b751-cdcd8a0487f6" /> ## Scope - [x] This PR is focused on one change (no unrelated edits). |
||
|
|
b9d69a0abd |
bump fsrs to 6.6.2 (#5494)
closes #5495 |
||
|
|
0218af9bef |
Hide FSRS-only sort orders in filtered deck dialog when FSRS is disabled (#5407)
Fixes #5397. Selecting ascending/descending retrievability (or relative overdueness) for a filtered deck while FSRS is disabled raises a `SQLITE` error, because `build_retrievability_query()` returns an empty order fragment without FSRS (`order by , fnvhash(...)`), and the overdueness expression reads FSRS memory state regardless. As suggested in the issue by Luc, this hides those three orders in the filtered deck dialog when FSRS is off, the same way deck options already handles review order. The combo box rows are mapped through the list of actually offered orders instead of assuming row == enum value, so stored configs stay valid. If a stored config references one of the hidden orders (e.g. FSRS was turned off after it was built), the dialog falls back to Random rather than showing a broken selection. I checked that the mapping round-trips correctly with FSRS on and off, including the fallback path. |
||
|
|
5a58c8516f |
feat: improved error message for CSV import (#5484)
<!-- 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 --> Fixes #4346 ## Summary / motivation (required) <!-- What this PR does and why. For larger changes, add enough context for reviewers. --> Improves the CSV import error experience for empty or invalid CSV files. Previously, the import error page exposed an HTTP status code and did not provide a clear way for the user to dismiss the error and return to the main Anki window. Changes: - displays the import error without exposing the HTTP status code - adds an "Okay" button to dismiss the error - use Anki frontend RPC architecture to close the active import dialog - handles CSV import loader errors with a try/catch and displays them through the shared ErrorPage component The solution uses the existing `ImportDialog.reject()` path so the dialog box performs normal clean up before closing. This implementation was inspired by the previous discussion and earlier work in #4383. Thank you @medProgAyat and @josod827. Your discussion and implementations were a great assest to helping me solve and implement a solution. ## 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 Anki and click on import button 2. select a empty csv 3. error page should open ## How to test (required) <!--- 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. - [ ] I added or updated tests when the change is non-trivial or behavior changed. ### Details <!-- Commands, manual steps, edge cases, and what you observed --> Manual testing: 1. Imported an empty CSV file. 2. Confirmed the custom error page is displayed. 3. Confirmed the HTTP status code is not displayed to the user. 4. Confirmed clicking the "Okay" button closes the import dialog and returns to the main Anki window. 5. Imported a valid CSV file and confirmed the normal CSV import flow still works. ## Before / after behavior (optional) <!-- For bugfixes: behavior before vs after. For other types: N/A or a short note. --> Before: - Empty CSV imports display an error without a clear dismissal action. - Development error output may include the HTTP status code. After: - The error is displayed through the shared error UI. - The user can click "Okay" to close the import dialog. - The close action is handled through a frontend RPC. ## Risk / compatibility / migration (optional) <!-- Breaking changes, rollout notes, or N/A for small / low-risk PRs --> Low risk. The change is limited to CSV import error handling and the import dialog close path. ## UI evidence (required for visual changes; otherwise N/A) <!-- Screenshot or short video --> before: <img width="800" height="800" alt="image" src="https://github.com/user-attachments/assets/4fe77689-3ebd-4a98-9bd9-4916214bbb29" /> after: <img width="788" height="818" alt="image" src="https://github.com/user-attachments/assets/8451f465-894a-4f52-a11b-459fc6840326" /> ## Scope - [x ] This PR is focused on one change (no unrelated edits). |
||
|
|
ce26e216f1 |
fix: only attach handler to add-on's root logger (#5497)
<!-- 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 #5487. ## Summary / motivation (required) <!-- What this PR does and why. For larger changes, add enough context for reviewers. --> When deriving a child logger from an add-on logger, both of them got `TimedRotatingFileHandler` attached to the loggers handler. This makes log messages duplicated in log file and cause `PermissionError: [WinError 32]` when rotating log files on Windows. To solve this issue, the new `get_logger` only attaches `TimedRotatingFileHandler` to the add-on logger (`addon.name`), not the child one (`addon.name.child`). Besides, when a child logger is created with `logging.getLogger("addon.name.child")`, the add-on logger (`addon.name`) is created if not existed. The add-on logger becomes the one and only logger that has `TimedRotatingFileHandler` handler down the hierarchy from itself. I also fixes a minor bug of `module` variable. In the previous implementation, the `module` only contained part of the module name if the name contained `addon.`. I use `removeprefix` to solve it. ## 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. --> `Anki2/addons21/test_logger/__init__.py`: ```python from aqt.addons import AddonManager logger = AddonManager.get_logger(__name__) child_logger = logger.getChild("child") child_logger.info("This info messages is duplicated in log file.") ``` When the log file is rotated on Windows, it raises `PermissionError: [WinError 32]`. Please check #5487 for detailed bug report. ## How to test (required) <!--- 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. - [x] I added or updated tests when the change is non-trivial or behavior changed. ### Details <!-- Commands, manual steps, edge cases, and what you observed --> Just create a test add-on to check this behavior as the "Steps to reproduce" above. ## Before / after behavior (optional) <!-- For bugfixes: behavior before vs after. For other types: N/A or a short note. --> ### Before 1. When deriving a child logger from an add-on logger, both of them got `TimedRotatingFileHandler` attached to the loggers handler. The log messages duplicated in log file and cause `PermissionError: [WinError 32]` when rotating log files on Windows. 2. If the logger name contains `addon.`, characters after `addon.` are stripped. ### After 1. When an add-on logger is created no matter it is an add-on logger (`addon.name`) or a child logger (`addon.name.child`), the add-on logger is created with `TimedRotatingFileHandler` handler, and the child logger is created as a normal logger. ## UI evidence (required for visual changes; otherwise N/A) N/A ## Scope - [x] This PR is focused on one change (no unrelated edits). |
||
|
|
3a5d285821 |
fix: play audio immediately when attached in the editor (#5320)
## Linked issue (required)
Closes #5321
## Summary / motivation (required)
In the old Qt-based editor, attaching an audio file (paperclip button or
the mic recording button) played the file immediately, before the card
was saved. This was handled by `Editor.fnameToLink()` in
`qt/aqt/editor.py`, which called `av_player.play_file_with_caller()`
right after building the `[sound:...]` tag.
That method was removed in
|
||
|
|
8bf7d91a50 |
chore: exclude docs contributions from BSD license check (#5267)
<!-- 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 #5265 <!-- Fixes #123 / Closes #123 / Refs #123 --> ## Summary / motivation (required) The document only changes are under the CC BY-SA 4 license, therefore no need to enforce BSD 3 license contribution agreement. <!-- What this PR does and why. For larger changes, add enough context for reviewers. --> ## Steps to reproduce (required, use N/A if not applicable) N/A <!-- 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. --> ## How to test (required) End-to-end local GH action: 1. Install the [act](https://github.com/nektos/act) tool to run GH action locally 2. Install ubuntu container: `podman pull ghcr.io/catthehacker/ubuntu:act-24.04` 3. Create a clean working tree from this branch: `git checkout -b test-docs` 4. Add a doc only change: `echo x >> README.md && git add README.md` 5. Commit the changes: `git -c user.email=nobody@example.com commit -m "docs only"` 6. Run the minilints step: `act -j minilints --container-daemon-socket unix:///tmp/podman.sock -P ubuntu-24.04=ghcr.io/catthehacker/ubuntu:act-24.04` 7. The log should contain the "Run minilints" step. <!--- 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. - [x] 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) Before: The documentation only changes trigger the contribution agreement check. After: If the changes are documentation only, it workflow skips the contribution check. <!-- For bugfixes: behavior before vs after. For other types: N/A or a short note. --> ## Risk / compatibility / migration (optional) N/A <!-- Breaking changes, rollout notes, or N/A for small / low-risk PRs --> ## UI evidence (required for visual changes; otherwise N/A) N/A <!-- Screenshot or short video --> ## Scope - [x] This PR is focused on one change (no unrelated edits). |
||
|
|
7c13bc4692 |
fix: Browse Add-ons URL (#5290)
## Linked issue (required) Fixes #5289 ## Summary / motivation (required) Updates the Browse Add-ons link to use the current `/shared/addons` URL directly instead of the legacy `/shared/addons/2.1` URL, which redirects to it. ## Steps to reproduce (required, use N/A if not applicable) 1. Open Tools → Add-ons. 2. Click Get Add-ons… 3. Click Browse Add-ons. 4. Observe that `/shared/addons/2.1` is opened and redirects to `/shared/addons`. ## How to test (required) ### Checklist (minimum) - [ ] 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 Manually verified that `/shared/addons/2.1` redirects to `/shared/addons`, and that `/shared/addons` loads directly. No local build or automated checks were run; the change was made using GitHub's web editor. ## Before / after behavior (optional) Before: Browse Add-ons opens the legacy `/shared/addons/2.1` URL and relies on a redirect. After: Browse Add-ons opens `/shared/addons` directly. ## Risk / compatibility / migration (optional) N/A. This is a one-line URL change. ## UI evidence (required for visual changes; otherwise N/A) N/A ## Scope - [x] This PR is focused on one change (no unrelated edits). |
||
|
|
3e85288731 |
fix: separate add-on entries in Tools menu with separator (#5124)
## Linked issue (required) Closes #5125 ## Summary / motivation (required) Add a separator at the end of Anki's built-in Tools menu actions, before any add-on-provided menu entries. Add-ons commonly append actions directly to `mw.form.menuTools`. This keeps add-on entries visually separated from Anki's built-in Tools actions, especially on macOS where Preferences is shown elsewhere and add-on entries can appear immediately below "Check for Updates". Because Qt hides trailing menu separators by default, users without add-on-provided Tools menu entries should not see an extra separator. ## Steps to reproduce (required, use N/A if not applicable) 1. Install one or more add-ons that append entries to the Tools menu. 2. Open the Tools menu. 3. Observe that add-on-provided entries appear directly after the built-in Tools menu entries. ## 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 - Parsed `qt/aqt/forms/main.ui` as XML. - Ran `git diff --check`. - No automated test was added because this is a one-line visual menu grouping change in a Qt Designer file. ## Before / after behavior (optional) Before: add-on-provided Tools menu entries can appear immediately after Anki's built-in actions. After: add-on-provided Tools menu entries are visually separated from Anki's built-in actions. ## Risk / compatibility / migration (optional) Low risk. The change adds a Qt menu separator after the built-in Tools actions. Qt hides trailing menu separators by default, so users without add-on-provided Tools menu entries should not see an extra divider. ## UI evidence (required for visual changes; otherwise N/A) <img width="627" height="576" alt="before after" src="https://github.com/user-attachments/assets/ac1b341a-abdf-4c98-8eeb-4af990bc8118" /> ## Scope - [x] This PR is focused on one change (no unrelated edits). |
||
|
|
51b741f591 |
fix: Guard on_operation_did_execute against deleted web views (#5234)
## Linked issue (required) Fixes #5233 ## Summary / motivation (required) Since #4029, every `AnkiWebView` subscribes to `gui_hooks.operation_did_execute` in `__init__`, and the subscription is only removed in `cleanup()`. Web views that are destroyed without a `cleanup()` call, which is common in add-ons that rely on Qt parent ownership for teardown, leave a dead handler behind, and the next `CollectionOp` then raises `RuntimeError: wrapped C/C++ object has been deleted`. This was latent before #4029: `__init__` only registered on `theme_did_change` and `body_classes_need_update`, which fire rarely enough that a missed `cleanup()` had no practical consequences. `operation_did_execute` fires after every operation, so the same leak now produces an error dialog within seconds. This isn't specific to one add-on: QA against 26.08b2 reproduced the identical error with several popular add-ons (Card Stats, Remaining Time, Cloze Hide All, AJT Japanese), see the linked issue for details. #5143 recently fixed the same crash class for the Find Duplicates dialog (#5140, there via `theme_did_change`) by adding the missing `cleanup()` call at the dialog's close site. That approach works for Anki's own dialogs, but there is no equivalent close site to patch for add-on web views that are destroyed through Qt parent ownership, so this gap needs to be covered in the handler itself. This PR guards the handler with `sip.isdeleted(self)` and unsubscribes the dead handler, mirroring the existing guards for late web events in `_maybeRunActions` and `_shouldIgnoreWebEvent`. Unsubscribing (rather than just returning) means leaked web views don't accumulate in the hook list and their Python wrappers can be collected. The removal is deferred via `mw.progress.single_shot(0, ...)` rather than done inline, because the generated hook code iterates `self._hooks` directly and an inline removal would skip the following handler for that firing. ## Steps to reproduce (required, use N/A if not applicable) Tested against 26.08b2, no add-on needed: 1. Open the debug console and run: ```python from aqt.webview import AnkiWebView w = AnkiWebView(parent=mw) w.deleteLater() ``` 2. Close the console and answer a card, or run any other `CollectionOp` 3. An error dialog appears with `RuntimeError: wrapped C/C++ object of type AnkiWebView has been deleted` ## How to test (required) ### 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. (Qt lifetime behavior, not currently covered by the test harness - see below, and #5143 for precedent) ### Details Manual verification via the repro steps above: after the fix, the operation completes without an error and the dead handler is removed from the hook (checkable via `len(gui_hooks.operation_did_execute._hooks)` in the debug console before and after). No unit test added: the behavior depends on Qt object deletion timing and a running event loop, which the Python test suite doesn't currently cover (the existing `sip.isdeleted` guards in this file are in the same situation). ## Before / after behavior (optional) Before: one error dialog per web view that was destroyed without `cleanup()`, raised on the next operation after the deletion, e.g. when answering the next card after closing a browser that hosted an add-on web view. After: the dead handler is silently dropped and the operation completes normally. ## Risk / compatibility / migration (optional) Low risk: a small guard with no behavior change for web views that are cleaned up properly. Add-ons are the beneficiaries, no add-on facing API changes. ## UI evidence (required for visual changes; otherwise N/A) N/A ## Scope - [x] This PR is focused on one change (no unrelated edits). |
||
|
|
f13c15aef0 |
fix: build bundled fcitx5 plugin against the bundled Qt (#5142)
## Linked issue (required) Fixes #5110 ## Summary / motivation (required) CI builds the fcitx5 plugin against one Qt but ships it inside PyQt6's Qt — a version mismatch that crashes Anki on startup for every fcitx5 user. **Root cause:** the plugin subclasses `QPlatformInputContext`, a Qt private API with no ABI guarantee across minor versions. Built against Qt 6.2.4 headers but run inside Qt 6.11.0, it dereferences a misplaced member and SIGSEGVs on first focus. **Solution:** build the plugin against the same Qt as the bundled `pyqt6-qt6` and install it where the packaging step (`qt/tools/build_installer.py`) looks. The per-flag reasoning is in the commit message. ## Steps to reproduce (required, use N/A if not applicable) 1. On a Linux desktop configured to use fcitx5 as the input method, install the official Anki 26.05 release. 2. Launch Anki normally. 3. It dies with SIGSEGV on startup, as soon as the main window takes focus. ## How to test (required) ### 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 `release.yml` is `workflow_dispatch`-only, so it can't run on the PR. I reproduced its build step locally: rebuilt the plugin against Qt 6.11.0 (confirmed the `Qt_6.11` ELF tag and a clean `dlopen(RTLD_NOW)`), dropped it into an Anki 26.05 bundle, and typed Chinese and Japanese without the crash. ## Risk / compatibility / migration (optional) 1. Fixes `build-linux-x86` only. `build-linux-arm-installer` installs a prebuilt `fcitx5-frontend-qt6` from apt, built against Ubuntu's Qt rather than the bundled 6.11.0 — the same class of mismatch. I have no arm hardware to confirm or fix it, so it's left for a follow-up. 2. Adds an `aqtinstall` step (~1.5 GB Qt download) to the Linux release build. 3. **Not validated on CI.** `release.yml` is `workflow_dispatch`-only, so an external contributor can't run it; only its build step is reproduced locally (see How to test). **Please confirm it in a real release build.** ## UI evidence (required for visual changes; otherwise N/A) N/A ## Scope - [x] This PR is focused on one change (no unrelated edits). |
||
|
|
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). |
||
|
|
3c9f2f7dff |
fix: clean up FindDuplicatesDialog's webview on close to prevent theme-change crash (#5143)
## Linked issue (required) Fixes #5140 ## Summary / motivation (required) `FindDuplicatesDialog` never called `AnkiWebView.cleanup()` on close, unlike the other dialogs that embed a webview (`EmptyCardsDialog`, `CardInfoDialog`, `NewDeckStats`), which all do. Without it, the webview's `on_theme_did_change` stays registered in `gui_hooks.theme_did_change` after Qt destroys the underlying C++ widget, so a theme change shortly after close crashes with `RuntimeError: wrapped C/C++ object of type FindDupesWebView has been deleted`. ## Steps to reproduce 1. Open Find Duplicates (Notes menu → Find Duplicates). 2. Close the dialog. 3. Trigger a theme change (toggle system light/dark mode). 4. Anki crashes with the RuntimeError above. ## How to test Reproduced the crash via the steps above. No automated test included - this change just applies the same `.cleanup()` call the three sibling dialogs already make in their `finished`/`reject` handler. `./ninja check` passes. ### 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. (same pattern as EmptyCardsDialog/CardInfoDialog/NewDeckStats - see above) ## Before / after behavior Before: closing Find Duplicates then triggering a theme change could crash Anki. After: the dialog cleans up its webview like its sibling dialogs, so the same theme change no longer touches a deleted object. ## Risk / compatibility / migration None - additive cleanup call, same pattern already used elsewhere in the codebase. ## UI evidence N/A - no visual change. ## Scope - [x] This PR is focused on one change (no unrelated edits). |
||
|
|
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). |
||
|
|
57eb9f49af |
fix: omit empty "Error details:" line from network errors (#5120)
<!-- 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 #5117 ## Summary / motivation (required) `NetworkError::message` always appended the `network-details` line, so an error with an empty `info` string rendered a dangling "Error details:" label with nothing after it. Only append the details when there is something to show. The message is built in the shared backend, so the empty label showed up on every client (it was reported on AnkiDroid) is still used unchanged when details are present. ## Steps to reproduce (required, use N/A if not applicable) Any network error ## How to test (required) Added tests ### Checklist (minimum) - [ ] 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 <!-- 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). |
||
|
|
b585d6a295 |
i18n: add core FTL strings for browse card-row status chips (#5111)
## Linked issue (required) Refs ankitects/ankimobile#10 (pt 1) ## Summary / motivation (required) Adds four new core FTL strings in `ftl/core/browsing.ftl` for AnkiMobile's redesigned browse card rows. AnkiMobile pt 1 still hardcodes a few user-visible and VoiceOver labels (leech chip, compact "now" due text, tag overflow, leech+lapse a11y). Compact due spans already reuse `scheduling-answer-button-time-*`; these are the remaining gaps called out in AnkiMobile PR #24. All keys are **new additions only** — no existing strings are changed, and nothing in desktop/core consumes them yet. Intended for AnkiMobile wiring first, with desktop and AnkiDroid able to reuse later. | Key | English | Use | |-----|---------|-----| | `browsing-leech` | Leech | Status chip label | | `browsing-due-now` | Now | Compact due text for overdue / due-today | | `browsing-more-tags` | 1 more tag / {count} more tags | VoiceOver for tag overflow chip | | `browsing-leech-lapses` | Leech, {count} lapse(s) | VoiceOver for leech badge with lapse count | Out of scope: `browsing-card-suspended-undo` (AnkiMobile pt 2, mobile-only for now). ## Steps to reproduce (required, use N/A if not applicable) N/A — new i18n keys only; no behavior change in this repo. ## How to test (required) ### 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 - Ran `./ninja rslib:i18n` to regenerate bindings. - Verified new keys appear in generated TypeScript/Python/Rust output (`browsingDueNow`, `browsingLeech`, `browsingMoreTags`, `browsingLeechLapses`). - No runtime callers added in this PR; AnkiMobile will wire these up after core lands. ## Before / after behavior (optional) N/A — strings are added to the template but unused until consumers adopt them. ## Risk / compatibility / migration (optional) Low risk. Additive-only FTL change; no breaking changes or migration needed. ## UI evidence (required for visual changes; otherwise N/A) N/A — no UI changes in this repo. ## Scope - [x] This PR is focused on one change (no unrelated edits). |
||
|
|
3186cc31c4 |
fix: detect empty cards when template uses special field conditionals (#4807)
## Linked issue (required) Fixes #4750 ## Summary / motivation (required) `new_cards_required_normal()` builds a set of non-empty fields from the note's actual fields, but only special-cases `Tags`. Other special fields like `Deck`, `Subdeck`, `Card`, `CardFlag`, `Type`, and `CardID` are always populated at render time but were missing from this set. This caused `{{^Deck}}` conditionals to be incorrectly evaluated as potentially rendering, making cards appear non-empty to the checker. The fix adds all always-present special fields to `nonempty_fields`. ## Steps to reproduce (required) 1. Create a Basic (and reversed card) note with content in both fields 2. Change Card 1's front template to `{{^Deck}}{{Back}}{{/Deck}}` 3. Observe the previewer shows a blank card 4. Run Tools → Check → Empty Cards — no empty cards are reported ## How to test (required) Run `./ninja check:pytest` — a new regression test `test_empty_cards_special_fields` in `pylib/tests/test_cards.py` covers this case. ### 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 New test verifies that a card with `{{^Deck}}{{Back}}{{/Deck}}` as its front template is correctly detected as empty. ## Before / after behavior (optional) Before: cards using special field conditionals like `{{^Deck}}` were not detected as empty. After: they are correctly flagged. ## Risk / compatibility / migration (optional) Low risk. Only affects empty card detection logic, not card rendering or scheduling. ## UI evidence (required for visual changes; otherwise N/A) N/A ## Scope - [x] This PR is focused on one change (no unrelated edits). |
||
|
|
9b5346a507 |
fix(bug): race condition (#4635)
Publish the media sync abort handle before spawning the worker, clear it with a scope guard, and return `CollectionNotOpen` instead of panicking if the collection is gone before the worker starts. This avoids a race where closing the collection during media sync startup could panic. |
||
|
|
7060797747 |
fix: follow Anki's language preference in webview and native form controls (#4935)
## Linked issue (required) Fixes #4823 ## Summary / motivation Webview form controls (e.g. `<input type="date">` picker) were rendered in the operating system's language instead of the language selected in Anki's preferences. The webview never receives Anki's chosen language, so its locale fell back to the system default. This change calls `QLocale.setDefault` passing Anki's resolved language in `setupLangAndBackend()` (`qt/aqt/__init__.py`), right after the language is applied, so locale aware controls follow the user's chosen language, as suggested in issue body. `setupLangAndBackend()` runs during Anki startup and is responsible for setting some language-related Qt state, such as QTranslator, therefore adding the QLocale default setting there is consistent with the existing responsibilities of the function. <img width="2070" height="1574" alt="image" src="https://github.com/user-attachments/assets/40c4a50d-1d39-48ac-b62b-e5db335e69db" /> ## Steps to reproduce 1. Set the operating system language to something other than current Anki lang (my OS is in portuguese) 2. Launch Anki, (e.g. `./run -l en -p teste`, -l is the language parameter) 3. Open a screen with a web date field (e.g. card type edit screen and add `<input type="date">`). 4. The picker's date format render in the system language, not in Anki's language. ## How to test 1. Launch Anki 2. Add > "Cards..." > Add a script like the one bellow to card type template field ```html <input type="date"> <pre id="loc"></pre> <script> document.getElementById('loc').textContent = "navigator.language = " + navigator.language + "\n" + "Intl locale = " + new Intl.DateTimeFormat().resolvedOptions().locale + "\n" + "toLocaleDateString = " + new Date().toLocaleDateString() + "\n" + "month name = " + new Intl.DateTimeFormat(undefined,{month:'long'}).format(new Date()); </script> ``` 3. Check if side preview language matches Anki's language. **You can also check QLocale language in debug console (ctrl+shift+;)** ```py import platform, anki.lang from aqt.qt import QLocale, QSysInfo from anki.utils import version_with_build print("Anki version :", version_with_build()) print("OS :", QSysInfo.prettyProductName(), platform.machine()) print("Anki lang :", anki.lang.current_lang) print("OS language :", QLocale.system().name()) print("QLocale def :", QLocale().name()) print("QLocale is equal to anki lang?", QLocale().name() == QLocale(anki.lang.current_lang).name()) ``` **Debug console script to check if all selectable languages in pylib/anki/lang.py:22-80 resolve to a valid QLocale** ```py import anki.lang from aqt.qt import QLocale # when the language is not valid, QLocale constructor falls back to QLocale.Language.C https://doc.qt.io/qt-6/qlocale.html print("fallback=", QLocale("not a valid language").name()) bad = [] for name, code in anki.lang.langs: disk = anki.lang.lang_to_disk_lang(code) qt_lang = disk.replace("-", "_") loc = QLocale(qt_lang) if loc.language() == QLocale.Language.C: # fallback bad.append((name, code, qt_lang)) print(f"{code:<8} -> {qt_lang:<7} -> {loc.name():<8} {loc.language().name}") n = len(anki.lang.langs) print(f"\ntotal={n} ok={n - len(bad)} fell_back_to_C={len(bad)}") print("FAILURES:", bad if bad else "none — all selectable langs resolve to a valid QLocale") ``` <img width="986" height="667" alt="image" src="https://github.com/user-attachments/assets/83a3bfee-1024-406e-a395-cdba30c61100" /> <img width="726" height="664" alt="image" src="https://github.com/user-attachments/assets/06ea79fe-a4e6-44e1-96b3-2baff8be1f96" /> ### Checklist - [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 change This is a trivial change (3 lines) in the Qt layer, and I chose not to add a test for two reasons: - Consistency: other Qt layer related stuff in setupLangAndBackend (such as QTranslator) is not currently tested, and nothing in the existing suite calls this function directly. - I could not find a place where a new test would be a natural fit. I considered adding something to test_i18n.py, but it did not seem aligned with the other tests ## Before (Anki language: Russian, system language: Brazilian Portuguese) <img width="1022" height="408" alt="image" src="https://github.com/user-attachments/assets/62f0af94-c7aa-461f-9a06-f3e42924d459" /> ## After (Anki language: Russian, system language: Brazilian Portuguese) <img width="1027" height="368" alt="image" src="https://github.com/user-attachments/assets/ac11c152-be16-431e-92f0-c7240a25ddda" /> ## Risk / compatibility / migration (optional) Low risk; The fix sets the app-wide default QLocale (via QLocale.setDefault) once at startup, to the language Anki already resolves. Language changes already require a restart (there's even a pop up asking for it), so there is no stale-locale edge. ## Scope - [x] This PR is focused on one change (no unrelated edits). |
||
|
|
8127fd2488 |
fix(fsrs): avoid interval regressions from fuzz (#4826)
## Linked issue (required) Fixes #4431 ## Summary / motivation (required) Fix an FSRS edge case where fuzz can make a `Good` interval go backwards, such as `4 -> 3`. The existing safeguard only handled cases where the newly rounded interval was already greater than the previous one. This change preserves the previous interval when it still falls inside the current fuzz range, and adds a regression test for the reported case. ## Steps to reproduce (required, use N/A if not applicable) 1. Start from an FSRS review state where the previous `Good` interval was fuzzed up from 3 days to 4 days. 2. Review the card again in a state where FSRS computes a new `Good` interval that rounds to 3 days but still has a fuzz range that includes 4. 3. Observe that the old logic schedules the next `Good` interval as 3 days. ## 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 - Ran `cargo test -p anki scheduler::states::review::test -- --nocapture` - Added a regression test for the reported `4 -> 3` case - Ran `./ninja check` successfully ## Before / after behavior (optional) Before: a card whose previous interval had been fuzzed up to 4 days could later show a `Good` interval of 3 days. After: when the previous interval still lies inside the current fuzz range, the interval is preserved instead of moving backwards. ## Risk / compatibility / migration (optional) Low risk, as the change is limited to FSRS passing review interval handling and no migration steps are required. ## UI evidence (required for visual changes; otherwise N/A) N/A ## Scope - [x] This PR is focused on one change (no unrelated edits). |
||
|
|
36213cb1a9 |
fix: set minimum height for deck options (#5062)
## Linked issue (required) Fixes #5061 ## Summary / motivation (required) This PR resolves a sizing issue with the options view on Hyprland and possibly other Wayland-based tiling window managers. ### Changes: **Minimum Height Constraint:** Adds `self.setMinimumHeight(500)` to `DeckOptionsDialog._setup_ui()`. This ensures that even when the webview is temporarily hidden on load (`self.web.hide_while_preserving_layout()`), tiling window managers do not collapse the dialog to an unusable height. ## Steps to reproduce (required, use N/A if not applicable) See #5061 ## 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. (The existing dialog geometry helpers cover this, and all Pytest/Aqt checks pass). ### Details Tested manually on a Linux setup running Hyprland: 1. **Minimum Size:** When opening the deck options, the window respects the minimum size constraints and opens at a readable size. <img width="1395" height="1375" alt="image" src="https://github.com/user-attachments/assets/963e9c46-ef7b-42f1-8b54-3a7f9a1473ce" /> ## Before / after behavior (optional) * **Before:** Window opened collapsed into a tiny horizontal box on tiling compositors under Wayland, and user-resized geometries were lost on Save/Cancel close events. <img width="1403" height="626" alt="image" src="https://github.com/user-attachments/assets/5cd02070-4563-4d73-9526-b86c8345649e" /> * **After:** Window is reliably opened at least 500px high, and user size adjustments are correctly saved on all close operations. <img width="1395" height="1375" alt="image" src="https://github.com/user-attachments/assets/963e9c46-ef7b-42f1-8b54-3a7f9a1473ce" /> ## Risk / compatibility / migration (optional) Low risk. A minimum height of 500px easily fits on all standard modern screens (including small laptops). The number may also be changed if this seems too large. ## UI evidence (required for visual changes; otherwise N/A) See screenshots above ## Scope - [x] This PR is focused on one change (no unrelated edits). |
||
|
|
c881800f77 |
Fix flaky scheduler test (#5030)
## Linked issue (required) Fixes #5025 ## Summary / motivation (required) `scheduler::answering::test::new_limited_by_reviews` in `rslib/src/scheduler/answering/mod.rs` is time-of-day dependent and fails when run between roughly 3:50–4:00 GMT. The source carried a `// FIXME: This fails between 3:50-4:00 GMT` comment documenting the flakiness. The test's final `answer_good()` schedules a learning card a 10-minute step ahead. Run shortly before the default 4am rollover, that step crosses into the next day, so the card is no longer counted as intraday learning and the final `assert_counts!(col, 0, 0, 0)` breaks. This guards the test with the same `if col.timing_today()?.near_cutoff() { return Ok(()); }` early-return that the sibling timing-sensitive tests in this file (`state_application`, `elapsed_secs`) already use, and replaces the FIXME with a comment explaining the cause. `near_cutoff()`'s 25-minute margin safely covers the 10-minute step window. ## Steps to reproduce (required, use N/A if not applicable) 1. Set the system clock to a few minutes before the collection's daily cutoff (the default 4am rollover — i.e. ~03:50–03:59 in the collection's timezone). 2. Run `cargo test -p anki --lib scheduler::answering::test::new_limited_by_reviews`. 3. The final `assert_counts!(col, 0, 0, 0)` fails because the last learning card's 10-minute step lands after the cutoff. ## How to test (required) ### Checklist (minimum) - [ ] 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 `cargo test -p anki --lib scheduler::answering::test::new_limited_by_reviews` passes, and the full `scheduler::answering` module passes (5/5). The fix mirrors the existing `near_cutoff()` guard already used by the two sibling timing-sensitive tests in the same file. ## Before / after behavior (optional) Before: the test fails non-deterministically in the ~10-minute window before the daily cutoff. After: it early-returns in the near-cutoff window (25-minute margin) and runs normally the rest of the day. ## Risk / compatibility / migration (optional) N/A — test-only change, no production behavior affected. ## 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: Fernando Lins <1887601+fernandolins@users.noreply.github.com> |
||
|
|
f57996576f |
test(pylib): add hook system tests (closes #4931) (#5023)
Closes #4931 ## Summary - Adds `pylib/tests/test_hooks.py` with full behavioral coverage of `pylib/anki/hooks.py` - Coverage increased from 35% → 100% (issue target was 80%) - Tests cover: hook registration/firing, `runFilter` chaining, `remHook`, exception propagation, and `wrap` (before/after/around) ## How to test ``` just test-py --coverage ``` --------- Co-authored-by: Fernando Lins <1887601+fernandolins@users.noreply.github.com> |
||
|
|
29a2853f1d |
fix: Bump rust builder and base image for syncserver (#5036)
## Linked issue (required) Fixes #5035 ## Summary / motivation (required) Rust builder is outdated an no longer builds the syncserver successfully. Bumping the version to match rust-toolchain.toml version 1.92.0 fixes this issue. I also bumped the base image to alpine:3.23 as alpine:3.21.0 is over a year old. ## Steps to reproduce (required, use N/A if not applicable) docker buildx build -f <Dockerfile> --no-cache --build-arg ANKI_VERSION=<version> -t anki-sync-server . ## How to test (required) docker buildx build -f <Dockerfile> --no-cache --build-arg ANKI_VERSION=<version> -t anki-sync-server . ### Checklist (minimum) - [x] I ran `docker buildx build -f Dockerfile --no-cache --build-arg ANKI_VERSION=26.05 -t anki-sync-server .` - [x] I ran `docker buildx build -f Dockerfile.distroless --no-cache --build-arg ANKI_VERSION=26.05 -t anki-sync-server .` ### Details Docker image compiled as expected following the updated versions. ## Scope - [x] This PR is focused addressing the syncserver docker image. Co-authored-by: Abdo <abdo@abdnh.net> |
||
|
|
daad13f01f |
fix: failing syncserver Docker build (#5021)
## Summary / motivation (required) This PR fixes Docker Image builds for syncserver failing due to a Rust version being used in both available Dockerfiles that is no longer supported by the two dependencies `time@0.3.47` and `time-core@0.1.8`. ## Issue Fixes #5022 ## Steps to reproduce (required, use N/A if not applicable) 1. `cd docs/syncserver` 2. `docker build -f Dockerfile --no-cache --build-arg ANKI_VERSION=26.05 -t anki-sync-server .` 3. Error (see issue) ## How to test (required) 1. `cd docs/syncserver` 2. `docker build -f Dockerfile --no-cache --build-arg ANKI_VERSION=26.05 -t anki-sync-server .` 3. run Docker image ## Scope - [x] This PR is focused on one change (no unrelated edits). Signed-off-by: Konstantin Tutsch <mail@konstantintutsch.com> |
||
|
|
9c72a8b828 |
feat: render sync server messages as rich text (#4560)
## Summary
- Pass `type="rich"` to `showText()` when displaying the sync server
message, enabling HTML rendering via `QTextBrowser.setHtml()`
- Plain-text messages continue to render identically — `setHtml()`
handles plain strings the same as `setPlainText()`
- Allows self-hosted sync servers to send formatted post-sync messages
(e.g. styled statistics, notices with links, tables)
## Context
The `/sync/meta` response includes a `msg` field that is displayed to
the user after sync. The display widget is already a `QTextBrowser`
(which supports Qt rich text/HTML), but `showText()` is called with the
default `type="text"`, routing through `setPlainText()`.
The `showText` helper already has full HTML support — it just needs
`type="rich"`:
```python
# qt/aqt/utils.py
text = QTextBrowser()
text.setOpenExternalLinks(True)
if type == "text":
text.setPlainText(txt) # current path
else:
text.setHtml(txt) # proposed path
```
**Security:** `QTextBrowser` does not execute JavaScript — only static
HTML/CSS. The sync server is explicitly configured and trusted by the
user.
|
||
|
|
e13092ef74 |
Replace print() logging in pylib with Python logging (#4666)
Use Python logging facilities instead of printing directly to stdout with print(). This prevents library consumers from being spammed with unwanted debug logs and stack traces. Closes #4665 --- My approach to the log levels is `warning` for any deprecation warning and `debug` for the "blocked main thread" messages. The pylib is used in the qt parts of the anki code. To the best of my understanding, logging is already correctly set up there and no adjustments are needed. The [issue I created](https://github.com/ankitects/anki/issues/4665) for this has a reproducer. With this change applied, the messages are gone when configuring the anki logger accordingly. ```python3 from anki.collection import Collection import logging logging.basicConfig(level=logging.DEBUG) logging.getLogger("anki").setLevel(logging.INFO) col = Collection("collection.anki2") auth = col.sync_login( username=r"<username>", password=r"<password>", endpoint="https://sync.ankiweb.net/", ) col.close() ``` setting the anki logger back to `DEBUG` makes the message appear again. Note that the format is only slightly different then previously. ```txt DEBUG:anki._backend:blocked main thread for 309ms Stack (most recent call last): File "/home/david/coding_stuff/anki/repro.py", line 8, in <module> auth = col.sync_login( File "/home/david/coding_stuff/anki/.venv/lib/python3.14/site-packages/anki/collection.py", line 1141, in sync_login return self._backend.sync_login( File "/home/david/coding_stuff/anki/.venv/lib/python3.14/site-packages/anki/_backend_generated.py", line 83, in sync_login raw_bytes = self._run_command(1, 3, message.SerializeToString()) File "/home/david/coding_stuff/anki/.venv/lib/python3.14/site-packages/anki/_backend.py", line 168, in _run_command logger.debug( ``` |
||
|
|
82eeda9d4f |
fix(export): preserve browser focus after note export (#4845)
## Linked issue (required) Fixes #4833 ## Summary / motivation (required) Keep the Browser window focused after exporting selected notes. The export dialog was opened from the Browser, but the background export progress and completion tooltip were still parented to the main window. That could leave the main screen focused after the export completed. This change carries the dialog parent through the export options and uses it for progress, errors, and the completion tooltip. ## Steps to reproduce (required, use N/A if not applicable) 1. Open the Browser. 2. Right-click a note and choose Notes > Export Notes. 3. Click Export, choose a save location, and save. 4. Observe that the main screen receives focus after export completion. ## 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 - Ran `./ninja check` successfully. - Tested behavior on macOS, however I currently don't have a Windows or Linux environment set up to test on right now as I'm on vacation. The change is limited to dialog parenting, so I expect it to work the same across platforms. Would appreciate testing on other platforms if possible. ## Before / after behavior (optional) Before: exporting selected notes from the Browser could focus the main window after completion. After: export progress and completion UI remain parented to the Browser, so focus is returned to the Browser. ## Risk / compatibility / migration (optional) Low risk. The change is limited to export UI parenting and falls back to the main window when no dialog parent is available. ## 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> |
||
|
|
1482fb0937 |
fix: atomic_rename now works with single component relative paths (#4773)
## Linked issue (required) [[<!-- Fixes #123 / Closes #123 / Refs #123 -->](https://forums.ankiweb.net/t/anki-collection-collection-export-anki-package-crashes-if-given-a-relative-path-with-one-component/69562)](https://forums.ankiweb.net/t/anki-collection-collection-export-anki-package-crashes-if-given-a-relative-path-with-one-component/69562) ## Summary / motivation (required) See discussion about this on [the forum](https://forums.ankiweb.net/t/anki-collection-collection-export-anki-package-crashes-if-given-a-relative-path-with-one-component/69562) ## Steps to reproduce (required, use N/A if not applicable) `demo.py`: ```python import tempfile from pathlib import Path import anki.collection with tempfile.TemporaryDirectory() as tempdir: tempdir = Path(tempdir) col = anki.collection.Collection(str(tempdir / "temp.anki2")) col.export_anki_package( out_path="output.apkg", options=anki.collection.ExportAnkiPackageOptions(), limit=None, ) ``` ```console $ python demo.py Traceback (most recent call last): [... some noise elided here ...] File "/nix/store/1w7swjzmjp7171yspih1q07a9vgacjzb-dev-env/lib/python3.14/site-packages/anki/_backend_generated.py", line 2037, in export_anki_package raw_bytes = self._run_command(37, 4, message.SerializeToString()) File "/nix/store/1w7swjzmjp7171yspih1q07a9vgacjzb-dev-env/lib/python3.14/site-packages/anki/_backend.py", line 171, in _run_command raise backend_exception_to_pylib(err) anki.errors.BackendIOError: Failed to open '': No such file or directory (os error 2) ``` ## How to test (required) I added a unit test. It passes. ### Checklist (minimum) - [ ] 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 <!-- 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). --------- Co-authored-by: Abdo <abdo@abdnh.net> |
||
|
|
a2262809c4 |
fix: regex error fix (#4837)
<!-- 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 #4835 ## Summary / motivation (required) <!-- What this PR does and why. For larger changes, add enough context for reviewers. --> Fixes the issue #4835 with a minor change in InvalidRegex error. ## 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 browser and do a find & replace. 2. Put b[ in “Find”. 3. Enable the regex option. 4. Confirm. ## How to test (required) <!--- How to test: how you verified the change (checks, unit tests, manual steps, edge cases — the "after" or general validation). ---> Reproduce the fix and verify the regex error is not within the `<pre>` tag. ### 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 Regex error was return with `<pre>` tags which are removed now so that the text in the error box is displayed correctly. ## Before / after behavior (optional) <img width="372" height="211" alt="image" src="https://github.com/user-attachments/assets/91f53745-301b-4679-b1a5-53fafd628de7" /> <!-- 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) <img width="360" height="226" alt="image" src="https://github.com/user-attachments/assets/3b7b3f23-9f35-423e-9b10-62834e0a0dd6" /> <!-- Screenshot or short video --> ## Scope - [x] This PR is focused on one change (no unrelated edits). --------- Co-authored-by: Abdo <abdo@abdnh.net> |
||
|
|
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 |
||
|
|
2ec326585c |
fix: use correct type annotation on Collection.add_notes (#4758)
## Linked issue (required) This is so minor I opted not to create an issue first. Hope that's ok! ## Summary / motivation (required) `add_notes` loops over the given `requests` 3 times. The previous type annotation (`Iterable`) allows for things like generators, which can only be iterated over one time. ## Steps to reproduce (required, use N/A if not applicable) N/A ## How to test (required) Advice appreciated here! ### 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 N/A ## Before / after behavior (optional) N/A ## Risk / compatibility / migration (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). |
||
|
|
d051be6290 |
fix: contains_cloze_replacement now checks nested conditionals (#4581) (#4651)
Fixes #4581 `contains_cloze_replacement()` was doing a shallow check, missing cloze fields nested inside conditionals. Delegating to `all_referenced_cloze_field_names()` fixes the inconsistency with `contains_field_replacement()` |
||
|
|
81eefc5f71 |
Render GH Markdown alerts in Sphinx docs (#4663)
See https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts We started using this syntax recently: #4466 #4662 ## How to test See preview: https://anki-core--4663.org.readthedocs.build/en/4663/windows.html |
||
|
|
65f45ea87d |
Update docker build command for syncserver (#4562)
The command for building the image does not include the` --platform` flag which prevents the image from running across all architectures. For example, if I build the image on an ARM system and then try running on x86, it won’t work. This issue can be fixed by using `docker buildx` and adding the flag to include all of the platforms. I have tested this by building the image with the `linux/arm64` and `linux/amd64` platform flags on an ARM system and then running a container with that image on an x86 system. This would be useful in scenarios where the syncserver runs on devices that cannot do builds. The correct command would be ```bash # Builds for all existing platforms supported by Docker docker buildx build -f <Dockerfile> --platform linux/amd64,linux/arm64,windows/amd64 --no-cache --build-arg ANKI_VERSION=<version> -t anki-sync-server . ``` Reference: https://docs.docker.com/build/building/multi-platform/ |
||
|
|
64386cab63 |
fix: clipboard image paste produces [sound:] tag instead of <img> (#4668)
## Summary - Fix regression from #4474 where pasting clipboard images into the editor produces `[sound:filename.png]` instead of `<img src="filename.png">` - The `pics` tuple in `editor.py` had comma-joined strings like `"png,PNG"` instead of separate entries `"png", "PNG"`, causing `ext in pics` to return `False` for png, gif, svg, and ico extensions ## Test plan - [x] Copy an image to clipboard, paste into editor field — confirms `<img>` tag is produced instead of `[sound:]` - [x] Verified build succeeds with `./ninja wheels` --------- Signed-off-by: Christos Longros <chris.longros@gmail.com> |
||
|
|
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 |
||
|
|
3ecde15621 |
Add Windows quick start guide for new contributors (#4466)
This pull request adds a beginner-friendly quick start guide for building Anki on Windows. The guide is intended to help first-time contributors set up their development environment more easily, without replacing or modifying the existing official documentation. No existing files were changed; this contribution only adds a complementary Markdown document. --------- Co-authored-by: user1823 <92206575+user1823@users.noreply.github.com> |
||
|
|
f8669fc4be | Add punctuation, convert raw URLs to markdown links in READMEs (#4570) | ||
|
|
831ca06bc6 |
Accept images with capitalized extensions. (#4474)
When using the "Attach pictures/audio/video" button in the "Browse" window, files with a capitalized extension (e.g. `image.JPG`) aren't selectable. Add a variable `pics_uppercase` with the extensions in upper case to be used when selecting a media. I modified only the places where pics (the list of supported extensions) was used with the funciton "getFile": on the other places, the text is converted to lowercase before comparing. I did only handle the pictures, because I don't think I ever saw a audio file with a capitalized extension, while for pictures it is very common (1/3 of the pictures I downloaded for my deck where ".JPG"). |
||
|
|
1340a12622 |
Wrap previewer type field syntax replacement in separate method (#4563)
It is currently difficult to modify how the previewer handles type-in-the-answer fields because the substitution is done as a very tiny line within the large `_render_scheduled` method. This PR adds a `type_ans_preview_filter` method that can easily be patched without affecting the entire renderer. |